@archme/cli 0.8.1 → 0.9.0

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 +166 -81
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -10005,9 +10005,23 @@ async function tailLog(opts = {}) {
10005
10005
  await new Promise((resolve6, reject) => {
10006
10006
  let fileWatcher = null;
10007
10007
  let dirWatcher = null;
10008
+ let poller = null;
10009
+ let settled = false;
10008
10010
  const dir = dirname7(path7);
10009
10011
  const name = basename(path7);
10010
- const onChange = async () => {
10012
+ const stop = () => {
10013
+ settled = true;
10014
+ fileWatcher?.close();
10015
+ dirWatcher?.close();
10016
+ if (poller) clearInterval(poller);
10017
+ process.removeListener("SIGINT", handleSigint);
10018
+ };
10019
+ const fail = (err2) => {
10020
+ if (settled) return;
10021
+ stop();
10022
+ reject(err2);
10023
+ };
10024
+ const readNew = async () => {
10011
10025
  try {
10012
10026
  const st = await stat(path7);
10013
10027
  if (st.size < offset) offset = 0;
@@ -10020,61 +10034,50 @@ async function tailLog(opts = {}) {
10020
10034
  });
10021
10035
  offset = st.size;
10022
10036
  }
10037
+ if (!fileWatcher && !settled) attachFileWatcher();
10023
10038
  } catch (err2) {
10024
10039
  if (err2.code === "ENOENT") return;
10025
- reject(err2);
10040
+ fail(err2);
10026
10041
  }
10027
10042
  };
10028
- const attachFileWatcher = () => {
10029
- try {
10030
- fileWatcher = watch(path7, (event) => {
10031
- if (event === "change") void onChange();
10032
- });
10033
- } catch (err2) {
10034
- if (err2.code !== "ENOENT") {
10035
- reject(err2);
10036
- return;
10037
- }
10038
- attachDirWatcher();
10039
- }
10043
+ let chain = Promise.resolve();
10044
+ const drain = () => {
10045
+ if (settled) return;
10046
+ chain = chain.then(readNew);
10040
10047
  };
10041
- const attachDirWatcher = () => {
10048
+ const attachFileWatcher = () => {
10042
10049
  try {
10043
- dirWatcher = watch(dir, (_event, filename) => {
10044
- if (filename === name) {
10045
- void onChange().then(() => {
10046
- dirWatcher?.close();
10047
- dirWatcher = null;
10048
- attachFileWatcher();
10049
- });
10050
- }
10051
- });
10052
- } catch (err2) {
10053
- reject(err2);
10050
+ fileWatcher = watch(path7, () => drain());
10051
+ dirWatcher?.close();
10052
+ dirWatcher = null;
10053
+ } catch {
10054
+ fileWatcher = null;
10054
10055
  }
10055
10056
  };
10056
- stat(path7).then(
10057
- () => attachFileWatcher(),
10058
- (err2) => {
10059
- if (err2.code === "ENOENT") {
10060
- attachDirWatcher();
10061
- } else {
10062
- reject(err2);
10063
- }
10064
- }
10065
- );
10057
+ try {
10058
+ dirWatcher = watch(dir, (_event, filename) => {
10059
+ if (filename === name) drain();
10060
+ });
10061
+ } catch (err2) {
10062
+ fail(err2);
10063
+ return;
10064
+ }
10066
10065
  const handleSigint = () => {
10067
- fileWatcher?.close();
10068
- dirWatcher?.close();
10066
+ if (settled) return;
10067
+ stop();
10069
10068
  resolve6();
10070
10069
  };
10071
10070
  process.once("SIGINT", handleSigint);
10071
+ poller = setInterval(drain, FOLLOW_POLL_MS);
10072
+ drain();
10072
10073
  });
10073
10074
  }
10075
+ var FOLLOW_POLL_MS;
10074
10076
  var init_logs = __esm({
10075
10077
  "src/daemon/service/logs.ts"() {
10076
10078
  "use strict";
10077
10079
  init_paths();
10080
+ FOLLOW_POLL_MS = 500;
10078
10081
  }
10079
10082
  });
10080
10083
 
@@ -13294,6 +13297,13 @@ var init_sequenceChapters = __esm({
13294
13297
  }
13295
13298
  });
13296
13299
 
13300
+ // ../shared/architectureDiff.ts
13301
+ var init_architectureDiff = __esm({
13302
+ "../shared/architectureDiff.ts"() {
13303
+ init_storyChanges();
13304
+ }
13305
+ });
13306
+
13297
13307
  // ../shared/walkthrough.ts
13298
13308
  var MAX_WALKTHROUGH_STEPS, MAX_STEP_TITLE_CHARS;
13299
13309
  var init_walkthrough = __esm({
@@ -13301,6 +13311,7 @@ var init_walkthrough = __esm({
13301
13311
  init_storyChanges();
13302
13312
  init_sequenceChapters();
13303
13313
  init_validationCodes();
13314
+ init_architectureDiff();
13304
13315
  MAX_WALKTHROUGH_STEPS = 20;
13305
13316
  MAX_STEP_TITLE_CHARS = 60;
13306
13317
  }
@@ -13317,13 +13328,15 @@ var init_walkthroughGuidance = __esm({
13317
13328
  - **Steps are in reading order, not build order.** This is not an implementation plan and not a restatement of your sequencing section \u2014 start where a reviewer should start, and put each step where it makes the next one make sense.
13318
13329
  - **Group by idea.** One step is one coherent change, usually 3\u20138 elements. A one-element step is fine and often valuable: an independent fix that would otherwise be lost among the rest. Aim for a handful of steps; the cap is ${MAX_WALKTHROUGH_STEPS}.
13319
13330
  - **Titles are terse** \u2014 a handle, not a summary. Under ${MAX_STEP_TITLE_CHARS} characters. \`Markers move per-repo\`, not \`This step explains how the source marker moves onto a per-repo table\`.
13320
- - **Description is optional and usually absent.** Never restate the components, stories or connections the step already references \u2014 the reader is looking at them. Write one only for something the diagrams cannot show: a constraint, a consequence, a trade-off that is not visible in the graph.
13321
- - **Write it for someone meeting this change for the first time.** They know the system; they have NOT read your proposal body and were not in the design discussion. Say plainly what the step does and why it is there. Two sentences is plenty.
13331
+ - **Every step gets a short description** \u2014 the narration a reader follows while the diagrams move beside it, one step at a time. Two or three plain sentences: what this part of the change is, why it exists, and how it fits the rest, at a high level. Example: \`This is the daemon picking up GitHub's answer after it posts a comment, review or approval on your behalf, and recording whether it worked.\`
13332
+ - Not prescriptive: describe the change, do not tell the reviewer what to check or what to think of it.
13333
+ - Not dense: no condensed paragraph of identifiers, flags and edge cases crammed into one sentence. Never restate the components, stories or connections the step already references \u2014 the reader is looking at them.
13334
+ - **Write it for someone meeting this change for the first time.** They know the system; they have NOT read your proposal body and were not in the design discussion. Say plainly what the step does and why it is there.
13322
13335
  - Don't argue with objections the reader hasn't raised \u2014 no \`not just a nicety\`, no \`as discussed\`, no \`this is why it is its own step\`. They don't know how the walkthrough was assembled and haven't disagreed with you. A trade-off worth stating is information (\`X was considered; it breaks Y\`), not a rebuttal.
13323
13336
  - Don't use the change's OWN new vocabulary undefined. If the step introduces a term, say what it means in the same breath or use plain words.
13324
13337
  - Bad: \`A correctness item, not a phase-3 nicety: anchors are repo-relative paths, so two repos with the same conventional layout collide silently and seed stories from the wrong one.\`
13325
13338
  - Good: \`With two repos in one project a path like src/index.ts exists in both, so looking up which component owns a changed file can return the wrong repo's. Each repo now records the container its code lives under, and lookups search only that subtree.\`
13326
- - **Cover every story the change touches.** A changed story no step references is a change the reader will step straight past.
13339
+ - **Cover every change** \u2014 every story, component and connection the change touches \u2014 or \`set_walkthrough\` rejects the walkthrough and lists what is missing. Guided mode walks the steps and nothing after them, so anything no step reaches is a change the reviewer never sees. A connection is covered when a step names it, names both of its components, or names a story page that draws it as an arrow. Name a connection inside a component as \`<component path>::<id>\` (\`daemon::session_auth->key_account\`); a root connection by its plain id.
13327
13340
  - **Skip it for a small change.** A handful of elements needs no walkthrough; omit it rather than writing a step per element.
13328
13341
  - **Prefer CHAPTERS to narrow a diagram.** If a story's sequence carries \`%% chapter: <id> | <Label>\` markers, name the ones your step covers as \`chapters: [{ story, chapter }]\`, using the **id**, never the label \u2014 ids are stable across a rename, labels are not. A chapter is a page break the story's author already wrote, so it is sharper than naming components, where one hub the step mentions decides the whole view.`;
13329
13342
  }
@@ -16129,7 +16142,9 @@ function observeGithubPull(p) {
16129
16142
  createdAt: p.created_at ?? null,
16130
16143
  updatedAt: p.updated_at ?? null,
16131
16144
  mergedAt: p.merged_at ?? null,
16132
- closedAt: p.closed_at ?? null
16145
+ closedAt: p.closed_at ?? null,
16146
+ // Before the merge it is GitHub's throwaway test merge.
16147
+ mergeCommitSha: p.merged_at ? p.merge_commit_sha ?? null : null
16133
16148
  };
16134
16149
  }
16135
16150
  function metaKey(o2) {
@@ -16164,6 +16179,7 @@ function toItem(o2, action, sent) {
16164
16179
  created_at: o2.createdAt,
16165
16180
  merged_at: o2.mergedAt,
16166
16181
  closed_at: o2.closedAt,
16182
+ merge_commit_sha: o2.mergeCommitSha,
16167
16183
  body: o2.body,
16168
16184
  ...action === "synchronize" && sent.seq !== void 0 ? { seq: sent.seq } : {}
16169
16185
  };
@@ -16573,6 +16589,22 @@ var init_defaultBranch = __esm({
16573
16589
  }
16574
16590
  });
16575
16591
 
16592
+ // ../shared/prRefs.ts
16593
+ function pushCommitSubject(message) {
16594
+ if (message == null) return null;
16595
+ const [subject = "", ...body] = message.split("\n");
16596
+ const pr = MERGE_PR_SUBJECT.exec(subject.trim());
16597
+ const title = body.find((line) => line.trim())?.trim();
16598
+ return pr && title && !TRAILER_LINE.test(title) ? `${title} (#${pr[1]})` : subject;
16599
+ }
16600
+ var MERGE_PR_SUBJECT, TRAILER_LINE;
16601
+ var init_prRefs = __esm({
16602
+ "../shared/prRefs.ts"() {
16603
+ MERGE_PR_SUBJECT = /^Merge pull request #(\d+) from \S+$/;
16604
+ TRAILER_LINE = /^[A-Za-z0-9]+(?:-[A-Za-z0-9]+)+:\s/;
16605
+ }
16606
+ });
16607
+
16576
16608
  // src/daemon/localGit.ts
16577
16609
  var localGit_exports = {};
16578
16610
  __export(localGit_exports, {
@@ -16641,13 +16673,14 @@ async function firstParentRange(dir, marker, tip) {
16641
16673
  async function readCommitMeta(dir, sha) {
16642
16674
  const { exitCode, stdout } = await git(
16643
16675
  dir,
16644
- ["show", "-s", "--format=%s%n%an%n%cI", sha],
16676
+ // %b last: it spans lines, and `pushCommitSubject` reads a merge commit's title from it.
16677
+ ["show", "-s", "--format=%an%n%cI%n%s%n%b", sha],
16645
16678
  { allowFail: true }
16646
16679
  );
16647
16680
  if (exitCode !== 0) return { message: "", authorName: null, committedAt: null };
16648
- const [message = "", authorName = "", committedAt = ""] = stdout.split("\n");
16681
+ const [authorName = "", committedAt = "", ...message] = stdout.split("\n");
16649
16682
  return {
16650
- message: message.trim(),
16683
+ message: (pushCommitSubject(message.join("\n")) ?? "").trim(),
16651
16684
  authorName: authorName.trim() || null,
16652
16685
  committedAt: committedAt.trim() || null
16653
16686
  };
@@ -16676,6 +16709,7 @@ var init_localGit = __esm({
16676
16709
  init_execa();
16677
16710
  init_preflight();
16678
16711
  init_defaultBranch();
16712
+ init_prRefs();
16679
16713
  GIT_TIMEOUT_MS = 10 * 60 * 1e3;
16680
16714
  }
16681
16715
  });
@@ -17117,6 +17151,7 @@ var init_localPoller = __esm({
17117
17151
  // src/daemon/run.ts
17118
17152
  var run_exports = {};
17119
17153
  __export(run_exports, {
17154
+ JOB_TOKEN_PREFIX: () => JOB_TOKEN_PREFIX,
17120
17155
  MultipleAccountsError: () => MultipleAccountsError,
17121
17156
  __testDispatch: () => __testDispatch,
17122
17157
  affectedFieldForComplete: () => affectedFieldForComplete,
@@ -17128,6 +17163,7 @@ __export(run_exports, {
17128
17163
  claudeFailureDetail: () => claudeFailureDetail,
17129
17164
  computeDiff: () => computeDiff,
17130
17165
  parseClaudeResult: () => parseClaudeResult,
17166
+ parseJobTokenResponse: () => parseJobTokenResponse,
17131
17167
  probeMcpAuth: () => probeMcpAuth,
17132
17168
  readBranchMarker: () => readBranchMarker,
17133
17169
  routeShutdownInterrupt: () => routeShutdownInterrupt,
@@ -17220,6 +17256,56 @@ async function getRepoCredentials(ctx, jobId, deviceId) {
17220
17256
  expires_at: typeof body.expires_at === "string" ? body.expires_at : null
17221
17257
  };
17222
17258
  }
17259
+ function parseJobTokenResponse(status, ok, body) {
17260
+ if (!ok) {
17261
+ throw new Error(`MCP job token unavailable: ${responseError(body, `job-token failed (${status})`)}`);
17262
+ }
17263
+ if (typeof body.token !== "string" || !body.token.startsWith(JOB_TOKEN_PREFIX) || body.token.length <= JOB_TOKEN_PREFIX.length) {
17264
+ throw new Error("MCP job token response did not carry an amj_ token");
17265
+ }
17266
+ const str2 = (v) => typeof v === "string" ? v : null;
17267
+ return {
17268
+ token: body.token,
17269
+ expires_at: str2(body.expires_at),
17270
+ project_id: str2(body.project_id),
17271
+ archme_branch: str2(body.archme_branch),
17272
+ scope: str2(body.scope)
17273
+ };
17274
+ }
17275
+ async function mintJobToken(ctx, jobId, deviceId) {
17276
+ const response = await fetchWithAuthRetry(
17277
+ "job-token",
17278
+ () => {
17279
+ const accessToken = ctx.auth.getAccessToken();
17280
+ if (!accessToken) throw new Error("daemon is not signed in with a registered device");
17281
+ return fetch(`${ctx.supabaseUrl.replace(/\/$/, "")}/functions/v1/agents-device/job-token`, {
17282
+ method: "POST",
17283
+ headers: {
17284
+ "Content-Type": "application/json",
17285
+ Authorization: `Bearer ${accessToken}`
17286
+ },
17287
+ body: JSON.stringify({
17288
+ device_id: deviceId,
17289
+ job_id: jobId
17290
+ })
17291
+ });
17292
+ },
17293
+ ctx.reLogin
17294
+ );
17295
+ const body = await parseJsonResponse(response);
17296
+ return parseJobTokenResponse(response.status, response.ok, body);
17297
+ }
17298
+ async function writeAgentMcpConfig(ctx, job, deviceId, projectSlug, source) {
17299
+ const { token } = await mintJobToken(ctx, job.id, deviceId);
17300
+ const { configPath, cleanup } = await writeMcpConfig({
17301
+ baseUrl: ctx.mcpBaseUrl,
17302
+ projectSlug,
17303
+ branch: job.archme_branch,
17304
+ bearer: token,
17305
+ source
17306
+ });
17307
+ return { configPath, cleanup, bearer: token };
17308
+ }
17223
17309
  async function buildGitEnv(credentials) {
17224
17310
  const dir = await mkdtemp3(join19(tmpdir3(), "archme-git-askpass-"));
17225
17311
  const askpassPath = join19(dir, "askpass.sh");
@@ -17771,7 +17857,7 @@ async function probeMcpAuth(mcpUrl, bearer, opts = {}) {
17771
17857
  body
17772
17858
  });
17773
17859
  lastStatus = res.status;
17774
- if (res.status !== 401) {
17860
+ if (res.status !== 401 && res.status !== 403) {
17775
17861
  return {
17776
17862
  ok: true,
17777
17863
  attempts: i2 + 1,
@@ -17779,13 +17865,13 @@ async function probeMcpAuth(mcpUrl, bearer, opts = {}) {
17779
17865
  detail: `MCP accepted the daemon token (status ${res.status})`
17780
17866
  };
17781
17867
  }
17782
- lastDetail = "MCP returned 401 \u2014 token rejected by JWKS verification";
17868
+ lastDetail = res.status === 403 ? "MCP returned 403 \u2014 job token rejected (job not running under this claim, or URL outside its binding)" : "MCP returned 401 \u2014 token rejected by JWKS verification";
17783
17869
  } catch (err2) {
17784
17870
  lastStatus = null;
17785
17871
  lastDetail = `network error reaching MCP: ${err2 instanceof Error ? err2.message : String(err2)}`;
17786
17872
  }
17787
17873
  }
17788
- if (lastStatus === 401) {
17874
+ if (lastStatus === 401 || lastStatus === 403) {
17789
17875
  return { ok: false, attempts, lastStatus, detail: lastDetail };
17790
17876
  }
17791
17877
  return {
@@ -17860,19 +17946,12 @@ function parseClaudeResult(stdout) {
17860
17946
  return { summary: raw, modelServed: null, usage: null, costUsd: null, isError: false, parsed: false };
17861
17947
  }
17862
17948
  }
17863
- async function setupDigestMcp(ctx, job) {
17949
+ async function setupDigestMcp(ctx, job, deviceId) {
17864
17950
  let cleanup;
17865
17951
  try {
17952
+ if (!deviceId) throw new Error("daemon is not signed in with a registered device");
17866
17953
  const projectSlug = await lookupProjectSlug(ctx.supabase, job.project_id);
17867
- const mcpBearer = await ctx.auth.getMcpAccessToken();
17868
- const { configPath, cleanup: cleanupFn } = await writeMcpConfig({
17869
- baseUrl: ctx.mcpBaseUrl,
17870
- projectSlug,
17871
- branch: job.archme_branch,
17872
- bearer: mcpBearer
17873
- // No `source`: digest reads must not produce source-tagged commits — they
17874
- // don't write at all, but leaving it off keeps the URL read-shaped.
17875
- });
17954
+ const { configPath, cleanup: cleanupFn, bearer: mcpBearer } = await writeAgentMcpConfig(ctx, job, deviceId, projectSlug);
17876
17955
  cleanup = cleanupFn;
17877
17956
  const probe = await probeMcpAuth(
17878
17957
  `${ctx.mcpBaseUrl}?project=${encodeURIComponent(projectSlug)}&branch=${encodeURIComponent(job.archme_branch)}`,
@@ -17895,7 +17974,7 @@ async function setupDigestMcp(ctx, job) {
17895
17974
  async function runJob(ctx, job, claimDeviceId) {
17896
17975
  if (job.job_kind === "digest") {
17897
17976
  const digestDevice = claimDeviceId ?? ctx.deviceForOrg(job.org_id) ?? ctx.auth.getDeviceId() ?? void 0;
17898
- const { mcp, cleanup } = await setupDigestMcp(ctx, job);
17977
+ const { mcp, cleanup } = await setupDigestMcp(ctx, job, digestDevice);
17899
17978
  try {
17900
17979
  await runDigestJob(ctx, job, digestDevice, inFlight, mcp);
17901
17980
  } finally {
@@ -17927,6 +18006,27 @@ async function runJob(ctx, job, claimDeviceId) {
17927
18006
  }, HEARTBEAT_INTERVAL_MS);
17928
18007
  try {
17929
18008
  const projectSlug = await lookupProjectSlug(ctx.supabase, job.project_id);
18009
+ const { configPath, cleanup: cleanupFn, bearer: mcpBearer } = await writeAgentMcpConfig(
18010
+ ctx,
18011
+ job,
18012
+ deviceId,
18013
+ projectSlug,
18014
+ {
18015
+ repo: `${job.repo_owner}/${job.repo_name}`,
18016
+ before_sha: job.before_sha ?? null,
18017
+ after_sha: job.after_sha,
18018
+ job_id: job.id,
18019
+ // Effective run mode — always diff or pr_update now (reconcile is gone).
18020
+ mode: job.source_mode === "pr_update" ? "pr_update" : "diff",
18021
+ commit_message: job.commit_message ?? null,
18022
+ committed_at: job.head_commit_timestamp ?? null,
18023
+ author_name: job.author_name ?? null,
18024
+ author_login: job.author_login ?? null
18025
+ }
18026
+ );
18027
+ activeCleanups.add(cleanupFn);
18028
+ cleanupFns.push(cleanupFn);
18029
+ markPhase("job_token");
17930
18030
  let clone;
17931
18031
  try {
17932
18032
  clone = await ensureCloneAndFetch(ctx, job, deviceId);
@@ -18007,27 +18107,6 @@ async function runJob(ctx, job, claimDeviceId) {
18007
18107
  throw new Error(`failed to materialize large diff to disk (${bytes} bytes): ${e.message}`);
18008
18108
  }
18009
18109
  }
18010
- const mcpBearer = await ctx.auth.getMcpAccessToken();
18011
- const { configPath, cleanup: cleanupFn } = await writeMcpConfig({
18012
- baseUrl: ctx.mcpBaseUrl,
18013
- projectSlug,
18014
- branch: job.archme_branch,
18015
- bearer: mcpBearer,
18016
- source: {
18017
- repo: `${job.repo_owner}/${job.repo_name}`,
18018
- before_sha: job.before_sha ?? null,
18019
- after_sha: job.after_sha,
18020
- job_id: job.id,
18021
- // Effective run mode — always diff or pr_update now (reconcile is gone).
18022
- mode: effectiveMode,
18023
- commit_message: job.commit_message ?? null,
18024
- committed_at: job.head_commit_timestamp ?? null,
18025
- author_name: job.author_name ?? null,
18026
- author_login: job.author_login ?? null
18027
- }
18028
- });
18029
- activeCleanups.add(cleanupFn);
18030
- cleanupFns.push(cleanupFn);
18031
18110
  let affectedSeed = "";
18032
18111
  let affectedForComplete = null;
18033
18112
  if (diff) {
@@ -18640,7 +18719,7 @@ ${signal} received, shutting down`);
18640
18719
  process.on("SIGINT", () => shutdown("SIGINT"));
18641
18720
  process.on("SIGTERM", () => shutdown("SIGTERM"));
18642
18721
  }
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;
18722
+ 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;
18644
18723
  var init_run = __esm({
18645
18724
  "src/daemon/run.ts"() {
18646
18725
  "use strict";
@@ -18702,6 +18781,7 @@ var init_run = __esm({
18702
18781
  ctxByJob = /* @__PURE__ */ new Map();
18703
18782
  deviceByJob = /* @__PURE__ */ new Map();
18704
18783
  DEFAULT_CLONE_BASE_DIR = `${homedir6()}/.archme-job-runner/clones`;
18784
+ JOB_TOKEN_PREFIX = "amj_";
18705
18785
  INLINE_PATCH_BYTE_CAP = 2e5;
18706
18786
  GIT_SHA_RE = /^[0-9a-f]{40}$/;
18707
18787
  CURATOR_PREAMBLE = `## Your role
@@ -18830,6 +18910,11 @@ ${ANCHOR_VERIFICATION}`;
18830
18910
  return deviceByJob.get(jobId);
18831
18911
  },
18832
18912
  runJob,
18913
+ // R7: the job-bound `amj_` MCP token mint and the digest's MCP setup, so a
18914
+ // test can assert the agent's mcp.json carries the job token and never the
18915
+ // daemon's own session.
18916
+ mintJobToken,
18917
+ setupDigestMcp,
18833
18918
  // Exposes the clone/fetch entrypoint so a test can assert that the repo-token
18834
18919
  // mint (getRepoCredentials -> agents-device/repo-token, gated on
18835
18920
  // claimed_by_device_id) uses the CLAIM-time device rather than recomputing via
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archme/cli",
3
- "version": "0.8.1",
3
+ "version": "0.9.0",
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",