@archme/cli 0.8.0 → 0.8.1

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 (2) hide show
  1. package/dist/cli.js +89 -5
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -10344,10 +10344,31 @@ var init_preflight = __esm({
10344
10344
  });
10345
10345
 
10346
10346
  // ../shared/daemonModel.ts
10347
- var DEFAULT_CLAUDE_MODEL;
10347
+ function parseClaudeCodeVersion(output) {
10348
+ return /(\d+)\.(\d+)\.(\d+)/.exec(output ?? "")?.[0] ?? null;
10349
+ }
10350
+ function versionBelow(version, minimum) {
10351
+ const a2 = version.split(".").map(Number);
10352
+ const b = minimum.split(".").map(Number);
10353
+ for (let i2 = 0; i2 < Math.max(a2.length, b.length); i2++) {
10354
+ const d = (a2[i2] ?? 0) - (b[i2] ?? 0);
10355
+ if (d !== 0) return d < 0;
10356
+ }
10357
+ return false;
10358
+ }
10359
+ function runnableModel(requested, installedVersion) {
10360
+ const needs = MIN_CLAUDE_CODE_VERSION[requested];
10361
+ if (!needs || !installedVersion || !versionBelow(installedVersion, needs)) return { model: requested };
10362
+ return { model: FALLBACK_CLAUDE_MODEL, fellBack: { from: requested, needs } };
10363
+ }
10364
+ var DEFAULT_CLAUDE_MODEL, FALLBACK_CLAUDE_MODEL, MIN_CLAUDE_CODE_VERSION;
10348
10365
  var init_daemonModel = __esm({
10349
10366
  "../shared/daemonModel.ts"() {
10350
10367
  DEFAULT_CLAUDE_MODEL = "claude-opus-5-5";
10368
+ FALLBACK_CLAUDE_MODEL = "claude-opus-4-8";
10369
+ MIN_CLAUDE_CODE_VERSION = {
10370
+ "claude-opus-5-5": "2.1.280"
10371
+ };
10351
10372
  }
10352
10373
  });
10353
10374
 
@@ -10509,6 +10530,39 @@ var init_spawn_claude = __esm({
10509
10530
  }
10510
10531
  });
10511
10532
 
10533
+ // src/scan/claudeVersion.ts
10534
+ async function runClaudeVersion() {
10535
+ const r = await execa("claude", ["--version"], { reject: false, timeout: 15e3 });
10536
+ return r.exitCode === 0 ? String(r.stdout) : null;
10537
+ }
10538
+ function createClaudeVersionProbe(opts = {}) {
10539
+ const exec = opts.exec ?? runClaudeVersion;
10540
+ const now = opts.now ?? Date.now;
10541
+ const ttlMs = opts.ttlMs ?? DEFAULT_TTL_MS;
10542
+ let cached;
10543
+ return async () => {
10544
+ if (cached && now() - cached.at < ttlMs) return cached.version;
10545
+ let version = null;
10546
+ try {
10547
+ version = parseClaudeCodeVersion(await exec());
10548
+ } catch {
10549
+ version = null;
10550
+ }
10551
+ cached = { version, at: now() };
10552
+ return version;
10553
+ };
10554
+ }
10555
+ var DEFAULT_TTL_MS, claudeCodeVersion;
10556
+ var init_claudeVersion = __esm({
10557
+ "src/scan/claudeVersion.ts"() {
10558
+ "use strict";
10559
+ init_execa();
10560
+ init_daemonModel();
10561
+ DEFAULT_TTL_MS = 10 * 6e4;
10562
+ claudeCodeVersion = createClaudeVersionProbe();
10563
+ }
10564
+ });
10565
+
10512
10566
  // src/scan/model-pin.ts
10513
10567
  async function resolveModelPin(supabase) {
10514
10568
  try {
@@ -10522,10 +10576,30 @@ async function resolveModelPin(supabase) {
10522
10576
  return DEFAULT_CLAUDE_MODEL;
10523
10577
  }
10524
10578
  }
10579
+ async function resolveRunnableModel(supabase, deps = {}) {
10580
+ const requested = await resolveModelPin(supabase);
10581
+ const installed = await (deps.version ?? claudeCodeVersion)();
10582
+ const { model, fellBack } = runnableModel(requested, installed);
10583
+ if (fellBack) {
10584
+ const warned = deps.warned ?? warnedFallbacks;
10585
+ const key = `${fellBack.from}@${installed}`;
10586
+ if (!warned.has(key)) {
10587
+ warned.add(key);
10588
+ (deps.warn ?? console.warn)(
10589
+ `Claude Code ${installed} can't run ${fellBack.from} (needs ${fellBack.needs}+); running ${model} instead. Run \`claude update\` to use ${fellBack.from}.`
10590
+ );
10591
+ }
10592
+ }
10593
+ return model;
10594
+ }
10595
+ var warnedFallbacks;
10525
10596
  var init_model_pin = __esm({
10526
10597
  "src/scan/model-pin.ts"() {
10527
10598
  "use strict";
10528
10599
  init_spawn_claude();
10600
+ init_daemonModel();
10601
+ init_claudeVersion();
10602
+ warnedFallbacks = /* @__PURE__ */ new Set();
10529
10603
  }
10530
10604
  });
10531
10605
 
@@ -10646,7 +10720,7 @@ async function runDigestJob(ctx, job, deviceId, inFlight2, mcp) {
10646
10720
  mcpConfigPath = join8(dir, "mcp.json");
10647
10721
  await writeFile5(mcpConfigPath, JSON.stringify({ mcpServers: {} }));
10648
10722
  }
10649
- const model = await resolveModelPin(ctx.supabase);
10723
+ const model = await resolveRunnableModel(ctx.supabase);
10650
10724
  const proc = spawnClaude({
10651
10725
  prompt: buildDigestPrompt(payload),
10652
10726
  mcpConfigPath,
@@ -17051,6 +17125,7 @@ __export(run_exports, {
17051
17125
  buildClaudeExitMessage: () => buildClaudeExitMessage,
17052
17126
  buildDevices: () => buildDevices,
17053
17127
  buildDiffPrompt: () => buildDiffPrompt,
17128
+ claudeFailureDetail: () => claudeFailureDetail,
17054
17129
  computeDiff: () => computeDiff,
17055
17130
  parseClaudeResult: () => parseClaudeResult,
17056
17131
  probeMcpAuth: () => probeMcpAuth,
@@ -17074,6 +17149,12 @@ function attributeKillCause(result, recorded) {
17074
17149
  function buildClaudeExitMessage(exitCode, causeSuffix, stderr) {
17075
17150
  return `claude exited ${exitCode}${causeSuffix}: ${stderr.slice(0, 4e3)}`;
17076
17151
  }
17152
+ function claudeFailureDetail(stderr, stdout) {
17153
+ if (stderr.trim() || !stdout.trim()) return null;
17154
+ const parsed = parseClaudeResult(stdout);
17155
+ const text = (parsed.parsed ? parsed.summary : stdout).trim();
17156
+ return text ? text.slice(-FAILURE_DETAIL_MAX) : null;
17157
+ }
17077
17158
  async function routeShutdownInterrupt(id, cancelled2, fail, exitCode) {
17078
17159
  if (cancelled2.has(id)) return;
17079
17160
  if (exitCode === 0) {
@@ -18016,7 +18097,7 @@ async function runJob(ctx, job, claimDeviceId) {
18016
18097
  `MCP auth preflight failed after ${probe.attempts} attempt(s) (last status ${probe.lastStatus ?? "n/a"}): ${probe.detail}. Not spawning claude \u2014 the architecture would not be writable and the run would silently no-op. Failing the job so it retries with a freshly minted token.`
18017
18098
  );
18018
18099
  }
18019
- const model = await resolveModelPin(ctx.supabase);
18100
+ const model = await resolveRunnableModel(ctx.supabase, { warn: log });
18020
18101
  metrics.model = model;
18021
18102
  log(`spawning claude (mcp config at ${configPath}, model ${model})`);
18022
18103
  const proc = spawnClaude({
@@ -18054,6 +18135,8 @@ async function runJob(ctx, job, claimDeviceId) {
18054
18135
  if (result.exitCode !== 0) {
18055
18136
  const causeSuffix = cause ? ` (${result.signal ?? "exit"}, cause: ${cause})` : "";
18056
18137
  const stderr = result.stderr ? String(result.stderr) : "";
18138
+ const detail = claudeFailureDetail(stderr, result.stdout ? String(result.stdout) : "");
18139
+ if (detail) log(`claude output: ${detail.replace(/\n/g, " ")}`);
18057
18140
  throw new Error(buildClaudeExitMessage(result.exitCode, causeSuffix, stderr));
18058
18141
  }
18059
18142
  const runResult = parseClaudeResult(result.stdout ? String(result.stdout) : "");
@@ -18557,7 +18640,7 @@ ${signal} received, shutting down`);
18557
18640
  process.on("SIGINT", () => shutdown("SIGINT"));
18558
18641
  process.on("SIGTERM", () => shutdown("SIGTERM"));
18559
18642
  }
18560
- var HEARTBEAT_INTERVAL_MS, CLAUDE_TIMEOUT_MS, CLAUDE_LARGE_DIFF_TIMEOUT_MS, SAFETY_SWEEP_MS, LOG_STDOUT_CAP_BYTES, LOG_STDERR_CAP_BYTES, PRE_SPAWN_TOKEN_MIN_LIFETIME_MS, SUPABASE_URL, SUPABASE_ANON_KEY, ARCHME_DAEMON_CLIENT_ID, ARCHME_MCP_BASE_URL, CLONE_BASE_DIR, DAEMON_ID, LEASE_SECONDS, inFlight, cancelled, killReasons, activeCleanups, cloneMutex, shuttingDown, daemonVersion, pauseUntilMs, resumeTimer, currentSched, MAX_CONCURRENT_JOBS, ctxByJob, deviceByJob, DEFAULT_CLONE_BASE_DIR, INLINE_PATCH_BYTE_CAP, GIT_SHA_RE, CURATOR_PREAMBLE, CORE_PRINCIPLES, STORY_COVERAGE_GATE, NEW_STORY_GUIDANCE, STORY_DESCRIPTION_EDIT_RULE, PHASE_GUIDANCE, BASE_RULES, DIFF_RULES, finite, channelSeq, MultipleAccountsError, __testDispatch;
18643
+ var HEARTBEAT_INTERVAL_MS, CLAUDE_TIMEOUT_MS, CLAUDE_LARGE_DIFF_TIMEOUT_MS, SAFETY_SWEEP_MS, LOG_STDOUT_CAP_BYTES, LOG_STDERR_CAP_BYTES, FAILURE_DETAIL_MAX, PRE_SPAWN_TOKEN_MIN_LIFETIME_MS, SUPABASE_URL, SUPABASE_ANON_KEY, ARCHME_DAEMON_CLIENT_ID, ARCHME_MCP_BASE_URL, CLONE_BASE_DIR, DAEMON_ID, LEASE_SECONDS, inFlight, cancelled, killReasons, activeCleanups, cloneMutex, shuttingDown, daemonVersion, pauseUntilMs, resumeTimer, currentSched, MAX_CONCURRENT_JOBS, ctxByJob, deviceByJob, DEFAULT_CLONE_BASE_DIR, INLINE_PATCH_BYTE_CAP, GIT_SHA_RE, CURATOR_PREAMBLE, CORE_PRINCIPLES, STORY_COVERAGE_GATE, NEW_STORY_GUIDANCE, STORY_DESCRIPTION_EDIT_RULE, PHASE_GUIDANCE, BASE_RULES, DIFF_RULES, finite, channelSeq, MultipleAccountsError, __testDispatch;
18561
18644
  var init_run = __esm({
18562
18645
  "src/daemon/run.ts"() {
18563
18646
  "use strict";
@@ -18603,6 +18686,7 @@ var init_run = __esm({
18603
18686
  SAFETY_SWEEP_MS = 3e4;
18604
18687
  LOG_STDOUT_CAP_BYTES = 30 * 1024;
18605
18688
  LOG_STDERR_CAP_BYTES = 30 * 1024;
18689
+ FAILURE_DETAIL_MAX = 400;
18606
18690
  PRE_SPAWN_TOKEN_MIN_LIFETIME_MS = CLAUDE_LARGE_DIFF_TIMEOUT_MS + 5 * 60 * 1e3;
18607
18691
  inFlight = /* @__PURE__ */ new Map();
18608
18692
  cancelled = /* @__PURE__ */ new Set();
@@ -19983,7 +20067,7 @@ async function runScan(opts, deps = {}) {
19983
20067
  ...sourceRepo ? { source: await buildScanSource(opts.repoPath, sourceRepo, headSha) } : {}
19984
20068
  });
19985
20069
  try {
19986
- const model = await resolveModelPin(supabase);
20070
+ const model = await resolveRunnableModel(supabase, { warn: log });
19987
20071
  const interactive = opts.interactive !== false;
19988
20072
  log(
19989
20073
  interactive ? `starting claude (mcp config at ${configPath}, model ${model}) \u2014 this session is interactive; answer its questions as they come` : `spawning claude (mcp config at ${configPath}, model ${model}, timeout ${Math.round(SCAN_CLAUDE_TIMEOUT_MS / 6e4)} min)`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archme/cli",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "description": "Map your codebase into a living architecture diagram: run `archme init` in a repo to scan it, `archme daemon start` to keep it current on every commit.",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE",