@remits/remits-cli 0.1.115 → 0.1.116
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 +14 -4
- package/index.js +421 -57
- package/package.json +1 -1
- package/skills/remits-cli/SKILL.md +19 -1
- package/skills/remits-cli/references/command-reference.md +47 -4
- package/skills/remits-cli/references/component-resolution.md +51 -9
- package/skills/remits-cli/references/development-loop.md +93 -3
package/README.md
CHANGED
|
@@ -22,7 +22,9 @@ remits-cli install --skills
|
|
|
22
22
|
remits-cli tools
|
|
23
23
|
remits-cli tool --base-url http://localhost:8080 --name "My Tool" --input '{"foo":"bar"}'
|
|
24
24
|
remits-cli tool --name mcp_firestore_search --input '{"collection":"statements","documentId":"1234"}' --scope children
|
|
25
|
-
remits-cli
|
|
25
|
+
remits-cli workspace use --auto # once per checkout: your own staging lane
|
|
26
|
+
remits-cli components stage --workset # normal iteration: stage exactly what you changed
|
|
27
|
+
remits-cli components stage # FULL SNAPSHOT of the repo into the lane
|
|
26
28
|
remits-cli components status
|
|
27
29
|
remits-cli components clear
|
|
28
30
|
remits-cli test run --test 45
|
|
@@ -30,6 +32,7 @@ remits-cli test run --test "My New Test" --names "test case 1,test case 2"
|
|
|
30
32
|
git add -A
|
|
31
33
|
git commit -m "sync passing changes"
|
|
32
34
|
git push
|
|
35
|
+
remits-cli components sync --safe # variant branch: gated, dry-runs first, refuses surprises
|
|
33
36
|
remits-cli components sync
|
|
34
37
|
remits-cli components sync --branch feature_branch --dry-run
|
|
35
38
|
remits-cli components sync --branch feature_branch --dry-run --summary
|
|
@@ -61,11 +64,17 @@ remits-cli install --skills --overwrite true
|
|
|
61
64
|
|
|
62
65
|
## How It Works
|
|
63
66
|
|
|
64
|
-
- `components stage`
|
|
67
|
+
- `components stage` has three modes, and the difference decides what a run in the lane resolves:
|
|
68
|
+
- **default (full snapshot)** — uploads the whole repository manifest and reconciles the lane to it, so stale aliases from prior stages are removed. Correct as a complete snapshot and as a "what is stale here?" reset; a poor progress signal, because the lane then holds every component in the repo.
|
|
69
|
+
- **`--workset`** — uploads only the components git reports changed and reconciles the lane to exactly those. The normal iteration mode. `--changed-only --replace-lane` is the explicit spelling.
|
|
70
|
+
- **`--changed-only`** — uploads only the changed components and MERGES, leaving every other staged entry in place. It therefore cannot shrink a lane inherited from an earlier full snapshot; the command warns when it retains entries that way.
|
|
71
|
+
- An **empty workset never clears a lane**: `--workset` on a clean working tree stages nothing and leaves the lane as it is. Pass `--empty-workset clear` to opt in, or use `components clear --all`.
|
|
72
|
+
- A **deleted component file cannot be represented in Redis staging** — clearing a staged entry falls back to the committed row, so the component still resolves. Those changes are reported as NOT REPRESENTABLE. On a non-trunk variant branch, prove a deletion through `components sync --dry-run --summary --fail-on-errors`; on trunk there is no dry-run plan, so use the full pre-sync safety check before any mutating reconcile.
|
|
65
73
|
- Schema `.meta.yml` sidecars can stage/sync the schema flags: `enableTrigger`, `enableFullText`, `enableRAG`, `enableRevisions`, `enableBigQuerySync`, `enableRules`, `anchor`, and `auxiliary`.
|
|
66
74
|
- `components status` shows the branch/user staging entries that can shadow DB components during CLI-scoped test-mode execution.
|
|
67
75
|
- `components clear` clears staged entries. Scope it with `--component-type` and/or `--component-id`. Component ids are type-local, so an id alone clears that one component when the id is staged in only one family; if the same id is staged across multiple families it returns an ambiguity error asking you to add `--component-type`. With no filter it clears every staged entry for the current branch; pass `--all` to force the full-branch wipe explicitly.
|
|
68
|
-
- `components stage`, `components status`, and `components clear` print concise summaries by default. Add `--json` or `--verbose` to print the full server response.
|
|
76
|
+
- `components stage`, `components status`, and `components clear` print concise summaries by default. Add `--json` or `--verbose` to print the full server response.
|
|
77
|
+
- `components stage` and `components status` report three separate numbers, and they answer three different questions: the **workset** (components git reports this working tree changed), what was **submitted**, and the materialized **overlay** the lane now holds — which is what a run resolves. A number that could not be established prints as `unknown`, never as `0`.
|
|
69
78
|
- `components sync` performs a server-side sync from the git remote into the Remits platform for the selected branch. It does not run local git commands. After a successful non-dry-run sync, the platform clears the branch/user staging scope so staged aliases cannot keep shadowing the newly synced DB rows.
|
|
70
79
|
- On trunk, `components sync` performs the full repo-to-DB reconcile. On a non-trunk branch, it writes `ComponentVariant` overlays only and may refresh branch-local `account-info.json`, `account-hierarchy.json`, and `account-configurations.json` for the subscribing account that initiated the sync. `--dry-run` is accepted only for non-trunk variant syncs and reports overrides/additions/tombstones without writing variants, caching the sync SHA, updating metadata, or clearing staging. Add `--summary` to dry-run output when you only need counts, removals/tombstones, errors, skipped items, and warnings. `--force-tombstones` is accepted only for non-trunk variant syncs and should be used only when missing trunk component files are intentional tombstone overrides.
|
|
71
80
|
- `components commit` is a convenience wrapper that performs local git commit/push, then `components sync`, then local `git fetch`/`git pull --ff-only`.
|
|
@@ -86,6 +95,7 @@ remits-cli install --skills --overwrite true
|
|
|
86
95
|
- 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.
|
|
87
96
|
- Avoid commas in individual test names. The `--names` filter is comma-delimited, so a single test case whose name contains commas cannot be targeted cleanly through `remits-cli test run --names ...`.
|
|
88
97
|
- Nested help is available before required-argument validation, including `remits-cli test run --help`, `remits-cli components sync --help`, and `remits-cli tool --help`.
|
|
98
|
+
- `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`.
|
|
89
99
|
- `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".
|
|
90
100
|
- Any command that can touch production prints a `PROD DATA` banner naming the operation, the resolved account, and the host, and distinguishes a live **write** from a live **read** (and from a dry run). This covers `tool`, `test run`, and `components sync`.
|
|
91
101
|
- When a tool call fails inside the platform runtime rather than inside the tool — Groovy reflective dispatch of a runtime-compiled component, an empty connection pool, a Redis reconnect, a lock-wait timeout — the server returns `503` with `failureClass: "transient_infrastructure"` and `retryable: true`, and the CLI prints `TRANSIENT INFRASTRUCTURE FAILURE (retryable)`. A genuine tool error stays a `500` with `failureClass: "tool_error"`. Retry the first; do not retry the second.
|
|
@@ -197,7 +207,7 @@ tail -n 200 ~/.remits-cli/tmux-activity.log
|
|
|
197
207
|
- `tmux` must be installed for agent dispatch to work. Without it, websocket dispatch is received but agent panes cannot be created.
|
|
198
208
|
- In sandboxed local agent environments, `remits-cli` network calls may fail with `ENOTFOUND`, `EAI_AGAIN`, `ECONNREFUSED`, `EPERM`, or similar errors even when dispatch worked correctly. That means the command needs escalated permissions or must be run outside the sandbox.
|
|
199
209
|
- If the platform returns a 500 or other unexpected server-side failure, stop normal task execution and escalate to a Remits system admin. Agents should not invent workarounds for platform faults.
|
|
200
|
-
- Recommended durable update flow for agents: `components stage` for testing, then `git add/commit/push`, then `remits-cli components sync`, then `git pull --ff-only` to confirm platform-generated files like `account-info.json`.
|
|
210
|
+
- Recommended durable update flow for agents: `components stage --workset` for testing, then `git add/commit/push`, then `remits-cli components sync --safe`, then `git pull --ff-only` to confirm platform-generated files like `account-info.json`. To verify the COMMITTED variant rather than your staging, run `components clear --all` first — staged entries still win for CLI-scoped runs.
|
|
201
211
|
- In sandboxed agent environments, local git writes may require a single approval step. If you want to minimize approval churn, `remits-cli components commit` consolidates the local git and sync phases into one CLI command.
|
|
202
212
|
- If account repo discovery fails for a websocket message, dispatch is skipped because the listener does not know which local directory to open for that account.
|
|
203
213
|
- If you authenticate a new account while the listener is already running, the listener now refreshes its websocket clients automatically instead of requiring a manual restart.
|
package/index.js
CHANGED
|
@@ -2458,12 +2458,29 @@ async function pushComponentsCommand(flags) {
|
|
|
2458
2458
|
const mode = requestedMode === 'push' ? 'stage' : requestedMode;
|
|
2459
2459
|
const changedFromWorkingTree = changedComponentsFromWorkingTree(cwd);
|
|
2460
2460
|
|
|
2461
|
-
//
|
|
2462
|
-
//
|
|
2463
|
-
//
|
|
2464
|
-
//
|
|
2465
|
-
|
|
2461
|
+
// WHAT this stage is. Three shapes, and the difference between them is the difference between a lane
|
|
2462
|
+
// that reads as "7 components in flight" and one that reads as "115 staged":
|
|
2463
|
+
//
|
|
2464
|
+
// full snapshot the whole repo manifest, lane reconciled to it `components stage`
|
|
2465
|
+
// workset replace only the git-changed components, lane reconciled `components stage --workset`
|
|
2466
|
+
// workset merge only the git-changed components, merged into the lane `components stage --changed-only`
|
|
2467
|
+
//
|
|
2468
|
+
// `--changed-only` keeps its old merge semantics deliberately — changing what an existing flag does
|
|
2469
|
+
// silently is how a safe command becomes a destructive one for somebody's script. The exact-lane
|
|
2470
|
+
// behavior is the new flag.
|
|
2471
|
+
const worksetReplace = flagEnabled(flags.workset) ||
|
|
2472
|
+
((flagEnabled(flags['changed-only']) || flagEnabled(flags.changedOnly)) &&
|
|
2473
|
+
(flagEnabled(flags['replace-lane']) || flagEnabled(flags.replaceLane)));
|
|
2474
|
+
const changedOnly = worksetReplace || flagEnabled(flags['changed-only']) || flagEnabled(flags.changedOnly);
|
|
2475
|
+
const stageMode = worksetReplace ? 'workset-replace' : (changedOnly ? 'workset-merge' : 'full-snapshot');
|
|
2476
|
+
const emptyWorksetPolicy = normalizeEmptyWorksetPolicy(flags);
|
|
2477
|
+
|
|
2466
2478
|
let components = collectComponents(cwd);
|
|
2479
|
+
// Changes git reported that a component payload cannot carry — a deleted component file has nothing to
|
|
2480
|
+
// stage, and Redis staging has no way to say "hide this during CLI-scoped runs". Removing its staged
|
|
2481
|
+
// entry falls back to the committed row, so the component still resolves. Reported out loud rather than
|
|
2482
|
+
// dropped, because "my delete did not take effect" otherwise looks like a platform bug.
|
|
2483
|
+
const unstageable = unrepresentableChanges(changedFromWorkingTree, components);
|
|
2467
2484
|
if (changedOnly) {
|
|
2468
2485
|
if (changedFromWorkingTree === null) {
|
|
2469
2486
|
throw new Error('--changed-only needs a git working tree to establish the changed set, and this directory is not one.');
|
|
@@ -2472,10 +2489,20 @@ async function pushComponentsCommand(flags) {
|
|
|
2472
2489
|
entry.type + ':' + (entry.id ? 'id:' + entry.id : 'name:' + String(entry.name || '').toLowerCase())));
|
|
2473
2490
|
components = components.filter((component) => wanted.has(
|
|
2474
2491
|
component.type + ':' + (component.id ? 'id:' + component.id : 'name:' + String(component.name || '').toLowerCase())));
|
|
2475
|
-
if (!components.length) {
|
|
2476
|
-
|
|
2492
|
+
if (!components.length && !(worksetReplace && emptyWorksetPolicy === 'clear')) {
|
|
2493
|
+
// Deliberately NOT an error, and deliberately not a clear. An agent that has not edited anything yet
|
|
2494
|
+
// is in an ordinary state; failing its loop teaches it nothing, and reconciling the lane to an empty
|
|
2495
|
+
// manifest would delete the overlay its next test run depends on.
|
|
2496
|
+
console.log('Stage mode:', stageMode);
|
|
2497
|
+
console.log('Workset detected from git: 0 component(s) — nothing was staged and the lane was left as it is.');
|
|
2498
|
+
if (worksetReplace) {
|
|
2499
|
+
console.log(' The lane was NOT cleared. Clearing stays explicit: `remits-cli components clear --all`,');
|
|
2500
|
+
console.log(' or re-run with `--empty-workset clear` if an empty lane is what you meant.');
|
|
2501
|
+
}
|
|
2477
2502
|
printStagingLane(branchName, workspace, workspaceSource(cwd, flags));
|
|
2478
|
-
|
|
2503
|
+
printUnrepresentableChanges(unstageable);
|
|
2504
|
+
return { success: true, mode, updated: 0, unchanged: 0, skipped: [], changedOnly: true, stageMode,
|
|
2505
|
+
worksetCount: 0, staged: false };
|
|
2479
2506
|
}
|
|
2480
2507
|
}
|
|
2481
2508
|
|
|
@@ -2503,13 +2530,27 @@ async function pushComponentsCommand(flags) {
|
|
|
2503
2530
|
...runContextPayload(flags),
|
|
2504
2531
|
agentId: flags['agent-id'] || process.env.REMITS_AGENT_ID || undefined,
|
|
2505
2532
|
acknowledge: flags.acknowledge || undefined,
|
|
2506
|
-
//
|
|
2507
|
-
//
|
|
2533
|
+
// What this stage IS, stated rather than implied. The server derives the lane behavior from the mode
|
|
2534
|
+
// (CliComponentStaging.stageRequest) instead of from `replace`, which was overloaded: a partial
|
|
2535
|
+
// manifest with `replace: true` would make the reconcile pass delete nearly the whole lane, so
|
|
2536
|
+
// `--changed-only` had to send `replace: false` and could therefore never shrink a lane it inherited.
|
|
2537
|
+
stageMode,
|
|
2538
|
+
manifestScope: changedOnly ? 'working-tree' : 'repo',
|
|
2539
|
+
emptyWorksetPolicy,
|
|
2540
|
+
// Whether git could answer at all. A zero rendered for an unavailable answer is exactly the kind of
|
|
2541
|
+
// confident wrong number this whole surface exists to remove.
|
|
2542
|
+
worksetKnown: changedFromWorkingTree !== null,
|
|
2543
|
+
// The git evidence, per component: which files changed and with what status. It is what lets the
|
|
2544
|
+
// console split a lane's "active workset" from the entries a previous full snapshot left behind.
|
|
2545
|
+
changedSet: (changedFromWorkingTree || []).concat(unstageable),
|
|
2546
|
+
// Kept for a platform that predates stageMode. Same meaning it always had.
|
|
2508
2547
|
replace: !changedOnly,
|
|
2509
2548
|
components
|
|
2510
2549
|
});
|
|
2511
2550
|
response.changedFromWorkingTree = changedFromWorkingTree || [];
|
|
2512
2551
|
response.changedFromWorkingTreeAvailable = changedFromWorkingTree !== null;
|
|
2552
|
+
response.requestedStageMode = stageMode;
|
|
2553
|
+
response.unrepresentableChanges = unstageable;
|
|
2513
2554
|
|
|
2514
2555
|
if (flagEnabled(flags.json)) {
|
|
2515
2556
|
console.log(JSON.stringify(response, null, 2));
|
|
@@ -2520,11 +2561,53 @@ async function pushComponentsCommand(flags) {
|
|
|
2520
2561
|
printResolvedBaseUrl(baseUrl);
|
|
2521
2562
|
printStagingLane(response.branchName || branchName, response.workspace || workspace, workspaceSource(cwd, flags));
|
|
2522
2563
|
console.log('Data mode:', response.dataMode || dataMode);
|
|
2523
|
-
console.log('Mode:', response.mode || mode + (changedOnly ? ' (--changed-only)' : ''));
|
|
2524
2564
|
printComponentPolicy(response);
|
|
2525
2565
|
printStageSummary(response, flags);
|
|
2526
2566
|
}
|
|
2527
2567
|
|
|
2568
|
+
/** `--empty-workset refuse|clear`. Refusing is the default, and the only safe one. */
|
|
2569
|
+
function normalizeEmptyWorksetPolicy(flags) {
|
|
2570
|
+
const raw = flags['empty-workset'] != null ? flags['empty-workset'] : flags.emptyWorkset;
|
|
2571
|
+
const value = raw === true ? '' : String(raw == null ? '' : raw).trim().toLowerCase();
|
|
2572
|
+
if (value === 'clear' || value === 'clears-lane') return 'clear';
|
|
2573
|
+
return 'refuse';
|
|
2574
|
+
}
|
|
2575
|
+
|
|
2576
|
+
/**
|
|
2577
|
+
* Git-reported component changes that a stage payload cannot carry.
|
|
2578
|
+
*
|
|
2579
|
+
* A deleted component file has no content to send, and Redis staging has no way to express "hide this
|
|
2580
|
+
* during CLI-scoped runs" — clearing its staged entry falls back to the committed row, so the component
|
|
2581
|
+
* still resolves. An agent that deletes a file, stages, and sees the component still running would read
|
|
2582
|
+
* that as a platform defect. Durable deletion lives in variant sync (tombstones), not here.
|
|
2583
|
+
*
|
|
2584
|
+
* Returns [] when git could not answer, because "nothing is unrepresentable" would be a claim.
|
|
2585
|
+
*/
|
|
2586
|
+
function unrepresentableChanges(changedFromWorkingTree, collected) {
|
|
2587
|
+
if (!Array.isArray(changedFromWorkingTree)) return [];
|
|
2588
|
+
const present = new Set((collected || []).map((component) =>
|
|
2589
|
+
component.type + ':' + (component.id ? 'id:' + component.id : 'name:' + String(component.name || '').toLowerCase())));
|
|
2590
|
+
return changedFromWorkingTree
|
|
2591
|
+
.filter((entry) => !present.has(entry.type + ':' + (entry.id ? 'id:' + entry.id : 'name:' + String(entry.name || '').toLowerCase())))
|
|
2592
|
+
.map((entry) => Object.assign({}, entry, {
|
|
2593
|
+
unrepresentable: true,
|
|
2594
|
+
reason: (entry.statuses || []).some((s) => String(s).includes('D')) ? 'deleted' : 'no-local-files'
|
|
2595
|
+
}));
|
|
2596
|
+
}
|
|
2597
|
+
|
|
2598
|
+
function printUnrepresentableChanges(unstageable) {
|
|
2599
|
+
if (!unstageable || !unstageable.length) return;
|
|
2600
|
+
console.log('');
|
|
2601
|
+
console.log('NOT REPRESENTABLE IN REDIS STAGING — ' + unstageable.length + ' change(s):');
|
|
2602
|
+
unstageable.slice(0, 20).forEach((entry) => {
|
|
2603
|
+
console.log(' ' + entry.type + ' ' + (entry.id || entry.name || '(unknown)') +
|
|
2604
|
+
' (' + entry.reason + ') ' + (entry.paths || []).join(', '));
|
|
2605
|
+
});
|
|
2606
|
+
console.log(' A staged entry cannot hide a component. Clearing it falls back to the committed row, so');
|
|
2607
|
+
console.log(' the component still resolves in a CLI-scoped run. Prove a deletion through the durable');
|
|
2608
|
+
console.log(' plan instead: remits-cli components sync --dry-run --summary --fail-on-errors');
|
|
2609
|
+
}
|
|
2610
|
+
|
|
2528
2611
|
/**
|
|
2529
2612
|
* A policy refusal is an ANSWER, not a transport failure.
|
|
2530
2613
|
*
|
|
@@ -2602,15 +2685,55 @@ function printComponentCommandResponse(label, response, flags) {
|
|
|
2602
2685
|
printFullResponseHint();
|
|
2603
2686
|
}
|
|
2604
2687
|
|
|
2688
|
+
/**
|
|
2689
|
+
* What this stage did, in the terms a human is actually asking about.
|
|
2690
|
+
*
|
|
2691
|
+
* Three numbers, and they are three different questions:
|
|
2692
|
+
*
|
|
2693
|
+
* WORKSET how many components this working tree changed, per git
|
|
2694
|
+
* SUBMITTED how many this command uploaded
|
|
2695
|
+
* OVERLAY how many staged entries the lane now holds, which is what a run resolves
|
|
2696
|
+
*
|
|
2697
|
+
* They used to be reported as one number — the overlay — so an agent that edited 5 components in a repo
|
|
2698
|
+
* of 115 produced "Updated: 115" and a console badge reading "STAGED 115". Nobody could tell 115 edits
|
|
2699
|
+
* from one full snapshot, and those want opposite actions.
|
|
2700
|
+
*/
|
|
2605
2701
|
function printStageSummary(response, flags) {
|
|
2606
|
-
// `stage` always uploads EVERY component in the repo, so the server's Updated/Unchanged counts
|
|
2607
|
-
// describe the whole staging overlay, not what this checkout edited. That is what made "Updated: 75"
|
|
2608
|
-
// read as "I changed 75 components". Lead with the working-tree delta, which is the number an agent
|
|
2609
|
-
// is actually asking about, and label the server counts as the overlay they describe.
|
|
2610
2702
|
const changed = Array.isArray(response.changedFromWorkingTree) ? response.changedFromWorkingTree : [];
|
|
2611
2703
|
const tracked = response.changedFromWorkingTreeAvailable !== false;
|
|
2704
|
+
const stage = response.stage || {};
|
|
2705
|
+
const lane = response.laneSummary || {};
|
|
2706
|
+
// What we ASKED for, and what the platform says it did. Kept apart deliberately: comparing the server's
|
|
2707
|
+
// answer against a value that already fell back to it can never disagree, so the mismatch note below
|
|
2708
|
+
// could never fire.
|
|
2709
|
+
const requestedMode = response.requestedStageMode || null;
|
|
2710
|
+
const stageMode = stage.mode || requestedMode || 'full-snapshot';
|
|
2711
|
+
|
|
2712
|
+
console.log('Stage mode:', describeStageMode(stageMode));
|
|
2713
|
+
|
|
2714
|
+
// A platform that predates stage modes ignores `stageMode` and reads `replace`, which a workset stage
|
|
2715
|
+
// sends as false — so it MERGES where the caller asked for a lane replacement, and returns no `stage`
|
|
2716
|
+
// block to say so. Silence there is the worst outcome available: the agent believes the lane holds
|
|
2717
|
+
// exactly its workset while the platform left an inherited overlay in place. Detect it by what came
|
|
2718
|
+
// back rather than by a version number.
|
|
2719
|
+
if (stage.mode == null && response.staging) {
|
|
2720
|
+
console.log('NOTE: this platform predates stage modes. It read the legacy `replace` flag, so a');
|
|
2721
|
+
console.log(' --workset stage MERGED into the lane instead of reconciling it, and the workset/');
|
|
2722
|
+
console.log(' overlay split below is not available. Use `components clear --all` first if you need');
|
|
2723
|
+
console.log(' the lane to hold exactly your workset.');
|
|
2724
|
+
} else if (stage.mode && requestedMode && stage.mode !== requestedMode) {
|
|
2725
|
+
console.log('NOTE: asked the platform for `' + requestedMode + '`; it applied `' + stage.mode + '`.');
|
|
2726
|
+
}
|
|
2727
|
+
|
|
2728
|
+
if (response.sharedLane === true) {
|
|
2729
|
+
console.log('Lane: SHARED (no workspace) — a full stage REPLACES it rather than merging, so a second');
|
|
2730
|
+
console.log(' agent staging here overwrites what you are testing. `remits-cli workspace use --auto`.');
|
|
2731
|
+
}
|
|
2732
|
+
|
|
2733
|
+
// "git could not answer" and "git says nothing changed" are different facts, and only one of them is a
|
|
2734
|
+
// number. Never render the missing answer as zero.
|
|
2612
2735
|
if (tracked) {
|
|
2613
|
-
console.log('
|
|
2736
|
+
console.log('Workset detected from git:', changed.length, 'component(s)');
|
|
2614
2737
|
changed.slice(0, 20).forEach((entry) => {
|
|
2615
2738
|
const label = (entry.type || 'component') + ' ' + (entry.id || entry.name || '(unknown)');
|
|
2616
2739
|
const name = entry.name && entry.id ? ' ' + entry.name : '';
|
|
@@ -2623,23 +2746,66 @@ function printStageSummary(response, flags) {
|
|
|
2623
2746
|
if (!changed.length) {
|
|
2624
2747
|
console.log(' (no uncommitted component edits — already-committed edits are staged but not listed here)');
|
|
2625
2748
|
}
|
|
2749
|
+
} else {
|
|
2750
|
+
console.log('Workset detected from git: UNKNOWN (this is not a git working tree)');
|
|
2626
2751
|
}
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2752
|
+
|
|
2753
|
+
const submitted = stage.submittedCount != null
|
|
2754
|
+
? stage.submittedCount
|
|
2755
|
+
: ((response.updated || 0) + (response.unchanged || 0));
|
|
2756
|
+
console.log('Submitted to the platform:', submitted, 'component(s) (' +
|
|
2757
|
+
(response.updated || 0) + ' newly written, ' + (response.unchanged || 0) + ' already current)');
|
|
2758
|
+
if (Array.isArray(response.skipped) && response.skipped.length) {
|
|
2759
|
+
console.log('Skipped (no type/hash/identity):', response.skipped.length);
|
|
2632
2760
|
}
|
|
2633
|
-
if (
|
|
2634
|
-
console.log('
|
|
2761
|
+
if (stage.removedRetainedCount) {
|
|
2762
|
+
console.log('Removed from the lane by this stage:', stage.removedRetainedCount, 'staged entry(ies)');
|
|
2763
|
+
} else if (response.reconcile) {
|
|
2764
|
+
console.log('Removed from the lane by this stage:', response.reconcile.removedCount || 0, 'staged entry(ies)');
|
|
2635
2765
|
}
|
|
2766
|
+
|
|
2767
|
+
const overlay = lane.stagedCount != null
|
|
2768
|
+
? lane.stagedCount
|
|
2769
|
+
: (response.staging ? response.staging.remainingCount : null);
|
|
2770
|
+
if (overlay != null) {
|
|
2771
|
+
console.log('Materialized overlay now held by the lane:', overlay, 'component(s)');
|
|
2772
|
+
}
|
|
2773
|
+
|
|
2774
|
+
// The trap `--changed-only` leaves behind: it merges, so a lane inherited from an earlier full snapshot
|
|
2775
|
+
// keeps every one of those entries resolving ahead of committed source. Silence here is what made
|
|
2776
|
+
// "I staged 7 components" and "this lane holds 115" look like the same statement.
|
|
2777
|
+
const retained = lane.retainedCount;
|
|
2778
|
+
if (retained > 0) {
|
|
2779
|
+
console.log('');
|
|
2780
|
+
console.log('WARNING: ' + retained + ' staged component(s) in this lane were NOT submitted by this stage.');
|
|
2781
|
+
console.log(' They came from an earlier stage and still shadow committed source for every run in this lane.');
|
|
2782
|
+
console.log(' Make the lane exactly your workset: remits-cli components stage --workset');
|
|
2783
|
+
console.log(' Or drop the overlay entirely: remits-cli components clear --all');
|
|
2784
|
+
}
|
|
2785
|
+
|
|
2786
|
+
printUnrepresentableChanges(response.unrepresentableChanges || (stage.unrepresentable || []));
|
|
2636
2787
|
printComponentCommandResponse('Components stage', response, flags);
|
|
2637
2788
|
}
|
|
2638
2789
|
|
|
2790
|
+
function describeStageMode(mode) {
|
|
2791
|
+
switch (mode) {
|
|
2792
|
+
case 'full-snapshot':
|
|
2793
|
+
return 'full snapshot (whole repo manifest; the lane is reconciled to it)';
|
|
2794
|
+
case 'workset-replace':
|
|
2795
|
+
return 'workset replace (only the components git reports changed; the lane is reconciled to exactly them)';
|
|
2796
|
+
case 'workset-merge':
|
|
2797
|
+
return 'workset merge (only the components git reports changed, MERGED into whatever the lane already held)';
|
|
2798
|
+
case 'partial':
|
|
2799
|
+
return 'partial (single-component field edit)';
|
|
2800
|
+
default:
|
|
2801
|
+
return mode || 'unknown';
|
|
2802
|
+
}
|
|
2803
|
+
}
|
|
2804
|
+
|
|
2639
2805
|
function printStatusSummary(response, flags) {
|
|
2640
2806
|
printBranchContext(response);
|
|
2641
2807
|
printStagingLane(response.branchName, response.workspace, null);
|
|
2642
|
-
|
|
2808
|
+
printLaneSummary(response);
|
|
2643
2809
|
printComponentTypeCounts(response.entries || []);
|
|
2644
2810
|
|
|
2645
2811
|
// Every lane staged on this branch, not just this one. An agent that believes it is working alone can
|
|
@@ -2663,6 +2829,48 @@ function printStatusSummary(response, flags) {
|
|
|
2663
2829
|
printComponentCommandResponse('Components staging', response, flags);
|
|
2664
2830
|
}
|
|
2665
2831
|
|
|
2832
|
+
/**
|
|
2833
|
+
* What THIS lane holds, separated into the work in flight and the overlay carried along with it.
|
|
2834
|
+
*
|
|
2835
|
+
* Read authoritatively from the staging keys (not the lane index), so the numbers here are the ones a run
|
|
2836
|
+
* in this lane will actually resolve.
|
|
2837
|
+
*/
|
|
2838
|
+
function printLaneSummary(response) {
|
|
2839
|
+
const lane = response.laneSummary || {};
|
|
2840
|
+
const overlay = lane.stagedCount != null ? lane.stagedCount : (response.stagedCount || 0);
|
|
2841
|
+
|
|
2842
|
+
console.log('Materialized overlay (what a run in this lane resolves):', overlay, 'component(s)');
|
|
2843
|
+
// An EMPTY lane has no last stage, so "Last stage: unknown" would invite a reader to go looking for a
|
|
2844
|
+
// stage that never happened. Nothing staged is clean state, not a missing answer.
|
|
2845
|
+
if (!overlay) {
|
|
2846
|
+
console.log('Nothing is staged in this lane; runs here resolve committed source.');
|
|
2847
|
+
} else if (lane.stageMode && lane.stageMode !== 'unknown') {
|
|
2848
|
+
console.log('Last stage:', describeStageMode(lane.stageMode));
|
|
2849
|
+
if (lane.submittedCount != null) {
|
|
2850
|
+
console.log(' submitted by that stage:', lane.submittedCount, 'component(s)');
|
|
2851
|
+
}
|
|
2852
|
+
// null is "not established", never zero. A lane staged by a build that predates stage provenance has
|
|
2853
|
+
// an unknown workset, and printing 0 would assert that nobody is working in it.
|
|
2854
|
+
if (lane.worksetKnown && lane.worksetCount != null) {
|
|
2855
|
+
console.log(' of which git reported as edited:', lane.worksetCount, 'component(s)');
|
|
2856
|
+
} else {
|
|
2857
|
+
console.log(' of which git reported as edited: unknown (that stage reported no git evidence — an');
|
|
2858
|
+
console.log(' older CLI, a non-git checkout, or a single-field MCP edit)');
|
|
2859
|
+
}
|
|
2860
|
+
if (lane.retainedCount > 0) {
|
|
2861
|
+
console.log(' retained from EARLIER stages:', lane.retainedCount, 'component(s) — these still shadow');
|
|
2862
|
+
console.log(' committed source. `components stage --workset` makes the lane exactly your workset.');
|
|
2863
|
+
}
|
|
2864
|
+
} else {
|
|
2865
|
+
console.log('Last stage: unknown (staged before stage modes existed, or by another door)');
|
|
2866
|
+
}
|
|
2867
|
+
|
|
2868
|
+
if (response.sharedLane === true) {
|
|
2869
|
+
console.log('Lane isolation: SHARED — no workspace. A full stage here REPLACES the lane rather than');
|
|
2870
|
+
console.log(' merging, so a second agent overwrites what you are testing. `remits-cli workspace use --auto`.');
|
|
2871
|
+
}
|
|
2872
|
+
}
|
|
2873
|
+
|
|
2666
2874
|
// Every staging lane on the ACCOUNT, across branches.
|
|
2667
2875
|
//
|
|
2668
2876
|
// The block above only ever covered lanes on the SAME git branch, because that is all its key pattern
|
|
@@ -2689,8 +2897,14 @@ function printAccountLanes(response) {
|
|
|
2689
2897
|
const who = lane.userEmail ? ' ' + lane.userEmail : '';
|
|
2690
2898
|
const ttl = lane.expiresInSeconds != null
|
|
2691
2899
|
? ' expires in ~' + Math.round(lane.expiresInSeconds / 60) + 'm' : '';
|
|
2900
|
+
// Overlay first (it is what a run resolves), then the work the last stage actually submitted. A lane
|
|
2901
|
+
// showing "115 overlay / 7 workset" is one agent iterating on 7 components, not 115 edits in flight.
|
|
2902
|
+
const workset = (lane.worksetKnown && lane.worksetCountAsOf != null)
|
|
2903
|
+
? ', workset ' + lane.worksetCountAsOf : '';
|
|
2904
|
+
const stageMode = lane.stageMode && lane.stageMode !== 'unknown' ? ' [' + lane.stageMode + ']' : '';
|
|
2905
|
+
const shared = lane.sharedLane === true ? ' SHARED LANE' : '';
|
|
2692
2906
|
console.log(' ' + (lane.mine ? '* ' : ' ') + lane.branchName + ws
|
|
2693
|
-
+ ' (' + world + ') — ' + (lane.stagedCountAsOf || 0) +
|
|
2907
|
+
+ ' (' + world + ') — overlay ' + (lane.stagedCountAsOf || 0) + workset + stageMode + shared + who + ttl);
|
|
2694
2908
|
});
|
|
2695
2909
|
console.log(' (* = this command\'s lane)');
|
|
2696
2910
|
console.log(' A variant-branch lane layers over that branch\'s ComponentVariant overlays, and a commit');
|
|
@@ -2887,8 +3101,13 @@ async function clearComponentsCommand(flags) {
|
|
|
2887
3101
|
return response;
|
|
2888
3102
|
}
|
|
2889
3103
|
|
|
2890
|
-
async function syncComponentsCommand(
|
|
3104
|
+
async function syncComponentsCommand(rawFlags) {
|
|
2891
3105
|
const cwd = process.cwd();
|
|
3106
|
+
// `--safe` is a NAME for the gate combination that should be the default agent path, not a new gate.
|
|
3107
|
+
// Spelling out --summary --changed-only --fail-on-errors --fail-on-removed correctly every time is the
|
|
3108
|
+
// step agents skip, and skipping it is how a stale branch writes 34 unrelated ComponentVariant rows.
|
|
3109
|
+
const safe = flagEnabled(rawFlags.safe);
|
|
3110
|
+
const flags = safe ? expandSafeSyncFlags(rawFlags) : rawFlags;
|
|
2892
3111
|
ensureLocalState(cwd);
|
|
2893
3112
|
const sessionContext = resolveSessionContext(cwd, flags);
|
|
2894
3113
|
const { session, accountId } = sessionContext;
|
|
@@ -2935,6 +3154,10 @@ async function syncComponentsCommand(flags) {
|
|
|
2935
3154
|
};
|
|
2936
3155
|
|
|
2937
3156
|
let preflightGate = null;
|
|
3157
|
+
// The flags the gates are actually evaluated with. `--safe` may resolve `--changed-since` for the
|
|
3158
|
+
// caller, and it must be the SAME value the preflight and the post-sync evaluation use, or the two
|
|
3159
|
+
// could disagree about the same plan.
|
|
3160
|
+
let gateFlags = flags;
|
|
2938
3161
|
if (preflightRequested && !dryRun) {
|
|
2939
3162
|
const statusResponse = await loggedPost(api, cwd, '/cli/components', {
|
|
2940
3163
|
token: session.token,
|
|
@@ -2947,31 +3170,63 @@ async function syncComponentsCommand(flags) {
|
|
|
2947
3170
|
|
|
2948
3171
|
const branchContext = statusResponse && statusResponse.branchContext;
|
|
2949
3172
|
if (!branchContext || branchContext.onTrunk) {
|
|
2950
|
-
|
|
2951
|
-
|
|
3173
|
+
// Trunk has no dry-run plan to gate on, and pretending otherwise would be worse than refusing:
|
|
3174
|
+
// an agent would read "safe" and get an ungated authoritative reconcile. Say what trunk sync is
|
|
3175
|
+
// and make the caller assert it.
|
|
3176
|
+
if (!safe) {
|
|
3177
|
+
throw new Error('components sync safety preflight requires a non-trunk variant branch because trunk sync has no dry-run plan. Re-run without safety flags only if you intend a full trunk reconcile.');
|
|
3178
|
+
}
|
|
3179
|
+
console.log('');
|
|
3180
|
+
console.log('TRUNK SYNC — there is no dry-run plan for trunk, so --safe cannot gate this.');
|
|
3181
|
+
console.log(' This is an AUTHORITATIVE full repo-to-DB reconcile of ' + (branchContext ? branchContext.trunkBranch || branchName : branchName) + ':');
|
|
3182
|
+
console.log(' every component row is rewritten from the pushed repo, and any live component');
|
|
3183
|
+
console.log(' missing from the repo is DELETED.');
|
|
3184
|
+
console.log(' For a gated plan, work on a variant branch instead.');
|
|
3185
|
+
if (!flagEnabled(flags.yes)) {
|
|
3186
|
+
throw new Error('components sync --safe on trunk requires --yes, because the plan cannot be previewed. ' +
|
|
3187
|
+
'Re-run with --yes to accept the full trunk reconcile, or switch to a variant branch for a gated sync.');
|
|
3188
|
+
}
|
|
3189
|
+
// Drop the gates that can only be evaluated against a plan. Leaving them on would fail the command
|
|
3190
|
+
// AFTER an authoritative reconcile had already been written, which is the worst of both.
|
|
3191
|
+
gateFlags = Object.assign({}, flags, {
|
|
3192
|
+
'changed-only': false, changedOnly: false,
|
|
3193
|
+
'fail-on-removed': false, failOnRemoved: false
|
|
3194
|
+
});
|
|
3195
|
+
} else {
|
|
3196
|
+
gateFlags = resolveChangedSinceFlags(flags, cwd, branchContext, safe);
|
|
2952
3197
|
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
3198
|
+
const previewResponse = await loggedPost(api, cwd, '/cli/components', Object.assign({}, syncPayload, {
|
|
3199
|
+
dryRun: true
|
|
3200
|
+
})).then((r) => r.data);
|
|
2956
3201
|
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
3202
|
+
if (!previewResponse.success) {
|
|
3203
|
+
throw new Error(previewResponse.message || 'Server sync dry-run failed');
|
|
3204
|
+
}
|
|
2960
3205
|
|
|
2961
|
-
|
|
3206
|
+
preflightGate = evaluateSyncGates(previewResponse, gateFlags, changedFromWorkingTree, cwd);
|
|
3207
|
+
|
|
3208
|
+
if (namesOnly) {
|
|
3209
|
+
printSessionResolutionWarning(sessionContext);
|
|
3210
|
+
printResolvedBaseUrl(baseUrl);
|
|
3211
|
+
console.log('Data mode:', previewResponse.dataMode || dataMode);
|
|
3212
|
+
console.log('Mode:', 'sync dry-run');
|
|
3213
|
+
console.log('Dry run: no variants were written and staging was not cleared.');
|
|
3214
|
+
printSyncNames(previewResponse);
|
|
3215
|
+
failOnSyncGate(preflightGate);
|
|
3216
|
+
return previewResponse;
|
|
3217
|
+
}
|
|
2962
3218
|
|
|
2963
|
-
if (namesOnly) {
|
|
2964
|
-
printSessionResolutionWarning(sessionContext);
|
|
2965
|
-
printResolvedBaseUrl(baseUrl);
|
|
2966
|
-
console.log('Data mode:', previewResponse.dataMode || dataMode);
|
|
2967
|
-
console.log('Mode:', 'sync dry-run');
|
|
2968
|
-
console.log('Dry run: no variants were written and staging was not cleared.');
|
|
2969
|
-
printSyncNames(previewResponse);
|
|
2970
3219
|
failOnSyncGate(preflightGate);
|
|
2971
|
-
return previewResponse;
|
|
2972
|
-
}
|
|
2973
3220
|
|
|
2974
|
-
|
|
3221
|
+
// The plan passed every gate. Show it before mutating anyway: a gate proves the plan matches the
|
|
3222
|
+
// changed set, not that the changed set is what the author meant.
|
|
3223
|
+
if (safe && !flagEnabled(flags.yes)) {
|
|
3224
|
+
console.log('');
|
|
3225
|
+
console.log('Planned durable writes (gates passed):');
|
|
3226
|
+
printSyncNames(previewResponse);
|
|
3227
|
+
console.log('');
|
|
3228
|
+
}
|
|
3229
|
+
}
|
|
2975
3230
|
}
|
|
2976
3231
|
|
|
2977
3232
|
// Through the same refusal-aware door as `stage`: the landing lease and the account's commit rules both
|
|
@@ -2983,7 +3238,7 @@ async function syncComponentsCommand(flags) {
|
|
|
2983
3238
|
}
|
|
2984
3239
|
|
|
2985
3240
|
const summary = buildSyncSummary(response);
|
|
2986
|
-
const gate = preflightGate || evaluateSyncGates(response,
|
|
3241
|
+
const gate = preflightGate || evaluateSyncGates(response, gateFlags, changedFromWorkingTree, cwd);
|
|
2987
3242
|
summary.gates = gate.checks;
|
|
2988
3243
|
|
|
2989
3244
|
if (flagEnabled(flags.json)) {
|
|
@@ -3093,8 +3348,73 @@ function printPromotionSignals(response) {
|
|
|
3093
3348
|
});
|
|
3094
3349
|
}
|
|
3095
3350
|
|
|
3351
|
+
/**
|
|
3352
|
+
* `--safe`: the gate combination that should be the normal agent path for a variant-branch sync.
|
|
3353
|
+
*
|
|
3354
|
+
* Named rather than remembered. The individual flags have existed for a while and the failure they exist
|
|
3355
|
+
* to stop — a stale branch whose old copies of untouched files become unrelated ComponentVariant
|
|
3356
|
+
* overrides — happens precisely when somebody forgets one of them.
|
|
3357
|
+
*
|
|
3358
|
+
* `--expected-removed` already implies fail-on-removed and NAMES the removals the caller accepts, so
|
|
3359
|
+
* --safe must not layer a blanket --fail-on-removed on top of it and contradict the narrower answer.
|
|
3360
|
+
*/
|
|
3361
|
+
function expandSafeSyncFlags(flags) {
|
|
3362
|
+
const expectedRemoved = flags['expected-removed'] != null ? flags['expected-removed'] : flags.expectedRemoved;
|
|
3363
|
+
return Object.assign({}, flags, {
|
|
3364
|
+
summary: true,
|
|
3365
|
+
'fail-on-errors': true,
|
|
3366
|
+
'changed-only': true,
|
|
3367
|
+
'fail-on-removed': expectedRemoved != null ? flags['fail-on-removed'] : true
|
|
3368
|
+
});
|
|
3369
|
+
}
|
|
3370
|
+
|
|
3371
|
+
/**
|
|
3372
|
+
* The base ref the `--changed-only` gate compares the server plan against.
|
|
3373
|
+
*
|
|
3374
|
+
* `--changed-only` alone reads UNCOMMITTED edits, and the documented flow commits and pushes BEFORE the
|
|
3375
|
+
* sync (the server syncs by pulling the remote, so it cannot see uncommitted work at all). So at the
|
|
3376
|
+
* moment the gate runs, the working tree is usually clean and the changed set is empty — which used to
|
|
3377
|
+
* make the gate refuse every plan.
|
|
3378
|
+
*
|
|
3379
|
+
* Under --safe the ref is derived from the branch's own merge base with trunk when the caller did not
|
|
3380
|
+
* name one. Local refs only: no implicit `git fetch`, because a status/gate command that quietly reaches
|
|
3381
|
+
* the network is a command whose cost and failure modes the caller did not agree to. When nothing local
|
|
3382
|
+
* can answer, this refuses with the command to run rather than gating against an empty set.
|
|
3383
|
+
*/
|
|
3384
|
+
function resolveChangedSinceFlags(flags, cwd, branchContext, safe) {
|
|
3385
|
+
const explicit = flags['changed-since'] || flags.changedSince;
|
|
3386
|
+
if (explicit || !safe) {
|
|
3387
|
+
return flags;
|
|
3388
|
+
}
|
|
3389
|
+
const trunk = (branchContext && branchContext.trunkBranch) || 'main';
|
|
3390
|
+
const candidates = ['origin/' + trunk, trunk];
|
|
3391
|
+
for (const candidate of candidates) {
|
|
3392
|
+
let base = null;
|
|
3393
|
+
try {
|
|
3394
|
+
base = execSync('git merge-base HEAD ' + JSON.stringify(candidate), {
|
|
3395
|
+
cwd,
|
|
3396
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
3397
|
+
}).toString().trim();
|
|
3398
|
+
} catch (_) {
|
|
3399
|
+
continue;
|
|
3400
|
+
}
|
|
3401
|
+
if (base) {
|
|
3402
|
+
console.log('--safe: comparing the sync plan against everything this branch added since ' +
|
|
3403
|
+
candidate + ' (' + base.slice(0, 12) + ').');
|
|
3404
|
+
return Object.assign({}, flags, { 'changed-since': base });
|
|
3405
|
+
}
|
|
3406
|
+
}
|
|
3407
|
+
throw new Error(
|
|
3408
|
+
'components sync --safe needs a base ref to compare the plan against, and neither origin/' + trunk +
|
|
3409
|
+
' nor ' + trunk + ' could be resolved locally.\n' +
|
|
3410
|
+
' Run `git fetch origin ' + trunk + '` and retry, or pass it yourself:\n' +
|
|
3411
|
+
' remits-cli components sync --safe --changed-since origin/' + trunk
|
|
3412
|
+
);
|
|
3413
|
+
}
|
|
3414
|
+
|
|
3096
3415
|
function syncPreflightRequested(flags) {
|
|
3097
3416
|
return Boolean(
|
|
3417
|
+
flagEnabled(flags.safe) ||
|
|
3098
3418
|
flagEnabled(flags['names-only']) ||
|
|
3099
3419
|
flagEnabled(flags.namesOnly) ||
|
|
3100
3420
|
flagEnabled(flags['changed-only']) ||
|
|
@@ -3214,7 +3534,16 @@ function evaluateSyncGates(response, flags, changedFromWorkingTree, cwd) {
|
|
|
3214
3534
|
? '\n The changed set is EMPTY: --changed-only reads UNCOMMITTED edits, and the documented flow ' +
|
|
3215
3535
|
'commits and pushes before syncing. Pass --changed-since <ref> (e.g. the commit you branched ' +
|
|
3216
3536
|
'from, or origin/main) so the gate can see committed work.'
|
|
3217
|
-
:
|
|
3537
|
+
// A plan that reaches well beyond a real changed set is almost always a STALE BRANCH: it still
|
|
3538
|
+
// physically carries old copies of files nobody on it touched, and a variant sync turns each of
|
|
3539
|
+
// those into an unrelated override. Naming the cause is the difference between a gate that
|
|
3540
|
+
// stops the damage and a gate that also tells you how to clear it.
|
|
3541
|
+
: (changedSet.length && unexpected.length > changedSet.length)
|
|
3542
|
+
? '\n Most likely cause: this branch is BEHIND trunk and still carries old copies of files ' +
|
|
3543
|
+
'it never changed. A variant sync turns each of those into an unrelated override.' +
|
|
3544
|
+
'\n Next step: merge trunk into this branch, push, then re-run the same command:' +
|
|
3545
|
+
'\n git merge <trunk> && git push origin <branch>'
|
|
3546
|
+
: '';
|
|
3218
3547
|
violations.push('--changed-only: plan touches ' + unexpected.length + ' component(s) this checkout did not change: ' +
|
|
3219
3548
|
unexpected.slice(0, 20).map(syncEntryToken).join(', ') + (unexpected.length > 20 ? ', ...' : '') + hint);
|
|
3220
3549
|
}
|
|
@@ -3717,6 +4046,14 @@ async function commitComponentsCommand(flags) {
|
|
|
3717
4046
|
}
|
|
3718
4047
|
|
|
3719
4048
|
console.log('Phase 2/3: server sync');
|
|
4049
|
+
if (flagEnabled(flags.safe)) {
|
|
4050
|
+
// The honest limit, stated where it applies. `components commit` runs git add/commit/push BEFORE the
|
|
4051
|
+
// server sync, so a --safe gate here cannot stop the push — it stops the PLATFORM from writing bad
|
|
4052
|
+
// overlays. The branch may already be pushed when the gate refuses; that is recoverable, an
|
|
4053
|
+
// unintended ComponentVariant tree is much less so.
|
|
4054
|
+
console.log(' --safe: the branch is already pushed. The gate below stops the platform from WRITING a');
|
|
4055
|
+
console.log(' surprising plan; it cannot un-push. Use `components sync --safe` alone for a pre-push gate.');
|
|
4056
|
+
}
|
|
3720
4057
|
const syncResponse = await syncComponentsCommand({
|
|
3721
4058
|
...flags,
|
|
3722
4059
|
branch: branchName,
|
|
@@ -9854,6 +10191,13 @@ function printComponentsHelp(subcommand) {
|
|
|
9854
10191
|
console.log('--summary prints compact counts, removals/tombstones, errors, skipped items, and warnings.');
|
|
9855
10192
|
console.log('');
|
|
9856
10193
|
console.log('Agent safety gates (each exits non-zero instead of printing a wall of JSON):');
|
|
10194
|
+
console.log(' --safe THE RECOMMENDED PATH on a variant branch. Expands to');
|
|
10195
|
+
console.log(' --summary --changed-only --fail-on-errors --fail-on-removed,');
|
|
10196
|
+
console.log(' resolves --changed-since from the merge base with trunk when you');
|
|
10197
|
+
console.log(' did not name one (local refs only, no implicit fetch), and prints');
|
|
10198
|
+
console.log(' the planned writes before mutating unless --yes is passed.');
|
|
10199
|
+
console.log(' On TRUNK there is no plan to gate, so it explains what a trunk');
|
|
10200
|
+
console.log(' reconcile does and requires --yes.');
|
|
9857
10201
|
console.log(' --changed-only dry-run first; fail unless every planned write is a component this checkout changed');
|
|
9858
10202
|
console.log(' --changed-since <ref> pair with --changed-only after committing: the changed set becomes');
|
|
9859
10203
|
console.log(' the components touched between <ref> and HEAD, plus uncommitted edits');
|
|
@@ -9865,14 +10209,29 @@ function printComponentsHelp(subcommand) {
|
|
|
9865
10209
|
return;
|
|
9866
10210
|
}
|
|
9867
10211
|
if (subcommand === 'stage' || subcommand === 'push') {
|
|
9868
|
-
console.log('Usage: remits-cli components stage [--base-url URL] [--account-id ID] [--branch BRANCH] [--workspace NAME] [--
|
|
10212
|
+
console.log('Usage: remits-cli components stage [--workset | --changed-only] [--base-url URL] [--account-id ID] [--branch BRANCH] [--workspace NAME] [--data-mode test|prod] [--json|--verbose]');
|
|
9869
10213
|
console.log('');
|
|
9870
10214
|
console.log('Stages local component files into the Redis staging cache. It never writes the database or git.');
|
|
9871
|
-
console.log('
|
|
10215
|
+
console.log('');
|
|
10216
|
+
console.log('Three stage modes, and the difference decides what a run in this lane resolves:');
|
|
10217
|
+
console.log(' (default) FULL SNAPSHOT — uploads the whole repo manifest and reconciles the lane');
|
|
10218
|
+
console.log(' to it. Correct as a complete snapshot and as a "what is stale?" reset;');
|
|
10219
|
+
console.log(' a poor progress signal, because the lane then holds every component.');
|
|
10220
|
+
console.log(' --workset WORKSET REPLACE — uploads only the components git reports changed and');
|
|
10221
|
+
console.log(' reconciles the lane to exactly them. THE NORMAL ITERATION MODE: the');
|
|
10222
|
+
console.log(' lane holds your work and nothing else, so the console shows what you');
|
|
10223
|
+
console.log(' are actually doing. An empty workset is refused, never a silent clear.');
|
|
10224
|
+
console.log(' --changed-only WORKSET MERGE — uploads only the changed components and leaves every');
|
|
10225
|
+
console.log(' other staged entry in place. Keeps its long-standing semantics, so it');
|
|
10226
|
+
console.log(' cannot shrink a lane inherited from an earlier full snapshot; the');
|
|
10227
|
+
console.log(' command warns when entries are retained that way.');
|
|
9872
10228
|
console.log('');
|
|
9873
10229
|
console.log(' --workspace NAME stage into an isolated lane on this branch (see `remits-cli workspace`).');
|
|
9874
|
-
console.log(' --
|
|
9875
|
-
console.log('
|
|
10230
|
+
console.log(' --empty-workset clear with --workset, let an empty workset clear the lane (default: refuse).');
|
|
10231
|
+
console.log(' --replace-lane with --changed-only, the explicit spelling of --workset.');
|
|
10232
|
+
console.log('');
|
|
10233
|
+
console.log('Output separates three different numbers: the WORKSET git reports, what was SUBMITTED, and');
|
|
10234
|
+
console.log('the materialized OVERLAY the lane now holds — which is the one a run resolves.');
|
|
9876
10235
|
return;
|
|
9877
10236
|
}
|
|
9878
10237
|
if (subcommand === 'status') {
|
|
@@ -9893,21 +10252,26 @@ function printComponentsHelp(subcommand) {
|
|
|
9893
10252
|
return;
|
|
9894
10253
|
}
|
|
9895
10254
|
if (subcommand === 'commit') {
|
|
9896
|
-
console.log('Usage: remits-cli components commit [--message "msg"] [--allow-empty true|false] [--skip-git true|false] [--branch BRANCH] [--data-mode test|prod] [--force-tombstones]');
|
|
10255
|
+
console.log('Usage: remits-cli components commit [--safe] [--message "msg"] [--allow-empty true|false] [--skip-git true|false] [--branch BRANCH] [--data-mode test|prod] [--force-tombstones]');
|
|
9897
10256
|
console.log('');
|
|
9898
10257
|
console.log('Runs local git add/commit/push, then server sync. Prefer explicit git + components sync when you need inspectable phases.');
|
|
9899
10258
|
console.log('components commit does not support --dry-run.');
|
|
10259
|
+
console.log('');
|
|
10260
|
+
console.log('--safe passes the sync gates through to phase 2. The honest limit: git push happens in phase 1,');
|
|
10261
|
+
console.log('so the gate stops the PLATFORM from writing a surprising plan — it cannot un-push the branch.');
|
|
10262
|
+
console.log('For a gate that runs before anything leaves your machine, push yourself and use');
|
|
10263
|
+
console.log('`remits-cli components sync --safe`.');
|
|
9900
10264
|
return;
|
|
9901
10265
|
}
|
|
9902
10266
|
console.log('Usage: remits-cli components <stage|status|clear|sync|commit|promotion|branches|branch>');
|
|
9903
10267
|
console.log('');
|
|
9904
10268
|
console.log('All staging subcommands accept --workspace NAME to isolate a lane on the same branch,');
|
|
9905
10269
|
console.log('so several agents can iterate at once. See: remits-cli workspace --help');
|
|
9906
|
-
console.log(' remits-cli components stage [--base-url URL] [--account-id ID] [--branch BRANCH] [--data-mode test|prod] [--json|--verbose]');
|
|
10270
|
+
console.log(' remits-cli components stage [--workset|--changed-only] [--base-url URL] [--account-id ID] [--branch BRANCH] [--data-mode test|prod] [--json|--verbose]');
|
|
9907
10271
|
console.log(' remits-cli components status [--base-url URL] [--account-id ID] [--branch BRANCH] [--component-type TYPE --component-id ID] [--json|--verbose]');
|
|
9908
10272
|
console.log(' remits-cli components clear [--base-url URL] [--account-id ID] [--branch BRANCH] [--component-type TYPE] [--component-id ID] [--all] [--json|--verbose]');
|
|
9909
|
-
console.log(' remits-cli components sync [--base-url URL] [--account-id ID] [--branch BRANCH] [--data-mode test|prod] [--force-tombstones] [--dry-run] [--summary]');
|
|
9910
|
-
console.log(' remits-cli components commit [--message "msg"] [--allow-empty true|false] [--skip-git true|false] [--branch BRANCH] [--data-mode test|prod] [--force-tombstones]');
|
|
10273
|
+
console.log(' remits-cli components sync [--safe] [--base-url URL] [--account-id ID] [--branch BRANCH] [--data-mode test|prod] [--force-tombstones] [--dry-run] [--summary]');
|
|
10274
|
+
console.log(' remits-cli components commit [--safe] [--message "msg"] [--allow-empty true|false] [--skip-git true|false] [--branch BRANCH] [--data-mode test|prod] [--force-tombstones]');
|
|
9911
10275
|
console.log('');
|
|
9912
10276
|
console.log(' --force-tombstones applies only on non-trunk variant syncs, when a missing trunk');
|
|
9913
10277
|
console.log(' component file is intentionally being recorded as a tombstone override.');
|
|
@@ -10057,10 +10421,10 @@ async function main() {
|
|
|
10057
10421
|
console.log(' remits-cli tool --name <toolName> [--base-url URL] [--branch BRANCH] [--input \"{...}\"|--input-file file.json] [--data-mode test|prod] [--scope self|children|hierarchy] [--target-account-id ID] [--account-ids 1,2,3] [--variant-branch NAME|none] [--timeout-ms 60000] [--async true --wait true]');
|
|
10058
10422
|
console.log(' remits-cli tool status --call-id <callId> [--base-url URL] [--account-id ID] [--data-mode test|prod]');
|
|
10059
10423
|
console.log(' remits-cli workspace [show|use <name>|use --auto|clear] # isolate staging when several agents share a repo');
|
|
10060
|
-
console.log(' remits-cli components stage [--base-url URL] [--account-id ID] [--branch BRANCH] [--workspace NAME] [--
|
|
10424
|
+
console.log(' remits-cli components stage [--workset|--changed-only] [--base-url URL] [--account-id ID] [--branch BRANCH] [--workspace NAME] [--data-mode test|prod] [--json|--verbose]');
|
|
10061
10425
|
console.log(' remits-cli components status [--base-url URL] [--account-id ID] [--branch BRANCH] [--workspace NAME] [--component-type TYPE --component-id ID] [--json|--verbose]');
|
|
10062
10426
|
console.log(' remits-cli components clear [--base-url URL] [--account-id ID] [--branch BRANCH] [--workspace NAME] [--component-type TYPE] [--component-id ID] [--all] [--json|--verbose]');
|
|
10063
|
-
console.log(' remits-cli components sync [--base-url URL] [--account-id ID] [--branch BRANCH] [--data-mode test|prod] [--force-tombstones] [--dry-run] [--summary]');
|
|
10427
|
+
console.log(' remits-cli components sync [--safe] [--base-url URL] [--account-id ID] [--branch BRANCH] [--data-mode test|prod] [--force-tombstones] [--dry-run] [--summary]');
|
|
10064
10428
|
console.log(' remits-cli components commit [--message \"msg\"] [--allow-empty true|false] [--skip-git true|false] [--branch BRANCH] [--data-mode test|prod] [--force-tombstones]');
|
|
10065
10429
|
console.log(' remits-cli components promotion [<branch>] [--json] [--no-fail] # promotion readiness + ordered next steps');
|
|
10066
10430
|
console.log(' remits-cli components branches [--json] # committed branch variants for this account');
|
package/package.json
CHANGED
|
@@ -60,6 +60,19 @@ reference named after it.
|
|
|
60
60
|
- **edit → stage → run, every time.** The platform executes whatever is in the staging cache at the
|
|
61
61
|
moment a run starts. Edit a file, run a test without `remits-cli components stage`, and the test runs
|
|
62
62
|
the OLD code. This is the single most common mistake. (`development-loop.md`)
|
|
63
|
+
- **Stage your WORKSET, not the whole repo: `remits-cli components stage --workset`.** It uploads only
|
|
64
|
+
the components git reports changed and makes the lane hold exactly them. A plain `components stage` is
|
|
65
|
+
a FULL SNAPSHOT — it puts every component in the repo into the lane, so "115 staged" tells a human
|
|
66
|
+
nothing about what you are working on, and every one of those entries shadows committed source until
|
|
67
|
+
it expires. Keep the full stage for a deliberate complete snapshot or a "what is stale here?" reset.
|
|
68
|
+
(`development-loop.md`)
|
|
69
|
+
- **Give each agent its own lane: `remits-cli workspace use --auto`.** Without a workspace you are in
|
|
70
|
+
the SHARED lane, where a full stage replaces what another agent is testing rather than merging with
|
|
71
|
+
it. (`component-resolution.md`)
|
|
72
|
+
- **On a variant branch, sync with `remits-cli components sync --safe`.** It dry-runs first and refuses
|
|
73
|
+
a plan that would write components this checkout did not change — which is what a branch that is
|
|
74
|
+
behind trunk produces, because it still physically carries old copies of files nobody touched.
|
|
75
|
+
(`component-integrity.md`, `branch-variants.md`)
|
|
63
76
|
- **`stage` is always safe. `components commit` on trunk is the most dangerous command in the CLI.**
|
|
64
77
|
It is not a convenience wrapper: it `git add -A`, commits, pushes, and then reconciles the whole
|
|
65
78
|
pushed repo into the live component database — creating, updating, renaming, and **hard-deleting**
|
|
@@ -84,6 +97,10 @@ reference named after it.
|
|
|
84
97
|
- **Resolution order is staged → variant → trunk, and each layers over the one beneath.** A populated
|
|
85
98
|
staging cache makes a committed variant look broken through any tokenized entry point; clear it before
|
|
86
99
|
verifying variant resolution. (`component-resolution.md`)
|
|
100
|
+
- **A lane's staged count is the OVERLAY, not your workset.** The overlay is every staged entry the lane
|
|
101
|
+
holds — what a run resolves. The workset is what git says you changed. A full stage makes them differ
|
|
102
|
+
by the size of the repo, and `--changed-only` merges, so it can never shrink an overlay it inherited.
|
|
103
|
+
`components status` prints all three numbers; so does the console. (`component-resolution.md`)
|
|
87
104
|
- **Host and data mode are two independent decisions.** `--base-url` picks the Remits host,
|
|
88
105
|
`--data-mode` picks the data segment on it. Neither implies the other. (`command-reference.md`)
|
|
89
106
|
- **`test run` ignores the stored session data lane.** It defaults to `test` even when `whoami` shows
|
|
@@ -139,7 +156,8 @@ reference named after it.
|
|
|
139
156
|
|
|
140
157
|
```bash
|
|
141
158
|
remits-cli whoami # account, user, branch, data mode, host for the NEXT tool call
|
|
142
|
-
remits-cli
|
|
159
|
+
remits-cli workspace use --auto # your own staging lane, named after this checkout
|
|
160
|
+
remits-cli components status # trunk or variant checkout, staging lane, workset vs overlay, who else is staging
|
|
143
161
|
remits-cli tools # which tools this account actually has (tools are per-account)
|
|
144
162
|
```
|
|
145
163
|
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
- [Hierarchy-scoped tool reads](#hierarchy-scoped-tool-reads)
|
|
16
16
|
- [Data Mode](#data-mode)
|
|
17
17
|
- [Command Reference](#command-reference)
|
|
18
|
+
- [Staging modes: workset vs full snapshot](#staging-modes-workset-vs-full-snapshot)
|
|
18
19
|
- [Prod banners and retryable failures](#prod-banners-and-retryable-failures)
|
|
19
20
|
|
|
20
21
|
## Getting Started
|
|
@@ -189,12 +190,12 @@ remits-cli status [--base-url URL] [--account-id ID] [--data-mode test|prod] [--
|
|
|
189
190
|
remits-cli whoami [--base-url URL] [--account-id ID] [--data-mode test|prod] [--json]
|
|
190
191
|
remits-cli listen [stop|status] [--foreground true] # compatibility alias
|
|
191
192
|
remits-cli data-mode [set test|prod]
|
|
192
|
-
remits-cli components stage [--branch <name>] [--workspace <name>] [--
|
|
193
|
+
remits-cli components stage [--workset | --changed-only] [--branch <name>] [--workspace <name>] [--empty-workset clear] [--data-mode test|prod] [--json|--verbose] # default = FULL SNAPSHOT of the repo; --workset = only what git says changed, lane reconciled to it
|
|
193
194
|
remits-cli workspace [show | use <name> | use --auto | clear]
|
|
194
195
|
remits-cli components status [--branch <name>] [--component-type <type>] [--component-id <id>] [--json|--verbose]
|
|
195
196
|
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
|
|
196
|
-
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
|
|
197
|
-
remits-cli components commit [--message "msg"] [--data-mode test|prod] [--force-tombstones]
|
|
197
|
+
remits-cli components sync [--safe [--yes]] [--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
|
|
198
|
+
remits-cli components commit [--safe] [--message "msg"] [--data-mode test|prod] [--force-tombstones] # --safe gates phase 2; it cannot un-push phase 1
|
|
198
199
|
remits-cli components branches [--json] # branches carrying committed variants, with counts + drift
|
|
199
200
|
remits-cli components branch <name> [--json] # one branch: overridden / added / removed, drift flags, subscribers
|
|
200
201
|
remits-cli components branch <name> --diff <componentId> --component-type <kind> [--json]
|
|
@@ -251,8 +252,50 @@ For tests specifically:
|
|
|
251
252
|
- `--names-only` — dry-run and print only `BUCKET type:id name` lines for the planned writes, then stop
|
|
252
253
|
without writing overlays.
|
|
253
254
|
|
|
255
|
+
- `--safe` — the NAME for that combination, and the recommended agent path on a variant branch. It
|
|
256
|
+
expands to `--summary --changed-only --fail-on-errors --fail-on-removed`, resolves `--changed-since`
|
|
257
|
+
from this branch's merge base with trunk when you did not name one (local refs only — it never runs
|
|
258
|
+
an implicit `git fetch`, and refuses with the fetch command when nothing local can answer), and
|
|
259
|
+
prints the planned writes before mutating unless `--yes` is passed. On TRUNK there is no plan to
|
|
260
|
+
gate, so it states what a trunk reconcile does (every row rewritten from the repo; any live component
|
|
261
|
+
missing from the repo DELETED) and requires `--yes`. It does not override a narrower
|
|
262
|
+
`--expected-removed`.
|
|
263
|
+
|
|
254
264
|
A good default for an unattended promotion is:
|
|
255
|
-
`remits-cli components sync --
|
|
265
|
+
`remits-cli components sync --safe`
|
|
266
|
+
|
|
267
|
+
When `--changed-only` refuses a plan far larger than your changed set, the usual cause is a branch that
|
|
268
|
+
is BEHIND trunk: it still physically carries old copies of files nobody on it touched, and a variant
|
|
269
|
+
sync turns each of those into an unrelated override. The refusal says so. Merge trunk in, push, re-run.
|
|
270
|
+
|
|
271
|
+
### Staging modes: workset vs full snapshot
|
|
272
|
+
|
|
273
|
+
`components stage` reports three numbers, and they answer three different questions:
|
|
274
|
+
|
|
275
|
+
| Number | Question |
|
|
276
|
+
|---|---|
|
|
277
|
+
| **workset** | how many components git reports this working tree changed |
|
|
278
|
+
| **submitted** | how many this command uploaded |
|
|
279
|
+
| **overlay** | how many staged entries the lane now holds — **what a run resolves** |
|
|
280
|
+
|
|
281
|
+
| Mode | Uploads | Lane afterwards |
|
|
282
|
+
|---|---|---|
|
|
283
|
+
| `components stage` (default) | the whole repository manifest | reconciled to the whole repo — a FULL SNAPSHOT |
|
|
284
|
+
| `components stage --workset` | only the git-changed components | reconciled to exactly those |
|
|
285
|
+
| `components stage --changed-only` | only the git-changed components | merged; earlier entries are left in place |
|
|
286
|
+
|
|
287
|
+
`--workset` is the iteration mode. `--changed-only` keeps its long-standing merge semantics, so it cannot
|
|
288
|
+
shrink a lane inherited from an earlier full stage; the command warns when it retains entries that way.
|
|
289
|
+
`--changed-only --replace-lane` is the explicit spelling of `--workset`.
|
|
290
|
+
|
|
291
|
+
An empty workset never clears a lane: `--workset` on a clean tree stages nothing and leaves the lane as
|
|
292
|
+
it is. `--empty-workset clear` opts into the clear; `components clear --all` is the direct way.
|
|
293
|
+
|
|
294
|
+
A deleted component file cannot be represented in Redis staging — clearing a staged entry falls back to
|
|
295
|
+
the committed row, so the component still resolves. The command reports those changes as NOT
|
|
296
|
+
REPRESENTABLE. On a non-trunk variant branch, prove a deletion through
|
|
297
|
+
`components sync --dry-run --summary --fail-on-errors`; on trunk there is no dry-run plan, so use the
|
|
298
|
+
full pre-sync safety check before any mutating reconcile.
|
|
256
299
|
|
|
257
300
|
### Prod banners and retryable failures
|
|
258
301
|
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
- [When staged overrides apply](#when-staged-overrides-apply)
|
|
16
16
|
- [Diagnosing which version is in play](#diagnosing-which-version-is-in-play)
|
|
17
17
|
- [Working alongside other agents: the staging WORKSPACE](#working-alongside-other-agents-the-staging-workspace)
|
|
18
|
+
- [A lane holds an OVERLAY; your workset is a different number](#a-lane-holds-an-overlay-your-workset-is-a-different-number)
|
|
18
19
|
- [Stage / sync / clear with remits-cli](#stage--sync--clear-with-remits-cli)
|
|
19
20
|
- [Stale after sync / commit (the in-memory compile cache)](#stale-after-sync--commit-the-in-memory-compile-cache)
|
|
20
21
|
|
|
@@ -184,19 +185,60 @@ commit write `ComponentVariant` overlays for a branch nobody subscribes to).
|
|
|
184
185
|
- Every stage / test run / token / clear prints its `Staging lane:` — if a change seems to have had no
|
|
185
186
|
effect, check that line FIRST. A mismatched lane resolves committed source, which looks identical to
|
|
186
187
|
"the stage did not work".
|
|
187
|
-
- `remits-cli components status` lists every lane staged on the branch
|
|
188
|
-
agent is working alongside you.
|
|
188
|
+
- `remits-cli components status` lists every lane staged on the branch AND every lane on the account, so
|
|
189
|
+
you can see whether another agent is working alongside you. Each row names its world (trunk or variant
|
|
190
|
+
branch), its overlay, its workset where known, and whether it is the SHARED lane.
|
|
189
191
|
- `remits-cli components clear --all` is scoped to YOUR lane and never touches another agent's.
|
|
190
192
|
|
|
193
|
+
### A lane holds an OVERLAY; your workset is a different number
|
|
194
|
+
|
|
195
|
+
This is the distinction that decides whether a lane is legible to anyone but you.
|
|
196
|
+
|
|
197
|
+
- The **overlay** is every staged entry the lane currently holds. It is what a CLI-scoped run resolves,
|
|
198
|
+
and it is the number the console shows as "staged".
|
|
199
|
+
- The **workset** is what git reports this working tree changed. It is the work in flight.
|
|
200
|
+
|
|
201
|
+
A plain `components stage` is a FULL SNAPSHOT: it uploads the whole repository manifest and reconciles
|
|
202
|
+
the lane to it, so on a 115-component repo the overlay is 115 whether you edited five components or all
|
|
203
|
+
of them. That is safe — it is a complete, known state — but it is a poor signal. Everyone reading the
|
|
204
|
+
console sees a lane that looks like 115 edits in flight, and all 115 entries shadow committed source for
|
|
205
|
+
every run in that lane until they expire.
|
|
206
|
+
|
|
207
|
+
`components stage --workset` uploads only the changed components and reconciles the lane to exactly
|
|
208
|
+
them, so the overlay IS the workset. That is the mode to iterate in.
|
|
209
|
+
|
|
210
|
+
`components stage --changed-only` uploads the same narrow set but MERGES: it deliberately leaves every
|
|
211
|
+
other staged entry alone. So it can never shrink a lane inherited from an earlier full snapshot — the
|
|
212
|
+
overlay stays at 115 while you work on seven. The command warns when entries are retained that way.
|
|
213
|
+
|
|
214
|
+
Every count is `unknown` rather than `0` when it cannot be established. "git could not answer" and "git
|
|
215
|
+
says nothing changed" are different facts and only one of them is a number.
|
|
216
|
+
|
|
217
|
+
**Deletion is not expressible here.** There is no staged removal: clearing a staged entry falls back to
|
|
218
|
+
the committed row, so the component still resolves. `stage --workset` reports a deleted component file as
|
|
219
|
+
NOT REPRESENTABLE rather than quietly omitting it. On a non-trunk variant branch, prove a deletion
|
|
220
|
+
through the durable variant plan — `remits-cli components sync --dry-run --summary --fail-on-errors` —
|
|
221
|
+
and read the removed/tombstone bucket. On trunk there is no dry-run plan; a deletion is only proven by
|
|
222
|
+
the full pre-sync safety check before a mutating reconcile.
|
|
223
|
+
|
|
224
|
+
**An empty workset never clears the lane.** `--workset` on a clean tree stages nothing and leaves the
|
|
225
|
+
lane as it is; reconciling to an empty manifest would delete the overlay the next run depends on.
|
|
226
|
+
Clearing stays explicit (`components clear --all`), or `--empty-workset clear` if that really is what you
|
|
227
|
+
meant.
|
|
228
|
+
|
|
191
229
|
### Stage / sync / clear with remits-cli
|
|
192
230
|
|
|
193
|
-
- `remits-cli components stage`
|
|
194
|
-
|
|
195
|
-
- `remits-cli components stage
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
- `remits-cli components
|
|
199
|
-
|
|
231
|
+
- `remits-cli components stage --workset` stages exactly what git says this working tree changed and
|
|
232
|
+
reconciles the lane to it. **The normal iteration mode.**
|
|
233
|
+
- `remits-cli components stage` stages the whole repository manifest (a full snapshot). Use it for a
|
|
234
|
+
deliberate complete snapshot, when a `.meta.yml` key you deleted must be reconciled against the whole
|
|
235
|
+
repo, or as a "what is stale in here?" reset — then clear when you are done.
|
|
236
|
+
- `remits-cli components stage --changed-only` stages just the changed components and does NOT reconcile,
|
|
237
|
+
so entries it did not mention are left alone rather than deleted. Kept as-is for compatibility;
|
|
238
|
+
`--workset` is the same narrow upload with the lane reconciled.
|
|
239
|
+
- `remits-cli components status` shows which branch/variant world the checkout resolves, whether the lane
|
|
240
|
+
is shared, the overlay/workset/retained split with the last stage's mode, plus staged entries, staged
|
|
241
|
+
fields, aliases, hashes, and TTLs. Use `--json` or `--verbose` for the full staged-entry payload.
|
|
200
242
|
- **A full `stage` makes the `.meta.yml` AUTHORITATIVE.** Staging layers a payload over what is already
|
|
201
243
|
staged, which is what lets `mcp_component_edit` write a single field without blanking the others. But a
|
|
202
244
|
`remits-cli components stage` sends the whole sidecar, so a key you DELETE from a sidecar is removed from
|
|
@@ -16,11 +16,14 @@
|
|
|
16
16
|
- [Step 1: Understand the Request](#step-1-understand-the-request)
|
|
17
17
|
- [Step 2: Make the Change](#step-2-make-the-change)
|
|
18
18
|
- [Step 3: Stage to Platform](#step-3-stage-to-platform)
|
|
19
|
+
- [Stage your workset, not the whole repo](#stage-your-workset-not-the-whole-repo)
|
|
20
|
+
- [Three numbers, three questions](#three-numbers-three-questions)
|
|
19
21
|
- [Step 4: Verify the Change](#step-4-verify-the-change)
|
|
20
22
|
- [Step 5: Iterate If Needed](#step-5-iterate-if-needed)
|
|
21
23
|
- [Step 6: Update Documentation](#step-6-update-documentation)
|
|
22
24
|
- [Temporary Experiment Workflow](#temporary-experiment-workflow)
|
|
23
25
|
- [Step 7: Commit and Durable Sync](#step-7-commit-and-durable-sync)
|
|
26
|
+
- [Verifying the COMMITTED variant, not your staging](#verifying-the-committed-variant-not-your-staging)
|
|
24
27
|
- [Step 8: Close the Ticket](#step-8-close-the-ticket)
|
|
25
28
|
- [User Confirmation Preferences](#user-confirmation-preferences)
|
|
26
29
|
|
|
@@ -174,10 +177,63 @@ overlay instead of pruning cleanly.
|
|
|
174
177
|
#### Step 3: Stage to Platform
|
|
175
178
|
|
|
176
179
|
```bash
|
|
177
|
-
remits-cli
|
|
180
|
+
remits-cli workspace use --auto # once per checkout: your own lane
|
|
181
|
+
remits-cli components stage --workset # every edit: stage what you changed
|
|
178
182
|
```
|
|
179
183
|
|
|
180
|
-
This uploads your local
|
|
184
|
+
This uploads your local component changes to the platform's staging cache (Redis, 240-minute TTL). It does NOT commit anything. The platform cannot see your local edits until you stage them.
|
|
185
|
+
|
|
186
|
+
##### Stage your workset, not the whole repo
|
|
187
|
+
|
|
188
|
+
There are three stage modes, and the difference decides what a run in your lane resolves and what a
|
|
189
|
+
human watching the console sees:
|
|
190
|
+
|
|
191
|
+
| Command | What it uploads | What the lane holds afterwards |
|
|
192
|
+
|---|---|---|
|
|
193
|
+
| `components stage --workset` | only the components git reports changed | **exactly those** — the lane is reconciled to your workset |
|
|
194
|
+
| `components stage` | the whole repository manifest | **every component in the repo** (a full snapshot) |
|
|
195
|
+
| `components stage --changed-only` | only the changed components | the changed ones **merged into whatever was already there** |
|
|
196
|
+
|
|
197
|
+
**Use `--workset` for normal iteration.** A full stage is correct and safe, but on a real repository it
|
|
198
|
+
puts a hundred-plus components into your lane, and every one of them then shadows committed source for
|
|
199
|
+
any run in that lane until it expires. A human looking at `/admin/platforms` sees "115 staged" and cannot
|
|
200
|
+
tell whether you edited 115 components or five.
|
|
201
|
+
|
|
202
|
+
`--changed-only` MERGES. It cannot shrink a lane it inherited from an earlier full stage, so a lane can
|
|
203
|
+
sit at 115 overlay entries while you are working on seven. The command warns when that happens; the fix
|
|
204
|
+
is `--workset` (or `components clear --all` once, then keep using `--workset`).
|
|
205
|
+
|
|
206
|
+
**When a full stage is the right answer:**
|
|
207
|
+
|
|
208
|
+
- you deliberately want a complete snapshot of the repo in the lane;
|
|
209
|
+
- you changed a `.meta.yml` sidecar and want removed keys reconciled against the whole repo;
|
|
210
|
+
- you cannot tell what is stale in the lane and want a clean, known state (then `components clear --all`
|
|
211
|
+
when you are done).
|
|
212
|
+
|
|
213
|
+
**An empty workset never clears your lane.** `--workset` on a clean working tree stages nothing and
|
|
214
|
+
leaves the lane alone — reconciling to an empty manifest would delete the overlay your next test run
|
|
215
|
+
depends on. Clearing stays explicit: `components clear --all`.
|
|
216
|
+
|
|
217
|
+
**Deleting a component file cannot be verified by staging.** There is no staged "removal": clearing a
|
|
218
|
+
staged entry falls back to the committed row, so the component still resolves. `stage --workset` reports
|
|
219
|
+
those changes as NOT REPRESENTABLE.
|
|
220
|
+
|
|
221
|
+
On a non-trunk variant branch, prove the removal through the durable variant plan:
|
|
222
|
+
`remits-cli components sync --dry-run --summary --fail-on-errors` and read the removed/tombstone bucket.
|
|
223
|
+
On trunk there is no dry-run plan; treat deletion as a high-risk durable reconcile and pass the full
|
|
224
|
+
pre-sync safety check before running any mutating sync.
|
|
225
|
+
|
|
226
|
+
##### Three numbers, three questions
|
|
227
|
+
|
|
228
|
+
`components stage` and `components status` report all three, and so does the admin console. They are not
|
|
229
|
+
interchangeable:
|
|
230
|
+
|
|
231
|
+
- **workset** — components git reports this working tree changed. The work in flight.
|
|
232
|
+
- **submitted** — what this command uploaded.
|
|
233
|
+
- **overlay** — every staged entry the lane now holds. **This is what a run resolves.**
|
|
234
|
+
|
|
235
|
+
A missing answer is printed as `unknown`, never as `0`: "git could not answer" and "git says nothing
|
|
236
|
+
changed" are different facts and only one of them is a number.
|
|
181
237
|
|
|
182
238
|
**THE STAGE-BEFORE-RUN RULE:** You MUST run `remits-cli components stage` after EVERY file edit and BEFORE any test run or verification. The platform executes whatever version is in the staging cache at the moment the test starts. If you edit a file and run a test without staging first, the test runs the OLD code — not your changes. This is the single most common mistake. Never skip staging. The sequence is always: **edit → stage → run**.
|
|
183
239
|
|
|
@@ -310,13 +366,16 @@ Redis cache can affect later test/tool runs, so always clear it after restoring
|
|
|
310
366
|
|
|
311
367
|
```bash
|
|
312
368
|
# make temporary local edit
|
|
313
|
-
remits-cli components stage
|
|
369
|
+
remits-cli components stage --workset
|
|
314
370
|
remits-cli test run --test <id-or-name> --names "<case name>"
|
|
315
371
|
git restore <file>
|
|
316
372
|
remits-cli components clear --all
|
|
317
373
|
remits-cli components status
|
|
318
374
|
```
|
|
319
375
|
|
|
376
|
+
With `--workset` the restore-and-clear is belt and braces rather than the only thing standing between
|
|
377
|
+
the experiment and a later run: the lane only ever held the component you were experimenting on.
|
|
378
|
+
|
|
320
379
|
For narrower cleanup when only one staged component should be cleared:
|
|
321
380
|
|
|
322
381
|
```bash
|
|
@@ -352,10 +411,41 @@ IDs or when unexpected deletes/renumbers are present.
|
|
|
352
411
|
Only after those checks pass, and only when the user intends to promote the repo to the platform database:
|
|
353
412
|
|
|
354
413
|
```bash
|
|
414
|
+
# On a VARIANT branch — the recommended path. Dry-runs first and refuses a surprising plan.
|
|
415
|
+
remits-cli components sync --safe
|
|
416
|
+
git pull --ff-only origin <branch>
|
|
417
|
+
|
|
418
|
+
# On TRUNK — there is no plan to gate, so --safe explains what a trunk reconcile does and needs --yes.
|
|
355
419
|
remits-cli components sync
|
|
356
420
|
git pull --ff-only origin <branch>
|
|
357
421
|
```
|
|
358
422
|
|
|
423
|
+
`--safe` expands to `--summary --changed-only --fail-on-errors --fail-on-removed`, resolves the
|
|
424
|
+
comparison base from this branch's merge base with trunk when you did not pass `--changed-since`, and
|
|
425
|
+
prints the planned writes before mutating unless you pass `--yes`. It refuses when the plan would write
|
|
426
|
+
components this checkout did not change — which is exactly what a branch that is BEHIND trunk produces,
|
|
427
|
+
because it still physically carries old copies of files nobody on it touched, and a variant sync turns
|
|
428
|
+
each of those into an unrelated override. When it refuses that way, merge trunk into your branch, push,
|
|
429
|
+
and re-run the same command.
|
|
430
|
+
|
|
431
|
+
When a removal is intended, name it rather than disabling the gate:
|
|
432
|
+
|
|
433
|
+
```bash
|
|
434
|
+
remits-cli components sync --safe --expected-removed action:50
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
`--force-tombstones` stays explicit and human-owned. Never pass it to get past a refusal.
|
|
438
|
+
|
|
439
|
+
##### Verifying the COMMITTED variant, not your staging
|
|
440
|
+
|
|
441
|
+
After a sync, staged entries still win for CLI-scoped runs, so a test that passes may be testing your
|
|
442
|
+
staging rather than what you just committed. Clear the lane first:
|
|
443
|
+
|
|
444
|
+
```bash
|
|
445
|
+
remits-cli components clear --all
|
|
446
|
+
remits-cli test run --test <id-or-name> --as-account <subscriber-id>
|
|
447
|
+
```
|
|
448
|
+
|
|
359
449
|
`remits-cli components sync` is the authoritative platform-sync step. It does not perform local git operations,
|
|
360
450
|
and it is capable of reconciling creates/deletes/renames from the remote repository into the database. Treat it
|
|
361
451
|
as a gated promote/reconciliation command, not as an exploratory command or fallback.
|