codebyplan 1.13.58 → 1.13.60

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -48,7 +48,7 @@ var VERSION, PACKAGE_NAME;
48
48
  var init_version = __esm({
49
49
  "src/lib/version.ts"() {
50
50
  "use strict";
51
- VERSION = "1.13.58";
51
+ VERSION = "1.13.60";
52
52
  PACKAGE_NAME = "codebyplan";
53
53
  }
54
54
  });
@@ -186,6 +186,17 @@ var init_local_config = __esm({
186
186
  });
187
187
 
188
188
  // src/lib/flags.ts
189
+ var flags_exports = {};
190
+ __export(flags_exports, {
191
+ coerceFieldValues: () => coerceFieldValues,
192
+ deriveRepoRoot: () => deriveRepoRoot,
193
+ findCodebyplanConfig: () => findCodebyplanConfig,
194
+ hasFlag: () => hasFlag,
195
+ kebabToSnakeKeys: () => kebabToSnakeKeys,
196
+ parseFlags: () => parseFlags,
197
+ parseFlagsFromArgs: () => parseFlagsFromArgs,
198
+ resolveConfig: () => resolveConfig
199
+ });
189
200
  import { readFile as readFile2 } from "node:fs/promises";
190
201
  import { join as join2, resolve } from "node:path";
191
202
  async function findCodebyplanConfig(startDir, maxDepth = 20) {
@@ -869,29 +880,42 @@ async function deleteFallback(filename) {
869
880
  } catch {
870
881
  }
871
882
  }
883
+ function useKeychain() {
884
+ const v = process.env.CODEBYPLAN_USE_KEYCHAIN;
885
+ return v === "1" || v === "true";
886
+ }
872
887
  async function saveTokens(tokens) {
873
- const Keyring = await loadKeyring();
874
- if (Keyring) {
875
- try {
876
- new Keyring(SERVICE, TOKEN_ACCOUNT).setPassword(JSON.stringify(tokens));
877
- return;
878
- } catch {
888
+ if (useKeychain()) {
889
+ const Keyring = await loadKeyring();
890
+ if (Keyring) {
891
+ try {
892
+ new Keyring(SERVICE, TOKEN_ACCOUNT).setPassword(JSON.stringify(tokens));
893
+ return;
894
+ } catch {
895
+ }
879
896
  }
880
897
  }
881
898
  await writeFallback("credentials.json", tokens);
882
899
  }
883
900
  async function loadTokens() {
901
+ const fromFile = await readFallback("credentials.json");
902
+ if (fromFile) return fromFile;
884
903
  const Keyring = await loadKeyring();
885
904
  if (Keyring) {
886
905
  try {
887
906
  const raw = new Keyring(SERVICE, TOKEN_ACCOUNT).getPassword();
888
- if (raw) return JSON.parse(raw);
907
+ if (raw) {
908
+ const tokens = JSON.parse(raw);
909
+ if (!useKeychain()) await writeFallback("credentials.json", tokens);
910
+ return tokens;
911
+ }
889
912
  } catch {
890
913
  }
891
914
  }
892
- return readFallback("credentials.json");
915
+ return null;
893
916
  }
894
917
  async function clearTokens() {
918
+ await deleteFallback("credentials.json");
895
919
  const Keyring = await loadKeyring();
896
920
  if (Keyring) {
897
921
  try {
@@ -899,31 +923,39 @@ async function clearTokens() {
899
923
  } catch {
900
924
  }
901
925
  }
902
- await deleteFallback("credentials.json");
903
926
  }
904
927
  async function saveClientRegistration(reg) {
905
- const Keyring = await loadKeyring();
906
- if (Keyring) {
907
- try {
908
- new Keyring(SERVICE, CLIENT_ACCOUNT).setPassword(JSON.stringify(reg));
909
- return;
910
- } catch {
928
+ if (useKeychain()) {
929
+ const Keyring = await loadKeyring();
930
+ if (Keyring) {
931
+ try {
932
+ new Keyring(SERVICE, CLIENT_ACCOUNT).setPassword(JSON.stringify(reg));
933
+ return;
934
+ } catch {
935
+ }
911
936
  }
912
937
  }
913
938
  await writeFallback("client.json", reg);
914
939
  }
915
940
  async function loadClientRegistration() {
941
+ const fromFile = await readFallback("client.json");
942
+ if (fromFile) return fromFile;
916
943
  const Keyring = await loadKeyring();
917
944
  if (Keyring) {
918
945
  try {
919
946
  const raw = new Keyring(SERVICE, CLIENT_ACCOUNT).getPassword();
920
- if (raw) return JSON.parse(raw);
947
+ if (raw) {
948
+ const reg = JSON.parse(raw);
949
+ if (!useKeychain()) await writeFallback("client.json", reg);
950
+ return reg;
951
+ }
921
952
  } catch {
922
953
  }
923
954
  }
924
- return readFallback("client.json");
955
+ return null;
925
956
  }
926
957
  async function clearClientRegistration() {
958
+ await deleteFallback("client.json");
927
959
  const Keyring = await loadKeyring();
928
960
  if (Keyring) {
929
961
  try {
@@ -931,7 +963,6 @@ async function clearClientRegistration() {
931
963
  } catch {
932
964
  }
933
965
  }
934
- await deleteFallback("client.json");
935
966
  }
936
967
  var SERVICE, TOKEN_ACCOUNT, CLIENT_ACCOUNT, keyringOverride;
937
968
  var init_keychain = __esm({
@@ -4809,9 +4840,9 @@ async function eslintInit(repoId, projectPath) {
4809
4840
  Install ${missingPkgs.length} missing packages? [Y/n] `
4810
4841
  );
4811
4842
  if (confirmed) {
4812
- const { execSync: execSync11 } = await import("node:child_process");
4843
+ const { execSync: execSync12 } = await import("node:child_process");
4813
4844
  try {
4814
- execSync11(installCmd, { cwd: projectPath, stdio: "inherit" });
4845
+ execSync12(installCmd, { cwd: projectPath, stdio: "inherit" });
4815
4846
  console.log(" Packages installed.\n");
4816
4847
  } catch (err) {
4817
4848
  console.error(
@@ -4989,6 +5020,7 @@ __export(state_store_exports, {
4989
5020
  deleteEntityFile: () => deleteEntityFile,
4990
5021
  hashEntity: () => hashEntity,
4991
5022
  listEntityFiles: () => listEntityFiles,
5023
+ pendingDir: () => pendingDir,
4992
5024
  pendingMarkerPath: () => pendingMarkerPath,
4993
5025
  pruneEntityTree: () => pruneEntityTree,
4994
5026
  readCursor: () => readCursor,
@@ -5054,6 +5086,9 @@ function todosPath(repoRoot) {
5054
5086
  function worktreesPath(repoRoot) {
5055
5087
  return join19(stateDir(repoRoot), "worktrees.json");
5056
5088
  }
5089
+ function pendingDir(repoRoot) {
5090
+ return join19(stateDir(repoRoot), "_pending");
5091
+ }
5057
5092
  function pendingMarkerPath(repoRoot, entityId) {
5058
5093
  return join19(stateDir(repoRoot), "_pending", `${entityId}.json`);
5059
5094
  }
@@ -5251,6 +5286,126 @@ var state_sync_exports = {};
5251
5286
  __export(state_sync_exports, {
5252
5287
  runSync: () => runSync
5253
5288
  });
5289
+ async function replayOutbox(repoRoot, authHeaders) {
5290
+ const markerPaths = await listEntityFiles(pendingDir(repoRoot));
5291
+ for (const markerPath of markerPaths) {
5292
+ try {
5293
+ const marker = await readEntityFile(markerPath);
5294
+ if (!marker) {
5295
+ await deleteEntityFile(markerPath);
5296
+ continue;
5297
+ }
5298
+ if (marker.operation === "complete") {
5299
+ process.stderr.write(
5300
+ `state-sync: replayOutbox: skipping "complete" marker for ${marker.entity} ${marker.id} (deferred; different endpoint \u2014 see CHK-222 TASK-4 follow-up)
5301
+ `
5302
+ );
5303
+ continue;
5304
+ }
5305
+ if (marker.operation !== "update" && marker.operation !== "update-log") {
5306
+ process.stderr.write(
5307
+ `state-sync: replayOutbox: discarding unknown operation "${marker.operation}" for ${marker.entity} ${marker.id}
5308
+ `
5309
+ );
5310
+ await deleteEntityFile(markerPath);
5311
+ continue;
5312
+ }
5313
+ let entityPath = null;
5314
+ let patchEndpoint = null;
5315
+ switch (marker.entity) {
5316
+ case "checkpoint":
5317
+ entityPath = checkpointPath(repoRoot, marker.id);
5318
+ patchEndpoint = `${backendCheckpointsEndpoint()}/${marker.id}`;
5319
+ break;
5320
+ case "task":
5321
+ if (marker.checkpoint_id) {
5322
+ entityPath = taskPath(repoRoot, marker.checkpoint_id, marker.id);
5323
+ }
5324
+ patchEndpoint = `${backendTasksEndpoint()}/${marker.id}`;
5325
+ break;
5326
+ case "round":
5327
+ if (marker.checkpoint_id && marker.task_id) {
5328
+ entityPath = roundPath(
5329
+ repoRoot,
5330
+ marker.checkpoint_id,
5331
+ marker.task_id,
5332
+ marker.id
5333
+ );
5334
+ }
5335
+ patchEndpoint = `${backendRoundsEndpoint()}/${marker.id}`;
5336
+ break;
5337
+ case "session_log":
5338
+ entityPath = sessionLogPath(repoRoot);
5339
+ patchEndpoint = `${backendSessionLogsEndpoint()}/${marker.id}`;
5340
+ break;
5341
+ default:
5342
+ process.stderr.write(
5343
+ `state-sync: replayOutbox: discarding unknown entity type "${marker.entity}" ${marker.id}
5344
+ `
5345
+ );
5346
+ await deleteEntityFile(markerPath);
5347
+ continue;
5348
+ }
5349
+ if (!entityPath) {
5350
+ process.stderr.write(
5351
+ `state-sync: replayOutbox: missing FK for ${marker.entity} ${marker.id} \u2014 discarding marker
5352
+ `
5353
+ );
5354
+ await deleteEntityFile(markerPath);
5355
+ continue;
5356
+ }
5357
+ const entityData = await readEntityFile(entityPath);
5358
+ if (!entityData) {
5359
+ await deleteEntityFile(markerPath);
5360
+ continue;
5361
+ }
5362
+ try {
5363
+ const res = await fetch(patchEndpoint, {
5364
+ method: "PATCH",
5365
+ headers: { ...authHeaders, "Content-Type": "application/json" },
5366
+ body: JSON.stringify({
5367
+ ...entityData,
5368
+ expected_sync_seq: entityData["sync_seq"]
5369
+ }),
5370
+ signal: AbortSignal.timeout(3e4)
5371
+ });
5372
+ if (res.ok || res.status >= 400 && res.status < 500) {
5373
+ await deleteEntityFile(markerPath);
5374
+ }
5375
+ } catch {
5376
+ }
5377
+ } catch (err) {
5378
+ process.stderr.write(
5379
+ `state-sync: replayOutbox: unexpected error for marker ${markerPath} (non-fatal): ${err instanceof Error ? err.message : String(err)}
5380
+ `
5381
+ );
5382
+ }
5383
+ }
5384
+ }
5385
+ function resolveTombstoneEntityPath(repoRoot, tomb) {
5386
+ switch (tomb.entity_table) {
5387
+ case "checkpoints":
5388
+ return checkpointPath(repoRoot, tomb.entity_id);
5389
+ case "tasks": {
5390
+ const checkpointId = tomb.parent_ids?.checkpoint_id;
5391
+ if (!checkpointId) return null;
5392
+ return taskPath(repoRoot, checkpointId, tomb.entity_id);
5393
+ }
5394
+ case "rounds": {
5395
+ const checkpointId = tomb.parent_ids?.checkpoint_id;
5396
+ const taskId = tomb.parent_ids?.task_id;
5397
+ if (!checkpointId || !taskId) return null;
5398
+ return roundPath(repoRoot, checkpointId, taskId, tomb.entity_id);
5399
+ }
5400
+ // Single-slot caches: no per-id file — reconcile on next full hydrate.
5401
+ // session_logs → session/current.json (deleting it would kill active session data)
5402
+ // todos → todos.json
5403
+ // worktrees → worktrees.json
5404
+ // Port allocations are intentionally not mirrored to state/.
5405
+ default:
5406
+ return null;
5407
+ }
5408
+ }
5254
5409
  async function runSync(repoRoot, repoId, worktreeId, opts = {}) {
5255
5410
  const { full = false, dryRun = false } = opts;
5256
5411
  try {
@@ -5273,6 +5428,9 @@ async function runSync(repoRoot, repoId, worktreeId, opts = {}) {
5273
5428
  error: `auth: ${err instanceof Error ? err.message : String(err)}`
5274
5429
  };
5275
5430
  }
5431
+ if (!dryRun) {
5432
+ await replayOutbox(repoRoot, authHeaders);
5433
+ }
5276
5434
  let result;
5277
5435
  try {
5278
5436
  const res = await fetch(url.toString(), {
@@ -5299,6 +5457,7 @@ async function runSync(repoRoot, repoId, worktreeId, opts = {}) {
5299
5457
  ok: true,
5300
5458
  written: 0,
5301
5459
  pruned: 0,
5460
+ tombstoned: 0,
5302
5461
  mode: isFull ? "full" : "delta",
5303
5462
  max_seq: result.max_seq
5304
5463
  };
@@ -5352,6 +5511,15 @@ async function runSync(repoRoot, repoId, worktreeId, opts = {}) {
5352
5511
  await writeEntityFile(worktreesPath(repoRoot), result.worktrees);
5353
5512
  written++;
5354
5513
  }
5514
+ let tombstoned = 0;
5515
+ for (const tomb of result.deleted ?? []) {
5516
+ const filePath = resolveTombstoneEntityPath(repoRoot, tomb);
5517
+ if (filePath) {
5518
+ await deleteEntityFile(filePath);
5519
+ delete entityHashes[tomb.entity_id];
5520
+ tombstoned++;
5521
+ }
5522
+ }
5355
5523
  let pruned = 0;
5356
5524
  if (isFull) {
5357
5525
  const knownIdsByTable = {
@@ -5378,6 +5546,7 @@ async function runSync(repoRoot, repoId, worktreeId, opts = {}) {
5378
5546
  ok: true,
5379
5547
  written,
5380
5548
  pruned,
5549
+ tombstoned,
5381
5550
  mode: isFull ? "full" : "delta",
5382
5551
  max_seq: result.max_seq
5383
5552
  };
@@ -14535,8 +14704,8 @@ var require_RealtimeChannel = __commonJS({
14535
14704
  }
14536
14705
  /** @internal */
14537
14706
  _notThisChannelEvent(event, ref) {
14538
- const { close, error, leave, join: join57 } = constants_1.CHANNEL_EVENTS;
14539
- const events = [close, error, leave, join57];
14707
+ const { close, error, leave, join: join60 } = constants_1.CHANNEL_EVENTS;
14708
+ const events = [close, error, leave, join60];
14540
14709
  return ref && events.includes(event) && ref !== this.joinPush.ref;
14541
14710
  }
14542
14711
  /** @internal */
@@ -27986,6 +28155,9 @@ var init_watch = __esm({
27986
28155
  });
27987
28156
 
27988
28157
  // src/lib/state-client.ts
28158
+ function isConflict(err) {
28159
+ return err instanceof BackendError && err.status === 409;
28160
+ }
27989
28161
  async function backendRequest(method, path22, body) {
27990
28162
  const url = `${getBackendBase()}${path22}`;
27991
28163
  const auth = await getAuthHeaders();
@@ -28037,11 +28209,316 @@ var init_state_client = __esm({
28037
28209
  }
28038
28210
  });
28039
28211
 
28212
+ // src/lib/mcp-client.ts
28213
+ async function mcpCall(toolName, args) {
28214
+ let accessToken;
28215
+ try {
28216
+ accessToken = await getAccessToken();
28217
+ } catch (err) {
28218
+ if (err instanceof NoTokenError) {
28219
+ throw new McpError(
28220
+ "Not logged in. Run `codebyplan login` to authenticate."
28221
+ );
28222
+ }
28223
+ throw err;
28224
+ }
28225
+ const body = {
28226
+ jsonrpc: "2.0",
28227
+ id: 1,
28228
+ method: "tools/call",
28229
+ params: { name: toolName, arguments: args }
28230
+ };
28231
+ let res;
28232
+ try {
28233
+ res = await fetch(mcpEndpoint(), {
28234
+ method: "POST",
28235
+ headers: {
28236
+ "Content-Type": "application/json",
28237
+ Accept: "application/json, text/event-stream",
28238
+ Authorization: `Bearer ${accessToken}`
28239
+ },
28240
+ body: JSON.stringify(body),
28241
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS3)
28242
+ });
28243
+ } catch (err) {
28244
+ const message = err instanceof Error ? err.message : String(err);
28245
+ throw new McpError(`mcp ${toolName} network error: ${message}`);
28246
+ }
28247
+ if (res.status === 401) {
28248
+ throw new McpError(
28249
+ "Authentication failed. Run `codebyplan login` to refresh your session.",
28250
+ 401
28251
+ );
28252
+ }
28253
+ if (!res.ok) {
28254
+ throw new McpError(
28255
+ `mcp ${toolName} failed with status ${res.status}`,
28256
+ res.status
28257
+ );
28258
+ }
28259
+ const contentType = res.headers.get("content-type") ?? "";
28260
+ let envelope;
28261
+ if (contentType.includes("text/event-stream")) {
28262
+ const text = await res.text();
28263
+ envelope = parseSseEnvelope(text, toolName);
28264
+ } else {
28265
+ envelope = await res.json();
28266
+ }
28267
+ if (envelope.error) {
28268
+ throw new McpError(
28269
+ envelope.error.message ?? `mcp ${toolName} protocol error`,
28270
+ void 0,
28271
+ envelope.error.code !== void 0 ? String(envelope.error.code) : void 0
28272
+ );
28273
+ }
28274
+ const result = envelope.result;
28275
+ if (!result) {
28276
+ throw new McpError(`mcp ${toolName} returned no result`);
28277
+ }
28278
+ const textContent = result.content?.find((c) => c.type === "text")?.text;
28279
+ if (textContent === void 0) {
28280
+ throw new McpError(`mcp ${toolName} returned no text content`);
28281
+ }
28282
+ if (result.isError === true) {
28283
+ throw new McpError(textContent);
28284
+ }
28285
+ try {
28286
+ return JSON.parse(textContent);
28287
+ } catch {
28288
+ throw new McpError(
28289
+ `mcp ${toolName} returned non-JSON payload: ${textContent.slice(0, 200)}`
28290
+ );
28291
+ }
28292
+ }
28293
+ function parseSseEnvelope(text, toolName) {
28294
+ const frames = text.split("\n\n");
28295
+ for (let i = frames.length - 1; i >= 0; i--) {
28296
+ const frame = frames[i];
28297
+ const dataLines = frame.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim());
28298
+ if (dataLines.length === 0) continue;
28299
+ const dataText = dataLines.join("\n");
28300
+ try {
28301
+ return JSON.parse(dataText);
28302
+ } catch {
28303
+ continue;
28304
+ }
28305
+ }
28306
+ throw new McpError(`mcp ${toolName} returned unparseable SSE body`);
28307
+ }
28308
+ var REQUEST_TIMEOUT_MS3, McpError;
28309
+ var init_mcp_client = __esm({
28310
+ "src/lib/mcp-client.ts"() {
28311
+ "use strict";
28312
+ init_token_refresh();
28313
+ init_urls();
28314
+ REQUEST_TIMEOUT_MS3 = 12e4;
28315
+ McpError = class extends Error {
28316
+ status;
28317
+ code;
28318
+ constructor(message, status, code) {
28319
+ super(message);
28320
+ this.name = "McpError";
28321
+ this.status = status;
28322
+ this.code = code;
28323
+ }
28324
+ };
28325
+ }
28326
+ });
28327
+
28328
+ // src/cli/export-writer.ts
28329
+ import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "node:fs";
28330
+ import { join as join21 } from "node:path";
28331
+ async function buildCheckpointExport(checkpointId) {
28332
+ const checkpointResponse = await apiGet(
28333
+ `/checkpoints/${checkpointId}`
28334
+ );
28335
+ const checkpoint = checkpointResponse.data;
28336
+ if (!checkpoint) {
28337
+ throw new Error(
28338
+ `buildCheckpointExport: checkpoint ${checkpointId} not found`
28339
+ );
28340
+ }
28341
+ const tasksResponse = await apiGet(
28342
+ "/tasks",
28343
+ { checkpoint_id: checkpointId }
28344
+ );
28345
+ const rawTasks = tasksResponse.data ?? [];
28346
+ const tasks = await Promise.all(
28347
+ rawTasks.map(async (task) => {
28348
+ const rawRounds = await fetchRoundsForTask(task.id);
28349
+ const rounds = rawRounds.map((r) => ({
28350
+ id: r.id,
28351
+ number: r.number,
28352
+ status: r.status,
28353
+ files_changed: r.files_changed ?? [],
28354
+ context: r.context ?? null
28355
+ }));
28356
+ return {
28357
+ id: task.id,
28358
+ number: task.number,
28359
+ title: task.title ?? null,
28360
+ status: task.status,
28361
+ completed_at: task.completed_at ?? null,
28362
+ files_changed: task.files_changed ?? [],
28363
+ qa: task.qa ?? null,
28364
+ rounds
28365
+ };
28366
+ })
28367
+ );
28368
+ return {
28369
+ schema_version: EXPORT_SCHEMA_VERSION,
28370
+ exported_at: (/* @__PURE__ */ new Date()).toISOString(),
28371
+ checkpoint_id: checkpointId,
28372
+ number: checkpoint.number,
28373
+ sync_seq: checkpoint.sync_seq,
28374
+ title: checkpoint.title ?? null,
28375
+ status: checkpoint.status,
28376
+ completed_at: checkpoint.completed_at ?? null,
28377
+ context: checkpoint.context ?? null,
28378
+ files_changed: checkpoint.files_changed ?? [],
28379
+ qa: checkpoint.qa ?? null,
28380
+ tasks
28381
+ };
28382
+ }
28383
+ async function buildStandaloneExport(taskId, repoRoot, repoIdHint) {
28384
+ let repoId = repoIdHint;
28385
+ if (!repoId) {
28386
+ const { findCodebyplanConfig: findCodebyplanConfig2 } = await Promise.resolve().then(() => (init_flags(), flags_exports));
28387
+ const found = await findCodebyplanConfig2(repoRoot);
28388
+ repoId = found?.contents.repo_id;
28389
+ }
28390
+ if (!repoId) {
28391
+ throw new Error(
28392
+ "buildStandaloneExport: could not resolve repo_id from .codebyplan/repo.json"
28393
+ );
28394
+ }
28395
+ const allTasks = await mcpCall("get_standalone_tasks", {
28396
+ repo_id: repoId
28397
+ });
28398
+ const task = allTasks.find((t) => t.id === taskId);
28399
+ if (!task) {
28400
+ throw new Error(
28401
+ `buildStandaloneExport: standalone task ${taskId} not found`
28402
+ );
28403
+ }
28404
+ const rawRounds = await mcpCall("get_standalone_rounds", {
28405
+ standalone_task_id: taskId
28406
+ });
28407
+ const rounds = rawRounds.map((r) => ({
28408
+ id: r.id,
28409
+ number: r.number,
28410
+ status: r.status,
28411
+ files_changed: r.files_changed ?? [],
28412
+ context: r.context ?? null
28413
+ }));
28414
+ const taskEntry = {
28415
+ id: task.id,
28416
+ number: task.number,
28417
+ title: task.title,
28418
+ status: task.status,
28419
+ completed_at: task.completed_at ?? null,
28420
+ files_changed: task.files_changed ?? [],
28421
+ qa: task.qa ?? null,
28422
+ rounds
28423
+ };
28424
+ return {
28425
+ schema_version: EXPORT_SCHEMA_VERSION,
28426
+ exported_at: (/* @__PURE__ */ new Date()).toISOString(),
28427
+ standalone_task_id: taskId,
28428
+ number: task.number,
28429
+ title: task.title,
28430
+ status: task.status,
28431
+ completed_at: task.completed_at ?? null,
28432
+ context: task.context ?? null,
28433
+ files_changed: task.files_changed ?? [],
28434
+ qa: task.qa ?? null,
28435
+ tasks: [taskEntry]
28436
+ };
28437
+ }
28438
+ function renderSummaryMd(data) {
28439
+ const isCheckpoint = "checkpoint_id" in data;
28440
+ const entityLabel = isCheckpoint ? "Checkpoint" : "Standalone Task";
28441
+ const entityId = isCheckpoint ? data.checkpoint_id : data.standalone_task_id;
28442
+ const completedLine = data.completed_at ? `**Completed**: ${data.completed_at}` : `**Status**: ${data.status}`;
28443
+ const ctx = data.context;
28444
+ const decisions = Array.isArray(ctx?.decisions) ? ctx.decisions : [];
28445
+ const decisionsBlock = decisions.length > 0 ? `
28446
+ ## Decisions
28447
+
28448
+ ${decisions.map(
28449
+ (d, i) => `${i + 1}. ${typeof d === "string" ? d : JSON.stringify(d)}`
28450
+ ).join("\n")}
28451
+ ` : "";
28452
+ const taskRows = data.tasks.map((t) => {
28453
+ const roundCount = t.rounds.length;
28454
+ const fileCount = Array.isArray(t.files_changed) ? t.files_changed.length : 0;
28455
+ return `| ${t.number} | ${t.title ?? "(untitled)"} | ${t.status} | ${roundCount} | ${fileCount} |`;
28456
+ }).join("\n");
28457
+ const tasksBlock = data.tasks.length > 0 ? `
28458
+ ## Tasks
28459
+
28460
+ | # | Title | Status | Rounds | Files |
28461
+ |---|-------|--------|--------|-------|
28462
+ ${taskRows}
28463
+ ` : "";
28464
+ const allFiles = /* @__PURE__ */ new Set();
28465
+ for (const task of data.tasks) {
28466
+ if (Array.isArray(task.files_changed)) {
28467
+ for (const f of task.files_changed) {
28468
+ const path22 = typeof f === "string" ? f : f?.path;
28469
+ if (path22) allFiles.add(path22);
28470
+ }
28471
+ }
28472
+ }
28473
+ const filesBlock = allFiles.size > 0 ? `
28474
+ ## Files Changed (${allFiles.size})
28475
+
28476
+ ${[...allFiles].sort().map((f) => `- \`${f}\``).join("\n")}
28477
+ ` : "";
28478
+ return `# ${entityLabel} Export
28479
+
28480
+ **${entityLabel} ID**: \`${entityId}\`
28481
+ **Number**: ${data.number}
28482
+ **Title**: ${data.title ?? "(untitled)"}
28483
+ ${completedLine}
28484
+ **Exported**: ${data.exported_at}
28485
+ ` + decisionsBlock + tasksBlock + filesBlock;
28486
+ }
28487
+ function writeExportArtifacts(outDir, exportData, summaryMd) {
28488
+ mkdirSync3(outDir, { recursive: true });
28489
+ const exportJsonPath = join21(outDir, "export.json");
28490
+ const summaryMdPath = join21(outDir, "summary.md");
28491
+ writeFileSync3(
28492
+ exportJsonPath,
28493
+ JSON.stringify(exportData, null, 2) + "\n",
28494
+ "utf-8"
28495
+ );
28496
+ writeFileSync3(summaryMdPath, summaryMd, "utf-8");
28497
+ return { exportJsonPath, summaryMdPath };
28498
+ }
28499
+ async function fetchRoundsForTask(taskId) {
28500
+ try {
28501
+ return await mcpCall("get_rounds", { task_id: taskId });
28502
+ } catch {
28503
+ return [];
28504
+ }
28505
+ }
28506
+ var EXPORT_SCHEMA_VERSION;
28507
+ var init_export_writer = __esm({
28508
+ "src/cli/export-writer.ts"() {
28509
+ "use strict";
28510
+ init_api();
28511
+ init_mcp_client();
28512
+ EXPORT_SCHEMA_VERSION = 1;
28513
+ }
28514
+ });
28515
+
28040
28516
  // src/cli/checkpoint.ts
28041
28517
  var checkpoint_exports = {};
28042
28518
  __export(checkpoint_exports, {
28043
28519
  runCheckpointCommand: () => runCheckpointCommand
28044
28520
  });
28521
+ import { join as join22 } from "node:path";
28045
28522
  async function resolveRepoRoot() {
28046
28523
  const found = await findCodebyplanConfig(process.cwd());
28047
28524
  if (!found?.contents.repo_id) return null;
@@ -28127,41 +28604,91 @@ async function runCheckpointUpdate(args) {
28127
28604
  const snakePatch = coerceFieldValues(kebabToSnakeKeys(patchBody));
28128
28605
  const optimistic = { ...snapshot ?? {}, ...snakePatch, id };
28129
28606
  await writeEntityFile(filePath, optimistic);
28130
- try {
28131
- const updated = await apiBackendPatch(
28132
- `${new URL(backendCheckpointsEndpoint()).pathname}/${id}`,
28133
- snakePatch
28134
- );
28135
- await writeEntityFile(filePath, updated);
28136
- await updateCursorHash(repoRoot, id, updated);
28137
- process.stdout.write(JSON.stringify(updated) + "\n");
28138
- } catch (err) {
28139
- if (err instanceof BackendError && err.status < 500) {
28140
- if (snapshot !== null) {
28141
- await writeEntityFile(filePath, snapshot);
28142
- } else {
28143
- await deleteEntityFile(filePath);
28144
- }
28145
- process.stderr.write(
28146
- `checkpoint update: backend rejected (${err.status}): ${err.message}
28147
- `
28607
+ let freshRow = snapshot;
28608
+ let lastErr;
28609
+ let successRow = null;
28610
+ if (freshRow === null) {
28611
+ try {
28612
+ const bootstrapResponse = await apiGet(
28613
+ `/checkpoints/${id}`
28148
28614
  );
28615
+ freshRow = bootstrapResponse.data;
28616
+ await writeEntityFile(filePath, freshRow);
28617
+ } catch {
28618
+ }
28619
+ }
28620
+ for (let attempt = 0; attempt <= MAX_OCC_RETRIES; attempt++) {
28621
+ const currentSeq = freshRow !== null && typeof freshRow.sync_seq === "number" ? freshRow.sync_seq : void 0;
28622
+ const patchWithSeq = currentSeq !== void 0 ? { ...snakePatch, expected_sync_seq: currentSeq } : { ...snakePatch };
28623
+ try {
28624
+ const updated = await apiBackendPatch(
28625
+ `${new URL(backendCheckpointsEndpoint()).pathname}/${id}`,
28626
+ patchWithSeq
28627
+ );
28628
+ await writeEntityFile(filePath, updated);
28629
+ await updateCursorHash(repoRoot, id, updated);
28630
+ successRow = updated;
28631
+ break;
28632
+ } catch (err2) {
28633
+ if (isConflict(err2) && attempt < MAX_OCC_RETRIES) {
28634
+ try {
28635
+ const freshResponse = await apiGet(
28636
+ `/checkpoints/${id}`
28637
+ );
28638
+ freshRow = freshResponse.data;
28639
+ await writeEntityFile(filePath, freshRow);
28640
+ } catch {
28641
+ lastErr = err2;
28642
+ break;
28643
+ }
28644
+ lastErr = err2;
28645
+ continue;
28646
+ }
28647
+ lastErr = err2;
28648
+ break;
28649
+ }
28650
+ }
28651
+ if (successRow !== null) {
28652
+ process.stdout.write(JSON.stringify(successRow) + "\n");
28653
+ process.exit(0);
28654
+ }
28655
+ const err = lastErr;
28656
+ if (isConflict(err)) {
28657
+ if (freshRow !== null) {
28658
+ await writeEntityFile(filePath, freshRow);
28659
+ } else if (snapshot !== null) {
28660
+ await writeEntityFile(filePath, snapshot);
28149
28661
  } else {
28150
- await writeEntityFile(pendingMarkerPath(repoRoot, id), {
28151
- entity: "checkpoint",
28152
- id,
28153
- operation: "update",
28154
- attempted_at: (/* @__PURE__ */ new Date()).toISOString(),
28155
- error: err instanceof Error ? err.message : String(err)
28156
- });
28157
- process.stderr.write(
28158
- `checkpoint update: backend unavailable \u2014 local write kept, pending marker written. Error: ${err instanceof Error ? err.message : String(err)}
28662
+ await deleteEntityFile(filePath);
28663
+ }
28664
+ process.stderr.write(
28665
+ `checkpoint update: conflict \u2014 another writer updated checkpoint ${id} concurrently. Re-read the checkpoint and retry.
28159
28666
  `
28160
- );
28667
+ );
28668
+ } else if (err instanceof BackendError && err.status < 500) {
28669
+ if (snapshot !== null) {
28670
+ await writeEntityFile(filePath, snapshot);
28671
+ } else {
28672
+ await deleteEntityFile(filePath);
28161
28673
  }
28162
- process.exit(1);
28674
+ process.stderr.write(
28675
+ `checkpoint update: backend rejected (${err.status}): ${err.message}
28676
+ `
28677
+ );
28678
+ } else {
28679
+ await writeEntityFile(pendingMarkerPath(repoRoot, id), {
28680
+ entity: "checkpoint",
28681
+ id,
28682
+ operation: "update",
28683
+ attempted_at: (/* @__PURE__ */ new Date()).toISOString(),
28684
+ error: err instanceof Error ? err.message : String(err)
28685
+ });
28686
+ process.stderr.write(
28687
+ `checkpoint update: backend unavailable \u2014 local write kept, pending marker written. Error: ${err instanceof Error ? err.message : String(err)}
28688
+ `
28689
+ );
28163
28690
  }
28164
- process.exit(0);
28691
+ process.exit(1);
28165
28692
  }
28166
28693
  async function runCheckpointComplete(args) {
28167
28694
  const { flags } = parseFlagsFromArgs(args);
@@ -28184,45 +28711,145 @@ async function runCheckpointComplete(args) {
28184
28711
  const snapshot = await readEntityFile(filePath);
28185
28712
  const optimistic = { ...snapshot ?? {}, id, status: "completed" };
28186
28713
  await writeEntityFile(filePath, optimistic);
28187
- try {
28188
- const completed = await apiBackendPost(
28189
- `${new URL(backendCheckpointsEndpoint()).pathname}/${id}/complete`,
28190
- {}
28191
- );
28192
- await writeEntityFile(filePath, completed);
28193
- await updateCursorHash(repoRoot, id, completed);
28194
- process.stdout.write(JSON.stringify(completed) + "\n");
28195
- } catch (err) {
28196
- if (err instanceof BackendError && err.status < 500) {
28197
- if (snapshot !== null) {
28198
- await writeEntityFile(filePath, snapshot);
28199
- } else {
28200
- await deleteEntityFile(filePath);
28714
+ let freshRow = snapshot;
28715
+ let lastErr;
28716
+ let successRow = null;
28717
+ for (let attempt = 0; attempt <= MAX_OCC_RETRIES; attempt++) {
28718
+ try {
28719
+ const completed = await apiBackendPost(
28720
+ `${new URL(backendCheckpointsEndpoint()).pathname}/${id}/complete`,
28721
+ {}
28722
+ );
28723
+ await writeEntityFile(filePath, completed);
28724
+ await updateCursorHash(repoRoot, id, completed);
28725
+ successRow = completed;
28726
+ break;
28727
+ } catch (err2) {
28728
+ if (isConflict(err2) && attempt < MAX_OCC_RETRIES) {
28729
+ try {
28730
+ const freshResponse = await apiGet(
28731
+ `/checkpoints/${id}`
28732
+ );
28733
+ freshRow = freshResponse.data;
28734
+ await writeEntityFile(filePath, freshRow);
28735
+ } catch {
28736
+ lastErr = err2;
28737
+ break;
28738
+ }
28739
+ lastErr = err2;
28740
+ continue;
28201
28741
  }
28202
- process.stderr.write(
28203
- `checkpoint complete: backend rejected (${err.status}): ${err.message}
28742
+ lastErr = err2;
28743
+ break;
28744
+ }
28745
+ }
28746
+ if (successRow !== null) {
28747
+ process.stdout.write(JSON.stringify(successRow) + "\n");
28748
+ process.exit(0);
28749
+ }
28750
+ const err = lastErr;
28751
+ if (isConflict(err)) {
28752
+ if (freshRow !== null) {
28753
+ await writeEntityFile(filePath, freshRow);
28754
+ } else if (snapshot !== null) {
28755
+ await writeEntityFile(filePath, snapshot);
28756
+ } else {
28757
+ await deleteEntityFile(filePath);
28758
+ }
28759
+ process.stderr.write(
28760
+ `checkpoint complete: conflict \u2014 another writer updated checkpoint ${id} concurrently. Re-read the checkpoint and retry.
28204
28761
  `
28205
- );
28762
+ );
28763
+ } else if (err instanceof BackendError && err.status < 500) {
28764
+ if (snapshot !== null) {
28765
+ await writeEntityFile(filePath, snapshot);
28206
28766
  } else {
28207
- await writeEntityFile(pendingMarkerPath(repoRoot, id), {
28208
- entity: "checkpoint",
28209
- id,
28210
- operation: "complete",
28211
- attempted_at: (/* @__PURE__ */ new Date()).toISOString(),
28212
- error: err instanceof Error ? err.message : String(err)
28213
- });
28767
+ await deleteEntityFile(filePath);
28768
+ }
28769
+ process.stderr.write(
28770
+ `checkpoint complete: backend rejected (${err.status}): ${err.message}
28771
+ `
28772
+ );
28773
+ } else {
28774
+ await writeEntityFile(pendingMarkerPath(repoRoot, id), {
28775
+ entity: "checkpoint",
28776
+ id,
28777
+ operation: "complete",
28778
+ attempted_at: (/* @__PURE__ */ new Date()).toISOString(),
28779
+ error: err instanceof Error ? err.message : String(err)
28780
+ });
28781
+ process.stderr.write(
28782
+ `checkpoint complete: backend unavailable \u2014 local write kept, pending marker written. Error: ${err instanceof Error ? err.message : String(err)}
28783
+ `
28784
+ );
28785
+ }
28786
+ process.exit(1);
28787
+ }
28788
+ async function runCheckpointExport(args) {
28789
+ const { flags } = parseFlagsFromArgs(args);
28790
+ const id = flags.id ?? flags["checkpoint-id"];
28791
+ if (!id) {
28792
+ process.stderr.write(
28793
+ "checkpoint export: --id <checkpoint-id> is required\n"
28794
+ );
28795
+ process.exit(1);
28796
+ }
28797
+ const repoInfo = await resolveRepoRoot();
28798
+ if (!repoInfo) {
28799
+ process.stderr.write(
28800
+ "checkpoint export: no .codebyplan/repo.json found. Run `codebyplan setup`.\n"
28801
+ );
28802
+ process.exit(1);
28803
+ }
28804
+ const { repoRoot } = repoInfo;
28805
+ try {
28806
+ const exportData = await buildCheckpointExport(id);
28807
+ const outDir = join22(
28808
+ repoRoot,
28809
+ ".codebyplan",
28810
+ "exports",
28811
+ String(exportData.number)
28812
+ );
28813
+ const summaryMd = renderSummaryMd(exportData);
28814
+ const { exportJsonPath, summaryMdPath } = writeExportArtifacts(
28815
+ outDir,
28816
+ exportData,
28817
+ summaryMd
28818
+ );
28819
+ const last_exported_at = exportData.exported_at;
28820
+ try {
28821
+ await apiBackendPatch(
28822
+ `${new URL(backendCheckpointsEndpoint()).pathname}/${id}`,
28823
+ { last_exported_at, expected_sync_seq: exportData.sync_seq }
28824
+ );
28825
+ } catch (err) {
28826
+ const detail = err instanceof BackendError ? `(${err.status}): ${err.message}` : err instanceof Error ? err.message : String(err);
28214
28827
  process.stderr.write(
28215
- `checkpoint complete: backend unavailable \u2014 local write kept, pending marker written. Error: ${err instanceof Error ? err.message : String(err)}
28828
+ `checkpoint export: warning \u2014 could not stamp last_exported_at ${detail}
28216
28829
  `
28217
28830
  );
28218
28831
  }
28832
+ process.stdout.write(
28833
+ JSON.stringify({
28834
+ exported: true,
28835
+ path: outDir,
28836
+ export_json: exportJsonPath,
28837
+ summary_md: summaryMdPath,
28838
+ last_exported_at
28839
+ }) + "\n"
28840
+ );
28841
+ } catch (err) {
28842
+ process.stderr.write(
28843
+ `checkpoint export: ${err instanceof Error ? err.message : String(err)}
28844
+ `
28845
+ );
28219
28846
  process.exit(1);
28220
28847
  }
28221
28848
  process.exit(0);
28222
28849
  }
28223
28850
  function printCheckpointHelp() {
28224
28851
  process.stdout.write(
28225
- "\n codebyplan checkpoint <subcommand>\n\n Subcommands:\n create Create a checkpoint (--repo-id auto-resolved, pass extra fields as --key value)\n update Update a checkpoint (--id <uuid> required, then --key value pairs)\n complete Complete a checkpoint (--id <uuid> required)\n\n"
28852
+ "\n codebyplan checkpoint <subcommand>\n\n Subcommands:\n create Create a checkpoint (--repo-id auto-resolved, pass extra fields as --key value)\n update Update a checkpoint (--id <uuid> required, then --key value pairs)\n complete Complete a checkpoint (--id <uuid> required)\n export Export checkpoint data to .codebyplan/exports/{N}/ (--id <uuid> required)\n\n"
28226
28853
  );
28227
28854
  }
28228
28855
  async function runCheckpointCommand(args) {
@@ -28239,6 +28866,10 @@ async function runCheckpointCommand(args) {
28239
28866
  await runCheckpointComplete(args.slice(1));
28240
28867
  return;
28241
28868
  }
28869
+ if (subcommand === "export") {
28870
+ await runCheckpointExport(args.slice(1));
28871
+ return;
28872
+ }
28242
28873
  if (subcommand === "help" || subcommand === "--help" || subcommand === "-h") {
28243
28874
  printCheckpointHelp();
28244
28875
  process.exit(0);
@@ -28254,13 +28885,17 @@ Run 'codebyplan checkpoint help' for usage.
28254
28885
  }
28255
28886
  process.exit(1);
28256
28887
  }
28888
+ var MAX_OCC_RETRIES;
28257
28889
  var init_checkpoint = __esm({
28258
28890
  "src/cli/checkpoint.ts"() {
28259
28891
  "use strict";
28892
+ init_api();
28260
28893
  init_flags();
28261
28894
  init_state_store();
28262
28895
  init_state_client();
28263
28896
  init_urls();
28897
+ init_export_writer();
28898
+ MAX_OCC_RETRIES = 3;
28264
28899
  }
28265
28900
  });
28266
28901
 
@@ -28364,42 +28999,88 @@ async function runTaskUpdate(args) {
28364
28999
  const snakePatch = coerceFieldValues(kebabToSnakeKeys(patchBody));
28365
29000
  const optimistic = { ...snapshot ?? {}, ...snakePatch, id };
28366
29001
  await writeEntityFile(filePath, optimistic);
28367
- try {
28368
- const updated = await apiBackendPatch(
28369
- `${new URL(backendTasksEndpoint()).pathname}/${id}`,
28370
- snakePatch
28371
- );
28372
- await writeEntityFile(filePath, updated);
28373
- await updateCursorHash2(repoRoot, id, updated);
28374
- process.stdout.write(JSON.stringify(updated) + "\n");
28375
- } catch (err) {
28376
- if (err instanceof BackendError && err.status < 500) {
28377
- if (snapshot !== null) {
28378
- await writeEntityFile(filePath, snapshot);
28379
- } else {
28380
- await deleteEntityFile(filePath);
28381
- }
28382
- process.stderr.write(
28383
- `task update: backend rejected (${err.status}): ${err.message}
28384
- `
29002
+ let freshRow = snapshot;
29003
+ let lastErr;
29004
+ let successRow = null;
29005
+ if (freshRow === null) {
29006
+ try {
29007
+ const bootstrapResponse = await apiGet(`/tasks/${id}`);
29008
+ freshRow = bootstrapResponse.data;
29009
+ await writeEntityFile(filePath, freshRow);
29010
+ } catch {
29011
+ }
29012
+ }
29013
+ for (let attempt = 0; attempt <= MAX_OCC_RETRIES2; attempt++) {
29014
+ const currentSeq = freshRow !== null && typeof freshRow.sync_seq === "number" ? freshRow.sync_seq : void 0;
29015
+ const patchWithSeq = currentSeq !== void 0 ? { ...snakePatch, expected_sync_seq: currentSeq } : { ...snakePatch };
29016
+ try {
29017
+ const updated = await apiBackendPatch(
29018
+ `${new URL(backendTasksEndpoint()).pathname}/${id}`,
29019
+ patchWithSeq
28385
29020
  );
29021
+ await writeEntityFile(filePath, updated);
29022
+ await updateCursorHash2(repoRoot, id, updated);
29023
+ successRow = updated;
29024
+ break;
29025
+ } catch (err2) {
29026
+ if (isConflict(err2) && attempt < MAX_OCC_RETRIES2) {
29027
+ try {
29028
+ const freshResponse = await apiGet(`/tasks/${id}`);
29029
+ freshRow = freshResponse.data;
29030
+ await writeEntityFile(filePath, freshRow);
29031
+ } catch {
29032
+ lastErr = err2;
29033
+ break;
29034
+ }
29035
+ lastErr = err2;
29036
+ continue;
29037
+ }
29038
+ lastErr = err2;
29039
+ break;
29040
+ }
29041
+ }
29042
+ if (successRow !== null) {
29043
+ process.stdout.write(JSON.stringify(successRow) + "\n");
29044
+ process.exit(0);
29045
+ }
29046
+ const err = lastErr;
29047
+ if (isConflict(err)) {
29048
+ if (freshRow !== null) {
29049
+ await writeEntityFile(filePath, freshRow);
29050
+ } else if (snapshot !== null) {
29051
+ await writeEntityFile(filePath, snapshot);
28386
29052
  } else {
28387
- await writeEntityFile(pendingMarkerPath(repoRoot, id), {
28388
- entity: "task",
28389
- id,
28390
- checkpoint_id: checkpointId,
28391
- operation: "update",
28392
- attempted_at: (/* @__PURE__ */ new Date()).toISOString(),
28393
- error: err instanceof Error ? err.message : String(err)
28394
- });
28395
- process.stderr.write(
28396
- `task update: backend unavailable \u2014 local write kept, pending marker written. Error: ${err instanceof Error ? err.message : String(err)}
29053
+ await deleteEntityFile(filePath);
29054
+ }
29055
+ process.stderr.write(
29056
+ `task update: conflict \u2014 another writer updated task ${id} concurrently. Re-read the task and retry.
28397
29057
  `
28398
- );
29058
+ );
29059
+ } else if (err instanceof BackendError && err.status < 500) {
29060
+ if (snapshot !== null) {
29061
+ await writeEntityFile(filePath, snapshot);
29062
+ } else {
29063
+ await deleteEntityFile(filePath);
28399
29064
  }
28400
- process.exit(1);
29065
+ process.stderr.write(
29066
+ `task update: backend rejected (${err.status}): ${err.message}
29067
+ `
29068
+ );
29069
+ } else {
29070
+ await writeEntityFile(pendingMarkerPath(repoRoot, id), {
29071
+ entity: "task",
29072
+ id,
29073
+ checkpoint_id: checkpointId,
29074
+ operation: "update",
29075
+ attempted_at: (/* @__PURE__ */ new Date()).toISOString(),
29076
+ error: err instanceof Error ? err.message : String(err)
29077
+ });
29078
+ process.stderr.write(
29079
+ `task update: backend unavailable \u2014 local write kept, pending marker written. Error: ${err instanceof Error ? err.message : String(err)}
29080
+ `
29081
+ );
28401
29082
  }
28402
- process.exit(0);
29083
+ process.exit(1);
28403
29084
  }
28404
29085
  async function runTaskComplete(args) {
28405
29086
  const { flags } = parseFlagsFromArgs(args);
@@ -28427,42 +29108,78 @@ async function runTaskComplete(args) {
28427
29108
  const snapshot = await readEntityFile(filePath);
28428
29109
  const optimistic = { ...snapshot ?? {}, id, status: "completed" };
28429
29110
  await writeEntityFile(filePath, optimistic);
28430
- try {
28431
- const completed = await apiBackendPost(
28432
- `${new URL(backendTasksEndpoint()).pathname}/${id}/complete`,
28433
- {}
28434
- );
28435
- await writeEntityFile(filePath, completed);
28436
- await updateCursorHash2(repoRoot, id, completed);
28437
- process.stdout.write(JSON.stringify(completed) + "\n");
28438
- } catch (err) {
28439
- if (err instanceof BackendError && err.status < 500) {
28440
- if (snapshot !== null) {
28441
- await writeEntityFile(filePath, snapshot);
28442
- } else {
28443
- await deleteEntityFile(filePath);
28444
- }
28445
- process.stderr.write(
28446
- `task complete: backend rejected (${err.status}): ${err.message}
28447
- `
29111
+ let freshRow = snapshot;
29112
+ let lastErr;
29113
+ let successRow = null;
29114
+ for (let attempt = 0; attempt <= MAX_OCC_RETRIES2; attempt++) {
29115
+ try {
29116
+ const completed = await apiBackendPost(
29117
+ `${new URL(backendTasksEndpoint()).pathname}/${id}/complete`,
29118
+ {}
28448
29119
  );
29120
+ await writeEntityFile(filePath, completed);
29121
+ await updateCursorHash2(repoRoot, id, completed);
29122
+ successRow = completed;
29123
+ break;
29124
+ } catch (err2) {
29125
+ if (isConflict(err2) && attempt < MAX_OCC_RETRIES2) {
29126
+ try {
29127
+ const freshResponse = await apiGet(`/tasks/${id}`);
29128
+ freshRow = freshResponse.data;
29129
+ await writeEntityFile(filePath, freshRow);
29130
+ } catch {
29131
+ lastErr = err2;
29132
+ break;
29133
+ }
29134
+ lastErr = err2;
29135
+ continue;
29136
+ }
29137
+ lastErr = err2;
29138
+ break;
29139
+ }
29140
+ }
29141
+ if (successRow !== null) {
29142
+ process.stdout.write(JSON.stringify(successRow) + "\n");
29143
+ process.exit(0);
29144
+ }
29145
+ const err = lastErr;
29146
+ if (isConflict(err)) {
29147
+ if (freshRow !== null) {
29148
+ await writeEntityFile(filePath, freshRow);
29149
+ } else if (snapshot !== null) {
29150
+ await writeEntityFile(filePath, snapshot);
28449
29151
  } else {
28450
- await writeEntityFile(pendingMarkerPath(repoRoot, id), {
28451
- entity: "task",
28452
- id,
28453
- checkpoint_id: checkpointId,
28454
- operation: "complete",
28455
- attempted_at: (/* @__PURE__ */ new Date()).toISOString(),
28456
- error: err instanceof Error ? err.message : String(err)
28457
- });
28458
- process.stderr.write(
28459
- `task complete: backend unavailable \u2014 local write kept, pending marker written. Error: ${err instanceof Error ? err.message : String(err)}
29152
+ await deleteEntityFile(filePath);
29153
+ }
29154
+ process.stderr.write(
29155
+ `task complete: conflict \u2014 another writer updated task ${id} concurrently. Re-read the task and retry.
28460
29156
  `
28461
- );
29157
+ );
29158
+ } else if (err instanceof BackendError && err.status < 500) {
29159
+ if (snapshot !== null) {
29160
+ await writeEntityFile(filePath, snapshot);
29161
+ } else {
29162
+ await deleteEntityFile(filePath);
28462
29163
  }
28463
- process.exit(1);
29164
+ process.stderr.write(
29165
+ `task complete: backend rejected (${err.status}): ${err.message}
29166
+ `
29167
+ );
29168
+ } else {
29169
+ await writeEntityFile(pendingMarkerPath(repoRoot, id), {
29170
+ entity: "task",
29171
+ id,
29172
+ checkpoint_id: checkpointId,
29173
+ operation: "complete",
29174
+ attempted_at: (/* @__PURE__ */ new Date()).toISOString(),
29175
+ error: err instanceof Error ? err.message : String(err)
29176
+ });
29177
+ process.stderr.write(
29178
+ `task complete: backend unavailable \u2014 local write kept, pending marker written. Error: ${err instanceof Error ? err.message : String(err)}
29179
+ `
29180
+ );
28464
29181
  }
28465
- process.exit(0);
29182
+ process.exit(1);
28466
29183
  }
28467
29184
  function printTaskHelp() {
28468
29185
  process.stdout.write(
@@ -28498,129 +29215,16 @@ Run 'codebyplan task help' for usage.
28498
29215
  }
28499
29216
  process.exit(1);
28500
29217
  }
29218
+ var MAX_OCC_RETRIES2;
28501
29219
  var init_task = __esm({
28502
29220
  "src/cli/task.ts"() {
28503
29221
  "use strict";
29222
+ init_api();
28504
29223
  init_flags();
28505
29224
  init_state_store();
28506
29225
  init_state_client();
28507
29226
  init_urls();
28508
- }
28509
- });
28510
-
28511
- // src/lib/mcp-client.ts
28512
- async function mcpCall(toolName, args) {
28513
- let accessToken;
28514
- try {
28515
- accessToken = await getAccessToken();
28516
- } catch (err) {
28517
- if (err instanceof NoTokenError) {
28518
- throw new McpError(
28519
- "Not logged in. Run `codebyplan login` to authenticate."
28520
- );
28521
- }
28522
- throw err;
28523
- }
28524
- const body = {
28525
- jsonrpc: "2.0",
28526
- id: 1,
28527
- method: "tools/call",
28528
- params: { name: toolName, arguments: args }
28529
- };
28530
- let res;
28531
- try {
28532
- res = await fetch(mcpEndpoint(), {
28533
- method: "POST",
28534
- headers: {
28535
- "Content-Type": "application/json",
28536
- Accept: "application/json, text/event-stream",
28537
- Authorization: `Bearer ${accessToken}`
28538
- },
28539
- body: JSON.stringify(body),
28540
- signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS3)
28541
- });
28542
- } catch (err) {
28543
- const message = err instanceof Error ? err.message : String(err);
28544
- throw new McpError(`mcp ${toolName} network error: ${message}`);
28545
- }
28546
- if (res.status === 401) {
28547
- throw new McpError(
28548
- "Authentication failed. Run `codebyplan login` to refresh your session.",
28549
- 401
28550
- );
28551
- }
28552
- if (!res.ok) {
28553
- throw new McpError(
28554
- `mcp ${toolName} failed with status ${res.status}`,
28555
- res.status
28556
- );
28557
- }
28558
- const contentType = res.headers.get("content-type") ?? "";
28559
- let envelope;
28560
- if (contentType.includes("text/event-stream")) {
28561
- const text = await res.text();
28562
- envelope = parseSseEnvelope(text, toolName);
28563
- } else {
28564
- envelope = await res.json();
28565
- }
28566
- if (envelope.error) {
28567
- throw new McpError(
28568
- envelope.error.message ?? `mcp ${toolName} protocol error`,
28569
- void 0,
28570
- envelope.error.code !== void 0 ? String(envelope.error.code) : void 0
28571
- );
28572
- }
28573
- const result = envelope.result;
28574
- if (!result) {
28575
- throw new McpError(`mcp ${toolName} returned no result`);
28576
- }
28577
- const textContent = result.content?.find((c) => c.type === "text")?.text;
28578
- if (textContent === void 0) {
28579
- throw new McpError(`mcp ${toolName} returned no text content`);
28580
- }
28581
- if (result.isError === true) {
28582
- throw new McpError(textContent);
28583
- }
28584
- try {
28585
- return JSON.parse(textContent);
28586
- } catch {
28587
- throw new McpError(
28588
- `mcp ${toolName} returned non-JSON payload: ${textContent.slice(0, 200)}`
28589
- );
28590
- }
28591
- }
28592
- function parseSseEnvelope(text, toolName) {
28593
- const frames = text.split("\n\n");
28594
- for (let i = frames.length - 1; i >= 0; i--) {
28595
- const frame = frames[i];
28596
- const dataLines = frame.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim());
28597
- if (dataLines.length === 0) continue;
28598
- const dataText = dataLines.join("\n");
28599
- try {
28600
- return JSON.parse(dataText);
28601
- } catch {
28602
- continue;
28603
- }
28604
- }
28605
- throw new McpError(`mcp ${toolName} returned unparseable SSE body`);
28606
- }
28607
- var REQUEST_TIMEOUT_MS3, McpError;
28608
- var init_mcp_client = __esm({
28609
- "src/lib/mcp-client.ts"() {
28610
- "use strict";
28611
- init_token_refresh();
28612
- init_urls();
28613
- REQUEST_TIMEOUT_MS3 = 12e4;
28614
- McpError = class extends Error {
28615
- status;
28616
- code;
28617
- constructor(message, status, code) {
28618
- super(message);
28619
- this.name = "McpError";
28620
- this.status = status;
28621
- this.code = code;
28622
- }
28623
- };
29227
+ MAX_OCC_RETRIES2 = 3;
28624
29228
  }
28625
29229
  });
28626
29230
 
@@ -28766,6 +29370,7 @@ var init_sync_approvals = __esm({
28766
29370
  var round_exports = {};
28767
29371
  __export(round_exports, {
28768
29372
  GIT_STATUS_CMD: () => GIT_STATUS_CMD,
29373
+ MAX_OCC_RETRIES: () => MAX_OCC_RETRIES3,
28769
29374
  RETRY_DELAY_MS: () => RETRY_DELAY_MS,
28770
29375
  fetchRoundsWithRetry: () => fetchRoundsWithRetry,
28771
29376
  isTransientMcpError: () => isTransientMcpError,
@@ -28777,7 +29382,7 @@ __export(round_exports, {
28777
29382
  setRetryDelayMs: () => setRetryDelayMs
28778
29383
  });
28779
29384
  import { access as access4 } from "node:fs/promises";
28780
- import { join as join21 } from "node:path";
29385
+ import { join as join23 } from "node:path";
28781
29386
  import { execSync as execSync3 } from "node:child_process";
28782
29387
  function setRetryDelayMs(ms) {
28783
29388
  RETRY_DELAY_MS = ms;
@@ -28953,43 +29558,89 @@ async function runRoundUpdate(args) {
28953
29558
  const snakePatch = coerceFieldValues(kebabToSnakeKeys(patchBody));
28954
29559
  const optimistic = { ...snapshot ?? {}, ...snakePatch, id };
28955
29560
  await writeEntityFile(filePath, optimistic);
28956
- try {
28957
- const updated = await apiBackendPatch(
28958
- `${new URL(backendRoundsEndpoint()).pathname}/${id}`,
28959
- snakePatch
28960
- );
28961
- await writeEntityFile(filePath, updated);
28962
- await updateRoundCursorHash(repoRoot, id, updated);
28963
- process.stdout.write(JSON.stringify(updated) + "\n");
28964
- } catch (err) {
28965
- if (err instanceof BackendError && err.status < 500) {
28966
- if (snapshot !== null) {
28967
- await writeEntityFile(filePath, snapshot);
28968
- } else {
28969
- await deleteEntityFile(filePath);
28970
- }
28971
- process.stderr.write(
28972
- `round update: backend rejected (${err.status}): ${err.message}
28973
- `
29561
+ let freshRow = snapshot;
29562
+ let lastErr;
29563
+ let successRow = null;
29564
+ if (freshRow === null) {
29565
+ try {
29566
+ const bootstrapResponse = await apiGet(`/rounds/${id}`);
29567
+ freshRow = bootstrapResponse.data;
29568
+ await writeEntityFile(filePath, freshRow);
29569
+ } catch {
29570
+ }
29571
+ }
29572
+ for (let attempt = 0; attempt <= MAX_OCC_RETRIES3; attempt++) {
29573
+ const currentSeq = freshRow !== null && typeof freshRow.sync_seq === "number" ? freshRow.sync_seq : void 0;
29574
+ const patchWithSeq = currentSeq !== void 0 ? { ...snakePatch, expected_sync_seq: currentSeq } : { ...snakePatch };
29575
+ try {
29576
+ const updated = await apiBackendPatch(
29577
+ `${new URL(backendRoundsEndpoint()).pathname}/${id}`,
29578
+ patchWithSeq
28974
29579
  );
29580
+ await writeEntityFile(filePath, updated);
29581
+ await updateRoundCursorHash(repoRoot, id, updated);
29582
+ successRow = updated;
29583
+ break;
29584
+ } catch (err2) {
29585
+ if (isConflict(err2) && attempt < MAX_OCC_RETRIES3) {
29586
+ try {
29587
+ const freshResponse = await apiGet(`/rounds/${id}`);
29588
+ freshRow = freshResponse.data;
29589
+ await writeEntityFile(filePath, freshRow);
29590
+ } catch {
29591
+ lastErr = err2;
29592
+ break;
29593
+ }
29594
+ lastErr = err2;
29595
+ continue;
29596
+ }
29597
+ lastErr = err2;
29598
+ break;
29599
+ }
29600
+ }
29601
+ if (successRow !== null) {
29602
+ process.stdout.write(JSON.stringify(successRow) + "\n");
29603
+ process.exit(0);
29604
+ }
29605
+ const err = lastErr;
29606
+ if (isConflict(err)) {
29607
+ if (freshRow !== null) {
29608
+ await writeEntityFile(filePath, freshRow);
29609
+ } else if (snapshot !== null) {
29610
+ await writeEntityFile(filePath, snapshot);
28975
29611
  } else {
28976
- await writeEntityFile(pendingMarkerPath(repoRoot, id), {
28977
- entity: "round",
28978
- id,
28979
- checkpoint_id: checkpointId,
28980
- task_id: taskId,
28981
- operation: "update",
28982
- attempted_at: (/* @__PURE__ */ new Date()).toISOString(),
28983
- error: err instanceof Error ? err.message : String(err)
28984
- });
28985
- process.stderr.write(
28986
- `round update: backend unavailable \u2014 local write kept, pending marker written. Error: ${err instanceof Error ? err.message : String(err)}
29612
+ await deleteEntityFile(filePath);
29613
+ }
29614
+ process.stderr.write(
29615
+ `round update: conflict \u2014 another writer updated round ${id} concurrently. Re-read the round and retry.
28987
29616
  `
28988
- );
29617
+ );
29618
+ } else if (err instanceof BackendError && err.status < 500) {
29619
+ if (snapshot !== null) {
29620
+ await writeEntityFile(filePath, snapshot);
29621
+ } else {
29622
+ await deleteEntityFile(filePath);
28989
29623
  }
28990
- process.exit(1);
29624
+ process.stderr.write(
29625
+ `round update: backend rejected (${err.status}): ${err.message}
29626
+ `
29627
+ );
29628
+ } else {
29629
+ await writeEntityFile(pendingMarkerPath(repoRoot, id), {
29630
+ entity: "round",
29631
+ id,
29632
+ checkpoint_id: checkpointId,
29633
+ task_id: taskId,
29634
+ operation: "update",
29635
+ attempted_at: (/* @__PURE__ */ new Date()).toISOString(),
29636
+ error: err instanceof Error ? err.message : String(err)
29637
+ });
29638
+ process.stderr.write(
29639
+ `round update: backend unavailable \u2014 local write kept, pending marker written. Error: ${err instanceof Error ? err.message : String(err)}
29640
+ `
29641
+ );
28991
29642
  }
28992
- process.exit(0);
29643
+ process.exit(1);
28993
29644
  }
28994
29645
  async function runRoundComplete(args) {
28995
29646
  const { flags } = parseFlagsFromArgs(args);
@@ -29022,43 +29673,79 @@ async function runRoundComplete(args) {
29022
29673
  const snapshot = await readEntityFile(filePath);
29023
29674
  const optimistic = { ...snapshot ?? {}, id, status: "completed" };
29024
29675
  await writeEntityFile(filePath, optimistic);
29025
- try {
29026
- const completed = await apiBackendPost(
29027
- `${new URL(backendRoundsEndpoint()).pathname}/${id}/complete`,
29028
- {}
29029
- );
29030
- await writeEntityFile(filePath, completed);
29031
- await updateRoundCursorHash(repoRoot, id, completed);
29032
- process.stdout.write(JSON.stringify(completed) + "\n");
29033
- } catch (err) {
29034
- if (err instanceof BackendError && err.status < 500) {
29035
- if (snapshot !== null) {
29036
- await writeEntityFile(filePath, snapshot);
29037
- } else {
29038
- await deleteEntityFile(filePath);
29039
- }
29040
- process.stderr.write(
29041
- `round complete: backend rejected (${err.status}): ${err.message}
29042
- `
29676
+ let freshRow = snapshot;
29677
+ let lastErr;
29678
+ let successRow = null;
29679
+ for (let attempt = 0; attempt <= MAX_OCC_RETRIES3; attempt++) {
29680
+ try {
29681
+ const completed = await apiBackendPost(
29682
+ `${new URL(backendRoundsEndpoint()).pathname}/${id}/complete`,
29683
+ {}
29043
29684
  );
29685
+ await writeEntityFile(filePath, completed);
29686
+ await updateRoundCursorHash(repoRoot, id, completed);
29687
+ successRow = completed;
29688
+ break;
29689
+ } catch (err2) {
29690
+ if (isConflict(err2) && attempt < MAX_OCC_RETRIES3) {
29691
+ try {
29692
+ const freshResponse = await apiGet(`/rounds/${id}`);
29693
+ freshRow = freshResponse.data;
29694
+ await writeEntityFile(filePath, freshRow);
29695
+ } catch {
29696
+ lastErr = err2;
29697
+ break;
29698
+ }
29699
+ lastErr = err2;
29700
+ continue;
29701
+ }
29702
+ lastErr = err2;
29703
+ break;
29704
+ }
29705
+ }
29706
+ if (successRow !== null) {
29707
+ process.stdout.write(JSON.stringify(successRow) + "\n");
29708
+ process.exit(0);
29709
+ }
29710
+ const err = lastErr;
29711
+ if (isConflict(err)) {
29712
+ if (freshRow !== null) {
29713
+ await writeEntityFile(filePath, freshRow);
29714
+ } else if (snapshot !== null) {
29715
+ await writeEntityFile(filePath, snapshot);
29044
29716
  } else {
29045
- await writeEntityFile(pendingMarkerPath(repoRoot, id), {
29046
- entity: "round",
29047
- id,
29048
- checkpoint_id: checkpointId,
29049
- task_id: taskId,
29050
- operation: "complete",
29051
- attempted_at: (/* @__PURE__ */ new Date()).toISOString(),
29052
- error: err instanceof Error ? err.message : String(err)
29053
- });
29054
- process.stderr.write(
29055
- `round complete: backend unavailable \u2014 local write kept, pending marker written. Error: ${err instanceof Error ? err.message : String(err)}
29717
+ await deleteEntityFile(filePath);
29718
+ }
29719
+ process.stderr.write(
29720
+ `round complete: conflict \u2014 another writer updated round ${id} concurrently. Re-read the round and retry.
29056
29721
  `
29057
- );
29722
+ );
29723
+ } else if (err instanceof BackendError && err.status < 500) {
29724
+ if (snapshot !== null) {
29725
+ await writeEntityFile(filePath, snapshot);
29726
+ } else {
29727
+ await deleteEntityFile(filePath);
29058
29728
  }
29059
- process.exit(1);
29729
+ process.stderr.write(
29730
+ `round complete: backend rejected (${err.status}): ${err.message}
29731
+ `
29732
+ );
29733
+ } else {
29734
+ await writeEntityFile(pendingMarkerPath(repoRoot, id), {
29735
+ entity: "round",
29736
+ id,
29737
+ checkpoint_id: checkpointId,
29738
+ task_id: taskId,
29739
+ operation: "complete",
29740
+ attempted_at: (/* @__PURE__ */ new Date()).toISOString(),
29741
+ error: err instanceof Error ? err.message : String(err)
29742
+ });
29743
+ process.stderr.write(
29744
+ `round complete: backend unavailable \u2014 local write kept, pending marker written. Error: ${err instanceof Error ? err.message : String(err)}
29745
+ `
29746
+ );
29060
29747
  }
29061
- process.exit(0);
29748
+ process.exit(1);
29062
29749
  }
29063
29750
  async function resolveCallerWorktreeId(repoRoot, currentBranch, repoId, overrideId) {
29064
29751
  if (overrideId) {
@@ -29157,7 +29844,7 @@ async function runRoundSyncApprovals(args) {
29157
29844
  "sync-approvals: git status failed; proceeding with empty diff\n"
29158
29845
  );
29159
29846
  }
29160
- const hookPath = join21(
29847
+ const hookPath = join23(
29161
29848
  repoRoot,
29162
29849
  ".claude",
29163
29850
  "hooks",
@@ -29234,7 +29921,7 @@ async function runRoundSyncApprovals(args) {
29234
29921
  process.stdout.write(stdoutPayload + "\n");
29235
29922
  process.exit(0);
29236
29923
  }
29237
- var RETRY_DELAY_MS, GIT_STATUS_CMD;
29924
+ var RETRY_DELAY_MS, MAX_OCC_RETRIES3, GIT_STATUS_CMD;
29238
29925
  var init_round = __esm({
29239
29926
  "src/cli/round.ts"() {
29240
29927
  "use strict";
@@ -29250,6 +29937,7 @@ var init_round = __esm({
29250
29937
  init_state_client();
29251
29938
  init_urls();
29252
29939
  RETRY_DELAY_MS = 1e3;
29940
+ MAX_OCC_RETRIES3 = 3;
29253
29941
  GIT_STATUS_CMD = "git status --short --porcelain --untracked-files=all -z";
29254
29942
  }
29255
29943
  });
@@ -29259,6 +29947,7 @@ var standalone_task_exports = {};
29259
29947
  __export(standalone_task_exports, {
29260
29948
  runStandaloneTaskCommand: () => runStandaloneTaskCommand
29261
29949
  });
29950
+ import { join as join24 } from "node:path";
29262
29951
  async function resolveRepoRoot3() {
29263
29952
  const found = await findCodebyplanConfig(process.cwd());
29264
29953
  if (!found?.contents.repo_id) return null;
@@ -29520,9 +30209,62 @@ async function runStandaloneTaskComplete(args) {
29520
30209
  }
29521
30210
  process.exit(0);
29522
30211
  }
30212
+ async function runStandaloneTaskExport(args) {
30213
+ const { flags } = parseFlagsFromArgs(args);
30214
+ const id = flags.id ?? flags["standalone-task-id"];
30215
+ if (!id) {
30216
+ process.stderr.write(
30217
+ "standalone-task export: --id <standalone-task-id> is required\n"
30218
+ );
30219
+ process.exit(1);
30220
+ }
30221
+ const repoInfo = await resolveRepoRoot3();
30222
+ if (!repoInfo) {
30223
+ process.stderr.write(
30224
+ "standalone-task export: no .codebyplan/repo.json found. Run `codebyplan setup`.\n"
30225
+ );
30226
+ process.exit(1);
30227
+ }
30228
+ const { repoRoot, repoId } = repoInfo;
30229
+ try {
30230
+ const exportData = await buildStandaloneExport(id, repoRoot, repoId);
30231
+ const outDir = join24(
30232
+ repoRoot,
30233
+ ".codebyplan",
30234
+ "exports",
30235
+ "standalone",
30236
+ String(exportData.number)
30237
+ );
30238
+ const summaryMd = renderSummaryMd(exportData);
30239
+ const { exportJsonPath, summaryMdPath } = writeExportArtifacts(
30240
+ outDir,
30241
+ exportData,
30242
+ summaryMd
30243
+ );
30244
+ process.stderr.write(
30245
+ "standalone-task export: note \u2014 standalone_tasks does not have a last_exported_at column; stamp skipped.\n"
30246
+ );
30247
+ process.stdout.write(
30248
+ JSON.stringify({
30249
+ exported: true,
30250
+ path: outDir,
30251
+ export_json: exportJsonPath,
30252
+ summary_md: summaryMdPath,
30253
+ last_exported_at: null
30254
+ }) + "\n"
30255
+ );
30256
+ } catch (err) {
30257
+ process.stderr.write(
30258
+ `standalone-task export: ${err instanceof Error ? err.message : String(err)}
30259
+ `
30260
+ );
30261
+ process.exit(1);
30262
+ }
30263
+ process.exit(0);
30264
+ }
29523
30265
  function printStandaloneTaskHelp() {
29524
30266
  process.stdout.write(
29525
- "\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"
30267
+ "\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 export Export task data to .codebyplan/exports/standalone/{N}/ (--id required)\n\n"
29526
30268
  );
29527
30269
  }
29528
30270
  async function runStandaloneTaskCommand(args) {
@@ -29539,6 +30281,10 @@ async function runStandaloneTaskCommand(args) {
29539
30281
  await runStandaloneTaskComplete(args.slice(1));
29540
30282
  return;
29541
30283
  }
30284
+ if (subcommand === "export") {
30285
+ await runStandaloneTaskExport(args.slice(1));
30286
+ return;
30287
+ }
29542
30288
  if (subcommand === "help" || subcommand === "--help" || subcommand === "-h") {
29543
30289
  printStandaloneTaskHelp();
29544
30290
  process.exit(0);
@@ -29561,6 +30307,7 @@ var init_standalone_task = __esm({
29561
30307
  init_mcp_client();
29562
30308
  init_round();
29563
30309
  init_flags();
30310
+ init_export_writer();
29564
30311
  init_git_utils();
29565
30312
  init_worktree_cache();
29566
30313
  init_resolve_worktree();
@@ -31101,7 +31848,7 @@ var init_session2 = __esm({
31101
31848
 
31102
31849
  // src/lib/migrate-branch-model.ts
31103
31850
  import { readFile as readFile18, writeFile as writeFile15 } from "node:fs/promises";
31104
- import { join as join24 } from "node:path";
31851
+ import { join as join27 } from "node:path";
31105
31852
  import { execSync as execSync4 } from "node:child_process";
31106
31853
  function assertValidBranchName(branch) {
31107
31854
  if (!/^[a-zA-Z0-9/_.-]+$/.test(branch)) {
@@ -31180,12 +31927,12 @@ async function runBranchMigration(opts) {
31180
31927
  if (found) {
31181
31928
  if (found.path.endsWith("/repo.json")) {
31182
31929
  const dir = found.path.slice(0, found.path.lastIndexOf("/"));
31183
- configPath = join24(dir, "git.json");
31930
+ configPath = join27(dir, "git.json");
31184
31931
  } else {
31185
31932
  configPath = found.path;
31186
31933
  }
31187
31934
  } else {
31188
- configPath = join24(cwd, ".codebyplan", "git.json");
31935
+ configPath = join27(cwd, ".codebyplan", "git.json");
31189
31936
  }
31190
31937
  let fileRaw;
31191
31938
  let fileParsed;
@@ -31532,7 +32279,7 @@ var init_branch = __esm({
31532
32279
 
31533
32280
  // src/lib/bump.ts
31534
32281
  import { readFile as readFile19, writeFile as writeFile16, access as access5, readdir as readdir4 } from "node:fs/promises";
31535
- import { join as join25, relative as relative5, resolve as resolve3 } from "node:path";
32282
+ import { join as join28, relative as relative5, resolve as resolve3 } from "node:path";
31536
32283
  import { spawnSync as spawnSync10 } from "node:child_process";
31537
32284
  function parsePnpmWorkspaceGlobs(raw) {
31538
32285
  const lines = raw.split("\n");
@@ -31562,18 +32309,18 @@ async function expandGlob(cwd, glob) {
31562
32309
  if (parts.length !== 2 || parts[1] !== "*") {
31563
32310
  return [];
31564
32311
  }
31565
- const parentDir = join25(cwd, parts[0]);
32312
+ const parentDir = join28(cwd, parts[0]);
31566
32313
  let dirs;
31567
32314
  try {
31568
32315
  const entries = await readdir4(parentDir, { withFileTypes: true });
31569
- dirs = entries.filter((e) => e.isDirectory()).map((e) => join25(parentDir, e.name));
32316
+ dirs = entries.filter((e) => e.isDirectory()).map((e) => join28(parentDir, e.name));
31570
32317
  } catch {
31571
32318
  return [];
31572
32319
  }
31573
32320
  const results = [];
31574
32321
  for (const dir of dirs) {
31575
32322
  try {
31576
- await access5(join25(dir, "package.json"));
32323
+ await access5(join28(dir, "package.json"));
31577
32324
  results.push(dir);
31578
32325
  } catch {
31579
32326
  }
@@ -31584,7 +32331,7 @@ async function buildPackageMap(cwd) {
31584
32331
  const map = /* @__PURE__ */ new Map();
31585
32332
  let globs = [];
31586
32333
  try {
31587
- const raw = await readFile19(join25(cwd, "pnpm-workspace.yaml"), "utf-8");
32334
+ const raw = await readFile19(join28(cwd, "pnpm-workspace.yaml"), "utf-8");
31588
32335
  globs = parsePnpmWorkspaceGlobs(raw);
31589
32336
  } catch {
31590
32337
  }
@@ -31592,7 +32339,7 @@ async function buildPackageMap(cwd) {
31592
32339
  const dirs = await expandGlob(cwd, glob);
31593
32340
  for (const dir of dirs) {
31594
32341
  try {
31595
- const pkgRaw = await readFile19(join25(dir, "package.json"), "utf-8");
32342
+ const pkgRaw = await readFile19(join28(dir, "package.json"), "utf-8");
31596
32343
  const pkg = JSON.parse(pkgRaw);
31597
32344
  const name = pkg.name ?? relative5(cwd, dir);
31598
32345
  map.set(dir, { name, dir });
@@ -31751,7 +32498,7 @@ async function runBump(opts) {
31751
32498
  const changedFiles = (diffResult.stdout ?? "").trim().split("\n").filter(Boolean).map((f) => resolve3(cwd, f));
31752
32499
  const packageMap = await buildPackageMap(cwd);
31753
32500
  const packageDirs = Array.from(packageMap.keys());
31754
- const rootPkgPath = join25(cwd, "package.json");
32501
+ const rootPkgPath = join28(cwd, "package.json");
31755
32502
  const changedPackageDirs = /* @__PURE__ */ new Set();
31756
32503
  for (const absFile of changedFiles) {
31757
32504
  const owner = findOwningPackage(absFile, packageDirs);
@@ -31762,19 +32509,19 @@ async function runBump(opts) {
31762
32509
  const entries = [];
31763
32510
  for (const pkgDir of changedPackageDirs) {
31764
32511
  const pkgInfo = packageMap.get(pkgDir);
31765
- const pkgJsonPath = join25(pkgDir, "package.json");
32512
+ const pkgJsonPath = join28(pkgDir, "package.json");
31766
32513
  if (pkgJsonPath === rootPkgPath) continue;
31767
32514
  const versionFileCandidates = [
31768
32515
  { abs: pkgJsonPath, rel: relative5(cwd, pkgJsonPath).replace(/\\/g, "/") }
31769
32516
  ];
31770
- const tauriConfPath = join25(pkgDir, "src-tauri", "tauri.conf.json");
32517
+ const tauriConfPath = join28(pkgDir, "src-tauri", "tauri.conf.json");
31771
32518
  const tauriRelPath = relative5(cwd, tauriConfPath).replace(/\\/g, "/");
31772
32519
  try {
31773
32520
  await access5(tauriConfPath);
31774
32521
  versionFileCandidates.push({ abs: tauriConfPath, rel: tauriRelPath });
31775
32522
  } catch {
31776
32523
  }
31777
- const appJsonPath = join25(pkgDir, "app.json");
32524
+ const appJsonPath = join28(pkgDir, "app.json");
31778
32525
  const appJsonRelPath = relative5(cwd, appJsonPath).replace(/\\/g, "/");
31779
32526
  try {
31780
32527
  await access5(appJsonPath);
@@ -31846,7 +32593,7 @@ async function runBump(opts) {
31846
32593
  }
31847
32594
  updatedVersionFiles.push(rel);
31848
32595
  }
31849
- const changelogPath = join25(pkgDir, "CHANGELOG.md");
32596
+ const changelogPath = join28(pkgDir, "CHANGELOG.md");
31850
32597
  const changelogUpdated = await prependChangelog(
31851
32598
  changelogPath,
31852
32599
  pkgInfo.name,
@@ -34314,7 +35061,7 @@ __export(version_status_exports, {
34314
35061
  });
34315
35062
  import { execFileSync, execSync as execSync7 } from "node:child_process";
34316
35063
  import { existsSync as existsSync10, readFileSync as readFileSync11 } from "node:fs";
34317
- import { dirname as dirname13, join as join32 } from "node:path";
35064
+ import { dirname as dirname13, join as join35 } from "node:path";
34318
35065
  function fetchLatestVersion() {
34319
35066
  try {
34320
35067
  return execFileSync("npm", ["view", "codebyplan", "version"], {
@@ -34338,9 +35085,9 @@ function detectPackageManager2(gitRoot) {
34338
35085
  let dir = process.cwd();
34339
35086
  const stopAt = gitRoot ?? null;
34340
35087
  while (true) {
34341
- if (existsSync10(join32(dir, "pnpm-lock.yaml"))) return "pnpm";
34342
- if (existsSync10(join32(dir, "yarn.lock"))) return "yarn";
34343
- if (existsSync10(join32(dir, "package-lock.json"))) return "npm";
35088
+ if (existsSync10(join35(dir, "pnpm-lock.yaml"))) return "pnpm";
35089
+ if (existsSync10(join35(dir, "yarn.lock"))) return "yarn";
35090
+ if (existsSync10(join35(dir, "package-lock.json"))) return "npm";
34344
35091
  if (stopAt !== null && dir === stopAt) break;
34345
35092
  const parent = dirname13(dir);
34346
35093
  if (parent === dir) break;
@@ -34360,7 +35107,7 @@ function buildInstallCommand2(pm) {
34360
35107
  }
34361
35108
  async function resolveGuard(gitRoot, currentBranch) {
34362
35109
  if (gitRoot !== null) {
34363
- const canonicalPkgPath = join32(
35110
+ const canonicalPkgPath = join35(
34364
35111
  gitRoot,
34365
35112
  "packages",
34366
35113
  "codebyplan-package",
@@ -34384,10 +35131,10 @@ async function resolveGuard(gitRoot, currentBranch) {
34384
35131
  let gitJsonPath;
34385
35132
  if (found.path.endsWith("/repo.json")) {
34386
35133
  const dir = found.path.slice(0, found.path.lastIndexOf("/"));
34387
- gitJsonPath = join32(dir, "git.json");
35134
+ gitJsonPath = join35(dir, "git.json");
34388
35135
  } else {
34389
35136
  const legacyDir = found.path.slice(0, found.path.lastIndexOf("/"));
34390
- gitJsonPath = join32(legacyDir, ".codebyplan", "git.json");
35137
+ gitJsonPath = join35(legacyDir, ".codebyplan", "git.json");
34391
35138
  }
34392
35139
  const raw = readFileSync11(gitJsonPath, "utf-8");
34393
35140
  const parsed = JSON.parse(raw);
@@ -34472,7 +35219,7 @@ __export(upload_e2e_images_exports, {
34472
35219
  runUploadE2eImagesCommand: () => runUploadE2eImagesCommand
34473
35220
  });
34474
35221
  import { readFile as readFile20 } from "node:fs/promises";
34475
- import { join as join33, basename as basename2, resolve as resolve10 } from "node:path";
35222
+ import { join as join36, basename as basename2, resolve as resolve10 } from "node:path";
34476
35223
  import { execSync as execSync8 } from "node:child_process";
34477
35224
  function baseUrl2() {
34478
35225
  return (process.env.CODEBYPLAN_API_URL ?? "https://www.codebyplan.com").replace(/\/$/, "");
@@ -34507,7 +35254,7 @@ function parseArgs(args) {
34507
35254
  async function readE2eConfig(projectPath) {
34508
35255
  try {
34509
35256
  const raw = await readFile20(
34510
- join33(projectPath, ".codebyplan", "e2e.json"),
35257
+ join36(projectPath, ".codebyplan", "e2e.json"),
34511
35258
  "utf-8"
34512
35259
  );
34513
35260
  return JSON.parse(raw);
@@ -34548,7 +35295,7 @@ function collectPngsFromGitDiff(projectPath, frameworkName, frameworkConfig, bas
34548
35295
  continue;
34549
35296
  const isNew = status === "A";
34550
35297
  results.push({
34551
- absolutePath: join33(projectPath, filePath),
35298
+ absolutePath: join36(projectPath, filePath),
34552
35299
  filename: basename2(filePath),
34553
35300
  framework: frameworkName,
34554
35301
  is_new: isNew
@@ -34724,7 +35471,7 @@ __export(arch_map_exports, {
34724
35471
  });
34725
35472
  import { readFile as readFile21, writeFile as writeFile17 } from "node:fs/promises";
34726
35473
  import { existsSync as existsSync11, readdirSync as readdirSync4 } from "node:fs";
34727
- import { join as join34 } from "node:path";
35474
+ import { join as join37 } from "node:path";
34728
35475
  import { spawnSync as spawnSync12 } from "node:child_process";
34729
35476
  function normalizeModulePath(modulePath) {
34730
35477
  return modulePath.replace(/\/+$/, "");
@@ -34749,7 +35496,7 @@ async function resolveCodebyplanDir(startDir) {
34749
35496
  }
34750
35497
  }
34751
35498
  async function readArchitectureConfig(codebyplanDir) {
34752
- const filePath = join34(codebyplanDir, "architecture.json");
35499
+ const filePath = join37(codebyplanDir, "architecture.json");
34753
35500
  try {
34754
35501
  const raw = await readFile21(filePath, "utf-8");
34755
35502
  const parsed = JSON.parse(raw);
@@ -34762,7 +35509,7 @@ async function readArchitectureConfig(codebyplanDir) {
34762
35509
  }
34763
35510
  }
34764
35511
  async function writeArchitectureConfig(codebyplanDir, config) {
34765
- const filePath = join34(codebyplanDir, "architecture.json");
35512
+ const filePath = join37(codebyplanDir, "architecture.json");
34766
35513
  await writeFile17(filePath, JSON.stringify(config, null, 2) + "\n", "utf-8");
34767
35514
  }
34768
35515
  function resolveGitSha(modulePath, cwd) {
@@ -34781,7 +35528,7 @@ function resolveGitSha(modulePath, cwd) {
34781
35528
  }
34782
35529
  async function discoverModulePaths(projectRoot) {
34783
35530
  const paths = [];
34784
- const workspacePath = join34(projectRoot, "pnpm-workspace.yaml");
35531
+ const workspacePath = join37(projectRoot, "pnpm-workspace.yaml");
34785
35532
  let patterns = [];
34786
35533
  try {
34787
35534
  const raw = await readFile21(workspacePath, "utf-8");
@@ -34808,7 +35555,7 @@ async function discoverModulePaths(projectRoot) {
34808
35555
  for (const pattern of patterns) {
34809
35556
  if (pattern.endsWith("/*")) {
34810
35557
  const dir = pattern.slice(0, -2);
34811
- const absDir = join34(projectRoot, dir);
35558
+ const absDir = join37(projectRoot, dir);
34812
35559
  try {
34813
35560
  if (existsSync11(absDir)) {
34814
35561
  const entries = readdirSync4(absDir, { withFileTypes: true });
@@ -34821,7 +35568,7 @@ async function discoverModulePaths(projectRoot) {
34821
35568
  } catch {
34822
35569
  }
34823
35570
  } else if (!pattern.includes("*")) {
34824
- const absPath = join34(projectRoot, pattern);
35571
+ const absPath = join37(projectRoot, pattern);
34825
35572
  if (existsSync11(absPath)) {
34826
35573
  paths.push(pattern);
34827
35574
  }
@@ -34829,7 +35576,7 @@ async function discoverModulePaths(projectRoot) {
34829
35576
  }
34830
35577
  const crossCutting = ["supabase", ".github", "scripts"];
34831
35578
  for (const dir of crossCutting) {
34832
- const absPath = join34(projectRoot, dir);
35579
+ const absPath = join37(projectRoot, dir);
34833
35580
  try {
34834
35581
  if (existsSync11(absPath)) {
34835
35582
  paths.push(dir);
@@ -35015,7 +35762,7 @@ async function runStamp(modulePath, sha, depthArg, projectRoot) {
35015
35762
  }
35016
35763
  await writeArchitectureConfig(codebyplanDir, config);
35017
35764
  const stampedEntry = existingIndex >= 0 ? config.modules[existingIndex] : config.modules[config.modules.length - 1];
35018
- const mapAbsPath = join34(repoRoot, stampedEntry.map_file);
35765
+ const mapAbsPath = join37(repoRoot, stampedEntry.map_file);
35019
35766
  let mapContent = null;
35020
35767
  try {
35021
35768
  mapContent = await readFile21(mapAbsPath, "utf-8");
@@ -35221,18 +35968,18 @@ var init_worktree_port_resolver = __esm({
35221
35968
 
35222
35969
  // src/lib/migrate-local-config.ts
35223
35970
  import { mkdir as mkdir9, readFile as readFile22, unlink as unlink5, writeFile as writeFile18 } from "node:fs/promises";
35224
- import { join as join35 } from "node:path";
35971
+ import { join as join38 } from "node:path";
35225
35972
  function legacySharedPath(projectPath) {
35226
- return join35(projectPath, ".codebyplan.json");
35973
+ return join38(projectPath, ".codebyplan.json");
35227
35974
  }
35228
35975
  function legacyLocalPath(projectPath) {
35229
- return join35(projectPath, ".codebyplan.local.json");
35976
+ return join38(projectPath, ".codebyplan.local.json");
35230
35977
  }
35231
35978
  function newDirPath(projectPath) {
35232
- return join35(projectPath, ".codebyplan");
35979
+ return join38(projectPath, ".codebyplan");
35233
35980
  }
35234
35981
  function sentinelPath(projectPath) {
35235
- return join35(projectPath, ".codebyplan", "repo.json");
35982
+ return join38(projectPath, ".codebyplan", "repo.json");
35236
35983
  }
35237
35984
  async function statSafe(p) {
35238
35985
  const { stat: stat3 } = await import("node:fs/promises");
@@ -35326,7 +36073,7 @@ async function runLocalMigration(projectPath) {
35326
36073
  if ("organization_id" in cfg) repoJson.organization_id = cfg.organization_id;
35327
36074
  if ("project_id" in cfg) repoJson.project_id = cfg.project_id;
35328
36075
  await writeFile18(
35329
- join35(projectPath, ".codebyplan", "repo.json"),
36076
+ join38(projectPath, ".codebyplan", "repo.json"),
35330
36077
  JSON.stringify(repoJson, null, 2) + "\n",
35331
36078
  "utf-8"
35332
36079
  );
@@ -35339,7 +36086,7 @@ async function runLocalMigration(projectPath) {
35339
36086
  if ("port_allocations" in cfg)
35340
36087
  serverJson.port_allocations = cfg.port_allocations;
35341
36088
  await writeFile18(
35342
- join35(projectPath, ".codebyplan", "server.json"),
36089
+ join38(projectPath, ".codebyplan", "server.json"),
35343
36090
  JSON.stringify(serverJson, null, 2) + "\n",
35344
36091
  "utf-8"
35345
36092
  );
@@ -35348,7 +36095,7 @@ async function runLocalMigration(projectPath) {
35348
36095
  if ("git_branch" in cfg) gitJson.git_branch = cfg.git_branch;
35349
36096
  if ("branch_config" in cfg) gitJson.branch_config = cfg.branch_config;
35350
36097
  await writeFile18(
35351
- join35(projectPath, ".codebyplan", "git.json"),
36098
+ join38(projectPath, ".codebyplan", "git.json"),
35352
36099
  JSON.stringify(gitJson, null, 2) + "\n",
35353
36100
  "utf-8"
35354
36101
  );
@@ -35356,35 +36103,35 @@ async function runLocalMigration(projectPath) {
35356
36103
  const shipmentJson = {};
35357
36104
  if ("shipment" in cfg) shipmentJson.shipment = cfg.shipment;
35358
36105
  await writeFile18(
35359
- join35(projectPath, ".codebyplan", "shipment.json"),
36106
+ join38(projectPath, ".codebyplan", "shipment.json"),
35360
36107
  JSON.stringify(shipmentJson, null, 2) + "\n",
35361
36108
  "utf-8"
35362
36109
  );
35363
36110
  filesChanged.push(".codebyplan/shipment.json");
35364
36111
  const vendorJson = {};
35365
36112
  await writeFile18(
35366
- join35(projectPath, ".codebyplan", "vendor.json"),
36113
+ join38(projectPath, ".codebyplan", "vendor.json"),
35367
36114
  JSON.stringify(vendorJson, null, 2) + "\n",
35368
36115
  "utf-8"
35369
36116
  );
35370
36117
  filesChanged.push(".codebyplan/vendor.json");
35371
36118
  const e2eJson = {};
35372
36119
  await writeFile18(
35373
- join35(projectPath, ".codebyplan", "e2e.json"),
36120
+ join38(projectPath, ".codebyplan", "e2e.json"),
35374
36121
  JSON.stringify(e2eJson, null, 2) + "\n",
35375
36122
  "utf-8"
35376
36123
  );
35377
36124
  filesChanged.push(".codebyplan/e2e.json");
35378
36125
  const eslintJson = {};
35379
36126
  await writeFile18(
35380
- join35(projectPath, ".codebyplan", "eslint.json"),
36127
+ join38(projectPath, ".codebyplan", "eslint.json"),
35381
36128
  JSON.stringify(eslintJson, null, 2) + "\n",
35382
36129
  "utf-8"
35383
36130
  );
35384
36131
  filesChanged.push(".codebyplan/eslint.json");
35385
36132
  if (!deviceWrittenByHelper) {
35386
36133
  await writeFile18(
35387
- join35(projectPath, ".codebyplan", "device.local.json"),
36134
+ join38(projectPath, ".codebyplan", "device.local.json"),
35388
36135
  JSON.stringify({ device_id: deviceId }, null, 2) + "\n",
35389
36136
  "utf-8"
35390
36137
  );
@@ -35396,7 +36143,7 @@ async function runLocalMigration(projectPath) {
35396
36143
  "Migration write incomplete: .codebyplan/repo.json was not persisted. Re-run migration to retry from a clean state."
35397
36144
  );
35398
36145
  }
35399
- const gitignorePath = join35(projectPath, ".gitignore");
36146
+ const gitignorePath = join38(projectPath, ".gitignore");
35400
36147
  try {
35401
36148
  const gitignoreContent = await readFile22(gitignorePath, "utf-8");
35402
36149
  const legacyLine = ".codebyplan.local.json";
@@ -35460,7 +36207,7 @@ __export(config_exports, {
35460
36207
  runConfigMigrate: () => runConfigMigrate
35461
36208
  });
35462
36209
  import { mkdir as mkdir10, readFile as readFile23, writeFile as writeFile19 } from "node:fs/promises";
35463
- import { join as join36 } from "node:path";
36210
+ import { join as join39 } from "node:path";
35464
36211
  async function runConfig() {
35465
36212
  const flags = parseFlags(3);
35466
36213
  const dryRun = hasFlag("dry-run", 3);
@@ -35493,7 +36240,7 @@ async function runConfig() {
35493
36240
  console.log("\n Config complete.\n");
35494
36241
  }
35495
36242
  async function syncConfigToFile(repoId, projectPath, dryRun) {
35496
- const codebyplanDir = join36(projectPath, ".codebyplan");
36243
+ const codebyplanDir = join39(projectPath, ".codebyplan");
35497
36244
  const {
35498
36245
  resolvedWorktreeId,
35499
36246
  portAllocations,
@@ -35586,7 +36333,7 @@ async function syncConfigToFile(repoId, projectPath, dryRun) {
35586
36333
  ];
35587
36334
  let anyUpdated = false;
35588
36335
  for (const { name, payload, createOnly } of files) {
35589
- const filePath = join36(codebyplanDir, name);
36336
+ const filePath = join39(codebyplanDir, name);
35590
36337
  const newJson = JSON.stringify(payload, null, 2) + "\n";
35591
36338
  let currentJson = "";
35592
36339
  try {
@@ -35606,7 +36353,7 @@ async function syncConfigToFile(repoId, projectPath, dryRun) {
35606
36353
  async function readRepoConfig(projectPath) {
35607
36354
  try {
35608
36355
  const raw = await readFile23(
35609
- join36(projectPath, ".codebyplan", "repo.json"),
36356
+ join39(projectPath, ".codebyplan", "repo.json"),
35610
36357
  "utf-8"
35611
36358
  );
35612
36359
  return JSON.parse(raw);
@@ -35617,7 +36364,7 @@ async function readRepoConfig(projectPath) {
35617
36364
  async function readServerConfig(projectPath) {
35618
36365
  try {
35619
36366
  const raw = await readFile23(
35620
- join36(projectPath, ".codebyplan", "server.json"),
36367
+ join39(projectPath, ".codebyplan", "server.json"),
35621
36368
  "utf-8"
35622
36369
  );
35623
36370
  return JSON.parse(raw);
@@ -35628,7 +36375,7 @@ async function readServerConfig(projectPath) {
35628
36375
  async function readGitConfig2(projectPath) {
35629
36376
  try {
35630
36377
  const raw = await readFile23(
35631
- join36(projectPath, ".codebyplan", "git.json"),
36378
+ join39(projectPath, ".codebyplan", "git.json"),
35632
36379
  "utf-8"
35633
36380
  );
35634
36381
  return JSON.parse(raw);
@@ -35639,7 +36386,7 @@ async function readGitConfig2(projectPath) {
35639
36386
  async function readShipmentConfig2(projectPath) {
35640
36387
  try {
35641
36388
  const raw = await readFile23(
35642
- join36(projectPath, ".codebyplan", "shipment.json"),
36389
+ join39(projectPath, ".codebyplan", "shipment.json"),
35643
36390
  "utf-8"
35644
36391
  );
35645
36392
  return JSON.parse(raw);
@@ -35650,7 +36397,7 @@ async function readShipmentConfig2(projectPath) {
35650
36397
  async function readVendorConfig(projectPath) {
35651
36398
  try {
35652
36399
  const raw = await readFile23(
35653
- join36(projectPath, ".codebyplan", "vendor.json"),
36400
+ join39(projectPath, ".codebyplan", "vendor.json"),
35654
36401
  "utf-8"
35655
36402
  );
35656
36403
  return JSON.parse(raw);
@@ -35661,7 +36408,7 @@ async function readVendorConfig(projectPath) {
35661
36408
  async function readE2eConfig2(projectPath) {
35662
36409
  try {
35663
36410
  const raw = await readFile23(
35664
- join36(projectPath, ".codebyplan", "e2e.json"),
36411
+ join39(projectPath, ".codebyplan", "e2e.json"),
35665
36412
  "utf-8"
35666
36413
  );
35667
36414
  return JSON.parse(raw);
@@ -35672,7 +36419,7 @@ async function readE2eConfig2(projectPath) {
35672
36419
  async function readServerLocalConfig(projectPath) {
35673
36420
  try {
35674
36421
  const raw = await readFile23(
35675
- join36(projectPath, ".codebyplan", "server.local.json"),
36422
+ join39(projectPath, ".codebyplan", "server.local.json"),
35676
36423
  "utf-8"
35677
36424
  );
35678
36425
  return JSON.parse(raw);
@@ -35745,7 +36492,7 @@ var init_config = __esm({
35745
36492
 
35746
36493
  // src/lib/server-detect.ts
35747
36494
  import { readFile as readFile24, readdir as readdir5, access as access6 } from "node:fs/promises";
35748
- import { join as join37 } from "node:path";
36495
+ import { join as join40 } from "node:path";
35749
36496
  async function fileExists4(filePath) {
35750
36497
  try {
35751
36498
  await access6(filePath);
@@ -35756,8 +36503,8 @@ async function fileExists4(filePath) {
35756
36503
  }
35757
36504
  function detectPackageManager3(dir) {
35758
36505
  return (async () => {
35759
- if (await fileExists4(join37(dir, "pnpm-lock.yaml"))) return "pnpm";
35760
- if (await fileExists4(join37(dir, "yarn.lock"))) return "yarn";
36506
+ if (await fileExists4(join40(dir, "pnpm-lock.yaml"))) return "pnpm";
36507
+ if (await fileExists4(join40(dir, "yarn.lock"))) return "yarn";
35761
36508
  return "npm";
35762
36509
  })();
35763
36510
  }
@@ -35789,12 +36536,12 @@ function detectPortFromScripts(pkg) {
35789
36536
  return null;
35790
36537
  }
35791
36538
  async function isMonorepo(dir) {
35792
- return await fileExists4(join37(dir, "turbo.json")) || await fileExists4(join37(dir, "pnpm-workspace.yaml"));
36539
+ return await fileExists4(join40(dir, "turbo.json")) || await fileExists4(join40(dir, "pnpm-workspace.yaml"));
35793
36540
  }
35794
36541
  async function detectServers(projectPath) {
35795
36542
  let pkg;
35796
36543
  try {
35797
- const raw = await readFile24(join37(projectPath, "package.json"), "utf-8");
36544
+ const raw = await readFile24(join40(projectPath, "package.json"), "utf-8");
35798
36545
  pkg = JSON.parse(raw);
35799
36546
  } catch {
35800
36547
  return {
@@ -35810,13 +36557,13 @@ async function detectServers(projectPath) {
35810
36557
  const mono = await isMonorepo(projectPath);
35811
36558
  const servers = [];
35812
36559
  if (mono) {
35813
- const appsDir = join37(projectPath, "apps");
36560
+ const appsDir = join40(projectPath, "apps");
35814
36561
  try {
35815
36562
  const entries = await readdir5(appsDir, { withFileTypes: true });
35816
36563
  const sorted = [...entries].sort((a, b) => a.name.localeCompare(b.name));
35817
36564
  for (const entry of sorted) {
35818
36565
  if (!entry.isDirectory()) continue;
35819
- const appPkgPath = join37(appsDir, entry.name, "package.json");
36566
+ const appPkgPath = join40(appsDir, entry.name, "package.json");
35820
36567
  try {
35821
36568
  const appRaw = await readFile24(appPkgPath, "utf-8");
35822
36569
  const appPkg = JSON.parse(appRaw);
@@ -35982,7 +36729,7 @@ __export(ports_exports, {
35982
36729
  runPorts: () => runPorts
35983
36730
  });
35984
36731
  import { mkdir as mkdir11, readFile as readFile26, writeFile as writeFile20 } from "node:fs/promises";
35985
- import { join as join38 } from "node:path";
36732
+ import { join as join41 } from "node:path";
35986
36733
  function printDetectionResult(result, projectPath) {
35987
36734
  console.log(`
35988
36735
  CodeByPlan Ports - List`);
@@ -36138,8 +36885,8 @@ async function writeServerLocalConfig(repoId, projectPath, dryRun) {
36138
36885
  // and ServerLocalConfig.port_allocations is typed the same — honest end-to-end.
36139
36886
  port_allocations: portAllocations
36140
36887
  };
36141
- const codebyplanDir = join38(projectPath, ".codebyplan");
36142
- const filePath = join38(codebyplanDir, "server.local.json");
36888
+ const codebyplanDir = join41(projectPath, ".codebyplan");
36889
+ const filePath = join41(codebyplanDir, "server.local.json");
36143
36890
  const newJson = JSON.stringify(payload, null, 2) + "\n";
36144
36891
  let currentJson = "";
36145
36892
  try {
@@ -36161,8 +36908,8 @@ async function writeServerLocalConfig(repoId, projectPath, dryRun) {
36161
36908
  );
36162
36909
  }
36163
36910
  async function provisionE2eEnv(projectPath, dryRun) {
36164
- const relSource = join38("apps", "web", ".env.local");
36165
- const sourcePath = join38(projectPath, relSource);
36911
+ const relSource = join41("apps", "web", ".env.local");
36912
+ const sourcePath = join41(projectPath, relSource);
36166
36913
  let sourceRaw;
36167
36914
  try {
36168
36915
  sourceRaw = await readFile26(sourcePath, "utf-8");
@@ -36194,8 +36941,8 @@ async function provisionE2eEnv(projectPath, dryRun) {
36194
36941
  );
36195
36942
  return;
36196
36943
  }
36197
- const codebyplanDir = join38(projectPath, ".codebyplan");
36198
- const filePath = join38(codebyplanDir, "e2e.env");
36944
+ const codebyplanDir = join41(projectPath, ".codebyplan");
36945
+ const filePath = join41(codebyplanDir, "e2e.env");
36199
36946
  const newContent = lines.join("\n") + "\n";
36200
36947
  let currentContent = "";
36201
36948
  try {
@@ -36480,7 +37227,7 @@ __export(docs_exports, {
36480
37227
  });
36481
37228
  import { existsSync as existsSync13 } from "node:fs";
36482
37229
  import { mkdir as mkdir12, readFile as readFile27, readdir as readdir6, rm as rm2, writeFile as writeFile21 } from "node:fs/promises";
36483
- import { dirname as dirname14, isAbsolute as isAbsolute2, join as join39, relative as relative7, sep as sep2 } from "node:path";
37230
+ import { dirname as dirname14, isAbsolute as isAbsolute2, join as join42, relative as relative7, sep as sep2 } from "node:path";
36484
37231
  function selectDependencies(deps) {
36485
37232
  const byName = /* @__PURE__ */ new Map();
36486
37233
  for (const dep of deps) {
@@ -36534,12 +37281,12 @@ async function mapWithConcurrency(items, limit, fn) {
36534
37281
  }
36535
37282
  async function resolveExactVersion(projectPath, dep) {
36536
37283
  const candidateDirs = [projectPath];
36537
- const sourceDir = join39(projectPath, dirname14(dep.sourcePath));
37284
+ const sourceDir = join42(projectPath, dirname14(dep.sourcePath));
36538
37285
  if (sourceDir !== projectPath) candidateDirs.push(sourceDir);
36539
37286
  for (const base of candidateDirs) {
36540
37287
  try {
36541
37288
  const raw = await readFile27(
36542
- join39(base, "node_modules", dep.name, "package.json"),
37289
+ join42(base, "node_modules", dep.name, "package.json"),
36543
37290
  "utf-8"
36544
37291
  );
36545
37292
  const pkg = JSON.parse(raw);
@@ -36554,7 +37301,7 @@ async function resolveExactVersion(projectPath, dep) {
36554
37301
  async function readVendorDocsPath(projectPath) {
36555
37302
  try {
36556
37303
  const raw = await readFile27(
36557
- join39(projectPath, ".codebyplan", "vendor.json"),
37304
+ join42(projectPath, ".codebyplan", "vendor.json"),
36558
37305
  "utf-8"
36559
37306
  );
36560
37307
  const parsed = JSON.parse(raw);
@@ -36567,7 +37314,7 @@ async function readVendorDocsPath(projectPath) {
36567
37314
  }
36568
37315
  async function resolveDocsDir(projectPath, flags) {
36569
37316
  const configured = flags["dir"] ?? await readVendorDocsPath(projectPath) ?? DEFAULT_DOCS_DIR;
36570
- const absDir = isAbsolute2(configured) ? configured : join39(projectPath, configured);
37317
+ const absDir = isAbsolute2(configured) ? configured : join42(projectPath, configured);
36571
37318
  const rel = relative7(projectPath, absDir);
36572
37319
  const relDir = rel === "" || rel.startsWith("..") ? null : rel.split(sep2).join("/");
36573
37320
  return { absDir, relDir };
@@ -36576,7 +37323,7 @@ async function readDocsLock(absDir) {
36576
37323
  const empty = { generated_at: "", libraries: {} };
36577
37324
  let raw;
36578
37325
  try {
36579
- raw = await readFile27(join39(absDir, LOCK_FILE), "utf-8");
37326
+ raw = await readFile27(join42(absDir, LOCK_FILE), "utf-8");
36580
37327
  } catch {
36581
37328
  return empty;
36582
37329
  }
@@ -36672,7 +37419,7 @@ function buildTopIndex(outcomes) {
36672
37419
  }
36673
37420
  async function ensureDocsGitignoreEntry(projectPath, relDir, dryRun) {
36674
37421
  const entry = `/${relDir}/`;
36675
- const gitignorePath = join39(projectPath, ".gitignore");
37422
+ const gitignorePath = join42(projectPath, ".gitignore");
36676
37423
  let existing = "";
36677
37424
  try {
36678
37425
  existing = await readFile27(gitignorePath, "utf-8");
@@ -36695,7 +37442,7 @@ async function markUncovered(dep, exactVersion, ctx) {
36695
37442
  console.log(
36696
37443
  ` uncovered ${dep.name}@${exactVersion || "?"} \u2014 no docs in the library mirror`
36697
37444
  );
36698
- const libPath = join39(ctx.absDir, libDirName(dep.name));
37445
+ const libPath = join42(ctx.absDir, libDirName(dep.name));
36699
37446
  if (existsSync13(libPath)) {
36700
37447
  if (ctx.dryRun) {
36701
37448
  console.log(` would remove stale mirror dir ${libPath}`);
@@ -36748,14 +37495,14 @@ async function syncOneLibrary(dep, ctx) {
36748
37495
  }
36749
37496
  }
36750
37497
  const lockEntry = ctx.lock.libraries[dep.name];
36751
- const libPath = join39(ctx.absDir, libDirName(dep.name));
36752
- const versionPath = join39(libPath, manifest.resolved_version);
37498
+ const libPath = join42(ctx.absDir, libDirName(dep.name));
37499
+ const versionPath = join42(libPath, manifest.resolved_version);
36753
37500
  if (manifestMatchesLock(manifest, lockEntry)) {
36754
37501
  console.log(` unchanged ${dep.name}@${manifest.resolved_version}`);
36755
- if (!ctx.dryRun && !existsSync13(join39(libPath, "INDEX.md"))) {
37502
+ if (!ctx.dryRun && !existsSync13(join42(libPath, "INDEX.md"))) {
36756
37503
  await mkdir12(libPath, { recursive: true });
36757
37504
  await writeFile21(
36758
- join39(libPath, "INDEX.md"),
37505
+ join42(libPath, "INDEX.md"),
36759
37506
  buildLibIndex(dep.name, manifest),
36760
37507
  "utf-8"
36761
37508
  );
@@ -36777,7 +37524,7 @@ async function syncOneLibrary(dep, ctx) {
36777
37524
  );
36778
37525
  continue;
36779
37526
  }
36780
- const target = join39(versionPath, rel);
37527
+ const target = join42(versionPath, rel);
36781
37528
  if (sameVersionLockFiles[file.path] === file.content_hash && existsSync13(target)) {
36782
37529
  continue;
36783
37530
  }
@@ -36795,7 +37542,7 @@ async function syncOneLibrary(dep, ctx) {
36795
37542
  const rel = sanitizeDocPath(lockedPath);
36796
37543
  if (rel === null) continue;
36797
37544
  removedFiles++;
36798
- if (!ctx.dryRun) await rm2(join39(versionPath, rel), { force: true });
37545
+ if (!ctx.dryRun) await rm2(join42(versionPath, rel), { force: true });
36799
37546
  }
36800
37547
  }
36801
37548
  let removedVersionDirs = 0;
@@ -36810,13 +37557,13 @@ async function syncOneLibrary(dep, ctx) {
36810
37557
  }
36811
37558
  removedVersionDirs++;
36812
37559
  if (!ctx.dryRun) {
36813
- await rm2(join39(libPath, entry.name), { recursive: true, force: true });
37560
+ await rm2(join42(libPath, entry.name), { recursive: true, force: true });
36814
37561
  }
36815
37562
  }
36816
37563
  if (!ctx.dryRun) {
36817
37564
  await mkdir12(libPath, { recursive: true });
36818
37565
  await writeFile21(
36819
- join39(libPath, "INDEX.md"),
37566
+ join42(libPath, "INDEX.md"),
36820
37567
  buildLibIndex(dep.name, manifest),
36821
37568
  "utf-8"
36822
37569
  );
@@ -36870,7 +37617,7 @@ async function runDocsSync() {
36870
37617
  );
36871
37618
  if (!dryRun) {
36872
37619
  await mkdir12(absDir, { recursive: true });
36873
- await writeFile21(join39(absDir, "INDEX.md"), buildTopIndex(outcomes), "utf-8");
37620
+ await writeFile21(join42(absDir, "INDEX.md"), buildTopIndex(outcomes), "utf-8");
36874
37621
  const libraries = {};
36875
37622
  for (const o of outcomes) {
36876
37623
  if (o.kind === "synced" || o.kind === "unchanged") {
@@ -36896,7 +37643,7 @@ async function runDocsSync() {
36896
37643
  libraries: sortedLibraries
36897
37644
  };
36898
37645
  await writeFile21(
36899
- join39(absDir, LOCK_FILE),
37646
+ join42(absDir, LOCK_FILE),
36900
37647
  JSON.stringify(newLock, null, 2) + "\n",
36901
37648
  "utf-8"
36902
37649
  );
@@ -36938,7 +37685,7 @@ async function countFilesRecursively(dirPath) {
36938
37685
  let count = 0;
36939
37686
  for (const entry of entries) {
36940
37687
  if (entry.isDirectory()) {
36941
- count += await countFilesRecursively(join39(dirPath, entry.name));
37688
+ count += await countFilesRecursively(join42(dirPath, entry.name));
36942
37689
  } else if (entry.isFile()) {
36943
37690
  count++;
36944
37691
  }
@@ -36955,7 +37702,7 @@ async function runDocsStatus() {
36955
37702
  `);
36956
37703
  let raw;
36957
37704
  try {
36958
- raw = await readFile27(join39(absDir, LOCK_FILE), "utf-8");
37705
+ raw = await readFile27(join42(absDir, LOCK_FILE), "utf-8");
36959
37706
  } catch {
36960
37707
  console.log(
36961
37708
  ` No ${LOCK_FILE} found \u2014 run \`codebyplan docs sync\` first.
@@ -36984,7 +37731,7 @@ async function runDocsStatus() {
36984
37731
  let outOfSync = 0;
36985
37732
  for (const name of names) {
36986
37733
  const entry = lock.libraries[name];
36987
- const versionPath = join39(absDir, libDirName(name), entry.resolved_version);
37734
+ const versionPath = join42(absDir, libDirName(name), entry.resolved_version);
36988
37735
  const expected = Object.keys(entry.files).length;
36989
37736
  if (!existsSync13(versionPath)) {
36990
37737
  outOfSync++;
@@ -37071,8 +37818,8 @@ var init_docs = __esm({
37071
37818
  });
37072
37819
 
37073
37820
  // src/lib/check-baseline.ts
37074
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync7 } from "node:fs";
37075
- import { join as join40 } from "node:path";
37821
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync8 } from "node:fs";
37822
+ import { join as join43 } from "node:path";
37076
37823
  function emptyBaseline() {
37077
37824
  return {
37078
37825
  lint: { known_failing: [] },
@@ -37082,7 +37829,7 @@ function emptyBaseline() {
37082
37829
  };
37083
37830
  }
37084
37831
  function loadBaseline(projectRoot) {
37085
- const filePath = join40(projectRoot, BASELINE_FILENAME);
37832
+ const filePath = join43(projectRoot, BASELINE_FILENAME);
37086
37833
  try {
37087
37834
  const raw = readFileSync12(filePath, "utf-8");
37088
37835
  const parsed = JSON.parse(raw);
@@ -37106,8 +37853,8 @@ function loadBaseline(projectRoot) {
37106
37853
  }
37107
37854
  }
37108
37855
  function saveBaseline(projectRoot, baseline) {
37109
- const filePath = join40(projectRoot, BASELINE_FILENAME);
37110
- writeFileSync7(filePath, JSON.stringify(baseline, null, 2) + "\n", "utf-8");
37856
+ const filePath = join43(projectRoot, BASELINE_FILENAME);
37857
+ writeFileSync8(filePath, JSON.stringify(baseline, null, 2) + "\n", "utf-8");
37111
37858
  }
37112
37859
  function diffBaseline(check, currentFailingPackages, baseline) {
37113
37860
  const knownFailing = new Set(baseline[check].known_failing);
@@ -37156,7 +37903,7 @@ var init_check_baseline = __esm({
37156
37903
 
37157
37904
  // src/lib/check.ts
37158
37905
  import { readFileSync as readFileSync13, existsSync as existsSync14 } from "node:fs";
37159
- import { join as join41 } from "node:path";
37906
+ import { join as join44 } from "node:path";
37160
37907
  import { spawnSync as spawnSync13 } from "node:child_process";
37161
37908
  function hasSentinelValue(arrays) {
37162
37909
  const SENTINELS = /* @__PURE__ */ new Set([
@@ -37224,9 +37971,9 @@ function parseFailingPackagesFromSummary(summaryPath) {
37224
37971
  return Array.from(failing).sort();
37225
37972
  }
37226
37973
  function resolveTurboBin(projectRoot) {
37227
- const localBin = join41(projectRoot, "node_modules", ".bin", "turbo");
37974
+ const localBin = join44(projectRoot, "node_modules", ".bin", "turbo");
37228
37975
  if (existsSync14(localBin)) return localBin;
37229
- const workspaceRootBin = join41(
37976
+ const workspaceRootBin = join44(
37230
37977
  projectRoot,
37231
37978
  "..",
37232
37979
  "..",
@@ -39365,7 +40112,7 @@ __export(generate_exports, {
39365
40112
  runGenerate: () => runGenerate
39366
40113
  });
39367
40114
  import { readFile as readFile28, mkdir as mkdir13, writeFile as writeFile22 } from "node:fs/promises";
39368
- import { join as join48, resolve as resolve11 } from "node:path";
40115
+ import { join as join51, resolve as resolve11 } from "node:path";
39369
40116
  async function readJsonFile4(filePath) {
39370
40117
  try {
39371
40118
  const raw = await readFile28(filePath, "utf-8");
@@ -39376,7 +40123,7 @@ async function readJsonFile4(filePath) {
39376
40123
  }
39377
40124
  async function readPkgName(absPath) {
39378
40125
  try {
39379
- const raw = await readFile28(join48(absPath, "package.json"), "utf-8");
40126
+ const raw = await readFile28(join51(absPath, "package.json"), "utf-8");
39380
40127
  const pkg = JSON.parse(raw);
39381
40128
  return typeof pkg.name === "string" ? pkg.name : null;
39382
40129
  } catch {
@@ -39390,7 +40137,7 @@ async function runGenerate(opts) {
39390
40137
  const rootDir = resolve11(projectDir);
39391
40138
  let packageManager;
39392
40139
  try {
39393
- const raw = await readFile28(join48(rootDir, "package.json"), "utf-8");
40140
+ const raw = await readFile28(join51(rootDir, "package.json"), "utf-8");
39394
40141
  const pkg = JSON.parse(raw);
39395
40142
  if (typeof pkg.packageManager === "string") {
39396
40143
  packageManager = pkg.packageManager;
@@ -39398,7 +40145,7 @@ async function runGenerate(opts) {
39398
40145
  } catch {
39399
40146
  }
39400
40147
  const serverJson = await readJsonFile4(
39401
- join48(rootDir, ".codebyplan", "server.json")
40148
+ join51(rootDir, ".codebyplan", "server.json")
39402
40149
  );
39403
40150
  const ports = [];
39404
40151
  for (const alloc of serverJson?.port_allocations ?? []) {
@@ -39411,7 +40158,7 @@ async function runGenerate(opts) {
39411
40158
  }
39412
40159
  }
39413
40160
  const gitJson = await readJsonFile4(
39414
- join48(rootDir, ".codebyplan", "git.json")
40161
+ join51(rootDir, ".codebyplan", "git.json")
39415
40162
  );
39416
40163
  const branchModel = gitJson?.branch_config?.production ? {
39417
40164
  production: gitJson.branch_config.production,
@@ -39420,7 +40167,7 @@ async function runGenerate(opts) {
39420
40167
  )
39421
40168
  } : void 0;
39422
40169
  const shipmentJson = await readJsonFile4(
39423
- join48(rootDir, ".codebyplan", "shipment.json")
40170
+ join51(rootDir, ".codebyplan", "shipment.json")
39424
40171
  );
39425
40172
  const shipmentSurfaces = [];
39426
40173
  const rawSurfaces = shipmentJson?.shipment?.surfaces ?? shipmentJson?.surfaces ?? {};
@@ -39491,7 +40238,7 @@ async function runGenerate(opts) {
39491
40238
  const structureMdContent = generateStructureMd(config);
39492
40239
  const agentsContent = generateAgentsMd(structureMdContent);
39493
40240
  if (check) {
39494
- const agentsMdPath2 = join48(rootDir, "AGENTS.md");
40241
+ const agentsMdPath2 = join51(rootDir, "AGENTS.md");
39495
40242
  let existingAgents = null;
39496
40243
  try {
39497
40244
  existingAgents = await readFile28(agentsMdPath2, "utf-8");
@@ -39530,13 +40277,13 @@ async function runGenerate(opts) {
39530
40277
  process.stdout.write(agentsContent);
39531
40278
  return;
39532
40279
  }
39533
- const outputDir = join48(rootDir, ".claude", "generated");
40280
+ const outputDir = join51(rootDir, ".claude", "generated");
39534
40281
  await mkdir13(outputDir, { recursive: true });
39535
- const outputPath = join48(outputDir, "structure.md");
40282
+ const outputPath = join51(outputDir, "structure.md");
39536
40283
  await writeFile22(outputPath, structureMdContent, "utf-8");
39537
40284
  process.stdout.write(`Wrote: .claude/generated/structure.md
39538
40285
  `);
39539
- const agentsMdPath = join48(rootDir, "AGENTS.md");
40286
+ const agentsMdPath = join51(rootDir, "AGENTS.md");
39540
40287
  let existingAgentsContent = null;
39541
40288
  try {
39542
40289
  existingAgentsContent = await readFile28(agentsMdPath, "utf-8");
@@ -39568,7 +40315,7 @@ __export(readme_exports, {
39568
40315
  runReadmeCommand: () => runReadmeCommand
39569
40316
  });
39570
40317
  import { readFile as readFile29, writeFile as writeFile23 } from "node:fs/promises";
39571
- import { join as join49, resolve as resolve12, relative as relative9 } from "node:path";
40318
+ import { join as join52, resolve as resolve12, relative as relative9 } from "node:path";
39572
40319
  async function readJsonFile5(filePath) {
39573
40320
  try {
39574
40321
  const raw = await readFile29(filePath, "utf-8");
@@ -39640,7 +40387,7 @@ async function discoverUnits(rootDir, rootPkgJson) {
39640
40387
  const discovered = await discoverMonorepoApps(rootDir);
39641
40388
  for (const app of discovered) {
39642
40389
  const pkgJson = await readJsonFile5(
39643
- join49(app.absPath, "package.json")
40390
+ join52(app.absPath, "package.json")
39644
40391
  );
39645
40392
  pkgJsonByPath.set(app.absPath, pkgJson);
39646
40393
  allPackages.push({
@@ -39666,7 +40413,7 @@ async function runReadme(opts) {
39666
40413
  const init = opts.init ?? opts["init"] ?? false;
39667
40414
  const rootDir = resolve12(projectDir);
39668
40415
  const rootPkgJson = await readJsonFile5(
39669
- join49(rootDir, "package.json")
40416
+ join52(rootDir, "package.json")
39670
40417
  );
39671
40418
  const { units, allPackages, pkgJsonByPath } = await discoverUnits(
39672
40419
  rootDir,
@@ -39675,8 +40422,8 @@ async function runReadme(opts) {
39675
40422
  const driftUnits = [];
39676
40423
  const missingUnits = [];
39677
40424
  for (const unit of units) {
39678
- const readmePath = join49(unit.absPath, "README.md");
39679
- const relPath = unit.isRoot ? "README.md" : join49(relative9(rootDir, unit.absPath), "README.md");
40425
+ const readmePath = join52(unit.absPath, "README.md");
40426
+ const relPath = unit.isRoot ? "README.md" : join52(relative9(rootDir, unit.absPath), "README.md");
39680
40427
  let existingContent = null;
39681
40428
  try {
39682
40429
  existingContent = await readFile29(readmePath, "utf-8");
@@ -39867,7 +40614,7 @@ import {
39867
40614
  readdir as readdir7
39868
40615
  } from "node:fs/promises";
39869
40616
  import { existsSync as existsSync21 } from "node:fs";
39870
- import { join as join50, resolve as resolve13, dirname as dirname16, sep as sep4 } from "node:path";
40617
+ import { join as join53, resolve as resolve13, dirname as dirname16, sep as sep4 } from "node:path";
39871
40618
  import { homedir as homedir8 } from "node:os";
39872
40619
  function encodeProjectPath(absPath) {
39873
40620
  return resolve13(absPath).replace(/[/\\]/g, "-");
@@ -39878,7 +40625,7 @@ function resolveAutoMemoryDir(opts) {
39878
40625
  }
39879
40626
  const projectDir = opts.projectDir ?? process.cwd();
39880
40627
  const encoded = encodeProjectPath(projectDir);
39881
- return join50(homedir8(), ".claude", "projects", encoded, "memory");
40628
+ return join53(homedir8(), ".claude", "projects", encoded, "memory");
39882
40629
  }
39883
40630
  function parseFrontmatter(content) {
39884
40631
  content = content.replace(/\r\n/g, "\n");
@@ -39944,7 +40691,7 @@ async function inventoryFiles(dir) {
39944
40691
  }
39945
40692
  const results = [];
39946
40693
  for (const filename of filenames) {
39947
- const sourcePath = join50(dir, filename);
40694
+ const sourcePath = join53(dir, filename);
39948
40695
  let raw;
39949
40696
  try {
39950
40697
  raw = await readFile30(sourcePath, "utf-8");
@@ -40033,8 +40780,8 @@ async function applyPlan(plan, opts) {
40033
40780
  if (entry.suggested_action !== "keep") continue;
40034
40781
  if (!entry.suggested_target?.startsWith("nested:")) continue;
40035
40782
  const relPath = entry.suggested_target.slice("nested:".length);
40036
- const targetDir = resolve13(join50(projectDir, relPath));
40037
- const targetFile = join50(targetDir, "CLAUDE.md");
40783
+ const targetDir = resolve13(join53(projectDir, relPath));
40784
+ const targetFile = join53(targetDir, "CLAUDE.md");
40038
40785
  if (!targetDir.startsWith(resolve13(projectDir) + sep4)) {
40039
40786
  process.stderr.write(
40040
40787
  `migrate-memory: skipping unsafe suggested_target "${entry.suggested_target}" \u2014 resolves outside projectDir
@@ -40084,7 +40831,7 @@ ${anchor}
40084
40831
  );
40085
40832
  }
40086
40833
  }
40087
- const rootClaudeMd = join50(projectDir, ".claude", "CLAUDE.md");
40834
+ const rootClaudeMd = join53(projectDir, ".claude", "CLAUDE.md");
40088
40835
  if (dryRun) {
40089
40836
  process.stdout.write(
40090
40837
  `[dry-run] Would ensure ${rootClaudeMd} contains: ${IMPORT_LINE}
@@ -40128,8 +40875,8 @@ ${IMPORT_LINE}
40128
40875
  } catch {
40129
40876
  }
40130
40877
  }
40131
- const memoryMd = join50(plan.auto_memory_dir, "MEMORY.md");
40132
- const safeRmdirBase = join50(homedir8(), ".claude", "projects");
40878
+ const memoryMd = join53(plan.auto_memory_dir, "MEMORY.md");
40879
+ const safeRmdirBase = join53(homedir8(), ".claude", "projects");
40133
40880
  if (dryRun) {
40134
40881
  process.stdout.write(`[dry-run] Would delete MEMORY.md: ${memoryMd}
40135
40882
  `);
@@ -40253,7 +41000,7 @@ var init_migrate_memory = __esm({
40253
41000
 
40254
41001
  // src/lib/claude-mode-audit.ts
40255
41002
  import { readdirSync as readdirSync7, readFileSync as readFileSync19, existsSync as existsSync22 } from "node:fs";
40256
- import { join as join51, basename as basename3 } from "node:path";
41003
+ import { join as join54, basename as basename3 } from "node:path";
40257
41004
  function parseFrontmatter2(content) {
40258
41005
  const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(content);
40259
41006
  if (!match) return {};
@@ -40330,19 +41077,19 @@ function auditSkill(filePath) {
40330
41077
  }
40331
41078
  function auditMode(templatesDir) {
40332
41079
  const entries = [];
40333
- const agentsDir = join51(templatesDir, "agents");
41080
+ const agentsDir = join54(templatesDir, "agents");
40334
41081
  if (existsSync22(agentsDir)) {
40335
41082
  const agentFiles = readdirSync7(agentsDir).filter((f) => f.endsWith(".md")).sort();
40336
41083
  for (const f of agentFiles) {
40337
- entries.push(auditAgent(join51(agentsDir, f)));
41084
+ entries.push(auditAgent(join54(agentsDir, f)));
40338
41085
  }
40339
41086
  }
40340
- const skillsDir = join51(templatesDir, "skills");
41087
+ const skillsDir = join54(templatesDir, "skills");
40341
41088
  if (existsSync22(skillsDir)) {
40342
41089
  const skillDirs = readdirSync7(skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
40343
41090
  for (const dir of skillDirs) {
40344
- if (existsSync22(join51(skillsDir, dir, "PROVENANCE.md"))) continue;
40345
- const skillMd = join51(skillsDir, dir, "SKILL.md");
41091
+ if (existsSync22(join54(skillsDir, dir, "PROVENANCE.md"))) continue;
41092
+ const skillMd = join54(skillsDir, dir, "SKILL.md");
40346
41093
  if (existsSync22(skillMd)) {
40347
41094
  entries.push(auditSkill(skillMd));
40348
41095
  }
@@ -40608,8 +41355,8 @@ function slugify(name) {
40608
41355
  function newMigration(args, deps = {}) {
40609
41356
  const cwd = deps.cwd ?? process.cwd();
40610
41357
  const genTimestamp = deps.generateTimestamp ?? generateMonotonicTimestamp;
40611
- const mkdirSync11 = deps.mkdirSyncFn ?? ((dir, opts) => fs19.mkdirSync(dir, opts));
40612
- const writeFileSync12 = deps.writeFileSyncFn ?? ((filePath2, content) => fs19.writeFileSync(filePath2, content));
41358
+ const mkdirSync12 = deps.mkdirSyncFn ?? ((dir, opts) => fs19.mkdirSync(dir, opts));
41359
+ const writeFileSync13 = deps.writeFileSyncFn ?? ((filePath2, content) => fs19.writeFileSync(filePath2, content));
40613
41360
  const rawName = args[0];
40614
41361
  if (!rawName) {
40615
41362
  process.stderr.write(
@@ -40625,8 +41372,8 @@ function newMigration(args, deps = {}) {
40625
41372
  const migrationsDir = path21.join(cwd, "supabase", "migrations");
40626
41373
  const filename = `${stamp}_${slug}.sql`;
40627
41374
  const filePath = path21.join(migrationsDir, filename);
40628
- mkdirSync11(migrationsDir, { recursive: true });
40629
- writeFileSync12(filePath, "");
41375
+ mkdirSync12(migrationsDir, { recursive: true });
41376
+ writeFileSync13(filePath, "");
40630
41377
  process.stdout.write(JSON.stringify({ path: filePath }) + "\n");
40631
41378
  return 0;
40632
41379
  }
@@ -41189,12 +41936,12 @@ var init_validate_waves2 = __esm({
41189
41936
  });
41190
41937
 
41191
41938
  // src/cli/worktree/path.ts
41192
- import { dirname as dirname17, basename as basename4, join as join53 } from "node:path";
41939
+ import { dirname as dirname17, basename as basename4, join as join56 } from "node:path";
41193
41940
  function computeWorktreePath(cwd, checkpointNumber) {
41194
41941
  const parent = dirname17(cwd);
41195
41942
  const base = basename4(cwd);
41196
41943
  const nnn = String(checkpointNumber).padStart(3, "0");
41197
- return join53(parent, `${base}-CHK-${nnn}`);
41944
+ return join56(parent, `${base}-CHK-${nnn}`);
41198
41945
  }
41199
41946
  var init_path = __esm({
41200
41947
  "src/cli/worktree/path.ts"() {
@@ -41203,7 +41950,7 @@ var init_path = __esm({
41203
41950
  });
41204
41951
 
41205
41952
  // src/cli/worktree/add.ts
41206
- import { join as join54, basename as basename5 } from "node:path";
41953
+ import { join as join57, basename as basename5 } from "node:path";
41207
41954
  import { mkdir as mkdir15, readFile as readFile32, writeFile as writeFile25 } from "node:fs/promises";
41208
41955
  import { spawnSync as spawnSync18 } from "node:child_process";
41209
41956
  async function defaultGetRepoId(cwd) {
@@ -41253,11 +42000,11 @@ async function copyClaudeSettings(srcCwd, destPath, deps = {}) {
41253
42000
  (fsp) => fsp.access(p).then(() => true).catch(() => false)
41254
42001
  ));
41255
42002
  const mkdirFn = deps.mkdirFn ?? ((p, opts) => mkdir15(p, opts));
41256
- await mkdirFn(join54(destPath, ".claude"), { recursive: true });
42003
+ await mkdirFn(join57(destPath, ".claude"), { recursive: true });
41257
42004
  const claudeStubs = ["settings.json", "settings.local.json"];
41258
42005
  for (const stub of claudeStubs) {
41259
- const srcFile = join54(srcCwd, ".claude", stub);
41260
- const destFile = join54(destPath, ".claude", stub);
42006
+ const srcFile = join57(srcCwd, ".claude", stub);
42007
+ const destFile = join57(destPath, ".claude", stub);
41261
42008
  try {
41262
42009
  const destExists = await existsFn(destFile);
41263
42010
  if (destExists) continue;
@@ -41268,22 +42015,22 @@ async function copyClaudeSettings(srcCwd, destPath, deps = {}) {
41268
42015
  }
41269
42016
  }
41270
42017
  async function defaultCopyConfigStubs(srcCwd, destPath) {
41271
- await mkdir15(join54(destPath, ".codebyplan"), { recursive: true });
42018
+ await mkdir15(join57(destPath, ".codebyplan"), { recursive: true });
41272
42019
  const topLevelStubs = [".mcp.json", ".env.local"];
41273
42020
  for (const stub of topLevelStubs) {
41274
42021
  try {
41275
- const content = await readFile32(join54(srcCwd, stub), "utf-8");
41276
- await writeFile25(join54(destPath, stub), content, "utf-8");
42022
+ const content = await readFile32(join57(srcCwd, stub), "utf-8");
42023
+ await writeFile25(join57(destPath, stub), content, "utf-8");
41277
42024
  } catch {
41278
42025
  }
41279
42026
  }
41280
42027
  try {
41281
42028
  const content = await readFile32(
41282
- join54(srcCwd, ".codebyplan", "repo.json"),
42029
+ join57(srcCwd, ".codebyplan", "repo.json"),
41283
42030
  "utf-8"
41284
42031
  );
41285
42032
  await writeFile25(
41286
- join54(destPath, ".codebyplan", "repo.json"),
42033
+ join57(destPath, ".codebyplan", "repo.json"),
41287
42034
  content,
41288
42035
  "utf-8"
41289
42036
  );
@@ -41448,7 +42195,7 @@ var init_add = __esm({
41448
42195
  });
41449
42196
 
41450
42197
  // src/cli/worktree/create.ts
41451
- import { join as join55 } from "node:path";
42198
+ import { join as join58 } from "node:path";
41452
42199
  async function defaultGetRepoIdentity(cwd) {
41453
42200
  const found = await findCodebyplanConfig(cwd);
41454
42201
  const contents = found?.contents ?? null;
@@ -41504,7 +42251,7 @@ async function runWorktreeCreate(args, deps = {}) {
41504
42251
  );
41505
42252
  return 1;
41506
42253
  }
41507
- const worktreePath = explicitPath ?? join55(cwd, "..", name);
42254
+ const worktreePath = explicitPath ?? join58(cwd, "..", name);
41508
42255
  const deviceId = await getDeviceId(cwd);
41509
42256
  let filesWritten = false;
41510
42257
  try {
@@ -42143,14 +42890,55 @@ var init_e2e2 = __esm({
42143
42890
  }
42144
42891
  });
42145
42892
 
42893
+ // src/cli/cleanup-plan-folders.ts
42894
+ var cleanup_plan_folders_exports = {};
42895
+ __export(cleanup_plan_folders_exports, {
42896
+ runCleanupPlanFolders: () => runCleanupPlanFolders
42897
+ });
42898
+ import { execSync as execSync10 } from "node:child_process";
42899
+ async function runCleanupPlanFolders(args) {
42900
+ void args;
42901
+ const found = await findCodebyplanConfig(process.cwd());
42902
+ if (!found) {
42903
+ process.stderr.write(
42904
+ "cleanup-plan-folders: no .codebyplan/repo.json found. Run `codebyplan setup`.\n"
42905
+ );
42906
+ process.exit(1);
42907
+ }
42908
+ const repoRoot = deriveRepoRoot(found.path);
42909
+ const targets = [".codebyplan/checkpoint", ".codebyplan/standalone"];
42910
+ let stdout7 = "";
42911
+ try {
42912
+ const result = execSync10(
42913
+ `git rm -r --ignore-unmatch -- ${targets.map((t) => `"${t}"`).join(" ")}`,
42914
+ { cwd: repoRoot, stdio: "pipe" }
42915
+ );
42916
+ stdout7 = result.toString("utf-8");
42917
+ } catch (err) {
42918
+ const msg = err instanceof Error ? err.message : String(err);
42919
+ process.stderr.write(`cleanup-plan-folders: git rm failed: ${msg}
42920
+ `);
42921
+ process.exit(1);
42922
+ }
42923
+ const removed = stdout7.split("\n").map((line) => line.trim()).filter((line) => line.startsWith("rm '") && line.endsWith("'")).map((line) => line.slice(4, -1));
42924
+ process.stdout.write(JSON.stringify({ removed, skipped: [] }) + "\n");
42925
+ process.exit(0);
42926
+ }
42927
+ var init_cleanup_plan_folders = __esm({
42928
+ "src/cli/cleanup-plan-folders.ts"() {
42929
+ "use strict";
42930
+ init_flags();
42931
+ }
42932
+ });
42933
+
42146
42934
  // src/cli/doctor.ts
42147
42935
  var doctor_exports = {};
42148
42936
  __export(doctor_exports, {
42149
42937
  runDoctor: () => runDoctor
42150
42938
  });
42151
42939
  import { existsSync as existsSync23 } from "node:fs";
42152
- import { join as join56 } from "node:path";
42153
- import { execSync as execSync10 } from "node:child_process";
42940
+ import { join as join59 } from "node:path";
42941
+ import { execSync as execSync11 } from "node:child_process";
42154
42942
  async function checkAuth() {
42155
42943
  try {
42156
42944
  await validateAuth();
@@ -42171,7 +42959,7 @@ async function checkVersion() {
42171
42959
  const installed = VERSION;
42172
42960
  let gitRoot = null;
42173
42961
  try {
42174
- gitRoot = execSync10("git rev-parse --show-toplevel", {
42962
+ gitRoot = execSync11("git rev-parse --show-toplevel", {
42175
42963
  encoding: "utf-8"
42176
42964
  }).trim();
42177
42965
  } catch {
@@ -42260,7 +43048,7 @@ function checkSettings() {
42260
43048
  try {
42261
43049
  let projectDir;
42262
43050
  try {
42263
- projectDir = execSync10("git rev-parse --show-toplevel", {
43051
+ projectDir = execSync11("git rev-parse --show-toplevel", {
42264
43052
  encoding: "utf-8"
42265
43053
  }).trim();
42266
43054
  } catch {
@@ -42273,7 +43061,7 @@ function checkSettings() {
42273
43061
  detail: "codebyplan not installed (no .cbp.manifest.json)"
42274
43062
  };
42275
43063
  }
42276
- if (!existsSync23(join56(projectDir, ".claude", "settings.json"))) {
43064
+ if (!existsSync23(join59(projectDir, ".claude", "settings.json"))) {
42277
43065
  return {
42278
43066
  name: "settings",
42279
43067
  status: "warn",
@@ -42819,6 +43607,12 @@ void (async () => {
42819
43607
  const rest = process.argv.slice(3);
42820
43608
  process.exit(await runE2eCommand2(rest));
42821
43609
  }
43610
+ if (arg === "cleanup-plan-folders") {
43611
+ const { runCleanupPlanFolders: runCleanupPlanFolders2 } = await Promise.resolve().then(() => (init_cleanup_plan_folders(), cleanup_plan_folders_exports));
43612
+ const rest = process.argv.slice(3);
43613
+ await runCleanupPlanFolders2(rest);
43614
+ process.exit(0);
43615
+ }
42822
43616
  if (arg === "doctor") {
42823
43617
  const { runDoctor: runDoctor2 } = await Promise.resolve().then(() => (init_doctor(), doctor_exports));
42824
43618
  const rest = process.argv.slice(3);