@remits/remits-cli 0.1.125 → 0.1.127

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
@@ -1,5 +1,27 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ /*
4
+ ## Table of Contents
5
+
6
+ - L22 Runtime Bootstrap And Shared State
7
+ - L116 Sessions, Account Resolution, And Production Guards
8
+ - L595 Local State, Workspaces, And Verification Evidence
9
+ - L1296 Account Repos, Guide Sync, And Platform Repo
10
+ - L1954 Component Discovery And HTTP Logging
11
+ - L2444 Skill Delivery And TOC Resolution
12
+ - L2754 Auth And Component Staging
13
+ - L3352 Component Summaries, Status, And Sync Gates
14
+ - L5107 Branches, Promotion, Commit, And Test Runs
15
+ - L6231 Tokens, Tools, Verification, And Config
16
+ - L7141 Service Dashboard And WebSocket Listener
17
+ - L9236 Agent And Ticket Workflows
18
+ - L12294 Help, Auto Update, And Command Dispatch
19
+ */
20
+
21
+ /*
22
+ ## Runtime Bootstrap And Shared State
23
+ */
24
+
3
25
  const fs = require('fs');
4
26
  const path = require('path');
5
27
  const os = require('os');
@@ -90,6 +112,10 @@ const REPEATABLE_FLAGS = new Set(['expected-removed', 'expectedRemoved', 'names'
90
112
  // a flag, never a value, and these aliases map it to its long name. Values like `-5` or `-abc` are untouched.
91
113
  const SHORT_FLAG_ALIASES = { m: 'message', h: 'help' };
92
114
 
115
+ /*
116
+ ## Sessions, Account Resolution, And Production Guards
117
+ */
118
+
93
119
  function isFlagToken(token) {
94
120
  return typeof token === 'string' && (token.startsWith('--') || /^-[a-zA-Z]$/.test(token));
95
121
  }
@@ -565,6 +591,10 @@ function resolveDataMode(flags, session) {
565
591
  return DEFAULT_DATA_MODE;
566
592
  }
567
593
 
594
+ /*
595
+ ## Local State, Workspaces, And Verification Evidence
596
+ */
597
+
568
598
  function ensureDir(dirPath) {
569
599
  if (!fs.existsSync(dirPath)) {
570
600
  fs.mkdirSync(dirPath, { recursive: true });
@@ -781,6 +811,42 @@ function writeToolResponseFile(cwd, callId, responsePayload) {
781
811
  return file;
782
812
  }
783
813
 
814
+ function fileEvidence(pathname) {
815
+ if (!pathname || !fs.existsSync(pathname)) return {};
816
+ const body = fs.readFileSync(pathname);
817
+ return {
818
+ responseFile: pathname,
819
+ bytes: body.length,
820
+ sha256: sha256(body)
821
+ };
822
+ }
823
+
824
+ function boundedToolExcerpt(data = {}) {
825
+ const result = data.result && typeof data.result === 'object' ? data.result : {};
826
+ const candidates = [
827
+ data.message,
828
+ data.toolMessage,
829
+ data.error,
830
+ result.message,
831
+ result.error,
832
+ result.reason
833
+ ].filter(Boolean).map((value) => String(value));
834
+ const excerpt = candidates.join(' | ').trim();
835
+ return excerpt ? excerpt.slice(0, 1200) : null;
836
+ }
837
+
838
+ function toolEvidenceSummary(data = {}, responseFile) {
839
+ return Object.assign({
840
+ callId: data.callId,
841
+ name: data.name,
842
+ async: data.async,
843
+ status: data.status,
844
+ toolSuccess: data.toolSuccess,
845
+ threadGroupingId: data.threadGroupingId,
846
+ message: boundedToolExcerpt(data)
847
+ }, fileEvidence(responseFile));
848
+ }
849
+
784
850
  function readToolResponseFile(cwd, callId) {
785
851
  if (!callId) return null;
786
852
  const paths = ensureLocalState(cwd);
@@ -1070,12 +1136,28 @@ function printVerificationEnvelope(envelope, fallbackEnvelopeId) {
1070
1136
  }
1071
1137
  }
1072
1138
 
1139
+ function printAcceptanceWarnings(envelope) {
1140
+ const warnings = envelope && envelope.acceptance && Array.isArray(envelope.acceptance.warnings)
1141
+ ? envelope.acceptance.warnings
1142
+ : [];
1143
+ if (!warnings.length) return;
1144
+ console.log('Manifest warnings:');
1145
+ warnings.forEach((warning) => {
1146
+ console.log('- ' + (warning.message || warning.code || JSON.stringify(warning)));
1147
+ });
1148
+ }
1149
+
1073
1150
  function buildCommandWorld(response, fallback = {}) {
1074
1151
  const source = (response && response.staging) || {};
1075
1152
  const resolution = (response && response.resolution) || {};
1153
+ const result = (response && response.result) || {};
1154
+ const laneHash = laneContentHashFrom(response) || fallback.laneContentHash || fallback.stagedOverlayHash;
1155
+ const executionAccountId = response && (response.executionAccountId || result.accountId || source.executionAccountId || response.accountId) ||
1156
+ resolution.executionAccountId ||
1157
+ fallback.executionAccountId;
1076
1158
  return {
1077
1159
  accountId: response && (response.accountId || response.ownerAccountId) || fallback.accountId,
1078
- executionAccountId: response && (response.executionAccountId || response.accountId) || resolution.executionAccountId || fallback.executionAccountId,
1160
+ executionAccountId,
1079
1161
  addressedAccountId: resolution.addressedAccountId || fallback.addressedAccountId,
1080
1162
  host: fallback.host,
1081
1163
  dataMode: response && response.dataMode || fallback.dataMode,
@@ -1083,12 +1165,26 @@ function buildCommandWorld(response, fallback = {}) {
1083
1165
  branchName: response && response.branchName || fallback.branchName,
1084
1166
  workspace: response && response.workspace !== undefined ? response.workspace : fallback.workspace,
1085
1167
  stagingLane: response && response.stagingLane || source.stagingLane || fallback.stagingLane,
1086
- componentBranch: resolution.componentBranch || response && response.variantBranch || fallback.componentBranch,
1168
+ laneContentHash: laneHash,
1169
+ stagedOverlayHash: laneHash,
1170
+ // `variantWorld.componentBranch` is the platform's answer for the run (the variant branch, else the
1171
+ // subscription); a bare `variantBranch` is null whenever the subscription decided.
1172
+ componentBranch: resolution.componentBranch ||
1173
+ response && ((response.variantWorld && response.variantWorld.componentBranch) || response.variantBranch) ||
1174
+ fallback.componentBranch,
1175
+ variantBranchSource: response && (response.variantBranchSource || (response.variantWorld && response.variantWorld.variantBranchSource)) || fallback.variantBranchSource,
1087
1176
  variantId: resolution.variantId || fallback.variantId,
1088
1177
  sourceLayer: fallback.sourceLayer || (source.testComponentSource === 'staged' ? 'staged' : undefined)
1089
1178
  };
1090
1179
  }
1091
1180
 
1181
+ function laneContentHashFrom(response) {
1182
+ if (!response) return null;
1183
+ const laneSummary = response.laneSummary || (response.staging && response.staging.laneSummary) ||
1184
+ (response.componentStatus && response.componentStatus.laneSummary);
1185
+ return laneSummary && laneSummary.contentHash ? laneSummary.contentHash : null;
1186
+ }
1187
+
1092
1188
  function normalizeName(name) {
1093
1189
  return name.replace(/([a-z])([A-Z])/g, '$1 $2').replace(/\s+/g, ' ').trim();
1094
1190
  }
@@ -1196,6 +1292,10 @@ function loadAccountInfo(cwd) {
1196
1292
  }
1197
1293
  }
1198
1294
 
1295
+ /*
1296
+ ## Account Repos, Guide Sync, And Platform Repo
1297
+ */
1298
+
1199
1299
  function readAccountRepoIndex() {
1200
1300
  if (!fs.existsSync(ACCOUNT_REPO_INDEX_FILE)) {
1201
1301
  return {};
@@ -1850,6 +1950,10 @@ function resolveSessionContext(cwd, flags) {
1850
1950
  };
1851
1951
  }
1852
1952
 
1953
+ /*
1954
+ ## Component Discovery And HTTP Logging
1955
+ */
1956
+
1853
1957
  /**
1854
1958
  * Record the FILE identity of a component (type + filename id, or type + filename stem) on an object without
1855
1959
  * sending it anywhere. Non-enumerable, so JSON payloads and the content hash never see it.
@@ -2336,6 +2440,10 @@ async function loggedGet(api, cwd, endpoint, params = {}) {
2336
2440
  }
2337
2441
  }
2338
2442
 
2443
+ /*
2444
+ ## Skill Delivery And TOC Resolution
2445
+ */
2446
+
2339
2447
  // ---------------------------------------------------------------------------
2340
2448
  // Skill delivery
2341
2449
  //
@@ -2465,6 +2573,14 @@ function resolveTocEntry(line, byAnchor, byTitle) {
2465
2573
  return formatTocEntry(legacyEntry[1], byTitle.get(title) || byAnchor.get(headingSlug(title)));
2466
2574
  }
2467
2575
 
2576
+ // Prepared form. This makes generated TOCs idempotent: a later package/build pass can re-derive the
2577
+ // current line numbers from the emitted heading text instead of preserving stale numbers.
2578
+ const preparedEntry = /^(\s*)[-*]\s+L\d+\s\s(.+?)\s*$/.exec(line);
2579
+ if (preparedEntry) {
2580
+ const title = preparedEntry[2];
2581
+ return formatTocEntry(preparedEntry[1], byTitle.get(title) || byAnchor.get(headingSlug(title)));
2582
+ }
2583
+
2468
2584
  return null;
2469
2585
  }
2470
2586
 
@@ -2634,6 +2750,10 @@ async function installSkillsCommand(flags) {
2634
2750
  }
2635
2751
  }
2636
2752
 
2753
+ /*
2754
+ ## Auth And Component Staging
2755
+ */
2756
+
2637
2757
  async function authCommand(flags) {
2638
2758
  const cwd = process.cwd();
2639
2759
  ensureLocalState(cwd);
@@ -2999,6 +3119,7 @@ async function pushComponentsCommand(flags) {
2999
3119
  rawRefs: { command: 'components stage' },
3000
3120
  stage: (err.responseBody || {}).stage,
3001
3121
  compileValidation: (err.responseBody || {}).compileValidation,
3122
+ refusalDetails: stageRefusalDetails(err.responseBody || {}),
3002
3123
  changedFromWorkingTree: changedFromWorkingTree || [],
3003
3124
  unrepresentableChanges: unstageable
3004
3125
  }, { quiet: true });
@@ -3033,6 +3154,7 @@ async function pushComponentsCommand(flags) {
3033
3154
  rawRefs: { command: 'components stage' },
3034
3155
  stage: response.stage,
3035
3156
  laneSummary: response.laneSummary,
3157
+ compileValidation: response.compileValidation || null,
3036
3158
  changedFromWorkingTree: response.changedFromWorkingTree,
3037
3159
  unrepresentableChanges: response.unrepresentableChanges
3038
3160
  }, { quiet: flagEnabled(flags.json) });
@@ -3218,6 +3340,18 @@ async function stageOrRefuse(api, cwd, payload) {
3218
3340
  }
3219
3341
  }
3220
3342
 
3343
+ function stageRefusalDetails(body = {}) {
3344
+ const details = {};
3345
+ ['duplicates', 'compileValidation', 'policy', 'editLease', 'skipped', 'reconcile', 'laneHoldsRejectedSource'].forEach((key) => {
3346
+ if (body[key] !== undefined) details[key] = sanitizeForLog(body[key]);
3347
+ });
3348
+ return Object.keys(details).length ? details : undefined;
3349
+ }
3350
+
3351
+ /*
3352
+ ## Component Summaries, Status, And Sync Gates
3353
+ */
3354
+
3221
3355
  /** What the account's rules said about this stage or commit — including "they could not be read". */
3222
3356
  function printComponentPolicy(response) {
3223
3357
  const policy = response && response.policy;
@@ -3269,6 +3403,10 @@ function printCompileValidation(validation, options) {
3269
3403
  // A skip is neither a pass nor a failure, and it used to be printed as a pass ("1/1 compiled" over a
3270
3404
  // component whose source was blank). Named, so the reader knows exactly what was NOT looked at.
3271
3405
  const unchecked = Array.isArray(validation.unchecked) ? validation.unchecked : [];
3406
+ const warnings = Array.isArray(validation.warnings) ? validation.warnings : [];
3407
+ warnings.forEach((warning) => {
3408
+ emit(' Warning: ' + (warning.message || JSON.stringify(warning)));
3409
+ });
3272
3410
  if (unchecked.length) {
3273
3411
  emit(' ' + unchecked.length + ' NOT compile-checked: ' + unchecked.map((entry) =>
3274
3412
  (entry.type || 'component') + ' ' + (entry.id || entry.name || '?') + ' (' + (entry.reason || 'unchecked') + ')').join(', '));
@@ -3433,6 +3571,9 @@ function printStageSummary(response, flags) {
3433
3571
  // The CLI's own findings (deleted files) and the platform's (a README withheld on a variant branch) are
3434
3572
  // different sources. Preferring the first whenever it existed — even as an empty list — hid the second.
3435
3573
  printUnrepresentableChanges(mergeUnrepresentable(response.unrepresentableChanges, stage.unrepresentable));
3574
+ if (typeof printOrphanedWorkspaceWarning === 'function') {
3575
+ printOrphanedWorkspaceWarning(response);
3576
+ }
3436
3577
  if (typeof printAccountLanes === 'function') {
3437
3578
  printAccountLanes(response);
3438
3579
  }
@@ -3458,6 +3599,7 @@ function printStatusSummary(response, flags) {
3458
3599
  printBranchContext(response);
3459
3600
  printStagingLane(response.branchName, response.workspace, null);
3460
3601
  printLaneSummary(response);
3602
+ printOrphanedWorkspaceWarning(response);
3461
3603
  printStagingFreshness(response.freshness);
3462
3604
  printRepositoryCheck(response.repositoryCheck);
3463
3605
  printComponentTypeCounts(response.entries || []);
@@ -3499,9 +3641,7 @@ function printLanesSummary(response, flags) {
3499
3641
  }
3500
3642
 
3501
3643
  function printStagingLaneRow(lane) {
3502
- const world = lane.onTrunk === true ? 'trunk'
3503
- : lane.onTrunk === false ? 'variant'
3504
- : 'unknown-world';
3644
+ const world = laneWorldLabel(lane);
3505
3645
  const ws = lane.workspace ? ' [ws:' + lane.workspace + ']' : ' [shared]';
3506
3646
  const workset = lane.worksetKnown && lane.worksetCountAsOf != null
3507
3647
  ? ' workset ' + lane.worksetCountAsOf
@@ -3598,10 +3738,7 @@ function printAccountLanes(response) {
3598
3738
  console.log('');
3599
3739
  console.log('Other staging lanes on this ACCOUNT (as of the last stage/clear in each):');
3600
3740
  lanes.forEach((lane) => {
3601
- // null onTrunk means the owner could not be resolved. Say "unknown" rather than guessing a world.
3602
- const world = lane.onTrunk === true ? 'trunk'
3603
- : lane.onTrunk === false ? 'variant branch'
3604
- : 'branch world unknown';
3741
+ const world = laneWorldLabel(lane);
3605
3742
  const ws = lane.workspace ? ' [ws:' + lane.workspace + ']' : '';
3606
3743
  const who = lane.userEmail ? ' ' + lane.userEmail : '';
3607
3744
  const ttl = lane.expiresInSeconds != null
@@ -3617,7 +3754,51 @@ function printAccountLanes(response) {
3617
3754
  });
3618
3755
  console.log(' (* = this command\'s lane)');
3619
3756
  console.log(' A variant-branch lane layers over that branch\'s ComponentVariant overlays, and a commit');
3620
- console.log(' from it writes overlays — never the trunk rows a trunk lane commits to.');
3757
+ console.log(' from it writes overlays — never the trunk rows a trunk lane commits to. A feature-branch lane');
3758
+ console.log(' resolves the account\'s subscription beneath its staged entries and cannot be landed.');
3759
+ }
3760
+
3761
+ function printOrphanedWorkspaceWarning(response) {
3762
+ const currentCount = Number((response && response.stagedCount) ||
3763
+ (response && response.laneSummary && response.laneSummary.stagedCount) || 0);
3764
+ if (currentCount > 0) return;
3765
+ const lanes = Array.isArray(response && response.accountLanes) ? response.accountLanes : [];
3766
+ const currentBranch = response && response.branchName;
3767
+ const currentWorkspace = response && response.workspace;
3768
+ const orphan = lanes.find((lane) => lane && lane.sameWorkspaceOtherBranch === true);
3769
+ if (!orphan) return;
3770
+ const age = orphan.updatedAtMs ? formatAge(Date.now() - Number(orphan.updatedAtMs)) + ' ago' : 'recently';
3771
+ console.log('');
3772
+ console.log('WARNING: Your workspace "' + (currentWorkspace || orphan.workspace || 'shared') + '" has ' +
3773
+ (orphan.stagedCountAsOf || orphan.stagedCount || 0) + ' staged entr' +
3774
+ ((orphan.stagedCountAsOf || orphan.stagedCount || 0) === 1 ? 'y' : 'ies') +
3775
+ ' on branch "' + orphan.branchName + '" (staged ' + age + '); this checkout is now on "' +
3776
+ (currentBranch || 'unknown') + '". Re-stage here, or switch back.');
3777
+ }
3778
+
3779
+ function formatAge(ms) {
3780
+ const value = Number(ms);
3781
+ if (!Number.isFinite(value) || value < 0) return 'unknown age';
3782
+ const seconds = Math.round(value / 1000);
3783
+ if (seconds < 90) return seconds + 's';
3784
+ const minutes = Math.round(seconds / 60);
3785
+ if (minutes < 90) return minutes + 'm';
3786
+ const hours = Math.round(minutes / 60);
3787
+ if (hours < 48) return hours + 'h';
3788
+ return Math.round(hours / 24) + 'd';
3789
+ }
3790
+
3791
+ // The world a lane row resolves, from the platform's answer. null onTrunk means the owner could not be
3792
+ // resolved, and a non-trunk lane without a reported source (an older platform) is only known to be
3793
+ // non-trunk — never guessed to be a variant branch. Pure.
3794
+ function laneWorldLabel(lane) {
3795
+ if (!lane || lane.onTrunk == null) return 'branch world unknown';
3796
+ if (lane.onTrunk === true) return 'trunk';
3797
+ if (lane.variantBranchSource === 'subscription-fallback') return 'feature branch';
3798
+ if (lane.variantBranchSource === 'branch-has-variants' || lane.variantBranchSource === 'branch-has-subscribers') {
3799
+ return 'variant branch';
3800
+ }
3801
+ return 'non-trunk branch';
3621
3802
  }
3622
3803
 
3623
3804
  function isCurrentStagingLane(lane) {
@@ -3638,10 +3819,15 @@ function printBranchContext(response) {
3638
3819
  console.log('');
3639
3820
  if (ctx.onTrunk) {
3640
3821
  console.log('Working tree: TRUNK (' + response.branchName + ')');
3822
+ } else if (ctx.variantBranchSource === 'subscription-fallback') {
3823
+ console.log('Working tree: FEATURE BRANCH "' + response.branchName + '" (not a variant branch; trunk is "' + ctx.trunkBranch + '")');
3641
3824
  } else {
3642
3825
  console.log('Working tree: VARIANT BRANCH "' + response.branchName + '" (trunk is "' + ctx.trunkBranch + '")');
3643
3826
  }
3644
3827
  console.log(' runs resolve: ' + ctx.resolves);
3828
+ if (ctx.variantBranchSource) {
3829
+ console.log(' variant world: ' + (ctx.variantBranch || 'none (subscription)') + ' [' + ctx.variantBranchSource + ']');
3830
+ }
3645
3831
  console.log(' commit writes: ' + ctx.commitWrites);
3646
3832
  if (Object.prototype.hasOwnProperty.call(ctx, 'lastSyncedSha')) {
3647
3833
  // null is "unknown" (never synced, expired, or the last sync had errors) — never "nothing landed".
@@ -3755,6 +3941,9 @@ async function statusComponentsCommand(flags) {
3755
3941
  workspace,
3756
3942
  dataMode,
3757
3943
  mode: 'status',
3944
+ // So "runs resolve" describes the run these flags would launch, by the same server-side rule.
3945
+ variantBranch: flags['variant-branch'],
3946
+ asAccountId: flags['as-account'] || flags['as-account-id'],
3758
3947
  componentType: flags['component-type'] || flags.type,
3759
3948
  componentId: flags['component-id'] || flags.id
3760
3949
  }).then((r) => r.data);
@@ -3767,6 +3956,7 @@ async function statusComponentsCommand(flags) {
3767
3956
  // supplies the facts (the last SHA it synced, each entry's base) and the comparison happens here.
3768
3957
  response.freshness = stagingFreshness({
3769
3958
  head: safeGitValue(cwd, 'git rev-parse HEAD'),
3959
+ originHead: safeGitValue(cwd, 'git rev-parse ' + shellQuote('origin/' + branchName)),
3770
3960
  lastSyncedSha: response.branchContext && response.branchContext.lastSyncedSha,
3771
3961
  entries: response.entries,
3772
3962
  laneSummary: response.laneSummary,
@@ -3983,6 +4173,9 @@ async function syncComponentsCommand(rawFlags) {
3983
4173
  ...runContextPayload(flags),
3984
4174
  agentId: flags['agent-id'] || process.env.REMITS_AGENT_ID || undefined,
3985
4175
  acknowledge: flags.acknowledge || undefined,
4176
+ // The platform refuses to sync a branch that is not a variant branch (see featureBranchLandingRefusal);
4177
+ // this is the explicit statement that a NEW variant branch is being created on purpose.
4178
+ createVariantBranch: flagEnabled(flags['create-variant-branch']) || undefined,
3986
4179
  components: []
3987
4180
  };
3988
4181
 
@@ -4005,7 +4198,7 @@ async function syncComponentsCommand(rawFlags) {
4005
4198
  // --safe is fail-closed: a sync that would reconcile a repository other than this checkout's is refused.
4006
4199
  const preflightRepoCheck = repositoryCheck(cwd, branchContext);
4007
4200
  if (preflightRepoCheck.matches === false) {
4008
- const mismatch = repositoryMismatchMessage(preflightRepoCheck, 'components sync');
4201
+ const mismatch = repositoryMismatchMessage(preflightRepoCheck, 'components sync', branchContext);
4009
4202
  if (flagEnabled(flags.json)) {
4010
4203
  const refusal = {
4011
4204
  success: false,
@@ -4132,7 +4325,7 @@ async function syncComponentsCommand(rawFlags) {
4132
4325
  response.repositoryCheck = repositoryCheck(cwd, { repository: response.sync && response.sync.repository });
4133
4326
  if (response.repositoryCheck.matches === false) {
4134
4327
  (flagEnabled(flags.json) ? console.error : console.log)('WARNING: ' +
4135
- repositoryMismatchMessage(response.repositoryCheck, 'components sync'));
4328
+ repositoryMismatchMessage(response.repositoryCheck, 'components sync', response.branchContext));
4136
4329
  }
4137
4330
  const summary = buildSyncSummary(response);
4138
4331
  summary.gates = gate.checks;
@@ -4550,11 +4743,66 @@ function repositoryCheck(cwd, branchContext) {
4550
4743
  return { platformRepository, checkoutOrigin, matches: repositoryNamesMatch(platformRepository, checkoutOrigin) };
4551
4744
  }
4552
4745
 
4553
- function repositoryMismatchMessage(check, commandName) {
4746
+ function repositoryMismatchMessage(check, commandName, branchContext) {
4554
4747
  return commandName + ': this checkout\'s origin is ' + check.checkoutOrigin + ', but the platform syncs repository "' +
4555
4748
  check.platformRepository + '" for this account. A push from here would never be synced, and the sync would ' +
4556
- 'reconcile a repository you are not editing. Run it from that repository\'s checkout, or check which account ' +
4557
- 'this command resolved (account-info.json, --account-id).';
4749
+ 'reconcile a repository you are not editing. ' + (featureBranchLandingHint(branchContext) ||
4750
+ ('Run it from that repository\'s checkout, or check which account this command resolved ' +
4751
+ '(account-info.json, --account-id).'));
4752
+ }
4753
+
4754
+ // A git branch that is not a variant world (no committed variants, no subscribers) resolves the account's
4755
+ // SUBSCRIBED branch beneath its staged entries — the platform reports that as
4756
+ // `variantBranchSource: 'subscription-fallback'`. Syncing such a branch would write overlays nobody resolves,
4757
+ // so the way to land it is through the branch it resolves. Pure; null when the context says nothing of the kind.
4758
+ function featureBranchLandingHint(branchContext) {
4759
+ if (!branchContext || branchContext.variantBranchSource !== 'subscription-fallback') return null;
4760
+ const subscribed = branchContext.subscribedComponentBranch;
4761
+ const trunk = branchContext.trunkBranch || 'trunk';
4762
+ // Without a subscription the platform cannot know which branch this was cut from; naming trunk would send a
4763
+ // branch cut from a variant branch into trunk.
4764
+ return 'This git branch is not a variant branch: runs from it resolve ' +
4765
+ (subscribed ? 'the \'' + subscribed + '\' variant branch this account subscribes to' : 'trunk') +
4766
+ ' plus what you staged. To land the work, ' +
4767
+ (subscribed
4768
+ ? 'merge this branch into \'' + subscribed + '\' and commit/sync \'' + subscribed + '\' from that checkout.'
4769
+ : 'merge this branch into the branch you cut it from (trunk \'' + trunk + '\', or the variant branch it came ' +
4770
+ 'from) and commit/sync that branch from its checkout.');
4771
+ }
4772
+
4773
+ // Refuse a commit from a branch the platform reports as NOT a variant branch, unless the caller says it is
4774
+ // creating one. Mirrors the server-side refusal so nothing is pushed that cannot be synced. Pure.
4775
+ function featureBranchCommitRefusal(branchContext, branchName, flags) {
4776
+ if (!branchContext || branchContext.variantBranchSource !== 'subscription-fallback') return null;
4777
+ if (flags && (flags['create-variant-branch'] === true || flags['create-variant-branch'] === 'true')) return null;
4778
+ return 'components commit refused: \'' + branchName + '\' is not a variant branch (no committed variants, no ' +
4779
+ 'subscribers). ' + featureBranchLandingHint(branchContext) + ' Committing here would write overlays for \'' +
4780
+ branchName + '\' that nobody subscribes to, and flip every other lane on that branch to them. If you are ' +
4781
+ 'deliberately creating a NEW variant branch, re-run with --create-variant-branch.';
4782
+ }
4783
+
4784
+ // What a verification envelope records as the world it verified. Taken from the platform's answer
4785
+ // (`variantWorld`, the same rule every run uses), never inferred from `onTrunk`: a feature branch cut from a
4786
+ // variant branch is not on trunk, yet resolves the SUBSCRIBED branch, and labelling it by its own name recorded
4787
+ // a world no run executed in. Pure.
4788
+ function verificationWorldFromStatus(statusResponse, branchName) {
4789
+ if (!statusResponse) return {};
4790
+ const ctx = statusResponse.branchContext || {};
4791
+ const world = statusResponse.variantWorld || null;
4792
+ const staged = !!(statusResponse.laneSummary && statusResponse.laneSummary.stagedCount > 0);
4793
+ let componentBranch;
4794
+ if (world) {
4795
+ componentBranch = world.componentBranch || 'trunk';
4796
+ } else {
4797
+ // An older platform that does not report the world: the previous inference, kept for compatibility.
4798
+ componentBranch = ctx.onTrunk ? 'trunk' : branchName;
4799
+ }
4800
+ const sourceLayer = staged ? 'staged' : (componentBranch && componentBranch !== 'trunk' ? 'variant' : 'trunk');
4801
+ return {
4802
+ componentBranch,
4803
+ sourceLayer,
4804
+ variantBranchSource: world ? world.variantBranchSource : undefined
4805
+ };
4558
4806
  }
4559
4807
 
4560
4808
  function printRepositoryCheck(check) {
@@ -4622,7 +4870,7 @@ function gitIsAncestor(cwd, ancestor, descendant) {
4622
4870
  * `stageBaseSha`; only the local checkout can order commits, so the comparison happens here. Pure — git is
4623
4871
  * injected as `isAncestor(a, b)` — so it can be tested without a repository.
4624
4872
  */
4625
- function stagingFreshness({ head, lastSyncedSha, entries, laneSummary, isAncestor }) {
4873
+ function stagingFreshness({ head, originHead, lastSyncedSha, entries, laneSummary, isAncestor }) {
4626
4874
  const rows = Array.isArray(entries) ? entries : [];
4627
4875
  const staged = rows.filter((entry) => entry && entry.stageBaseSha);
4628
4876
  const memo = new Map();
@@ -4648,7 +4896,9 @@ function stagingFreshness({ head, lastSyncedSha, entries, laneSummary, isAncesto
4648
4896
 
4649
4897
  return {
4650
4898
  head: head || null,
4899
+ originHead: originHead || null,
4651
4900
  lastSyncedSha: lastSyncedSha || null,
4901
+ freshnessUnknownReason: !originHead ? 'branch has no upstream' : (!lastSyncedSha ? 'platform has no last synced SHA for this branch' : null),
4652
4902
  landedSinceHead,
4653
4903
  entriesBehindLastSync: behindLastSync.length,
4654
4904
  entriesFromOtherCommits: fromOtherCommits.length,
@@ -4662,6 +4912,9 @@ function stagingFreshness({ head, lastSyncedSha, entries, laneSummary, isAncesto
4662
4912
  function printStagingFreshness(freshness) {
4663
4913
  if (!freshness) return;
4664
4914
  const plural = (n) => n + ' staged entr' + (n === 1 ? 'y' : 'ies');
4915
+ if (freshness.freshnessUnknownReason) {
4916
+ console.log('Freshness unknown:', freshness.freshnessUnknownReason + '.');
4917
+ }
4665
4918
  if (freshness.landedSinceHead === true) {
4666
4919
  console.log('');
4667
4920
  console.log('LANDED SINCE YOUR BASE: the platform last synced ' + short(freshness.lastSyncedSha) +
@@ -4850,6 +5103,10 @@ function buildSyncSummary(response) {
4850
5103
  };
4851
5104
  }
4852
5105
 
5106
+ /*
5107
+ ## Branches, Promotion, Commit, And Test Runs
5108
+ */
5109
+
4853
5110
  // Inspect committed branch variants: durable, branch-scoped overlays of this account's components.
4854
5111
  // Unlike `components status` (which shows the ephemeral Redis staging cache), these are what
4855
5112
  // subscribing accounts actually resolve in production.
@@ -5457,7 +5714,7 @@ async function refuseUnsafeTrunkCommitBeforeGit(flags, accountId, branchName, sk
5457
5714
  // `--skip-git` pushes nothing, so there is nothing to mis-deliver.
5458
5715
  const repoCheck = skipGit ? null : repositoryCheck(cwd, branchContext);
5459
5716
  if (repoCheck && repoCheck.matches === false) {
5460
- const mismatch = repositoryMismatchMessage(repoCheck, 'components commit');
5717
+ const mismatch = repositoryMismatchMessage(repoCheck, 'components commit', branchContext);
5461
5718
  if (flagEnabled(flags.json)) {
5462
5719
  console.log(JSON.stringify({
5463
5720
  success: false,
@@ -5478,6 +5735,30 @@ async function refuseUnsafeTrunkCommitBeforeGit(flags, accountId, branchName, sk
5478
5735
  }
5479
5736
  throw new Error(mismatch + ' No git commit or push was attempted.');
5480
5737
  }
5738
+ // Before ANY git write: is this a feature branch (not a variant branch)? The platform refuses its sync, and
5739
+ // a commit pushed first would leave a pushed-but-unsyncable branch. Same decision, taken before the push.
5740
+ const featureRefusal = featureBranchCommitRefusal(branchContext, branchName, flags);
5741
+ if (featureRefusal) {
5742
+ if (flagEnabled(flags.json)) {
5743
+ console.log(JSON.stringify({
5744
+ success: false,
5745
+ mode: 'commit',
5746
+ dataMode,
5747
+ accountId,
5748
+ branchName,
5749
+ workspace,
5750
+ branchContext,
5751
+ refusal: 'feature_branch_landing',
5752
+ phase: 'pre-git',
5753
+ gitWritten: false,
5754
+ gateViolations: [featureRefusal],
5755
+ message: featureRefusal
5756
+ }, null, 2));
5757
+ process.exitCode = 1;
5758
+ return true;
5759
+ }
5760
+ throw new Error(featureRefusal + ' No git commit or push was attempted.');
5761
+ }
5481
5762
  if (!flagEnabled(flags.yes) && branchContextIsTrunk(branchContext)) {
5482
5763
  printTrunkSyncWarning(branchContext ? branchContext.trunkBranch || branchName : branchName, {
5483
5764
  stderr: flagEnabled(flags.json)
@@ -5594,6 +5875,84 @@ async function waitForStatus(api, cwd, accountId, branchName, taskId, token, dat
5594
5875
  }
5595
5876
  }
5596
5877
 
5878
+ function testEvidenceCategories(status = {}, selectedNames = []) {
5879
+ const categories = new Set(['test_run']);
5880
+ const test = status.test || {};
5881
+ if (test.name) categories.add('test_run.suite:' + String(test.name));
5882
+ if (test.id !== undefined && test.id !== null) categories.add('test_run.suite_id:' + String(test.id));
5883
+ (Array.isArray(selectedNames) ? selectedNames : []).forEach((name) => {
5884
+ if (name) categories.add('test_run.' + String(name));
5885
+ });
5886
+ const results = status.result && Array.isArray(status.result.tests) ? status.result.tests : [];
5887
+ results.forEach((result) => {
5888
+ if (result && result.passed === true && result.name) {
5889
+ categories.add('test_run.case:' + String(result.name));
5890
+ categories.add('test_run.' + String(result.name));
5891
+ }
5892
+ });
5893
+ return Array.from(categories);
5894
+ }
5895
+
5896
+ function testComponentProvenance(status = {}) {
5897
+ const direct = status.resolvedComponentProvenance || (status.result && status.result.resolvedComponentProvenance) ||
5898
+ (status.staging && status.staging.resolvedComponentProvenance);
5899
+ if (Array.isArray(direct)) return direct;
5900
+ const staging = status.staging || {};
5901
+ if (staging.testComponentSignature) {
5902
+ return [{
5903
+ type: 'test',
5904
+ id: status.test && status.test.id,
5905
+ name: status.test && status.test.name,
5906
+ signature: staging.testComponentSignature,
5907
+ source: staging.testComponentSource || null
5908
+ }];
5909
+ }
5910
+ return [];
5911
+ }
5912
+
5913
+ function testCompileSignatures(status = {}) {
5914
+ return testComponentProvenance(status)
5915
+ .map((entry) => entry && entry.signature)
5916
+ .filter(Boolean);
5917
+ }
5918
+
5919
+ function printTestRunPivots(status = {}) {
5920
+ const tests = status.result && Array.isArray(status.result.tests) ? status.result.tests : [];
5921
+ if (!tests.length) return;
5922
+ const slowest = tests.slice().sort((a, b) => Number(b.duration || 0) - Number(a.duration || 0))[0];
5923
+ if (slowest && slowest.duration != null) {
5924
+ console.log('Slowest case:', (slowest.name || '(unnamed)') + ' in ' + formatDurationMs(slowest.duration));
5925
+ }
5926
+ tests.filter((test) => test && test.passed === false).forEach((test) => {
5927
+ console.log('Pivots for failed case:', test.name || '(unnamed)');
5928
+ if (test.duration != null) console.log(' Duration:', formatDurationMs(test.duration));
5929
+ if (test.threadGroupingId || test.threadGroupId || test.traceId) {
5930
+ console.log(' Trace:', test.traceId || test.threadGroupingId || test.threadGroupId);
5931
+ }
5932
+ if (test.error) console.log(' Error:', String(test.error).slice(0, 500));
5933
+ const diagnostics = test.diagnostics && typeof test.diagnostics === 'object' ? test.diagnostics : null;
5934
+ if (diagnostics && Object.keys(diagnostics).length) {
5935
+ console.log(' Diagnostics:', JSON.stringify(diagnostics).slice(0, 1200));
5936
+ }
5937
+ if (Array.isArray(test.resolvedComponentProvenance) && test.resolvedComponentProvenance.length) {
5938
+ console.log(' Components:', test.resolvedComponentProvenance.slice(0, 8).map((entry) =>
5939
+ (entry.type || 'component') + ' ' + (entry.id || entry.name || '?') +
5940
+ (entry.source ? ' [' + entry.source + ']' : '') +
5941
+ (entry.signature ? ' ' + short(entry.signature) : '')).join(', '));
5942
+ }
5943
+ if (Array.isArray(test.liveHttpCalls) && test.liveHttpCalls.length) {
5944
+ console.log(' Live HTTP calls:', test.liveHttpCalls.length);
5945
+ }
5946
+ });
5947
+ }
5948
+
5949
+ function formatDurationMs(value) {
5950
+ const ms = Number(value);
5951
+ if (!Number.isFinite(ms)) return String(value);
5952
+ if (ms < 1000) return ms + 'ms';
5953
+ return (ms / 1000).toFixed(ms < 10000 ? 1 : 0) + 's';
5954
+ }
5955
+
5597
5956
  async function waitForToolStatus(api, cwd, options) {
5598
5957
  const started = Date.now();
5599
5958
  let pollDelayMs = parsePositiveInt(options.pollIntervalMs, 1000);
@@ -5744,6 +6103,7 @@ async function testCommand(flags) {
5744
6103
  printResolvedBaseUrl(baseUrl);
5745
6104
  printStagingLane(branchName, workspace, workspaceSource(cwd, flags));
5746
6105
  if (start.staging && Array.isArray(start.staging.accountLanes)) {
6106
+ printOrphanedWorkspaceWarning(start.staging);
5747
6107
  printAccountLanes({ accountLanes: start.staging.accountLanes, branchName, workspace });
5748
6108
  }
5749
6109
  console.log('Test run started:', start.taskId);
@@ -5770,6 +6130,7 @@ async function testCommand(flags) {
5770
6130
  console.log(JSON.stringify(status, null, 2));
5771
6131
  } else {
5772
6132
  console.log('Final status:', JSON.stringify(status, null, 2));
6133
+ printTestRunPivots(status);
5773
6134
  }
5774
6135
 
5775
6136
  // A selector that matched no case is a mis-specified run, not a passing one. Say so in the terminal
@@ -5800,7 +6161,8 @@ async function testCommand(flags) {
5800
6161
  claim: 'Test run ' + String(testRef),
5801
6162
  world: buildCommandWorld(status, { accountId, dataMode: status.dataMode || dataMode, dataModeSource, branchName, workspace, host: normalizeBaseUrl(baseUrl), sourceLayer: status.staging && status.staging.testComponentSource }),
5802
6163
  revision: Object.assign(collectVerificationSource(cwd, flags), {
5803
- compileSignatures: status.staging && status.staging.testComponentSignature ? [status.staging.testComponentSignature] : []
6164
+ compileSignatures: testCompileSignatures(status),
6165
+ componentProvenance: testComponentProvenance(status)
5804
6166
  }),
5805
6167
  test: {
5806
6168
  taskId: start.taskId,
@@ -5809,16 +6171,66 @@ async function testCommand(flags) {
5809
6171
  selectedCases: names,
5810
6172
  passed: status.result && status.result.passed,
5811
6173
  failed: status.result && status.result.failed,
6174
+ tests: status.result && status.result.tests,
5812
6175
  dataModeSource: status.dataModeSource || dataModeSource,
5813
6176
  unmatchedTestNames: unmatched
5814
6177
  },
5815
- evidenceCategories: ['test_run'].concat(names.map((name) => 'test_run.' + name)),
6178
+ evidenceCategories: testEvidenceCategories(status, names),
5816
6179
  limitations: unmatched.length ? ['One or more requested test case selectors matched no case.'] : [],
5817
6180
  rawRefs: { testStatusKey: start.taskId },
5818
6181
  status
5819
6182
  }, { quiet: jsonOutput });
5820
6183
  }
5821
6184
 
6185
+ async function testStatusCommand(flags) {
6186
+ const cwd = process.cwd();
6187
+ ensureLocalState(cwd);
6188
+ const sessionContext = resolveSessionContext(cwd, flags);
6189
+ const { session, accountId } = sessionContext;
6190
+ const baseUrl = flags['base-url'] || session.baseUrl || DEFAULT_BASE_URL;
6191
+ const branchName = flags.branch || currentBranch(cwd);
6192
+ const dataMode = hasExplicitDataModeFlag(flags)
6193
+ ? resolveDataMode(flags, null)
6194
+ : DEFAULT_DATA_MODE;
6195
+ const taskId = flags['task-id'] || flags.taskId || flags.id || (flags._ && flags._[2]);
6196
+ const jsonOutput = flagEnabled(flags.json);
6197
+ if (!taskId) throw new Error('Missing --task-id <taskId>');
6198
+
6199
+ const api = buildAxios(baseUrl, session.token);
6200
+ const status = await loggedPost(api, cwd, '/cli/test', {
6201
+ token: session.token,
6202
+ command: 'status',
6203
+ accountId,
6204
+ branchName,
6205
+ taskId,
6206
+ dataMode
6207
+ }).then((r) => r.data);
6208
+
6209
+ if (jsonOutput) {
6210
+ console.log(JSON.stringify(status, null, 2));
6211
+ return status;
6212
+ }
6213
+ printSessionResolutionWarning(sessionContext);
6214
+ printResolvedBaseUrl(baseUrl);
6215
+ console.log('Test run status:', status.status || 'unknown');
6216
+ console.log('Task ID:', taskId);
6217
+ console.log('Data mode:', status.dataMode || dataMode);
6218
+ if (status.result) {
6219
+ console.log('Cases:', (status.result.passed || 0) + ' passed, ' + (status.result.failed || 0) + ' failed, ' + (status.result.total || 0) + ' total');
6220
+ }
6221
+ if (status.error || status.message) {
6222
+ console.log('Message:', status.error || status.message);
6223
+ }
6224
+ if (status.status === 'failed' || (status.result && status.result.failed > 0)) {
6225
+ process.exitCode = 1;
6226
+ }
6227
+ return status;
6228
+ }
6229
+
6230
+ /*
6231
+ ## Tokens, Tools, Verification, And Config
6232
+ */
6233
+
5822
6234
  async function tokenCommand(flags) {
5823
6235
  const subcommand = flags._ && flags._[1];
5824
6236
  if (subcommand === 'inspect' || subcommand === 'details' || subcommand === 'decode') {
@@ -5876,6 +6288,9 @@ async function tokenCommand(flags) {
5876
6288
  accountId: data.accountId,
5877
6289
  branchName: data.branchName,
5878
6290
  variantBranch: data.variantBranch,
6291
+ // Why the variant branch is (or is not) set — the platform's one rule, so a null is explainable.
6292
+ variantBranchSource: data.variantBranchSource,
6293
+ variantWorld: data.variantWorld,
5879
6294
  workspace: data.workspace !== undefined ? data.workspace : workspace,
5880
6295
  stagingLane: data.stagingLane,
5881
6296
  accountLanes: data.accountLanes,
@@ -6136,8 +6551,7 @@ async function toolCommand(flags) {
6136
6551
  revision: collectVerificationSource(cwd, flags),
6137
6552
  evidenceCategories: ['tool_call'],
6138
6553
  rawRefs: { toolResponsePath: statusResponse.responseFile, callId: requestedCallId },
6139
- tool: { callId: requestedCallId, status: data.status, name: stored && stored.name },
6140
- result: data
6554
+ tool: Object.assign(toolEvidenceSummary(data, statusResponse.responseFile), { callId: requestedCallId, name: stored && stored.name })
6141
6555
  }, { quiet: jsonOutput });
6142
6556
  if (jsonOutput) {
6143
6557
  console.log(JSON.stringify(data, null, 2));
@@ -6150,7 +6564,8 @@ async function toolCommand(flags) {
6150
6564
  console.log('Data mode:', data.dataMode || dataMode);
6151
6565
  if (data.threadGroupingId) console.log('Thread grouping ID:', data.threadGroupingId);
6152
6566
  console.log('Session log:', data.sessionLog);
6153
- console.log('Tool response file:', data.responseFile);
6567
+ const responseEvidence = fileEvidence(data.responseFile);
6568
+ console.log('Tool response file:', data.responseFile + (responseEvidence.bytes ? ' (' + responseEvidence.bytes + ' bytes, sha256 ' + responseEvidence.sha256.slice(0, 12) + ')' : ''));
6154
6569
  if (polledFailed) {
6155
6570
  console.log('Tool error:', data.toolMessage);
6156
6571
  }
@@ -6201,6 +6616,9 @@ async function toolCommand(flags) {
6201
6616
  data.sessionLog = sessionJsonlFile(cwd);
6202
6617
  if (toolFailureMessage && !data.toolMessage) data.toolMessage = toolFailureMessage;
6203
6618
 
6619
+ let evidenceData = data;
6620
+ let evidenceResponseFile = response.responseFile;
6621
+
6204
6622
  if (!jsonOutput) {
6205
6623
  printSessionResolutionWarning(sessionContext);
6206
6624
  printResolvedBaseUrl(baseUrl);
@@ -6218,7 +6636,8 @@ async function toolCommand(flags) {
6218
6636
  console.log('Component:', `${data.componentSource}${sig}`);
6219
6637
  }
6220
6638
  console.log('Session log:', data.sessionLog);
6221
- console.log('Tool response file:', data.responseFile);
6639
+ const responseEvidence = fileEvidence(data.responseFile);
6640
+ console.log('Tool response file:', data.responseFile + (responseEvidence.bytes ? ' (' + responseEvidence.bytes + ' bytes, sha256 ' + responseEvidence.sha256.slice(0, 12) + ')' : ''));
6222
6641
  }
6223
6642
 
6224
6643
  // Non-zero exit so scripted/agent callers that check status notice the refusal too.
@@ -6238,9 +6657,12 @@ async function toolCommand(flags) {
6238
6657
 
6239
6658
  finalStatus.data.responseFile = finalStatus.responseFile;
6240
6659
  finalStatus.data.sessionLog = sessionJsonlFile(cwd);
6660
+ evidenceData = finalStatus.data;
6661
+ evidenceResponseFile = finalStatus.responseFile;
6241
6662
  if (!jsonOutput) {
6242
6663
  console.log('Final status:', finalStatus.data.status);
6243
- console.log('Tool response file:', finalStatus.responseFile);
6664
+ const finalEvidence = fileEvidence(finalStatus.responseFile);
6665
+ console.log('Tool response file:', finalStatus.responseFile + (finalEvidence.bytes ? ' (' + finalEvidence.bytes + ' bytes, sha256 ' + finalEvidence.sha256.slice(0, 12) + ')' : ''));
6244
6666
  }
6245
6667
  if (finalStatus.data.status !== 'completed') {
6246
6668
  process.exitCode = 1;
@@ -6250,20 +6672,19 @@ async function toolCommand(flags) {
6250
6672
 
6251
6673
  await appendVerificationPacket(api, cwd, session, accountId, flags, {
6252
6674
  type: 'tool_call',
6253
- success: !toolFailed && (!asyncMode || !waitForAsync || process.exitCode !== 1),
6675
+ success: asyncMode && !waitForAsync ? null : (!toolFailed && (!asyncMode || process.exitCode !== 1)),
6254
6676
  claim: 'Tool call ' + String(toolName),
6255
- world: buildCommandWorld(data, { accountId: data.accountId || accountId, dataMode: data.dataMode || dataMode, branchName: data.branchName || branchName, workspace: resolveWorkspace(cwd, flags), host: normalizeBaseUrl(baseUrl), sourceLayer: data.componentSource }),
6677
+ 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 }),
6256
6678
  revision: Object.assign(collectVerificationSource(cwd, flags), {
6257
- compileSignatures: data.componentSignature ? [data.componentSignature] : []
6679
+ compileSignatures: evidenceData.componentSignature ? [evidenceData.componentSignature] : []
6258
6680
  }),
6259
6681
  evidenceCategories: ['tool_call'],
6260
6682
  dependencies: {
6261
6683
  mocks: [],
6262
- liveHttpCalls: data.liveHttpCalls || []
6684
+ liveHttpCalls: evidenceData.liveHttpCalls || []
6263
6685
  },
6264
- rawRefs: { toolResponsePath: response.responseFile, callId },
6265
- tool: { callId, name: String(toolName), async: asyncMode, status: data.status, toolSuccess: data.toolSuccess },
6266
- result: data
6686
+ rawRefs: { toolResponsePath: evidenceResponseFile, callId },
6687
+ tool: Object.assign(toolEvidenceSummary(evidenceData, evidenceResponseFile), { callId, name: String(toolName), async: asyncMode })
6267
6688
  }, { quiet: jsonOutput });
6268
6689
  if (jsonOutput) {
6269
6690
  console.log(JSON.stringify(data, null, 2));
@@ -6346,10 +6767,12 @@ async function verifyCommand(flags, subcommand) {
6346
6767
 
6347
6768
  const source = collectVerificationSource(cwd, flags);
6348
6769
  if (statusResponse) {
6349
- source.componentBranch = statusResponse.branchContext && statusResponse.branchContext.onTrunk ? 'trunk' : branchName;
6350
- source.sourceLayer = statusResponse.laneSummary && statusResponse.laneSummary.stagedCount > 0
6351
- ? 'staged' : (statusResponse.branchContext && statusResponse.branchContext.onTrunk ? 'trunk' : 'variant');
6352
- source.stagedOverlayHash = statusResponse.laneSummary ? sha256(stableStringify(statusResponse.laneSummary)) : null;
6770
+ const verifiedWorld = verificationWorldFromStatus(statusResponse, branchName);
6771
+ source.componentBranch = verifiedWorld.componentBranch;
6772
+ source.sourceLayer = verifiedWorld.sourceLayer;
6773
+ if (verifiedWorld.variantBranchSource) source.variantBranchSource = verifiedWorld.variantBranchSource;
6774
+ source.laneContentHash = laneContentHashFrom(statusResponse);
6775
+ source.stagedOverlayHash = source.laneContentHash;
6353
6776
  }
6354
6777
 
6355
6778
  const response = await postVerificationCommand(api, cwd, session, accountId, 'start', {
@@ -6364,6 +6787,7 @@ async function verifyCommand(flags, subcommand) {
6364
6787
  writeActiveVerificationEnvelope(cwd, envelope.envelopeId, activeContext);
6365
6788
  console.log('Verification envelope started:', envelope.envelopeId);
6366
6789
  console.log('Target:', 'account=' + accountId + ', branch=' + branchName + ', workspace=' + (workspace || 'shared') + ', dataMode=' + dataMode);
6790
+ printAcceptanceWarnings(envelope);
6367
6791
  console.log('Local mirror:', verificationPaths(cwd, envelope.envelopeId).base);
6368
6792
  return envelope;
6369
6793
  }
@@ -6404,6 +6828,7 @@ async function verifyCommand(flags, subcommand) {
6404
6828
  writeLocalVerificationEnvelope(cwd, response.envelope);
6405
6829
  console.log('Manifest attached to envelope:', envelopeId);
6406
6830
  console.log('Required evidence:', (((response.envelope || {}).acceptance || {}).requiredEvidence || []).length);
6831
+ printAcceptanceWarnings(response.envelope);
6407
6832
  return response.envelope;
6408
6833
  }
6409
6834
 
@@ -6712,6 +7137,10 @@ async function sessionsCommand(flags, subcommand) {
6712
7137
  throw new Error('Unknown sessions subcommand: ' + subcommand + '. Use: list, remove');
6713
7138
  }
6714
7139
 
7140
+ /*
7141
+ ## Service Dashboard And WebSocket Listener
7142
+ */
7143
+
6715
7144
  // --- Listener PID management ---
6716
7145
  function isListenerRunning() {
6717
7146
  if (!fs.existsSync(LISTENER_PID_FILE)) {
@@ -8803,6 +9232,10 @@ async function runManagedWebsocketWatchdog() {
8803
9232
 
8804
9233
  // --- Persistent WebSocket listener ---
8805
9234
  // ---------------------------------------------------------------------------------------------
9235
+ /*
9236
+ ## Agent And Ticket Workflows
9237
+ */
9238
+
8806
9239
  // Local agent sessions
8807
9240
  //
8808
9241
  // A "CLI agent" is ONE terminal session — one tab running Claude, Codex, or a person — that has
@@ -11857,6 +12290,10 @@ async function listenStatusCommand(flags = {}) {
11857
12290
  printSessionIdentity(identity);
11858
12291
  }
11859
12292
 
12293
+ /*
12294
+ ## Help, Auto Update, And Command Dispatch
12295
+ */
12296
+
11860
12297
  async function whoamiCommand(flags = {}) {
11861
12298
  const identity = resolveSessionIdentity(process.cwd(), flags);
11862
12299
  printSessionResolutionWarning(identity);
@@ -12024,7 +12461,7 @@ function printComponentsHelp(subcommand) {
12024
12461
  return;
12025
12462
  }
12026
12463
  if (subcommand === 'sync') {
12027
- console.log('Usage: remits-cli components sync [--base-url URL] [--account-id ID] [--branch BRANCH] [--data-mode test|prod] [--force-tombstones] [--dry-run] [--summary] [--json]');
12464
+ console.log('Usage: remits-cli components sync [--base-url URL] [--account-id ID] [--branch BRANCH] [--data-mode test|prod] [--force-tombstones] [--dry-run] [--create-variant-branch] [--summary] [--json]');
12028
12465
  console.log('');
12029
12466
  console.log('On trunk this reconciles the pushed repository into live component rows.');
12030
12467
  console.log('On a variant branch this writes ComponentVariant overlays only.');
@@ -12110,7 +12547,7 @@ function printComponentsHelp(subcommand) {
12110
12547
  return;
12111
12548
  }
12112
12549
  if (subcommand === 'commit') {
12113
- console.log('Usage: remits-cli components commit [--yes] [--safe] [--message|-m "msg"] [--allow-empty true|false] [--skip-git true|false] [--branch BRANCH] [--data-mode test|prod] [--force-tombstones] [--json [--summary]]');
12550
+ console.log('Usage: remits-cli components commit [--yes] [--safe] [--create-variant-branch] [--message|-m "msg"] [--allow-empty true|false] [--skip-git true|false] [--branch BRANCH] [--data-mode test|prod] [--force-tombstones] [--json [--summary]]');
12114
12551
  console.log('');
12115
12552
  console.log('Runs staged compile validation, local git add/commit/push, server sync, then fast-forward pull.');
12116
12553
  console.log('Prefer explicit stage + git + components sync when you need inspectable phases.');
@@ -12152,11 +12589,13 @@ function printComponentsHelp(subcommand) {
12152
12589
 
12153
12590
  function printTestHelp() {
12154
12591
  console.log('Usage: remits-cli test run --test <id|name> [--base-url URL] [--branch stagingScope] [--names "a|b"] [--watch true|false] [--data-mode test|prod] [--as-account ID] [--variant-branch NAME|none] [--json]');
12592
+ console.log(' remits-cli test status --task-id <taskId> [--base-url URL] [--account-id ID] [--branch stagingScope] [--data-mode test|prod] [--json]');
12155
12593
  console.log('');
12156
12594
  console.log('Runs a Test component against the staged/variant world for this checkout.');
12157
12595
  console.log('Examples:');
12158
12596
  console.log(' remits-cli test run --test 11');
12159
12597
  console.log(' remits-cli test run --test "Merchant Statements" --names "managed account case"');
12598
+ console.log(' remits-cli test status --task-id 7a1b...');
12160
12599
  console.log('');
12161
12600
  console.log('Notes:');
12162
12601
  console.log(' --names is delimited by "|" (comma also works when no "|" is present), and may be repeated.');
@@ -12543,6 +12982,10 @@ async function main() {
12543
12982
  await testCommand(args);
12544
12983
  return;
12545
12984
  }
12985
+ if (command === 'test' && subcommand === 'status') {
12986
+ await testStatusCommand(args);
12987
+ return;
12988
+ }
12546
12989
 
12547
12990
  if (command === 'token') {
12548
12991
  await tokenCommand(args);