codebyplan 1.13.23 → 1.13.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/cli.js +445 -187
  2. package/package.json +2 -2
  3. package/templates/agents/cbp-cc-executor.md +7 -7
  4. package/templates/agents/cbp-improve-round.md +2 -2
  5. package/templates/agents/cbp-round-executor.md +20 -4
  6. package/templates/agents/cbp-testing-qa-agent.md +3 -3
  7. package/templates/hooks/README.md +1 -1
  8. package/templates/hooks/cbp-statusline.mjs +106 -11
  9. package/templates/hooks/cbp-statusline.py +79 -13
  10. package/templates/hooks/cbp-statusline.sh +97 -17
  11. package/templates/hooks/validate-structure-patterns.sh +1 -1
  12. package/templates/skills/cbp-checkpoint-check/SKILL.md +2 -2
  13. package/templates/skills/cbp-checkpoint-complete/SKILL.md +2 -2
  14. package/templates/skills/cbp-merge-main/SKILL.md +1 -1
  15. package/templates/skills/cbp-round-end/SKILL.md +12 -35
  16. package/templates/skills/cbp-round-end/reference/findings-presentation.md +76 -3
  17. package/templates/skills/cbp-round-execute/SKILL.md +13 -60
  18. package/templates/skills/cbp-round-start/SKILL.md +3 -1
  19. package/templates/skills/cbp-round-update/SKILL.md +1 -1
  20. package/templates/skills/cbp-session-start/SKILL.md +2 -0
  21. package/templates/skills/cbp-ship-configure/SKILL.md +1 -1
  22. package/templates/skills/cbp-ship-configure/reference/supabase.md +2 -2
  23. package/templates/skills/cbp-ship-main/SKILL.md +2 -0
  24. package/templates/skills/cbp-standalone-task-create/SKILL.md +1 -1
  25. package/templates/skills/cbp-task-check/SKILL.md +1 -1
  26. package/templates/skills/cbp-task-complete/SKILL.md +1 -1
  27. package/templates/skills/cbp-task-create/SKILL.md +50 -1
  28. package/templates/skills/cbp-task-start/SKILL.md +2 -2
  29. package/templates/skills/cbp-task-testing/SKILL.md +2 -2
  30. package/templates/skills/cbp-todo/SKILL.md +36 -3
  31. package/templates/skills/cbp-todo/qa-regression.md +8 -1
package/dist/cli.js CHANGED
@@ -14,7 +14,7 @@ var VERSION, PACKAGE_NAME;
14
14
  var init_version = __esm({
15
15
  "src/lib/version.ts"() {
16
16
  "use strict";
17
- VERSION = "1.13.23";
17
+ VERSION = "1.13.25";
18
18
  PACKAGE_NAME = "codebyplan";
19
19
  }
20
20
  });
@@ -149,6 +149,7 @@ var init_gitignore_block = __esm({
149
149
  ".codebyplan/device.local.json",
150
150
  ".codebyplan/statusline.local.json",
151
151
  ".codebyplan/worktree.local.json",
152
+ ".codebyplan/claude-status.local.json",
152
153
  ".codebyplan.local.json"
153
154
  ];
154
155
  GITIGNORE_BLOCK_START = "# >>> codebyplan (managed) >>>";
@@ -225,8 +226,8 @@ async function readLocalConfig(projectPath, onMigrationNotice) {
225
226
  }
226
227
  async function writeLocalConfig(projectPath, config) {
227
228
  const content = { device_id: config.device_id };
228
- const path8 = localConfigPath(projectPath);
229
- const dirPath = dirname(path8);
229
+ const path10 = localConfigPath(projectPath);
230
+ const dirPath = dirname(path10);
230
231
  let phase = "stat config directory";
231
232
  try {
232
233
  try {
@@ -246,7 +247,7 @@ async function writeLocalConfig(projectPath, config) {
246
247
  phase = "create config directory";
247
248
  await mkdir(dirPath, { recursive: true });
248
249
  phase = "write local config";
249
- await writeFile2(path8, JSON.stringify(content, null, 2) + "\n", "utf-8");
250
+ await writeFile2(path10, JSON.stringify(content, null, 2) + "\n", "utf-8");
250
251
  } catch (err) {
251
252
  const code = err.code;
252
253
  if (code === "LEGACY_FILE_BLOCKS_DIR") {
@@ -396,7 +397,8 @@ var init_statusline_config = __esm({
396
397
  rate_limits: true,
397
398
  repo_pr: true,
398
399
  worktree: true,
399
- infra_drift: true
400
+ infra_drift: true,
401
+ package_freshness: true
400
402
  },
401
403
  no_color: false
402
404
  };
@@ -467,12 +469,12 @@ async function readFallback(filename) {
467
469
  }
468
470
  }
469
471
  async function writeFallback(filename, data) {
470
- const path8 = fallbackFile(filename);
471
- await mkdir3(dirname2(path8), { recursive: true });
472
- await writeFile4(path8, JSON.stringify(data, null, 2) + "\n", "utf-8");
472
+ const path10 = fallbackFile(filename);
473
+ await mkdir3(dirname2(path10), { recursive: true });
474
+ await writeFile4(path10, JSON.stringify(data, null, 2) + "\n", "utf-8");
473
475
  if (platform() !== "win32") {
474
476
  try {
475
- await chmod(path8, 384);
477
+ await chmod(path10, 384);
476
478
  } catch {
477
479
  }
478
480
  }
@@ -677,8 +679,8 @@ async function getAuthHeaders() {
677
679
  return { headers: { "x-api-key": key }, via: "api_key" };
678
680
  }
679
681
  }
680
- function buildUrl(path8, params) {
681
- const url = new URL(`${baseUrl()}/api${path8}`);
682
+ function buildUrl(path10, params) {
683
+ const url = new URL(`${baseUrl()}/api${path10}`);
682
684
  if (params) {
683
685
  for (const [key, value] of Object.entries(params)) {
684
686
  if (value !== void 0) {
@@ -695,10 +697,10 @@ function isRetryable(err) {
695
697
  return false;
696
698
  }
697
699
  function delay(ms) {
698
- return new Promise((resolve8) => setTimeout(resolve8, ms));
700
+ return new Promise((resolve9) => setTimeout(resolve9, ms));
699
701
  }
700
- async function request(method, path8, options) {
701
- const url = buildUrl(path8, options?.params);
702
+ async function request(method, path10, options) {
703
+ const url = buildUrl(path10, options?.params);
702
704
  const auth = await getAuthHeaders();
703
705
  let lastError;
704
706
  for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
@@ -718,7 +720,7 @@ async function request(method, path8, options) {
718
720
  signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
719
721
  });
720
722
  if (!res.ok) {
721
- let message = `API ${method} ${path8} failed with status ${res.status}`;
723
+ let message = `API ${method} ${path10} failed with status ${res.status}`;
722
724
  let code;
723
725
  try {
724
726
  const body = await res.json();
@@ -752,14 +754,14 @@ async function request(method, path8, options) {
752
754
  }
753
755
  throw lastError;
754
756
  }
755
- async function apiGet(path8, params) {
756
- return request("GET", path8, { params });
757
+ async function apiGet(path10, params) {
758
+ return request("GET", path10, { params });
757
759
  }
758
- async function apiPost(path8, body) {
759
- return request("POST", path8, { body });
760
+ async function apiPost(path10, body) {
761
+ return request("POST", path10, { body });
760
762
  }
761
- async function apiPut(path8, body) {
762
- return request("PUT", path8, { body });
763
+ async function apiPut(path10, body) {
764
+ return request("PUT", path10, { body });
763
765
  }
764
766
  async function callMcpTool(toolName, params) {
765
767
  const url = mcpEndpoint();
@@ -1053,7 +1055,7 @@ var init_device_flow = __esm({
1053
1055
  this.name = "OAuthInvalidClientError";
1054
1056
  }
1055
1057
  };
1056
- defaultSleep = (ms) => new Promise((resolve8) => setTimeout(resolve8, ms));
1058
+ defaultSleep = (ms) => new Promise((resolve9) => setTimeout(resolve9, ms));
1057
1059
  }
1058
1060
  });
1059
1061
 
@@ -1861,9 +1863,9 @@ import { createInterface } from "node:readline/promises";
1861
1863
  function getConfigPath(scope) {
1862
1864
  return scope === "user" ? join9(homedir4(), ".claude.json") : join9(process.cwd(), ".mcp.json");
1863
1865
  }
1864
- async function readConfig(path8) {
1866
+ async function readConfig(path10) {
1865
1867
  try {
1866
- const raw = await readFile6(path8, "utf-8");
1868
+ const raw = await readFile6(path10, "utf-8");
1867
1869
  const parsed = JSON.parse(raw);
1868
1870
  if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
1869
1871
  return parsed;
@@ -2088,8 +2090,8 @@ async function runSetup() {
2088
2090
  const deviceId = await getOrCreateDeviceId(projectPath);
2089
2091
  let branch = "main";
2090
2092
  try {
2091
- const { execSync: execSync8 } = await import("node:child_process");
2092
- branch = execSync8("git symbolic-ref --short HEAD", {
2093
+ const { execSync: execSync9 } = await import("node:child_process");
2094
+ branch = execSync9("git symbolic-ref --short HEAD", {
2093
2095
  cwd: projectPath,
2094
2096
  encoding: "utf-8"
2095
2097
  }).trim();
@@ -2450,9 +2452,9 @@ import { join as join11 } from "node:path";
2450
2452
  function configPaths() {
2451
2453
  return [join11(homedir5(), ".claude.json"), join11(process.cwd(), ".mcp.json")];
2452
2454
  }
2453
- async function readConfig2(path8) {
2455
+ async function readConfig2(path10) {
2454
2456
  try {
2455
- const raw = await readFile8(path8, "utf-8");
2457
+ const raw = await readFile8(path10, "utf-8");
2456
2458
  const parsed = JSON.parse(raw);
2457
2459
  if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
2458
2460
  return parsed;
@@ -2466,7 +2468,7 @@ function entryHasLegacyApiKey(entry) {
2466
2468
  if (!entry || !entry.headers) return false;
2467
2469
  return "x-api-key" in entry.headers;
2468
2470
  }
2469
- async function rewriteConfig(path8, config, newUrl) {
2471
+ async function rewriteConfig(path10, config, newUrl) {
2470
2472
  const servers = config.mcpServers;
2471
2473
  if (!servers) return false;
2472
2474
  const entry = servers.codebyplan;
@@ -2474,7 +2476,7 @@ async function rewriteConfig(path8, config, newUrl) {
2474
2476
  if (!entryHasLegacyApiKey(entry) && entry.url === newUrl && entry.type === "http")
2475
2477
  return false;
2476
2478
  servers.codebyplan = { type: "http", url: newUrl };
2477
- await writeFile7(path8, JSON.stringify(config, null, 2) + "\n", "utf-8");
2479
+ await writeFile7(path10, JSON.stringify(config, null, 2) + "\n", "utf-8");
2478
2480
  return true;
2479
2481
  }
2480
2482
  async function runUpgradeAuth() {
@@ -2482,12 +2484,12 @@ async function runUpgradeAuth() {
2482
2484
  await runLogin();
2483
2485
  const newUrl = mcpEndpoint();
2484
2486
  let migrated = 0;
2485
- for (const path8 of configPaths()) {
2486
- const config = await readConfig2(path8);
2487
+ for (const path10 of configPaths()) {
2488
+ const config = await readConfig2(path10);
2487
2489
  if (!config) continue;
2488
- const changed = await rewriteConfig(path8, config, newUrl);
2490
+ const changed = await rewriteConfig(path10, config, newUrl);
2489
2491
  if (changed) {
2490
- console.log(` Updated ${path8}`);
2492
+ console.log(` Updated ${path10}`);
2491
2493
  migrated++;
2492
2494
  }
2493
2495
  }
@@ -3759,9 +3761,9 @@ async function eslintInit(repoId, projectPath) {
3759
3761
  Install ${missingPkgs.length} missing packages? [Y/n] `
3760
3762
  );
3761
3763
  if (confirmed) {
3762
- const { execSync: execSync8 } = await import("node:child_process");
3764
+ const { execSync: execSync9 } = await import("node:child_process");
3763
3765
  try {
3764
- execSync8(installCmd, { cwd: projectPath, stdio: "inherit" });
3766
+ execSync9(installCmd, { cwd: projectPath, stdio: "inherit" });
3765
3767
  console.log(" Packages installed.\n");
3766
3768
  } catch (err) {
3767
3769
  console.error(
@@ -4196,7 +4198,7 @@ function setRetryDelayMs(ms) {
4196
4198
  RETRY_DELAY_MS = ms;
4197
4199
  }
4198
4200
  function sleep(ms) {
4199
- return new Promise((resolve8) => setTimeout(resolve8, ms));
4201
+ return new Promise((resolve9) => setTimeout(resolve9, ms));
4200
4202
  }
4201
4203
  function isTransientMcpError(err) {
4202
4204
  if (!(err instanceof McpError)) return false;
@@ -6247,6 +6249,8 @@ var init_create_repo = __esm({
6247
6249
  // src/cli/version-status.ts
6248
6250
  var version_status_exports = {};
6249
6251
  __export(version_status_exports, {
6252
+ fetchLatestVersion: () => fetchLatestVersion,
6253
+ resolveGuard: () => resolveGuard,
6250
6254
  runVersionStatus: () => runVersionStatus
6251
6255
  });
6252
6256
  import { execFileSync, execSync as execSync6 } from "node:child_process";
@@ -6669,8 +6673,8 @@ async function resolveWorktreePortAllocations(repoId, projectPath) {
6669
6673
  const deviceId = await getOrCreateDeviceId(projectPath);
6670
6674
  let branch = "main";
6671
6675
  try {
6672
- const { execSync: execSync8 } = await import("node:child_process");
6673
- branch = execSync8("git symbolic-ref --short HEAD", {
6676
+ const { execSync: execSync9 } = await import("node:child_process");
6677
+ branch = execSync9("git symbolic-ref --short HEAD", {
6674
6678
  cwd: projectPath,
6675
6679
  encoding: "utf-8"
6676
6680
  }).trim();
@@ -7675,10 +7679,10 @@ async function runTechStack() {
7675
7679
  );
7676
7680
  }
7677
7681
  try {
7678
- const { execSync: execSync8 } = await import("node:child_process");
7682
+ const { execSync: execSync9 } = await import("node:child_process");
7679
7683
  let branch = "main";
7680
7684
  try {
7681
- branch = execSync8("git symbolic-ref --short HEAD", {
7685
+ branch = execSync9("git symbolic-ref --short HEAD", {
7682
7686
  cwd: projectPath,
7683
7687
  encoding: "utf-8"
7684
7688
  }).trim();
@@ -7823,6 +7827,301 @@ var init_tech_stack = __esm({
7823
7827
  }
7824
7828
  });
7825
7829
 
7830
+ // src/lib/claude-plan.ts
7831
+ import * as fs5 from "node:fs";
7832
+ import * as path6 from "node:path";
7833
+ function buildDriftPlan(projectDir, templatesDir, manifest) {
7834
+ const packaged = walkTemplates(templatesDir);
7835
+ const packagedBySrc = new Map(packaged.map((f) => [f.src, f]));
7836
+ const manifestBySrc = new Map(manifest.files.map((f) => [f.src, f]));
7837
+ const plan = {
7838
+ unchanged: [],
7839
+ overwriteSafe: [],
7840
+ overwriteHandEdited: [],
7841
+ newOptIn: [],
7842
+ removedFromPackage: []
7843
+ };
7844
+ for (const pkg of packaged) {
7845
+ const inManifest = manifestBySrc.get(pkg.src);
7846
+ const absDest = path6.join(projectDir, ".claude", pkg.dest);
7847
+ const absSrc = path6.join(templatesDir, pkg.src);
7848
+ if (!inManifest) {
7849
+ plan.newOptIn.push({
7850
+ packaged: { src: pkg.src, dest: pkg.dest, hash: pkg.hash },
7851
+ absSrc
7852
+ });
7853
+ continue;
7854
+ }
7855
+ const onDiskExists = fs5.existsSync(absDest);
7856
+ const onDiskContent = onDiskExists ? fs5.readFileSync(absDest) : Buffer.alloc(0);
7857
+ const onDiskHash = onDiskExists ? sha256(onDiskContent) : null;
7858
+ if (pkg.hash === inManifest.hash && onDiskHash === inManifest.hash) {
7859
+ plan.unchanged.push(inManifest);
7860
+ continue;
7861
+ }
7862
+ if (onDiskHash !== null && onDiskHash !== inManifest.hash) {
7863
+ plan.overwriteHandEdited.push({
7864
+ packaged: { src: pkg.src, dest: pkg.dest, hash: pkg.hash },
7865
+ absSrc,
7866
+ onDiskContent
7867
+ });
7868
+ } else {
7869
+ plan.overwriteSafe.push({
7870
+ packaged: { src: pkg.src, dest: pkg.dest, hash: pkg.hash },
7871
+ absSrc
7872
+ });
7873
+ }
7874
+ }
7875
+ for (const m of manifest.files) {
7876
+ if (!packagedBySrc.has(m.src)) {
7877
+ plan.removedFromPackage.push(m);
7878
+ }
7879
+ }
7880
+ return plan;
7881
+ }
7882
+ var init_claude_plan = __esm({
7883
+ "src/lib/claude-plan.ts"() {
7884
+ "use strict";
7885
+ init_template_walker();
7886
+ init_hash();
7887
+ }
7888
+ });
7889
+
7890
+ // src/cli/claude/status.ts
7891
+ var status_exports = {};
7892
+ __export(status_exports, {
7893
+ runStatus: () => runStatus
7894
+ });
7895
+ import * as fs6 from "node:fs";
7896
+ import * as path7 from "node:path";
7897
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
7898
+ import { execSync as execSync8 } from "node:child_process";
7899
+ function makeFailSafe(checked_at) {
7900
+ return {
7901
+ installed: VERSION,
7902
+ manifest_version: null,
7903
+ latest: null,
7904
+ newer: false,
7905
+ version_skip: false,
7906
+ drifted_files: [],
7907
+ new_in_package: [],
7908
+ removed_from_package: [],
7909
+ settings_drift: false,
7910
+ guarded: true,
7911
+ guard_reason: "unknown",
7912
+ in_sync: true,
7913
+ action: null,
7914
+ checked_at
7915
+ };
7916
+ }
7917
+ function resolveTemplatesDirFromInstall() {
7918
+ const here = path7.dirname(fileURLToPath2(import.meta.url));
7919
+ const candidates = [
7920
+ path7.resolve(here, "..", "templates"),
7921
+ path7.resolve(here, "..", "..", "templates"),
7922
+ path7.resolve(here, "..", "..", "..", "templates")
7923
+ ];
7924
+ for (const c of candidates) {
7925
+ if (fs6.existsSync(c) && fs6.statSync(c).isDirectory()) {
7926
+ return c;
7927
+ }
7928
+ }
7929
+ throw new Error(
7930
+ `codebyplan claude: could not locate templates/ directory. Probed:
7931
+ ${candidates.join(
7932
+ "\n "
7933
+ )}`
7934
+ );
7935
+ }
7936
+ function resolveGitRoot2() {
7937
+ try {
7938
+ return execSync8("git rev-parse --show-toplevel", {
7939
+ encoding: "utf-8"
7940
+ }).trim();
7941
+ } catch {
7942
+ return null;
7943
+ }
7944
+ }
7945
+ function resolveCurrentBranch2() {
7946
+ try {
7947
+ return execSync8("git symbolic-ref --short HEAD", {
7948
+ encoding: "utf-8"
7949
+ }).trim();
7950
+ } catch {
7951
+ try {
7952
+ return execSync8("git rev-parse --abbrev-ref HEAD", {
7953
+ encoding: "utf-8"
7954
+ }).trim();
7955
+ } catch {
7956
+ return "";
7957
+ }
7958
+ }
7959
+ }
7960
+ async function runStatus(argv) {
7961
+ const checked_at = (/* @__PURE__ */ new Date()).toISOString();
7962
+ const writeCache = argv.includes("--write-cache");
7963
+ const quiet = argv.includes("--quiet");
7964
+ try {
7965
+ const projectDir = resolveGitRoot2() ?? process.cwd();
7966
+ let templatesDir;
7967
+ try {
7968
+ templatesDir = resolveTemplatesDirFromInstall();
7969
+ } catch {
7970
+ const gitRoot2 = resolveGitRoot2();
7971
+ const currentBranch2 = resolveCurrentBranch2();
7972
+ const { guardReason: guardReason2 } = await resolveGuard(gitRoot2, currentBranch2);
7973
+ const result2 = {
7974
+ ...makeFailSafe(checked_at),
7975
+ // Mirror the main path: only the canonical source repo hides the
7976
+ // segment. A protected_branch consumer whose templates can't be
7977
+ // resolved must still be guarded:false (segment visible).
7978
+ guarded: guardReason2 === "canonical_source",
7979
+ guard_reason: guardReason2 ?? "no_manifest",
7980
+ in_sync: true
7981
+ };
7982
+ emitResult(result2, writeCache, quiet, projectDir);
7983
+ process.exit(0);
7984
+ return;
7985
+ }
7986
+ const gitRoot = resolveGitRoot2();
7987
+ const currentBranch = resolveCurrentBranch2();
7988
+ const { guardReason } = await resolveGuard(gitRoot, currentBranch);
7989
+ const manifest = readManifest(projectDir);
7990
+ if (manifest === null || guardReason === "canonical_source") {
7991
+ const result2 = {
7992
+ installed: VERSION,
7993
+ manifest_version: manifest === null ? null : manifest.version,
7994
+ latest: null,
7995
+ newer: false,
7996
+ version_skip: false,
7997
+ drifted_files: [],
7998
+ new_in_package: [],
7999
+ removed_from_package: [],
8000
+ settings_drift: false,
8001
+ guarded: true,
8002
+ // guarded:true must always pair with a non-null reason. canonical_source
8003
+ // keeps its reason; the bare no-manifest case (never-installed consumer)
8004
+ // gets the "no_manifest" sentinel instead of null.
8005
+ guard_reason: guardReason ?? "no_manifest",
8006
+ in_sync: true,
8007
+ action: null,
8008
+ checked_at
8009
+ };
8010
+ emitResult(result2, writeCache, quiet, projectDir);
8011
+ process.exit(0);
8012
+ return;
8013
+ }
8014
+ const installed = VERSION;
8015
+ const manifest_version = manifest.version;
8016
+ const version_skip = manifest_version != null && compareSemver(installed, manifest_version) > 0;
8017
+ const driftPlan = buildDriftPlan(projectDir, templatesDir, manifest);
8018
+ const drifted_files = driftPlan.overwriteHandEdited.map(
8019
+ (e) => e.packaged.dest
8020
+ );
8021
+ const new_in_package = driftPlan.newOptIn.map((e) => e.packaged.dest);
8022
+ const removed_from_package = driftPlan.removedFromPackage.map(
8023
+ (e) => e.dest
8024
+ );
8025
+ const latest = fetchLatestVersion();
8026
+ const newer = latest !== null && compareSemver(latest, installed) > 0;
8027
+ let settings_drift = false;
8028
+ const settingsPath = path7.join(projectDir, ".claude", "settings.json");
8029
+ const baseSettingsPath = path7.join(
8030
+ templatesDir,
8031
+ "settings.project.base.json"
8032
+ );
8033
+ const hooksJsonPath = path7.join(templatesDir, "hooks", "hooks.json");
8034
+ if (fs6.existsSync(settingsPath)) {
8035
+ try {
8036
+ const settingsRaw = fs6.readFileSync(settingsPath, "utf8");
8037
+ const before = JSON.stringify(JSON.parse(settingsRaw));
8038
+ const cloned = JSON.parse(settingsRaw);
8039
+ if (fs6.existsSync(baseSettingsPath)) {
8040
+ const base = JSON.parse(
8041
+ fs6.readFileSync(baseSettingsPath, "utf8")
8042
+ );
8043
+ mergeBaseSettingsIntoSettings(cloned, base);
8044
+ }
8045
+ if (fs6.existsSync(hooksJsonPath)) {
8046
+ const hooksJson = JSON.parse(
8047
+ fs6.readFileSync(hooksJsonPath, "utf8")
8048
+ );
8049
+ mergeHooksIntoSettings(cloned, hooksJson);
8050
+ }
8051
+ const after = JSON.stringify(cloned);
8052
+ settings_drift = before !== after;
8053
+ } catch {
8054
+ settings_drift = false;
8055
+ }
8056
+ }
8057
+ const in_sync = !version_skip && drifted_files.length === 0 && new_in_package.length === 0 && removed_from_package.length === 0 && !settings_drift;
8058
+ let action = null;
8059
+ if (guardReason !== "protected_branch") {
8060
+ action = !in_sync ? newer ? "newer available + drift: run npx codebyplan claude update" : "run npx codebyplan claude update" : newer ? "newer available: run npx codebyplan claude update" : null;
8061
+ }
8062
+ const result = {
8063
+ installed,
8064
+ manifest_version,
8065
+ latest,
8066
+ newer,
8067
+ version_skip,
8068
+ drifted_files,
8069
+ new_in_package,
8070
+ removed_from_package,
8071
+ settings_drift,
8072
+ // Always false in the full-detection path: a protected_branch consumer
8073
+ // (the only guarded case that reaches here) must still SEE the segment;
8074
+ // only canonical_source / no-manifest (Branch A) report guarded:true.
8075
+ guarded: false,
8076
+ guard_reason: guardReason,
8077
+ in_sync,
8078
+ action,
8079
+ checked_at
8080
+ };
8081
+ emitResult(result, writeCache, quiet, projectDir);
8082
+ process.exit(0);
8083
+ } catch (err) {
8084
+ const safe = makeFailSafe(checked_at);
8085
+ if (!quiet) {
8086
+ try {
8087
+ process.stdout.write(JSON.stringify(safe) + "\n");
8088
+ } catch {
8089
+ }
8090
+ }
8091
+ void err;
8092
+ process.exit(0);
8093
+ }
8094
+ }
8095
+ function emitResult(result, writeCache, quiet, projectDir) {
8096
+ const json = JSON.stringify(result, null, 2);
8097
+ if (writeCache) {
8098
+ try {
8099
+ const cacheDir = path7.join(projectDir, ".codebyplan");
8100
+ fs6.mkdirSync(cacheDir, { recursive: true });
8101
+ fs6.writeFileSync(
8102
+ path7.join(cacheDir, "claude-status.local.json"),
8103
+ json + "\n",
8104
+ "utf8"
8105
+ );
8106
+ } catch {
8107
+ }
8108
+ }
8109
+ if (!quiet) {
8110
+ process.stdout.write(json + "\n");
8111
+ }
8112
+ }
8113
+ var init_status = __esm({
8114
+ "src/cli/claude/status.ts"() {
8115
+ "use strict";
8116
+ init_version();
8117
+ init_bump();
8118
+ init_manifest();
8119
+ init_claude_plan();
8120
+ init_settings_merge();
8121
+ init_version_status();
8122
+ }
8123
+ });
8124
+
7826
8125
  // src/lib/prompt.ts
7827
8126
  import * as readline from "node:readline";
7828
8127
  async function ask(q, opts) {
@@ -7841,11 +8140,11 @@ async function ask(q, opts) {
7841
8140
  try {
7842
8141
  while (true) {
7843
8142
  const choices = q.choices.map((c) => `[${c.key}] ${c.label}`).join(" ");
7844
- const answer = await new Promise((resolve8) => {
8143
+ const answer = await new Promise((resolve9) => {
7845
8144
  rl.question(`${q.message}
7846
8145
  ${choices}
7847
8146
  > `, (input) => {
7848
- resolve8(input.trim().toLowerCase());
8147
+ resolve9(input.trim().toLowerCase());
7849
8148
  });
7850
8149
  });
7851
8150
  const match = q.choices.find(
@@ -7939,10 +8238,10 @@ var update_exports = {};
7939
8238
  __export(update_exports, {
7940
8239
  runUpdate: () => runUpdate
7941
8240
  });
7942
- import * as fs5 from "node:fs";
8241
+ import * as fs7 from "node:fs";
7943
8242
  import * as os3 from "node:os";
7944
- import * as path6 from "node:path";
7945
- import { fileURLToPath as fileURLToPath2 } from "node:url";
8243
+ import * as path8 from "node:path";
8244
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
7946
8245
  async function runUpdate(opts, deps = {}) {
7947
8246
  await Promise.resolve();
7948
8247
  const scope = opts.scope ?? "project";
@@ -7958,7 +8257,7 @@ async function runUpdate(opts, deps = {}) {
7958
8257
  const projectDir = deps.projectDir ?? process.cwd();
7959
8258
  let templatesDir;
7960
8259
  try {
7961
- templatesDir = deps.templatesDir ?? resolveTemplatesDirFromInstall();
8260
+ templatesDir = deps.templatesDir ?? resolveTemplatesDirFromInstall2();
7962
8261
  } catch (err) {
7963
8262
  console.error(
7964
8263
  err instanceof Error ? err.message : `codebyplan claude update: ${String(err)}`
@@ -7972,16 +8271,16 @@ async function runUpdate(opts, deps = {}) {
7972
8271
  await runInstall(opts, { ...deps, templatesDir });
7973
8272
  return;
7974
8273
  }
7975
- const plan = buildPlan(projectDir, templatesDir, manifestBefore);
8274
+ const plan = buildDriftPlan(projectDir, templatesDir, manifestBefore);
7976
8275
  const finalManifestEntries = [];
7977
8276
  for (const e of plan.unchanged) {
7978
8277
  finalManifestEntries.push(e);
7979
8278
  }
7980
8279
  for (const { packaged, absSrc } of plan.overwriteSafe) {
7981
- const absDest = path6.join(projectDir, ".claude", packaged.dest);
8280
+ const absDest = path8.join(projectDir, ".claude", packaged.dest);
7982
8281
  if (!opts.dryRun) {
7983
- fs5.mkdirSync(path6.dirname(absDest), { recursive: true });
7984
- fs5.copyFileSync(absSrc, absDest);
8282
+ fs7.mkdirSync(path8.dirname(absDest), { recursive: true });
8283
+ fs7.copyFileSync(absSrc, absDest);
7985
8284
  if (opts.verbose) console.log(`updated ${packaged.dest}`);
7986
8285
  } else if (opts.verbose) {
7987
8286
  console.log(`[dry-run] would update ${packaged.dest}`);
@@ -7993,8 +8292,8 @@ async function runUpdate(opts, deps = {}) {
7993
8292
  absSrc,
7994
8293
  onDiskContent
7995
8294
  } of plan.overwriteHandEdited) {
7996
- const absDest = path6.join(projectDir, ".claude", packaged.dest);
7997
- const newContent = fs5.readFileSync(absSrc);
8295
+ const absDest = path8.join(projectDir, ".claude", packaged.dest);
8296
+ const newContent = fs7.readFileSync(absSrc);
7998
8297
  const showDiff = () => {
7999
8298
  console.log(
8000
8299
  renderDiff(
@@ -8006,8 +8305,8 @@ async function runUpdate(opts, deps = {}) {
8006
8305
  const answer = await promptOverwrite(packaged.dest, opts, showDiff);
8007
8306
  if (answer === "overwrite") {
8008
8307
  if (!opts.dryRun) {
8009
- fs5.mkdirSync(path6.dirname(absDest), { recursive: true });
8010
- fs5.copyFileSync(absSrc, absDest);
8308
+ fs7.mkdirSync(path8.dirname(absDest), { recursive: true });
8309
+ fs7.copyFileSync(absSrc, absDest);
8011
8310
  }
8012
8311
  finalManifestEntries.push(packaged);
8013
8312
  } else {
@@ -8022,10 +8321,10 @@ async function runUpdate(opts, deps = {}) {
8022
8321
  for (const { packaged, absSrc } of plan.newOptIn) {
8023
8322
  const answer = await promptOptIn(packaged.dest, opts);
8024
8323
  if (answer === "opt-in") {
8025
- const absDest = path6.join(projectDir, ".claude", packaged.dest);
8324
+ const absDest = path8.join(projectDir, ".claude", packaged.dest);
8026
8325
  if (!opts.dryRun) {
8027
- fs5.mkdirSync(path6.dirname(absDest), { recursive: true });
8028
- fs5.copyFileSync(absSrc, absDest);
8326
+ fs7.mkdirSync(path8.dirname(absDest), { recursive: true });
8327
+ fs7.copyFileSync(absSrc, absDest);
8029
8328
  }
8030
8329
  finalManifestEntries.push(packaged);
8031
8330
  if (opts.verbose) console.log(`installed new file ${packaged.dest}`);
@@ -8036,25 +8335,25 @@ async function runUpdate(opts, deps = {}) {
8036
8335
  for (const e of plan.removedFromPackage) {
8037
8336
  const answer = await promptRemove(e.dest, opts);
8038
8337
  if (answer === "remove") {
8039
- const absDest = path6.join(projectDir, ".claude", e.dest);
8040
- if (!opts.dryRun && fs5.existsSync(absDest)) {
8041
- fs5.rmSync(absDest);
8042
- const claudeDir = path6.join(projectDir, ".claude");
8043
- let cur = path6.dirname(absDest);
8044
- while (cur !== claudeDir && cur !== path6.dirname(cur)) {
8045
- if (path6.dirname(cur) === claudeDir) break;
8338
+ const absDest = path8.join(projectDir, ".claude", e.dest);
8339
+ if (!opts.dryRun && fs7.existsSync(absDest)) {
8340
+ fs7.rmSync(absDest);
8341
+ const claudeDir = path8.join(projectDir, ".claude");
8342
+ let cur = path8.dirname(absDest);
8343
+ while (cur !== claudeDir && cur !== path8.dirname(cur)) {
8344
+ if (path8.dirname(cur) === claudeDir) break;
8046
8345
  try {
8047
- fs5.rmdirSync(cur);
8346
+ fs7.rmdirSync(cur);
8048
8347
  if (opts.verbose)
8049
8348
  console.log(
8050
- `pruned empty dir ${path6.relative(claudeDir, cur)}`
8349
+ `pruned empty dir ${path8.relative(claudeDir, cur)}`
8051
8350
  );
8052
- cur = path6.dirname(cur);
8351
+ cur = path8.dirname(cur);
8053
8352
  } catch (err) {
8054
8353
  const code = err.code;
8055
8354
  if (code !== "ENOTEMPTY" && code !== "ENOENT") {
8056
8355
  console.warn(
8057
- `codebyplan claude: could not prune empty dir ${path6.relative(claudeDir, cur)}: ${err.message}`
8356
+ `codebyplan claude: could not prune empty dir ${path8.relative(claudeDir, cur)}: ${err.message}`
8058
8357
  );
8059
8358
  }
8060
8359
  break;
@@ -8066,28 +8365,28 @@ async function runUpdate(opts, deps = {}) {
8066
8365
  if (opts.verbose) console.log(`kept (untracked) ${e.dest}`);
8067
8366
  }
8068
8367
  }
8069
- const hooksJsonPath = path6.join(templatesDir, "hooks", "hooks.json");
8070
- const baseSettingsPath = path6.join(
8368
+ const hooksJsonPath = path8.join(templatesDir, "hooks", "hooks.json");
8369
+ const baseSettingsPath = path8.join(
8071
8370
  templatesDir,
8072
8371
  "settings.project.base.json"
8073
8372
  );
8074
- const settingsPath = path6.join(projectDir, ".claude", "settings.json");
8075
- const existingSettings = fs5.existsSync(settingsPath) ? JSON.parse(fs5.readFileSync(settingsPath, "utf8")) : {};
8076
- if (fs5.existsSync(baseSettingsPath)) {
8373
+ const settingsPath = path8.join(projectDir, ".claude", "settings.json");
8374
+ const existingSettings = fs7.existsSync(settingsPath) ? JSON.parse(fs7.readFileSync(settingsPath, "utf8")) : {};
8375
+ if (fs7.existsSync(baseSettingsPath)) {
8077
8376
  const base = JSON.parse(
8078
- fs5.readFileSync(baseSettingsPath, "utf8")
8377
+ fs7.readFileSync(baseSettingsPath, "utf8")
8079
8378
  );
8080
8379
  mergeBaseSettingsIntoSettings(existingSettings, base);
8081
8380
  }
8082
- if (fs5.existsSync(hooksJsonPath)) {
8381
+ if (fs7.existsSync(hooksJsonPath)) {
8083
8382
  const hooksJson = JSON.parse(
8084
- fs5.readFileSync(hooksJsonPath, "utf8")
8383
+ fs7.readFileSync(hooksJsonPath, "utf8")
8085
8384
  );
8086
8385
  mergeHooksIntoSettings(existingSettings, hooksJson);
8087
8386
  }
8088
8387
  if (!opts.dryRun) {
8089
- fs5.mkdirSync(path6.dirname(settingsPath), { recursive: true });
8090
- fs5.writeFileSync(
8388
+ fs7.mkdirSync(path8.dirname(settingsPath), { recursive: true });
8389
+ fs7.writeFileSync(
8091
8390
  settingsPath,
8092
8391
  JSON.stringify(existingSettings, null, 2) + "\n",
8093
8392
  "utf8"
@@ -8099,7 +8398,7 @@ async function runUpdate(opts, deps = {}) {
8099
8398
  );
8100
8399
  if (opts.verbose && gitignoreAction !== "unchanged") {
8101
8400
  console.log(
8102
- `${opts.dryRun ? "[dry-run] would " : ""}${gitignoreAction} managed .gitignore block in ${path6.relative(projectDir, path6.join(projectDir, ".gitignore"))}`
8401
+ `${opts.dryRun ? "[dry-run] would " : ""}${gitignoreAction} managed .gitignore block in ${path8.relative(projectDir, path8.join(projectDir, ".gitignore"))}`
8103
8402
  );
8104
8403
  }
8105
8404
  if (!opts.dryRun) {
@@ -8125,7 +8424,7 @@ async function runUpdate(opts, deps = {}) {
8125
8424
  function runUpdateUser(opts, deps) {
8126
8425
  let templatesDir;
8127
8426
  try {
8128
- templatesDir = deps.templatesDir ?? resolveTemplatesDirFromInstall();
8427
+ templatesDir = deps.templatesDir ?? resolveTemplatesDirFromInstall2();
8129
8428
  } catch (err) {
8130
8429
  console.error(
8131
8430
  err instanceof Error ? err.message : `codebyplan claude update: ${String(err)}`
@@ -8134,9 +8433,9 @@ function runUpdateUser(opts, deps) {
8134
8433
  return;
8135
8434
  }
8136
8435
  try {
8137
- const userDir = deps.userDir ?? path6.join(os3.homedir(), ".claude");
8138
- const settingsPath = path6.join(userDir, "settings.json");
8139
- const userBaseSettingsPath = path6.join(
8436
+ const userDir = deps.userDir ?? path8.join(os3.homedir(), ".claude");
8437
+ const settingsPath = path8.join(userDir, "settings.json");
8438
+ const userBaseSettingsPath = path8.join(
8140
8439
  templatesDir,
8141
8440
  "settings.user.base.json"
8142
8441
  );
@@ -8148,7 +8447,7 @@ function runUpdateUser(opts, deps) {
8148
8447
  process.exitCode = 1;
8149
8448
  return;
8150
8449
  }
8151
- if (!fs5.existsSync(userBaseSettingsPath)) {
8450
+ if (!fs7.existsSync(userBaseSettingsPath)) {
8152
8451
  console.error(
8153
8452
  "codebyplan claude update: settings.user.base.json not found in templates."
8154
8453
  );
@@ -8156,13 +8455,13 @@ function runUpdateUser(opts, deps) {
8156
8455
  return;
8157
8456
  }
8158
8457
  const userBase = JSON.parse(
8159
- fs5.readFileSync(userBaseSettingsPath, "utf8")
8458
+ fs7.readFileSync(userBaseSettingsPath, "utf8")
8160
8459
  );
8161
- const existingSettings = fs5.existsSync(settingsPath) ? JSON.parse(fs5.readFileSync(settingsPath, "utf8")) : {};
8460
+ const existingSettings = fs7.existsSync(settingsPath) ? JSON.parse(fs7.readFileSync(settingsPath, "utf8")) : {};
8162
8461
  mergeBaseSettingsIntoSettings(existingSettings, userBase);
8163
8462
  if (!opts.dryRun) {
8164
- fs5.mkdirSync(userDir, { recursive: true });
8165
- fs5.writeFileSync(
8463
+ fs7.mkdirSync(userDir, { recursive: true });
8464
+ fs7.writeFileSync(
8166
8465
  settingsPath,
8167
8466
  JSON.stringify(existingSettings, null, 2) + "\n",
8168
8467
  "utf8"
@@ -8185,64 +8484,15 @@ function runUpdateUser(opts, deps) {
8185
8484
  process.exitCode = 1;
8186
8485
  }
8187
8486
  }
8188
- function buildPlan(projectDir, templatesDir, manifest) {
8189
- const packaged = walkTemplates(templatesDir);
8190
- const packagedBySrc = new Map(packaged.map((f) => [f.src, f]));
8191
- const manifestBySrc = new Map(manifest.files.map((f) => [f.src, f]));
8192
- const plan = {
8193
- unchanged: [],
8194
- overwriteSafe: [],
8195
- overwriteHandEdited: [],
8196
- newOptIn: [],
8197
- removedFromPackage: []
8198
- };
8199
- for (const pkg of packaged) {
8200
- const inManifest = manifestBySrc.get(pkg.src);
8201
- const absDest = path6.join(projectDir, ".claude", pkg.dest);
8202
- const absSrc = path6.join(templatesDir, pkg.src);
8203
- if (!inManifest) {
8204
- plan.newOptIn.push({
8205
- packaged: { src: pkg.src, dest: pkg.dest, hash: pkg.hash },
8206
- absSrc
8207
- });
8208
- continue;
8209
- }
8210
- const onDiskExists = fs5.existsSync(absDest);
8211
- const onDiskContent = onDiskExists ? fs5.readFileSync(absDest) : Buffer.alloc(0);
8212
- const onDiskHash = onDiskExists ? sha256(onDiskContent) : null;
8213
- if (pkg.hash === inManifest.hash && onDiskHash === inManifest.hash) {
8214
- plan.unchanged.push(inManifest);
8215
- continue;
8216
- }
8217
- if (onDiskHash !== null && onDiskHash !== inManifest.hash) {
8218
- plan.overwriteHandEdited.push({
8219
- packaged: { src: pkg.src, dest: pkg.dest, hash: pkg.hash },
8220
- absSrc,
8221
- onDiskContent
8222
- });
8223
- } else {
8224
- plan.overwriteSafe.push({
8225
- packaged: { src: pkg.src, dest: pkg.dest, hash: pkg.hash },
8226
- absSrc
8227
- });
8228
- }
8229
- }
8230
- for (const m of manifest.files) {
8231
- if (!packagedBySrc.has(m.src)) {
8232
- plan.removedFromPackage.push(m);
8233
- }
8234
- }
8235
- return plan;
8236
- }
8237
- function resolveTemplatesDirFromInstall() {
8238
- const here = path6.dirname(fileURLToPath2(import.meta.url));
8487
+ function resolveTemplatesDirFromInstall2() {
8488
+ const here = path8.dirname(fileURLToPath3(import.meta.url));
8239
8489
  const candidates = [
8240
- path6.resolve(here, "..", "templates"),
8241
- path6.resolve(here, "..", "..", "templates"),
8242
- path6.resolve(here, "..", "..", "..", "templates")
8490
+ path8.resolve(here, "..", "templates"),
8491
+ path8.resolve(here, "..", "..", "templates"),
8492
+ path8.resolve(here, "..", "..", "..", "templates")
8243
8493
  ];
8244
8494
  for (const c of candidates) {
8245
- if (fs5.existsSync(c) && fs5.statSync(c).isDirectory()) {
8495
+ if (fs7.existsSync(c) && fs7.statSync(c).isDirectory()) {
8246
8496
  return c;
8247
8497
  }
8248
8498
  }
@@ -8256,14 +8506,14 @@ function resolveTemplatesDirFromInstall() {
8256
8506
  var init_update = __esm({
8257
8507
  "src/cli/claude/update.ts"() {
8258
8508
  "use strict";
8259
- init_template_walker();
8260
8509
  init_gitignore_block();
8261
- init_hash();
8262
8510
  init_manifest();
8263
8511
  init_settings_merge();
8264
8512
  init_prompt();
8265
8513
  init_install();
8266
8514
  init_statusline_config();
8515
+ init_hash();
8516
+ init_claude_plan();
8267
8517
  }
8268
8518
  });
8269
8519
 
@@ -8272,9 +8522,9 @@ var uninstall_exports = {};
8272
8522
  __export(uninstall_exports, {
8273
8523
  runUninstall: () => runUninstall
8274
8524
  });
8275
- import * as fs6 from "node:fs";
8525
+ import * as fs8 from "node:fs";
8276
8526
  import * as os4 from "node:os";
8277
- import * as path7 from "node:path";
8527
+ import * as path9 from "node:path";
8278
8528
  async function runUninstall(opts, deps = {}) {
8279
8529
  await Promise.resolve();
8280
8530
  const scope = opts.scope ?? "project";
@@ -8303,15 +8553,15 @@ async function runUninstall(opts, deps = {}) {
8303
8553
  let removed = 0;
8304
8554
  let warnings = 0;
8305
8555
  for (const entry of manifest.files) {
8306
- const abs = path7.join(projectDir, ".claude", entry.dest);
8307
- if (!fs6.existsSync(abs)) {
8556
+ const abs = path9.join(projectDir, ".claude", entry.dest);
8557
+ if (!fs8.existsSync(abs)) {
8308
8558
  console.warn(
8309
8559
  `codebyplan claude uninstall: ${entry.dest} already absent (skipping).`
8310
8560
  );
8311
8561
  warnings += 1;
8312
8562
  continue;
8313
8563
  }
8314
- const onDiskHash = sha256(fs6.readFileSync(abs));
8564
+ const onDiskHash = sha256(fs8.readFileSync(abs));
8315
8565
  if (onDiskHash !== entry.hash) {
8316
8566
  console.warn(
8317
8567
  `codebyplan claude uninstall: ${entry.dest} has been modified locally; removing anyway.`
@@ -8319,7 +8569,7 @@ async function runUninstall(opts, deps = {}) {
8319
8569
  warnings += 1;
8320
8570
  }
8321
8571
  if (!opts.dryRun) {
8322
- fs6.rmSync(abs);
8572
+ fs8.rmSync(abs);
8323
8573
  }
8324
8574
  removed += 1;
8325
8575
  if (opts.verbose) console.log(`removed ${entry.dest}`);
@@ -8327,15 +8577,15 @@ async function runUninstall(opts, deps = {}) {
8327
8577
  if (!opts.dryRun) {
8328
8578
  pruneEmptyManagedDirs(projectDir);
8329
8579
  }
8330
- const settingsPath = path7.join(projectDir, ".claude", "settings.json");
8331
- if (fs6.existsSync(settingsPath)) {
8580
+ const settingsPath = path9.join(projectDir, ".claude", "settings.json");
8581
+ if (fs8.existsSync(settingsPath)) {
8332
8582
  const settings = JSON.parse(
8333
- fs6.readFileSync(settingsPath, "utf8")
8583
+ fs8.readFileSync(settingsPath, "utf8")
8334
8584
  );
8335
- const baseSettingsPath = templatesDir ? path7.join(templatesDir, "settings.project.base.json") : null;
8336
- if (baseSettingsPath && fs6.existsSync(baseSettingsPath)) {
8585
+ const baseSettingsPath = templatesDir ? path9.join(templatesDir, "settings.project.base.json") : null;
8586
+ if (baseSettingsPath && fs8.existsSync(baseSettingsPath)) {
8337
8587
  const base = JSON.parse(
8338
- fs6.readFileSync(baseSettingsPath, "utf8")
8588
+ fs8.readFileSync(baseSettingsPath, "utf8")
8339
8589
  );
8340
8590
  stripBaseSettingsFromSettings(settings, base);
8341
8591
  }
@@ -8343,9 +8593,9 @@ async function runUninstall(opts, deps = {}) {
8343
8593
  if (!opts.dryRun) {
8344
8594
  const isEmpty = Object.keys(settings).length === 0;
8345
8595
  if (isEmpty) {
8346
- fs6.rmSync(settingsPath);
8596
+ fs8.rmSync(settingsPath);
8347
8597
  } else {
8348
- fs6.writeFileSync(
8598
+ fs8.writeFileSync(
8349
8599
  settingsPath,
8350
8600
  JSON.stringify(settings, null, 2) + "\n",
8351
8601
  "utf8"
@@ -8364,11 +8614,11 @@ async function runUninstall(opts, deps = {}) {
8364
8614
  }
8365
8615
  if (!opts.dryRun) {
8366
8616
  const m = manifestPath(projectDir);
8367
- if (fs6.existsSync(m)) fs6.rmSync(m);
8617
+ if (fs8.existsSync(m)) fs8.rmSync(m);
8368
8618
  const mid = midManifestPath(projectDir);
8369
- if (fs6.existsSync(mid)) fs6.rmSync(mid);
8619
+ if (fs8.existsSync(mid)) fs8.rmSync(mid);
8370
8620
  const legacy = oldManifestPath(projectDir);
8371
- if (fs6.existsSync(legacy)) fs6.rmSync(legacy);
8621
+ if (fs8.existsSync(legacy)) fs8.rmSync(legacy);
8372
8622
  }
8373
8623
  console.log(
8374
8624
  `codebyplan claude uninstall${opts.dryRun ? " (dry-run)" : ""}: removed ${removed} files${warnings > 0 ? ` (${warnings} warnings)` : ""}.`
@@ -8390,7 +8640,7 @@ function runUninstallUser(opts, deps) {
8390
8640
  }
8391
8641
  }
8392
8642
  try {
8393
- const userDir = deps.userDir ?? path7.join(os4.homedir(), ".claude");
8643
+ const userDir = deps.userDir ?? path9.join(os4.homedir(), ".claude");
8394
8644
  const existingManifest = readManifestForScope("user", userDir);
8395
8645
  if (!existingManifest) {
8396
8646
  console.error(
@@ -8399,24 +8649,24 @@ function runUninstallUser(opts, deps) {
8399
8649
  process.exitCode = 1;
8400
8650
  return;
8401
8651
  }
8402
- const settingsPath = path7.join(userDir, "settings.json");
8403
- if (fs6.existsSync(settingsPath)) {
8652
+ const settingsPath = path9.join(userDir, "settings.json");
8653
+ if (fs8.existsSync(settingsPath)) {
8404
8654
  const settings = JSON.parse(
8405
- fs6.readFileSync(settingsPath, "utf8")
8655
+ fs8.readFileSync(settingsPath, "utf8")
8406
8656
  );
8407
- const userBaseSettingsPath = templatesDir != null ? path7.join(templatesDir, "settings.user.base.json") : null;
8408
- if (userBaseSettingsPath && fs6.existsSync(userBaseSettingsPath)) {
8657
+ const userBaseSettingsPath = templatesDir != null ? path9.join(templatesDir, "settings.user.base.json") : null;
8658
+ if (userBaseSettingsPath && fs8.existsSync(userBaseSettingsPath)) {
8409
8659
  const userBase = JSON.parse(
8410
- fs6.readFileSync(userBaseSettingsPath, "utf8")
8660
+ fs8.readFileSync(userBaseSettingsPath, "utf8")
8411
8661
  );
8412
8662
  stripBaseSettingsFromSettings(settings, userBase);
8413
8663
  }
8414
8664
  if (!opts.dryRun) {
8415
8665
  const isEmpty = Object.keys(settings).length === 0;
8416
8666
  if (isEmpty) {
8417
- fs6.rmSync(settingsPath);
8667
+ fs8.rmSync(settingsPath);
8418
8668
  } else {
8419
- fs6.writeFileSync(
8669
+ fs8.writeFileSync(
8420
8670
  settingsPath,
8421
8671
  JSON.stringify(settings, null, 2) + "\n",
8422
8672
  "utf8"
@@ -8426,11 +8676,11 @@ function runUninstallUser(opts, deps) {
8426
8676
  }
8427
8677
  if (!opts.dryRun) {
8428
8678
  const m = userManifestPath(userDir);
8429
- if (fs6.existsSync(m)) fs6.rmSync(m);
8679
+ if (fs8.existsSync(m)) fs8.rmSync(m);
8430
8680
  const midUser = userMidManifestPath(userDir);
8431
- if (fs6.existsSync(midUser)) fs6.rmSync(midUser);
8681
+ if (fs8.existsSync(midUser)) fs8.rmSync(midUser);
8432
8682
  const oldUser = userOldManifestPath(userDir);
8433
- if (fs6.existsSync(oldUser)) fs6.rmSync(oldUser);
8683
+ if (fs8.existsSync(oldUser)) fs8.rmSync(oldUser);
8434
8684
  }
8435
8685
  console.log(
8436
8686
  `codebyplan claude uninstall --scope user${opts.dryRun ? " (dry-run)" : ""}: user base settings stripped.`
@@ -8445,23 +8695,23 @@ function runUninstallUser(opts, deps) {
8445
8695
  function pruneEmptyManagedDirs(projectDir) {
8446
8696
  const managedRoots = ["skills", "agents", "hooks", "rules"];
8447
8697
  for (const root of managedRoots) {
8448
- const abs = path7.join(projectDir, ".claude", root);
8449
- if (!fs6.existsSync(abs)) continue;
8698
+ const abs = path9.join(projectDir, ".claude", root);
8699
+ if (!fs8.existsSync(abs)) continue;
8450
8700
  pruneLeafFirst(abs);
8451
8701
  }
8452
8702
  }
8453
8703
  function pruneLeafFirst(dir) {
8454
- if (!fs6.existsSync(dir)) return;
8455
- const stat2 = fs6.statSync(dir);
8704
+ if (!fs8.existsSync(dir)) return;
8705
+ const stat2 = fs8.statSync(dir);
8456
8706
  if (!stat2.isDirectory()) return;
8457
- for (const entry of fs6.readdirSync(dir, { withFileTypes: true })) {
8707
+ for (const entry of fs8.readdirSync(dir, { withFileTypes: true })) {
8458
8708
  if (entry.isDirectory()) {
8459
- pruneLeafFirst(path7.join(dir, entry.name));
8709
+ pruneLeafFirst(path9.join(dir, entry.name));
8460
8710
  }
8461
8711
  }
8462
- const remaining = fs6.readdirSync(dir);
8712
+ const remaining = fs8.readdirSync(dir);
8463
8713
  if (remaining.length === 0) {
8464
- fs6.rmdirSync(dir);
8714
+ fs8.rmdirSync(dir);
8465
8715
  }
8466
8716
  }
8467
8717
  var init_uninstall = __esm({
@@ -8477,13 +8727,13 @@ var init_uninstall = __esm({
8477
8727
 
8478
8728
  // src/index.ts
8479
8729
  init_version();
8480
- import { readFileSync as readFileSync8 } from "node:fs";
8481
- import { resolve as resolve7 } from "node:path";
8730
+ import { readFileSync as readFileSync10 } from "node:fs";
8731
+ import { resolve as resolve8 } from "node:path";
8482
8732
  void (async () => {
8483
8733
  if (!process.env.CODEBYPLAN_API_KEY) {
8484
8734
  try {
8485
- const envPath = resolve7(process.cwd(), ".env.local");
8486
- const content = readFileSync8(envPath, "utf-8");
8735
+ const envPath = resolve8(process.cwd(), ".env.local");
8736
+ const content = readFileSync10(envPath, "utf-8");
8487
8737
  for (const line of content.split("\n")) {
8488
8738
  const trimmed = line.trim();
8489
8739
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -8640,6 +8890,11 @@ void (async () => {
8640
8890
  const subcommand = process.argv[3];
8641
8891
  const flagArgs = process.argv.slice(3);
8642
8892
  const parsed = parseClaudeFlags(flagArgs);
8893
+ if (subcommand === "status") {
8894
+ const { runStatus: runStatus2 } = await Promise.resolve().then(() => (init_status(), status_exports));
8895
+ await runStatus2(process.argv.slice(4));
8896
+ return;
8897
+ }
8643
8898
  if (subcommand === "install") {
8644
8899
  if (parsed === null) {
8645
8900
  process.exit(1);
@@ -8680,6 +8935,9 @@ void (async () => {
8680
8935
  codebyplan claude install [flags] Install skills/agents/hooks into ./.claude/
8681
8936
  codebyplan claude update [flags] Update installed assets to latest versions
8682
8937
  codebyplan claude uninstall [flags] Remove installed assets from ./.claude/
8938
+ codebyplan claude status Check package-sync state (drift, version skip, settings drift)
8939
+ --write-cache Write result to .codebyplan/claude-status.local.json
8940
+ --quiet Suppress stdout output
8683
8941
 
8684
8942
  Flags (apply to install/update/uninstall):
8685
8943
  --yes, -y Auto-accept every prompt with its recommended default