@archme/cli 0.11.1 → 0.11.2

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 +204 -73
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -13127,6 +13127,21 @@ Everything a reader sees in a sequence \u2014 arrow labels, Note text, and the c
13127
13127
  }
13128
13128
  });
13129
13129
 
13130
+ // ../shared/waypoints.ts
13131
+ var init_waypoints = __esm({
13132
+ "../shared/waypoints.ts"() {
13133
+ }
13134
+ });
13135
+
13136
+ // ../shared/leafEndpoints.ts
13137
+ var LEAF_ENDPOINT_GUIDANCE;
13138
+ var init_leafEndpoints = __esm({
13139
+ "../shared/leafEndpoints.ts"() {
13140
+ init_waypoints();
13141
+ LEAF_ENDPOINT_GUIDANCE = `- **Arrows connect leaves.** Name the component that does the work, never the parent it sits in. A component with children (other than its input/output interface ports) is a group: an arrow to it lands on the group's box and can't be followed inside, while an arrow to a leaf still draws on the parent's box whenever the parent is collapsed. If a parent's own code acts, model that code as one more child and name that child. An arrow between a component and one of its own children is always this mistake.`;
13142
+ }
13143
+ });
13144
+
13130
13145
  // ../shared/componentOrganization.ts
13131
13146
  var COMPONENT_ORGANIZATION_GUIDANCE;
13132
13147
  var init_componentOrganization = __esm({
@@ -13181,9 +13196,10 @@ ${draftBlock}Sequence diagram format:
13181
13196
  - Use "as" aliases for display names: participant root.api.gateway as API Gateway
13182
13197
  - Supports combined fragments: alt/else, opt, par/and, loop, critical/option, break
13183
13198
  - All participant references MUST be absolute paths prefixed with "root." (e.g., root.backend.api, never backend.api). Scope-relative paths are rejected on write \u2014 they break move_architecture's path-rewriter and the UML grouping logic.
13199
+ ${LEAF_ENDPOINT_GUIDANCE}
13184
13200
  - Example: "sequenceDiagram\\n root.mobile_user->>root.client: opens app\\n root.client->>root.api.post_users: POST /users\\n root.api.post_users->>root.db: Save new user\\n root.db-->>root.api.post_users: user record\\n root.api.post_users-->>root.client: Account created"
13185
- - With branching: "sequenceDiagram\\n root.client->>root.api: POST /upload\\n alt File accepted\\n root.api-->>root.client: Upload saved\\n else Wrong type or too large\\n root.api-->>root.client: Upload rejected\\n end"
13186
- - With parallelism: "sequenceDiagram\\n root.api->>root.db: Record the upload\\n par\\n root.api->>root.storage: Store the file\\n and\\n root.api->>root.search: Make it searchable\\n end"
13201
+ - With branching: "sequenceDiagram\\n root.client->>root.api.upload: POST /upload\\n alt File accepted\\n root.api.upload-->>root.client: Upload saved\\n else Wrong type or too large\\n root.api.upload-->>root.client: Upload rejected\\n end"
13202
+ - With parallelism: "sequenceDiagram\\n root.api.upload->>root.db: Record the upload\\n par\\n root.api.upload->>root.storage: Store the file\\n and\\n root.api.upload->>root.search: Make it searchable\\n end"
13187
13203
 
13188
13204
  ${SEQUENCE_WORDING_GUIDANCE}
13189
13205
 
@@ -13221,6 +13237,7 @@ var init_scanPrompts = __esm({
13221
13237
  init_storyNaming();
13222
13238
  init_storyChapterGuidance();
13223
13239
  init_sequenceWording();
13240
+ init_leafEndpoints();
13224
13241
  init_componentOrganization();
13225
13242
  BUILD_METHODOLOGY = `You are a build sub-agent. You are part of a multi-agent system that builds a comprehensive visual architecture of a codebase \u2014 components, connections, and user stories that trace data flow end-to-end. Your role is to translate research findings into actual architecture: creating components backed by real code references, wiring connections between them, and building story sequence diagrams that trace how data flows through the system.
13226
13243
 
@@ -13229,7 +13246,7 @@ var init_scanPrompts = __esm({
13229
13246
  - Only build what the research findings and code evidence support. Do not invent components, connections, or user stories that aren't backed by real code. If findings are insufficient, flag gaps rather than guessing.
13230
13247
  - Every connection MUST have a non-empty method
13231
13248
  - Keep component hierarchies nested (e.g., backend > api > routes)
13232
- - Use at: "parent" to target nested scopes for connectionUpdates
13249
+ - Name connectionUpdates endpoints with "root."-prefixed paths; each connection is stored where its endpoints meet
13233
13250
  - External services must be created as components with \`role: "external"\` (and \`kind: "human"\` for human actors), then referenced by dot-path in sequences like any other component
13234
13251
  - Small utility modules are implementation details \u2014 omit unless architecturally significant
13235
13252
  - Components must exist before non-draft stories can reference them. Add components first, then wire stories.
@@ -13341,8 +13358,9 @@ Component annotations:
13341
13358
  - Example: { id: "user-org", from: "user", to: "org", method: "belongs_to", reference: true }
13342
13359
 
13343
13360
  Connection format:
13344
- - Use dot-paths to target interface children: "api_server.post_users"
13345
- - ID format: "from-to" (e.g., "client-api_server.post_users")
13361
+ - Name both endpoints with "root."-prefixed dot-paths, as in sequences (or paths relative to the entry's "at"); interface children too: "root.api_server.post_users"
13362
+ - You don't choose the scope: each connection is stored where its endpoints' parents meet, and the result says where and under which id \u2014 use that "at" + id to remove it
13363
+ - id is optional: omitted, it is minted as "<from>-><to>"
13346
13364
  - method describes how components communicate (e.g., REST, gRPC, GraphQL, WebSocket, Kafka, function, event)`;
13347
13365
  FORMAT_REF_DRAFT_STORIES_BLOCK = `Draft stories:
13348
13366
  - Set draft: true for stories where the architecture isn't built yet
@@ -13571,6 +13589,16 @@ var init_validationCodes = __esm({
13571
13589
  // ──────────────────────────────────────────────────────────────────────────
13572
13590
  /** componentUpdates[].remove references an id that doesn't exist at scope. */
13573
13591
  COMPONENT_REMOVE_ID_NOT_FOUND: "component.remove.id-not-found",
13592
+ /**
13593
+ * WARNING (non-blocking): this write removed components, so the connections
13594
+ * naming them (or their descendants), in any scope, were removed with them.
13595
+ */
13596
+ COMPONENT_REMOVE_CONNECTIONS_REMOVED: "component.remove.connections-removed",
13597
+ /**
13598
+ * WARNING (non-blocking): this write removed components that story sequences
13599
+ * still name. The sequences are left as they are for the agent to fix.
13600
+ */
13601
+ COMPONENT_REMOVE_STORY_STILL_REFERENCES: "component.remove.story-still-references",
13574
13602
  // ──────────────────────────────────────────────────────────────────────────
13575
13603
  // connection.add
13576
13604
  // ──────────────────────────────────────────────────────────────────────────
@@ -13584,9 +13612,16 @@ var init_validationCodes = __esm({
13584
13612
  CONNECTION_ADD_MISSING_TO: "connection.add.missing-to",
13585
13613
  /** Missing or empty `method`. */
13586
13614
  CONNECTION_ADD_MISSING_METHOD: "connection.add.missing-method",
13587
- /** Connection emitted at a parent scope outside the current scope. */
13615
+ /**
13616
+ * @deprecated No longer emitted: a connection add is stored where its
13617
+ * endpoints meet (`resolveConnectionUpdates`), whatever scope it names.
13618
+ * Connection emitted at a parent scope outside the current scope.
13619
+ */
13588
13620
  CONNECTION_AT_WRONG_SCOPE_OUTSIDE: "connection.add.wrong-scope-outside",
13589
- /** Connection internal to a child but added at the parent level. */
13621
+ /**
13622
+ * @deprecated No longer emitted, as above.
13623
+ * Connection internal to a child but added at the parent level.
13624
+ */
13590
13625
  CONNECTION_AT_WRONG_SCOPE_INTERNAL: "connection.add.wrong-scope-internal",
13591
13626
  // ──────────────────────────────────────────────────────────────────────────
13592
13627
  // connection.remove
@@ -13606,6 +13641,12 @@ var init_validationCodes = __esm({
13606
13641
  * One code, both sites: to an agent it's the same rule.
13607
13642
  */
13608
13643
  CONNECTION_PAIR_ALREADY_CONNECTED: "connection.add.pair-already-connected",
13644
+ /**
13645
+ * WARNING (non-blocking): an explicit `connectionUpdates.add` was NOT stored —
13646
+ * a finer edge on the same pair (one endpoint a child of this one's) already
13647
+ * says it, and the subsumption prune keeps only the finer edge.
13648
+ */
13649
+ CONNECTION_ADD_SUBSUMED_BY_CHILD: "connection.add.subsumed-by-child",
13609
13650
  // ──────────────────────────────────────────────────────────────────────────
13610
13651
  // story.add — story-add fields
13611
13652
  // ──────────────────────────────────────────────────────────────────────────
@@ -13696,6 +13737,14 @@ var init_validationCodes = __esm({
13696
13737
  PARTICIPANT_NOT_ROOT_PREFIXED: "sequence.participant.not-root-prefixed",
13697
13738
  /** Participant dot-path references a component that doesn't exist. */
13698
13739
  PARTICIPANT_COMPONENT_NOT_FOUND: "sequence.participant.component-not-found",
13740
+ /**
13741
+ * WARNING (non-blocking): an arrow in a sequence the write authored, or a
13742
+ * `connectionUpdates` add, ends on a component with children
13743
+ * (`LEAF_ENDPOINT_GUIDANCE`). The connection lands on the group's box instead
13744
+ * of the part that does the work. One code for both sites, as with
13745
+ * CONNECTION_PAIR_ALREADY_CONNECTED. Silent on a byte-identical re-send.
13746
+ */
13747
+ PARTICIPANT_HAS_CHILDREN: "sequence.participant.has-children",
13699
13748
  // ──────────────────────────────────────────────────────────────────────────
13700
13749
  // sequence.ref — validateSequenceRefs
13701
13750
  // ──────────────────────────────────────────────────────────────────────────
@@ -16540,7 +16589,19 @@ function stripPromptEcho(haystack, prompt) {
16540
16589
  function rateLimitDeferPlanForExit(channels, nowMs = Date.now(), defaultTimeZone = localTimeZone()) {
16541
16590
  const haystack = stripPromptEcho(`${channels.message}
16542
16591
  ${channels.stderr}`, channels.prompt);
16543
- return rateLimitDeferPlan(haystack, nowMs, defaultTimeZone);
16592
+ const plan = rateLimitDeferPlan(haystack, nowMs, defaultTimeZone);
16593
+ if (plan?.parsed) return plan;
16594
+ return claudeErrorDeferPlan(channels.claudeError, nowMs, defaultTimeZone) ?? plan;
16595
+ }
16596
+ function claudeErrorDeferPlan(error, nowMs, defaultTimeZone) {
16597
+ if (!error) return null;
16598
+ const text = error.text.trim();
16599
+ const parsed = rateLimitPauseUntil(text, nowMs, defaultTimeZone);
16600
+ const opensWithLimit = parsed != null && text.startsWith(parsed.info.matched);
16601
+ if (error.apiErrorStatus === 429 || error.apiErrorStatus == null && opensWithLimit) {
16602
+ return parsed ? { parsed: true, pauseUntil: parsed.pauseUntil, info: parsed.info } : { parsed: false, pauseUntil: new Date(nowMs + RATE_LIMIT_TENTATIVE_PAUSE_MS), info: null };
16603
+ }
16604
+ return null;
16544
16605
  }
16545
16606
  var RATE_LIMIT_RE, MONTHS, RATE_LIMIT_TENTATIVE_PAUSE_MS, DATED_RESET_ROLLOVER_GRACE_MS, RATE_LIMIT_PAUSE_PADDING_MS, RATE_LIMIT_SHAPED_RE;
16546
16607
  var init_rateLimit = __esm({
@@ -17840,16 +17901,20 @@ __export(run_exports, {
17840
17901
  buildClaudeExitMessage: () => buildClaudeExitMessage,
17841
17902
  buildDevices: () => buildDevices,
17842
17903
  buildDiffPrompt: () => buildDiffPrompt,
17904
+ claudeErrorDetails: () => claudeErrorDetails,
17905
+ claudeErrorResult: () => claudeErrorResult,
17843
17906
  claudeFailureDetail: () => claudeFailureDetail,
17844
17907
  computeDiff: () => computeDiff,
17845
17908
  forkRepoScope: () => forkRepoScope,
17909
+ isClaudeAuthFailure: () => isClaudeAuthFailure,
17846
17910
  parseClaudeResult: () => parseClaudeResult,
17847
17911
  parseJobTokenResponse: () => parseJobTokenResponse,
17848
17912
  probeMcpAuth: () => probeMcpAuth,
17849
17913
  readBranchMarker: () => readBranchMarker,
17850
17914
  routeShutdownInterrupt: () => routeShutdownInterrupt,
17851
17915
  runDaemon: () => runDaemon,
17852
- teeStream: () => teeStream
17916
+ teeStream: () => teeStream,
17917
+ withFailureDetail: () => withFailureDetail
17853
17918
  });
17854
17919
  import { createClient } from "@supabase/supabase-js";
17855
17920
  import { randomUUID } from "node:crypto";
@@ -17866,12 +17931,29 @@ function attributeKillCause(result, recorded) {
17866
17931
  function buildClaudeExitMessage(exitCode, causeSuffix, stderr) {
17867
17932
  return `claude exited ${exitCode}${causeSuffix}: ${stderr.slice(0, 4e3)}`;
17868
17933
  }
17934
+ function claudeErrorResult(stdout) {
17935
+ return claudeErrorDetails(stdout)?.text || null;
17936
+ }
17937
+ function claudeErrorDetails(stdout) {
17938
+ const parsed = parseClaudeResult(stdout);
17939
+ return parsed.parsed && parsed.isError ? { text: parsed.summary.trim(), apiErrorStatus: parsed.apiErrorStatus } : null;
17940
+ }
17869
17941
  function claudeFailureDetail(stderr, stdout) {
17942
+ const result = claudeErrorResult(stdout);
17943
+ if (result) return result.slice(-FAILURE_DETAIL_MAX);
17870
17944
  if (stderr.trim() || !stdout.trim()) return null;
17871
17945
  const parsed = parseClaudeResult(stdout);
17872
17946
  const text = (parsed.parsed ? parsed.summary : stdout).trim();
17873
17947
  return text ? text.slice(-FAILURE_DETAIL_MAX) : null;
17874
17948
  }
17949
+ function isClaudeAuthFailure(errorResult, stderr) {
17950
+ return /failed to authenticate|oauth session expired|not logged in|please run \/login/i.test(`${errorResult ?? ""}
17951
+ ${stderr}`);
17952
+ }
17953
+ function withFailureDetail(message, detail) {
17954
+ return detail ? `claude said: ${detail}
17955
+ ${message}` : message;
17956
+ }
17875
17957
  async function routeShutdownInterrupt(id, cancelled2, fail, exitCode) {
17876
17958
  if (cancelled2.has(id)) return;
17877
17959
  if (exitCode === 0) {
@@ -18625,10 +18707,11 @@ function parseClaudeResult(stdout) {
18625
18707
  usage: modelUsage ? sumModelUsage(modelUsage) : null,
18626
18708
  costUsd: typeof d.total_cost_usd === "number" ? d.total_cost_usd : null,
18627
18709
  isError: d.is_error === true,
18710
+ apiErrorStatus: typeof d.api_error_status === "number" ? d.api_error_status : null,
18628
18711
  parsed: true
18629
18712
  };
18630
18713
  } catch {
18631
- return { summary: raw, modelServed: null, usage: null, costUsd: null, isError: false, parsed: false };
18714
+ return { summary: raw, modelServed: null, usage: null, costUsd: null, isError: false, apiErrorStatus: null, parsed: false };
18632
18715
  }
18633
18716
  }
18634
18717
  async function setupDigestMcp(ctx, job, deviceId) {
@@ -18917,16 +19000,30 @@ async function runJob(ctx, job, claimDeviceId) {
18917
19000
  if (result.exitCode !== 0) {
18918
19001
  const causeSuffix = cause ? ` (${result.signal ?? "exit"}, cause: ${cause})` : "";
18919
19002
  const stderr = result.stderr ? String(result.stderr) : "";
18920
- const detail = claudeFailureDetail(stderr, result.stdout ? String(result.stdout) : "");
19003
+ const stdout = result.stdout ? String(result.stdout) : "";
19004
+ const detail = claudeFailureDetail(stderr, stdout);
18921
19005
  if (detail) log(`claude output: ${detail.replace(/\n/g, " ")}`);
18922
- throw new Error(buildClaudeExitMessage(result.exitCode, causeSuffix, stderr));
19006
+ throw new ClaudeRunError(
19007
+ buildClaudeExitMessage(result.exitCode, causeSuffix, stderr),
19008
+ detail,
19009
+ isClaudeAuthFailure(claudeErrorResult(stdout), stderr),
19010
+ claudeErrorDetails(stdout)
19011
+ );
18923
19012
  }
18924
19013
  const runResult = parseClaudeResult(result.stdout ? String(result.stdout) : "");
18925
19014
  if (!runResult.parsed) {
18926
19015
  log(`warning: claude --output-format json stdout was not a JSON result object; using raw text as summary`);
18927
19016
  }
18928
19017
  if (runResult.isError) {
18929
- throw new Error(`claude reported is_error in its JSON result (no successful completion)`);
19018
+ throw new ClaudeRunError(
19019
+ `claude reported is_error in its JSON result (no successful completion)`,
19020
+ runResult.summary.trim().slice(-FAILURE_DETAIL_MAX) || null,
19021
+ isClaudeAuthFailure(
19022
+ claudeErrorResult(result.stdout ? String(result.stdout) : ""),
19023
+ result.stderr ? String(result.stderr) : ""
19024
+ ),
19025
+ claudeErrorDetails(result.stdout ? String(result.stdout) : "")
19026
+ );
18930
19027
  }
18931
19028
  const summary2 = runResult.summary.trim().slice(0, 4e3);
18932
19029
  if (summary2) log(`claude> ${summary2.slice(0, 500).replace(/\n/g, " ")}`);
@@ -18983,20 +19080,22 @@ async function runJob(ctx, job, claimDeviceId) {
18983
19080
  return;
18984
19081
  }
18985
19082
  const msg = err4 instanceof Error ? err4.message : String(err4);
19083
+ const runErr = err4 instanceof ClaudeRunError ? err4 : null;
18986
19084
  const haystack = stripPromptEcho(`${msg}
18987
19085
  ${logs.stderr}`, sentPrompt);
18988
19086
  const plan = rateLimitDeferPlanForExit({
18989
19087
  message: msg,
18990
19088
  stderr: logs.stderr,
18991
19089
  stdout: logs.stdout,
18992
- prompt: sentPrompt
19090
+ prompt: sentPrompt,
19091
+ claudeError: runErr?.claudeError
18993
19092
  });
18994
19093
  if (plan) {
18995
19094
  const { parsed, pauseUntil, info } = plan;
18996
19095
  if (!parsed) {
18997
19096
  void rpcReportEvent(ctx, job.org_id, "rate_limit_unmatched_exit", {
18998
19097
  job_id: job.id,
18999
- excerpt: haystack.slice(0, 500)
19098
+ excerpt: [runErr?.claudeError?.text, haystack].filter(Boolean).join("\n").slice(0, 500)
19000
19099
  });
19001
19100
  }
19002
19101
  console.log(
@@ -19016,8 +19115,21 @@ ${logs.stderr}`, sentPrompt);
19016
19115
  if (deferred) return;
19017
19116
  console.warn(`[${job.id}] defer no-op \u2014 falling through to fail`);
19018
19117
  }
19019
- console.log(`[${job.id}] failed: ${msg}`);
19020
- await rpcFail(ctx, job.id, deviceId, msg, metrics, logs);
19118
+ if (!plan && runErr?.authFailure) {
19119
+ const pauseUntil = new Date(Date.now() + AUTH_PAUSE_MS);
19120
+ console.error(
19121
+ `[${job.id}] CLAUDE CODE IS NOT AUTHENTICATED on this host (${runErr.detail ?? "auth failure"}). Run \`claude /login\` from this machine's DESKTOP session, not over SSH: on macOS an SSH login writes ~/.claude/.credentials.json while this daemon reads the keychain. Deferring job + pausing daemon until ${pauseUntil.toISOString()}.`
19122
+ );
19123
+ const detail = { job_id: job.id, claude_said: runErr.detail };
19124
+ await rpcSetDaemonPause(ctx, pauseUntil, "claude_auth_expired", detail);
19125
+ setLocalPause(pauseUntil.getTime());
19126
+ const deferred = await rpcDefer(ctx, job.id, deviceId, "claude_auth_expired", detail);
19127
+ if (deferred) return;
19128
+ console.warn(`[${job.id}] defer no-op \u2014 falling through to fail`);
19129
+ }
19130
+ const failMessage = withFailureDetail(msg, runErr?.detail ?? null);
19131
+ console.log(`[${job.id}] failed: ${failMessage}`);
19132
+ await rpcFail(ctx, job.id, deviceId, failMessage, metrics, logs);
19021
19133
  } finally {
19022
19134
  clearInterval(heartbeat);
19023
19135
  inFlight.delete(job.id);
@@ -19422,7 +19534,7 @@ ${signal} received, shutting down`);
19422
19534
  process.on("SIGINT", () => shutdown("SIGINT"));
19423
19535
  process.on("SIGTERM", () => shutdown("SIGTERM"));
19424
19536
  }
19425
- 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, JOB_TOKEN_PREFIX, 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;
19537
+ 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, AUTH_PAUSE_MS, ClaudeRunError, 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, JOB_TOKEN_PREFIX, 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;
19426
19538
  var init_run = __esm({
19427
19539
  "src/daemon/run.ts"() {
19428
19540
  "use strict";
@@ -19472,6 +19584,15 @@ var init_run = __esm({
19472
19584
  LOG_STDOUT_CAP_BYTES = 30 * 1024;
19473
19585
  LOG_STDERR_CAP_BYTES = 30 * 1024;
19474
19586
  FAILURE_DETAIL_MAX = 400;
19587
+ AUTH_PAUSE_MS = 15 * 60 * 1e3;
19588
+ ClaudeRunError = class extends Error {
19589
+ constructor(message, detail, authFailure, claudeError) {
19590
+ super(message);
19591
+ this.detail = detail;
19592
+ this.authFailure = authFailure;
19593
+ this.claudeError = claudeError;
19594
+ }
19595
+ };
19475
19596
  PRE_SPAWN_TOKEN_MIN_LIFETIME_MS = CLAUDE_LARGE_DIFF_TIMEOUT_MS + 5 * 60 * 1e3;
19476
19597
  inFlight = /* @__PURE__ */ new Map();
19477
19598
  cancelled = /* @__PURE__ */ new Set();
@@ -22514,9 +22635,9 @@ var init_run_init = __esm({
22514
22635
  }
22515
22636
  });
22516
22637
 
22517
- // ../shared/waypoints.ts
22518
- var init_waypoints = __esm({
22519
- "../shared/waypoints.ts"() {
22638
+ // ../shared/connectionPair.ts
22639
+ var init_connectionPair = __esm({
22640
+ "../shared/connectionPair.ts"() {
22520
22641
  }
22521
22642
  });
22522
22643
 
@@ -22645,80 +22766,87 @@ function toAbsolute(endpoint, scopePath) {
22645
22766
  if (scopePath.length === 0) return "root." + endpoint;
22646
22767
  return "root." + scopePath.join(".") + "." + endpoint;
22647
22768
  }
22648
- function toRelative(absolute, scopePath) {
22649
- const prefix = scopePath.length === 0 ? "root." : "root." + scopePath.join(".") + ".";
22650
- if (absolute.startsWith(prefix)) return absolute.slice(prefix.length);
22651
- return absolute;
22652
- }
22653
- function rewriteAllConnections(arch, rewriter) {
22654
- const entries = [];
22655
- function walkConns(connections, scopePath) {
22769
+ function listAllConnections(arch) {
22770
+ const out = [];
22771
+ const walkConns = (connections, scopePath) => {
22656
22772
  for (const conn of connections) {
22657
- const absFrom = toAbsolute(conn.from, scopePath);
22658
- const absTo = toAbsolute(conn.to, scopePath);
22659
- const newAbsFrom = rewriter(absFrom);
22660
- const newAbsTo = rewriter(absTo);
22661
- const changed = newAbsFrom !== absFrom || newAbsTo !== absTo;
22662
- entries.push({
22773
+ out.push({
22663
22774
  conn,
22664
22775
  scopePath,
22665
- newFrom: newAbsFrom,
22666
- newTo: newAbsTo,
22667
- changed
22776
+ absFrom: toAbsolute(conn.from, scopePath),
22777
+ absTo: toAbsolute(conn.to, scopePath)
22668
22778
  });
22669
22779
  }
22670
- }
22671
- function walkComponents(components, basePath) {
22780
+ };
22781
+ const walkComponents = (components, basePath) => {
22672
22782
  for (const comp of components) {
22673
22783
  const compPath = [...basePath, comp.id];
22674
22784
  if (comp.connections) walkConns(comp.connections, compPath);
22675
22785
  if (comp.components) walkComponents(comp.components, compPath);
22676
22786
  }
22677
- }
22787
+ };
22678
22788
  walkConns(arch.connections, []);
22679
22789
  walkComponents(arch.components, []);
22680
- const changedCount = entries.filter((e) => e.changed).length;
22681
- if (changedCount === 0) return { arch, connectionsUpdated: 0 };
22790
+ return out;
22791
+ }
22792
+ function dropConnections(arch, doomed) {
22793
+ const byScope = /* @__PURE__ */ new Map();
22794
+ for (const { conn, scopePath } of doomed) {
22795
+ const key = scopePath.join(".");
22796
+ const ids = byScope.get(key) ?? /* @__PURE__ */ new Set();
22797
+ ids.add(conn.id);
22798
+ byScope.set(key, ids);
22799
+ }
22682
22800
  let result = arch;
22683
- const removeByScopeKey = /* @__PURE__ */ new Map();
22684
- for (const entry of entries) {
22685
- if (!entry.changed) continue;
22686
- const key = entry.scopePath.join(".");
22687
- if (!removeByScopeKey.has(key)) removeByScopeKey.set(key, /* @__PURE__ */ new Set());
22688
- removeByScopeKey.get(key).add(entry.conn.id);
22689
- }
22690
- for (const [scopeKey, connIds] of removeByScopeKey) {
22801
+ for (const [scopeKey, ids] of byScope) {
22691
22802
  const scopePath = scopeKey === "" ? [] : scopeKey.split(".");
22692
22803
  const nav = navigateToScope(result, scopePath);
22693
22804
  if (!nav) continue;
22694
22805
  result = replaceScopeAtPath(result, scopePath, {
22695
22806
  ...nav.scope,
22696
- connections: nav.scope.connections.filter((c3) => !connIds.has(c3.id))
22807
+ connections: nav.scope.connections.filter((c3) => !ids.has(c3.id))
22697
22808
  });
22698
22809
  }
22699
- for (const entry of entries) {
22700
- if (!entry.changed) continue;
22701
- const fromSegments = entry.newFrom.split(".");
22702
- const toSegments = entry.newTo.split(".");
22703
- const lcaSegments = findLCA(fromSegments, toSegments);
22704
- const lcaScopePath = lcaSegments.length > 1 ? lcaSegments.slice(1) : [];
22705
- const lcaNav = navigateToScope(result, lcaScopePath);
22706
- if (!lcaNav) continue;
22810
+ return result;
22811
+ }
22812
+ function connectionHome(absFrom, absTo) {
22813
+ const a2 = absFrom.split(".").slice(1);
22814
+ const b = absTo.split(".").slice(1);
22815
+ const scopePath = findLCA(a2.slice(0, -1), b.slice(0, -1));
22816
+ return { scopePath, from: a2.slice(scopePath.length).join("."), to: b.slice(scopePath.length).join(".") };
22817
+ }
22818
+ function rewriteAllConnections(arch, rewriter) {
22819
+ const changed = listAllConnections(arch).map((e) => ({ ...e, newFrom: rewriter(e.absFrom), newTo: rewriter(e.absTo) })).filter((e) => e.newFrom !== e.absFrom || e.newTo !== e.absTo);
22820
+ if (changed.length === 0) return { arch, connectionsUpdated: 0, notCarried: [] };
22821
+ let result = dropConnections(arch, changed);
22822
+ const notCarried = [];
22823
+ for (const entry of changed) {
22824
+ const home2 = connectionHome(entry.newFrom, entry.newTo);
22825
+ const nav = navigateToScope(result, home2.scopePath);
22826
+ if (!nav) continue;
22707
22827
  const newConn = {
22708
22828
  ...entry.conn,
22709
- from: toRelative(entry.newFrom, lcaScopePath),
22710
- to: toRelative(entry.newTo, lcaScopePath),
22711
- id: `${toRelative(entry.newFrom, lcaScopePath)}-${toRelative(entry.newTo, lcaScopePath)}`
22712
- };
22713
- const existingConns = lcaNav.scope.connections;
22714
- if (!existingConns.some((c3) => c3.id === newConn.id)) {
22715
- result = replaceScopeAtPath(result, lcaScopePath, {
22716
- ...lcaNav.scope,
22717
- connections: [...existingConns, newConn]
22718
- });
22829
+ from: home2.from,
22830
+ to: home2.to,
22831
+ id: `${home2.from}-${home2.to}`
22832
+ };
22833
+ const existingConns = nav.scope.connections;
22834
+ const taken = existingConns.find((c3) => c3.id === newConn.id);
22835
+ if (taken) {
22836
+ notCarried.push(
22837
+ `connection \`${entry.conn.id}\` (${newConn.from} \u2192 ${newConn.to}) was not carried over: ${scopeLabel(home2.scopePath)} already has \`${taken.id}\` (${taken.from} \u2192 ${taken.to}).`
22838
+ );
22839
+ continue;
22719
22840
  }
22841
+ result = replaceScopeAtPath(result, home2.scopePath, {
22842
+ ...nav.scope,
22843
+ connections: [...existingConns, newConn]
22844
+ });
22720
22845
  }
22721
- return { arch: result, connectionsUpdated: changedCount };
22846
+ return { arch: result, connectionsUpdated: changed.length, notCarried };
22847
+ }
22848
+ function scopeLabel(scopePath) {
22849
+ return scopePath.length === 0 ? "root" : `root.${scopePath.join(".")}`;
22722
22850
  }
22723
22851
  function applyComponentMove(arch, move) {
22724
22852
  const fromPath = move.from === "root" || move.from === "" ? [] : move.from.replace(/^root\./, "").split(".");
@@ -22767,7 +22895,7 @@ function applyComponentMove(arch, move) {
22767
22895
  });
22768
22896
  const rewriter = buildPathRewriter(oldAbsolute, newAbsolute);
22769
22897
  result = rewriteArchPaths(result, rewriter);
22770
- const { arch: afterConns, connectionsUpdated } = rewriteAllConnections(
22898
+ const { arch: afterConns, connectionsUpdated, notCarried } = rewriteAllConnections(
22771
22899
  result,
22772
22900
  rewriter
22773
22901
  );
@@ -22793,13 +22921,16 @@ function applyComponentMove(arch, move) {
22793
22921
  walk(a2.components);
22794
22922
  };
22795
22923
  countAllHops(result);
22796
- const summary2 = `Moved component '${move.id}' from ${move.from} to ${move.to}. As part of this move, ${hopsRewritten} story hop(s) and ${connectionsUpdated} connection(s) were updated to reflect the new path.`;
22924
+ const notCarriedNote = notCarried.length > 0 ? ` Warning: ${notCarried.join(" ")}` : "";
22925
+ const summary2 = `Moved component '${move.id}' from ${move.from} to ${move.to}. As part of this move, ${hopsRewritten} story hop(s) and ${connectionsUpdated} connection(s) were updated to reflect the new path.${notCarriedNote}`;
22797
22926
  return { arch: result, summary: summary2 };
22798
22927
  }
22799
22928
  var init_archUpdate = __esm({
22800
22929
  "../shared/archUpdate.ts"() {
22801
22930
  init_waypoints();
22802
22931
  init_validationCodes();
22932
+ init_connectionPair();
22933
+ init_leafEndpoints();
22803
22934
  }
22804
22935
  });
22805
22936
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archme/cli",
3
- "version": "0.11.1",
3
+ "version": "0.11.2",
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",