@remits/remits-cli 0.1.127 → 0.1.128
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/README.md +3 -2
- package/index.js +378 -40
- package/package.json +1 -1
- package/skills/remits-cli/references/cli-state.md +5 -0
- package/skills/remits-cli/references/command-reference.md +31 -14
- package/skills/remits-cli/references/component-resolution.md +7 -2
- package/skills/remits-cli/references/development-loop.md +7 -3
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ remits-cli components stage # FULL SNAPSHOT of the repo into t
|
|
|
28
28
|
remits-cli components status
|
|
29
29
|
remits-cli components clear
|
|
30
30
|
remits-cli test run --test 45
|
|
31
|
-
remits-cli test run --test "My New Test" --names "test case 1
|
|
31
|
+
remits-cli test run --test "My New Test" --names "test case 1|test case 2"
|
|
32
32
|
git add -A
|
|
33
33
|
git commit -m "sync passing changes"
|
|
34
34
|
git push
|
|
@@ -98,7 +98,8 @@ remits-cli install --skills --overwrite true
|
|
|
98
98
|
- Branch defaults to the current local git branch.
|
|
99
99
|
- Data mode defaults to `test`. For `remits-cli test run`, that default is now enforced even if the most-recent authenticated session for the account is `prod`; a production test run therefore requires an explicit `--data-mode prod` on the command line. Use `remits-cli data-mode set prod` only for production investigation.
|
|
100
100
|
- If the same account is authenticated against more than one host and you omit `--base-url`, the CLI auto-resolves the best matching session and now prints the resolved host. Pass `--base-url` explicitly whenever the target host matters.
|
|
101
|
-
-
|
|
101
|
+
- `--names` is `|`-delimited and may be repeated. A comma still splits a single value for compatibility,
|
|
102
|
+
so a case name containing a comma should be passed with `|` or by repeating `--names`.
|
|
102
103
|
- Nested help is available before required-argument validation, including `remits-cli test run --help`, `remits-cli components sync --help`, and `remits-cli tool --help`.
|
|
103
104
|
- `components sync --safe` is the recommended agent path on a variant branch. It expands to `--summary --changed-only --fail-on-errors --fail-on-removed`, resolves `--changed-since` from the branch's merge base with trunk when you did not name one (local refs only; it never runs an implicit `git fetch`), and prints the planned writes before mutating unless `--yes` is passed. On trunk there is no plan to gate, so it states what a trunk reconcile does and requires `--yes`.
|
|
104
105
|
- `components sync` has fail-closed safety gates for unattended/agent use. Each exits non-zero instead of printing a wall of JSON: `--changed-only` (fail unless every planned write is a component this checkout edited), `--names-only` (print only `BUCKET type:id name` lines), `--fail-on-removed`, `--fail-on-errors`, and `--expected-removed <type:id>` (repeatable or comma-delimited; implies `--fail-on-removed`, so any removal you did not name fails). `--changed-only` also fails closed when the checkout is not a git working tree, because "git could not answer" must never be read as "nothing changed".
|
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
|
+
- L1399 Account Repos, Guide Sync, And Platform Repo
|
|
10
|
+
- L2057 Component Discovery And HTTP Logging
|
|
11
|
+
- L2549 Skill Delivery And TOC Resolution
|
|
12
|
+
- L2859 Auth And Component Staging
|
|
13
|
+
- L3457 Component Summaries, Status, And Sync Gates
|
|
14
|
+
- L5323 Branches, Promotion, Commit, And Test Runs
|
|
15
|
+
- L6562 Tokens, Tools, Verification, And Config
|
|
16
|
+
- L7478 Service Dashboard And WebSocket Listener
|
|
17
|
+
- L9573 Agent And Ticket Workflows
|
|
18
|
+
- L12631 Help, Auto Update, And Command Dispatch
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
/*
|
|
@@ -821,6 +821,22 @@ function fileEvidence(pathname) {
|
|
|
821
821
|
};
|
|
822
822
|
}
|
|
823
823
|
|
|
824
|
+
const LARGE_TOOL_RESPONSE_BYTES = 200 * 1024;
|
|
825
|
+
|
|
826
|
+
function formatToolResponseFile(pathname) {
|
|
827
|
+
const evidence = fileEvidence(pathname);
|
|
828
|
+
if (!evidence.bytes) return String(pathname || '');
|
|
829
|
+
return pathname + ' (' + evidence.bytes + ' bytes, sha256 ' + evidence.sha256.slice(0, 12) + ')';
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
function printToolResponseFileLine(label, pathname) {
|
|
833
|
+
console.log(label + ':', formatToolResponseFile(pathname));
|
|
834
|
+
const evidence = fileEvidence(pathname);
|
|
835
|
+
if (evidence.bytes && evidence.bytes >= LARGE_TOOL_RESPONSE_BYTES) {
|
|
836
|
+
console.log('Large response: inspect selectively with jq or targeted offset reads instead of opening the whole file.');
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
|
|
824
840
|
function boundedToolExcerpt(data = {}) {
|
|
825
841
|
const result = data.result && typeof data.result === 'object' ? data.result : {};
|
|
826
842
|
const candidates = [
|
|
@@ -982,6 +998,35 @@ function readLocalVerificationEnvelope(cwd, envelopeId) {
|
|
|
982
998
|
}
|
|
983
999
|
}
|
|
984
1000
|
|
|
1001
|
+
function readLocalVerificationPackets(cwd, envelopeId, filters = {}) {
|
|
1002
|
+
const paths = verificationPaths(cwd, envelopeId);
|
|
1003
|
+
const packets = [];
|
|
1004
|
+
if (fs.existsSync(paths.packetsDir)) {
|
|
1005
|
+
fs.readdirSync(paths.packetsDir)
|
|
1006
|
+
.filter((name) => name.endsWith('.json'))
|
|
1007
|
+
.forEach((name) => {
|
|
1008
|
+
try {
|
|
1009
|
+
const parsed = JSON.parse(fs.readFileSync(path.join(paths.packetsDir, name), 'utf8'));
|
|
1010
|
+
if (parsed && typeof parsed === 'object') packets.push(parsed);
|
|
1011
|
+
} catch (_) {
|
|
1012
|
+
// Ignore one malformed local packet; the rest of the envelope remains useful.
|
|
1013
|
+
}
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
if (!packets.length) {
|
|
1017
|
+
const envelope = readLocalVerificationEnvelope(cwd, envelopeId);
|
|
1018
|
+
if (envelope && Array.isArray(envelope.packets)) packets.push(...envelope.packets);
|
|
1019
|
+
}
|
|
1020
|
+
const type = filters.type || filters.packetType;
|
|
1021
|
+
const suite = filters.suite || filters.testName;
|
|
1022
|
+
const max = parsePositiveInt(filters.max, 50);
|
|
1023
|
+
const filtered = packets
|
|
1024
|
+
.filter((packet) => !type || packet.type === type)
|
|
1025
|
+
.filter((packet) => !suite || testRunSuiteNameFromPacket(packet) === String(suite))
|
|
1026
|
+
.sort((a, b) => Number(a.createdAt || 0) - Number(b.createdAt || 0));
|
|
1027
|
+
return filtered.slice(Math.max(0, filtered.length - max));
|
|
1028
|
+
}
|
|
1029
|
+
|
|
985
1030
|
function writeLocalVerificationPacket(cwd, envelopeId, packet) {
|
|
986
1031
|
if (!envelopeId || !packet) return null;
|
|
987
1032
|
const paths = verificationPaths(cwd, envelopeId);
|
|
@@ -1103,11 +1148,13 @@ function printVerificationSummaryLine(env) {
|
|
|
1103
1148
|
const failures = env.currentFailureCount ? ' currentFailures=' + env.currentFailureCount : (env.failedCount ? ' failed=' + env.failedCount : '');
|
|
1104
1149
|
const required = ' required=' + (env.satisfiedCount || 0) + '/' + (env.requiredCount || 0);
|
|
1105
1150
|
const noManifest = env.noRequiredEvidence ? ' no-manifest' : '';
|
|
1151
|
+
const stale = env.staleCount ? ' stale=' + env.staleCount : '';
|
|
1152
|
+
const changed = env.requirementsChangedAfterEvidence ? ' requirements-changed' : '';
|
|
1106
1153
|
const lane = [env.branchName || 'unknown-branch', env.workspace ? 'ws:' + env.workspace : 'shared', env.dataMode || null, env.sourceLayer || null]
|
|
1107
1154
|
.filter(Boolean).join(' | ');
|
|
1108
|
-
console.log('- ' + (env.envelopeId || '(no id)') + ' ' + (env.status || 'unknown') + health + failures + required + noManifest);
|
|
1155
|
+
console.log('- ' + (env.envelopeId || '(no id)') + ' ' + (env.status || 'unknown') + health + failures + required + stale + noManifest + changed);
|
|
1109
1156
|
if (env.summary) console.log(' ' + env.summary);
|
|
1110
|
-
console.log(' ' + lane + ' packets=' + (env.packetCount || 0) + ' updated=' + formatTime(env.updatedAtMs || env.latestPacketAtMs));
|
|
1157
|
+
console.log(' ' + lane + ' packets=' + (env.packetCount || 0) + ' lastPacket=' + formatTime(env.lastPacketAtMs || env.latestPacketAtMs) + ' updated=' + formatTime(env.updatedAtMs || env.latestPacketAtMs));
|
|
1111
1158
|
}
|
|
1112
1159
|
|
|
1113
1160
|
function printVerificationEnvelope(envelope, fallbackEnvelopeId) {
|
|
@@ -1124,6 +1171,12 @@ function printVerificationEnvelope(envelope, fallbackEnvelopeId) {
|
|
|
1124
1171
|
if (evaluation.failedCount) {
|
|
1125
1172
|
console.log('Failed packets:', evaluation.failedCount, 'current failures:', evaluation.currentFailureCount || 0);
|
|
1126
1173
|
}
|
|
1174
|
+
if (evaluation.requirementsChangedAfterEvidence) {
|
|
1175
|
+
console.log('Requirements changed after evidence was collected.');
|
|
1176
|
+
}
|
|
1177
|
+
if (Array.isArray(evaluation.stale) && evaluation.stale.length) {
|
|
1178
|
+
console.log('Stale packets:', evaluation.stale.length);
|
|
1179
|
+
}
|
|
1127
1180
|
const packets = Array.isArray(envelope.packets) ? envelope.packets : [];
|
|
1128
1181
|
console.log('Packets:', packets.length);
|
|
1129
1182
|
packets.slice(-20).forEach((packet) => {
|
|
@@ -1147,6 +1200,56 @@ function printAcceptanceWarnings(envelope) {
|
|
|
1147
1200
|
});
|
|
1148
1201
|
}
|
|
1149
1202
|
|
|
1203
|
+
function printEnvelopeWarnings(envelope) {
|
|
1204
|
+
const warnings = envelope && Array.isArray(envelope.warnings) ? envelope.warnings : [];
|
|
1205
|
+
if (!warnings.length) return;
|
|
1206
|
+
console.log('Envelope warnings:');
|
|
1207
|
+
warnings.forEach((warning) => {
|
|
1208
|
+
console.log('- ' + (warning.message || warning.code || JSON.stringify(warning)));
|
|
1209
|
+
if (Array.isArray(warning.envelopes) && warning.envelopes.length) {
|
|
1210
|
+
warning.envelopes.forEach((env) => {
|
|
1211
|
+
console.log(' ' + (env.envelopeId || '(no id)') + (env.status ? ' ' + env.status : ''));
|
|
1212
|
+
});
|
|
1213
|
+
}
|
|
1214
|
+
});
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
function manifestRequiredEvidence(manifest = {}) {
|
|
1218
|
+
const required = [];
|
|
1219
|
+
if (Array.isArray(manifest.requiredEvidence)) required.push(...manifest.requiredEvidence);
|
|
1220
|
+
const journeys = Array.isArray(manifest.journeys) ? manifest.journeys :
|
|
1221
|
+
(Array.isArray(manifest.requestedJourneys) ? manifest.requestedJourneys : []);
|
|
1222
|
+
journeys.forEach((journey) => {
|
|
1223
|
+
if (journey && Array.isArray(journey.requiredEvidence)) required.push(...journey.requiredEvidence);
|
|
1224
|
+
});
|
|
1225
|
+
return required;
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
async function readVerificationEnvelopeForDiagnostics(api, cwd, session, accountId, envelopeId) {
|
|
1229
|
+
if (!envelopeId) return null;
|
|
1230
|
+
try {
|
|
1231
|
+
const response = await postVerificationCommand(api, cwd, session, accountId, 'show', { envelopeId });
|
|
1232
|
+
if (response.envelope) {
|
|
1233
|
+
writeLocalVerificationEnvelope(cwd, response.envelope);
|
|
1234
|
+
return response.envelope;
|
|
1235
|
+
}
|
|
1236
|
+
} catch (_) {
|
|
1237
|
+
// Local evidence is still useful for diagnostics when the platform read fails.
|
|
1238
|
+
}
|
|
1239
|
+
return readLocalVerificationEnvelope(cwd, envelopeId);
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
async function readVerificationPacketsForDiagnostics(api, cwd, session, accountId, envelopeId, filters = {}) {
|
|
1243
|
+
if (!envelopeId) return [];
|
|
1244
|
+
try {
|
|
1245
|
+
const response = await postVerificationCommand(api, cwd, session, accountId, 'packets', Object.assign({ envelopeId }, filters));
|
|
1246
|
+
if (Array.isArray(response.packets)) return response.packets;
|
|
1247
|
+
} catch (_) {
|
|
1248
|
+
// Local full-fidelity packet files remain the fallback when the platform read is unavailable.
|
|
1249
|
+
}
|
|
1250
|
+
return readLocalVerificationPackets(cwd, envelopeId, filters);
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1150
1253
|
function buildCommandWorld(response, fallback = {}) {
|
|
1151
1254
|
const source = (response && response.staging) || {};
|
|
1152
1255
|
const resolution = (response && response.resolution) || {};
|
|
@@ -2395,21 +2498,23 @@ async function loggedPost(api, cwd, endpoint, payload, options = {}) {
|
|
|
2395
2498
|
error = err;
|
|
2396
2499
|
throw err;
|
|
2397
2500
|
} finally {
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2501
|
+
if (!options.skipSessionLog) {
|
|
2502
|
+
const status = error ? 'error' : 'success';
|
|
2503
|
+
const responseForLog = responseFile
|
|
2504
|
+
? { responseFile, note: 'Tool response stored externally' }
|
|
2505
|
+
: sanitizeForLog(responseData);
|
|
2506
|
+
appendSessionLog(cwd, {
|
|
2507
|
+
ts: new Date().toISOString(),
|
|
2508
|
+
requestId,
|
|
2509
|
+
endpoint,
|
|
2510
|
+
method: 'POST',
|
|
2511
|
+
status,
|
|
2512
|
+
durationMs: Date.now() - started,
|
|
2513
|
+
request: sanitizeForLog(payload),
|
|
2514
|
+
response: responseForLog,
|
|
2515
|
+
error: error ? describeError(error) : null
|
|
2516
|
+
});
|
|
2517
|
+
}
|
|
2413
2518
|
}
|
|
2414
2519
|
}
|
|
2415
2520
|
|
|
@@ -3598,6 +3703,7 @@ function describeStageMode(mode) {
|
|
|
3598
3703
|
function printStatusSummary(response, flags) {
|
|
3599
3704
|
printBranchContext(response);
|
|
3600
3705
|
printStagingLane(response.branchName, response.workspace, null);
|
|
3706
|
+
printStagingLaneOwnerNotice(response);
|
|
3601
3707
|
printLaneSummary(response);
|
|
3602
3708
|
printOrphanedWorkspaceWarning(response);
|
|
3603
3709
|
printStagingFreshness(response.freshness);
|
|
@@ -3625,6 +3731,28 @@ function printStatusSummary(response, flags) {
|
|
|
3625
3731
|
printComponentCommandResponse('Components staging', response, flags);
|
|
3626
3732
|
}
|
|
3627
3733
|
|
|
3734
|
+
function stagingLaneOwnerNotice(response = {}) {
|
|
3735
|
+
const owner = response.laneOwnerAccountId != null ? response.laneOwnerAccountId :
|
|
3736
|
+
(response.staging && response.staging.laneOwnerAccountId);
|
|
3737
|
+
const checkout = response.checkoutAccountId != null ? response.checkoutAccountId :
|
|
3738
|
+
(response.accountId != null ? response.accountId : (response.staging && response.staging.checkoutAccountId));
|
|
3739
|
+
const stagedCount = Number(response.stagedCount != null ? response.stagedCount :
|
|
3740
|
+
(response.laneSummary && response.laneSummary.stagedCount != null ? response.laneSummary.stagedCount :
|
|
3741
|
+
(response.staging && response.staging.stagedCount != null ? response.staging.stagedCount :
|
|
3742
|
+
(response.staging && response.staging.laneSummary && response.staging.laneSummary.stagedCount))));
|
|
3743
|
+
if (owner == null || checkout == null || String(owner) === String(checkout) || !(stagedCount > 0)) {
|
|
3744
|
+
return [];
|
|
3745
|
+
}
|
|
3746
|
+
return [
|
|
3747
|
+
'WARNING: staged entries shown here are under account ' + owner + ', not checkout account ' + checkout + '.',
|
|
3748
|
+
' clear with: remits-cli components clear --all --account-id ' + owner
|
|
3749
|
+
];
|
|
3750
|
+
}
|
|
3751
|
+
|
|
3752
|
+
function printStagingLaneOwnerNotice(response = {}) {
|
|
3753
|
+
stagingLaneOwnerNotice(response).forEach((line) => console.log(line));
|
|
3754
|
+
}
|
|
3755
|
+
|
|
3628
3756
|
function printLanesSummary(response, flags) {
|
|
3629
3757
|
printBranchContext(response);
|
|
3630
3758
|
const lanes = Array.isArray(response.lanes) ? response.lanes : (Array.isArray(response.accountLanes) ? response.accountLanes : []);
|
|
@@ -3999,19 +4127,25 @@ async function lanesComponentsCommand(flags) {
|
|
|
3999
4127
|
const dataMode = resolveDataMode(flags, session);
|
|
4000
4128
|
const api = buildAxios(baseUrl, session.token);
|
|
4001
4129
|
|
|
4002
|
-
const
|
|
4130
|
+
const requestPayload = {
|
|
4003
4131
|
token: session.token,
|
|
4004
4132
|
accountId,
|
|
4005
4133
|
branchName,
|
|
4006
4134
|
workspace,
|
|
4007
4135
|
dataMode,
|
|
4008
4136
|
mode: 'lanes'
|
|
4009
|
-
}
|
|
4137
|
+
};
|
|
4138
|
+
|
|
4139
|
+
let response = await loggedPost(api, cwd, '/cli/components', requestPayload).then((r) => r.data);
|
|
4010
4140
|
|
|
4011
4141
|
if (!response.success) {
|
|
4012
4142
|
throw new Error(response.message || 'Staging lanes failed');
|
|
4013
4143
|
}
|
|
4014
4144
|
|
|
4145
|
+
if (flagEnabled(flags['wait-change']) || flagEnabled(flags.waitChange)) {
|
|
4146
|
+
response = await waitForLaneChange(api, cwd, requestPayload, response, flags);
|
|
4147
|
+
}
|
|
4148
|
+
|
|
4015
4149
|
if (flagEnabled(flags.json)) {
|
|
4016
4150
|
console.log(JSON.stringify(response, null, 2));
|
|
4017
4151
|
return response;
|
|
@@ -4024,6 +4158,55 @@ async function lanesComponentsCommand(flags) {
|
|
|
4024
4158
|
return response;
|
|
4025
4159
|
}
|
|
4026
4160
|
|
|
4161
|
+
function laneWaitSignature(response, laneId) {
|
|
4162
|
+
const lanes = Array.isArray(response && response.lanes)
|
|
4163
|
+
? response.lanes
|
|
4164
|
+
: (Array.isArray(response && response.accountLanes) ? response.accountLanes : []);
|
|
4165
|
+
const selected = laneId ? lanes.filter((lane) => String(lane.laneId || '') === String(laneId)) : lanes;
|
|
4166
|
+
return stableStringify(selected.map((lane) => ({
|
|
4167
|
+
laneId: lane.laneId,
|
|
4168
|
+
accountId: lane.accountId,
|
|
4169
|
+
branchName: lane.branchName,
|
|
4170
|
+
workspace: lane.workspace || null,
|
|
4171
|
+
stagedCount: lane.stagedCountAsOf || lane.stagedCount || 0,
|
|
4172
|
+
contentHash: lane.contentHash || (lane.laneSummary && lane.laneSummary.contentHash) || null,
|
|
4173
|
+
updatedAtMs: lane.updatedAtMs || null
|
|
4174
|
+
})));
|
|
4175
|
+
}
|
|
4176
|
+
|
|
4177
|
+
async function waitForLaneChange(api, cwd, requestPayload, initialResponse, flags) {
|
|
4178
|
+
const laneId = flags['lane-id'] || flags.laneId || flags.lane;
|
|
4179
|
+
const timeoutSeconds = parsePositiveInt(flags.timeout || flags['timeout-seconds'] || flags.timeoutSeconds, 600);
|
|
4180
|
+
const pollMs = parsePositiveInt(flags['poll-ms'] || flags.pollMs, 2000);
|
|
4181
|
+
const started = Date.now();
|
|
4182
|
+
const initialSignature = laneWaitSignature(initialResponse, laneId);
|
|
4183
|
+
if (!flagEnabled(flags.json)) {
|
|
4184
|
+
console.log('Waiting for staging lane change' + (laneId ? ' on ' + laneId : '') + ' for up to ' + timeoutSeconds + 's...');
|
|
4185
|
+
}
|
|
4186
|
+
while (Date.now() - started < timeoutSeconds * 1000) {
|
|
4187
|
+
await new Promise((resolve) => setTimeout(resolve, pollMs));
|
|
4188
|
+
const next = await loggedPost(api, cwd, '/cli/components', requestPayload, { skipSessionLog: true }).then((r) => r.data);
|
|
4189
|
+
if (!next.success) {
|
|
4190
|
+
throw new Error(next.message || 'Staging lanes failed while waiting for a change');
|
|
4191
|
+
}
|
|
4192
|
+
if (laneWaitSignature(next, laneId) !== initialSignature) {
|
|
4193
|
+
next.waitChange = {
|
|
4194
|
+
changed: true,
|
|
4195
|
+
laneId: laneId || null,
|
|
4196
|
+
waitedMs: Date.now() - started
|
|
4197
|
+
};
|
|
4198
|
+
return next;
|
|
4199
|
+
}
|
|
4200
|
+
}
|
|
4201
|
+
initialResponse.waitChange = {
|
|
4202
|
+
changed: false,
|
|
4203
|
+
laneId: laneId || null,
|
|
4204
|
+
waitedMs: Date.now() - started,
|
|
4205
|
+
timeoutSeconds
|
|
4206
|
+
};
|
|
4207
|
+
return initialResponse;
|
|
4208
|
+
}
|
|
4209
|
+
|
|
4027
4210
|
async function entriesComponentsCommand(flags) {
|
|
4028
4211
|
const cwd = process.cwd();
|
|
4029
4212
|
ensureLocalState(cwd);
|
|
@@ -4219,6 +4402,30 @@ async function syncComponentsCommand(rawFlags) {
|
|
|
4219
4402
|
}
|
|
4220
4403
|
throw new Error(mismatch + ' Nothing was synced.');
|
|
4221
4404
|
}
|
|
4405
|
+
const featureRefusal = !namesOnly ? featureBranchSyncRefusal(branchContext, branchName, flags) : null;
|
|
4406
|
+
if (featureRefusal) {
|
|
4407
|
+
if (flagEnabled(flags.json)) {
|
|
4408
|
+
const refusal = {
|
|
4409
|
+
success: false,
|
|
4410
|
+
mode: 'sync',
|
|
4411
|
+
dataMode,
|
|
4412
|
+
accountId,
|
|
4413
|
+
branchName,
|
|
4414
|
+
workspace,
|
|
4415
|
+
dryRun,
|
|
4416
|
+
safe,
|
|
4417
|
+
branchContext,
|
|
4418
|
+
repositoryCheck: preflightRepoCheck,
|
|
4419
|
+
refusal: 'feature_branch_landing',
|
|
4420
|
+
gateViolations: [featureRefusal],
|
|
4421
|
+
message: featureRefusal
|
|
4422
|
+
};
|
|
4423
|
+
console.log(JSON.stringify(refusal, null, 2));
|
|
4424
|
+
process.exitCode = 1;
|
|
4425
|
+
return refusal;
|
|
4426
|
+
}
|
|
4427
|
+
throw new Error(featureRefusal + ' Nothing was synced.');
|
|
4428
|
+
}
|
|
4222
4429
|
if (branchContextIsTrunk(branchContext)) {
|
|
4223
4430
|
// Trunk has no dry-run plan to gate on, and pretending otherwise would be worse than refusing:
|
|
4224
4431
|
// an agent would read "safe" and get an ungated authoritative reconcile. Say what trunk sync is
|
|
@@ -4781,6 +4988,15 @@ function featureBranchCommitRefusal(branchContext, branchName, flags) {
|
|
|
4781
4988
|
'deliberately creating a NEW variant branch, re-run with --create-variant-branch.';
|
|
4782
4989
|
}
|
|
4783
4990
|
|
|
4991
|
+
function featureBranchSyncRefusal(branchContext, branchName, flags) {
|
|
4992
|
+
if (!branchContext || branchContext.variantBranchSource !== 'subscription-fallback') return null;
|
|
4993
|
+
if (flags && (flags['create-variant-branch'] === true || flags['create-variant-branch'] === 'true')) return null;
|
|
4994
|
+
return 'components sync refused: \'' + branchName + '\' is not a variant branch (no committed variants, no ' +
|
|
4995
|
+
'subscribers). ' + featureBranchLandingHint(branchContext) + ' Syncing here would write overlays for \'' +
|
|
4996
|
+
branchName + '\' that nobody subscribes to, and flip every other lane on that branch to them. If you are ' +
|
|
4997
|
+
'deliberately creating a NEW variant branch, re-run with --create-variant-branch.';
|
|
4998
|
+
}
|
|
4999
|
+
|
|
4784
5000
|
// What a verification envelope records as the world it verified. Taken from the platform's answer
|
|
4785
5001
|
// (`variantWorld`, the same rule every run uses), never inferred from `onTrunk`: a feature branch cut from a
|
|
4786
5002
|
// variant branch is not on trunk, yet resolves the SUBSCRIBED branch, and labelling it by its own name recorded
|
|
@@ -5916,6 +6132,94 @@ function testCompileSignatures(status = {}) {
|
|
|
5916
6132
|
.filter(Boolean);
|
|
5917
6133
|
}
|
|
5918
6134
|
|
|
6135
|
+
function testRunSuiteNameFromStatus(status = {}) {
|
|
6136
|
+
return status.test && status.test.name ? String(status.test.name) : null;
|
|
6137
|
+
}
|
|
6138
|
+
|
|
6139
|
+
function testRunSuiteNameFromPacket(packet = {}) {
|
|
6140
|
+
return packet.test && packet.test.testName
|
|
6141
|
+
? String(packet.test.testName)
|
|
6142
|
+
: (packet.status && packet.status.test && packet.status.test.name ? String(packet.status.test.name) : null);
|
|
6143
|
+
}
|
|
6144
|
+
|
|
6145
|
+
function testCaseOutcomesFromStatusLike(status = {}) {
|
|
6146
|
+
const tests = status.result && Array.isArray(status.result.tests) ? status.result.tests :
|
|
6147
|
+
(status.test && Array.isArray(status.test.tests) ? status.test.tests : []);
|
|
6148
|
+
return tests
|
|
6149
|
+
.filter((test) => test && test.name && typeof test.passed === 'boolean')
|
|
6150
|
+
.map((test) => ({ name: String(test.name), passed: test.passed === true }));
|
|
6151
|
+
}
|
|
6152
|
+
|
|
6153
|
+
function provenanceSignature(value) {
|
|
6154
|
+
const rows = Array.isArray(value) ? value : [];
|
|
6155
|
+
return sha256(stableStringify(rows.map((entry) => ({
|
|
6156
|
+
type: entry && entry.type || null,
|
|
6157
|
+
id: entry && entry.id || null,
|
|
6158
|
+
name: entry && entry.name || null,
|
|
6159
|
+
signature: entry && entry.signature || null,
|
|
6160
|
+
source: entry && entry.source || null
|
|
6161
|
+
}))));
|
|
6162
|
+
}
|
|
6163
|
+
|
|
6164
|
+
function packetEvidenceIdentity(packet = {}) {
|
|
6165
|
+
const world = packet.world || {};
|
|
6166
|
+
const revision = packet.revision || {};
|
|
6167
|
+
const laneSummary = packet.laneSummary || (packet.componentStatus && packet.componentStatus.laneSummary) || {};
|
|
6168
|
+
return {
|
|
6169
|
+
laneContentHash: world.laneContentHash || world.stagedOverlayHash || revision.laneContentHash ||
|
|
6170
|
+
revision.stagedOverlayHash || laneSummary.contentHash || null,
|
|
6171
|
+
gitHead: revision.gitHead || null
|
|
6172
|
+
};
|
|
6173
|
+
}
|
|
6174
|
+
|
|
6175
|
+
function sameNondeterminismIdentity(left = {}, right = {}) {
|
|
6176
|
+
return !!(left.laneContentHash && right.laneContentHash && left.gitHead && right.gitHead &&
|
|
6177
|
+
String(left.laneContentHash) === String(right.laneContentHash) &&
|
|
6178
|
+
String(left.gitHead) === String(right.gitHead));
|
|
6179
|
+
}
|
|
6180
|
+
|
|
6181
|
+
function detectNondeterministicTestRun(priorPackets, currentStatus, currentProvenance, currentIdentity = {}) {
|
|
6182
|
+
const packets = Array.isArray(priorPackets) ? priorPackets : [];
|
|
6183
|
+
const suiteName = testRunSuiteNameFromStatus(currentStatus);
|
|
6184
|
+
const currentCases = testCaseOutcomesFromStatusLike(currentStatus);
|
|
6185
|
+
if (!suiteName || !currentCases.length) return null;
|
|
6186
|
+
if (!Array.isArray(currentProvenance) || !currentProvenance.length) return null;
|
|
6187
|
+
if (!currentIdentity.laneContentHash || !currentIdentity.gitHead) return null;
|
|
6188
|
+
|
|
6189
|
+
const currentSignature = provenanceSignature(currentProvenance);
|
|
6190
|
+
const currentByName = new Map(currentCases.map((outcome) => [outcome.name, outcome]));
|
|
6191
|
+
for (let i = packets.length - 1; i >= 0; i -= 1) {
|
|
6192
|
+
const packet = packets[i];
|
|
6193
|
+
if (!packet || packet.type !== 'test_run') continue;
|
|
6194
|
+
if (testRunSuiteNameFromPacket(packet) !== suiteName) continue;
|
|
6195
|
+
const previousProvenance = packet.revision && packet.revision.componentProvenance;
|
|
6196
|
+
if (!Array.isArray(previousProvenance) || !previousProvenance.length) continue;
|
|
6197
|
+
if (!sameNondeterminismIdentity(currentIdentity, packetEvidenceIdentity(packet))) continue;
|
|
6198
|
+
const previousSignature = provenanceSignature(previousProvenance);
|
|
6199
|
+
if (previousSignature !== currentSignature) continue;
|
|
6200
|
+
const previousCases = testCaseOutcomesFromStatusLike(packet.status || packet);
|
|
6201
|
+
const flips = previousCases
|
|
6202
|
+
.map((previous) => {
|
|
6203
|
+
const current = currentByName.get(previous.name);
|
|
6204
|
+
return current && current.passed !== previous.passed
|
|
6205
|
+
? { caseName: previous.name, previousPassed: previous.passed, currentPassed: current.passed, previousPacketId: packet.packetId }
|
|
6206
|
+
: null;
|
|
6207
|
+
})
|
|
6208
|
+
.filter(Boolean);
|
|
6209
|
+
if (flips.length) {
|
|
6210
|
+
return {
|
|
6211
|
+
nondeterministic: true,
|
|
6212
|
+
message: 'Outcome changed with no source change - likely nondeterministic (AI/live data).',
|
|
6213
|
+
suite: suiteName,
|
|
6214
|
+
provenanceSignature: currentSignature,
|
|
6215
|
+
previousPacketId: packet.packetId,
|
|
6216
|
+
flips
|
|
6217
|
+
};
|
|
6218
|
+
}
|
|
6219
|
+
}
|
|
6220
|
+
return null;
|
|
6221
|
+
}
|
|
6222
|
+
|
|
5919
6223
|
function printTestRunPivots(status = {}) {
|
|
5920
6224
|
const tests = status.result && Array.isArray(status.result.tests) ? status.result.tests : [];
|
|
5921
6225
|
if (!tests.length) return;
|
|
@@ -6102,6 +6406,7 @@ async function testCommand(flags) {
|
|
|
6102
6406
|
printSessionResolutionWarning(sessionContext);
|
|
6103
6407
|
printResolvedBaseUrl(baseUrl);
|
|
6104
6408
|
printStagingLane(branchName, workspace, workspaceSource(cwd, flags));
|
|
6409
|
+
printStagingLaneOwnerNotice(start.staging || {});
|
|
6105
6410
|
if (start.staging && Array.isArray(start.staging.accountLanes)) {
|
|
6106
6411
|
printOrphanedWorkspaceWarning(start.staging);
|
|
6107
6412
|
printAccountLanes({ accountLanes: start.staging.accountLanes, branchName, workspace });
|
|
@@ -6130,6 +6435,7 @@ async function testCommand(flags) {
|
|
|
6130
6435
|
console.log(JSON.stringify(status, null, 2));
|
|
6131
6436
|
} else {
|
|
6132
6437
|
console.log('Final status:', JSON.stringify(status, null, 2));
|
|
6438
|
+
printStagingLaneOwnerNotice(status.staging || {});
|
|
6133
6439
|
printTestRunPivots(status);
|
|
6134
6440
|
}
|
|
6135
6441
|
|
|
@@ -6155,15 +6461,38 @@ async function testCommand(flags) {
|
|
|
6155
6461
|
process.exitCode = 1;
|
|
6156
6462
|
}
|
|
6157
6463
|
|
|
6464
|
+
const verificationContext = activeVerificationContext(cwd, flags, session, accountId);
|
|
6465
|
+
const activeEnvelopeId = verificationEnvelopeIdForCommand(cwd, flags, verificationContext);
|
|
6466
|
+
const componentProvenance = testComponentProvenance(status);
|
|
6467
|
+
const sourceRevision = collectVerificationSource(cwd, flags);
|
|
6468
|
+
const priorPackets = await readVerificationPacketsForDiagnostics(api, cwd, session, accountId, activeEnvelopeId, {
|
|
6469
|
+
packetType: 'test_run',
|
|
6470
|
+
suite: status.test && status.test.name,
|
|
6471
|
+
max: 50
|
|
6472
|
+
});
|
|
6473
|
+
const nondeterminism = detectNondeterministicTestRun(priorPackets, status, componentProvenance, {
|
|
6474
|
+
laneContentHash: status.staging && status.staging.laneSummary && status.staging.laneSummary.contentHash,
|
|
6475
|
+
gitHead: sourceRevision.gitHead
|
|
6476
|
+
});
|
|
6477
|
+
if (nondeterminism && !jsonOutput) {
|
|
6478
|
+
console.log('Nondeterministic signal:', nondeterminism.message);
|
|
6479
|
+
nondeterminism.flips.slice(0, 6).forEach((flip) => {
|
|
6480
|
+
console.log(' - ' + flip.caseName + ': ' + (flip.previousPassed ? 'passed' : 'failed') + ' -> ' + (flip.currentPassed ? 'passed' : 'failed') +
|
|
6481
|
+
' (previous packet ' + (flip.previousPacketId || 'unknown') + ')');
|
|
6482
|
+
});
|
|
6483
|
+
}
|
|
6484
|
+
|
|
6158
6485
|
await appendVerificationPacket(api, cwd, session, accountId, flags, {
|
|
6159
6486
|
type: 'test_run',
|
|
6160
6487
|
success: status.status === 'completed' && !(status.result && status.result.failed > 0) && !unmatched.length,
|
|
6161
6488
|
claim: 'Test run ' + String(testRef),
|
|
6162
6489
|
world: buildCommandWorld(status, { accountId, dataMode: status.dataMode || dataMode, dataModeSource, branchName, workspace, host: normalizeBaseUrl(baseUrl), sourceLayer: status.staging && status.staging.testComponentSource }),
|
|
6163
|
-
revision: Object.assign(
|
|
6490
|
+
revision: Object.assign(sourceRevision, {
|
|
6164
6491
|
compileSignatures: testCompileSignatures(status),
|
|
6165
|
-
componentProvenance
|
|
6492
|
+
componentProvenance
|
|
6166
6493
|
}),
|
|
6494
|
+
nondeterministic: nondeterminism ? true : undefined,
|
|
6495
|
+
nondeterminism: nondeterminism || undefined,
|
|
6167
6496
|
test: {
|
|
6168
6497
|
taskId: start.taskId,
|
|
6169
6498
|
testId: status.test && status.test.id,
|
|
@@ -6176,7 +6505,9 @@ async function testCommand(flags) {
|
|
|
6176
6505
|
unmatchedTestNames: unmatched
|
|
6177
6506
|
},
|
|
6178
6507
|
evidenceCategories: testEvidenceCategories(status, names),
|
|
6179
|
-
limitations:
|
|
6508
|
+
limitations: []
|
|
6509
|
+
.concat(unmatched.length ? ['One or more requested test case selectors matched no case.'] : [])
|
|
6510
|
+
.concat(nondeterminism ? [nondeterminism.message] : []),
|
|
6180
6511
|
rawRefs: { testStatusKey: start.taskId },
|
|
6181
6512
|
status
|
|
6182
6513
|
}, { quiet: jsonOutput });
|
|
@@ -6564,8 +6895,7 @@ async function toolCommand(flags) {
|
|
|
6564
6895
|
console.log('Data mode:', data.dataMode || dataMode);
|
|
6565
6896
|
if (data.threadGroupingId) console.log('Thread grouping ID:', data.threadGroupingId);
|
|
6566
6897
|
console.log('Session log:', data.sessionLog);
|
|
6567
|
-
|
|
6568
|
-
console.log('Tool response file:', data.responseFile + (responseEvidence.bytes ? ' (' + responseEvidence.bytes + ' bytes, sha256 ' + responseEvidence.sha256.slice(0, 12) + ')' : ''));
|
|
6898
|
+
printToolResponseFileLine('Tool response file', data.responseFile);
|
|
6569
6899
|
if (polledFailed) {
|
|
6570
6900
|
console.log('Tool error:', data.toolMessage);
|
|
6571
6901
|
}
|
|
@@ -6636,8 +6966,7 @@ async function toolCommand(flags) {
|
|
|
6636
6966
|
console.log('Component:', `${data.componentSource}${sig}`);
|
|
6637
6967
|
}
|
|
6638
6968
|
console.log('Session log:', data.sessionLog);
|
|
6639
|
-
|
|
6640
|
-
console.log('Tool response file:', data.responseFile + (responseEvidence.bytes ? ' (' + responseEvidence.bytes + ' bytes, sha256 ' + responseEvidence.sha256.slice(0, 12) + ')' : ''));
|
|
6969
|
+
printToolResponseFileLine('Tool response file', data.responseFile);
|
|
6641
6970
|
}
|
|
6642
6971
|
|
|
6643
6972
|
// Non-zero exit so scripted/agent callers that check status notice the refusal too.
|
|
@@ -6661,8 +6990,7 @@ async function toolCommand(flags) {
|
|
|
6661
6990
|
evidenceResponseFile = finalStatus.responseFile;
|
|
6662
6991
|
if (!jsonOutput) {
|
|
6663
6992
|
console.log('Final status:', finalStatus.data.status);
|
|
6664
|
-
|
|
6665
|
-
console.log('Tool response file:', finalStatus.responseFile + (finalEvidence.bytes ? ' (' + finalEvidence.bytes + ' bytes, sha256 ' + finalEvidence.sha256.slice(0, 12) + ')' : ''));
|
|
6993
|
+
printToolResponseFileLine('Tool response file', finalStatus.responseFile);
|
|
6666
6994
|
}
|
|
6667
6995
|
if (finalStatus.data.status !== 'completed') {
|
|
6668
6996
|
process.exitCode = 1;
|
|
@@ -6673,6 +7001,7 @@ async function toolCommand(flags) {
|
|
|
6673
7001
|
await appendVerificationPacket(api, cwd, session, accountId, flags, {
|
|
6674
7002
|
type: 'tool_call',
|
|
6675
7003
|
success: asyncMode && !waitForAsync ? null : (!toolFailed && (!asyncMode || process.exitCode !== 1)),
|
|
7004
|
+
pending: asyncMode && !waitForAsync,
|
|
6676
7005
|
claim: 'Tool call ' + String(toolName),
|
|
6677
7006
|
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 }),
|
|
6678
7007
|
revision: Object.assign(collectVerificationSource(cwd, flags), {
|
|
@@ -6753,6 +7082,12 @@ async function verifyCommand(flags, subcommand) {
|
|
|
6753
7082
|
if (flags.manifest || flags.file) {
|
|
6754
7083
|
manifest = parseManifestFile(flags.manifest || flags.file);
|
|
6755
7084
|
}
|
|
7085
|
+
if (dataMode === 'prod' && !manifestRequiredEvidence(manifest).length && !flagEnabled(flags['no-contract'])) {
|
|
7086
|
+
console.error('');
|
|
7087
|
+
console.error('Warning: starting a prod-data verification envelope with no required evidence contract.');
|
|
7088
|
+
console.error('Attach a manifest with requiredEvidence before destructive work, or pass --no-contract when this is intentionally evidence-only.');
|
|
7089
|
+
console.error('');
|
|
7090
|
+
}
|
|
6756
7091
|
let statusResponse = null;
|
|
6757
7092
|
try {
|
|
6758
7093
|
statusResponse = await loggedPost(api, cwd, '/cli/components', {
|
|
@@ -6788,6 +7123,7 @@ async function verifyCommand(flags, subcommand) {
|
|
|
6788
7123
|
console.log('Verification envelope started:', envelope.envelopeId);
|
|
6789
7124
|
console.log('Target:', 'account=' + accountId + ', branch=' + branchName + ', workspace=' + (workspace || 'shared') + ', dataMode=' + dataMode);
|
|
6790
7125
|
printAcceptanceWarnings(envelope);
|
|
7126
|
+
printEnvelopeWarnings(envelope);
|
|
6791
7127
|
console.log('Local mirror:', verificationPaths(cwd, envelope.envelopeId).base);
|
|
6792
7128
|
return envelope;
|
|
6793
7129
|
}
|
|
@@ -6829,6 +7165,7 @@ async function verifyCommand(flags, subcommand) {
|
|
|
6829
7165
|
console.log('Manifest attached to envelope:', envelopeId);
|
|
6830
7166
|
console.log('Required evidence:', (((response.envelope || {}).acceptance || {}).requiredEvidence || []).length);
|
|
6831
7167
|
printAcceptanceWarnings(response.envelope);
|
|
7168
|
+
printEnvelopeWarnings(response.envelope);
|
|
6832
7169
|
return response.envelope;
|
|
6833
7170
|
}
|
|
6834
7171
|
|
|
@@ -12521,11 +12858,12 @@ function printComponentsHelp(subcommand) {
|
|
|
12521
12858
|
return;
|
|
12522
12859
|
}
|
|
12523
12860
|
if (subcommand === 'lanes' || subcommand === 'lane') {
|
|
12524
|
-
console.log('Usage: remits-cli components lanes [--base-url URL] [--account-id ID] [--branch BRANCH] [--workspace NAME] [--json]');
|
|
12861
|
+
console.log('Usage: remits-cli components lanes [--base-url URL] [--account-id ID] [--branch BRANCH] [--workspace NAME] [--wait-change [--lane-id ID] [--timeout 600]] [--json]');
|
|
12525
12862
|
console.log('');
|
|
12526
12863
|
console.log('Lists every indexed staging lane on the account, across users, branches, and workspaces.');
|
|
12527
12864
|
console.log('This is read-only and uses the same lane registry that powers the admin Platforms view.');
|
|
12528
12865
|
console.log('Use `components entries --lane-id <id>` to inspect the actual staged files in a lane.');
|
|
12866
|
+
console.log('Use --wait-change to wait until the lane registry changes instead of polling in a loop.');
|
|
12529
12867
|
return;
|
|
12530
12868
|
}
|
|
12531
12869
|
if (subcommand === 'entries' || subcommand === 'entry' || subcommand === 'staged') {
|
|
@@ -12564,7 +12902,7 @@ function printComponentsHelp(subcommand) {
|
|
|
12564
12902
|
console.log('so several agents can iterate at once. See: remits-cli workspace --help');
|
|
12565
12903
|
console.log(' remits-cli components stage [--workset|--changed-only] [--base-url URL] [--account-id ID] [--branch BRANCH] [--data-mode test|prod] [--json|--verbose]');
|
|
12566
12904
|
console.log(' remits-cli components status [--base-url URL] [--account-id ID] [--branch BRANCH] [--component-type TYPE --component-id ID] [--json|--verbose]');
|
|
12567
|
-
console.log(' remits-cli components lanes [--base-url URL] [--account-id ID] [--json]');
|
|
12905
|
+
console.log(' remits-cli components lanes [--base-url URL] [--account-id ID] [--wait-change [--lane-id ID] [--timeout 600]] [--json]');
|
|
12568
12906
|
console.log(' remits-cli components entries --lane-id ID [--base-url URL] [--account-id ID] [--json|--verbose]');
|
|
12569
12907
|
console.log(' remits-cli components clear [--base-url URL] [--account-id ID] [--branch BRANCH] [--component-type TYPE] [--component-id ID] [--all] [--json|--verbose]');
|
|
12570
12908
|
console.log(' remits-cli components sync [--safe] [--base-url URL] [--account-id ID] [--branch BRANCH] [--data-mode test|prod] [--force-tombstones] [--dry-run] [--summary]');
|
package/package.json
CHANGED
|
@@ -72,6 +72,11 @@ Think about remits-cli as two cooperating layers:
|
|
|
72
72
|
- where a large tool response was written
|
|
73
73
|
- which tool schemas were most recently cached here
|
|
74
74
|
|
|
75
|
+
Tool response files under `./.remits-cli/tool-responses/` are the full CLI results. The CLI may print a
|
|
76
|
+
size/hash hint for large files so you can inspect them selectively with `jq`, `rg`, or byte-range reads,
|
|
77
|
+
but it does not truncate the local JSON file. Do not confuse this with the in-platform OpenRouter client,
|
|
78
|
+
which can offload large tool results inside the model conversation and inject a read-back tool for the AI.
|
|
79
|
+
|
|
75
80
|
When a user asks an indirect question, map it to the right layer first:
|
|
76
81
|
|
|
77
82
|
- "Why did this ticket open in the wrong repo?" → start in global state.
|
|
@@ -85,7 +85,10 @@ deterministically:
|
|
|
85
85
|
remits-cli tool --name "mcp_run_action" --input '{"accountId":49,"actionId":200,"executionMode":"async","actionRunId":"my-stable-run-id","actionInput":{"sourceDocumentId":"..."}}' --data-mode prod
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
-
Every tool response is saved to `./.remits-cli/tool-responses/<callId>.json`.
|
|
88
|
+
Every tool response is saved in full to `./.remits-cli/tool-responses/<callId>.json`. Large responses may
|
|
89
|
+
print an early size/hash line and a selective-read hint before any preview text, but the saved CLI file is
|
|
90
|
+
not truncated or offloaded. That is separate from the platform's OpenRouter model loop, where the AI
|
|
91
|
+
client may offload large tool results before feeding context back to the model.
|
|
89
92
|
|
|
90
93
|
### Hierarchy-scoped tool reads
|
|
91
94
|
|
|
@@ -272,7 +275,10 @@ Existing commands also accept `--verify-envelope <id>` to attach to a specific e
|
|
|
272
275
|
Failed `remits-cli test run` output includes compact pivots per failed case: duration, trace id,
|
|
273
276
|
bounded `report(...)` diagnostics, live HTTP signals, and resolved component provenance when the
|
|
274
277
|
platform returns it. Re-read an existing run with `remits-cli test status --task-id <taskId>` before
|
|
275
|
-
rerunning a long suite.
|
|
278
|
+
rerunning a long suite. If the same suite/case flips pass/fail while the resolved component provenance
|
|
279
|
+
signature is unchanged, the CLI prints a nondeterministic signal and records `nondeterministic:true` on
|
|
280
|
+
the `test_run` packet. Treat that as an AI/live-data/flaky-fixture investigation cue, not as proof that
|
|
281
|
+
source changed.
|
|
276
282
|
|
|
277
283
|
Test-run requirements must name the suite/cases they mean:
|
|
278
284
|
|
|
@@ -281,23 +287,28 @@ Test-run requirements must name the suite/cases they mean:
|
|
|
281
287
|
{"id":"one_case", "packetType":"test_run", "suite":"Statement API Flow", "cases":["rejects duplicated fee evidence"]}
|
|
282
288
|
```
|
|
283
289
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
tool
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
290
|
+
A suite-only Test requirement is treated as `allCases:true`. A category-only Test requirement matches only
|
|
291
|
+
a run carrying that exact evidence category, or the case whose name the category names; unrelated Test
|
|
292
|
+
runs cannot replace it. `verify start` and `verify manifest` warn when a requirement has a `packetType` but
|
|
293
|
+
no discriminator. The evaluator treats pending async tool packets as pending, not passing; a later
|
|
294
|
+
`tool status` packet is the proof. Tool packets store the response file path, byte size and hash rather
|
|
295
|
+
than copying the whole tool payload into the envelope.
|
|
296
|
+
|
|
297
|
+
Use `remits-cli verify list` to review open envelopes for the account without SQL. The list shows stale
|
|
298
|
+
counts, requirements-changed markers, and last packet time. `verify start` warns when another open envelope
|
|
299
|
+
with the same summary/account/world exists; use `verify use <id>` to continue that contract or
|
|
300
|
+
`verify supersede` when a newer envelope replaces it. If a duplicate or abandoned attempt should no longer
|
|
301
|
+
read as live work, use `verify abandon`. Both keep the evidence history. Starting a prod-data envelope
|
|
302
|
+
without required evidence prints a loud no-contract warning; add a manifest unless the envelope is
|
|
303
|
+
intentionally evidence-only.
|
|
292
304
|
|
|
293
305
|
The manifest is the proof contract. It should name the world being exercised: repo account, host,
|
|
294
306
|
git/component branch, workspace, data mode, source layer, user journeys, artifacts and hashes, and the
|
|
295
307
|
evidence categories required before the final response may claim the work is done. If the manifest
|
|
296
308
|
cannot be written because the workflow is ambiguous, ask before implementation. For machine-gated report
|
|
297
309
|
requirements, prefer structured entries like `{"id":"actual_upload","packetType":"browser_step",
|
|
298
|
-
"category":"browser_session.actual_upload"}`. Plain strings work
|
|
299
|
-
|
|
300
|
-
envelope.
|
|
310
|
+
"category":"browser_session.actual_upload"}`. Plain strings work for evidence categories, but do not use
|
|
311
|
+
plain `"test_run"` as acceptance: it cannot say which Test or case proved the behavior.
|
|
301
312
|
|
|
302
313
|
Common packet meanings:
|
|
303
314
|
|
|
@@ -318,7 +329,13 @@ Final claims should come from `remits-cli verify report`. Treat `Verified` as th
|
|
|
318
329
|
the report lists it under `Verified`. If the report says `partially_verified`, stale, or missing evidence,
|
|
319
330
|
say that plainly instead of widening the claim. In particular, staged proof is not committed variant/trunk
|
|
320
331
|
proof, a token is not browser proof, and a direct DOM or Alpine state mutation is not the same as a user
|
|
321
|
-
|
|
332
|
+
action.
|
|
333
|
+
|
|
334
|
+
Evidence from the wrong world is excluded before it can satisfy a requirement. When the manifest declares
|
|
335
|
+
fields such as `repoAccountId`, `gitBranch`, `componentBranch`, `workspace`, or `dataMode`, the report names
|
|
336
|
+
the mismatch (`expected X got Y`) under missing evidence. Later stage/sync/source facts can stale earlier
|
|
337
|
+
packets; explicit invalidations stale only packets collected before the invalidation, so a fresh rerun can
|
|
338
|
+
verify the same requirement again.
|
|
322
339
|
|
|
323
340
|
For tests specifically:
|
|
324
341
|
- If `--data-mode` is omitted, `remits-cli test run` uses `test` and sends `dataModeSource:"cliDefault"`.
|
|
@@ -160,7 +160,10 @@ TestMode still uses the committed DB source. Staging remains a dev/verification
|
|
|
160
160
|
- **Inspect another lane without impersonating it:** `remits-cli components lanes` lists every indexed
|
|
161
161
|
lane on the account; `remits-cli components entries --lane-id <id>` reads the authoritative staged
|
|
162
162
|
files for one lane. These are read-only review surfaces. They do not switch your workspace, clear
|
|
163
|
-
anything, or change what your own test/token/tool runs resolve.
|
|
163
|
+
anything, or change what your own test/token/tool runs resolve. Use
|
|
164
|
+
`remits-cli components lanes --wait-change [--lane-id ID] [--timeout 600]` when you need to wait for a
|
|
165
|
+
sibling lane to move; it watches the lane registry's `updatedAtMs`/content hash instead of making you
|
|
166
|
+
poll in a loop.
|
|
164
167
|
- **Clear only your own lane:** `remits-cli components clear` removes entries when you intentionally want
|
|
165
168
|
to fall back to DB source. `--all` is scoped to the command's account/user/branch/workspace lane, not
|
|
166
169
|
every lane another agent may be using.
|
|
@@ -209,7 +212,9 @@ commit write `ComponentVariant` overlays for a branch nobody subscribes to).
|
|
|
209
212
|
account's lane context in their responses. A shared current lane is printed loudly; sibling lanes are
|
|
210
213
|
listed when they matter.
|
|
211
214
|
- `remits-cli components lanes` is the review view across users/branches/workspaces, and
|
|
212
|
-
`remits-cli components entries --lane-id <id>` is the drill-down into actual staged files.
|
|
215
|
+
`remits-cli components entries --lane-id <id>` is the drill-down into actual staged files. When you are
|
|
216
|
+
coordinating with another agent, prefer `components lanes --wait-change --lane-id <id>` to repeated
|
|
217
|
+
status checks.
|
|
213
218
|
- `remits-cli components clear --all` is scoped to YOUR lane and never touches another agent's.
|
|
214
219
|
- In lane rows, `currentLane` (also `mine`) marks THIS command's lane; `ownedByCaller` marks every lane
|
|
215
220
|
staged by your CLI user — your other clones' agents included.
|
|
@@ -104,9 +104,13 @@ For Test requirements, be specific enough for the evaluator to know what a pass
|
|
|
104
104
|
{"id":"duplicate_fee_case", "packetType":"test_run", "suite":"Statement API Flow", "cases":["rejects duplicated fee evidence"]}
|
|
105
105
|
```
|
|
106
106
|
|
|
107
|
-
A requirement
|
|
108
|
-
|
|
109
|
-
|
|
107
|
+
A suite-only Test requirement is treated as `allCases:true`. A category-only Test requirement matches only
|
|
108
|
+
a run carrying that exact evidence category, or the case whose name the category names. A requirement that
|
|
109
|
+
says only `{"packetType":"test_run"}` is intentionally only a warning-worthy sketch: it will not turn a
|
|
110
|
+
random Test packet into acceptance. Full-suite runs emit suite and passed-case categories automatically,
|
|
111
|
+
so case-level requirements can be satisfied by a real full run. The evaluator excludes packets collected
|
|
112
|
+
in the wrong account/data lane/git branch/component branch/workspace before satisfying requirements, and
|
|
113
|
+
pending async packets do not count until a result packet arrives.
|
|
110
114
|
|
|
111
115
|
## Development Workflow
|
|
112
116
|
|