@remits/remits-cli 0.1.104 → 0.1.106
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 +131 -13
- package/package.json +1 -1
- package/skills/remits-cli/SKILL.md +40 -5
package/index.js
CHANGED
|
@@ -74,7 +74,7 @@ const ACCOUNT_SCAN_EXCLUDE_DIRS = new Set([
|
|
|
74
74
|
|
|
75
75
|
// Flags that are legitimately repeatable accumulate into an array instead of last-wins. Every other
|
|
76
76
|
// flag keeps last-wins so existing callers are unaffected.
|
|
77
|
-
const REPEATABLE_FLAGS = new Set(['expected-removed', 'expectedRemoved']);
|
|
77
|
+
const REPEATABLE_FLAGS = new Set(['expected-removed', 'expectedRemoved', 'names']);
|
|
78
78
|
|
|
79
79
|
function parseArgs(argv) {
|
|
80
80
|
const out = { _: [] };
|
|
@@ -1572,6 +1572,42 @@ function changedComponentsFromWorkingTree(cwd) {
|
|
|
1572
1572
|
});
|
|
1573
1573
|
}
|
|
1574
1574
|
|
|
1575
|
+
/**
|
|
1576
|
+
* Components touched by commits between `ref` and HEAD.
|
|
1577
|
+
*
|
|
1578
|
+
* `changedComponentsFromWorkingTree` reads `git status`, i.e. UNCOMMITTED edits only — which is empty
|
|
1579
|
+
* at exactly the moment `--changed-only` is meant to run, because the documented flow is
|
|
1580
|
+
* commit -> push -> sync (sync reads the pushed remote, so it cannot see uncommitted work at all).
|
|
1581
|
+
* The gate was therefore unusable in the flow it exists to guard: it refused every plan with
|
|
1582
|
+
* "plan touches N component(s) this working tree did not edit". `--changed-since <ref>` supplies the
|
|
1583
|
+
* base so the same guard works after the commit.
|
|
1584
|
+
*/
|
|
1585
|
+
function changedComponentsSinceRef(cwd, ref) {
|
|
1586
|
+
let output = '';
|
|
1587
|
+
try {
|
|
1588
|
+
output = execSync('git diff --name-only ' + JSON.stringify(ref) + '...HEAD -- components', {
|
|
1589
|
+
cwd,
|
|
1590
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
1591
|
+
}).toString();
|
|
1592
|
+
} catch (_) {
|
|
1593
|
+
return null;
|
|
1594
|
+
}
|
|
1595
|
+
const byKey = new Map();
|
|
1596
|
+
for (const rawLine of output.split(/\r?\n/)) {
|
|
1597
|
+
const filePath = rawLine.trim();
|
|
1598
|
+
if (!filePath) continue;
|
|
1599
|
+
const info = componentPathInfo(cwd, filePath);
|
|
1600
|
+
if (!info) continue;
|
|
1601
|
+
if (!byKey.has(info.key)) {
|
|
1602
|
+
byKey.set(info.key, { type: info.type, id: info.id, name: info.name, fields: [], paths: [] });
|
|
1603
|
+
}
|
|
1604
|
+
const entry = byKey.get(info.key);
|
|
1605
|
+
if (!entry.fields.includes(info.field)) entry.fields.push(info.field);
|
|
1606
|
+
if (!entry.paths.includes(info.path)) entry.paths.push(info.path);
|
|
1607
|
+
}
|
|
1608
|
+
return Array.from(byKey.values());
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1575
1611
|
function parsePositiveInt(value, fallback) {
|
|
1576
1612
|
const parsed = Number(value);
|
|
1577
1613
|
return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : fallback;
|
|
@@ -2309,7 +2345,7 @@ async function syncComponentsCommand(flags) {
|
|
|
2309
2345
|
throw new Error(previewResponse.message || 'Server sync dry-run failed');
|
|
2310
2346
|
}
|
|
2311
2347
|
|
|
2312
|
-
preflightGate = evaluateSyncGates(previewResponse, flags, changedFromWorkingTree);
|
|
2348
|
+
preflightGate = evaluateSyncGates(previewResponse, flags, changedFromWorkingTree, cwd);
|
|
2313
2349
|
|
|
2314
2350
|
if (namesOnly) {
|
|
2315
2351
|
printSessionResolutionWarning(sessionContext);
|
|
@@ -2332,7 +2368,7 @@ async function syncComponentsCommand(flags) {
|
|
|
2332
2368
|
}
|
|
2333
2369
|
|
|
2334
2370
|
const summary = buildSyncSummary(response);
|
|
2335
|
-
const gate = preflightGate || evaluateSyncGates(response, flags, changedFromWorkingTree);
|
|
2371
|
+
const gate = preflightGate || evaluateSyncGates(response, flags, changedFromWorkingTree, cwd);
|
|
2336
2372
|
summary.gates = gate.checks;
|
|
2337
2373
|
|
|
2338
2374
|
if (flagEnabled(flags.json)) {
|
|
@@ -2505,7 +2541,7 @@ function parseExpectedRemoved(flags) {
|
|
|
2505
2541
|
}
|
|
2506
2542
|
|
|
2507
2543
|
// Fail-closed gates. Each returns a violation string or null; the command exits non-zero if any fire.
|
|
2508
|
-
function evaluateSyncGates(response, flags, changedFromWorkingTree) {
|
|
2544
|
+
function evaluateSyncGates(response, flags, changedFromWorkingTree, cwd) {
|
|
2509
2545
|
const entries = syncPlanEntries(response);
|
|
2510
2546
|
const results = ((response && response.sync) || {}).syncResults || {};
|
|
2511
2547
|
const removed = entries.filter((e) => e.bucket === 'removed');
|
|
@@ -2536,13 +2572,19 @@ function evaluateSyncGates(response, flags, changedFromWorkingTree) {
|
|
|
2536
2572
|
}
|
|
2537
2573
|
|
|
2538
2574
|
if (flagEnabled(flags['changed-only']) || flagEnabled(flags.changedOnly)) {
|
|
2539
|
-
|
|
2575
|
+
const since = flags['changed-since'] || flags.changedSince;
|
|
2576
|
+
const fromRef = since ? changedComponentsSinceRef(cwd, String(since)) : null;
|
|
2577
|
+
if (since && fromRef === null) {
|
|
2578
|
+
violations.push('--changed-since: could not diff against "' + since + '". Check the ref exists (git fetch first for a remote ref).');
|
|
2579
|
+
checks.changedOnly = false;
|
|
2580
|
+
} else if (changedFromWorkingTree === null && fromRef === null) {
|
|
2540
2581
|
violations.push('--changed-only: this checkout is not a git working tree, so the changed set cannot be established.');
|
|
2541
2582
|
checks.changedOnly = false;
|
|
2542
2583
|
} else {
|
|
2584
|
+
const changedSet = (changedFromWorkingTree || []).concat(fromRef || []);
|
|
2543
2585
|
// A component the checkout edited is identified by type + id, or type + name for `new_` files.
|
|
2544
|
-
const allowedIds = new Set(
|
|
2545
|
-
const allowedNames = new Set(
|
|
2586
|
+
const allowedIds = new Set(changedSet.filter((c) => c.id != null).map((c) => c.type + ':' + c.id));
|
|
2587
|
+
const allowedNames = new Set(changedSet.filter((c) => c.name).map((c) => c.type + ':' + String(c.name).toLowerCase()));
|
|
2546
2588
|
const unexpected = entries.filter((entry) => {
|
|
2547
2589
|
if (entry.id != null && allowedIds.has(entry.type + ':' + entry.id)) return false;
|
|
2548
2590
|
if (entry.name && allowedNames.has(entry.type + ':' + String(entry.name).toLowerCase())) return false;
|
|
@@ -2550,8 +2592,15 @@ function evaluateSyncGates(response, flags, changedFromWorkingTree) {
|
|
|
2550
2592
|
});
|
|
2551
2593
|
checks.changedOnly = unexpected.length === 0;
|
|
2552
2594
|
if (unexpected.length) {
|
|
2553
|
-
|
|
2554
|
-
|
|
2595
|
+
// Name the most likely cause instead of only the symptom. An empty changed set with a non-empty
|
|
2596
|
+
// plan is the ordinary post-commit state, not evidence that the plan is dangerous.
|
|
2597
|
+
const hint = (!changedSet.length && !since)
|
|
2598
|
+
? '\n The changed set is EMPTY: --changed-only reads UNCOMMITTED edits, and the documented flow ' +
|
|
2599
|
+
'commits and pushes before syncing. Pass --changed-since <ref> (e.g. the commit you branched ' +
|
|
2600
|
+
'from, or origin/main) so the gate can see committed work.'
|
|
2601
|
+
: '';
|
|
2602
|
+
violations.push('--changed-only: plan touches ' + unexpected.length + ' component(s) this checkout did not change: ' +
|
|
2603
|
+
unexpected.slice(0, 20).map(syncEntryToken).join(', ') + (unexpected.length > 20 ? ', ...' : '') + hint);
|
|
2555
2604
|
}
|
|
2556
2605
|
}
|
|
2557
2606
|
}
|
|
@@ -2571,6 +2620,36 @@ function buildSyncSummary(response) {
|
|
|
2571
2620
|
const errors = Array.isArray(results.errors) ? results.errors : [];
|
|
2572
2621
|
const skipped = Array.isArray(results.skipped) ? results.skipped : [];
|
|
2573
2622
|
|
|
2623
|
+
// A sync that SHORT-CIRCUITED on the cached branch SHA did no work at all, and the server says so
|
|
2624
|
+
// (`skipped: true` plus a message). Rendering it through the normal shape below printed
|
|
2625
|
+
// `overridden: 0, added: 0, removed: []` — which is what a sync that ran and found nothing to do
|
|
2626
|
+
// prints, so the two were indistinguishable. That matters because the cache can hold a SHA whose
|
|
2627
|
+
// overlays were since removed by another path: an ordinary re-sync then reports zeros forever and
|
|
2628
|
+
// the operator has no reason to suspect the branch was never re-read. Report the skip as itself.
|
|
2629
|
+
// `syncResults` absent is the fallback signal, so this still reads correctly against a server that
|
|
2630
|
+
// predates the explicit `skipped` flag rather than silently falling back to the misleading zeros.
|
|
2631
|
+
const shortCircuited = sync.skipped === true
|
|
2632
|
+
|| (sync.syncResults == null && !!sync.message && sync.dryRun !== true);
|
|
2633
|
+
if (shortCircuited) {
|
|
2634
|
+
return {
|
|
2635
|
+
success: response && response.success === true,
|
|
2636
|
+
accountId: response && response.accountId,
|
|
2637
|
+
branchName: sync.branchName || (response && response.branchName),
|
|
2638
|
+
mode: sync.mode || 'variant',
|
|
2639
|
+
skipped: true,
|
|
2640
|
+
reason: sync.message || 'branch head matches the cached sync SHA; the branch was not re-read',
|
|
2641
|
+
branchHeadSha: sync.branchHeadSha || sync.postSyncSha || null,
|
|
2642
|
+
warnings: [
|
|
2643
|
+
// Scoped to COMPONENTS deliberately: the short-circuit still refreshes the delivered guides and
|
|
2644
|
+
// account metadata, so "nothing happened" would be its own small untruth.
|
|
2645
|
+
'NO COMPONENTS WERE SYNCED. This is the cached-SHA short-circuit, not an empty plan — the ' +
|
|
2646
|
+
'branch was never re-read.',
|
|
2647
|
+
'Use --dry-run to see the real plan (a dry run always re-reads the branch), or ' +
|
|
2648
|
+
'--force-tombstones to re-read and apply it.'
|
|
2649
|
+
]
|
|
2650
|
+
};
|
|
2651
|
+
}
|
|
2652
|
+
|
|
2574
2653
|
// A TRUNK sync and a VARIANT sync report in different vocabularies, and this summary only ever spoke
|
|
2575
2654
|
// variant. So the single most consequential line of a promotion — "component X was CREATED on trunk as
|
|
2576
2655
|
// id N" — printed as `added: 0`, and the runbook's definition of done ("components created, with their
|
|
@@ -3153,7 +3232,26 @@ async function testCommand(flags) {
|
|
|
3153
3232
|
throw new Error('Missing --test <id-or-name>');
|
|
3154
3233
|
}
|
|
3155
3234
|
|
|
3156
|
-
|
|
3235
|
+
// Test case names are English prose, so COMMAS occur in them naturally. A comma-delimited selector
|
|
3236
|
+
// therefore split a legitimate name in half, matched nothing, and (before the server learned to
|
|
3237
|
+
// report unmatched selectors) reported a clean run of zero cases. `|` is the delimiter now; the
|
|
3238
|
+
// comma still works when no `|` is present, so existing invocations keep behaving as before.
|
|
3239
|
+
// `--names` may also be repeated, which needs no delimiter at all.
|
|
3240
|
+
const rawNames = flags.names === undefined || flags.names === null
|
|
3241
|
+
? []
|
|
3242
|
+
: (Array.isArray(flags.names) ? flags.names : [flags.names]);
|
|
3243
|
+
const repeated = rawNames.length > 1;
|
|
3244
|
+
const names = rawNames
|
|
3245
|
+
.flatMap((value) => {
|
|
3246
|
+
const text = String(value);
|
|
3247
|
+
if (text.includes('|')) return text.split('|');
|
|
3248
|
+
// Comma-splitting is the legacy behaviour, kept so existing invocations still work. It is applied
|
|
3249
|
+
// ONLY to a single --names value: repeating the flag is already unambiguous, so splitting there
|
|
3250
|
+
// would take a deliberate literal name back apart.
|
|
3251
|
+
return repeated ? [text] : text.split(',');
|
|
3252
|
+
})
|
|
3253
|
+
.map((s) => s.trim())
|
|
3254
|
+
.filter(Boolean);
|
|
3157
3255
|
|
|
3158
3256
|
const api = buildAxios(baseUrl, session.token);
|
|
3159
3257
|
// --as-account runs the Test AS a (descendant) subscriber account, so that account's relationship
|
|
@@ -3209,6 +3307,23 @@ async function testCommand(flags) {
|
|
|
3209
3307
|
}
|
|
3210
3308
|
|
|
3211
3309
|
console.log('Final status:', JSON.stringify(status, null, 2));
|
|
3310
|
+
|
|
3311
|
+
// A selector that matched no case is a mis-specified run, not a passing one. Say so in the terminal
|
|
3312
|
+
// and exit non-zero, or "0 passed, 0 failed" reads exactly like a suite where everything passed.
|
|
3313
|
+
const unmatched = (status.result && status.result.unmatchedTestNames) || [];
|
|
3314
|
+
if (unmatched.length) {
|
|
3315
|
+
console.error('');
|
|
3316
|
+
console.error('No test case matched: ' + unmatched.map((n) => '"' + n + '"').join(', '));
|
|
3317
|
+
const declared = (status.result && status.result.declaredTestNames) || [];
|
|
3318
|
+
if (declared.length) {
|
|
3319
|
+
console.error('Cases declared by this suite:');
|
|
3320
|
+
declared.forEach((n) => console.error(' - ' + n));
|
|
3321
|
+
}
|
|
3322
|
+
console.error('--names is delimited by "|" (a comma still works when no "|" is present), so a case');
|
|
3323
|
+
console.error('name containing a comma must be passed with "|" or by repeating --names.');
|
|
3324
|
+
process.exitCode = 1;
|
|
3325
|
+
}
|
|
3326
|
+
|
|
3212
3327
|
if (status.status !== 'completed') {
|
|
3213
3328
|
process.exitCode = 1;
|
|
3214
3329
|
} else if (status.result && status.result.failed > 0) {
|
|
@@ -5977,6 +6092,8 @@ function printComponentsHelp(subcommand) {
|
|
|
5977
6092
|
console.log('');
|
|
5978
6093
|
console.log('Agent safety gates (each exits non-zero instead of printing a wall of JSON):');
|
|
5979
6094
|
console.log(' --changed-only dry-run first; fail unless every planned write is a component this checkout changed');
|
|
6095
|
+
console.log(' --changed-since <ref> pair with --changed-only after committing: the changed set becomes');
|
|
6096
|
+
console.log(' the components touched between <ref> and HEAD, plus uncommitted edits');
|
|
5980
6097
|
console.log(' --names-only dry-run first; print only "type id name" lines and write nothing');
|
|
5981
6098
|
console.log(' --fail-on-removed dry-run first; fail if the plan removes/tombstones anything');
|
|
5982
6099
|
console.log(' --fail-on-errors dry-run first; fail if the server reported any per-component sync error');
|
|
@@ -6037,7 +6154,7 @@ function printComponentsHelp(subcommand) {
|
|
|
6037
6154
|
}
|
|
6038
6155
|
|
|
6039
6156
|
function printTestHelp() {
|
|
6040
|
-
console.log('Usage: remits-cli test run --test <id|name> [--base-url URL] [--branch stagingScope] [--names
|
|
6157
|
+
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]');
|
|
6041
6158
|
console.log('');
|
|
6042
6159
|
console.log('Runs a Test component against the staged/variant world for this checkout.');
|
|
6043
6160
|
console.log('Examples:');
|
|
@@ -6045,7 +6162,8 @@ function printTestHelp() {
|
|
|
6045
6162
|
console.log(' remits-cli test run --test "Merchant Statements" --names "managed account case"');
|
|
6046
6163
|
console.log('');
|
|
6047
6164
|
console.log('Notes:');
|
|
6048
|
-
console.log(' --names is
|
|
6165
|
+
console.log(' --names is delimited by "|" (comma also works when no "|" is present), and may be repeated.');
|
|
6166
|
+
console.log(' A selector that matches no case fails the run rather than reporting 0 passed / 0 failed.');
|
|
6049
6167
|
console.log(' --as-account changes the execution account so subscriber branch edges apply.');
|
|
6050
6168
|
console.log(' --variant-branch <name> probes a committed variant branch; use "none" or "trunk"');
|
|
6051
6169
|
console.log(' to force production/subscription semantics from a variant checkout.');
|
|
@@ -6146,7 +6264,7 @@ async function main() {
|
|
|
6146
6264
|
console.log(' remits-cli components branch <name> --subscribe <accountId> [--dry-run] [--confirm-primary-edge]');
|
|
6147
6265
|
console.log(' remits-cli components branch <name> --unsubscribe <accountId> # return that account to trunk');
|
|
6148
6266
|
console.log(' remits-cli components branch <name> --retire [--force] # delete the branch\'s overlays');
|
|
6149
|
-
console.log(' remits-cli test run --test <id|name> [--base-url URL] [--branch stagingScope] [--names
|
|
6267
|
+
console.log(' 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]');
|
|
6150
6268
|
console.log(' remits-cli token [--base-url URL] [--branch BRANCH] [--path embeddable/path] [--data-mode test|prod] [--as-account ID] [--variant-branch NAME|none]');
|
|
6151
6269
|
console.log(' remits-cli token inspect --token <token|tokenKey|URL> [--base-url URL] [--account-id ID]');
|
|
6152
6270
|
console.log('');
|
package/package.json
CHANGED
|
@@ -339,6 +339,14 @@ file is catastrophic.
|
|
|
339
339
|
- Local branch is committed and pushed; sync will read the intended remote commit.
|
|
340
340
|
- Local filenames and live inventory (`mcp_account_view`) **agree on id and name for every component**: no live
|
|
341
341
|
component appears locally under a different id, and no expected component is missing a repo file.
|
|
342
|
+
> **Do not run this comparison against `account-info.json` alone — it will report false orphans.** That
|
|
343
|
+
> file omits `auxiliary: true` components by design, so every auxiliary component looks like a repo file
|
|
344
|
+
> with no DB row, i.e. exactly the "a trunk sync will CREATE a duplicate" signal this check exists to
|
|
345
|
+
> catch. It also omits README- and AGENT-purpose Prompts, which live at the repo root and as `.md`
|
|
346
|
+
> sidecars in `components/agents/` rather than in `components/prompts/`, so they look like DB rows with
|
|
347
|
+
> no repo file — the "will be DELETED" signal. Both are benign. Before treating a flagged component as
|
|
348
|
+
> drift, confirm against the live row: `mcp_component_view`, or
|
|
349
|
+
> `mcp_run_action controlAction:"describe"` for an Action. A component that answers is not an orphan.
|
|
342
350
|
- You can state the expected create/update/delete set. **If any delete or renumber is unexpected, stop.**
|
|
343
351
|
|
|
344
352
|
### If something looks wrong — stop, don't paper over
|
|
@@ -895,7 +903,16 @@ Tests run on the platform against your staged snapshot. They stream results in r
|
|
|
895
903
|
|
|
896
904
|
Important test-runner constraints:
|
|
897
905
|
- `remits-cli test run` now defaults to `test` dataMode unless you explicitly pass `--data-mode prod`.
|
|
898
|
-
-
|
|
906
|
+
- **`--names` is delimited by `|`, and may be repeated.** A comma still splits a single `--names` value
|
|
907
|
+
(legacy behaviour), which is why a case name containing a comma used to be cut in half and match
|
|
908
|
+
nothing. Prefer `|` or repetition whenever a name might contain punctuation:
|
|
909
|
+
```bash
|
|
910
|
+
remits-cli test run --test 13 --names "a case, with a comma|another case"
|
|
911
|
+
remits-cli test run --test 13 --names "a case, with a comma" --names "another case"
|
|
912
|
+
```
|
|
913
|
+
- **A selector that matches no case FAILS the run.** It used to report `0 passed, 0 failed`,
|
|
914
|
+
`completed`, and exit 0 — indistinguishable from a suite where everything passed. The run now names
|
|
915
|
+
the unmatched selectors and lists the cases the suite actually declared, and exits non-zero.
|
|
899
916
|
|
|
900
917
|
If no relevant Test component exists yet, consider creating one. Test components live in `components/tests/` and follow the same component structure. They provide permanent regression protection — every test you write today saves debugging time tomorrow.
|
|
901
918
|
|
|
@@ -1617,6 +1634,15 @@ a confirm-gated override when the removal guard refuses. Preview there is the sa
|
|
|
1617
1634
|
- A test/tool response reports `testComponentSource` as `staged` | `variant` | `db`, so you can see which
|
|
1618
1635
|
layer the run resolved without reading logs.
|
|
1619
1636
|
|
|
1637
|
+
> **A populated staging cache makes a variant look broken.** Anything carrying a CLI `TestMode` — a
|
|
1638
|
+
> `remits-cli token` URL, `/s/<tokenKey>/...`, an `X-Auth-Token` request, a script-loader embed — resolves
|
|
1639
|
+
> the STAGED layer, which outranks the variant. So with components staged under the same branch/user, a
|
|
1640
|
+
> tokenized page reports `Component Branch: <branch>` but `Variant Applied: none` and renders trunk, while
|
|
1641
|
+
> an anonymous `?account_id=` request to the same page reports the variant. That is the documented
|
|
1642
|
+
> precedence (staged -> variant -> trunk) working correctly, and it reads exactly like "tokens break
|
|
1643
|
+
> variant resolution". **Run `remits-cli components clear --all` before verifying variant resolution
|
|
1644
|
+
> through any tokenized entry point**, or check `remits-cli components status` first.
|
|
1645
|
+
|
|
1620
1646
|
## Production Support Workflow
|
|
1621
1647
|
|
|
1622
1648
|
Switch to prod mode for investigations:
|
|
@@ -2804,7 +2830,7 @@ remits-cli data-mode [set test|prod]
|
|
|
2804
2830
|
remits-cli components stage [--branch <name>] [--data-mode test|prod] [--json|--verbose]
|
|
2805
2831
|
remits-cli components status [--branch <name>] [--component-type <type>] [--component-id <id>] [--json|--verbose]
|
|
2806
2832
|
remits-cli components clear [--branch <name>] [--component-type <type>] [--component-id <id>] [--all] [--json|--verbose] # id alone scopes to one component when unambiguous (ids are type-local; add --component-type if the same id is staged in multiple families); no filter clears the whole branch scope; --all forces the full wipe
|
|
2807
|
-
remits-cli components sync [--branch <name>] [--data-mode test|prod] [--force-tombstones] [--dry-run] [--summary] # gated; on trunk = full repo->DB reconcile, on a variant branch = ComponentVariant overlays only
|
|
2833
|
+
remits-cli components sync [--branch <name>] [--data-mode test|prod] [--force-tombstones] [--dry-run] [--summary] [--changed-only [--changed-since <ref>]] # gated; on trunk = full repo->DB reconcile, on a variant branch = ComponentVariant overlays only
|
|
2808
2834
|
remits-cli components commit [--message "msg"] [--data-mode test|prod] [--force-tombstones]
|
|
2809
2835
|
remits-cli components branches [--json] # branches carrying committed variants, with counts + drift
|
|
2810
2836
|
remits-cli components branch <name> [--json] # one branch: overridden / added / removed, drift flags, subscribers
|
|
@@ -2813,7 +2839,7 @@ remits-cli components branch <name> --subscribers [--json]
|
|
|
2813
2839
|
remits-cli components branch <name> --subscribe <accountId> [--parent-account <id>] [--domain <host>] [--dry-run] [--confirm-primary-edge] # make an account resolve this branch
|
|
2814
2840
|
remits-cli components branch <name> --unsubscribe <accountId> # return that account to trunk
|
|
2815
2841
|
remits-cli components branch <name> --retire [--force] # delete the branch's overlays
|
|
2816
|
-
remits-cli test run --test <id|name> [--branch <stagingScope>] [--names "a
|
|
2842
|
+
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>]
|
|
2817
2843
|
remits-cli token [--path <embeddablePathOrId>] [--data-mode test|prod] [--as-account <ID>] [--variant-branch <name|none>]
|
|
2818
2844
|
remits-cli token inspect --token <token|tokenKey|URL> # inspect token metadata, safety/dataMode evidence, and full context
|
|
2819
2845
|
remits-cli tools [--branch <name>] [--data-mode test|prod] [--variant-branch <name|none>]
|
|
@@ -2823,7 +2849,8 @@ remits-cli tool status --call-id <callId> [--data-mode test|prod]
|
|
|
2823
2849
|
|
|
2824
2850
|
For tests specifically:
|
|
2825
2851
|
- If `--data-mode` is omitted, `remits-cli test run` uses `test`.
|
|
2826
|
-
- `--names` is
|
|
2852
|
+
- `--names` is `|`-delimited (a comma still splits a single value) and may be repeated; an unmatched
|
|
2853
|
+
selector fails the run instead of reporting zero cases as success.
|
|
2827
2854
|
- `--as-account <ID>` runs AS a descendant subscriber so its edge selects the component branch
|
|
2828
2855
|
(*"what does customer X get?"*); `--variant-branch <name>` probes a branch from any checkout
|
|
2829
2856
|
(*"what does branch Y look like?"*), and `--variant-branch none` forces production/subscription semantics.
|
|
@@ -2841,10 +2868,16 @@ For tests specifically:
|
|
|
2841
2868
|
- **Fail-closed sync gates.** On non-trunk variant branches these flags now force a server dry-run first,
|
|
2842
2869
|
evaluate that plan before any overlay row is written, and only then run the mutating sync when the plan
|
|
2843
2870
|
passes. On trunk, there is no safe dry-run plan, so do not treat these as scoped commit controls:
|
|
2844
|
-
- `--changed-only` — fail unless every planned write is a component **this checkout actually
|
|
2871
|
+
- `--changed-only` — fail unless every planned write is a component **this checkout actually changed**.
|
|
2845
2872
|
This is the strongest guard against a sync that quietly rewrites components you never touched. It
|
|
2846
2873
|
also fails when the checkout is not a git working tree, because "git could not answer" must never
|
|
2847
2874
|
be read as "nothing changed".
|
|
2875
|
+
> **Pair it with `--changed-since <ref>` after you have committed.** On its own `--changed-only`
|
|
2876
|
+
> reads UNCOMMITTED edits, and the documented flow commits and pushes *before* syncing (sync reads
|
|
2877
|
+
> the pushed remote, so it cannot see uncommitted work at all) — so the changed set is empty at
|
|
2878
|
+
> exactly the moment the gate runs, and it refuses the whole plan. `--changed-since origin/main`
|
|
2879
|
+
> (or the commit you branched from) makes the changed set the components your commits touched.
|
|
2880
|
+
> The refusal message says this when it detects the empty-set case.
|
|
2848
2881
|
- `--fail-on-removed` — fail if the plan removes or tombstones anything.
|
|
2849
2882
|
- `--expected-removed <type:id>` — whitelist the removals you intend (repeatable, or comma-delimited,
|
|
2850
2883
|
e.g. `--expected-removed action:5,reader:9`). It **implies** `--fail-on-removed`, so any removal you
|
|
@@ -2902,6 +2935,8 @@ For tests specifically:
|
|
|
2902
2935
|
| A component vanished for one account after a variant-branch sync | Its file is missing from that branch, so the sync created a **tombstone** that hides it from subscribers. Restore the file on the branch and re-sync. Trunk is unaffected. |
|
|
2903
2936
|
| A variant Test suite fails wholesale, asserting trunk where you expect a variant | You are almost certainly running it from a **variant checkout**: `variantBranch` outranks every subscription, so the suite's own fixture accounts resolve YOUR branch. Re-run from trunk or with `--variant-branch none` before treating it as a regression. |
|
|
2904
2937
|
| `components sync` on a branch says "No changes detected" but trunk has moved | Re-run it; a trunk sync now invalidates the branch's cached verdict. If it still skips, the branch genuinely matches trunk - check `components branch <name>` for what is actually stored. |
|
|
2938
|
+
| A sync summary reports `skipped: true` with `NO COMPONENTS WERE SYNCED` | The branch head matches the cached sync SHA, so the branch was never re-read — this is NOT an empty plan. It matters when something else changed the overlays at that same SHA: an ordinary re-sync then answers "nothing to do" forever. `--dry-run` always re-reads the branch and shows the real plan; `--force-tombstones` re-reads and applies it. |
|
|
2939
|
+
| A branch sync stores overlays for components you never edited on the branch | **Trunk moved.** Sparseness compares the branch against CURRENT trunk, so editing a component on trunk without merging trunk into the branch turns it into a branch overlay on the next branch sync. The branch did not change. Merge trunk in, push, re-sync — the overlays prune. Check `components promotion` for the phase. |
|
|
2905
2940
|
| After promoting a `new_*` component to trunk, the branch still shows it as `added` | You have not re-synced the branch since the trunk sync. Merge trunk into the branch and `components sync`: the file adopts the promoted id and, if unchanged, removes its own overlay. If the branch carries BOTH `new_Foo.*` and `<id>_Foo.*`, the id file wins and the `new_` one is reported `skipped: superseded` - delete it. |
|
|
2906
2941
|
| After promoting a standalone Prompt, the branch still shows `OVERRIDDEN prompt:<id>` with only `purpose` changed | The trunk Prompt row does not match repo metadata. Ensure the Prompt sidecar has `description` and `purpose: CUSTOM`, run a platform build that imports Prompt `purpose`, re-sync trunk, merge back, and re-sync the branch. |
|
|
2907
2942
|
| A branch preview reports a `removed` component nobody deleted | Check whether that component has a file on **trunk**. A DB row with no trunk file is missing from every branch, so it reads as a removal everywhere (and the trunk sync tries to hard-delete it each run). Repair trunk, not the branch. |
|