codebyplan 1.13.53 → 1.13.55

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 +1364 -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 +58 -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.55";
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,610 @@ 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 markUncovered(dep, exactVersion, ctx) {
35481
+ console.log(
35482
+ ` uncovered ${dep.name}@${exactVersion || "?"} \u2014 no docs in the library mirror`
35483
+ );
35484
+ const libPath = join37(ctx.absDir, libDirName(dep.name));
35485
+ if (existsSync13(libPath)) {
35486
+ if (ctx.dryRun) {
35487
+ console.log(` would remove stale mirror dir ${libPath}`);
35488
+ } else {
35489
+ await rm2(libPath, { recursive: true, force: true });
35490
+ console.log(` removed stale mirror dir for ${dep.name}`);
35491
+ }
35492
+ }
35493
+ return { kind: "uncovered", dep, exactVersion };
35494
+ }
35495
+ async function syncOneLibrary(dep, ctx) {
35496
+ const exactVersion = await resolveExactVersion(ctx.projectPath, dep);
35497
+ let manifest;
35498
+ try {
35499
+ const res = await apiGet(
35500
+ "/library-docs/export/manifest",
35501
+ {
35502
+ slug: dep.name,
35503
+ ...exactVersion !== "" ? { version: exactVersion } : {}
35504
+ }
35505
+ );
35506
+ manifest = res.data;
35507
+ } catch (err) {
35508
+ if (err instanceof ApiError && err.status === 404) {
35509
+ return markUncovered(dep, exactVersion, ctx);
35510
+ }
35511
+ const message = err instanceof Error ? err.message : String(err);
35512
+ console.warn(
35513
+ ` Warning: docs sync failed for ${dep.name}@${exactVersion || "?"}: ${message}`
35514
+ );
35515
+ return { kind: "failed", dep, exactVersion, message };
35516
+ }
35517
+ if (!manifest || !Array.isArray(manifest.files) || manifest.files.length === 0) {
35518
+ return markUncovered(dep, exactVersion, ctx);
35519
+ }
35520
+ const unsafeCount = manifest.files.filter(
35521
+ (f) => sanitizeDocPath(f.path) === null
35522
+ ).length;
35523
+ if (unsafeCount > 0) {
35524
+ const sample = manifest.files.find((f) => sanitizeDocPath(f.path) === null);
35525
+ console.warn(
35526
+ ` Warning: ${dep.name}: dropping ${unsafeCount} unsafe doc path(s) from manifest (e.g. ${JSON.stringify(sample?.path)})`
35527
+ );
35528
+ manifest = {
35529
+ ...manifest,
35530
+ files: manifest.files.filter((f) => sanitizeDocPath(f.path) !== null)
35531
+ };
35532
+ if (manifest.files.length === 0) {
35533
+ return markUncovered(dep, exactVersion, ctx);
35534
+ }
35535
+ }
35536
+ const lockEntry = ctx.lock.libraries[dep.name];
35537
+ const libPath = join37(ctx.absDir, libDirName(dep.name));
35538
+ const versionPath = join37(libPath, manifest.resolved_version);
35539
+ if (manifestMatchesLock(manifest, lockEntry)) {
35540
+ console.log(` unchanged ${dep.name}@${manifest.resolved_version}`);
35541
+ if (!ctx.dryRun && !existsSync13(join37(libPath, "INDEX.md"))) {
35542
+ await mkdir12(libPath, { recursive: true });
35543
+ await writeFile20(
35544
+ join37(libPath, "INDEX.md"),
35545
+ buildLibIndex(dep.name, manifest),
35546
+ "utf-8"
35547
+ );
35548
+ }
35549
+ return { kind: "unchanged", dep, exactVersion, manifest };
35550
+ }
35551
+ const fetched = await fetchAllFiles(manifest.version_id);
35552
+ const sameVersionLockFiles = lockEntry && lockEntry.resolved_version === manifest.resolved_version ? lockEntry.files : {};
35553
+ const manifestPaths = new Set(manifest.files.map((f) => f.path));
35554
+ let written = 0;
35555
+ for (const file of fetched) {
35556
+ if (!manifestPaths.has(file.path)) {
35557
+ continue;
35558
+ }
35559
+ const rel = sanitizeDocPath(file.path);
35560
+ if (rel === null) {
35561
+ console.warn(
35562
+ ` Warning: skipping unsafe doc path from ${dep.name}: ${JSON.stringify(file.path)}`
35563
+ );
35564
+ continue;
35565
+ }
35566
+ const target = join37(versionPath, rel);
35567
+ if (sameVersionLockFiles[file.path] === file.content_hash && existsSync13(target)) {
35568
+ continue;
35569
+ }
35570
+ written++;
35571
+ if (!ctx.dryRun) {
35572
+ await mkdir12(dirname13(target), { recursive: true });
35573
+ await writeFile20(target, file.content, "utf-8");
35574
+ }
35575
+ }
35576
+ let removedFiles = 0;
35577
+ if (lockEntry && lockEntry.resolved_version === manifest.resolved_version) {
35578
+ const manifestPaths2 = new Set(manifest.files.map((f) => f.path));
35579
+ for (const lockedPath of Object.keys(lockEntry.files)) {
35580
+ if (manifestPaths2.has(lockedPath)) continue;
35581
+ const rel = sanitizeDocPath(lockedPath);
35582
+ if (rel === null) continue;
35583
+ removedFiles++;
35584
+ if (!ctx.dryRun) await rm2(join37(versionPath, rel), { force: true });
35585
+ }
35586
+ }
35587
+ let removedVersionDirs = 0;
35588
+ let libEntries = [];
35589
+ try {
35590
+ libEntries = await readdir6(libPath, { withFileTypes: true });
35591
+ } catch {
35592
+ }
35593
+ for (const entry of libEntries) {
35594
+ if (!entry.isDirectory() || entry.name === manifest.resolved_version) {
35595
+ continue;
35596
+ }
35597
+ removedVersionDirs++;
35598
+ if (!ctx.dryRun) {
35599
+ await rm2(join37(libPath, entry.name), { recursive: true, force: true });
35600
+ }
35601
+ }
35602
+ if (!ctx.dryRun) {
35603
+ await mkdir12(libPath, { recursive: true });
35604
+ await writeFile20(
35605
+ join37(libPath, "INDEX.md"),
35606
+ buildLibIndex(dep.name, manifest),
35607
+ "utf-8"
35608
+ );
35609
+ }
35610
+ const removals = removedFiles + removedVersionDirs;
35611
+ console.log(
35612
+ 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)" : ""}`
35613
+ );
35614
+ return {
35615
+ kind: "synced",
35616
+ dep,
35617
+ exactVersion,
35618
+ manifest,
35619
+ written,
35620
+ removedFiles,
35621
+ removedVersionDirs
35622
+ };
35623
+ }
35624
+ async function runDocsSync() {
35625
+ const flags = parseFlags(4);
35626
+ const dryRun = hasFlag("dry-run", 4);
35627
+ await validateAuth();
35628
+ const config = await resolveConfig(flags);
35629
+ const { repoId, projectPath } = config;
35630
+ const { absDir, relDir } = await resolveDocsDir(projectPath, flags);
35631
+ console.log(`
35632
+ CodeByPlan Docs Sync`);
35633
+ console.log(` Repo: ${repoId}`);
35634
+ console.log(` Path: ${projectPath}`);
35635
+ console.log(` Dir: ${absDir}`);
35636
+ if (dryRun) console.log(` Mode: dry-run`);
35637
+ if (hasFlag("include-dev", 4)) {
35638
+ console.log(` Dev dependencies: included (already the default)`);
35639
+ }
35640
+ console.log();
35641
+ const started = Date.now();
35642
+ const { dependencies } = await scanAllDependencies(projectPath);
35643
+ const selected = selectDependencies(dependencies);
35644
+ if (selected.length === 0) {
35645
+ console.log(" No dependencies eligible for docs sync.\n");
35646
+ return;
35647
+ }
35648
+ console.log(` ${selected.length} dependencies after skip rules + dedup
35649
+ `);
35650
+ const lock = await readDocsLock(absDir);
35651
+ const ctx = { projectPath, absDir, lock, dryRun };
35652
+ const outcomes = await mapWithConcurrency(
35653
+ selected,
35654
+ SYNC_CONCURRENCY,
35655
+ (dep) => syncOneLibrary(dep, ctx)
35656
+ );
35657
+ if (!dryRun) {
35658
+ await mkdir12(absDir, { recursive: true });
35659
+ await writeFile20(join37(absDir, "INDEX.md"), buildTopIndex(outcomes), "utf-8");
35660
+ const libraries = {};
35661
+ for (const o of outcomes) {
35662
+ if (o.kind === "synced" || o.kind === "unchanged") {
35663
+ libraries[o.dep.name] = {
35664
+ version: o.exactVersion,
35665
+ resolved_version: o.manifest.resolved_version,
35666
+ resolution: o.manifest.resolution,
35667
+ files: Object.fromEntries(
35668
+ o.manifest.files.map((f) => [f.path, f.content_hash])
35669
+ )
35670
+ };
35671
+ } else if (o.kind === "failed") {
35672
+ const previous = lock.libraries[o.dep.name];
35673
+ if (previous) libraries[o.dep.name] = previous;
35674
+ }
35675
+ }
35676
+ const sortedLibraries = {};
35677
+ for (const name of Object.keys(libraries).sort()) {
35678
+ sortedLibraries[name] = libraries[name];
35679
+ }
35680
+ const newLock = {
35681
+ generated_at: (/* @__PURE__ */ new Date()).toISOString(),
35682
+ libraries: sortedLibraries
35683
+ };
35684
+ await writeFile20(
35685
+ join37(absDir, LOCK_FILE),
35686
+ JSON.stringify(newLock, null, 2) + "\n",
35687
+ "utf-8"
35688
+ );
35689
+ }
35690
+ if (relDir !== null) {
35691
+ const action = await ensureDocsGitignoreEntry(projectPath, relDir, dryRun);
35692
+ if (action === "added") {
35693
+ console.log(
35694
+ dryRun ? `
35695
+ would add /${relDir}/ to .gitignore` : `
35696
+ Added /${relDir}/ to .gitignore`
35697
+ );
35698
+ }
35699
+ }
35700
+ const synced = outcomes.filter((o) => o.kind === "synced").length;
35701
+ const unchanged = outcomes.filter((o) => o.kind === "unchanged").length;
35702
+ const uncovered = outcomes.filter((o) => o.kind === "uncovered").length;
35703
+ const failed = outcomes.filter((o) => o.kind === "failed").length;
35704
+ const thin = outcomes.filter(
35705
+ (o) => (o.kind === "synced" || o.kind === "unchanged") && isThin(o.manifest)
35706
+ ).length;
35707
+ const durationS = ((Date.now() - started) / 1e3).toFixed(1);
35708
+ if (failed > 0) {
35709
+ console.warn(` Warning: ${failed} libraries failed to sync (see above).`);
35710
+ }
35711
+ console.log(
35712
+ `
35713
+ Docs sync ${dryRun ? "plan " : ""}complete: ${synced} synced, ${unchanged} unchanged, ${uncovered} uncovered, ${thin} thin (${durationS}s).
35714
+ `
35715
+ );
35716
+ }
35717
+ async function countFilesRecursively(dirPath) {
35718
+ let entries;
35719
+ try {
35720
+ entries = await readdir6(dirPath, { withFileTypes: true });
35721
+ } catch {
35722
+ return 0;
35723
+ }
35724
+ let count = 0;
35725
+ for (const entry of entries) {
35726
+ if (entry.isDirectory()) {
35727
+ count += await countFilesRecursively(join37(dirPath, entry.name));
35728
+ } else if (entry.isFile()) {
35729
+ count++;
35730
+ }
35731
+ }
35732
+ return count;
35733
+ }
35734
+ async function runDocsStatus() {
35735
+ const flags = parseFlags(4);
35736
+ const projectPath = flags["path"] ?? process.cwd();
35737
+ const { absDir } = await resolveDocsDir(projectPath, flags);
35738
+ console.log(`
35739
+ CodeByPlan Docs Status`);
35740
+ console.log(` Dir: ${absDir}
35741
+ `);
35742
+ let raw;
35743
+ try {
35744
+ raw = await readFile26(join37(absDir, LOCK_FILE), "utf-8");
35745
+ } catch {
35746
+ console.log(
35747
+ ` No ${LOCK_FILE} found \u2014 run \`codebyplan docs sync\` first.
35748
+ `
35749
+ );
35750
+ return;
35751
+ }
35752
+ let lock;
35753
+ try {
35754
+ lock = JSON.parse(raw);
35755
+ if (lock === null || typeof lock.libraries !== "object") {
35756
+ throw new Error("malformed lock");
35757
+ }
35758
+ } catch {
35759
+ console.warn(
35760
+ ` Warning: corrupt ${LOCK_FILE} \u2014 run \`codebyplan docs sync\` to regenerate.
35761
+ `
35762
+ );
35763
+ return;
35764
+ }
35765
+ const names = Object.keys(lock.libraries).sort();
35766
+ if (names.length === 0) {
35767
+ console.log(" Lock contains no libraries.\n");
35768
+ return;
35769
+ }
35770
+ let outOfSync = 0;
35771
+ for (const name of names) {
35772
+ const entry = lock.libraries[name];
35773
+ const versionPath = join37(absDir, libDirName(name), entry.resolved_version);
35774
+ const expected = Object.keys(entry.files).length;
35775
+ if (!existsSync13(versionPath)) {
35776
+ outOfSync++;
35777
+ console.log(
35778
+ ` ${name}@${entry.resolved_version} \u2014 MISSING dir (${expected} files in lock)`
35779
+ );
35780
+ continue;
35781
+ }
35782
+ const onDisk = await countFilesRecursively(versionPath);
35783
+ if (onDisk === expected) {
35784
+ console.log(` ${name}@${entry.resolved_version} \u2014 ok (${onDisk} files)`);
35785
+ } else {
35786
+ outOfSync++;
35787
+ console.log(
35788
+ ` ${name}@${entry.resolved_version} \u2014 MISMATCH (${onDisk} files on disk / ${expected} in lock)`
35789
+ );
35790
+ }
35791
+ }
35792
+ console.log(
35793
+ outOfSync === 0 ? `
35794
+ ${names.length} libraries in sync.
35795
+ ` : `
35796
+ ${outOfSync} of ${names.length} libraries out of sync \u2014 run \`codebyplan docs sync\`.
35797
+ `
35798
+ );
35799
+ }
35800
+ function printUsage() {
35801
+ console.log(`
35802
+ codebyplan docs
35803
+
35804
+ Local dependency docs mirror \u2014 sync covered library docs from CodeByPlan.
35805
+
35806
+ Subcommands:
35807
+ sync Fetch doc files for installed dependencies into the local mirror
35808
+ status Offline report of lock vs on-disk mirror state (no network)
35809
+
35810
+ Flags (sync):
35811
+ --path <dir> Project root directory (default: cwd)
35812
+ --dir <dir> Mirror directory (default: .codebyplan/vendor.json
35813
+ vendor_docs_path, else ${DEFAULT_DOCS_DIR})
35814
+ --include-dev Explicitly include dev dependencies (already the default)
35815
+ --dry-run Print the full sync plan without writing any files
35816
+
35817
+ Flags (status):
35818
+ --path <dir> Project root directory (default: cwd)
35819
+ --dir <dir> Mirror directory (same resolution as sync)
35820
+ `);
35821
+ }
35822
+ async function runDocs() {
35823
+ const subcommand = process.argv[3];
35824
+ if (subcommand === "sync") {
35825
+ await runDocsSync();
35826
+ return;
35827
+ }
35828
+ if (subcommand === "status") {
35829
+ await runDocsStatus();
35830
+ return;
35831
+ }
35832
+ if (subcommand === void 0 || subcommand === "help" || subcommand === "--help" || subcommand === "-h") {
35833
+ printUsage();
35834
+ return;
35835
+ }
35836
+ console.error(
35837
+ ` docs: unknown subcommand '${subcommand}'. Run 'codebyplan docs help' for usage.`
35838
+ );
35839
+ process.exitCode = 1;
35840
+ }
35841
+ var DEFAULT_DOCS_DIR, LOCK_FILE, SYNC_CONCURRENCY, FILES_PAGE_LIMIT, THIN_MIN_CHUNKS, THIN_MIN_FILES, GITIGNORE_COMMENT, SKIP_VERSION_RE;
35842
+ var init_docs = __esm({
35843
+ "src/cli/docs.ts"() {
35844
+ "use strict";
35845
+ init_flags();
35846
+ init_api();
35847
+ init_tech_detect();
35848
+ DEFAULT_DOCS_DIR = "docs/dependencies";
35849
+ LOCK_FILE = "docs.lock.json";
35850
+ SYNC_CONCURRENCY = 5;
35851
+ FILES_PAGE_LIMIT = 200;
35852
+ THIN_MIN_CHUNKS = 25;
35853
+ THIN_MIN_FILES = 5;
35854
+ GITIGNORE_COMMENT = "# DocsByPlan local mirror";
35855
+ SKIP_VERSION_RE = /^(workspace:|file:|link:|git\+|https?:)/;
35856
+ }
35857
+ });
35858
+
34908
35859
  // src/lib/check-baseline.ts
34909
35860
  import { readFileSync as readFileSync12, writeFileSync as writeFileSync7 } from "node:fs";
34910
- import { join as join37 } from "node:path";
35861
+ import { join as join38 } from "node:path";
34911
35862
  function emptyBaseline() {
34912
35863
  return {
34913
35864
  lint: { known_failing: [] },
@@ -34917,7 +35868,7 @@ function emptyBaseline() {
34917
35868
  };
34918
35869
  }
34919
35870
  function loadBaseline(projectRoot) {
34920
- const filePath = join37(projectRoot, BASELINE_FILENAME);
35871
+ const filePath = join38(projectRoot, BASELINE_FILENAME);
34921
35872
  try {
34922
35873
  const raw = readFileSync12(filePath, "utf-8");
34923
35874
  const parsed = JSON.parse(raw);
@@ -34941,7 +35892,7 @@ function loadBaseline(projectRoot) {
34941
35892
  }
34942
35893
  }
34943
35894
  function saveBaseline(projectRoot, baseline) {
34944
- const filePath = join37(projectRoot, BASELINE_FILENAME);
35895
+ const filePath = join38(projectRoot, BASELINE_FILENAME);
34945
35896
  writeFileSync7(filePath, JSON.stringify(baseline, null, 2) + "\n", "utf-8");
34946
35897
  }
34947
35898
  function diffBaseline(check, currentFailingPackages, baseline) {
@@ -34990,8 +35941,8 @@ var init_check_baseline = __esm({
34990
35941
  });
34991
35942
 
34992
35943
  // src/lib/check.ts
34993
- import { readFileSync as readFileSync13, existsSync as existsSync13 } from "node:fs";
34994
- import { join as join38 } from "node:path";
35944
+ import { readFileSync as readFileSync13, existsSync as existsSync14 } from "node:fs";
35945
+ import { join as join39 } from "node:path";
34995
35946
  import { spawnSync as spawnSync10 } from "node:child_process";
34996
35947
  function hasSentinelValue(arrays) {
34997
35948
  const SENTINELS = /* @__PURE__ */ new Set([
@@ -35001,6 +35952,11 @@ function hasSentinelValue(arrays) {
35001
35952
  ]);
35002
35953
  return arrays.some((arr) => arr.some((v) => SENTINELS.has(v)));
35003
35954
  }
35955
+ function resolveNewFailures(check, failingPackages, baseline, updateBaseline, noBaseline) {
35956
+ if (updateBaseline) return [];
35957
+ if (noBaseline) return failingPackages;
35958
+ return diffBaseline(check, failingPackages, baseline);
35959
+ }
35004
35960
  function defaultSpawnFn(command, opts) {
35005
35961
  const result = spawnSync10(command, {
35006
35962
  shell: true,
@@ -35054,9 +36010,9 @@ function parseFailingPackagesFromSummary(summaryPath) {
35054
36010
  return Array.from(failing).sort();
35055
36011
  }
35056
36012
  function resolveTurboBin(projectRoot) {
35057
- const localBin = join38(projectRoot, "node_modules", ".bin", "turbo");
35058
- if (existsSync13(localBin)) return localBin;
35059
- const workspaceRootBin = join38(
36013
+ const localBin = join39(projectRoot, "node_modules", ".bin", "turbo");
36014
+ if (existsSync14(localBin)) return localBin;
36015
+ const workspaceRootBin = join39(
35060
36016
  projectRoot,
35061
36017
  "..",
35062
36018
  "..",
@@ -35064,7 +36020,7 @@ function resolveTurboBin(projectRoot) {
35064
36020
  ".bin",
35065
36021
  "turbo"
35066
36022
  );
35067
- if (existsSync13(workspaceRootBin)) return workspaceRootBin;
36023
+ if (existsSync14(workspaceRootBin)) return workspaceRootBin;
35068
36024
  return TURBO_NOT_FOUND_SENTINEL;
35069
36025
  }
35070
36026
  function runTurboWithSummary(task, projectRoot, spawnFn) {
@@ -35101,13 +36057,20 @@ function runCheck(opts) {
35101
36057
  projectRoot = process.cwd(),
35102
36058
  changedFiles,
35103
36059
  spawnFn = defaultSpawnFn,
35104
- updateBaseline = false,
36060
+ updateBaseline: updateBaselineOpt = false,
36061
+ noBaseline = false,
35105
36062
  loadBaselineFn = loadBaseline,
35106
36063
  saveBaselineFn = saveBaseline
35107
36064
  } = opts;
35108
36065
  if (changedFiles !== void 0) {
35109
36066
  process.stderr.write("check: --files is ignored in whole-repo mode\n");
35110
36067
  }
36068
+ if (noBaseline && updateBaselineOpt) {
36069
+ process.stderr.write(
36070
+ "check: --no-baseline and --update-baseline are mutually exclusive \u2014 ignoring --update-baseline (strict mode never writes the baseline)\n"
36071
+ );
36072
+ }
36073
+ const updateBaseline = noBaseline ? false : updateBaselineOpt;
35111
36074
  const baseline = loadBaselineFn(projectRoot);
35112
36075
  const results = [];
35113
36076
  {
@@ -35146,7 +36109,13 @@ function runCheck(opts) {
35146
36109
  command: lintCommand
35147
36110
  } = runTurboWithSummary("lint", projectRoot, spawnFn);
35148
36111
  currentFailing.lint = failingPackages;
35149
- const newFailures = updateBaseline ? [] : diffBaseline("lint", failingPackages, baseline);
36112
+ const newFailures = resolveNewFailures(
36113
+ "lint",
36114
+ failingPackages,
36115
+ baseline,
36116
+ updateBaseline,
36117
+ noBaseline
36118
+ );
35150
36119
  results.push({
35151
36120
  check: "lint",
35152
36121
  status: newFailures.length > 0 ? "fail" : "pass",
@@ -35165,7 +36134,13 @@ function runCheck(opts) {
35165
36134
  command: typecheckCommand
35166
36135
  } = runTurboWithSummary("typecheck", projectRoot, spawnFn);
35167
36136
  currentFailing.typecheck = failingPackages;
35168
- const newFailures = updateBaseline ? [] : diffBaseline("typecheck", failingPackages, baseline);
36137
+ const newFailures = resolveNewFailures(
36138
+ "typecheck",
36139
+ failingPackages,
36140
+ baseline,
36141
+ updateBaseline,
36142
+ noBaseline
36143
+ );
35169
36144
  results.push({
35170
36145
  check: "typecheck",
35171
36146
  status: newFailures.length > 0 ? "fail" : "pass",
@@ -35184,7 +36159,13 @@ function runCheck(opts) {
35184
36159
  command: testsCommand
35185
36160
  } = runTurboWithSummary("test", projectRoot, spawnFn);
35186
36161
  currentFailing.tests = failingPackages;
35187
- const newFailures = updateBaseline ? [] : diffBaseline("tests", failingPackages, baseline);
36162
+ const newFailures = resolveNewFailures(
36163
+ "tests",
36164
+ failingPackages,
36165
+ baseline,
36166
+ updateBaseline,
36167
+ noBaseline
36168
+ );
35188
36169
  results.push({
35189
36170
  check: "tests",
35190
36171
  status: newFailures.length > 0 ? "fail" : "pass",
@@ -35210,7 +36191,14 @@ function runCheck(opts) {
35210
36191
  };
35211
36192
  }
35212
36193
  const currentGhsaIds = parseAuditJson(result.stdout);
35213
- const newAdvisories = updateBaseline ? [] : diffAudit(currentGhsaIds, baseline.audit.ghsa_ids);
36194
+ let newAdvisories;
36195
+ if (updateBaseline) {
36196
+ newAdvisories = [];
36197
+ } else if (noBaseline) {
36198
+ newAdvisories = currentGhsaIds;
36199
+ } else {
36200
+ newAdvisories = diffAudit(currentGhsaIds, baseline.audit.ghsa_ids);
36201
+ }
35214
36202
  results.push({
35215
36203
  check: "audit",
35216
36204
  status: newAdvisories.length > 0 ? "fail" : "pass",
@@ -35346,7 +36334,7 @@ function runCheck(opts) {
35346
36334
  }
35347
36335
  const hard_fail_checks = results.filter((r) => r.status === "fail").map((r) => r.check);
35348
36336
  const any_failed = hard_fail_checks.length > 0;
35349
- return { results, any_failed, hard_fail_checks };
36337
+ return { results, any_failed, hard_fail_checks, no_baseline: noBaseline };
35350
36338
  }
35351
36339
  var SUMMARY_UNREADABLE, SPAWN_KILLED, TURBO_NOT_FOUND_SENTINEL;
35352
36340
  var init_check = __esm({
@@ -35369,6 +36357,7 @@ function parseCheckArgs(args) {
35369
36357
  let json = false;
35370
36358
  let files;
35371
36359
  let updateBaseline = false;
36360
+ let noBaseline = false;
35372
36361
  for (let i = 0; i < args.length; i++) {
35373
36362
  const arg = args[i];
35374
36363
  if (arg === "--scope") {
@@ -35388,6 +36377,8 @@ function parseCheckArgs(args) {
35388
36377
  json = true;
35389
36378
  } else if (arg === "--update-baseline") {
35390
36379
  updateBaseline = true;
36380
+ } else if (arg === "--no-baseline") {
36381
+ noBaseline = true;
35391
36382
  } else if (arg === "--files") {
35392
36383
  const val = args[i + 1];
35393
36384
  if (val !== void 0 && !val.startsWith("--")) {
@@ -35401,15 +36392,16 @@ function parseCheckArgs(args) {
35401
36392
  }
35402
36393
  }
35403
36394
  }
35404
- return { scope, json, files, updateBaseline };
36395
+ return { scope, json, files, updateBaseline, noBaseline };
35405
36396
  }
35406
36397
  function emitTable(result) {
36398
+ const strict = result.no_baseline === true;
35407
36399
  const headers = ["check", "status", "exit_code", "new_failures", "command"];
35408
36400
  const rows = result.results.map((r) => {
35409
36401
  let newFailuresCell = "";
35410
36402
  if (r.new_failures !== void 0) {
35411
36403
  if (r.new_failures.length === 0 && r.executed) {
35412
- newFailuresCell = r.exit_code !== null && r.exit_code !== 0 ? "(baselined)" : "";
36404
+ newFailuresCell = !strict && r.exit_code !== null && r.exit_code !== 0 ? "(baselined)" : "";
35413
36405
  } else {
35414
36406
  newFailuresCell = r.new_failures.join(", ");
35415
36407
  }
@@ -35436,6 +36428,11 @@ function emitTable(result) {
35436
36428
  );
35437
36429
  }
35438
36430
  lines.push("");
36431
+ if (strict) {
36432
+ lines.push(
36433
+ "MODE: strict (--no-baseline) \u2014 baseline ignored; every failure counts"
36434
+ );
36435
+ }
35439
36436
  if (result.any_failed) {
35440
36437
  lines.push(
35441
36438
  `FAILED: ${result.hard_fail_checks.join(", ")} (${result.hard_fail_checks.length} check(s) failed)`
@@ -35468,12 +36465,13 @@ function runCheckCommand(args) {
35468
36465
  if (parsed === null) {
35469
36466
  return;
35470
36467
  }
35471
- const { scope, json, files, updateBaseline } = parsed;
36468
+ const { scope, json, files, updateBaseline, noBaseline } = parsed;
35472
36469
  const result = runCheck({
35473
36470
  scope,
35474
36471
  changedFiles: files,
35475
36472
  // NO-OP in whole-repo mode; notice emitted by runCheck
35476
- updateBaseline
36473
+ updateBaseline,
36474
+ noBaseline
35477
36475
  });
35478
36476
  if (json) {
35479
36477
  process.stdout.write(JSON.stringify(result, null, 2) + "\n");
@@ -37116,11 +38114,11 @@ var generate_exports = {};
37116
38114
  __export(generate_exports, {
37117
38115
  runGenerate: () => runGenerate
37118
38116
  });
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";
38117
+ import { readFile as readFile27, mkdir as mkdir13, writeFile as writeFile21 } from "node:fs/promises";
38118
+ import { join as join46, resolve as resolve11 } from "node:path";
37121
38119
  async function readJsonFile4(filePath) {
37122
38120
  try {
37123
- const raw = await readFile26(filePath, "utf-8");
38121
+ const raw = await readFile27(filePath, "utf-8");
37124
38122
  return JSON.parse(raw);
37125
38123
  } catch {
37126
38124
  return null;
@@ -37128,7 +38126,7 @@ async function readJsonFile4(filePath) {
37128
38126
  }
37129
38127
  async function readPkgName(absPath) {
37130
38128
  try {
37131
- const raw = await readFile26(join45(absPath, "package.json"), "utf-8");
38129
+ const raw = await readFile27(join46(absPath, "package.json"), "utf-8");
37132
38130
  const pkg = JSON.parse(raw);
37133
38131
  return typeof pkg.name === "string" ? pkg.name : null;
37134
38132
  } catch {
@@ -37142,7 +38140,7 @@ async function runGenerate(opts) {
37142
38140
  const rootDir = resolve11(projectDir);
37143
38141
  let packageManager;
37144
38142
  try {
37145
- const raw = await readFile26(join45(rootDir, "package.json"), "utf-8");
38143
+ const raw = await readFile27(join46(rootDir, "package.json"), "utf-8");
37146
38144
  const pkg = JSON.parse(raw);
37147
38145
  if (typeof pkg.packageManager === "string") {
37148
38146
  packageManager = pkg.packageManager;
@@ -37150,7 +38148,7 @@ async function runGenerate(opts) {
37150
38148
  } catch {
37151
38149
  }
37152
38150
  const serverJson = await readJsonFile4(
37153
- join45(rootDir, ".codebyplan", "server.json")
38151
+ join46(rootDir, ".codebyplan", "server.json")
37154
38152
  );
37155
38153
  const ports = [];
37156
38154
  for (const alloc of serverJson?.port_allocations ?? []) {
@@ -37163,7 +38161,7 @@ async function runGenerate(opts) {
37163
38161
  }
37164
38162
  }
37165
38163
  const gitJson = await readJsonFile4(
37166
- join45(rootDir, ".codebyplan", "git.json")
38164
+ join46(rootDir, ".codebyplan", "git.json")
37167
38165
  );
37168
38166
  const branchModel = gitJson?.branch_config?.production ? {
37169
38167
  production: gitJson.branch_config.production,
@@ -37172,7 +38170,7 @@ async function runGenerate(opts) {
37172
38170
  )
37173
38171
  } : void 0;
37174
38172
  const shipmentJson = await readJsonFile4(
37175
- join45(rootDir, ".codebyplan", "shipment.json")
38173
+ join46(rootDir, ".codebyplan", "shipment.json")
37176
38174
  );
37177
38175
  const shipmentSurfaces = [];
37178
38176
  const rawSurfaces = shipmentJson?.shipment?.surfaces ?? shipmentJson?.surfaces ?? {};
@@ -37243,10 +38241,10 @@ async function runGenerate(opts) {
37243
38241
  const structureMdContent = generateStructureMd(config);
37244
38242
  const agentsContent = generateAgentsMd(structureMdContent);
37245
38243
  if (check) {
37246
- const agentsMdPath2 = join45(rootDir, "AGENTS.md");
38244
+ const agentsMdPath2 = join46(rootDir, "AGENTS.md");
37247
38245
  let existingAgents = null;
37248
38246
  try {
37249
- existingAgents = await readFile26(agentsMdPath2, "utf-8");
38247
+ existingAgents = await readFile27(agentsMdPath2, "utf-8");
37250
38248
  } catch {
37251
38249
  existingAgents = null;
37252
38250
  }
@@ -37282,16 +38280,16 @@ async function runGenerate(opts) {
37282
38280
  process.stdout.write(agentsContent);
37283
38281
  return;
37284
38282
  }
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");
38283
+ const outputDir = join46(rootDir, ".claude", "generated");
38284
+ await mkdir13(outputDir, { recursive: true });
38285
+ const outputPath = join46(outputDir, "structure.md");
38286
+ await writeFile21(outputPath, structureMdContent, "utf-8");
37289
38287
  process.stdout.write(`Wrote: .claude/generated/structure.md
37290
38288
  `);
37291
- const agentsMdPath = join45(rootDir, "AGENTS.md");
38289
+ const agentsMdPath = join46(rootDir, "AGENTS.md");
37292
38290
  let existingAgentsContent = null;
37293
38291
  try {
37294
- existingAgentsContent = await readFile26(agentsMdPath, "utf-8");
38292
+ existingAgentsContent = await readFile27(agentsMdPath, "utf-8");
37295
38293
  } catch {
37296
38294
  existingAgentsContent = null;
37297
38295
  }
@@ -37299,7 +38297,7 @@ async function runGenerate(opts) {
37299
38297
  process.stdout.write(`Up to date: AGENTS.md
37300
38298
  `);
37301
38299
  } else {
37302
- await writeFile20(agentsMdPath, agentsContent, "utf-8");
38300
+ await writeFile21(agentsMdPath, agentsContent, "utf-8");
37303
38301
  process.stdout.write(`Wrote: AGENTS.md
37304
38302
  `);
37305
38303
  }
@@ -37319,11 +38317,11 @@ __export(readme_exports, {
37319
38317
  runReadme: () => runReadme,
37320
38318
  runReadmeCommand: () => runReadmeCommand
37321
38319
  });
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";
38320
+ import { readFile as readFile28, writeFile as writeFile22 } from "node:fs/promises";
38321
+ import { join as join47, resolve as resolve12, relative as relative9 } from "node:path";
37324
38322
  async function readJsonFile5(filePath) {
37325
38323
  try {
37326
- const raw = await readFile27(filePath, "utf-8");
38324
+ const raw = await readFile28(filePath, "utf-8");
37327
38325
  return JSON.parse(raw);
37328
38326
  } catch {
37329
38327
  return null;
@@ -37392,7 +38390,7 @@ async function discoverUnits(rootDir, rootPkgJson) {
37392
38390
  const discovered = await discoverMonorepoApps(rootDir);
37393
38391
  for (const app of discovered) {
37394
38392
  const pkgJson = await readJsonFile5(
37395
- join46(app.absPath, "package.json")
38393
+ join47(app.absPath, "package.json")
37396
38394
  );
37397
38395
  pkgJsonByPath.set(app.absPath, pkgJson);
37398
38396
  allPackages.push({
@@ -37418,7 +38416,7 @@ async function runReadme(opts) {
37418
38416
  const init = opts.init ?? opts["init"] ?? false;
37419
38417
  const rootDir = resolve12(projectDir);
37420
38418
  const rootPkgJson = await readJsonFile5(
37421
- join46(rootDir, "package.json")
38419
+ join47(rootDir, "package.json")
37422
38420
  );
37423
38421
  const { units, allPackages, pkgJsonByPath } = await discoverUnits(
37424
38422
  rootDir,
@@ -37427,11 +38425,11 @@ async function runReadme(opts) {
37427
38425
  const driftUnits = [];
37428
38426
  const missingUnits = [];
37429
38427
  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");
38428
+ const readmePath = join47(unit.absPath, "README.md");
38429
+ const relPath = unit.isRoot ? "README.md" : join47(relative9(rootDir, unit.absPath), "README.md");
37432
38430
  let existingContent = null;
37433
38431
  try {
37434
- existingContent = await readFile27(readmePath, "utf-8");
38432
+ existingContent = await readFile28(readmePath, "utf-8");
37435
38433
  } catch {
37436
38434
  existingContent = null;
37437
38435
  }
@@ -37466,7 +38464,7 @@ ${newContent}
37466
38464
  `
37467
38465
  );
37468
38466
  } else {
37469
- await writeFile21(readmePath, newContent, "utf-8");
38467
+ await writeFile22(readmePath, newContent, "utf-8");
37470
38468
  process.stdout.write(`Wrote (scaffold): ${relPath}
37471
38469
  `);
37472
38470
  }
@@ -37504,7 +38502,7 @@ ${newContent}
37504
38502
  `
37505
38503
  );
37506
38504
  } else {
37507
- await writeFile21(readmePath, newContent, "utf-8");
38505
+ await writeFile22(readmePath, newContent, "utf-8");
37508
38506
  process.stdout.write(`Wrote (refresh): ${relPath}
37509
38507
  `);
37510
38508
  }
@@ -37523,7 +38521,7 @@ ${newContent}
37523
38521
  `
37524
38522
  );
37525
38523
  } else {
37526
- await writeFile21(readmePath, newContent, "utf-8");
38524
+ await writeFile22(readmePath, newContent, "utf-8");
37527
38525
  process.stdout.write(`Wrote (init): ${relPath}
37528
38526
  `);
37529
38527
  }
@@ -37611,15 +38609,15 @@ __export(migrate_memory_exports, {
37611
38609
  runMigrateMemory: () => runMigrateMemory
37612
38610
  });
37613
38611
  import {
37614
- readFile as readFile28,
37615
- writeFile as writeFile22,
37616
- mkdir as mkdir13,
38612
+ readFile as readFile29,
38613
+ writeFile as writeFile23,
38614
+ mkdir as mkdir14,
37617
38615
  unlink as unlink6,
37618
38616
  rmdir,
37619
- readdir as readdir6
38617
+ readdir as readdir7
37620
38618
  } 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";
38619
+ import { existsSync as existsSync21 } from "node:fs";
38620
+ import { join as join48, resolve as resolve13, dirname as dirname15, sep as sep4 } from "node:path";
37623
38621
  import { homedir as homedir8 } from "node:os";
37624
38622
  function encodeProjectPath(absPath) {
37625
38623
  return resolve13(absPath).replace(/[/\\]/g, "-");
@@ -37630,7 +38628,7 @@ function resolveAutoMemoryDir(opts) {
37630
38628
  }
37631
38629
  const projectDir = opts.projectDir ?? process.cwd();
37632
38630
  const encoded = encodeProjectPath(projectDir);
37633
- return join47(homedir8(), ".claude", "projects", encoded, "memory");
38631
+ return join48(homedir8(), ".claude", "projects", encoded, "memory");
37634
38632
  }
37635
38633
  function parseFrontmatter(content) {
37636
38634
  content = content.replace(/\r\n/g, "\n");
@@ -37689,17 +38687,17 @@ function parseFrontmatter(content) {
37689
38687
  async function inventoryFiles(dir) {
37690
38688
  let filenames;
37691
38689
  try {
37692
- const entries = await readdir6(dir);
38690
+ const entries = await readdir7(dir);
37693
38691
  filenames = entries.filter((f) => f.endsWith(".md") && f !== "MEMORY.md").sort();
37694
38692
  } catch {
37695
38693
  return [];
37696
38694
  }
37697
38695
  const results = [];
37698
38696
  for (const filename of filenames) {
37699
- const sourcePath = join47(dir, filename);
38697
+ const sourcePath = join48(dir, filename);
37700
38698
  let raw;
37701
38699
  try {
37702
- raw = await readFile28(sourcePath, "utf-8");
38700
+ raw = await readFile29(sourcePath, "utf-8");
37703
38701
  } catch (err) {
37704
38702
  const msg = err instanceof Error ? err.message : String(err);
37705
38703
  results.push({
@@ -37785,9 +38783,9 @@ async function applyPlan(plan, opts) {
37785
38783
  if (entry.suggested_action !== "keep") continue;
37786
38784
  if (!entry.suggested_target?.startsWith("nested:")) continue;
37787
38785
  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)) {
38786
+ const targetDir = resolve13(join48(projectDir, relPath));
38787
+ const targetFile = join48(targetDir, "CLAUDE.md");
38788
+ if (!targetDir.startsWith(resolve13(projectDir) + sep4)) {
37791
38789
  process.stderr.write(
37792
38790
  `migrate-memory: skipping unsafe suggested_target "${entry.suggested_target}" \u2014 resolves outside projectDir
37793
38791
  `
@@ -37806,7 +38804,7 @@ ${anchor}
37806
38804
  process.stdout.write(`[dry-run] Would create/append: ${targetFile}
37807
38805
  `);
37808
38806
  if (resolve13(entry.source_path).startsWith(
37809
- resolve13(plan.auto_memory_dir) + sep3
38807
+ resolve13(plan.auto_memory_dir) + sep4
37810
38808
  )) {
37811
38809
  process.stdout.write(
37812
38810
  `[dry-run] Would delete migrated keep source: ${entry.source_path}
@@ -37815,16 +38813,16 @@ ${anchor}
37815
38813
  }
37816
38814
  continue;
37817
38815
  }
37818
- await mkdir13(targetDir, { recursive: true });
38816
+ await mkdir14(targetDir, { recursive: true });
37819
38817
  let existing = "";
37820
38818
  try {
37821
- existing = await readFile28(targetFile, "utf-8");
38819
+ existing = await readFile29(targetFile, "utf-8");
37822
38820
  } catch {
37823
38821
  }
37824
38822
  if (!existing.includes(anchor)) {
37825
- await writeFile22(targetFile, existing + appendContent, "utf-8");
38823
+ await writeFile23(targetFile, existing + appendContent, "utf-8");
37826
38824
  }
37827
- if (resolve13(entry.source_path).startsWith(resolve13(plan.auto_memory_dir) + sep3)) {
38825
+ if (resolve13(entry.source_path).startsWith(resolve13(plan.auto_memory_dir) + sep4)) {
37828
38826
  try {
37829
38827
  await unlink6(entry.source_path);
37830
38828
  } catch {
@@ -37836,7 +38834,7 @@ ${anchor}
37836
38834
  );
37837
38835
  }
37838
38836
  }
37839
- const rootClaudeMd = join47(projectDir, ".claude", "CLAUDE.md");
38837
+ const rootClaudeMd = join48(projectDir, ".claude", "CLAUDE.md");
37840
38838
  if (dryRun) {
37841
38839
  process.stdout.write(
37842
38840
  `[dry-run] Would ensure ${rootClaudeMd} contains: ${IMPORT_LINE}
@@ -37845,12 +38843,12 @@ ${anchor}
37845
38843
  } else {
37846
38844
  let claudeMdContent = "";
37847
38845
  try {
37848
- claudeMdContent = await readFile28(rootClaudeMd, "utf-8");
38846
+ claudeMdContent = await readFile29(rootClaudeMd, "utf-8");
37849
38847
  } catch {
37850
- await mkdir13(dirname14(rootClaudeMd), { recursive: true });
38848
+ await mkdir14(dirname15(rootClaudeMd), { recursive: true });
37851
38849
  }
37852
38850
  if (!claudeMdContent.includes(IMPORT_LINE)) {
37853
- await writeFile22(
38851
+ await writeFile23(
37854
38852
  rootClaudeMd,
37855
38853
  claudeMdContent + `
37856
38854
  ${IMPORT_LINE}
@@ -37862,7 +38860,7 @@ ${IMPORT_LINE}
37862
38860
  for (const entry of plan.entries) {
37863
38861
  if (entry.suggested_action !== "drop") continue;
37864
38862
  if (!resolve13(entry.source_path).startsWith(
37865
- resolve13(plan.auto_memory_dir) + sep3
38863
+ resolve13(plan.auto_memory_dir) + sep4
37866
38864
  )) {
37867
38865
  process.stderr.write(
37868
38866
  `migrate-memory: skipping delete of "${entry.source_path}" \u2014 resolves outside auto_memory_dir
@@ -37880,13 +38878,13 @@ ${IMPORT_LINE}
37880
38878
  } catch {
37881
38879
  }
37882
38880
  }
37883
- const memoryMd = join47(plan.auto_memory_dir, "MEMORY.md");
37884
- const safeRmdirBase = join47(homedir8(), ".claude", "projects");
38881
+ const memoryMd = join48(plan.auto_memory_dir, "MEMORY.md");
38882
+ const safeRmdirBase = join48(homedir8(), ".claude", "projects");
37885
38883
  if (dryRun) {
37886
38884
  process.stdout.write(`[dry-run] Would delete MEMORY.md: ${memoryMd}
37887
38885
  `);
37888
38886
  } else {
37889
- if (resolve13(plan.auto_memory_dir).startsWith(safeRmdirBase + sep3)) {
38887
+ if (resolve13(plan.auto_memory_dir).startsWith(safeRmdirBase + sep4)) {
37890
38888
  try {
37891
38889
  await unlink6(memoryMd);
37892
38890
  } catch {
@@ -37904,14 +38902,14 @@ ${IMPORT_LINE}
37904
38902
  `
37905
38903
  );
37906
38904
  } else {
37907
- if (!resolve13(plan.auto_memory_dir).startsWith(safeRmdirBase + sep3)) {
38905
+ if (!resolve13(plan.auto_memory_dir).startsWith(safeRmdirBase + sep4)) {
37908
38906
  process.stderr.write(
37909
38907
  `migrate-memory: skipping rmdir of "${plan.auto_memory_dir}" \u2014 not under ~/.claude/projects
37910
38908
  `
37911
38909
  );
37912
38910
  } else {
37913
38911
  try {
37914
- const remaining = await readdir6(plan.auto_memory_dir);
38912
+ const remaining = await readdir7(plan.auto_memory_dir);
37915
38913
  if (remaining.length === 0) {
37916
38914
  await rmdir(plan.auto_memory_dir);
37917
38915
  }
@@ -37934,7 +38932,7 @@ async function runMigrateMemory(opts) {
37934
38932
  if (applyFile) {
37935
38933
  let planJson;
37936
38934
  try {
37937
- planJson = await readFile28(resolve13(applyFile), "utf-8");
38935
+ planJson = await readFile29(resolve13(applyFile), "utf-8");
37938
38936
  } catch (err) {
37939
38937
  const msg = err instanceof Error ? err.message : String(err);
37940
38938
  process.stderr.write(
@@ -37962,7 +38960,7 @@ async function runMigrateMemory(opts) {
37962
38960
  );
37963
38961
  return;
37964
38962
  }
37965
- if (!existsSync20(autoMemoryDir)) {
38963
+ if (!existsSync21(autoMemoryDir)) {
37966
38964
  process.stdout.write(
37967
38965
  JSON.stringify(
37968
38966
  {
@@ -38004,8 +39002,8 @@ var init_migrate_memory = __esm({
38004
39002
  });
38005
39003
 
38006
39004
  // 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";
39005
+ import { readdirSync as readdirSync7, readFileSync as readFileSync19, existsSync as existsSync22 } from "node:fs";
39006
+ import { join as join49, basename as basename2 } from "node:path";
38009
39007
  function parseFrontmatter2(content) {
38010
39008
  const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(content);
38011
39009
  if (!match) return {};
@@ -38082,20 +39080,20 @@ function auditSkill(filePath) {
38082
39080
  }
38083
39081
  function auditMode(templatesDir) {
38084
39082
  const entries = [];
38085
- const agentsDir = join48(templatesDir, "agents");
38086
- if (existsSync21(agentsDir)) {
39083
+ const agentsDir = join49(templatesDir, "agents");
39084
+ if (existsSync22(agentsDir)) {
38087
39085
  const agentFiles = readdirSync7(agentsDir).filter((f) => f.endsWith(".md")).sort();
38088
39086
  for (const f of agentFiles) {
38089
- entries.push(auditAgent(join48(agentsDir, f)));
39087
+ entries.push(auditAgent(join49(agentsDir, f)));
38090
39088
  }
38091
39089
  }
38092
- const skillsDir = join48(templatesDir, "skills");
38093
- if (existsSync21(skillsDir)) {
39090
+ const skillsDir = join49(templatesDir, "skills");
39091
+ if (existsSync22(skillsDir)) {
38094
39092
  const skillDirs = readdirSync7(skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
38095
39093
  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)) {
39094
+ if (existsSync22(join49(skillsDir, dir, "PROVENANCE.md"))) continue;
39095
+ const skillMd = join49(skillsDir, dir, "SKILL.md");
39096
+ if (existsSync22(skillMd)) {
38099
39097
  entries.push(auditSkill(skillMd));
38100
39098
  }
38101
39099
  }
@@ -39149,7 +40147,7 @@ var validate_waves_exports = {};
39149
40147
  __export(validate_waves_exports, {
39150
40148
  runValidateWavesCommand: () => runValidateWavesCommand
39151
40149
  });
39152
- import { readFile as readFile29 } from "node:fs/promises";
40150
+ import { readFile as readFile30 } from "node:fs/promises";
39153
40151
  async function readStdin() {
39154
40152
  return new Promise((resolve16, reject) => {
39155
40153
  const chunks = [];
@@ -39168,7 +40166,7 @@ async function runValidateWavesCommand(args) {
39168
40166
  let raw;
39169
40167
  if (filePath) {
39170
40168
  try {
39171
- raw = await readFile29(filePath, "utf-8");
40169
+ raw = await readFile30(filePath, "utf-8");
39172
40170
  } catch (err) {
39173
40171
  const msg = err instanceof Error ? err.message : String(err);
39174
40172
  process.stderr.write(
@@ -39248,12 +40246,12 @@ var init_validate_waves2 = __esm({
39248
40246
  });
39249
40247
 
39250
40248
  // src/cli/worktree/path.ts
39251
- import { dirname as dirname15, basename as basename4, join as join50 } from "node:path";
40249
+ import { dirname as dirname16, basename as basename4, join as join51 } from "node:path";
39252
40250
  function computeWorktreePath(cwd, checkpointNumber) {
39253
- const parent = dirname15(cwd);
40251
+ const parent = dirname16(cwd);
39254
40252
  const base = basename4(cwd);
39255
40253
  const nnn = String(checkpointNumber).padStart(3, "0");
39256
- return join50(parent, `${base}-CHK-${nnn}`);
40254
+ return join51(parent, `${base}-CHK-${nnn}`);
39257
40255
  }
39258
40256
  var init_path = __esm({
39259
40257
  "src/cli/worktree/path.ts"() {
@@ -39262,8 +40260,8 @@ var init_path = __esm({
39262
40260
  });
39263
40261
 
39264
40262
  // 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";
40263
+ import { join as join52, basename as basename5 } from "node:path";
40264
+ import { mkdir as mkdir15, readFile as readFile31, writeFile as writeFile24 } from "node:fs/promises";
39267
40265
  import { spawnSync as spawnSync16 } from "node:child_process";
39268
40266
  async function defaultGetRepoId(cwd) {
39269
40267
  const found = await findCodebyplanConfig(cwd);
@@ -39306,22 +40304,22 @@ function defaultGitRun(args, cwd) {
39306
40304
  };
39307
40305
  }
39308
40306
  async function defaultCopyConfigStubs(srcCwd, destPath) {
39309
- await mkdir14(join51(destPath, ".codebyplan"), { recursive: true });
40307
+ await mkdir15(join52(destPath, ".codebyplan"), { recursive: true });
39310
40308
  const topLevelStubs = [".mcp.json", ".env.local"];
39311
40309
  for (const stub of topLevelStubs) {
39312
40310
  try {
39313
- const content = await readFile30(join51(srcCwd, stub), "utf-8");
39314
- await writeFile23(join51(destPath, stub), content, "utf-8");
40311
+ const content = await readFile31(join52(srcCwd, stub), "utf-8");
40312
+ await writeFile24(join52(destPath, stub), content, "utf-8");
39315
40313
  } catch {
39316
40314
  }
39317
40315
  }
39318
40316
  try {
39319
- const content = await readFile30(
39320
- join51(srcCwd, ".codebyplan", "repo.json"),
40317
+ const content = await readFile31(
40318
+ join52(srcCwd, ".codebyplan", "repo.json"),
39321
40319
  "utf-8"
39322
40320
  );
39323
- await writeFile23(
39324
- join51(destPath, ".codebyplan", "repo.json"),
40321
+ await writeFile24(
40322
+ join52(destPath, ".codebyplan", "repo.json"),
39325
40323
  content,
39326
40324
  "utf-8"
39327
40325
  );
@@ -39482,7 +40480,7 @@ var init_add = __esm({
39482
40480
  });
39483
40481
 
39484
40482
  // src/cli/worktree/create.ts
39485
- import { join as join52 } from "node:path";
40483
+ import { join as join53 } from "node:path";
39486
40484
  async function defaultGetRepoIdentity(cwd) {
39487
40485
  const found = await findCodebyplanConfig(cwd);
39488
40486
  const contents = found?.contents ?? null;
@@ -39537,7 +40535,7 @@ async function runWorktreeCreate(args, deps = {}) {
39537
40535
  );
39538
40536
  return 1;
39539
40537
  }
39540
- const worktreePath = explicitPath ?? join52(cwd, "..", name);
40538
+ const worktreePath = explicitPath ?? join53(cwd, "..", name);
39541
40539
  const deviceId = await getDeviceId(cwd);
39542
40540
  let filesWritten = false;
39543
40541
  try {
@@ -40022,7 +41020,7 @@ var init_e2e = __esm({
40022
41020
  });
40023
41021
 
40024
41022
  // src/cli/e2e/verify-round.ts
40025
- import { readFile as readFile31 } from "node:fs/promises";
41023
+ import { readFile as readFile32 } from "node:fs/promises";
40026
41024
  import { resolve as resolve14 } from "node:path";
40027
41025
  async function defaultFetchRounds(taskId) {
40028
41026
  return mcpCall("get_rounds", { task_id: taskId });
@@ -40030,7 +41028,7 @@ async function defaultFetchRounds(taskId) {
40030
41028
  async function defaultReadE2eConfig(cwd) {
40031
41029
  try {
40032
41030
  const p = resolve14(cwd, ".codebyplan", "e2e.json");
40033
- const raw = await readFile31(p, "utf-8");
41031
+ const raw = await readFile32(p, "utf-8");
40034
41032
  return JSON.parse(raw);
40035
41033
  } catch {
40036
41034
  return null;
@@ -40436,6 +41434,12 @@ void (async () => {
40436
41434
  await runTaskCommand2(rest);
40437
41435
  process.exit(0);
40438
41436
  }
41437
+ if (arg === "standalone-task") {
41438
+ const { runStandaloneTaskCommand: runStandaloneTaskCommand2 } = await Promise.resolve().then(() => (init_standalone_task(), standalone_task_exports));
41439
+ const rest = process.argv.slice(3);
41440
+ await runStandaloneTaskCommand2(rest);
41441
+ process.exit(0);
41442
+ }
40439
41443
  if (arg === "session") {
40440
41444
  const { runSessionCommand: runSessionCommand2 } = await Promise.resolve().then(() => (init_session(), session_exports));
40441
41445
  const rest = process.argv.slice(3);
@@ -40540,6 +41544,11 @@ void (async () => {
40540
41544
  await runTechStack2();
40541
41545
  process.exit(0);
40542
41546
  }
41547
+ if (arg === "docs") {
41548
+ const { runDocs: runDocs2 } = await Promise.resolve().then(() => (init_docs(), docs_exports));
41549
+ await runDocs2();
41550
+ process.exit(process.exitCode ?? 0);
41551
+ }
40543
41552
  if (arg === "check") {
40544
41553
  const { runCheckCommand: runCheckCommand2 } = await Promise.resolve().then(() => (init_check2(), check_exports));
40545
41554
  const rest = process.argv.slice(3);
@@ -40798,6 +41807,7 @@ void (async () => {
40798
41807
  codebyplan doctor Run diagnostics: auth, version, worktree (always exits 0)
40799
41808
  codebyplan tech-stack Detect and sync tech stack dependencies
40800
41809
  (--full-tech-stack: sync every local worktree on this device)
41810
+ codebyplan docs Local dependency docs mirror (sync / status)
40801
41811
  codebyplan eslint ESLint config management (init)
40802
41812
  codebyplan ci CI configuration management (init / scaffold-workflow / enforce-check)
40803
41813
  codebyplan cd CD configuration management (init / scaffold-workflow)
@@ -40809,6 +41819,8 @@ void (async () => {
40809
41819
  codebyplan watch status Show daemon status (--json for machine-readable)
40810
41820
  codebyplan watch run Run daemon in foreground (used internally by start)
40811
41821
  codebyplan round sync-approvals Sync git diff and approvals with round/task state
41822
+ codebyplan standalone-task Standalone task writes via MCP (create/update/complete)
41823
+ (create --title; update --id; complete --id \u2014 no --checkpoint-id)
40812
41824
  codebyplan bump [--prerelease <id>] Detect changed packages and patch-bump versions
40813
41825
  codebyplan ship Ship current feat branch to production via PR
40814
41826
  codebyplan upload-e2e-images Upload new/changed committed e2e PNGs for a checkpoint