@remits/remits-cli 0.1.126 → 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
|
@@ -6,16 +6,16 @@
|
|
|
6
6
|
- L22 Runtime Bootstrap And Shared State
|
|
7
7
|
- L116 Sessions, Account Resolution, And Production Guards
|
|
8
8
|
- L595 Local State, Workspaces, And Verification Evidence
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
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
19
|
*/
|
|
20
20
|
|
|
21
21
|
/*
|
|
@@ -811,6 +811,42 @@ function writeToolResponseFile(cwd, callId, responsePayload) {
|
|
|
811
811
|
return file;
|
|
812
812
|
}
|
|
813
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
|
+
|
|
814
850
|
function readToolResponseFile(cwd, callId) {
|
|
815
851
|
if (!callId) return null;
|
|
816
852
|
const paths = ensureLocalState(cwd);
|
|
@@ -1100,12 +1136,28 @@ function printVerificationEnvelope(envelope, fallbackEnvelopeId) {
|
|
|
1100
1136
|
}
|
|
1101
1137
|
}
|
|
1102
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
|
+
|
|
1103
1150
|
function buildCommandWorld(response, fallback = {}) {
|
|
1104
1151
|
const source = (response && response.staging) || {};
|
|
1105
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;
|
|
1106
1158
|
return {
|
|
1107
1159
|
accountId: response && (response.accountId || response.ownerAccountId) || fallback.accountId,
|
|
1108
|
-
executionAccountId
|
|
1160
|
+
executionAccountId,
|
|
1109
1161
|
addressedAccountId: resolution.addressedAccountId || fallback.addressedAccountId,
|
|
1110
1162
|
host: fallback.host,
|
|
1111
1163
|
dataMode: response && response.dataMode || fallback.dataMode,
|
|
@@ -1113,16 +1165,26 @@ function buildCommandWorld(response, fallback = {}) {
|
|
|
1113
1165
|
branchName: response && response.branchName || fallback.branchName,
|
|
1114
1166
|
workspace: response && response.workspace !== undefined ? response.workspace : fallback.workspace,
|
|
1115
1167
|
stagingLane: response && response.stagingLane || source.stagingLane || fallback.stagingLane,
|
|
1168
|
+
laneContentHash: laneHash,
|
|
1169
|
+
stagedOverlayHash: laneHash,
|
|
1116
1170
|
// `variantWorld.componentBranch` is the platform's answer for the run (the variant branch, else the
|
|
1117
1171
|
// subscription); a bare `variantBranch` is null whenever the subscription decided.
|
|
1118
1172
|
componentBranch: resolution.componentBranch ||
|
|
1119
1173
|
response && ((response.variantWorld && response.variantWorld.componentBranch) || response.variantBranch) ||
|
|
1120
1174
|
fallback.componentBranch,
|
|
1175
|
+
variantBranchSource: response && (response.variantBranchSource || (response.variantWorld && response.variantWorld.variantBranchSource)) || fallback.variantBranchSource,
|
|
1121
1176
|
variantId: resolution.variantId || fallback.variantId,
|
|
1122
1177
|
sourceLayer: fallback.sourceLayer || (source.testComponentSource === 'staged' ? 'staged' : undefined)
|
|
1123
1178
|
};
|
|
1124
1179
|
}
|
|
1125
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
|
+
|
|
1126
1188
|
function normalizeName(name) {
|
|
1127
1189
|
return name.replace(/([a-z])([A-Z])/g, '$1 $2').replace(/\s+/g, ' ').trim();
|
|
1128
1190
|
}
|
|
@@ -3057,6 +3119,7 @@ async function pushComponentsCommand(flags) {
|
|
|
3057
3119
|
rawRefs: { command: 'components stage' },
|
|
3058
3120
|
stage: (err.responseBody || {}).stage,
|
|
3059
3121
|
compileValidation: (err.responseBody || {}).compileValidation,
|
|
3122
|
+
refusalDetails: stageRefusalDetails(err.responseBody || {}),
|
|
3060
3123
|
changedFromWorkingTree: changedFromWorkingTree || [],
|
|
3061
3124
|
unrepresentableChanges: unstageable
|
|
3062
3125
|
}, { quiet: true });
|
|
@@ -3091,6 +3154,7 @@ async function pushComponentsCommand(flags) {
|
|
|
3091
3154
|
rawRefs: { command: 'components stage' },
|
|
3092
3155
|
stage: response.stage,
|
|
3093
3156
|
laneSummary: response.laneSummary,
|
|
3157
|
+
compileValidation: response.compileValidation || null,
|
|
3094
3158
|
changedFromWorkingTree: response.changedFromWorkingTree,
|
|
3095
3159
|
unrepresentableChanges: response.unrepresentableChanges
|
|
3096
3160
|
}, { quiet: flagEnabled(flags.json) });
|
|
@@ -3276,6 +3340,14 @@ async function stageOrRefuse(api, cwd, payload) {
|
|
|
3276
3340
|
}
|
|
3277
3341
|
}
|
|
3278
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
|
+
|
|
3279
3351
|
/*
|
|
3280
3352
|
## Component Summaries, Status, And Sync Gates
|
|
3281
3353
|
*/
|
|
@@ -3331,6 +3403,10 @@ function printCompileValidation(validation, options) {
|
|
|
3331
3403
|
// A skip is neither a pass nor a failure, and it used to be printed as a pass ("1/1 compiled" over a
|
|
3332
3404
|
// component whose source was blank). Named, so the reader knows exactly what was NOT looked at.
|
|
3333
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
|
+
});
|
|
3334
3410
|
if (unchecked.length) {
|
|
3335
3411
|
emit(' ' + unchecked.length + ' NOT compile-checked: ' + unchecked.map((entry) =>
|
|
3336
3412
|
(entry.type || 'component') + ' ' + (entry.id || entry.name || '?') + ' (' + (entry.reason || 'unchecked') + ')').join(', '));
|
|
@@ -3495,6 +3571,9 @@ function printStageSummary(response, flags) {
|
|
|
3495
3571
|
// The CLI's own findings (deleted files) and the platform's (a README withheld on a variant branch) are
|
|
3496
3572
|
// different sources. Preferring the first whenever it existed — even as an empty list — hid the second.
|
|
3497
3573
|
printUnrepresentableChanges(mergeUnrepresentable(response.unrepresentableChanges, stage.unrepresentable));
|
|
3574
|
+
if (typeof printOrphanedWorkspaceWarning === 'function') {
|
|
3575
|
+
printOrphanedWorkspaceWarning(response);
|
|
3576
|
+
}
|
|
3498
3577
|
if (typeof printAccountLanes === 'function') {
|
|
3499
3578
|
printAccountLanes(response);
|
|
3500
3579
|
}
|
|
@@ -3520,6 +3599,7 @@ function printStatusSummary(response, flags) {
|
|
|
3520
3599
|
printBranchContext(response);
|
|
3521
3600
|
printStagingLane(response.branchName, response.workspace, null);
|
|
3522
3601
|
printLaneSummary(response);
|
|
3602
|
+
printOrphanedWorkspaceWarning(response);
|
|
3523
3603
|
printStagingFreshness(response.freshness);
|
|
3524
3604
|
printRepositoryCheck(response.repositoryCheck);
|
|
3525
3605
|
printComponentTypeCounts(response.entries || []);
|
|
@@ -3678,6 +3758,36 @@ function printAccountLanes(response) {
|
|
|
3678
3758
|
console.log(' resolves the account\'s subscription beneath its staged entries and cannot be landed.');
|
|
3679
3759
|
}
|
|
3680
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
|
+
|
|
3681
3791
|
// The world a lane row resolves, from the platform's answer. null onTrunk means the owner could not be
|
|
3682
3792
|
// resolved, and a non-trunk lane without a reported source (an older platform) is only known to be
|
|
3683
3793
|
// non-trunk — never guessed to be a variant branch. Pure.
|
|
@@ -3846,6 +3956,7 @@ async function statusComponentsCommand(flags) {
|
|
|
3846
3956
|
// supplies the facts (the last SHA it synced, each entry's base) and the comparison happens here.
|
|
3847
3957
|
response.freshness = stagingFreshness({
|
|
3848
3958
|
head: safeGitValue(cwd, 'git rev-parse HEAD'),
|
|
3959
|
+
originHead: safeGitValue(cwd, 'git rev-parse ' + shellQuote('origin/' + branchName)),
|
|
3849
3960
|
lastSyncedSha: response.branchContext && response.branchContext.lastSyncedSha,
|
|
3850
3961
|
entries: response.entries,
|
|
3851
3962
|
laneSummary: response.laneSummary,
|
|
@@ -4759,7 +4870,7 @@ function gitIsAncestor(cwd, ancestor, descendant) {
|
|
|
4759
4870
|
* `stageBaseSha`; only the local checkout can order commits, so the comparison happens here. Pure — git is
|
|
4760
4871
|
* injected as `isAncestor(a, b)` — so it can be tested without a repository.
|
|
4761
4872
|
*/
|
|
4762
|
-
function stagingFreshness({ head, lastSyncedSha, entries, laneSummary, isAncestor }) {
|
|
4873
|
+
function stagingFreshness({ head, originHead, lastSyncedSha, entries, laneSummary, isAncestor }) {
|
|
4763
4874
|
const rows = Array.isArray(entries) ? entries : [];
|
|
4764
4875
|
const staged = rows.filter((entry) => entry && entry.stageBaseSha);
|
|
4765
4876
|
const memo = new Map();
|
|
@@ -4785,7 +4896,9 @@ function stagingFreshness({ head, lastSyncedSha, entries, laneSummary, isAncesto
|
|
|
4785
4896
|
|
|
4786
4897
|
return {
|
|
4787
4898
|
head: head || null,
|
|
4899
|
+
originHead: originHead || null,
|
|
4788
4900
|
lastSyncedSha: lastSyncedSha || null,
|
|
4901
|
+
freshnessUnknownReason: !originHead ? 'branch has no upstream' : (!lastSyncedSha ? 'platform has no last synced SHA for this branch' : null),
|
|
4789
4902
|
landedSinceHead,
|
|
4790
4903
|
entriesBehindLastSync: behindLastSync.length,
|
|
4791
4904
|
entriesFromOtherCommits: fromOtherCommits.length,
|
|
@@ -4799,6 +4912,9 @@ function stagingFreshness({ head, lastSyncedSha, entries, laneSummary, isAncesto
|
|
|
4799
4912
|
function printStagingFreshness(freshness) {
|
|
4800
4913
|
if (!freshness) return;
|
|
4801
4914
|
const plural = (n) => n + ' staged entr' + (n === 1 ? 'y' : 'ies');
|
|
4915
|
+
if (freshness.freshnessUnknownReason) {
|
|
4916
|
+
console.log('Freshness unknown:', freshness.freshnessUnknownReason + '.');
|
|
4917
|
+
}
|
|
4802
4918
|
if (freshness.landedSinceHead === true) {
|
|
4803
4919
|
console.log('');
|
|
4804
4920
|
console.log('LANDED SINCE YOUR BASE: the platform last synced ' + short(freshness.lastSyncedSha) +
|
|
@@ -5759,6 +5875,84 @@ async function waitForStatus(api, cwd, accountId, branchName, taskId, token, dat
|
|
|
5759
5875
|
}
|
|
5760
5876
|
}
|
|
5761
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
|
+
|
|
5762
5956
|
async function waitForToolStatus(api, cwd, options) {
|
|
5763
5957
|
const started = Date.now();
|
|
5764
5958
|
let pollDelayMs = parsePositiveInt(options.pollIntervalMs, 1000);
|
|
@@ -5909,6 +6103,7 @@ async function testCommand(flags) {
|
|
|
5909
6103
|
printResolvedBaseUrl(baseUrl);
|
|
5910
6104
|
printStagingLane(branchName, workspace, workspaceSource(cwd, flags));
|
|
5911
6105
|
if (start.staging && Array.isArray(start.staging.accountLanes)) {
|
|
6106
|
+
printOrphanedWorkspaceWarning(start.staging);
|
|
5912
6107
|
printAccountLanes({ accountLanes: start.staging.accountLanes, branchName, workspace });
|
|
5913
6108
|
}
|
|
5914
6109
|
console.log('Test run started:', start.taskId);
|
|
@@ -5935,6 +6130,7 @@ async function testCommand(flags) {
|
|
|
5935
6130
|
console.log(JSON.stringify(status, null, 2));
|
|
5936
6131
|
} else {
|
|
5937
6132
|
console.log('Final status:', JSON.stringify(status, null, 2));
|
|
6133
|
+
printTestRunPivots(status);
|
|
5938
6134
|
}
|
|
5939
6135
|
|
|
5940
6136
|
// A selector that matched no case is a mis-specified run, not a passing one. Say so in the terminal
|
|
@@ -5965,7 +6161,8 @@ async function testCommand(flags) {
|
|
|
5965
6161
|
claim: 'Test run ' + String(testRef),
|
|
5966
6162
|
world: buildCommandWorld(status, { accountId, dataMode: status.dataMode || dataMode, dataModeSource, branchName, workspace, host: normalizeBaseUrl(baseUrl), sourceLayer: status.staging && status.staging.testComponentSource }),
|
|
5967
6163
|
revision: Object.assign(collectVerificationSource(cwd, flags), {
|
|
5968
|
-
compileSignatures: status
|
|
6164
|
+
compileSignatures: testCompileSignatures(status),
|
|
6165
|
+
componentProvenance: testComponentProvenance(status)
|
|
5969
6166
|
}),
|
|
5970
6167
|
test: {
|
|
5971
6168
|
taskId: start.taskId,
|
|
@@ -5974,16 +6171,62 @@ async function testCommand(flags) {
|
|
|
5974
6171
|
selectedCases: names,
|
|
5975
6172
|
passed: status.result && status.result.passed,
|
|
5976
6173
|
failed: status.result && status.result.failed,
|
|
6174
|
+
tests: status.result && status.result.tests,
|
|
5977
6175
|
dataModeSource: status.dataModeSource || dataModeSource,
|
|
5978
6176
|
unmatchedTestNames: unmatched
|
|
5979
6177
|
},
|
|
5980
|
-
evidenceCategories:
|
|
6178
|
+
evidenceCategories: testEvidenceCategories(status, names),
|
|
5981
6179
|
limitations: unmatched.length ? ['One or more requested test case selectors matched no case.'] : [],
|
|
5982
6180
|
rawRefs: { testStatusKey: start.taskId },
|
|
5983
6181
|
status
|
|
5984
6182
|
}, { quiet: jsonOutput });
|
|
5985
6183
|
}
|
|
5986
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
|
+
|
|
5987
6230
|
/*
|
|
5988
6231
|
## Tokens, Tools, Verification, And Config
|
|
5989
6232
|
*/
|
|
@@ -6308,8 +6551,7 @@ async function toolCommand(flags) {
|
|
|
6308
6551
|
revision: collectVerificationSource(cwd, flags),
|
|
6309
6552
|
evidenceCategories: ['tool_call'],
|
|
6310
6553
|
rawRefs: { toolResponsePath: statusResponse.responseFile, callId: requestedCallId },
|
|
6311
|
-
tool: { callId: requestedCallId,
|
|
6312
|
-
result: data
|
|
6554
|
+
tool: Object.assign(toolEvidenceSummary(data, statusResponse.responseFile), { callId: requestedCallId, name: stored && stored.name })
|
|
6313
6555
|
}, { quiet: jsonOutput });
|
|
6314
6556
|
if (jsonOutput) {
|
|
6315
6557
|
console.log(JSON.stringify(data, null, 2));
|
|
@@ -6322,7 +6564,8 @@ async function toolCommand(flags) {
|
|
|
6322
6564
|
console.log('Data mode:', data.dataMode || dataMode);
|
|
6323
6565
|
if (data.threadGroupingId) console.log('Thread grouping ID:', data.threadGroupingId);
|
|
6324
6566
|
console.log('Session log:', data.sessionLog);
|
|
6325
|
-
|
|
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) + ')' : ''));
|
|
6326
6569
|
if (polledFailed) {
|
|
6327
6570
|
console.log('Tool error:', data.toolMessage);
|
|
6328
6571
|
}
|
|
@@ -6373,6 +6616,9 @@ async function toolCommand(flags) {
|
|
|
6373
6616
|
data.sessionLog = sessionJsonlFile(cwd);
|
|
6374
6617
|
if (toolFailureMessage && !data.toolMessage) data.toolMessage = toolFailureMessage;
|
|
6375
6618
|
|
|
6619
|
+
let evidenceData = data;
|
|
6620
|
+
let evidenceResponseFile = response.responseFile;
|
|
6621
|
+
|
|
6376
6622
|
if (!jsonOutput) {
|
|
6377
6623
|
printSessionResolutionWarning(sessionContext);
|
|
6378
6624
|
printResolvedBaseUrl(baseUrl);
|
|
@@ -6390,7 +6636,8 @@ async function toolCommand(flags) {
|
|
|
6390
6636
|
console.log('Component:', `${data.componentSource}${sig}`);
|
|
6391
6637
|
}
|
|
6392
6638
|
console.log('Session log:', data.sessionLog);
|
|
6393
|
-
|
|
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) + ')' : ''));
|
|
6394
6641
|
}
|
|
6395
6642
|
|
|
6396
6643
|
// Non-zero exit so scripted/agent callers that check status notice the refusal too.
|
|
@@ -6410,9 +6657,12 @@ async function toolCommand(flags) {
|
|
|
6410
6657
|
|
|
6411
6658
|
finalStatus.data.responseFile = finalStatus.responseFile;
|
|
6412
6659
|
finalStatus.data.sessionLog = sessionJsonlFile(cwd);
|
|
6660
|
+
evidenceData = finalStatus.data;
|
|
6661
|
+
evidenceResponseFile = finalStatus.responseFile;
|
|
6413
6662
|
if (!jsonOutput) {
|
|
6414
6663
|
console.log('Final status:', finalStatus.data.status);
|
|
6415
|
-
|
|
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) + ')' : ''));
|
|
6416
6666
|
}
|
|
6417
6667
|
if (finalStatus.data.status !== 'completed') {
|
|
6418
6668
|
process.exitCode = 1;
|
|
@@ -6422,20 +6672,19 @@ async function toolCommand(flags) {
|
|
|
6422
6672
|
|
|
6423
6673
|
await appendVerificationPacket(api, cwd, session, accountId, flags, {
|
|
6424
6674
|
type: 'tool_call',
|
|
6425
|
-
success: !toolFailed && (!asyncMode ||
|
|
6675
|
+
success: asyncMode && !waitForAsync ? null : (!toolFailed && (!asyncMode || process.exitCode !== 1)),
|
|
6426
6676
|
claim: 'Tool call ' + String(toolName),
|
|
6427
|
-
world: buildCommandWorld(
|
|
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 }),
|
|
6428
6678
|
revision: Object.assign(collectVerificationSource(cwd, flags), {
|
|
6429
|
-
compileSignatures:
|
|
6679
|
+
compileSignatures: evidenceData.componentSignature ? [evidenceData.componentSignature] : []
|
|
6430
6680
|
}),
|
|
6431
6681
|
evidenceCategories: ['tool_call'],
|
|
6432
6682
|
dependencies: {
|
|
6433
6683
|
mocks: [],
|
|
6434
|
-
liveHttpCalls:
|
|
6684
|
+
liveHttpCalls: evidenceData.liveHttpCalls || []
|
|
6435
6685
|
},
|
|
6436
|
-
rawRefs: { toolResponsePath:
|
|
6437
|
-
tool: { callId, name: String(toolName), async: asyncMode
|
|
6438
|
-
result: data
|
|
6686
|
+
rawRefs: { toolResponsePath: evidenceResponseFile, callId },
|
|
6687
|
+
tool: Object.assign(toolEvidenceSummary(evidenceData, evidenceResponseFile), { callId, name: String(toolName), async: asyncMode })
|
|
6439
6688
|
}, { quiet: jsonOutput });
|
|
6440
6689
|
if (jsonOutput) {
|
|
6441
6690
|
console.log(JSON.stringify(data, null, 2));
|
|
@@ -6522,7 +6771,8 @@ async function verifyCommand(flags, subcommand) {
|
|
|
6522
6771
|
source.componentBranch = verifiedWorld.componentBranch;
|
|
6523
6772
|
source.sourceLayer = verifiedWorld.sourceLayer;
|
|
6524
6773
|
if (verifiedWorld.variantBranchSource) source.variantBranchSource = verifiedWorld.variantBranchSource;
|
|
6525
|
-
source.
|
|
6774
|
+
source.laneContentHash = laneContentHashFrom(statusResponse);
|
|
6775
|
+
source.stagedOverlayHash = source.laneContentHash;
|
|
6526
6776
|
}
|
|
6527
6777
|
|
|
6528
6778
|
const response = await postVerificationCommand(api, cwd, session, accountId, 'start', {
|
|
@@ -6537,6 +6787,7 @@ async function verifyCommand(flags, subcommand) {
|
|
|
6537
6787
|
writeActiveVerificationEnvelope(cwd, envelope.envelopeId, activeContext);
|
|
6538
6788
|
console.log('Verification envelope started:', envelope.envelopeId);
|
|
6539
6789
|
console.log('Target:', 'account=' + accountId + ', branch=' + branchName + ', workspace=' + (workspace || 'shared') + ', dataMode=' + dataMode);
|
|
6790
|
+
printAcceptanceWarnings(envelope);
|
|
6540
6791
|
console.log('Local mirror:', verificationPaths(cwd, envelope.envelopeId).base);
|
|
6541
6792
|
return envelope;
|
|
6542
6793
|
}
|
|
@@ -6577,6 +6828,7 @@ async function verifyCommand(flags, subcommand) {
|
|
|
6577
6828
|
writeLocalVerificationEnvelope(cwd, response.envelope);
|
|
6578
6829
|
console.log('Manifest attached to envelope:', envelopeId);
|
|
6579
6830
|
console.log('Required evidence:', (((response.envelope || {}).acceptance || {}).requiredEvidence || []).length);
|
|
6831
|
+
printAcceptanceWarnings(response.envelope);
|
|
6580
6832
|
return response.envelope;
|
|
6581
6833
|
}
|
|
6582
6834
|
|
|
@@ -12337,11 +12589,13 @@ function printComponentsHelp(subcommand) {
|
|
|
12337
12589
|
|
|
12338
12590
|
function printTestHelp() {
|
|
12339
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]');
|
|
12340
12593
|
console.log('');
|
|
12341
12594
|
console.log('Runs a Test component against the staged/variant world for this checkout.');
|
|
12342
12595
|
console.log('Examples:');
|
|
12343
12596
|
console.log(' remits-cli test run --test 11');
|
|
12344
12597
|
console.log(' remits-cli test run --test "Merchant Statements" --names "managed account case"');
|
|
12598
|
+
console.log(' remits-cli test status --task-id 7a1b...');
|
|
12345
12599
|
console.log('');
|
|
12346
12600
|
console.log('Notes:');
|
|
12347
12601
|
console.log(' --names is delimited by "|" (comma also works when no "|" is present), and may be repeated.');
|
|
@@ -12728,6 +12982,10 @@ async function main() {
|
|
|
12728
12982
|
await testCommand(args);
|
|
12729
12983
|
return;
|
|
12730
12984
|
}
|
|
12985
|
+
if (command === 'test' && subcommand === 'status') {
|
|
12986
|
+
await testStatusCommand(args);
|
|
12987
|
+
return;
|
|
12988
|
+
}
|
|
12731
12989
|
|
|
12732
12990
|
if (command === 'token') {
|
|
12733
12991
|
await tokenCommand(args);
|
package/package.json
CHANGED
|
@@ -207,6 +207,7 @@ remits-cli components branch <name> --subscribe <accountId> [--parent-account <i
|
|
|
207
207
|
remits-cli components branch <name> --unsubscribe <accountId> # return that account to trunk
|
|
208
208
|
remits-cli components branch <name> --retire [--force] # delete the branch's overlays
|
|
209
209
|
remits-cli test run --test <id|name> [--branch <stagingScope>] [--names "a|b"] [--watch true|false] [--data-mode test|prod] [--as-account <ID>] [--variant-branch <name|none>] [--json]
|
|
210
|
+
remits-cli test status --task-id <taskId> [--branch <stagingScope>] [--data-mode test|prod] [--json]
|
|
210
211
|
remits-cli token [--path <embeddablePathOrId>] [--data-mode test|prod] [--as-account <ID>] [--variant-branch <name|none>]
|
|
211
212
|
remits-cli token inspect --token <token|tokenKey|URL> # inspect token metadata, safety/dataMode evidence, and full context
|
|
212
213
|
remits-cli tools [--branch <name>] [--data-mode test|prod] [--variant-branch <name|none>]
|
|
@@ -268,6 +269,23 @@ remits-cli verify report
|
|
|
268
269
|
Existing commands also accept `--verify-envelope <id>` to attach to a specific envelope and
|
|
269
270
|
`--no-verify-envelope` to suppress automatic attachment for one command.
|
|
270
271
|
|
|
272
|
+
Failed `remits-cli test run` output includes compact pivots per failed case: duration, trace id,
|
|
273
|
+
bounded `report(...)` diagnostics, live HTTP signals, and resolved component provenance when the
|
|
274
|
+
platform returns it. Re-read an existing run with `remits-cli test status --task-id <taskId>` before
|
|
275
|
+
rerunning a long suite.
|
|
276
|
+
|
|
277
|
+
Test-run requirements must name the suite/cases they mean:
|
|
278
|
+
|
|
279
|
+
```json
|
|
280
|
+
{"id":"api_flow", "packetType":"test_run", "suite":"Statement API Flow", "allCases":true}
|
|
281
|
+
{"id":"one_case", "packetType":"test_run", "suite":"Statement API Flow", "cases":["rejects duplicated fee evidence"]}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
`verify start` and `verify manifest` warn when a requirement has a `packetType` but no discriminator.
|
|
285
|
+
The evaluator treats pending async tool packets as pending, not passing; a later `tool status` packet is
|
|
286
|
+
the proof. Tool packets store the response file path, byte size and hash rather than copying the whole
|
|
287
|
+
tool payload into the envelope.
|
|
288
|
+
|
|
271
289
|
Use `remits-cli verify list` to review open envelopes for the account without SQL. If a newer envelope
|
|
272
290
|
replaces an older one, use `verify supersede`; if a duplicate or abandoned attempt should no longer read
|
|
273
291
|
as live work, use `verify abandon`. Both keep the evidence history.
|
|
@@ -57,9 +57,10 @@ source-layer facts the command observed. A staged test packet is proof of the st
|
|
|
57
57
|
is proof of a source transition; a token packet is proof of the browser token's resolution tuple. Those
|
|
58
58
|
are not interchangeable.
|
|
59
59
|
|
|
60
|
-
Use `remits-cli verify report` before summarizing the work. It will
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
Use `remits-cli verify report` before summarizing the work. It will say when the evidence only covered
|
|
61
|
+
staged source, when committed variant/trunk proof is missing, when the manifest world does not match the
|
|
62
|
+
packet world, or when later packet facts made an earlier proof stale. Packets carry a lane content hash,
|
|
63
|
+
git head, workspace, data lane, and variant-world facts so a re-stage or branch move is visible.
|
|
63
64
|
|
|
64
65
|
### Staging cache key format
|
|
65
66
|
|
|
@@ -212,6 +213,9 @@ commit write `ComponentVariant` overlays for a branch nobody subscribes to).
|
|
|
212
213
|
- `remits-cli components clear --all` is scoped to YOUR lane and never touches another agent's.
|
|
213
214
|
- In lane rows, `currentLane` (also `mine`) marks THIS command's lane; `ownedByCaller` marks every lane
|
|
214
215
|
staged by your CLI user — your other clones' agents included.
|
|
216
|
+
- If your current lane is empty but the same workspace has staged entries on another branch, the CLI
|
|
217
|
+
prints a warning. That usually means the checkout switched branches after staging; re-stage on this
|
|
218
|
+
branch or switch back.
|
|
215
219
|
- **A landing clears only the lander's lane.** Every stage records the commit it came from
|
|
216
220
|
(`stageBaseSha`), and `components status` compares that with the last commit the platform synced for the
|
|
217
221
|
branch (`branchContext.lastSyncedSha`) using your local git:
|
|
@@ -97,6 +97,17 @@ remits-cli verify report
|
|
|
97
97
|
The report is the final-response source. It separates verified claims, missing evidence, stale packets,
|
|
98
98
|
and the source/account/lane tuple, so do not replace it with a generic "verified" sentence.
|
|
99
99
|
|
|
100
|
+
For Test requirements, be specific enough for the evaluator to know what a pass means:
|
|
101
|
+
|
|
102
|
+
```json
|
|
103
|
+
{"id":"api_flow", "packetType":"test_run", "suite":"Statement API Flow", "allCases":true}
|
|
104
|
+
{"id":"duplicate_fee_case", "packetType":"test_run", "suite":"Statement API Flow", "cases":["rejects duplicated fee evidence"]}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
A requirement that says only `{"packetType":"test_run"}` is intentionally only a warning-worthy sketch:
|
|
108
|
+
it will not turn a random Test packet into acceptance. Full-suite runs emit suite and passed-case
|
|
109
|
+
categories automatically, so case-level requirements can be satisfied by a real full run.
|
|
110
|
+
|
|
100
111
|
## Development Workflow
|
|
101
112
|
|
|
102
113
|
### The Golden Rule: Writing Code Is Not Finishing the Job
|
|
@@ -327,9 +338,13 @@ This applies to:
|
|
|
327
338
|
```bash
|
|
328
339
|
remits-cli test run --test <TEST_ID_OR_NAME>
|
|
329
340
|
remits-cli test run --test "Invoice Tests" --names "specific test case"
|
|
341
|
+
remits-cli test status --task-id <TASK_ID>
|
|
330
342
|
```
|
|
331
343
|
|
|
332
|
-
Tests run on the platform against your staged snapshot. They stream results in real-time. If they fail,
|
|
344
|
+
Tests run on the platform against your staged snapshot. They stream results in real-time. If they fail,
|
|
345
|
+
read the printed pivots first: failed cases include timing, trace ids, bounded `report(...)`
|
|
346
|
+
diagnostics, live HTTP signals, and resolved component provenance when available. Fix the code,
|
|
347
|
+
re-stage, and re-run only after those pivots explain the failure.
|
|
333
348
|
|
|
334
349
|
Important test-runner constraints:
|
|
335
350
|
- `remits-cli test run` now defaults to `test` dataMode unless you explicitly pass `--data-mode prod`.
|