@remits/remits-cli 0.1.133 → 0.1.136

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -6,16 +6,16 @@
6
6
  - L22 Runtime Bootstrap And Shared State
7
7
  - L119 Sessions, Account Resolution, And Production Guards
8
8
  - L603 Local State, Workspaces, And Verification Evidence
9
- - L2255 Account Repos, Guide Sync, And Platform Repo
10
- - L2949 Component Discovery And HTTP Logging
11
- - L3525 Skill Delivery And TOC Resolution
12
- - L3835 Auth And Component Staging
13
- - L4449 Component Summaries, Status, And Sync Gates
14
- - L6518 Branches, Promotion, Commit, And Test Runs
15
- - L8183 Tokens, Tools, Verification, And Config
16
- - L9405 Service Dashboard And WebSocket Listener
17
- - L11521 Agent And Ticket Workflows
18
- - L14815 Help, Auto Update, And Command Dispatch
9
+ - L2624 Account Repos, Guide Sync, And Platform Repo
10
+ - L3432 Component Discovery And HTTP Logging
11
+ - L4073 Skill Delivery And TOC Resolution
12
+ - L4383 Auth And Component Staging
13
+ - L4998 Component Summaries, Status, And Sync Gates
14
+ - L7067 Branches, Promotion, Commit, And Test Runs
15
+ - L8750 Tokens, Tools, Verification, And Config
16
+ - L10177 Service Dashboard And WebSocket Listener
17
+ - L12298 Agent And Ticket Workflows
18
+ - L15888 Help, Auto Update, And Command Dispatch
19
19
  */
20
20
 
21
21
  /*
@@ -621,10 +621,12 @@ function localStatePaths(cwd) {
621
621
  actorDir,
622
622
  sharedDir: path.join(base, 'shared'),
623
623
  toolsDir: path.join(base, 'shared', 'tools'),
624
+ workstreamsDir: path.join(base, 'shared', 'workstreams'),
624
625
  sessionsDir: path.join(actorDir, 'sessions'),
625
626
  toolResponsesDir: path.join(actorDir, 'tool-responses'),
626
627
  verificationDir: path.join(actorDir, 'verification'),
627
628
  diagnosticsDir: path.join(actorDir, 'diagnostics'),
629
+ evidenceFile: path.join(actorDir, 'evidence.jsonl'),
628
630
  activeVerificationFile: path.join(actorDir, 'verification', 'active'),
629
631
  activeVerificationContextsFile: path.join(actorDir, 'verification', 'active-contexts.json'),
630
632
  currentSessionFile: path.join(actorDir, 'current-session.txt'),
@@ -924,6 +926,112 @@ function localStateSummary(cwd, flags = {}) {
924
926
  };
925
927
  }
926
928
 
929
+ function normalizeWorkstreamId(value) {
930
+ if (value === undefined || value === null) return null;
931
+ const trimmed = String(value).trim();
932
+ if (!trimmed) return null;
933
+ let normalized = trimmed
934
+ .toLowerCase()
935
+ .replace(/[^a-z0-9._-]/g, '-')
936
+ .replace(/-+/g, '-')
937
+ .replace(/^[-.]+/, '')
938
+ .replace(/[-.]+$/, '');
939
+ return normalized ? normalized.slice(0, 96).replace(/[-.]+$/, '') : null;
940
+ }
941
+
942
+ // The LOCAL proof-campaign id: the thing that groups several actor directories working one goal. It is not
943
+ // the same `workstream` a support ticket carries — that one is an account's own routing label, matched by
944
+ // `agent serve --serves`. They share a flag name and nothing else; keep the distinction in any text that
945
+ // mentions either.
946
+ function resolveWorkstreamId(cwd, flags = {}, context = {}) {
947
+ return normalizeWorkstreamId(flags.workstream || flags['workstream-id'] || process.env.REMITS_WORKSTREAM_ID ||
948
+ context.workstream || context.workspace || resolveWorkspace(cwd, flags));
949
+ }
950
+
951
+ const WORKSTREAM_ROLE_KEYS = ['repoAccountId', 'accountId', 'executionAccountId', 'targetAccountId',
952
+ 'branchName', 'componentBranch', 'workspace', 'dataMode'];
953
+
954
+ function workstreamRoleSignature(world) {
955
+ if (!world || typeof world !== 'object') return null;
956
+ const signature = {};
957
+ WORKSTREAM_ROLE_KEYS.forEach((key) => {
958
+ if (world[key] !== undefined && world[key] !== null) signature[key] = world[key];
959
+ });
960
+ return Object.keys(signature).length ? signature : null;
961
+ }
962
+
963
+ function workstreamWorldParts(world) {
964
+ const roles = world || {};
965
+ return [
966
+ roles.repoAccountId ? 'repo ' + roles.repoAccountId : null,
967
+ roles.accountId ? 'account ' + roles.accountId : null,
968
+ roles.executionAccountId ? 'exec ' + roles.executionAccountId : null,
969
+ roles.targetAccountId ? 'target ' + roles.targetAccountId : null,
970
+ roles.branchName ? 'branch ' + roles.branchName : null,
971
+ roles.componentBranch ? 'componentBranch ' + roles.componentBranch : null,
972
+ roles.workspace ? 'ws:' + roles.workspace : null,
973
+ roles.dataMode ? 'data ' + roles.dataMode : null
974
+ ].filter(Boolean);
975
+ }
976
+
977
+ function dedupeWorkstreamWorlds(worlds) {
978
+ const seen = new Set();
979
+ const out = [];
980
+ (worlds || []).forEach((world) => {
981
+ if (!world || !Object.keys(world).length) return;
982
+ const key = JSON.stringify(WORKSTREAM_ROLE_KEYS.map((k) => world[k] === undefined ? null : world[k]));
983
+ if (seen.has(key)) return;
984
+ seen.add(key);
985
+ out.push(world);
986
+ });
987
+ return out.slice(-12);
988
+ }
989
+
990
+ function workstreamFile(cwd, workstreamId) {
991
+ const paths = ensureLocalState(cwd);
992
+ ensureDir(paths.workstreamsDir);
993
+ return path.join(paths.workstreamsDir, normalizeWorkstreamId(workstreamId) + '.json');
994
+ }
995
+
996
+ function recordWorkstreamArtifact(cwd, flags = {}, artifact = {}) {
997
+ const workstreamId = resolveWorkstreamId(cwd, flags, artifact);
998
+ if (!workstreamId) return null;
999
+ const file = workstreamFile(cwd, workstreamId);
1000
+ let existing = {};
1001
+ try {
1002
+ if (fs.existsSync(file)) existing = JSON.parse(fs.readFileSync(file, 'utf8')) || {};
1003
+ } catch (_) {
1004
+ existing = {};
1005
+ }
1006
+ const now = new Date().toISOString();
1007
+ const actor = localStatePaths(cwd).actor.id;
1008
+ const item = Object.assign({
1009
+ actor,
1010
+ recordedAt: now
1011
+ }, artifact || {});
1012
+ const artifacts = Array.isArray(existing.artifacts) ? existing.artifacts : [];
1013
+ const dedupeKey = [item.type, item.callId, item.envelopeId, item.packetId, item.path].filter(Boolean).join('|');
1014
+ const filtered = dedupeKey
1015
+ ? artifacts.filter((row) => [row.type, row.callId, row.envelopeId, row.packetId, row.path].filter(Boolean).join('|') !== dedupeKey)
1016
+ : artifacts;
1017
+ // The LAST world, never a merge of all of them. Merging produced a "World:" line that no single run ever
1018
+ // executed in — a leftover targetAccountId from one command printed next to the executionAccountId of
1019
+ // another. A campaign that legitimately spans worlds is reported as several worlds, not as one blend.
1020
+ const roles = workstreamRoleSignature(artifact.world) || existing.roles || {};
1021
+ const priorWorlds = Array.isArray(existing.worlds) ? existing.worlds : (existing.roles ? [existing.roles] : []);
1022
+ const worlds = dedupeWorkstreamWorlds(priorWorlds.concat([roles]));
1023
+ const next = Object.assign({}, existing, {
1024
+ workstreamId,
1025
+ updatedAt: now,
1026
+ actors: Array.from(new Set([].concat(existing.actors || [], actor))).sort(),
1027
+ roles,
1028
+ worlds,
1029
+ artifacts: filtered.concat([item]).slice(-200)
1030
+ });
1031
+ atomicWriteFile(file, JSON.stringify(next, null, 2));
1032
+ return file;
1033
+ }
1034
+
927
1035
  // ---------------------------------------------------------------------------------------------------
928
1036
  // Staging workspace
929
1037
  // ---------------------------------------------------------------------------------------------------
@@ -1463,6 +1571,40 @@ function writeActiveVerificationContexts(cwd, contexts) {
1463
1571
  atomicWriteFile(paths.activeVerificationContextsFile, JSON.stringify(contexts || {}, null, 2));
1464
1572
  }
1465
1573
 
1574
+ function describeVerificationWorld(context = {}) {
1575
+ return ['account ' + (context.accountId != null ? context.accountId : '?'),
1576
+ 'branch ' + (context.branchName || context.branch || '?'),
1577
+ 'workspace ' + (normalizeWorkspace(context.workspace) || 'shared'),
1578
+ context.dataMode ? 'data ' + context.dataMode : null].filter(Boolean).join(', ');
1579
+ }
1580
+
1581
+ /**
1582
+ * Active envelope pointers this actor holds for a DIFFERENT checkout world than the one running now. Used
1583
+ * only to explain a silence: an agent that started an envelope on `main` and then worked `--branch forked`
1584
+ * otherwise gets no envelope, no detach message, and no reason.
1585
+ */
1586
+ function otherWorldVerificationEnvelopes(cwd, context) {
1587
+ try {
1588
+ const currentKey = verificationContextKey(context);
1589
+ const contexts = readActiveVerificationContexts(cwd);
1590
+ return Object.keys(contexts)
1591
+ .filter((key) => key !== currentKey && !legacyVerificationContextKeys(currentKey).includes(key))
1592
+ .map((key) => {
1593
+ const entry = typeof contexts[key] === 'string' ? { envelopeId: contexts[key] } : contexts[key];
1594
+ if (!entry || !entry.envelopeId) return null;
1595
+ return { envelopeId: String(entry.envelopeId), updatedAt: entry.updatedAt, context: entry.context || {} };
1596
+ })
1597
+ .filter(Boolean)
1598
+ // Only pointers for THIS host and account: an envelope for another account is not a missing binding,
1599
+ // it is different work.
1600
+ .filter((entry) => String(entry.context.accountId) === String(context.accountId) &&
1601
+ normalizeBaseUrl(entry.context.baseUrl || '') === normalizeBaseUrl(context.baseUrl || ''))
1602
+ .sort((left, right) => String(right.updatedAt || '').localeCompare(String(left.updatedAt || '')));
1603
+ } catch (_) {
1604
+ return [];
1605
+ }
1606
+ }
1607
+
1466
1608
  function activeVerificationContext(cwd, flags = {}, session = null, accountId = null) {
1467
1609
  return {
1468
1610
  baseUrl: normalizeBaseUrl(flags['base-url'] || (session && session.baseUrl) || DEFAULT_BASE_URL),
@@ -1768,7 +1910,25 @@ async function verificationPreflight(api, cwd, session, accountId, flags = {}, p
1768
1910
  const label = options.command || 'this command';
1769
1911
  const context = activeVerificationContext(cwd, flags, session, accountId);
1770
1912
  const envelopeId = verificationEnvelopeIdForCommand(cwd, flags, context);
1771
- if (!envelopeId) return { envelopeId: null, verdict: 'none', evidenceFlags: flags };
1913
+ if (!envelopeId) {
1914
+ // An envelope is bound per CHECKOUT WORLD, so `verify start` on one branch/workspace and the work on
1915
+ // another leaves the agent with an active envelope it cannot see and a command that says nothing at all:
1916
+ // it did the work, attached no evidence, and reported neither. Silence is only correct when there is no
1917
+ // envelope to speak of.
1918
+ if (!options.quiet) {
1919
+ const orphan = otherWorldVerificationEnvelopes(cwd, context);
1920
+ if (orphan.length) {
1921
+ console.error('No verification envelope is active for this world (' + describeVerificationWorld(context) + '), ' +
1922
+ 'so ' + label + ' attaches no evidence. You have ' + orphan.length + ' active envelope(s) bound to other worlds:');
1923
+ orphan.slice(0, 3).forEach((entry) => {
1924
+ console.error(' - ' + entry.envelopeId + ' ' + describeVerificationWorld(entry.context));
1925
+ });
1926
+ console.error(' Start one here with `remits-cli verify start --summary "..."`, adopt one with ' +
1927
+ '`remits-cli verify use <id>`, or re-run in the world that envelope was started in.');
1928
+ }
1929
+ }
1930
+ return { envelopeId: null, verdict: 'none', evidenceFlags: flags };
1931
+ }
1772
1932
  const pinned = Object.assign({}, flags, { 'verify-envelope': envelopeId });
1773
1933
  let result;
1774
1934
  try {
@@ -1840,9 +2000,145 @@ async function printVerificationRunProbe(api, cwd, session, accountId, envelopeI
1840
2000
  return result;
1841
2001
  }
1842
2002
 
2003
+ /**
2004
+ * The always-on evidence trail.
2005
+ *
2006
+ * <p>One world-stamped line per evidence-producing command, written whether or not any verification envelope
2007
+ * exists. This is the answer to "what have I actually run, and in which world?" - the question agents were
2008
+ * opening envelopes to answer, and an envelope is a far heavier instrument than that question needs. It is
2009
+ * per-actor, so two agents in one checkout never read each other's trail, and it is append-only with a bounded
2010
+ * tail so it cannot grow without limit.</p>
2011
+ *
2012
+ * <p>Recording is deliberately decision-free: there is no flag to remember, no state to start, and nothing an
2013
+ * agent can fail to satisfy.</p>
2014
+ */
2015
+ const EVIDENCE_TRAIL_MAX_LINES = 2000;
2016
+
2017
+ function recordEvidenceEntry(cwd, packet, context = {}) {
2018
+ try {
2019
+ const paths = ensureLocalState(cwd);
2020
+ const world = (packet && packet.world && typeof packet.world === 'object') ? packet.world : {};
2021
+ const hasSuccess = packet && Object.prototype.hasOwnProperty.call(packet, 'success');
2022
+ const entry = {
2023
+ ts: new Date().toISOString(),
2024
+ type: packet && packet.type || 'unknown',
2025
+ claim: packet && packet.claim || null,
2026
+ success: hasSuccess ? packet.success : true,
2027
+ pending: packet && packet.pending === true || (hasSuccess && packet.success === null),
2028
+ world: {
2029
+ repoAccountId: world.repoAccountId != null ? world.repoAccountId : context.accountId || null,
2030
+ executionAccountId: world.executionAccountId != null ? world.executionAccountId : (world.accountId != null ? world.accountId : null),
2031
+ targetAccountId: world.targetAccountId != null ? world.targetAccountId : null,
2032
+ dataMode: world.dataMode || context.dataMode || null,
2033
+ branchName: world.branchName || world.gitBranch || context.branchName || null,
2034
+ componentBranch: world.componentBranch || world.variantBranch || null,
2035
+ workspace: world.workspace !== undefined ? world.workspace : (context.workspace !== undefined ? context.workspace : null),
2036
+ sourceLayer: world.sourceLayer || null,
2037
+ laneContentHash: world.laneContentHash || world.stagedOverlayHash || null,
2038
+ host: world.host || context.baseUrl || null
2039
+ },
2040
+ ref: {
2041
+ packetId: packet && packet.packetId || null,
2042
+ taskId: (packet && packet.test && packet.test.taskId) || (packet && packet.status && packet.status.taskId) || null,
2043
+ callId: (packet && packet.tool && packet.tool.callId) || null,
2044
+ responseFile: (packet && packet.tool && packet.tool.responseFile) || null
2045
+ },
2046
+ // Present when an envelope happened to be active. Never required, and never something to chase.
2047
+ envelopeId: context.envelopeId || null,
2048
+ claims: Array.isArray(packet && packet.claims) ? packet.claims : undefined
2049
+ };
2050
+ fs.appendFileSync(paths.evidenceFile, JSON.stringify(entry) + '\n');
2051
+ trimEvidenceTrail(paths.evidenceFile);
2052
+ return entry;
2053
+ } catch (_) {
2054
+ // A trail write must never fail the command it is describing.
2055
+ return null;
2056
+ }
2057
+ }
2058
+
2059
+ function evidenceOutcome(row) {
2060
+ if (row && (row.pending === true || row.success === null)) return 'pending';
2061
+ return row && row.success === false ? 'failed' : 'ok';
2062
+ }
2063
+
2064
+ function evidenceOutcomeLabel(row) {
2065
+ const outcome = evidenceOutcome(row);
2066
+ if (outcome === 'failed') return 'FAILED ';
2067
+ if (outcome === 'pending') return 'PENDING ';
2068
+ return '';
2069
+ }
2070
+
2071
+ function trimEvidenceTrail(file) {
2072
+ try {
2073
+ const lines = fs.readFileSync(file, 'utf8').split('\n').filter(Boolean);
2074
+ if (lines.length <= EVIDENCE_TRAIL_MAX_LINES) return;
2075
+ atomicWriteFile(file, lines.slice(lines.length - EVIDENCE_TRAIL_MAX_LINES).join('\n') + '\n');
2076
+ } catch (_) { /* best effort */ }
2077
+ }
2078
+
2079
+ function readEvidenceTrail(cwd, limit = 50) {
2080
+ try {
2081
+ const paths = localStatePaths(cwd);
2082
+ if (!fs.existsSync(paths.evidenceFile)) return [];
2083
+ const lines = fs.readFileSync(paths.evidenceFile, 'utf8').split('\n').filter(Boolean);
2084
+ return lines.slice(Math.max(0, lines.length - limit)).map((line) => {
2085
+ try { return JSON.parse(line); } catch (_) { return null; }
2086
+ }).filter(Boolean);
2087
+ } catch (_) {
2088
+ return [];
2089
+ }
2090
+ }
2091
+
2092
+ /**
2093
+ * WHERE a command ran: accounts, lane, branch, workspace. Deliberately NOT `sourceLayer` or
2094
+ * `laneContentHash` - those legitimately differ between two commands in the same world (a stage is `staged`,
2095
+ * the test that follows resolves `db`), so keying the grouping on them split one world into several and made
2096
+ * two entries from the same context look like two different places. They belong on the entry.
2097
+ */
2098
+ function describeEvidenceWorld(world) {
2099
+ const w = world || {};
2100
+ const accounts = w.executionAccountId && String(w.executionAccountId) !== String(w.repoAccountId)
2101
+ ? 'account ' + w.repoAccountId + ' -> runs as ' + w.executionAccountId
2102
+ : 'account ' + (w.repoAccountId == null ? '?' : w.repoAccountId);
2103
+ return [
2104
+ accounts,
2105
+ w.targetAccountId && String(w.targetAccountId) !== String(w.executionAccountId) ? 'targets ' + w.targetAccountId : null,
2106
+ w.dataMode ? w.dataMode + ' lane' : null,
2107
+ w.branchName ? 'branch ' + w.branchName : null,
2108
+ w.componentBranch ? 'components ' + w.componentBranch : null,
2109
+ 'ws:' + (w.workspace || 'shared')
2110
+ ].filter(Boolean).join(' · ');
2111
+ }
2112
+
2113
+ function claimIdsFromFlags(flags) {
2114
+ return []
2115
+ .concat((flags && flags.claim) || [])
2116
+ .concat((flags && flags.claims) || [])
2117
+ .filter(Boolean)
2118
+ .flatMap((value) => String(value).split(','))
2119
+ .map((value) => value.trim())
2120
+ .filter(Boolean);
2121
+ }
2122
+
2123
+ function stampPacketClaims(packet, flags) {
2124
+ const claims = claimIdsFromFlags(flags);
2125
+ if (!claims.length) return packet;
2126
+ if (!Array.isArray(packet.evidenceCategories)) packet.evidenceCategories = [];
2127
+ claims.forEach((claim) => {
2128
+ if (!packet.evidenceCategories.includes(claim)) packet.evidenceCategories.push(claim);
2129
+ });
2130
+ packet.claims = claims;
2131
+ return packet;
2132
+ }
2133
+
1843
2134
  async function appendVerificationPacket(api, cwd, session, accountId, flags, packet, options = {}) {
1844
2135
  const context = activeVerificationContext(cwd, flags, session, accountId);
1845
2136
  const envelopeId = verificationEnvelopeIdForCommand(cwd, flags, context);
2137
+ // The trail is written FIRST and unconditionally. Evidence recording must not depend on an agent having
2138
+ // remembered to start anything: an envelope is an optional verdict on top of this, never the thing that
2139
+ // makes a command's world durable.
2140
+ stampPacketClaims(packet, flags);
2141
+ recordEvidenceEntry(cwd, packet, Object.assign({}, context, { accountId, envelopeId }));
1846
2142
  if (!envelopeId) return null;
1847
2143
  const finalPacket = Object.assign({
1848
2144
  packetId: crypto.randomUUID(),
@@ -1851,7 +2147,23 @@ async function appendVerificationPacket(api, cwd, session, accountId, flags, pac
1851
2147
  }, packet || {});
1852
2148
  finalPacket.envelopeId = envelopeId;
1853
2149
  stampPacketRepoAccount(finalPacket, accountId);
2150
+ // The workstream id travels WITH the evidence. Without it the local index and the platform's
2151
+ // `activity inspect --scope related` share no identifier, so two agents on one campaign — the case this
2152
+ // whole surface exists for — could never be shown the same set of work.
2153
+ const packetWorkstreamId = resolveWorkstreamId(cwd, flags, finalPacket.world || {});
2154
+ if (packetWorkstreamId) {
2155
+ if (!finalPacket.world || typeof finalPacket.world !== 'object') finalPacket.world = {};
2156
+ if (!finalPacket.world.workstreamId) finalPacket.world.workstreamId = packetWorkstreamId;
2157
+ }
1854
2158
  writeLocalVerificationPacket(cwd, envelopeId, finalPacket);
2159
+ recordWorkstreamArtifact(cwd, flags, {
2160
+ type: 'verification_packet',
2161
+ envelopeId,
2162
+ packetId: finalPacket.packetId,
2163
+ packetType: finalPacket.type,
2164
+ world: finalPacket.world || {},
2165
+ workspace: finalPacket.world && finalPacket.world.workspace
2166
+ });
1855
2167
  try {
1856
2168
  const response = await postVerificationCommand(api, cwd, session, accountId, 'packet', {
1857
2169
  envelopeId,
@@ -1892,7 +2204,7 @@ function printVerificationSummaryLine(env) {
1892
2204
  const health = env.health ? ' health=' + env.health : '';
1893
2205
  const failures = env.currentFailureCount ? ' currentFailures=' + env.currentFailureCount : (env.failedCount ? ' failed=' + env.failedCount : '');
1894
2206
  const required = ' required=' + (env.satisfiedCount || 0) + '/' + (env.requiredCount || 0);
1895
- const noManifest = env.noRequiredEvidence ? ' no-manifest' : '';
2207
+ const noManifest = env.noRequiredEvidence ? ' evidence-log' : '';
1896
2208
  const stale = env.staleCount ? ' stale=' + env.staleCount : '';
1897
2209
  const changed = env.requirementsChangedAfterEvidence ? ' requirements-changed' : '';
1898
2210
  const lane = [env.branchName || 'unknown-branch', env.workspace ? 'ws:' + env.workspace : 'shared', env.dataMode || null, env.sourceLayer || null]
@@ -1909,7 +2221,7 @@ function printVerificationEnvelope(envelope, fallbackEnvelopeId) {
1909
2221
  const evaluation = envelope.evaluation || {};
1910
2222
  if (evaluation.health) console.log('Health:', evaluation.health);
1911
2223
  if (evaluation.noRequiredEvidence) {
1912
- console.log('Required evidence: none declared - evidence packets do not prove a complete acceptance contract.');
2224
+ console.log('Required evidence: none declared - evidence log only; no verdict is outstanding.');
1913
2225
  } else {
1914
2226
  console.log('Required evidence:', (evaluation.satisfiedCount || 0) + '/' + (evaluation.requiredCount || 0));
1915
2227
  }
@@ -1934,13 +2246,14 @@ function printVerificationEnvelope(envelope, fallbackEnvelopeId) {
1934
2246
  }
1935
2247
  }
1936
2248
 
1937
- function slimVerificationPacket(packet) {
2249
+ function slimVerificationPacket(packet, options = {}) {
1938
2250
  const p = packet || {};
1939
2251
  const world = p.world || {};
2252
+ const hasSuccess = Object.prototype.hasOwnProperty.call(p, 'success');
1940
2253
  return {
1941
2254
  packetId: p.packetId || p.id || null,
1942
2255
  type: p.type || 'unknown',
1943
- success: p.success !== false,
2256
+ success: hasSuccess ? p.success !== false : options.defaultSuccess !== false,
1944
2257
  stale: Boolean(p.stale),
1945
2258
  claim: p.claim || p.summary || null,
1946
2259
  taskId: p.taskId || (p.testRun && p.testRun.taskId) || null,
@@ -1967,7 +2280,7 @@ function compactVerificationEnvelope(envelope, fallbackEnvelopeId) {
1967
2280
  const nextActions = [];
1968
2281
 
1969
2282
  if (evaluation.noRequiredEvidence) {
1970
- nextActions.push('Attach a manifest or required evidence contract; packets are context, not proof of completion.');
2283
+ nextActions.push('No verdict was requested. Leave this as an evidence log, or add `verify claim <id> --text "..."` only if someone needs a checkable verdict.');
1971
2284
  }
1972
2285
  if (failures.length) {
1973
2286
  nextActions.push('Inspect and replace the current failing evidence packet(s).');
@@ -2002,7 +2315,7 @@ function compactVerificationEnvelope(envelope, fallbackEnvelopeId) {
2002
2315
  recent: packets.slice(-5).map(slimVerificationPacket)
2003
2316
  },
2004
2317
  latestByType,
2005
- currentFailures: failures.slice(0, 10).map(slimVerificationPacket),
2318
+ currentFailures: failures.slice(0, 10).map((packet) => slimVerificationPacket(packet, { defaultSuccess: false })),
2006
2319
  stale: stale.slice(0, 10).map(slimVerificationPacket),
2007
2320
  warnings: (env.warnings || []).slice(0, 10),
2008
2321
  nextActions
@@ -2051,14 +2364,27 @@ function printEnvelopeWarnings(envelope) {
2051
2364
  console.log('Envelope warnings:');
2052
2365
  warnings.forEach((warning) => {
2053
2366
  console.log('- ' + (warning.message || warning.code || JSON.stringify(warning)));
2367
+ if (warning.command) console.log(' ' + warning.command);
2054
2368
  if (Array.isArray(warning.envelopes) && warning.envelopes.length) {
2055
2369
  warning.envelopes.forEach((env) => {
2056
- console.log(' ' + (env.envelopeId || '(no id)') + (env.status ? ' ' + env.status : ''));
2370
+ // Say whether the duplicate is in THIS world. A restart in a genuinely different lane or branch is
2371
+ // sometimes exactly right; a second envelope for the same goal in the same world is the loop.
2372
+ const world = env.world && typeof env.world === 'object'
2373
+ ? Object.keys(env.world).map((key) => key + '=' + env.world[key]).join(' ')
2374
+ : '';
2375
+ console.log(' ' + (env.envelopeId || '(no id)') + (env.status ? ' ' + env.status : '') +
2376
+ (env.sameWorld === true ? ' [same world]' : (world ? ' [' + world + ']' : '')));
2057
2377
  });
2378
+ console.log(' Continue one of those with `remits-cli verify use <id>`, or supersede it once this one supersedes its goal.');
2058
2379
  }
2059
2380
  });
2060
2381
  }
2061
2382
 
2383
+ function manifestClaims(manifest) {
2384
+ const claims = manifest && manifest.claims;
2385
+ return Array.isArray(claims) ? claims.filter(Boolean) : [];
2386
+ }
2387
+
2062
2388
  function manifestRequiredEvidence(manifest = {}) {
2063
2389
  const required = [];
2064
2390
  if (Array.isArray(manifest.requiredEvidence)) required.push(...manifest.requiredEvidence);
@@ -2096,16 +2422,35 @@ async function readVerificationPacketsForDiagnostics(api, cwd, session, accountI
2096
2422
  }
2097
2423
 
2098
2424
  function buildCommandWorld(response, fallback = {}) {
2425
+ if (response && response.world && typeof response.world === 'object') {
2426
+ return Object.assign({}, response.world, {
2427
+ host: response.world.host || fallback.host,
2428
+ dataMode: response.world.dataMode || response.dataMode || fallback.dataMode,
2429
+ branchName: response.world.branchName || response.branchName || fallback.branchName,
2430
+ workspace: response.world.workspace !== undefined ? response.world.workspace : fallback.workspace
2431
+ });
2432
+ }
2099
2433
  const source = (response && response.staging) || {};
2100
2434
  const resolution = (response && response.resolution) || {};
2101
2435
  const result = (response && response.result) || {};
2102
2436
  const laneHash = laneContentHashFrom(response) || fallback.laneContentHash || fallback.stagedOverlayHash;
2103
- const executionAccountId = response && (response.executionAccountId || result.accountId || source.executionAccountId || response.accountId) ||
2437
+ const addressedAccountId = response && (response.repoAccountId || response.checkoutAccountId || response.scopeRootAccountId ||
2438
+ response.accountId || response.ownerAccountId) ||
2439
+ fallback.repoAccountId || fallback.checkoutAccountId || fallback.scopeRootAccountId || fallback.addressedAccountId || fallback.accountId;
2440
+ const executionAccountId = response && (response.executionAccountId || result.accountId || source.executionAccountId) ||
2104
2441
  resolution.executionAccountId ||
2105
- fallback.executionAccountId;
2442
+ fallback.executionAccountId ||
2443
+ // Legacy/fallback callers historically supplied only `accountId`, and for all non-split worlds that
2444
+ // value is both repo and execution. Never let a top-level response.accountId (usually addressed/repo) be
2445
+ // the execution account unless the caller intentionally passed it as the fallback.
2446
+ fallback.accountId;
2106
2447
  return {
2107
- accountId: response && (response.accountId || response.ownerAccountId) || fallback.accountId,
2448
+ accountId: executionAccountId,
2449
+ repoAccountId: response && response.repoAccountId || fallback.repoAccountId || addressedAccountId,
2450
+ scopeRootAccountId: response && response.scopeRootAccountId || fallback.scopeRootAccountId,
2451
+ checkoutAccountId: response && response.checkoutAccountId || fallback.checkoutAccountId || addressedAccountId,
2108
2452
  executionAccountId,
2453
+ targetAccountId: response && response.targetAccountId || fallback.targetAccountId,
2109
2454
  addressedAccountId: resolution.addressedAccountId || fallback.addressedAccountId,
2110
2455
  host: fallback.host,
2111
2456
  dataMode: response && response.dataMode || fallback.dataMode,
@@ -2122,10 +2467,34 @@ function buildCommandWorld(response, fallback = {}) {
2122
2467
  fallback.componentBranch,
2123
2468
  variantBranchSource: response && (response.variantBranchSource || (response.variantWorld && response.variantWorld.variantBranchSource)) || fallback.variantBranchSource,
2124
2469
  variantId: resolution.variantId || fallback.variantId,
2470
+ componentSource: response && response.componentSource || fallback.componentSource,
2471
+ componentSignature: response && response.componentSignature || fallback.componentSignature,
2125
2472
  sourceLayer: fallback.sourceLayer || (source.testComponentSource === 'staged' ? 'staged' : undefined)
2126
2473
  };
2127
2474
  }
2128
2475
 
2476
+ function buildToolStatusWorld(data, stored = {}, fallback = {}) {
2477
+ const response = data && typeof data === 'object' ? data : {};
2478
+ const previous = stored && typeof stored === 'object' ? stored : {};
2479
+ return buildCommandWorld(response, {
2480
+ repoAccountId: response.repoAccountId || previous.repoAccountId || fallback.repoAccountId || previous.checkoutAccountId || previous.accountId || fallback.accountId,
2481
+ scopeRootAccountId: response.scopeRootAccountId || previous.scopeRootAccountId || fallback.scopeRootAccountId,
2482
+ checkoutAccountId: response.checkoutAccountId || previous.checkoutAccountId || fallback.checkoutAccountId,
2483
+ accountId: response.executionAccountId || previous.executionAccountId || fallback.executionAccountId || fallback.accountId,
2484
+ executionAccountId: response.executionAccountId || previous.executionAccountId || fallback.executionAccountId,
2485
+ targetAccountId: response.targetAccountId || previous.targetAccountId || fallback.targetAccountId,
2486
+ dataMode: response.dataMode || previous.dataMode || fallback.dataMode,
2487
+ branchName: response.branchName || previous.branchName || fallback.branchName,
2488
+ workspace: response.workspace !== undefined ? response.workspace : (previous.workspace !== undefined ? previous.workspace : fallback.workspace),
2489
+ host: fallback.host,
2490
+ componentSource: response.componentSource || previous.componentSource || fallback.componentSource,
2491
+ componentSignature: response.componentSignature || previous.componentSignature || fallback.componentSignature,
2492
+ componentBranch: response.componentBranch || previous.componentBranch || fallback.componentBranch,
2493
+ variantBranch: response.variantBranch || previous.variantBranch || fallback.variantBranch,
2494
+ variantBranchSource: response.variantBranchSource || previous.variantBranchSource || fallback.variantBranchSource
2495
+ });
2496
+ }
2497
+
2129
2498
  // A variant sync writes overlays on exactly one component branch; say so in the packet world, where a manifest's
2130
2499
  // componentBranch is compared. Without it no sync_mutation could satisfy a manifest that declares one.
2131
2500
  function syncPacketWorld(response, world, branchName) {
@@ -2444,6 +2813,120 @@ async function downloadAndSyncGuides(cwd, ctx) {
2444
2813
  return writeGuidePayloadIntoRepo(cwd, payload);
2445
2814
  }
2446
2815
 
2816
+ function guideSyncSummaryLine(summary) {
2817
+ if (!summary) {
2818
+ return 'guide sync skipped';
2819
+ }
2820
+ const repoCount = Number(summary.repoCount || 0);
2821
+ const updatedRepos = Number(summary.updatedRepos || 0);
2822
+ const updatedFiles = Number(summary.updatedFiles || 0);
2823
+ const skipped = (summary.skipped || []).length;
2824
+ return updatedRepos + '/' + repoCount + ' repo(s) updated, ' + updatedFiles +
2825
+ ' file(s) changed' + (skipped ? ', ' + skipped + ' skipped' : '');
2826
+ }
2827
+
2828
+ function guideSyncSessions(requestedBaseUrl = null) {
2829
+ return listSessionsFromStore(readAllSessions()).filter((session) => {
2830
+ if (!session || !session.token) return false;
2831
+ if (!requestedBaseUrl) return true;
2832
+ return normalizeBaseUrl(session.baseUrl || DEFAULT_BASE_URL) === normalizeBaseUrl(requestedBaseUrl);
2833
+ });
2834
+ }
2835
+
2836
+ function guideSessionForRepo(entry, sessions) {
2837
+ const accountId = Number(entry && entry.accountId);
2838
+ return sessions.find((session) => Number(session.accountId) === accountId) || sessions[0] || null;
2839
+ }
2840
+
2841
+ // Refresh every indexed account checkout from the authenticated platform guide payload. This is the
2842
+ // operator-level repair path for "agents are reading stale front-stage docs": it does not wait for each
2843
+ // checkout to happen to run a component command.
2844
+ async function syncGuidesAcrossIndexedRepos(options = {}) {
2845
+ if (options.rescan !== false) {
2846
+ try { discoverAccountRepos(); } catch (_) { /* use the current index */ }
2847
+ }
2848
+
2849
+ const sessions = guideSyncSessions(options.baseUrl || null);
2850
+ const entries = listAccountRepoEntries(readAccountRepoIndex())
2851
+ .sort((a, b) => Number(a.accountId) - Number(b.accountId));
2852
+ const result = {
2853
+ repoCount: entries.length,
2854
+ updatedRepos: 0,
2855
+ unchangedRepos: 0,
2856
+ updatedFiles: 0,
2857
+ removedFiles: 0,
2858
+ skipped: [],
2859
+ repos: []
2860
+ };
2861
+
2862
+ if (!sessions.length) {
2863
+ result.skipped.push({
2864
+ reason: 'no_authenticated_session',
2865
+ message: 'Run remits-cli auth before syncing guides.'
2866
+ });
2867
+ return result;
2868
+ }
2869
+
2870
+ for (const entry of entries) {
2871
+ const repo = {
2872
+ accountId: Number(entry.accountId),
2873
+ name: entry.name || '',
2874
+ directory: entry.directory,
2875
+ updated: [],
2876
+ removed: []
2877
+ };
2878
+ if (!entry.directory || !isAccountCheckout(entry.directory)) {
2879
+ repo.skipped = true;
2880
+ repo.reason = 'not_a_git_checkout';
2881
+ result.skipped.push(repo);
2882
+ result.repos.push(repo);
2883
+ continue;
2884
+ }
2885
+
2886
+ const session = guideSessionForRepo(entry, sessions);
2887
+ if (!session) {
2888
+ repo.skipped = true;
2889
+ repo.reason = 'no_session_for_repo';
2890
+ result.skipped.push(repo);
2891
+ result.repos.push(repo);
2892
+ continue;
2893
+ }
2894
+
2895
+ const baseUrl = normalizeBaseUrl(session.baseUrl || options.baseUrl || DEFAULT_BASE_URL);
2896
+ const summary = await downloadAndSyncGuides(entry.directory, {
2897
+ session,
2898
+ baseUrl,
2899
+ accountId: Number(entry.accountId) || session.accountId
2900
+ });
2901
+ if (!summary) {
2902
+ repo.skipped = true;
2903
+ repo.reason = 'guide_payload_unavailable';
2904
+ result.skipped.push(repo);
2905
+ result.repos.push(repo);
2906
+ continue;
2907
+ }
2908
+
2909
+ repo.updated = summary.updated || [];
2910
+ repo.removed = summary.removed || [];
2911
+ result.updatedFiles += repo.updated.length;
2912
+ result.removedFiles += repo.removed.length;
2913
+ if (repo.updated.length || repo.removed.length) {
2914
+ result.updatedRepos += 1;
2915
+ } else {
2916
+ result.unchangedRepos += 1;
2917
+ }
2918
+ result.repos.push(repo);
2919
+ }
2920
+
2921
+ appendGlobalActivityLog('guides', 'sync_all', {
2922
+ repoCount: result.repoCount,
2923
+ updatedRepos: result.updatedRepos,
2924
+ updatedFiles: result.updatedFiles,
2925
+ skipped: result.skipped.length
2926
+ });
2927
+ return result;
2928
+ }
2929
+
2447
2930
  // Ensure the current account repo has the latest guides. Authenticates first
2448
2931
  // (interactive terminals only) so guides are only ever delivered to users who
2449
2932
  // can authenticate to the platform.
@@ -3435,11 +3918,76 @@ function sandboxNetworkHint(err) {
3435
3918
  return ' Network access to ' + baseUrl + ' appears unavailable from the current sandbox. If remits-cli is being run by a local AI agent (for example Codex or Claude Code), retry the command with escalated permissions or outside the sandbox.';
3436
3919
  }
3437
3920
 
3921
+ // The tool inventory is ONE file per checkout, shared by every actor in it, and every refresh replaces it
3922
+ // wholesale. That is fine for one agent and wrong for several: `--as-account`, `--branch` and `--data-mode`
3923
+ // each produce a different inventory, so a second agent's refresh silently becomes the first agent's
3924
+ // "current tools". The file cannot be per-world without breaking the documented path, so instead: write it
3925
+ // atomically (a concurrent reader never sees a half-written file) and return the world it REPLACED, so the
3926
+ // caller can say out loud that it just overwrote somebody else's.
3927
+ function toolsSnapshotWorldSignature(snapshot) {
3928
+ if (!snapshot || typeof snapshot !== 'object') return null;
3929
+ return {
3930
+ accountId: snapshot.accountId,
3931
+ executionAccountId: snapshot.executionAccountId,
3932
+ branchName: snapshot.branchName,
3933
+ variantBranch: snapshot.variantBranch || null,
3934
+ dataMode: snapshot.dataMode
3935
+ };
3936
+ }
3937
+
3438
3938
  function saveToolsSnapshot(cwd, data) {
3439
3939
  const paths = ensureLocalState(cwd);
3440
3940
  const file = path.join(paths.toolsDir, 'tools.json');
3441
- fs.writeFileSync(file, JSON.stringify(data, null, 2));
3442
- return file;
3941
+ let previous = null;
3942
+ try {
3943
+ if (fs.existsSync(file)) previous = JSON.parse(fs.readFileSync(file, 'utf8'));
3944
+ } catch (_) {
3945
+ previous = null;
3946
+ }
3947
+ atomicWriteFile(file, JSON.stringify(data, null, 2));
3948
+ const before = toolsSnapshotWorldSignature(previous);
3949
+ const after = toolsSnapshotWorldSignature(data);
3950
+ const replaced = before && JSON.stringify(before) !== JSON.stringify(after) ? before : null;
3951
+ return { file, replaced };
3952
+ }
3953
+
3954
+ // The other half of the shared-snapshot problem: the writer is warned, but the agent that READS
3955
+ // .remits-cli/shared/tools/tools.json — to pick a tool name, or an input shape — gets no signal that the
3956
+ // file describes somebody else's account, branch or data lane. Answered at the moment it matters: the call.
3957
+ function staleToolsSnapshotWarning(cwd, world) {
3958
+ try {
3959
+ const file = path.join(localStatePaths(cwd).toolsDir, 'tools.json');
3960
+ if (!fs.existsSync(file)) return null;
3961
+ const snapshot = JSON.parse(fs.readFileSync(file, 'utf8'));
3962
+ const snapshotExec = snapshot.executionAccountId || snapshot.accountId;
3963
+ const mismatch = [];
3964
+ if (snapshotExec != null && String(snapshotExec) !== String(world.executionAccountId)) {
3965
+ mismatch.push('account ' + snapshotExec + ' (this call: ' + world.executionAccountId + ')');
3966
+ }
3967
+ if (snapshot.branchName && String(snapshot.branchName) !== String(world.branchName)) {
3968
+ mismatch.push('branch ' + snapshot.branchName + ' (this call: ' + world.branchName + ')');
3969
+ }
3970
+ if (snapshot.dataMode && String(snapshot.dataMode) !== String(world.dataMode)) {
3971
+ mismatch.push('data ' + snapshot.dataMode + ' (this call: ' + world.dataMode + ')');
3972
+ }
3973
+ if (!mismatch.length) return null;
3974
+ return {
3975
+ code: 'tools_snapshot_other_world',
3976
+ message: '.remits-cli/shared/tools/tools.json was last refreshed for ' + mismatch.join(', ') +
3977
+ '. It is one file per checkout, so it may be another agent\'s. Run `remits-cli tools` with your own ' +
3978
+ 'flags before trusting the tool list or an input schema read from it.'
3979
+ };
3980
+ } catch (_) {
3981
+ return null;
3982
+ }
3983
+ }
3984
+
3985
+ function describeToolsSnapshotWorld(world) {
3986
+ if (!world) return 'unknown world';
3987
+ return ['account ' + (world.executionAccountId || world.accountId || '?'),
3988
+ 'branch ' + (world.branchName || '?'),
3989
+ world.variantBranch ? "variant '" + world.variantBranch + "'" : null,
3990
+ 'data ' + (world.dataMode || '?')].filter(Boolean).join(', ');
3443
3991
  }
3444
3992
 
3445
3993
  async function loggedPost(api, cwd, endpoint, payload, options = {}) {
@@ -3979,14 +4527,15 @@ async function authCommand(flags) {
3979
4527
  dataMode: session.dataMode
3980
4528
  }).then((r) => r.data);
3981
4529
  if (toolsData && toolsData.success) {
3982
- const toolsFile = saveToolsSnapshot(cwd, {
4530
+ const toolsSnapshot = saveToolsSnapshot(cwd, {
3983
4531
  accountId: session.accountId,
4532
+ executionAccountId: session.accountId,
3984
4533
  branchName,
3985
4534
  dataMode: toolsData.dataMode || session.dataMode,
3986
4535
  fetchedAt: new Date().toISOString(),
3987
4536
  tools: toolsData.tools || []
3988
4537
  });
3989
- console.log('Tools cached at:', toolsFile);
4538
+ console.log('Tools cached at:', toolsSnapshot.file);
3990
4539
  }
3991
4540
  } catch (err) {
3992
4541
  console.log('Warning: failed to refresh tools after auth:', err.message);
@@ -4152,7 +4701,7 @@ async function pushComponentsCommand(flags) {
4152
4701
  const api = buildAxios(baseUrl, session.token, stageTimeoutMs(components, flags));
4153
4702
  const verification = await verificationPreflight(api, cwd, session, accountId, flags, {
4154
4703
  packetType: 'stage',
4155
- world: { accountId, dataMode, branchName, workspace }
4704
+ world: { repoAccountId: accountId, accountId, executionAccountId: accountId, dataMode, branchName, workspace }
4156
4705
  }, { command: 'components stage' });
4157
4706
  const stagePayload = {
4158
4707
  token: session.token,
@@ -5434,7 +5983,7 @@ async function syncComponentsCommand(rawFlags) {
5434
5983
 
5435
5984
  const verification = await verificationPreflight(api, cwd, session, accountId, flags, {
5436
5985
  packetType: dryRun || namesOnly ? 'sync_dry_run' : 'sync_mutation',
5437
- world: { accountId, dataMode, branchName, workspace }
5986
+ world: { repoAccountId: accountId, accountId, executionAccountId: accountId, dataMode, branchName, workspace }
5438
5987
  }, { command: 'components sync' });
5439
5988
 
5440
5989
  printProdDataBanner({
@@ -6911,7 +7460,7 @@ async function commitComponentsCommand(flags) {
6911
7460
  buildAxios(flags['base-url'] || session.baseUrl || DEFAULT_BASE_URL, session.token),
6912
7461
  cwd, session, accountId, Object.assign({}, flags, { branch: branchName }), {
6913
7462
  packetType: 'sync_mutation',
6914
- world: { accountId, dataMode: resolveDataMode(flags, session), branchName, workspace: resolveWorkspace(cwd, flags) }
7463
+ world: { repoAccountId: accountId, accountId, executionAccountId: accountId, dataMode: resolveDataMode(flags, session), branchName, workspace: resolveWorkspace(cwd, flags) }
6915
7464
  }, { command: 'components commit' });
6916
7465
 
6917
7466
  // Landing is serial and nothing can make it concurrent: `git add -A` sweeps a shared checkout, and the
@@ -7614,17 +8163,31 @@ function runWorldLines(world, context = {}) {
7614
8163
  if (!world || typeof world !== 'object') return [];
7615
8164
  const lines = ['World:'];
7616
8165
  if (context.host) lines.push(' host: ' + context.host);
8166
+ const scopeAccount = world.scopeRootAccountId || world.repoAccountId || world.checkoutAccountId || context.scopeRootAccountId;
8167
+ if (scopeAccount) lines.push(' scoped from: account ' + scopeAccount);
7617
8168
  const accounts = ['account ' + (world.executionAccountId || context.accountId || 'unknown')];
7618
8169
  if (world.checkoutAccountId && world.checkoutAccountId !== world.executionAccountId) accounts.push('checkout ' + world.checkoutAccountId);
7619
8170
  if (world.componentOwnerAccountId && world.componentOwnerAccountId !== world.executionAccountId) accounts.push('component owner ' + world.componentOwnerAccountId);
7620
8171
  lines.push(' runs as: ' + accounts.join(', '));
8172
+ if (world.targetAccountId && world.targetAccountId !== world.executionAccountId) {
8173
+ lines.push(' targets: account ' + world.targetAccountId);
8174
+ }
7621
8175
  lines.push(' data lane: ' + (world.dataMode || context.dataMode || 'unknown'));
7622
8176
  const contextVariantWorld = context.variantWorld && typeof context.variantWorld === 'object' ? context.variantWorld : {};
7623
8177
  const subscribedComponentBranch = world.subscribedComponentBranch || contextVariantWorld.subscribedComponentBranch;
7624
8178
  const componentBranch = world.componentBranch || subscribedComponentBranch || contextVariantWorld.componentBranch || world.variantBranch;
7625
- const branchSource = componentBranch && subscribedComponentBranch && String(componentBranch) === String(subscribedComponentBranch)
7626
- ? 'subscription'
7627
- : (world.componentBranchSource || contextVariantWorld.componentBranchSource || world.variantBranchSource || contextVariantWorld.variantBranchSource);
8179
+ // The platform owns this answer (remits.cli.CliVariantWorld). Reporting a locally re-derived source is how
8180
+ // a line ends up contradicting itself: an --variant-branch probe REPLACES the subscription, and a variant
8181
+ // branch's working tree decides the world before any edge is consulted, but both resolve to a branch the
8182
+ // account often also subscribes to — so "componentBranch == subscribedComponentBranch" cannot mean
8183
+ // "the subscription chose it". Only fill the source in when the platform reports the TRUNK case (the git
8184
+ // branch named no world, so the account's own subscription decided) or reports nothing at all.
8185
+ const resolvedBranchSource = world.componentBranchSource || contextVariantWorld.componentBranchSource ||
8186
+ world.variantBranchSource || contextVariantWorld.variantBranchSource;
8187
+ const subscriptionDecided = componentBranch && subscribedComponentBranch &&
8188
+ String(componentBranch) === String(subscribedComponentBranch) &&
8189
+ (!resolvedBranchSource || resolvedBranchSource === 'trunk' || resolvedBranchSource === 'no-branch');
8190
+ const branchSource = subscriptionDecided ? 'subscription' : resolvedBranchSource;
7628
8191
  const branchLabel = componentBranch
7629
8192
  ? "componentBranch=" + componentBranch + (branchSource ? ' via ' + branchSource : '')
7630
8193
  : (branchSource || '');
@@ -7649,6 +8212,9 @@ function runWorldLines(world, context = {}) {
7649
8212
  lines.push(' test component: ' + world.testComponentSource + (world.testComponentSignature ? ' ' + shortSignature(world.testComponentSignature) : '') +
7650
8213
  (world.testComponentBranch ? " variant '" + world.testComponentBranch + "'" : ''));
7651
8214
  }
8215
+ if (world.componentSource) {
8216
+ lines.push(' component: ' + world.componentSource + (world.componentSignature ? ' ' + shortSignature(world.componentSignature) : ''));
8217
+ }
7652
8218
  lines.push(...stagedMaskedByVariantLines(world.stagedMaskedByVariant, world.executionAccountId || context.accountId));
7653
8219
  return lines;
7654
8220
  }
@@ -7878,7 +8444,8 @@ async function testCommand(flags) {
7878
8444
  const verification = await verificationPreflight(api, cwd, session, accountId, flags, {
7879
8445
  packetType: 'test_run',
7880
8446
  world: {
7881
- accountId,
8447
+ repoAccountId: accountId,
8448
+ accountId: asAccountId ? Number(asAccountId) : Number(accountId),
7882
8449
  executionAccountId: asAccountId ? Number(asAccountId) : Number(accountId),
7883
8450
  dataMode,
7884
8451
  branchName,
@@ -8208,7 +8775,8 @@ async function tokenCommand(flags) {
8208
8775
  const verification = await verificationPreflight(api, cwd, session, accountId, flags, {
8209
8776
  packetType: 'token_inspect',
8210
8777
  world: {
8211
- accountId,
8778
+ repoAccountId: accountId,
8779
+ accountId: tokenAsAccountId ? Number(tokenAsAccountId) : Number(accountId),
8212
8780
  executionAccountId: tokenAsAccountId ? Number(tokenAsAccountId) : Number(accountId),
8213
8781
  dataMode,
8214
8782
  branchName,
@@ -8250,6 +8818,12 @@ async function tokenCommand(flags) {
8250
8818
  baseUrl: normalizeBaseUrl(baseUrl),
8251
8819
  tokenKey: data.tokenKey,
8252
8820
  accountId: data.accountId,
8821
+ executionAccountId: data.accountId,
8822
+ // The account the command was addressed to, i.e. the repo/checkout account. Distinct from `accountId`
8823
+ // (which account the URL EXECUTES as, moved by --as-account) and from
8824
+ // `resolution.componentOwnerAccountId` (whose components run). Three different accounts on a subscriber
8825
+ // token, and the response used to carry only one of them.
8826
+ repoAccountId: data.ownerAccountId,
8253
8827
  branchName: data.branchName,
8254
8828
  variantBranch: data.variantBranch,
8255
8829
  // Why the variant branch is (or is not) set — the platform's one rule, so a null is explainable.
@@ -8293,6 +8867,29 @@ async function tokenCommand(flags) {
8293
8867
  printSessionResolutionWarning(sessionContext);
8294
8868
  printLocalCommandContext(cwd, flags, { accountId: output.accountId || accountId, branchName, workspace: output.workspace, dataMode: output.dataMode || dataMode });
8295
8869
  printLocalStateWarnings(cwd, flags);
8870
+ // The same World block `tools`, `tool` and `test run` print. A token is the surface that hands a browser
8871
+ // a whole resolved world, and it was the one that made the reader dig it out of a JSON dump — including
8872
+ // the `resolution.summary` sentence that says, in words, which account's components the page will run.
8873
+ runWorldLines({
8874
+ accountId: output.accountId,
8875
+ executionAccountId: output.accountId,
8876
+ checkoutAccountId: output.repoAccountId,
8877
+ repoAccountId: output.repoAccountId,
8878
+ componentOwnerAccountId: output.resolution && output.resolution.componentOwnerAccountId,
8879
+ dataMode: output.dataMode || dataMode,
8880
+ branchName: output.branchName || branchName,
8881
+ workspace: output.workspace,
8882
+ stagingLane: output.stagingLane,
8883
+ componentBranch: output.variantWorld && output.variantWorld.componentBranch,
8884
+ subscribedComponentBranch: output.variantWorld && output.variantWorld.subscribedComponentBranch,
8885
+ variantBranch: output.variantBranch,
8886
+ variantBranchSource: output.variantBranchSource,
8887
+ resolves: output.variantWorld && output.variantWorld.resolves,
8888
+ stagedMaskedByVariant: output.stagedMaskedByVariant
8889
+ }, { host: normalizeBaseUrl(baseUrl) }).forEach((line) => console.log(line));
8890
+ if (output.resolution && output.resolution.summary) {
8891
+ console.log(' resolves as: ' + output.resolution.summary);
8892
+ }
8296
8893
  }
8297
8894
  console.log(JSON.stringify(output, null, 2));
8298
8895
 
@@ -8300,7 +8897,16 @@ async function tokenCommand(flags) {
8300
8897
  type: 'token_inspect',
8301
8898
  success: true,
8302
8899
  claim: 'Verification token minted',
8303
- world: buildCommandWorld(output, { accountId: output.accountId || accountId, dataMode: output.dataMode || dataMode, branchName, workspace: output.workspace, host: normalizeBaseUrl(baseUrl) }),
8900
+ world: buildCommandWorld(output, {
8901
+ repoAccountId: output.repoAccountId || accountId,
8902
+ checkoutAccountId: output.repoAccountId || accountId,
8903
+ accountId: output.executionAccountId || output.accountId || accountId,
8904
+ executionAccountId: output.executionAccountId || output.accountId || accountId,
8905
+ dataMode: output.dataMode || dataMode,
8906
+ branchName,
8907
+ workspace: output.workspace,
8908
+ host: normalizeBaseUrl(baseUrl)
8909
+ }),
8304
8910
  revision: collectVerificationSource(cwd, flags),
8305
8911
  browser: {
8306
8912
  tokenKeyHash: output.tokenKey ? sha256(String(output.tokenKey)) : null,
@@ -8369,6 +8975,7 @@ async function toolsCommand(flags) {
8369
8975
  const data = await loggedPost(api, cwd, '/cli/tools', {
8370
8976
  token: session.token,
8371
8977
  accountId,
8978
+ asAccountId: flags['as-account'] || flags['as-account-id'],
8372
8979
  variantBranch,
8373
8980
  branchName,
8374
8981
  workspace,
@@ -8379,12 +8986,17 @@ async function toolsCommand(flags) {
8379
8986
  throw new Error('Failed to fetch tools');
8380
8987
  }
8381
8988
 
8382
- const toolsFile = saveToolsSnapshot(cwd, {
8383
- accountId,
8989
+ const toolsSnapshot = saveToolsSnapshot(cwd, {
8990
+ accountId: data.accountId || accountId,
8991
+ repoAccountId: data.repoAccountId || accountId,
8992
+ checkoutAccountId: data.checkoutAccountId || accountId,
8993
+ executionAccountId: data.executionAccountId || data.accountId || accountId,
8384
8994
  baseUrl: normalizeBaseUrl(baseUrl),
8385
- branchName,
8995
+ branchName: data.branchName || branchName,
8386
8996
  variantBranch: data.variantBranch || variantBranch,
8387
8997
  dataMode: data.dataMode || dataMode,
8998
+ world: data.world,
8999
+ staging: data.staging,
8388
9000
  fetchedAt: new Date().toISOString(),
8389
9001
  tools: data.tools || []
8390
9002
  });
@@ -8392,10 +9004,17 @@ async function toolsCommand(flags) {
8392
9004
  printSessionResolutionWarning(sessionContext);
8393
9005
  printResolvedBaseUrl(baseUrl);
8394
9006
  console.log('Tools refreshed.');
9007
+ if (data.world) runWorldLines(data.world, { host: normalizeBaseUrl(baseUrl), gitHead: safeGitValue(cwd, 'git rev-parse HEAD') }).forEach((line) => console.log(line));
8395
9008
  console.log('Data mode:', data.dataMode || dataMode);
8396
9009
  if (data.variantBranch || variantBranch) console.log('Variant branch:', data.variantBranch || variantBranch);
8397
- console.log('Tools file:', toolsFile);
9010
+ console.log('Tools file:', toolsSnapshot.file);
8398
9011
  console.log('Tool count:', (data.tools || []).length);
9012
+ if (toolsSnapshot.replaced) {
9013
+ console.error('Shared snapshot warning: this refresh replaced the tool inventory for ' +
9014
+ describeToolsSnapshotWorld(toolsSnapshot.replaced) + '. ' +
9015
+ '.remits-cli/shared/tools/tools.json is one file per checkout, so another agent reading it now sees ' +
9016
+ 'YOUR world. Re-run `remits-cli tools` with your own flags before trusting it.');
9017
+ }
8399
9018
  }
8400
9019
 
8401
9020
  function parseToolInput(flags) {
@@ -8429,10 +9048,18 @@ function applyScopeFlags(input, flags) {
8429
9048
  merged.scope = normalized;
8430
9049
  }
8431
9050
 
8432
- const targetAccountId = flags && (flags['target-account-id'] || flags.targetAccountId);
9051
+ const targetAccountId = flags && (flags['target-account'] || flags.targetAccount || flags['target-account-id'] || flags.targetAccountId);
8433
9052
  if (targetAccountId && merged.targetAccountId === undefined) {
8434
9053
  merged.targetAccountId = String(targetAccountId).trim();
8435
9054
  }
9055
+ // ...and as `accountId`, because that is the key the platform's account-targeting tools actually read
9056
+ // (mcp_run_action resolves `input.accountId`, falling back to the RUN SCOPE when it is absent). Without
9057
+ // this, `--target-account 36` printed "targets: account 36" in the World block while the tool ran against
9058
+ // the scope account — a flag that reads like a target and silently is not. An explicit `accountId` in
9059
+ // --input still wins, like every other scope flag.
9060
+ if (targetAccountId && merged.accountId === undefined && merged.account_id === undefined) {
9061
+ merged.accountId = String(targetAccountId).trim();
9062
+ }
8436
9063
 
8437
9064
  const anchorAccountId = flags && (flags['anchor-account-id'] || flags.anchorAccountId);
8438
9065
  if (anchorAccountId && merged.anchorAccountId === undefined) {
@@ -8477,6 +9104,8 @@ async function toolCommand(flags) {
8477
9104
  const branchName = flags.branch || currentBranch(cwd);
8478
9105
  const dataMode = resolveDataMode(flags, session);
8479
9106
  const variantBranch = flags['variant-branch'];
9107
+ const asAccountId = flags['as-account'] || flags['as-account-id'] || flags.asAccountId;
9108
+ const targetAccountId = flags['target-account'] || flags.targetAccount || flags['target-account-id'] || flags.targetAccountId;
8480
9109
  const timeoutMs = parsePositiveInt(flags['timeout-ms'] || flags.timeoutMs, 60000);
8481
9110
  const callId = String(flags['call-id'] || flags.callId || crypto.randomUUID());
8482
9111
  const asyncMode = flagEnabled(flags.async) || flagEnabled(input.async);
@@ -8512,6 +9141,24 @@ async function toolCommand(flags) {
8512
9141
  data.responseFile = statusResponse.responseFile;
8513
9142
  data.sessionLog = sessionJsonlFile(cwd);
8514
9143
  data.localState = localStateSummary(cwd, flags);
9144
+ const statusBranchName = flags.branch || data.branchName || (stored && stored.branchName) || branchName;
9145
+ const statusWorkspace = data.workspace !== undefined ? data.workspace : resolveWorkspace(cwd, flags);
9146
+ const statusWorld = buildToolStatusWorld(data, stored, {
9147
+ repoAccountId: accountId,
9148
+ accountId: statusAccountId,
9149
+ checkoutAccountId: accountId,
9150
+ dataMode: data.dataMode || dataMode,
9151
+ branchName: statusBranchName,
9152
+ workspace: statusWorkspace,
9153
+ host: normalizeBaseUrl(baseUrl)
9154
+ });
9155
+ recordWorkstreamArtifact(cwd, flags, {
9156
+ type: 'tool_status',
9157
+ callId: requestedCallId,
9158
+ path: statusResponse.responseFile,
9159
+ world: statusWorld,
9160
+ workspace: data.workspace
9161
+ });
8515
9162
  // A completed run can still carry a tool-level refusal — see toolResultFailed.
8516
9163
  const polledFailed = data.toolSuccess === false || toolResultFailed(data.result);
8517
9164
  if (polledFailed) {
@@ -8521,13 +9168,13 @@ async function toolCommand(flags) {
8521
9168
  // Same question as the call that started it: a poll must not attach where the call itself would not.
8522
9169
  const statusVerification = await verificationPreflight(api, cwd, session, accountId, flags, {
8523
9170
  packetType: 'tool_call',
8524
- world: { accountId: statusAccountId, dataMode: data.dataMode || dataMode, branchName: flags.branch || (stored && stored.branchName) || branchName, workspace: resolveWorkspace(cwd, flags) }
9171
+ world: statusWorld
8525
9172
  }, { command: 'tool status', neverRefuse: true, quiet: jsonOutput });
8526
9173
  await appendVerificationPacket(api, cwd, session, accountId, statusVerification.evidenceFlags, {
8527
9174
  type: 'tool_call',
8528
9175
  success: data.status !== 'failed' && !polledFailed,
8529
9176
  claim: 'Tool status ' + requestedCallId,
8530
- world: buildCommandWorld(data, { accountId: statusAccountId, dataMode: data.dataMode || dataMode, branchName: flags.branch || (stored && stored.branchName) || branchName, host: normalizeBaseUrl(baseUrl) }),
9177
+ world: statusWorld,
8531
9178
  revision: collectVerificationSource(cwd, flags),
8532
9179
  evidenceCategories: ['tool_call'],
8533
9180
  rawRefs: { toolResponsePath: statusResponse.responseFile, callId: requestedCallId },
@@ -8542,6 +9189,7 @@ async function toolCommand(flags) {
8542
9189
  printLocalCommandContext(cwd, flags, { accountId: statusAccountId, branchName: flags.branch || (stored && stored.branchName) || branchName, dataMode: data.dataMode || dataMode });
8543
9190
  printLocalStateWarnings(cwd, flags);
8544
9191
  console.log('Tool call status:', data.status);
9192
+ if (data.world) runWorldLines(data.world, { host: normalizeBaseUrl(baseUrl) }).forEach((line) => console.log(line));
8545
9193
  console.log('Call ID:', requestedCallId);
8546
9194
  console.log('Data mode:', data.dataMode || dataMode);
8547
9195
  if (data.threadGroupingId) console.log('Thread grouping ID:', data.threadGroupingId);
@@ -8564,15 +9212,49 @@ async function toolCommand(flags) {
8564
9212
  });
8565
9213
 
8566
9214
  const toolWorkspace = resolveWorkspace(cwd, flags);
9215
+ const legacyInputAccountId = input && (input.accountId || input.account_id);
9216
+ // A STRUCTURED warning, not only a printed one: agents run with --json, and `input.accountId` no longer
9217
+ // decides the execution account, so the one signal that the command's component resolution belongs to a
9218
+ // different account than the tool's data must survive into the payload the agent actually reads.
9219
+ const toolWarnings = [];
9220
+ if (legacyInputAccountId && !asAccountId && !targetAccountId && String(legacyInputAccountId) !== String(accountId)) {
9221
+ toolWarnings.push({
9222
+ code: 'legacy_input_account_id',
9223
+ inputAccountId: String(legacyInputAccountId),
9224
+ scopeAccountId: String(accountId),
9225
+ message: 'input.accountId=' + legacyInputAccountId +
9226
+ ' is treated as a tool target while the command stays scoped from account ' + accountId +
9227
+ '. Use --as-account ' + legacyInputAccountId + ' for execution-account semantics, or --target-account ' + legacyInputAccountId + ' to make the target explicit.'
9228
+ });
9229
+ }
9230
+ const snapshotWarning = staleToolsSnapshotWarning(cwd, {
9231
+ executionAccountId: asAccountId || accountId,
9232
+ branchName,
9233
+ dataMode
9234
+ });
9235
+ if (snapshotWarning) toolWarnings.push(snapshotWarning);
9236
+ // stderr always: it never corrupts --json stdout, and suppressing it there hid the warning from exactly
9237
+ // the callers that cannot see the human output.
9238
+ toolWarnings.forEach((warning) => console.error('Warning: ' + warning.message));
8567
9239
  const verification = await verificationPreflight(api, cwd, session, accountId, flags, {
8568
9240
  packetType: 'tool_call',
8569
- world: { accountId, dataMode, branchName, workspace: toolWorkspace }
9241
+ world: {
9242
+ repoAccountId: accountId,
9243
+ accountId: asAccountId || accountId,
9244
+ executionAccountId: asAccountId || accountId,
9245
+ targetAccountId: targetAccountId || legacyInputAccountId,
9246
+ dataMode,
9247
+ branchName,
9248
+ workspace: toolWorkspace
9249
+ }
8570
9250
  }, { command: 'tool --name ' + String(toolName) });
8571
9251
 
8572
9252
  const response = await loggedPost(api, cwd, '/cli/tool', {
8573
9253
  token: session.token,
8574
9254
  accountId,
8575
9255
  accountIdExplicit,
9256
+ asAccountId,
9257
+ targetAccountId: targetAccountId || undefined,
8576
9258
  // The repo/session account, sent as a FIRST-CLASS value rather than left to be inferred from
8577
9259
  // `accountId`: a read tool may reinterpret its own `input.accountId`, and the scope root — what bounds
8578
9260
  // what the call may discover — has to survive that. The server validates it; it is never authorization.
@@ -8602,6 +9284,15 @@ async function toolCommand(flags) {
8602
9284
  data.responseFile = response.responseFile;
8603
9285
  data.sessionLog = sessionJsonlFile(cwd);
8604
9286
  data.localState = localStateSummary(cwd, flags);
9287
+ if (toolWarnings.length) data.warnings = (data.warnings || []).concat(toolWarnings);
9288
+ recordWorkstreamArtifact(cwd, flags, {
9289
+ type: asyncMode ? 'tool_async_start' : 'tool_call',
9290
+ callId,
9291
+ path: response.responseFile,
9292
+ tool: String(toolName),
9293
+ world: buildCommandWorld(data, { repoAccountId: accountId, accountId: data.executionAccountId || asAccountId || accountId, dataMode: data.dataMode || dataMode, branchName, workspace: toolWorkspace, host: normalizeBaseUrl(baseUrl) }),
9294
+ workspace: data.workspace || toolWorkspace
9295
+ });
8605
9296
  if (toolFailureMessage && !data.toolMessage) data.toolMessage = toolFailureMessage;
8606
9297
 
8607
9298
  let evidenceData = data;
@@ -8614,6 +9305,7 @@ async function toolCommand(flags) {
8614
9305
  printLocalStateWarnings(cwd, flags);
8615
9306
  console.log(asyncMode ? 'Tool call started.'
8616
9307
  : (toolFailed ? 'Tool call FAILED — the tool ran and returned an error.' : 'Tool call succeeded.'));
9308
+ if (data.world) runWorldLines(data.world, { host: normalizeBaseUrl(baseUrl), gitHead: safeGitValue(cwd, 'git rev-parse HEAD') }).forEach((line) => console.log(line));
8617
9309
  if (toolFailed && toolFailureMessage) console.log('Tool error:', toolFailureMessage);
8618
9310
  console.log('Call ID:', callId);
8619
9311
  console.log('Data mode:', data.dataMode || dataMode);
@@ -8646,10 +9338,26 @@ async function toolCommand(flags) {
8646
9338
 
8647
9339
  finalStatus.data.responseFile = finalStatus.responseFile;
8648
9340
  finalStatus.data.sessionLog = sessionJsonlFile(cwd);
9341
+ recordWorkstreamArtifact(cwd, flags, {
9342
+ type: 'tool_async_final',
9343
+ callId,
9344
+ path: finalStatus.responseFile,
9345
+ tool: String(toolName),
9346
+ world: buildCommandWorld(finalStatus.data, { repoAccountId: accountId, accountId: finalStatus.data.executionAccountId || asAccountId || accountId, dataMode: finalStatus.data.dataMode || dataMode, branchName: finalStatus.data.branchName || branchName, workspace: toolWorkspace, host: normalizeBaseUrl(baseUrl) }),
9347
+ workspace: finalStatus.data.workspace || toolWorkspace
9348
+ });
8649
9349
  evidenceData = finalStatus.data;
8650
9350
  evidenceResponseFile = finalStatus.responseFile;
8651
9351
  if (!jsonOutput) {
8652
9352
  console.log('Final status:', finalStatus.data.status);
9353
+ if (finalStatus.data.world) {
9354
+ runWorldLines(finalStatus.data.world, { host: normalizeBaseUrl(baseUrl), gitHead: safeGitValue(cwd, 'git rev-parse HEAD') }).forEach((line) => console.log(line));
9355
+ const startWorldText = JSON.stringify(data.world || {});
9356
+ const finalWorldText = JSON.stringify(finalStatus.data.world || {});
9357
+ if (data.world && startWorldText !== finalWorldText) {
9358
+ console.log('World changed since start: inspect the final response file for the exact resolved execution world.');
9359
+ }
9360
+ }
8653
9361
  printToolResponseFileLine('Tool response file', finalStatus.responseFile);
8654
9362
  }
8655
9363
  if (finalStatus.data.status !== 'completed') {
@@ -8663,7 +9371,20 @@ async function toolCommand(flags) {
8663
9371
  success: asyncMode && !waitForAsync ? null : (!toolFailed && (!asyncMode || process.exitCode !== 1)),
8664
9372
  pending: asyncMode && !waitForAsync,
8665
9373
  claim: 'Tool call ' + String(toolName),
8666
- world: buildCommandWorld(evidenceData, { accountId: evidenceData.accountId || accountId, dataMode: evidenceData.dataMode || dataMode, branchName: evidenceData.branchName || branchName, workspace: resolveWorkspace(cwd, flags), host: normalizeBaseUrl(baseUrl), sourceLayer: evidenceData.componentSource }),
9374
+ world: buildCommandWorld(evidenceData, {
9375
+ accountId: evidenceData.executionAccountId || asAccountId || evidenceData.accountId || accountId,
9376
+ repoAccountId: accountId,
9377
+ checkoutAccountId: accountId,
9378
+ executionAccountId: evidenceData.executionAccountId || asAccountId || accountId,
9379
+ targetAccountId: evidenceData.targetAccountId || targetAccountId || legacyInputAccountId,
9380
+ dataMode: evidenceData.dataMode || dataMode,
9381
+ branchName: evidenceData.branchName || branchName,
9382
+ workspace: resolveWorkspace(cwd, flags),
9383
+ host: normalizeBaseUrl(baseUrl),
9384
+ componentSource: evidenceData.componentSource,
9385
+ componentSignature: evidenceData.componentSignature,
9386
+ sourceLayer: evidenceData.componentSource
9387
+ }),
8667
9388
  revision: Object.assign(collectVerificationSource(cwd, flags), {
8668
9389
  compileSignatures: evidenceData.componentSignature ? [evidenceData.componentSignature] : []
8669
9390
  }),
@@ -8994,10 +9715,16 @@ async function verifyCommand(flags, subcommand) {
8994
9715
  if (flags.manifest || flags.file) {
8995
9716
  manifest = parseManifestFile(flags.manifest || flags.file);
8996
9717
  }
8997
- if (dataMode === 'prod' && !manifestRequiredEvidence(manifest).length && !flagEnabled(flags['no-contract'])) {
9718
+ // PROD only, and framed as risk rather than as an unmet obligation. An envelope with no contract is an
9719
+ // evidence log, which is the normal and complete mode; warning about it on every lane taught agents that
9720
+ // their own record-keeping was an exam they were failing, and they responded by starting it over.
9721
+ if (dataMode === 'prod' && !manifestRequiredEvidence(manifest).length && !manifestClaims(manifest).length &&
9722
+ !flagEnabled(flags['no-contract'])) {
8998
9723
  console.error('');
8999
- console.error('Warning: starting a prod-data verification envelope with no required evidence contract.');
9000
- console.error('Attach a manifest with requiredEvidence before destructive work, or pass --no-contract when this is intentionally evidence-only.');
9724
+ console.error('Note: this envelope records PRODUCTION-data evidence with no acceptance contract.');
9725
+ console.error('That is fine for investigation. Before destructive or irreversible production work, name what');
9726
+ console.error('must be true so the result is checkable by someone else:');
9727
+ console.error(' remits-cli verify claim <id> --text "what must be true" [--test "<suite>"]');
9001
9728
  console.error('');
9002
9729
  }
9003
9730
  let statusResponse = null;
@@ -9027,12 +9754,17 @@ async function verifyCommand(flags, subcommand) {
9027
9754
  ticketId: flags.ticket,
9028
9755
  account: collectVerificationAccount(cwd, accountId, baseUrl, dataMode),
9029
9756
  source,
9030
- manifest
9757
+ manifest,
9758
+ noContract: flagEnabled(flags['no-contract']) || undefined
9031
9759
  });
9032
9760
  const envelope = response.envelope;
9033
9761
  writeLocalVerificationEnvelope(cwd, envelope);
9034
9762
  writeActiveVerificationEnvelope(cwd, envelope.envelopeId, activeContext);
9035
9763
  console.log('Verification envelope started:', envelope.envelopeId);
9764
+ if (!manifestRequiredEvidence(manifest).length && !manifestClaims(manifest).length) {
9765
+ console.log('Mode: evidence log (no acceptance contract asked for). Commands attach world-stamped evidence;');
9766
+ console.log(' nothing is outstanding. Add `verify claim <id> --text "..."` only if someone needs a verdict.');
9767
+ }
9036
9768
  printLocalCommandContext(cwd, flags, { accountId, branchName, workspace, dataMode });
9037
9769
  printLocalStateWarnings(cwd, flags);
9038
9770
  console.log('Target:', checkoutWorldLine + ', dataMode=' + dataMode +
@@ -9063,7 +9795,11 @@ async function verifyCommand(flags, subcommand) {
9063
9795
  return response;
9064
9796
  }
9065
9797
 
9066
- const envelopeId = flags.envelope || flags['verify-envelope'] || flags.id || readActiveVerificationEnvelope(cwd, activeContext);
9798
+ const positionalEnvelopeId = flags._ && flags._[2];
9799
+ const showLikeSubcommands = ['show', 'read', 'get', 'packets', 'status', 'report'];
9800
+ const explicitEnvelopeId = flags.envelope || flags['verify-envelope'] || flags.id ||
9801
+ (showLikeSubcommands.includes(sub) ? positionalEnvelopeId : null);
9802
+ const envelopeId = explicitEnvelopeId || readActiveVerificationEnvelope(cwd, activeContext);
9067
9803
  if (!envelopeId) {
9068
9804
  throw new Error('No verification envelope selected. Run `remits-cli verify start --summary "..."` or pass --envelope <id>.');
9069
9805
  }
@@ -9108,6 +9844,40 @@ async function verifyCommand(flags, subcommand) {
9108
9844
  return envelope;
9109
9845
  }
9110
9846
 
9847
+ if (sub === 'claim' || sub === 'claims') {
9848
+ const claimId = flags.id || (flags._ && flags._[2]);
9849
+ const text = flags.text || flags.claim || flags.summary || flags.note;
9850
+ if (!claimId) {
9851
+ throw new Error('Usage: remits-cli verify claim <id> --text "what must be true" [--test "<suite>"] [--packet-type <type>]');
9852
+ }
9853
+ const claim = { id: String(claimId) };
9854
+ if (text) claim.text = String(text);
9855
+ // A claim may name the shape of evidence that proves it, so one line of contract can bind to a suite
9856
+ // instead of needing a second parallel requiredEvidence list kept in sync by hand.
9857
+ const suite = flags.test || flags.suite;
9858
+ if (suite) {
9859
+ claim.packetType = 'test_run';
9860
+ claim.suite = String(suite);
9861
+ if (flags.case || flags.cases) {
9862
+ claim.cases = [].concat(flags.case || []).concat(flags.cases || [])
9863
+ .flatMap((value) => String(value).split(',')).map((value) => value.trim()).filter(Boolean);
9864
+ }
9865
+ } else if (flags['packet-type'] || flags.packetType) {
9866
+ claim.packetType = String(flags['packet-type'] || flags.packetType);
9867
+ }
9868
+ const response = await postVerificationCommand(api, cwd, session, accountId, 'claim', { envelopeId, claim });
9869
+ writeLocalVerificationEnvelope(cwd, response.envelope);
9870
+ const acceptance = (response.envelope || {}).acceptance || {};
9871
+ console.log('Claim recorded on envelope ' + envelopeId + ': ' + claim.id);
9872
+ console.log('Contract now requires ' + (acceptance.requiredEvidence || []).length + ' item(s).');
9873
+ console.log('Prove it by adding --claim ' + claim.id + ' to the command that demonstrates it, e.g.');
9874
+ console.log(' remits-cli verify test --test "<suite>" --claim ' + claim.id);
9875
+ console.log(' remits-cli verify attach --claim ' + claim.id + ' --note "what you observed"');
9876
+ printAcceptanceWarnings(response.envelope);
9877
+ printEnvelopeWarnings(response.envelope);
9878
+ return response.envelope;
9879
+ }
9880
+
9111
9881
  if (sub === 'attach') {
9112
9882
  const artifacts = [];
9113
9883
  let type = 'manual_observation';
@@ -9144,7 +9914,7 @@ async function verifyCommand(flags, subcommand) {
9144
9914
  artifacts,
9145
9915
  evidenceCategories
9146
9916
  };
9147
- const response = await appendVerificationPacket(api, cwd, session, accountId, { 'verify-envelope': envelopeId }, packet);
9917
+ const response = await appendVerificationPacket(api, cwd, session, accountId, Object.assign({}, flags, { 'verify-envelope': envelopeId }), packet);
9148
9918
  console.log('Evidence attached:', (response.packet || packet).packetId);
9149
9919
  return response;
9150
9920
  }
@@ -9155,7 +9925,9 @@ async function verifyCommand(flags, subcommand) {
9155
9925
  const packet = {
9156
9926
  type: sub === 'browser-start' ? 'browser_session' : (sub === 'browser-snapshot' ? 'browser_snapshot' : 'browser_step'),
9157
9927
  success: !flagEnabled(flags.failed),
9158
- claim: flags.claim || flags.label || action,
9928
+ // `--claim` now names a declared acceptance claim id, so the free-text label for a browser step is
9929
+ // `--label`. Reading both from one flag made the id double as prose and the prose double as an id.
9930
+ claim: flags.label || flags.text || action,
9159
9931
  world: buildCommandWorld(null, { accountId, dataMode, branchName, workspace, host: normalizeBaseUrl(baseUrl) }),
9160
9932
  browser: {
9161
9933
  action,
@@ -9168,7 +9940,7 @@ async function verifyCommand(flags, subcommand) {
9168
9940
  evidenceCategories: [sub, action === 'upload' ? 'browser_session.actual_upload' : null, action === 'click' ? 'browser_session.actual_clicks' : null]
9169
9941
  .filter(Boolean)
9170
9942
  };
9171
- const response = await appendVerificationPacket(api, cwd, session, accountId, { 'verify-envelope': envelopeId }, packet);
9943
+ const response = await appendVerificationPacket(api, cwd, session, accountId, Object.assign({}, flags, { 'verify-envelope': envelopeId }), packet);
9172
9944
  console.log('Browser evidence attached:', (response.packet || packet).packetId);
9173
9945
  return response;
9174
9946
  }
@@ -9222,7 +9994,7 @@ async function verifyCommand(flags, subcommand) {
9222
9994
  // report would list as wrong-world evidence on every status check.
9223
9995
  const statusVerification = await verificationPreflight(api, cwd, session, accountId, { 'verify-envelope': envelopeId }, {
9224
9996
  packetType: 'component_status',
9225
- world: { accountId, dataMode, branchName, workspace }
9997
+ world: { repoAccountId: accountId, accountId, executionAccountId: accountId, dataMode, branchName, workspace }
9226
9998
  }, { command: 'verify status', quiet: true, neverRefuse: true });
9227
9999
  const componentStatus = await loggedPost(api, cwd, '/cli/components', {
9228
10000
  token: session.token,
@@ -10591,6 +11363,7 @@ function renderDashboardHtml(snapshot) {
10591
11363
  '<div class="action-row">' +
10592
11364
  '<button class="action-btn" data-action="export-issues">Export Issue Bundle</button>' +
10593
11365
  '<button class="action-btn" data-action="rescan-repos">Rescan Account Repos</button>' +
11366
+ '<button class="action-btn" data-action="sync-guides">Update Guides In All Repos</button>' +
10594
11367
  '<button class="action-btn" data-action="reconnect-websockets">Reconnect Websockets</button>' +
10595
11368
  '<button class="action-btn" data-action="refresh-agents">Refresh Agents</button>' +
10596
11369
  '</div>' +
@@ -10663,7 +11436,7 @@ function renderDashboardHtml(snapshot) {
10663
11436
  'summaryItem("Available Agents",String(agentList.length)),' +
10664
11437
  'summaryItem("Routed Tickets",String(ticketCounts.routed||0))' +
10665
11438
  '].join("");}' +
10666
- 'function renderLastAction(state){const target=document.getElementById("last-action");if(!state.lastAction){target.className="empty";target.textContent="No dashboard actions have been triggered yet.";return;}target.className="summary-item";target.innerHTML=\'<span class="k">Last Action</span><span class="v">\'+escapeHtml(state.lastAction.action||"unknown")+\'</span><div class="muted" style="margin-top:8px;">\'+escapeHtml(formatTime(state.lastAction.at))+\'</div>\'+(state.lastAction.bundlePath?\'<div class="muted" style="margin-top:8px;word-break:break-word;"><strong>Bundle:</strong> \'+escapeHtml(state.lastAction.bundlePath)+\'</div>\':"");}' +
11439
+ 'function renderLastAction(state){const target=document.getElementById("last-action");if(!state.lastAction){target.className="empty";target.textContent="No dashboard actions have been triggered yet.";return;}target.className="summary-item";target.innerHTML=\'<span class="k">Last Action</span><span class="v">\'+escapeHtml(state.lastAction.action||"unknown")+\'</span><div class="muted" style="margin-top:8px;">\'+escapeHtml(formatTime(state.lastAction.at))+\'</div>\'+(state.lastAction.summary?\'<div class="muted" style="margin-top:8px;">\'+escapeHtml(state.lastAction.summary)+\'</div>\':"")+(state.lastAction.bundlePath?\'<div class="muted" style="margin-top:8px;word-break:break-word;"><strong>Bundle:</strong> \'+escapeHtml(state.lastAction.bundlePath)+\'</div>\':"");}' +
10667
11440
  'function renderIssues(state){const target=document.getElementById("issues-section");const issues=state.issues||[];if(!issues.length){target.innerHTML=\'<div class="empty">No automatic issue signals are currently detected. If something still feels wrong, you can still click "Export Issue Bundle" to prepare a package for a Remits system admin.</div>\';return;}target.innerHTML=issues.map(function(issue,index){const tone=issue.severity==="bad"?"bad":"warn";const meta=badge(issue.severity==="bad"?"critical":"warning",tone)+\' <span class="muted">\'+escapeHtml(issue.scope+(issue.repoAccountId?(" account "+issue.repoAccountId):""))+\'</span>\';const evidence=(issue.evidence||[]).map(function(line){return "<li>"+escapeHtml(line)+"</li>";}).join("");const body=\'<div class="stack"><div class="muted">\'+escapeHtml(issue.summary||"")+\'</div><div class="mini-card"><strong>Recommendation</strong>\'+escapeHtml(issue.recommendation||"") + \'</div><div><strong style="display:block;margin-bottom:8px;">Evidence</strong><ul style="margin:0;padding-left:18px;display:grid;gap:8px;">\'+(evidence||"<li>No evidence lines captured.</li>")+\'</ul></div></div>\';return detailsBlock("issue:"+issue.id,(index+1)+". "+issue.title,meta,body,index===0);}).join("");}' +
10668
11441
  'function renderSupportTickets(state){const target=document.getElementById("support-ticket-section");const support=state.supportTickets||{};const parts=support.compartments||[];const errors=support.errors||[];let html="";if(errors.length){html+=detailsBlock("support:errors","Queue fetch errors",badge(String(errors.length)+" error(s)","warn"),codeBlock("support-errors",JSON.stringify(errors,null,2),"log"),false);}if(!parts.length&&!errors.length){target.innerHTML=html+\'<div class="empty">No authenticated support-ticket queue is available yet.</div>\';return;}html+=parts.map(function(part,pi){const lane=(part.dataMode||"prod");const meta=badge(lane+" lane",lane==="test"?"warn":"ok")+\' \'+badge(String(part.open||0)+" open","ok")+\' <span class="muted">\'+escapeHtml(String(part.queueCount||0))+\' account queue(s)\'+(part.overlappingQueuesDropped?", "+part.overlappingQueuesDropped+" overlapping dropped":"")+\'</span>\';const caveat=part.scopeUnknown?\'<div class="mini-card"><strong>Note</strong>A queue did not report which accounts it scanned, so this total may count a ticket more than once.</div>\':"";const cards=\'<div class="mini-grid"><div class="mini-card"><strong>Open</strong>\'+escapeHtml(String(part.open||0))+\'</div><div class="mini-card"><strong>Routed To An Agent</strong>\'+escapeHtml(String(part.routed||0))+\'</div><div class="mini-card"><strong>Unrouted</strong>\'+escapeHtml(String(part.unrouted||0))+\'</div><div class="mini-card"><strong>Awaiting A Reply</strong>\'+escapeHtml(String(part.awaitingReplyOnPage||0))+\' <span class="muted">of \'+escapeHtml(String((part.tickets||[]).length))+\' shown</span></div></div>\'+caveat;const rows=(part.tickets||[]).length?(part.tickets||[]).map(function(ticket,index){const routed=!!ticket.routedAgentId;const waiting=!!ticket.awaitingResponseSince;const meta2=badge(ticket.priority||"priority",ticket.priority==="critical"||ticket.priority==="high"?"bad":"warn")+\' \'+badge(ticket.status||"status",ticket.status==="open"?"ok":"warn")+\' \'+badge(routed?("routed: "+(ticket.routedAgentLabel||ticket.routedAgentId)):"unrouted",routed?"ok":"warn")+(waiting?\' \'+badge("awaiting reply","warn"):"")+\' <span class="muted">\'+escapeHtml(ticket.accountName||ticket.accountId||"unknown account")+\'</span>\';return detailsBlock("support-ticket:"+pi+":"+ticket.ticketId,String(ticket.ticketId||"ticket")+" "+(ticket.subject||""),meta2,supportTicketDetails(state,ticket,"support-ticket:"+pi+":"+index),false);}).join(""):\'<div class="empty">No open support tickets in this compartment.</div>\';return detailsBlock("compartment:"+part.compartment,part.baseUrl||part.compartment,meta,cards+\'<div class="accordion">\'+rows+\'</div>\',pi===0);}).join("");html+=\'<div class="muted" style="margin-top:10px;">Each platform and data lane is counted separately. A localhost test-lane queue is full of suite fixtures and is not comparable to a production queue — summing them is what made this panel report 583 open tickets against a production queue of 24.</div>\';target.innerHTML=html;}' +
10669
11442
  'function supportTicketDetails(state,ticket,key){const agents=((state.agents||{}).registered)||[];const ticketAccountId=String(ticket.accountId||"");const eligible=agents.filter(function(a){if(ticket.baseUrl&&a.baseUrl&&a.baseUrl!==ticket.baseUrl)return false;if(ticket.dataMode&&a.dataMode&&a.dataMode!==ticket.dataMode)return false;return (a.accounts||[]).some(function(acc){return String(acc.accountId)===ticketAccountId||acc.routingScope==="self_and_descendants";});});const options=[\'<option value="">- unrouted -</option>\'].concat(eligible.map(function(a){const sel=a.agentId===ticket.routedAgentId?" selected":"";return \'<option value="\'+escapeHtml(a.agentId)+\'"\'+sel+\'>\'+escapeHtml((a.label||a.userName||a.agentId)+" - "+(a.state||"idle")+(a.activity?" ("+a.activity+")":""))+\'</option>\';})).join("");const routeControl=eligible.length?(\'<div class="mini-card"><strong>Route To Agent</strong><div style="display:flex;gap:8px;margin-top:6px;"><select data-route-ticket="\'+escapeHtml(String(ticket.ticketId||""))+\'" data-route-account="\'+escapeHtml(ticketAccountId)+\'" data-route-base="\'+escapeHtml(ticket.baseUrl||"")+\'" data-route-mode="\'+escapeHtml(ticket.dataMode||"")+\'" style="flex:1;min-width:0;">\'+options+\'</select><button class="action-btn" data-action="route-ticket" data-ticket="\'+escapeHtml(String(ticket.ticketId||""))+\'">Route</button></div></div>\'):\'<div class="mini-card"><strong>Route To Agent</strong>No registered agent covers this account.</div>\';const lane=ticket.dataMode||"prod";const planning=[ticket.workstream?("Workstream "+ticket.workstream):"",ticket.plannedIn?("Planned "+ticket.plannedIn):"",ticket.boardStage?("Board "+ticket.boardStage):"",ticket.rank!==undefined&&ticket.rank!==null&&ticket.rank!==""?("Rank "+ticket.rank):"",ticket.size?("Size "+ticket.size):"",ticket.blockedBy?("Blocked by "+ticket.blockedBy):""].filter(Boolean).join(" / ");const command="remits-cli ticket read --ticket "+String(ticket.ticketId||"")+" --data-mode "+lane+"\\nremits-cli ticket accept --ticket "+String(ticket.ticketId||"")+" --data-mode "+lane;return \'<div class="stack"><div class="mini-grid"><div class="mini-card"><strong>Ticket</strong>\'+escapeHtml(ticket.ticketId||"n/a")+\'</div><div class="mini-card"><strong>Account</strong>\'+escapeHtml((ticket.accountName||"n/a")+" ("+(ticket.accountId||"n/a")+")")+\'</div><div class="mini-card"><strong>Type</strong>\'+escapeHtml(ticket.type||"n/a")+\'</div><div class="mini-card"><strong>Owned By</strong>\'+escapeHtml(ticket.assignedTo||"unassigned")+\'</div><div class="mini-card"><strong>Routed To</strong>\'+escapeHtml(ticket.routedAgentLabel||ticket.routedAgentId||"nobody")+\'</div><div class="mini-card"><strong>Implementation</strong>\'+escapeHtml((ticket.implementationAccountName||"n/a")+" ("+(ticket.implementationAccountId||"n/a")+")")+\'</div><div class="mini-card"><strong>Planning</strong>\'+escapeHtml(planning||"No planning fields")+\'</div></div>\'+routeControl+\'<div class="muted">\'+escapeHtml(ticket.affectedComponent||"No affected component recorded.")+\'</div>\'+codeBlock(key+":cmd",command)+\'</div>\';}' +
@@ -11054,6 +11827,10 @@ function startDashboardServer(preferredPort) {
11054
11827
  } else if (action === 'rescan-repos') {
11055
11828
  recordDiscoverySummary(discoverAccountRepos());
11056
11829
  supportTicketSnapshotCache = { fetchedAtMs: 0, value: null };
11830
+ } else if (action === 'sync-guides') {
11831
+ actionResult = await syncGuidesAcrossIndexedRepos({ rescan: true });
11832
+ runtimeState.lastAction.summary = guideSyncSummaryLine(actionResult);
11833
+ recordDiscoverySummary(discoverAccountRepos());
11057
11834
  } else if (action === 'refresh-agents') {
11058
11835
  agentSnapshotCache = { fetchedAtMs: 0, value: null };
11059
11836
  supportTicketSnapshotCache = { fetchedAtMs: 0, value: null };
@@ -12151,6 +12928,11 @@ function resolveAgentId(flags, options = {}) {
12151
12928
  * agent is silently useless for support: it registers, appears online, and can never be routed a
12152
12929
  * production ticket. Ask for `--data-mode test` when you want a fixture agent.
12153
12930
  */
12931
+ /**
12932
+ * The host and lane an agent command runs against: the flag, then this agent's own prior registration, then
12933
+ * the PRODUCTION platform and lane. That last default is deliberate — `agent register` joins the support
12934
+ * fleet, and a fleet member on a test lane appears online and can never be routed a real ticket.
12935
+ */
12154
12936
  function agentDefaults(flags, entry) {
12155
12937
  return {
12156
12938
  baseUrl: normalizeBaseUrl(
@@ -12163,10 +12945,29 @@ function agentDefaults(flags, entry) {
12163
12945
  function agentSessionFor(flags, entry) {
12164
12946
  const cwd = (entry && entry.cwd) || process.cwd();
12165
12947
  const defaults = agentDefaults(flags, entry);
12166
- const context = resolveSessionContext(cwd, Object.assign({}, flags, {
12167
- 'base-url': defaults.baseUrl,
12168
- 'data-mode': defaults.dataMode
12169
- }));
12948
+ const pinnedByCaller = Boolean((flags && flags['base-url']) || (entry && entry.baseUrl));
12949
+ let context;
12950
+ try {
12951
+ context = resolveSessionContext(cwd, Object.assign({}, flags, {
12952
+ 'base-url': defaults.baseUrl,
12953
+ 'data-mode': defaults.dataMode
12954
+ }));
12955
+ } catch (err) {
12956
+ // The prod default is intentional, and invisible: every OTHER command in this checkout follows the
12957
+ // active session, so "not authenticated at <some prod url>" reads as a broken session rather than as
12958
+ // "this command deliberately went somewhere else". Say which host it chose and why.
12959
+ if (!pinnedByCaller) {
12960
+ const local = readSession({}) || {};
12961
+ const refusal = new Error('Agent commands join the SUPPORT FLEET, which lives on the production ' +
12962
+ 'platform, so this one targeted ' + defaults.baseUrl + ' — not the session this checkout uses' +
12963
+ (local.baseUrl ? ' (' + normalizeBaseUrl(local.baseUrl) + ')' : '') + '. ' +
12964
+ 'Authenticate there with `remits-cli auth --base-url ' + defaults.baseUrl + '`, or point this ' +
12965
+ 'command at another platform with `--base-url <url>`.');
12966
+ refusal.cause = err;
12967
+ throw refusal;
12968
+ }
12969
+ throw err;
12970
+ }
12170
12971
  if (!context.session || !context.session.token) {
12171
12972
  throw new Error('No authenticated session for ' + defaults.baseUrl +
12172
12973
  '. Run: remits-cli auth --base-url ' + defaults.baseUrl);
@@ -12747,7 +13548,7 @@ async function postAgentStatus(context, agentId, update) {
12747
13548
  * the same thing. Only the local supervisor can add machine-local repository hints; the worker owns the
12748
13549
  * final grounding step, including creating or choosing a git worktree before it edits.
12749
13550
  */
12750
- function spawnTicketWorker(context, ctx, ticket) {
13551
+ async function spawnTicketWorker(context, ctx, ticket) {
12751
13552
  const { spawn } = require('child_process');
12752
13553
  const agentId = ctx.agentId;
12753
13554
  const ticketId = String(ticket.id || ticket.ticketId);
@@ -12766,6 +13567,32 @@ function spawnTicketWorker(context, ctx, ticket) {
12766
13567
  const targetBranch = repositoryWork ? ticketTargetBranch(ticket, baseWorkingDirectory) : gitBranchNameInDirectory(baseWorkingDirectory);
12767
13568
  const suggestedWorktree = repositoryWork && baseWorkingDirectory ? ticketWorktreePath(baseWorkingDirectory, ticketId) : null;
12768
13569
  const workingDirectory = baseWorkingDirectory;
13570
+ if (workingDirectory) {
13571
+ try {
13572
+ const guideSummary = await downloadAndSyncGuides(workingDirectory, {
13573
+ session: context.session,
13574
+ baseUrl: context.baseUrl,
13575
+ accountId: Number(ticket.accountId) || context.accountId
13576
+ });
13577
+ if (guideSummary && ((guideSummary.updated || []).length || (guideSummary.removed || []).length)) {
13578
+ appendGlobalActivityLog('guides', 'worker_repo_synced', {
13579
+ agentId,
13580
+ ticketId,
13581
+ accountId: Number(ticket.accountId) || null,
13582
+ directory: workingDirectory,
13583
+ updated: (guideSummary.updated || []).length,
13584
+ removed: (guideSummary.removed || []).length
13585
+ });
13586
+ }
13587
+ } catch (err) {
13588
+ appendGlobalActivityLog('guides', 'worker_repo_sync_failed', {
13589
+ agentId,
13590
+ ticketId,
13591
+ directory: workingDirectory,
13592
+ error: describeError(err)
13593
+ }, 'error');
13594
+ }
13595
+ }
12769
13596
  // The brief is rendered server-side without the local path (the platform cannot know it), so the
12770
13597
  // resolved local repository hint is appended here. When nothing resolved, the server-rendered brief already
12771
13598
  // tells the worker to find it rather than guess — do not paper over that with a default.
@@ -12796,6 +13623,7 @@ function spawnTicketWorker(context, ctx, ticket) {
12796
13623
  'git -C ' + shellQuote(workingDirectory) + ' worktree add --force ' + shellQuote(suggestedWorktree) +
12797
13624
  (targetBranch ? ' ' + targetBranch : ' <branch>') + '\n' +
12798
13625
  'cd ' + shellQuote(suggestedWorktree) + '\n' +
13626
+ 'remits-cli guides sync\n' +
12799
13627
  (targetBranch
12800
13628
  ? 'git pull --ff-only origin ' + shellQuote(targetBranch) + '\n' +
12801
13629
  'git rev-list --left-right --count HEAD...origin/' + shellQuote(targetBranch) + ' # must print: 0 0\n'
@@ -13169,7 +13997,7 @@ async function agentHeartbeatDaemonCommand(flags) {
13169
13997
  if (!ticketId || inFlight.has(ticketId)) {
13170
13998
  continue;
13171
13999
  }
13172
- const started = spawnTicketWorker(context, ctx, ticket);
14000
+ const started = await spawnTicketWorker(context, ctx, ticket);
13173
14001
  inFlight.set(ticketId, { pid: started.pid, accountId: ticket.accountId });
13174
14002
  started.done.then(() => {
13175
14003
  inFlight.delete(ticketId);
@@ -14018,6 +14846,13 @@ async function activityInspectCommand(flags) {
14018
14846
  dataMode: resolveDataMode(flags, session),
14019
14847
  userId: flags['user-id'] || flags.userId || flags['cli-user-id'],
14020
14848
  userEmail: flags['user-email'] || flags.email,
14849
+ workspace: flags.workspace,
14850
+ branchName: flags.branch || flags['branch-name'],
14851
+ variantBranch: flags['variant-branch'],
14852
+ // The local proof-campaign id, so the platform-side related view and `workstream status` can be asked
14853
+ // the same question. Never defaulted from the workspace here: an unasked-for filter that silently
14854
+ // narrows an investigation is worse than no filter.
14855
+ workstreamId: normalizeWorkstreamId(flags.workstream || flags['workstream-id']),
14021
14856
  limit: flags.limit || flags.max || 20,
14022
14857
  envelopeLimit: flags['envelope-limit'] || flags.envelopeLimit
14023
14858
  }).then((r) => r.data);
@@ -14059,26 +14894,68 @@ function slimActivitySmell(smell) {
14059
14894
  return {
14060
14895
  severity: s.severity || 'info',
14061
14896
  type: s.type || 'signal',
14897
+ title: s.title || s.envelopeSummary || s.testName || null,
14062
14898
  summary: s.summary || null,
14899
+ status: s.status || s.health || null,
14900
+ currentFailureCount: s.currentFailureCount == null ? null : s.currentFailureCount,
14063
14901
  accountId: s.accountId || null,
14064
14902
  laneId: s.laneId || null,
14065
14903
  envelopeId: s.envelopeId || null,
14066
14904
  taskId: s.taskId || null,
14905
+ testName: s.testName || null,
14067
14906
  branchName: s.branchName || null,
14068
- workspace: s.workspace || null
14907
+ workspace: s.workspace || null,
14908
+ packetCount: s.packetCount == null ? null : s.packetCount,
14909
+ requiredCount: s.requiredCount == null ? null : s.requiredCount,
14910
+ openCount: s.openCount == null ? null : s.openCount,
14911
+ command: s.command || null,
14912
+ updatedAt: formatTime(s.updatedAtMs || s.updatedAt || s.at || s.createdAt)
14069
14913
  };
14070
14914
  }
14071
14915
 
14916
+ /**
14917
+ * The next COMMAND, not the smell restated. This list previously mapped each signal to its own summary
14918
+ * sentence, so "Next actions" contained no action; and it filtered on severity <= 2, which permanently
14919
+ * excluded `no_required_evidence` - the one signal that explains why an envelope never closes.
14920
+ * A signal that knows its own fix is actionable at any severity.
14921
+ */
14922
+ function buildActivityNextActions(smells) {
14923
+ const seen = new Set();
14924
+ return (smells || [])
14925
+ .filter((smell) => smell && (smell.command || smellSeverityRank(smell) <= 2))
14926
+ .sort((left, right) => {
14927
+ const command = (right.command ? 1 : 0) - (left.command ? 1 : 0);
14928
+ if (command) return command;
14929
+ return smellSeverityRank(left) - smellSeverityRank(right);
14930
+ })
14931
+ .map((smell) => {
14932
+ const title = smell.title || smell.envelopeSummary || smell.testName ||
14933
+ (Array.isArray(smell.workspaces) ? smell.workspaces.join(', ') : null);
14934
+ return {
14935
+ type: smell.type || 'signal',
14936
+ severity: smell.severity || 'info',
14937
+ why: (title ? title + ' — ' : '') + (smell.summary || 'inspect this signal'),
14938
+ command: smell.command || null
14939
+ };
14940
+ })
14941
+ .filter((action) => {
14942
+ // Dedupe on the COMMAND when there is one: two different signals about one envelope that resolve to
14943
+ // the same next move are one next move.
14944
+ const key = action.command ? 'cmd|' + action.command : action.type + '|' + action.why;
14945
+ if (seen.has(key)) return false;
14946
+ seen.add(key);
14947
+ return true;
14948
+ })
14949
+ .slice(0, 6);
14950
+ }
14951
+
14072
14952
  function compactActivityInspect(response) {
14073
14953
  const counts = response.counts || {};
14074
14954
  const smells = sortedActivitySmells(response.smells);
14075
14955
  const lanes = Array.isArray(response.lanes) ? response.lanes : [];
14076
14956
  const envelopes = Array.isArray(response.envelopes) ? response.envelopes : [];
14077
14957
  const testRuns = Array.isArray(response.testRuns) ? response.testRuns : [];
14078
- const nextActions = smells
14079
- .filter((smell) => smellSeverityRank(smell) <= 2)
14080
- .slice(0, 5)
14081
- .map((smell) => (smell.type || 'signal') + ': ' + (smell.summary || 'inspect this signal'));
14958
+ const nextActions = buildActivityNextActions(smells);
14082
14959
 
14083
14960
  return {
14084
14961
  success: response.success,
@@ -14134,16 +15011,43 @@ function printActivityInspectSummary(response) {
14134
15011
  ? ' for ' + (response.subjectUser.email || response.subjectUser.name || ('user ' + response.subjectUser.id))
14135
15012
  : '';
14136
15013
  console.log('Activity inspection' + subject + ' — ' + response.dataMode + ' lane');
15014
+ // A scope of several hundred accounts printed as one line is not a scope statement, it is a wall. Say how
15015
+ // many, show the head, and point at --json for the rest.
15016
+ const scopeIds = response.accountIds || [];
15017
+ const scopeIdText = scopeIds.length > 12
15018
+ ? scopeIds.slice(0, 12).join(', ') + ' … (+' + (scopeIds.length - 12) + ' more, --json for all)'
15019
+ : (scopeIds.join(', ') || String(response.accountId));
14137
15020
  console.log('Scope: ' + (response.accountName || ('account ' + response.accountId)) +
14138
15021
  ' (' + response.accountId + '), ' + (response.scope || 'self') +
14139
- ' · accounts ' + ((response.accountIds || []).join(', ') || response.accountId));
15022
+ ' · ' + (scopeIds.length || 1) + ' account(s): ' + scopeIdText);
15023
+ if (response.filters && (response.filters.workspace || response.filters.branchName || response.filters.workstreamId)) {
15024
+ console.log('Filters: ' + [
15025
+ response.filters.workspace ? 'workspace ' + response.filters.workspace : null,
15026
+ response.filters.branchName ? 'branch ' + response.filters.branchName : null,
15027
+ response.filters.workstreamId ? 'workstream ' + response.filters.workstreamId : null
15028
+ ].filter(Boolean).join(' · ') + ' (evidence below is narrowed to it)');
15029
+ }
15030
+ if (Array.isArray(response.relatedAccounts) && response.relatedAccounts.length) {
15031
+ const related = response.relatedAccounts;
15032
+ const shown = related.slice(0, 12).map((row) =>
15033
+ row.id + ' ' + (row.name || '') + (row.role ? ' (' + row.role + ')' : '')).join('; ');
15034
+ console.log('Related accounts: ' + shown +
15035
+ (related.length > 12 ? '; … (+' + (related.length - 12) + ' more, --json for all)' : ''));
15036
+ }
14140
15037
  const counts = response.counts || {};
14141
15038
  console.log('Counts: ' +
14142
15039
  (counts.agents || 0) + ' agent(s), ' +
14143
15040
  (counts.lanes || 0) + ' lane(s), ' +
14144
15041
  (counts.verificationEnvelopes || 0) + ' envelope(s), ' +
14145
15042
  (counts.testRuns || 0) + ' test run(s), ' +
14146
- (counts.corpusResults || 0) + ' corpus result(s)');
15043
+ (counts.corpusResults || 0) + ' corpus result(s)' +
15044
+ (counts.distinctStagedEntries != null && counts.distinctStagedEntries !== counts.stagedEntries
15045
+ ? ', ' + counts.stagedEntries + ' staged entries across lanes (' + counts.distinctStagedEntries + ' distinct)'
15046
+ : (counts.stagedEntries ? ', ' + counts.stagedEntries + ' staged entries' : '')));
15047
+ if (response.envelopeScope === 'all-data-lanes') {
15048
+ console.log('Note: test runs and corpus results are narrowed to the ' + response.dataMode +
15049
+ ' lane; envelopes are listed across BOTH lanes (stage/sync evidence is lane-independent).');
15050
+ }
14147
15051
 
14148
15052
  const smells = sortedActivitySmells(response.smells);
14149
15053
  console.log('');
@@ -14153,6 +15057,18 @@ function printActivityInspectSummary(response) {
14153
15057
  console.log('Top smells (' + smells.length + '):');
14154
15058
  smells.slice(0, 20).forEach((smell) => {
14155
15059
  console.log(' [' + (smell.severity || 'info') + '] ' + (smell.type || 'signal') + ': ' + smell.summary);
15060
+ // The numbers were in the JSON and not on the line, so twenty signals read as three sentences repeated.
15061
+ const facts = [
15062
+ smell.packetCount != null ? smell.packetCount + ' packet(s)' : null,
15063
+ smell.currentFailureCount != null ? smell.currentFailureCount + ' current failure(s)' : null,
15064
+ smell.requiredCount != null ? smell.requiredCount + ' requirement(s)' : null,
15065
+ smell.openCount != null ? smell.openCount + ' open' : null,
15066
+ smell.recentRuns != null ? smell.recentRuns + ' recent run(s)' : null,
15067
+ smell.failedRuns != null ? smell.failedRuns + ' failed' : null,
15068
+ smell.stagedCount != null ? smell.stagedCount + ' staged' : null,
15069
+ Array.isArray(smell.workspaces) ? smell.workspaces.join(', ') : null
15070
+ ].filter(Boolean);
15071
+ if (facts.length) console.log(' ' + facts.join(' · '));
14156
15072
  const pivots = [
14157
15073
  smell.accountId ? 'account ' + smell.accountId : null,
14158
15074
  smell.laneId ? 'lane ' + smell.laneId : null,
@@ -14165,12 +15081,36 @@ function printActivityInspectSummary(response) {
14165
15081
  if (pivots.length) console.log(' ' + pivots.join(' · '));
14166
15082
  });
14167
15083
  if (smells.length > 20) console.log(' ...' + (smells.length - 20) + ' more; rerun with --json');
14168
- const actions = compactActivityInspect(response).nextActions;
14169
- if (actions.length) {
14170
- console.log('');
14171
- console.log('Next actions:');
14172
- actions.forEach((action) => console.log(' - ' + action));
14173
- }
15084
+ }
15085
+ const actions = buildActivityNextActions(smells);
15086
+ if (actions.length) {
15087
+ console.log('');
15088
+ console.log('Next actions:');
15089
+ actions.forEach((action) => {
15090
+ console.log(' - ' + action.why);
15091
+ if (action.command) console.log(' ' + action.command);
15092
+ });
15093
+ }
15094
+
15095
+ // "2 agent(s)" with no names is the shape of the question, not the answer: an agent asking who else is in
15096
+ // this repo right now could read the count and still have to go find them. The payload already carries the
15097
+ // directory, branch, workspace and last-seen of each.
15098
+ const liveAgents = response.agents || [];
15099
+ if (liveAgents.length) {
15100
+ console.log('');
15101
+ console.log('Live agents here:');
15102
+ liveAgents.slice(0, 10).forEach((agent) => {
15103
+ const who = agent.label || agent.userEmail || agent.agentId || 'unknown agent';
15104
+ const where = [agent.branch ? 'branch ' + agent.branch : null,
15105
+ 'workspace ' + (agent.workspace || 'shared'),
15106
+ agent.dataMode ? 'data ' + agent.dataMode : null].filter(Boolean).join(' · ');
15107
+ const seen = agent.lastSeenAtMs ? ' · seen ' + formatTime(agent.lastSeenAtMs) : '';
15108
+ console.log(' - ' + who + ' [' + (agent.state || 'unknown') + ']' +
15109
+ (agent.source === 'implicit' ? ' (implicit)' : '') + seen);
15110
+ console.log(' ' + where);
15111
+ if (agent.workingDirectory) console.log(' ' + agent.workingDirectory);
15112
+ });
15113
+ if (liveAgents.length > 10) console.log(' ...' + (liveAgents.length - 10) + ' more agent(s)');
14174
15114
  }
14175
15115
 
14176
15116
  const lanes = response.lanes || [];
@@ -14195,8 +15135,14 @@ function printActivityInspectSummary(response) {
14195
15135
  console.log('');
14196
15136
  console.log('Recent verification envelopes:');
14197
15137
  envelopes.slice(0, 8).forEach((env) => {
15138
+ const world = [env.branchName || null, env.workspace ? 'ws:' + env.workspace : null, env.dataMode || null]
15139
+ .filter(Boolean).join('/');
14198
15140
  console.log(' - ' + (env.status || 'unknown') + ' · ' + (env.packetCount || 0) + ' packet(s) · ' +
15141
+ (world ? '[' + world + '] · ' : '') +
14199
15142
  (env.summary || env.envelopeId) + ' · ' + formatTime(env.updatedAtMs || env.lastPacketAtMs || env.createdAt));
15143
+ if (env.noRequiredEvidence) {
15144
+ console.log(' evidence log — no verdict requested; add a claim only if someone needs one: remits-cli verify claim <id> --text "..." --envelope ' + env.envelopeId);
15145
+ }
14200
15146
  if (env.currentFailureCount) console.log(' current failures: ' + env.currentFailureCount);
14201
15147
  });
14202
15148
  }
@@ -14237,6 +15183,133 @@ function printActivityInspectSummary(response) {
14237
15183
  console.log('Full structured payload: remits-cli activity inspect --json');
14238
15184
  }
14239
15185
 
15186
+ function readWorkstreamIndex(cwd, workstreamId) {
15187
+ const id = normalizeWorkstreamId(workstreamId);
15188
+ if (!id) return null;
15189
+ const file = workstreamFile(cwd, id);
15190
+ if (!fs.existsSync(file)) return null;
15191
+ try {
15192
+ return JSON.parse(fs.readFileSync(file, 'utf8'));
15193
+ } catch (_) {
15194
+ return null;
15195
+ }
15196
+ }
15197
+
15198
+ function listWorkstreamIndexes(cwd) {
15199
+ const paths = ensureLocalState(cwd);
15200
+ if (!fs.existsSync(paths.workstreamsDir)) return [];
15201
+ return fs.readdirSync(paths.workstreamsDir, { withFileTypes: true })
15202
+ .filter((entry) => entry.isFile() && entry.name.endsWith('.json'))
15203
+ .map((entry) => readWorkstreamIndex(cwd, entry.name.replace(/\.json$/, '')))
15204
+ .filter(Boolean)
15205
+ .sort((a, b) => String(b.updatedAt || '').localeCompare(String(a.updatedAt || '')));
15206
+ }
15207
+
15208
+ async function evidenceCommand(flags) {
15209
+ const cwd = process.cwd();
15210
+ ensureLocalState(cwd);
15211
+ const limit = Math.max(1, Math.min(parseInt(flags.limit || flags.max || '40', 10) || 40, 500));
15212
+ const entries = readEvidenceTrail(cwd, limit);
15213
+ if (flagEnabled(flags.json)) {
15214
+ console.log(JSON.stringify({ success: true, actor: resolveLocalActor().id, count: entries.length, entries }, null, 2));
15215
+ return entries;
15216
+ }
15217
+ const paths = localStatePaths(cwd);
15218
+ console.log('Evidence trail for actor ' + paths.actor.id + ' (' + paths.evidenceFile + ')');
15219
+ if (!entries.length) {
15220
+ console.log('Nothing recorded yet. Every stage, test, token, tool and sync appends one world-stamped line here,');
15221
+ console.log('with no envelope and nothing to start.');
15222
+ return entries;
15223
+ }
15224
+ // Grouped by WORLD, because "what have I proven" is only answerable per world. An agent that ran the same
15225
+ // suite in two lanes has two different facts, and a flat list hides exactly that.
15226
+ const groups = new Map();
15227
+ entries.forEach((entry) => {
15228
+ const key = describeEvidenceWorld(entry.world);
15229
+ if (!groups.has(key)) groups.set(key, []);
15230
+ groups.get(key).push(entry);
15231
+ });
15232
+ groups.forEach((rows, world) => {
15233
+ console.log('');
15234
+ console.log(world);
15235
+ const byClaim = new Map();
15236
+ rows.forEach((row) => {
15237
+ const key = (row.type || 'unknown') + '|' + (row.claim || row.type || 'evidence') + '|' + evidenceOutcome(row);
15238
+ if (!byClaim.has(key)) byClaim.set(key, { row, count: 0 });
15239
+ const bucket = byClaim.get(key);
15240
+ bucket.count += 1;
15241
+ bucket.row = row;
15242
+ });
15243
+ Array.from(byClaim.values())
15244
+ .sort((left, right) => right.count - left.count)
15245
+ .slice(0, 15)
15246
+ .forEach((bucket) => {
15247
+ const row = bucket.row;
15248
+ const layer = row.world && row.world.sourceLayer ? ' source ' + row.world.sourceLayer : '';
15249
+ console.log(' ' + (bucket.count > 1 ? bucket.count + ' x ' : '') +
15250
+ evidenceOutcomeLabel(row) + (row.type || 'unknown') + ': ' + (row.claim || '(no claim)') +
15251
+ ' ' + formatTime(row.ts) + layer +
15252
+ (Array.isArray(row.claims) && row.claims.length ? ' [claims ' + row.claims.join(', ') + ']' : '') +
15253
+ (row.envelopeId ? ' [envelope ' + row.envelopeId.slice(0, 8) + ']' : ''));
15254
+ });
15255
+ if (byClaim.size > 15) console.log(' ...' + (byClaim.size - 15) + ' more distinct entries; --json for all');
15256
+ });
15257
+ console.log('');
15258
+ console.log(entries.length + ' entry(s) shown. This is a log, not a verdict: nothing here is outstanding.');
15259
+ console.log('If someone needs a checkable verdict, start an envelope and name what must be true:');
15260
+ console.log(' remits-cli verify start --summary "..." && remits-cli verify claim <id> --text "..."');
15261
+ return entries;
15262
+ }
15263
+
15264
+ async function workstreamCommand(flags, subcommand) {
15265
+ const cwd = process.cwd();
15266
+ ensureLocalState(cwd);
15267
+ const action = subcommand || 'status';
15268
+ if (action !== 'status') throw new Error('Unknown workstream subcommand: ' + action);
15269
+ const requested = resolveWorkstreamId(cwd, flags, { workspace: flags.workspace });
15270
+ let rows = requested ? [readWorkstreamIndex(cwd, requested)].filter(Boolean) : listWorkstreamIndexes(cwd);
15271
+ if (flags.workspace) {
15272
+ const normalizedWorkspace = normalizeWorkspace(flags.workspace);
15273
+ rows = rows.filter((row) => {
15274
+ if (normalizeWorkspace(row.roles && row.roles.workspace) === normalizedWorkspace) return true;
15275
+ return (row.artifacts || []).some((artifact) => normalizeWorkspace(artifact.workspace || (artifact.world && artifact.world.workspace)) === normalizedWorkspace);
15276
+ });
15277
+ }
15278
+ if (flagEnabled(flags.json)) {
15279
+ console.log(JSON.stringify({ success: true, workstreams: rows }, null, 2));
15280
+ return;
15281
+ }
15282
+ if (!rows.length) {
15283
+ console.log('No local workstream index found.');
15284
+ console.log('Start one implicitly with a workspace, or pass --workstream <id> / REMITS_WORKSTREAM_ID while running tool or verify commands.');
15285
+ return;
15286
+ }
15287
+ rows.forEach((row) => {
15288
+ console.log('Workstream:', row.workstreamId);
15289
+ console.log('Updated:', row.updatedAt || 'unknown');
15290
+ console.log('Actors:', (row.actors || []).join(', ') || 'none recorded');
15291
+ const roleParts = workstreamWorldParts(row.roles);
15292
+ if (roleParts.length) console.log('Latest world:', roleParts.join(' · '));
15293
+ // A campaign that spanned several worlds says so. One blended line would describe a run that never
15294
+ // happened, and "which world proved this?" is the question the whole surface exists to answer.
15295
+ const worlds = Array.isArray(row.worlds) ? row.worlds : [];
15296
+ if (worlds.length > 1) {
15297
+ console.log('Worlds in this workstream: ' + worlds.length);
15298
+ worlds.slice(-6).forEach((world) => {
15299
+ const parts = workstreamWorldParts(world);
15300
+ if (parts.length) console.log(' - ' + parts.join(' · '));
15301
+ });
15302
+ }
15303
+ const artifacts = Array.isArray(row.artifacts) ? row.artifacts.slice(-12) : [];
15304
+ console.log('Artifacts:', artifacts.length ? '' : 'none recorded');
15305
+ artifacts.forEach((artifact) => {
15306
+ const label = [artifact.type, artifact.tool, artifact.callId, artifact.envelopeId, artifact.packetType].filter(Boolean).join(' ');
15307
+ console.log(' - ' + (artifact.recordedAt || '') + ' ' + label);
15308
+ if (artifact.path) console.log(' ' + artifact.path);
15309
+ });
15310
+ });
15311
+ }
15312
+
14240
15313
  /**
14241
15314
  * The live work map: which repositories are being edited, by whom, and who else is around.
14242
15315
  *
@@ -15166,12 +16239,16 @@ function printCorpusHelp() {
15166
16239
  }
15167
16240
 
15168
16241
  function printToolHelp() {
15169
- console.log('Usage: remits-cli tool --name <toolName> [--base-url URL] [--account-id ID] [--branch BRANCH] [--input "{...}"|--input-file file.json] [--data-mode test|prod] [--variant-branch NAME|none] [--timeout-ms 60000] [--async true --wait true]');
16242
+ console.log('Usage: remits-cli tool --name <toolName> [--base-url URL] [--account-id ID] [--as-account ID] [--target-account ID] [--branch BRANCH] [--input "{...}"|--input-file file.json] [--data-mode test|prod] [--variant-branch NAME|none] [--timeout-ms 60000] [--async true --wait true]');
15170
16243
  console.log(' remits-cli tool status --call-id <callId> [--base-url URL] [--account-id ID] [--data-mode test|prod]');
15171
16244
  console.log('');
15172
16245
  console.log('Hierarchy scope (read/discovery tools) — you do NOT need the owning child account id:');
15173
16246
  console.log(' --scope self|children|hierarchy expand from the repo account when searching');
15174
16247
  console.log(' --target-account-id ID the exact account to act on (required by writes)');
16248
+ console.log(' --as-account ID execute as this reachable account; subscriber branch edges apply');
16249
+ console.log(' --target-account ID the account the tool acts ON (sent as input.accountId and');
16250
+ console.log(' input.targetAccountId when --input omits them). It does NOT change');
16251
+ console.log(' component resolution — add --as-account ID for that.');
15175
16252
  console.log(' --account-ids 1,2,3 an explicit bounded list of owner accounts');
15176
16253
  console.log(' --anchor-account-id ID disambiguate the path for a multi-parent account');
15177
16254
  console.log(' These are merged into --input; a value already in --input wins.');
@@ -15179,17 +16256,68 @@ function printToolHelp() {
15179
16256
  console.log('Examples:');
15180
16257
  console.log(' remits-cli tool --name mcp_firestore_search --input-file query.json --data-mode prod');
15181
16258
  console.log(' remits-cli tool --name mcp_firestore_search --input \'{"collection":"statements","documentId":"1234"}\' --scope children');
15182
- console.log(' remits-cli tool --name mcp_run_action --input \'{"accountId":49,"actionId":200,"executionMode":"async"}\' --data-mode prod');
16259
+ console.log(' remits-cli tool --name mcp_run_action --account-id 21 --as-account 36 --target-account 36 --input \'{"actionId":200,"executionMode":"async"}\' --data-mode prod');
15183
16260
  console.log('');
15184
16261
  console.log('For mcp_run_action and mcp_run_agent, prefer the tool\'s own executionMode:"async" and poll with that tool\'s controlAction:"status".');
15185
16262
  }
15186
16263
 
15187
16264
  function printToolsHelp() {
15188
- console.log('Usage: remits-cli tools [--base-url URL] [--account-id ID] [--branch BRANCH] [--data-mode test|prod] [--variant-branch NAME|none]');
16265
+ console.log('Usage: remits-cli tools [--base-url URL] [--account-id ID] [--as-account ID] [--branch BRANCH] [--data-mode test|prod] [--variant-branch NAME|none]');
15189
16266
  console.log('');
15190
16267
  console.log('Refreshes .remits-cli/tools/tools.json for the current account/branch/data-mode context.');
15191
16268
  }
15192
16269
 
16270
+ function printGuidesHelp() {
16271
+ console.log('Usage: remits-cli guides sync [--all] [--base-url URL] [--json]');
16272
+ console.log('');
16273
+ console.log('Refreshes front-stage guides from the authenticated platform.');
16274
+ console.log(' --all updates every indexed account checkout on this machine.');
16275
+ console.log(' without --all, run from an account repo to refresh only that checkout.');
16276
+ }
16277
+
16278
+ async function guidesCommand(flags, subcommand) {
16279
+ const mode = subcommand || 'sync';
16280
+ if (mode === 'help' || mode === '--help' || flagEnabled(flags.help)) {
16281
+ printGuidesHelp();
16282
+ return;
16283
+ }
16284
+ if (mode !== 'sync' && mode !== 'refresh' && mode !== 'update') {
16285
+ throw new Error('Unknown guides command: ' + mode + '. Use: remits-cli guides sync [--all]');
16286
+ }
16287
+
16288
+ if (flagEnabled(flags.all)) {
16289
+ const result = await syncGuidesAcrossIndexedRepos({
16290
+ baseUrl: flags['base-url'] || null,
16291
+ rescan: !flagEnabled(flags['no-rescan'])
16292
+ });
16293
+ if (flagEnabled(flags.json)) {
16294
+ console.log(JSON.stringify(result, null, 2));
16295
+ return;
16296
+ }
16297
+ console.log('Guides sync: ' + guideSyncSummaryLine(result));
16298
+ for (const repo of result.repos || []) {
16299
+ if (repo.skipped) {
16300
+ console.log(' skipped account ' + (repo.accountId || '?') + ': ' + repo.reason + ' ' + repo.directory);
16301
+ } else if ((repo.updated || []).length || (repo.removed || []).length) {
16302
+ console.log(' updated account ' + repo.accountId + ': ' +
16303
+ ((repo.updated || []).length + (repo.removed || []).length) + ' file(s) ' + repo.directory);
16304
+ }
16305
+ }
16306
+ return;
16307
+ }
16308
+
16309
+ if (!loadAccountInfo(process.cwd())) {
16310
+ throw new Error('Run `remits-cli guides sync` from an account repo, or pass --all.');
16311
+ }
16312
+ const before = Date.now();
16313
+ await ensureGuidesSynced(process.cwd(), flags);
16314
+ if (flagEnabled(flags.json)) {
16315
+ console.log(JSON.stringify({ success: true, directory: process.cwd(), elapsedMs: Date.now() - before }, null, 2));
16316
+ return;
16317
+ }
16318
+ console.log('Guides synced for ' + process.cwd());
16319
+ }
16320
+
15193
16321
  function printTokenHelp() {
15194
16322
  console.log('Usage: remits-cli token [--base-url URL] [--branch BRANCH] [--path embeddable/path] [--data-mode test|prod] [--as-account ID] [--variant-branch NAME|none]');
15195
16323
  console.log(' remits-cli token inspect --token <token|tokenKey|URL> [--base-url URL] [--account-id ID]');
@@ -15208,6 +16336,9 @@ function printVerifyHelp() {
15208
16336
  console.log(' remits-cli verify use <envelopeId>');
15209
16337
  console.log(' remits-cli verify current');
15210
16338
  console.log(' remits-cli verify clear [--all]');
16339
+ console.log(' remits-cli verify claim <id> --text "what must be true" [--test "<suite>"] [--packet-type TYPE]');
16340
+ console.log(' # declares a contract in ONE command when someone needs a verdict; no-contract envelopes are evidence logs');
16341
+ console.log(' # prove it by adding --claim <id> to any verify test/token/tool/stage/attach command');
15211
16342
  console.log(' remits-cli verify show [--envelope ID] [--summary|--compact] [--json]');
15212
16343
  console.log(' remits-cli verify status [--envelope ID]');
15213
16344
  console.log(' remits-cli verify report [--envelope ID]');
@@ -15276,7 +16407,7 @@ async function main() {
15276
16407
  try { updateAccountRepoIndex(process.cwd()); } catch (_) { /* not in account repo */ }
15277
16408
  // Auto-start the background service if not already running.
15278
16409
  // Skip for lifecycle subcommands and help.
15279
- const isLifecycleCmd = command === 'listen' || command === 'start' || command === 'stop' || command === 'status' || command === 'whoami' || command === 'doctor' || command === 'agent' || command === 'ticket';
16410
+ const isLifecycleCmd = command === 'listen' || command === 'start' || command === 'stop' || command === 'status' || command === 'whoami' || command === 'doctor' || command === 'agent' || command === 'ticket' || command === 'guides';
15280
16411
  const isHelpCmd = !command || command === 'help' || command === '--help' || wantsHelp;
15281
16412
  if (!wantsJson && !isHelpCmd && command === 'components') {
15282
16413
  const commandName = 'components ' + (subcommand || 'command');
@@ -15313,22 +16444,25 @@ async function main() {
15313
16444
  console.log(' remits-cli agent status [--state idle|working|paused] [--ticket ID] [--activity "..."]');
15314
16445
  console.log(' remits-cli agent list [--account-id ID] # who else is available');
15315
16446
  console.log(' remits-cli agent map [--account-ids 1,4] [--json] # who is EDITING what, where, right now');
15316
- console.log(' remits-cli activity inspect [--account-id ID] [--scope self|children] [--user-email E] [--summary|--compact] [--json]');
16447
+ console.log(' remits-cli activity inspect [--account-id ID] [--scope self|children|related] [--workspace NAME] [--workstream ID] [--user-email E] [--summary|--compact] [--json]');
16448
+ console.log(' remits-cli workstream status [--workstream ID|--workspace NAME] [--json]');
15317
16449
  console.log(' remits-cli agent release # stop receiving tickets now');
15318
16450
  console.log(' remits-cli start [--foreground true] [--port 8787] # human dashboard in the browser');
15319
16451
  console.log(' remits-cli stop');
15320
16452
  console.log(' remits-cli status [--base-url URL] [--account-id ID] [--data-mode test|prod] [--json]');
15321
16453
  console.log(' remits-cli doctor local-state [--json] # actor/workspace/local state hygiene');
15322
16454
  console.log(' remits-cli whoami [--base-url URL] [--account-id ID] [--data-mode test|prod] [--json]');
16455
+ console.log(' remits-cli guides sync [--all] [--base-url URL] [--json] # refresh front-stage guides');
15323
16456
  console.log(' remits-cli listen [stop|status] [--foreground true] # compatibility alias');
15324
16457
  console.log(' remits-cli data-mode [set test|prod]');
15325
16458
  console.log(' remits-cli install --skills [--target codex|claude|gemini|all] [--overwrite true]');
15326
- console.log(' remits-cli tools [--base-url URL] [--account-id ID] [--branch BRANCH] [--data-mode test|prod] [--variant-branch NAME|none]');
15327
- console.log(' remits-cli tool --name <toolName> [--base-url URL] [--branch BRANCH] [--input \"{...}\"|--input-file file.json] [--data-mode test|prod] [--scope self|children|hierarchy] [--target-account-id ID] [--account-ids 1,2,3] [--variant-branch NAME|none] [--timeout-ms 60000] [--async true --wait true]');
16459
+ console.log(' remits-cli tools [--base-url URL] [--account-id ID] [--as-account ID] [--branch BRANCH] [--data-mode test|prod] [--variant-branch NAME|none]');
16460
+ console.log(' remits-cli tool --name <toolName> [--base-url URL] [--account-id ID] [--as-account ID] [--target-account ID] [--branch BRANCH] [--input \"{...}\"|--input-file file.json] [--data-mode test|prod] [--scope self|children|hierarchy] [--account-ids 1,2,3] [--variant-branch NAME|none] [--timeout-ms 60000] [--async true --wait true]');
15328
16461
  console.log(' remits-cli tool status --call-id <callId> [--base-url URL] [--account-id ID] [--data-mode test|prod]');
15329
16462
  console.log(' remits-cli workspace [show|use <name>|use --auto|clear] # isolate staging when several agents share a repo');
15330
- console.log(' remits-cli verify start --summary "..." [--manifest file.json] # start a verification envelope');
15331
- console.log(' remits-cli verify [current|list|use|clear|show|status|report|attach|abandon|supersede|test|token|stage|sync|tool]');
16463
+ console.log(' remits-cli evidence [--limit N] [--json] # what you have run and in which world (always recorded)');
16464
+ console.log(' remits-cli verify start --summary "..." [--manifest file.json] # OPTIONAL: ask for a checkable verdict');
16465
+ console.log(' remits-cli verify [current|list|use|clear|show|status|report|claim|attach|abandon|supersede|test|token|stage|sync|tool]');
15332
16466
  console.log(' remits-cli components stage [--workset|--changed-only] [--base-url URL] [--account-id ID] [--branch BRANCH] [--workspace NAME] [--data-mode test|prod] [--json|--verbose]');
15333
16467
  console.log(' remits-cli components status [--base-url URL] [--account-id ID] [--branch BRANCH] [--workspace NAME] [--component-type TYPE --component-id ID] [--json|--verbose]');
15334
16468
  console.log(' remits-cli components lanes [--base-url URL] [--account-id ID] [--json]');
@@ -15407,6 +16541,11 @@ async function main() {
15407
16541
  process.exit(0);
15408
16542
  }
15409
16543
 
16544
+ if (wantsHelp && command === 'guides') {
16545
+ printGuidesHelp();
16546
+ process.exit(0);
16547
+ }
16548
+
15410
16549
  if (wantsHelp && command === 'token') {
15411
16550
  printTokenHelp();
15412
16551
  process.exit(0);
@@ -15427,6 +16566,11 @@ async function main() {
15427
16566
  return;
15428
16567
  }
15429
16568
 
16569
+ if (command === 'guides') {
16570
+ await guidesCommand(args, subcommand);
16571
+ return;
16572
+ }
16573
+
15430
16574
  if (command === 'components' && (subcommand === 'push' || subcommand === 'stage')) {
15431
16575
  if (subcommand === 'push' && !args.mode) {
15432
16576
  args.mode = 'push';
@@ -15539,11 +16683,16 @@ async function main() {
15539
16683
 
15540
16684
  if (command === 'activity') {
15541
16685
  if (wantsHelp || !subcommand) {
15542
- console.log('Usage: remits-cli activity inspect [--account-id ID] [--scope self|children] [--user-id ID|--user-email EMAIL] [--limit N] [--summary|--compact] [--json]');
16686
+ console.log('Usage: remits-cli activity inspect [--account-id ID] [--scope self|children|related] [--workspace NAME] [--branch NAME] [--workstream ID] [--user-id ID|--user-email EMAIL] [--limit N] [--summary|--compact] [--json]');
15543
16687
  console.log('');
15544
16688
  console.log('Read-only workstream inspection for agent/operator review. Joins current staging lanes,');
15545
16689
  console.log('verification envelopes, durable test runs, corpus measurements, live agent presence,');
15546
16690
  console.log('and heuristic smell signals without changing any lane or checkout.');
16691
+ console.log('');
16692
+ console.log('--scope related also follows component owners, staging lanes and verification packet worlds');
16693
+ console.log('out of the hierarchy. --workspace / --branch / --workstream NARROW it: the account set becomes');
16694
+ console.log('the accounts with matching evidence (plus this account and its component owners), and the');
16695
+ console.log('lanes, envelopes, test runs and corpus results are filtered to that workstream.');
15547
16696
  return;
15548
16697
  }
15549
16698
  if (subcommand === 'inspect') {
@@ -15553,6 +16702,39 @@ async function main() {
15553
16702
  throw new Error('Unknown activity subcommand: ' + subcommand);
15554
16703
  }
15555
16704
 
16705
+ if (command === 'evidence') {
16706
+ if (wantsHelp) {
16707
+ console.log('Usage: remits-cli evidence [--limit N] [--json]');
16708
+ console.log('');
16709
+ console.log('What you have actually run, and in which world. Every stage, test, token, tool and sync');
16710
+ console.log('appends one world-stamped line automatically - there is nothing to start and nothing to');
16711
+ console.log('satisfy. Grouped by world, because the same suite run in two lanes is two different facts.');
16712
+ console.log('');
16713
+ console.log('This is per-actor, so agents sharing a checkout never read each other\'s trail.');
16714
+ console.log('Use `remits-cli verify` only when someone needs a checkable VERDICT on top of this.');
16715
+ return;
16716
+ }
16717
+ await evidenceCommand(args);
16718
+ return;
16719
+ }
16720
+
16721
+ if (command === 'workstream') {
16722
+ if (wantsHelp) {
16723
+ console.log('Usage: remits-cli workstream status [--workstream ID|--workspace NAME] [--json]');
16724
+ console.log('');
16725
+ console.log('Shows the local logical proof campaign index across actor-scoped state. It references');
16726
+ console.log('tool response files, verification packets, actors and account/world roles without copying payloads.');
16727
+ console.log('');
16728
+ console.log('The id is stamped onto every verification packet world, so the same value works server-side:');
16729
+ console.log(' remits-cli activity inspect --scope related --workstream <id>');
16730
+ console.log('This is NOT a support ticket\'s --workstream, which is an account\'s own routing label matched');
16731
+ console.log('by `agent serve --serves`. Same flag name, unrelated namespaces.');
16732
+ return;
16733
+ }
16734
+ await workstreamCommand(args, subcommand);
16735
+ return;
16736
+ }
16737
+
15556
16738
  if (command === 'ticket') {
15557
16739
  if (wantsHelp || !subcommand) {
15558
16740
  printTicketHelp();