instar 1.3.556 → 1.3.558

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.
@@ -0,0 +1,75 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ - **The 5 `multiMachine.seamlessness` coherence flags** — `ws3OneVoice` (one-voice
9
+ election), `ws13Reconcile` (ownership reconcile), `ws41DurableAck` (durable cross-machine
10
+ acknowledgement), `ws43RoleGuard` (role-guard-at-spawn), `ws43JournalLease` (journal-lease
11
+ cutover) — **moved from a hardcoded `false` in `ConfigDefaults` to the developmentAgent
12
+ gate (live-on-dev / dark-fleet)**, mirroring the already-merged `ws44PoolLinks` /
13
+ `ws44PoolCache` precedent.
14
+ - **Driver — operator directive (topic 13481, 2026-06-13):** "Nothing should ship dark for
15
+ development agents. Everything fully functional so it actually gets tested and doesn't
16
+ just rot." This is PR2 of that directive; PR1 (#1151) moved the 7 cross-machine memory
17
+ stores.
18
+ - **How it resolves now:** each flag's `ConfigDefaults` block OMITS the literal so
19
+ `resolveDevAgentGate` decides — LIVE on a development agent, DARK on the fleet. Every
20
+ consumer read-site (the SpeakerElection, the OwnershipReconciler, the durable remote-ack
21
+ routes, the scheduler role-guard, the journal-lease cutover gate, and the heartbeat
22
+ capability advert) was routed through that one gate, and `ws43JournalLeaseDryRun` is
23
+ computed COHERENTLY with its gate (a dev agent runs the cutover genuinely live, the fleet
24
+ stays in the safe dry-run posture).
25
+ - **The session-pool master switch is deliberately HELD.** `multiMachine.sessionPool`
26
+ (the active-active master plus its inbound-queue and hold-for-stability sub-flags) was NOT
27
+ moved. It carries a second, structurally-enforced rollout gate (`sessionPool.stage`,
28
+ advanced only through an end-to-end-test-gated ladder); flipping only the dev gate would
29
+ leave the pool inert, and forcing the stage would bypass that deliberate cutover
30
+ discipline. It is surfaced for an operator decision instead (advance the stage on the dev
31
+ mesh if active-active pooling should be dogfooded live).
32
+ - **Fleet + single-machine agents are an unchanged no-op** — every layer resolves dark
33
+ exactly as before, and each is a strict no-op without a second online machine. An
34
+ operator's explicit per-flag setting remains the documented force-dark (false) /
35
+ fleet-flip (true) override.
36
+
37
+ ## What to Tell Your User
38
+
39
+ Nothing changes for fleet agents — these multi-machine coherence features stay off for
40
+ everyone except development agents. On a development agent that runs across two of your
41
+ machines, the coordination layers that keep the two machines behaving as one agent (one
42
+ voice per conversation, no double-replies, durable acknowledgements that survive a briefly
43
+ offline machine, and a guard that keeps state-writing jobs on the right machine) now run
44
+ live, so they finally get real-world testing before reaching everyone. There is no
45
+ user-facing action and no behavior change on a normal install or any single-machine agent.
46
+ The active-active session pool itself stays off pending a separate operator decision, because
47
+ it has its own staged rollout that should advance deliberately.
48
+
49
+ ## Summary of New Capabilities
50
+
51
+ - The five cross-machine coherence layers — single outbound voice, ownership reconcile,
52
+ durable cross-machine acknowledgement, the spawn-time role-guard, and the job journal-lease
53
+ cutover — now run LIVE on development agents (dark on the fleet) so they are actually
54
+ exercised across the operator's two machines instead of shipping dark everywhere.
55
+
56
+ ## Evidence
57
+
58
+ - `tests/unit/seamlessness-dev-gate-wiring.test.ts` (13): each flag resolves live-on-dev /
59
+ dark-on-fleet through the gate; the dryRun coherence holds; a no-missed-consumer
60
+ source-string check fails CI if any consumer reverts to the raw read; the registry
61
+ coherence check confirms the 5 flags are dev-gated and the 3 session-pool flags stay held.
62
+ - `tests/unit/PostUpdateMigrator-seamlessnessDevGate.test.ts` (9): the migrator strips the
63
+ default-shaped values, leaves operator-touched config alone, and is idempotent.
64
+ - `tests/e2e/attention-remote-ack-alive.test.ts`: two new dev-gate cases prove the durable
65
+ remote-ack route goes ALIVE on a dev agent with the flag omitted and stays a 503 on the
66
+ fleet — the gate flips the real route, not just a registry entry.
67
+ - `tests/unit/lint-dev-agent-dark-gate.test.ts`: the EXPECTED attribution map line numbers
68
+ were recomputed; the 3 session-pool flags remain as held hardcoded defaults.
69
+
70
+ ## Migration
71
+
72
+ - `PostUpdateMigrator.migrateConfigSeamlessnessDevGate` strips a default-shaped `false` per
73
+ flag on update (and the paired `ws43JournalLeaseDryRun:true` only alongside a
74
+ default-shaped `ws43JournalLease:false`) so the gate resolves on already-deployed dev
75
+ agents. An operator-set value is left entirely alone — reach is not authority. Idempotent.
@@ -0,0 +1,115 @@
1
+ # Side-Effects Review — Age-Kill Transcript-Awareness
2
+
3
+ **Version / slug:** `age-kill-transcript-aware-resume`
4
+ **Date:** `2026-06-14`
5
+ **Author:** `echo`
6
+ **Second-pass reviewer:** spec-convergence panel (6 internal reviewers + Standards-Conformance gate; external non-Claude pass degraded — see convergence report)
7
+
8
+ ## Summary of the change
9
+
10
+ The session age-limit reaper (`SessionManager.monitorTick`, the `elapsed > limit` block) decides
11
+ whether an over-age session is "truly idle" and therefore age-kill-eligible. Before this change
12
+ that decision used only two signals — the tmux pane shows an idle prompt, and there is no
13
+ non-baseline child process — both BLIND to MCP/tool work (the Playwright MCP server runs out of
14
+ the pane's process tree; bash tool calls are short-lived). On 2026-06-13 an actively-working
15
+ session was terminal-killed because, between tool calls, it looked idle. This change adds a third
16
+ signal: `isTranscriptRecentlyActive(session, 120_000)` — true when the session's framework
17
+ transcript (JSONL) was modified within the last 2 minutes. The idle decision is extracted into a
18
+ pure, exported `isAgeGateTrulyIdle(idleAtPrompt, hasActiveProcs, transcriptActive)` so the exact
19
+ incident is reproducible at the decision boundary in a unit test. Files: `src/core/SessionManager.ts`
20
+ (constant `AGE_GATE_TRANSCRIPT_ACTIVE_MS`, the `isAgeGateTrulyIdle` function, the
21
+ `isTranscriptRecentlyActive` method, the import of `resolveFrameworkTranscriptPath`, and the
22
+ age-gate wiring); tests in `tests/unit/session-timeout-activity-aware.test.ts` and
23
+ `tests/unit/session-manager-behavioral.test.ts`.
24
+
25
+ ## Decision-point inventory
26
+
27
+ - `SessionManager.monitorTick age-gate (ageGateTrulyIdle)` — **modify** — adds a transcript-activity
28
+ defer condition; the kill now requires idle-pane AND no-child-proc AND transcript-not-recently-active.
29
+
30
+ ---
31
+
32
+ ## 1. Over-block
33
+
34
+ **What legitimate inputs does this change reject that it shouldn't?**
35
+
36
+ In reap terms, "over-block" = wrongly DEFERRING a kill. A genuinely-finished session whose
37
+ transcript was touched <2 min before the check is held off for up to one 2-minute window. This is
38
+ harmless: the idle-detection block immediately below the age gate still reaps it once its
39
+ transcript goes quiet. No session is kept alive indefinitely on a stale signal — only real,
40
+ ongoing transcript writes (which require real model activity) sustain the defer.
41
+
42
+ ## 2. Under-block
43
+
44
+ **What failure modes does this still miss?**
45
+
46
+ The change ONLY adds a defer condition; it removes no kill. A session with NO resolvable transcript
47
+ (an unknown framework, or pi-cli which has no resolver case) gets `isTranscriptRecentlyActive ===
48
+ false` and the age gate falls back to EXACTLY today's pane/procs verdict — so such a session is no
49
+ better protected than before (safe degradation, no NEW under-block). The deliberate non-coverage:
50
+ the age gate does not try to be a hard resource ceiling for runaway-but-active sessions — that is
51
+ owned by SessionWatchdog (stuck-process), the context-wedge sentinel, and the CPU/memory-pressure
52
+ reaper, all unchanged.
53
+
54
+ ## 3. Level-of-abstraction fit
55
+
56
+ **Is this at the right layer?**
57
+
58
+ Yes. The SessionReaper (the pressure/idle reap path) ALREADY layers transcript-growth awareness
59
+ (`probeTranscript`/`transcriptDelta`). This change brings the parallel age-kill path to parity at
60
+ the SAME layer using the SAME core resolver (`resolveFrameworkTranscriptPath` from
61
+ `core/FrameworkSessionStore`). It is a cheap detector feeding an existing defer, not a new
62
+ authority and not a re-implementation of an existing primitive. It deliberately does NOT depend on
63
+ `monitoring/transcriptProber` (that would invert core→monitoring layering); it uses the core
64
+ resolver directly.
65
+
66
+ ## 4. Signal vs authority compliance
67
+
68
+ **Does this hold blocking authority with brittle logic, or produce a signal that feeds a smart gate?**
69
+
70
+ It produces a SIGNAL that ADDS a defer (keep) condition to an existing authority. It grants no new
71
+ blocking authority and moves strictly in the safe direction (fewer kills of active sessions). The
72
+ probe is brittle (a single statSync) but its failure mode is bounded by the fail-to-`false`
73
+ contract: a probe error never keeps a session alive — it only declines to add the new defer
74
+ reason, falling through to the existing pane/procs verdict. (Ref: `docs/signal-vs-authority.md`.)
75
+
76
+ ## 5. Interactions
77
+
78
+ **Does it shadow another check, get shadowed, double-fire, or race with adjacent cleanup?**
79
+
80
+ Consistent with the SessionReaper's existing transcript gate (same resolver, same intent). The
81
+ single-point mtime check vs the SessionReaper's cross-tick delta is an intentional, benign
82
+ asymmetry (the SessionReaper requires repeated positive-idle confirmations before it reaps, so the
83
+ two never produce a contradictory kill on the same tick). The idle-detection block below the age
84
+ gate still owns genuinely-idle reaping (the held off path falls through, no `continue`). No new
85
+ kill/revive loop: the change does NOT touch `WorkEvidence`/`ReapGuard.evaluate()`/`workEvidence()`,
86
+ so the documented evaluate-vs-workEvidence disagreement class (2026-06-13 13-session loop) is
87
+ untouched. Adversarial review confirmed loop-safety. The `overAgeButActiveLogged` set already
88
+ prevents log spam on the held off path.
89
+
90
+ ## 6. External surfaces
91
+
92
+ **Does it change anything visible to other agents/users/systems? Timing/state dependence?**
93
+
94
+ No API route, no config key, no hook, no template, no dashboard surface. The only externally
95
+ observable effect: a long-running, actively-working session is no longer terminal-killed at ~5h —
96
+ which is the intended, operator-requested behavior. The new constant is code-level (not a config
97
+ surface). The change reads only a local file's mtime; it depends on the framework runtime writing
98
+ its transcript on activity (true for claude-code, codex-cli, gemini-cli; pi-cli safely degrades).
99
+
100
+ ## 7. Multi-machine posture (Cross-Machine Coherence)
101
+
102
+ **Machine-local BY DESIGN.** Each machine's `SessionManager` reaps only its OWN sessions and reads
103
+ only its OWN local transcripts. There is no shared/replicated/proxied state: the decision is a
104
+ per-session, per-machine read of a local file. No cross-machine surface is introduced or needed —
105
+ a reap decision is inherently local to the machine running the session. No user-facing notice is
106
+ added (so no one-voice gating concern); no durable cross-machine state (so no topic-transfer
107
+ strand); no generated URL.
108
+
109
+ ## 8. Rollback cost
110
+
111
+ Revert the PR — a single-file behavior change plus its tests; no data migration, no agent-state
112
+ repair, no config rollback. As an interim dial without a revert, setting
113
+ `AGE_GATE_TRANSCRIPT_ACTIVE_MS` to 0 makes the new defer condition inert (reverts to the prior
114
+ two-signal behavior). Ships with the dist on update (no PostUpdateMigrator entry needed — the
115
+ change is internal runtime behavior, not an agent-installed file).
@@ -0,0 +1,149 @@
1
+ # Side-Effects — multiMachine.seamlessness coherence flags → dev-gated (topic 13481), sessionPool master HELD
2
+
3
+ **Change (PR2 of the no-dark-on-dev multi-machine directive):** Re-gate the 5
4
+ `multiMachine.seamlessness` coherence flags — `ws3OneVoice` (one-voice election),
5
+ `ws13Reconcile` (ownership reconcile), `ws41DurableAck` (durable cross-machine /ack),
6
+ `ws43RoleGuard` (role-guard-at-spawn), `ws43JournalLease` (journal-lease cutover) — from
7
+ hardcoded `false` in `ConfigDefaults.ts` to the developmentAgent gate (live-on-dev /
8
+ dark-fleet), mirroring the merged `ws44PoolLinks` / `ws44PoolCache` precedent. The
9
+ `ws43JournalLeaseDryRun` sub-knob is computed COHERENTLY with its gate (dev → live/false,
10
+ fleet → dry-run/true). **The `multiMachine.sessionPool.*` master switch and its
11
+ `inboundQueue` / `holdForStability` sub-flags are deliberately HELD** (see #7).
12
+
13
+ **Driver:** Operator directive (Justin, topic 13481, 2026-06-13): "NOTHING should ship
14
+ dark on development agents. Everything fully functional so it actually gets tested and
15
+ doesn't just rot." PR1 (#1151) moved the 7 stateSync memory stores; PR2 is the remaining
16
+ dark-on-dev multi-machine seamlessness layers.
17
+
18
+ **Spec:** `docs/specs/MULTI-MACHINE-SEAMLESSNESS-SPEC.md` (the converged + approved spec the
19
+ seamlessness flags were built against) + `docs/specs/multi-machine-replicated-store-
20
+ foundation.md` (sessionPool context). This is a follow-up gating fix to already-merged
21
+ features, not a new feature.
22
+
23
+ ## What was wrong
24
+
25
+ The 5 seamlessness flags shipped with a literal `false` in `ConfigDefaults.ts` (e.g.
26
+ `ws3OneVoice: false`). A written literal force-darks even a development agent (the #1001
27
+ anti-pattern), so the coherence layers never ran ANYWHERE — they could never be dogfooded
28
+ on Echo / the Mini and never graduated toward the fleet. ws44PoolLinks/ws44PoolCache had
29
+ already been corrected to the omitted-gate pattern; these five lagged behind.
30
+
31
+ ## What changed
32
+
33
+ - `src/config/ConfigDefaults.ts` — OMIT the hardcoded `false` for all 5 flags (each carries
34
+ a ws44-style "DELIBERATELY OMITTED" comment) so `resolveDevAgentGate` decides at runtime.
35
+ `ws43JournalLeaseDryRun` is ALSO omitted (computed coherently at the consumer). `ws13DryRun`
36
+ stays a plain hardcoded default `true` (it is the in-component "log intended CAS without
37
+ performing it" rung, NOT the dev-gate). The sessionPool block is UNTOUCHED.
38
+ - `src/commands/server.ts` — route each consumer read through `resolveDevAgentGate(...)`:
39
+ - `ws3OneVoice`: `SpeakerElection.enabled` → `resolveDevAgentGate(ws3Cfg().ws3OneVoice, config)`.
40
+ - `ws13Reconcile`: `OwnershipReconciler.enabled` → gated; `dryRun` stays the plain `ws13DryRun !== false` read.
41
+ - `ws43RoleGuard`: `scheduler.setRoleGuard().enabled` → gated.
42
+ - `ws43JournalLease`: `setJournalLeaseCutover().enabled` → gated; `dryRun` →
43
+ `cfg?.ws43JournalLeaseDryRun ?? !resolveDevAgentGate(undefined, config)` (coherent).
44
+ - the heartbeat `seamlessnessFlags` advert resolves journal-lease via the gate, not the raw read.
45
+ - `src/server/routes.ts` — `ws41DurableAckEnabled()` reads through
46
+ `resolveDevAgentGate(..., ctx.config)`, so the route + precedence guard agree.
47
+ - `src/core/devGatedFeatures.ts` — ADD all 5 flags as `DEV_GATED_FEATURES` entries
48
+ (by-name, each with a non-destructive/no-egress justification). A header comment records
49
+ WHY the sessionPool master + sub-flags are NOT moved here (the StageAdvancer stage-gate).
50
+ - `src/core/PostUpdateMigrator.ts` — NEW `migrateConfigSeamlessnessDevGate(config)`: strips
51
+ a default-shaped `false` per flag (and the paired `ws43JournalLeaseDryRun:true` only
52
+ alongside a default-shaped `ws43JournalLease:false`) so the gate resolves on update. An
53
+ operator-set value (explicit `true`, a divergent dryRun) is left ENTIRELY alone. Idempotent.
54
+ Wired into the migrate path with `upgraded`/`skipped` reporting.
55
+ - `tests/unit/lint-dev-agent-dark-gate.test.ts` — EXPECTED attribution map line numbers
56
+ recomputed via the attributor (the 5 removed literals + comment reflow shift sessionPool
57
+ 786→809 and cartographer 1100→1123). The 3 sessionPool flags STAY in the map (held,
58
+ hardcoded false). No paths added/removed (the seamlessness flags were never `enabled:`-named).
59
+
60
+ ## Decision-point inventory (Phase-4)
61
+
62
+ ### 1. Over-block
63
+ A gate that resolved live when it shouldn't. Not possible: `resolveDevAgentGate` resolves
64
+ true ONLY when `developmentAgent: true` (or an explicit `true`), so the fleet is byte-for-byte
65
+ unchanged. Each layer is additionally a strict single-machine no-op (the election never
66
+ engages below 2 online machines; the role-guard never fires when this machine holds the lease;
67
+ the journal-lease cutover requires ≥2 flag-coherent peers).
68
+
69
+ ### 2. Under-block
70
+ A consumer left on the raw `=== true` read would keep the feature DARK on a dev agent even
71
+ though ConfigDefaults omits it (the PR1 "still-dark-on-dev = incomplete" failure). Closed by
72
+ grepping ALL consumer sites (5 flags + the heartbeat advert + the dryRun coherence formula)
73
+ and routing every one through the gate, then pinning it with a source-string no-missed-consumer
74
+ wiring test (`tests/unit/seamlessness-dev-gate-wiring.test.ts`, section B) that fails CI if a
75
+ consumer reverts to the raw read.
76
+
77
+ ### 3. Level-of-abstraction fit
78
+ The gate is resolved at the SAME construction/route boundary as the ws44 precedent — one
79
+ funnel (`resolveDevAgentGate`) reads the raw config value and the agent's `developmentAgent`
80
+ flag. No new abstraction; the consumers' downstream `enabled`-true semantics are unchanged.
81
+
82
+ ### 4. Signal vs authority compliance
83
+ Each layer runs in the SAFE direction and none gains authority from going live-on-dev:
84
+ ws3OneVoice only WITHHOLDS a duplicate send (never fabricates one); ws13Reconcile runs in its
85
+ own dry-run (logs intended CAS, performs none); ws43RoleGuard can only REFUSE a spawn (never
86
+ wrongly spawn); ws41DurableAck persists an intent bound to the AUTHENTICATED operator and the
87
+ owner REVALIDATES at apply time; ws43JournalLease coordinates job claims over durable state
88
+ and the cutover gate guarantees the two mechanisms are never both live for a job set.
89
+
90
+ ### 5. Interactions
91
+ - ws43JournalLease ↔ ws43JournalLeaseDryRun: the two MUST resolve coherently or a dev agent
92
+ would run live-but-dry-run (logged, never exercised) — handled by the `?? !resolveDevAgentGate`
93
+ formula at the consumer AND the migration's paired strip.
94
+ - The heartbeat `seamlessnessFlags.ws43JournalLease` advert (what peers read to decide
95
+ coherence) now mirrors the resolved enabled+not-dry-run, so a dev agent honestly advertises
96
+ the capability to a peer.
97
+ - sessionPool HELD: because the seamlessness flags have NO stage-gate, they are independent of
98
+ the sessionPool master — flipping them live-on-dev does not activate the (held) session pool.
99
+
100
+ ### 6. External surfaces
101
+ None new. The five features' routes/behaviors already existed; this only changes WHICH agents
102
+ resolve them live. Replication/coordination is between the operator's OWN machines — no
103
+ external egress, no third-party spend, no new API.
104
+
105
+ ### 7. Rollback cost & multi-machine posture (the heart)
106
+ - **Rollback:** trivial and per-flag — set an explicit `multiMachine.seamlessness.<flag>: false`
107
+ in `.instar/config.json` to force-dark even a dev agent; an explicit `true` is the fleet-flip.
108
+ No data migration to undo (these are coordination layers, not stores). The 3 sessionPool flags
109
+ need no rollback — they were never flipped.
110
+ - **Multi-machine posture:** these layers EXIST to make the operator's two machines behave as
111
+ one agent. Live-on-dev means Echo (laptop) + the Mac Mini now genuinely exercise one-voice
112
+ election, ownership reconcile, durable cross-machine acks, the spawn-time role-guard, and the
113
+ journal-lease cutover across the real two-machine mesh — the only way they get tested before
114
+ fleet. A single-machine dev agent is a strict no-op for all five (no peers).
115
+ - **sessionPool HELD — the deliberate exception:** `multiMachine.sessionPool.{enabled,
116
+ inboundQueue.enabled, holdForStability.enabled}` were NOT moved to dev-gated. They share a
117
+ SECOND, structurally-enforced gate: `sessionPool.stage` is **StageAdvancer-write-only** (a
118
+ module-private `STAGE_WRITE_TOKEN`, rejected by `stageWriteGuard.ts` for any other writer),
119
+ defaults to `'dark'`, and advances only through an E2E-gated rollout ladder
120
+ (`dark → shadow → live-transfer → rebalance`). The activation expression EVERYWHERE is
121
+ `enabled && stage !== 'dark'` (server.ts:1990, 1998, 14351, 15611; boot-sweep gate 7019-7024).
122
+ So: (a) dev-gating `enabled` alone leaves the pool INERT on a dev agent (`stage` is still
123
+ dark) — a hollow move that fails the directive's "actually gets tested"; and (b) forcing
124
+ `stage` past dark in ConfigDefaults would BYPASS the deliberate cutover discipline the
125
+ StageAdvancer exists to enforce — not a clean, safe-by-default, reversible change. This is a
126
+ genuine collision between the "nothing dark on dev" directive and an explicit structural
127
+ safety invariant, so it is surfaced to the operator rather than forced. **Operator decision
128
+ needed:** advance `sessionPool.stage` on the dev mesh via the StageAdvancer ladder (gated on
129
+ green E2E) if active-active pooling should be dogfooded live; until then it stays dark.
130
+
131
+ ## Migration parity
132
+
133
+ `applyDefaults` is add-missing-only deep-merge, so a new agent gets the omitted-flag shape via
134
+ `init`. Existing agents that received the old `false` literals would keep them (explicit values
135
+ are not overwritten) and stay dark even on a dev agent — so `migrateConfigSeamlessnessDevGate`
136
+ strips the exact default-shaped `false` (and paired dryRun) on update. An operator's hand-edited
137
+ value is never touched (reach is not authority). Idempotent (a second run strips nothing).
138
+
139
+ ## Tests
140
+
141
+ Unit: `seamlessness-dev-gate-wiring` (13 — resolution + no-missed-consumer source-seam +
142
+ registry coherence), `PostUpdateMigrator-seamlessnessDevGate` (9), `lint-dev-agent-dark-gate`
143
+ (line-map recomputed), `devGatedFeatures-wiring` (74), `feature-delivery-completeness` (99),
144
+ `SpeakerElection` / `OwnershipReconciler` / `ws3-one-voice-wiring`. Integration/E2E: the
145
+ existing `scheduler-role-guard`, `scheduler-journal-lease-cutover`, `attention-remote-ack`
146
+ suites stay green; `attention-remote-ack-alive` E2E gains two dev-gate cases (flag OMITTED +
147
+ developmentAgent:true → route ALIVE; fleet → 503) proving the gate flips the route, not just
148
+ the registry. Typecheck clean (`tsc --noEmit` exit 0); full lint suite clean. The full suite is
149
+ left to CI (the authority).