_MIN_SEVERITY`) so low-value noise never pages an
+external system. Channels are pure sinks: they forward an advisory notification
+and **never** actuate hardware or call the LLM. Delivery is best-effort +
+non-blocking and only fires when `LIKU_ENABLE_PERIPHERALS=1`.
+
+### Auto-acknowledge + flapping cooldown
+
+The Supervisor gained two noise-reduction controls (both **default OFF**, both with
+a hard safety floor — Class A / `requiresHuman` / critical are **never** affected):
+
+| Control | Config | Behaviour |
+| --- | --- | --- |
+| Auto-acknowledge | `LIKU_PERIPHERAL_AUTO_ACK_SEVERITIES="info,low"` (or `autoAckSeverities` option) | Routine low-severity notifications/tasks are resolved automatically (`autoAcknowledged: true`) so a human is not paged. |
+| Task cooldown | `LIKU_PERIPHERAL_TASK_COOLDOWN_MS=60000` (or `taskCooldownMs` option) | A task for the same `device:metric:level` is **suppressed** if one was active within the window — flapping-sensor spam protection. Cooldown is recorded on create/coalesce/resolve. |
+
+Per-severity routing is unchanged (`escalate`/`notify`/`log` by priority) and now
+queryable: `getEscalatedPeripheralTasks()`, `getPeripheralTasksBySeverity(p)`.
+
+### New driver — Zigbee
+
+`zigbee-driver.js` (mesh, `REMOTE=true`, HIL-capable) joins
+mock/mqtt/serial/ble in the PAL's `DRIVER_IDS`. Available when devices are
+declared (`LIKU_ZIGBEE_DEVICES`) **and** (HIL is on **or** a coordinator is
+configured, `LIKU_ZIGBEE_COORDINATOR`); the optional `zigbee-herdsman` lib is
+required lazily. It emits the same signed DCP envelope and refuses unsigned
+commands when a secret is set — identical safety to BLE/MQTT.
+
+CLI: `liku peripherals tasks [--escalated|--pending|--severity ]`,
+`liku peripherals notifications [--pending|--severity ]`,
+`liku peripherals channels` (show configured escalation sinks). `status` now also
+lists active channels.
+
+## Real bidirectional transport + power telemetry (Phase 12)
+
+### Real BLE connect / notify / write
+
+`ble-driver.js` now implements a real **bidirectional** transport (in addition to
+HIL). A `BleCentral` connection manager scans for declared peripherals, connects,
+resolves a **write** characteristic and subscribes to a **notify** characteristic:
+
+- `perform()` (real path) writes the signed **DCP envelope bytes** to the connected
+ write characteristic. Until a connection exists it returns
+ `{ ok:false, reason:'not-connected' }` and kicks a lazy connect — but the PAL has
+ already enforced the class gate, so a Class A action still requires confirmation.
+- Inbound notify **value-changes** are parsed (JSON) and forwarded to
+ `PAL.ingestSensorReading()`, so wireless sensor updates flow into the normal
+ grounding + monitor + escalation pipeline.
+- The optional `@abandonware/noble`/`noble` lib is required lazily; a test seam
+ (`_setBleLibForTest`) allows the real path to be exercised with a fake adapter.
+- Extra device config: `peripheralId` / `address` (match), `serviceUuid`,
+ `writeCharUuid`, `notifyCharUuid`. HIL stays fully isolated — the real transport
+ is used **only** when HIL is off.
+
+### Power telemetry history + trending
+
+`power-history.js` persists a **rolling** JSONL log
+(`~/.liku/power-history.jsonl`, ≤1000 samples, atomic + locked, flag-gated). The
+PAL records a snapshot after every successful actuation (`recordPowerSample()`),
+and exposes:
+
+| Accessor | Result |
+| --- | --- |
+| `getPowerHistory({ sinceMs, limit })` | recent timestamped samples |
+| `getPowerTrend({ sinceMs })` | `{ count, peakW, avgW, currentW, perDevicePeakW }` |
+
+`powerStatus()` now folds in `peakW` / `avgW` / `samples`. CLI:
+`liku peripherals power --history` and `--trend`.
+
+### Per-device power schedules (time-boxed budgets)
+
+`power-schedule.js` is an **additive, default-OFF** restriction layer. Declare
+`LIKU_PERIPHERAL_SCHEDULES=[{ id, fromHour, toHour, maxW }]` and the PAL enforces
+it **before** the class gate: inside the window a device may draw up to `maxW`;
+outside the window its cap is `0` (must be off). Over the cap → rejected with
+`power-schedule-exceeded`. Schedules can only ever make actuation **more**
+restrictive — they never grant power and never bypass DCP / class gate /
+pending-confirm. No schedules configured → no effect. CLI:
+`liku peripherals schedules`.
+
+### Lock contention metrics
+
+`atomic-file.js` now tracks best-effort counters
+(`acquired / contended / steals / fallbacks / retries`) via `getLockMetrics()`.
+Surfaced in `liku peripherals status`.
+
+## Real Zigbee + smarter power (Phase 13)
+
+### Real bidirectional Zigbee
+
+`zigbee-driver.js` now implements a real **bidirectional** mesh transport (in
+addition to HIL). A `ZigbeeCoordinator` wraps a `zigbee-herdsman` Controller:
+
+- `perform()` (real path) resolves the device endpoint
+ (`getDeviceByIeeeAddr` → `getEndpoint`) and issues a **ZCL command** mapped from
+ the action (`on/off`→`genOnOff`, `lock/unlock`→`closuresDoorLock`,
+ `open/close`→`closuresWindowCovering`, `brightness`→`genLevelCtrl`). Until the
+ endpoint resolves it returns `{ ok:false, reason:'not-connected' }` — but the
+ PAL has already enforced the class gate, so Class A still requires confirmation.
+- Inbound **attribute reports** (`message` events) are parsed and forwarded to
+ `PAL.ingestSensorReading()`, so mesh sensor updates flow into the normal
+ grounding + monitor + escalation pipeline.
+- The `zigbee-herdsman` lib is required lazily; a test seam
+ (`_setZigbeeLibForTest`) exercises the real path with a fake controller.
+
+### Advanced power schedules
+
+`power-schedule.js` gained:
+
+- **Per-day** rules — `days: [0..6]` or names (`["mon","tue"]`). A rule only
+ governs on its days; if a device has only day-restricted rules and none match
+ today, it is **unrestricted** today (schedules only ever restrict).
+- **Sunrise/sunset** window tokens — `fromHour`/`toHour` may be `"sunrise"` /
+ `"sunset"`, resolved from `LIKU_PERIPHERAL_SUNRISE_HOUR` / `_SUNSET_HOUR`
+ (defaults 6/18) or per-rule `sunriseHour` / `sunsetHour`.
+
+Still additive + restrictive-only and enforced **before** the class gate.
+
+### Power anomaly detection
+
+`power-anomaly.js` reads the rolling power history and flags advisory anomalies:
+
+| Type | Trigger |
+| --- | --- |
+| `spike` | latest > `baselineMean × spikeFactor` **and** > `mean + σ·stddev`, with a min absolute delta |
+| `sustained` | last N samples all above `baselineMean × sustainedFactor` |
+| `over-budget` | latest sample exceeded its recorded budget |
+
+`recordPowerSample()` runs detection after each actuation and emits a decoupled
+**`power-anomaly`** event (pure observation — never actuates). Accessor:
+`getPowerAnomalies()`; `powerStatus().anomalies` carries the count. CLI:
+`liku peripherals power --anomalies`. Tunable via `LIKU_PERIPHERAL_ANOMALY_*`.
+
+## Actionable anomalies + robotics foundation (Phase 14)
+
+### Anomaly → escalation
+
+`power-anomaly-consumer.js` bridges the advisory `power-anomaly` event into the
+SAME human-gated escalation pipeline used by sensor alerts:
+
+```
+ PAL 'power-anomaly' → buildAnomalyNotification() (advisory, Class C synthetic)
+ → SupervisorAgent.receiveNotification() (bounded inbox + channels)
+ → orchestrator.emit('supervisor:notification')
+ → SupervisorAgent.createPeripheralTask({ source:'power-anomaly' })
+ → orchestrator.emit('supervisor:task') (reviewable, pending-review)
+```
+
+- The consumer applies its OWN **dedup + cooldown** (`LIKU_PERIPHERAL_ANOMALY_COOLDOWN_MS`,
+ default 60 s) so a flapping power signal cannot spam the queue, independent of
+ the Supervisor's task cooldown.
+- Tasks are tagged `source:'power-anomaly'` and stay `pending-review`,
+ `requiresHuman:true`, `autonomousAction:false` — **strictly advisory**. The
+ anomaly is modelled as a read-only **Class C** synthetic device so it can never
+ become an actuation path. `createAgentSystem` auto-attaches the consumer
+ (flag-gated). CLI: `liku peripherals tasks --anomaly`.
+
+### ROS2 bridge foundation
+
+`ros2-driver.js` (robotics, `REMOTE=true`, HIL-capable) joins the driver set. A
+`Ros2Bridge` wraps a `rclnodejs` node:
+
+- `perform()` (real path) **publishes** the signed DCP envelope to the device's
+ command topic; until the node is ready it returns `{ ok:false, reason:'not-connected' }`
+ — but the PAL has already enforced the class gate, so Class A still confirms.
+- Inbound messages on the state topic are parsed and forwarded to
+ `PAL.ingestSensorReading()`.
+- Available when devices are declared (`LIKU_ROS2_DEVICES`) **and** (HIL is on
+ **or** a domain is configured, `LIKU_ROS2_DOMAIN`); `rclnodejs` is required
+ lazily, with a `_setRos2LibForTest` seam for the real path.
+
+## Matter/Thread + anomaly tiers (Phase 15)
+
+### Matter/Thread bridge foundation
+
+`matter-driver.js` (smart-home, `REMOTE=true`, HIL-capable) joins the driver
+set. A `MatterController` wraps a matter.js commissioning controller:
+
+- `perform()` (real path) **invokes** a Matter cluster command mapped from the
+ action (`on/off`→`OnOff`, `lock/unlock`→`DoorLock`,
+ `open/close`→`WindowCovering`, `brightness`→`LevelControl`) on the node
+ endpoint (`getNode` → `getEndpoint` → `invoke`). Until the node resolves it
+ returns `{ ok:false, reason:'not-connected' }` — but the PAL has already
+ enforced the class gate, so Class A still confirms.
+- Inbound **attribute reports** are parsed and forwarded to
+ `PAL.ingestSensorReading()`.
+- Available when devices are declared (`LIKU_MATTER_DEVICES`) **and** (HIL is on
+ **or** a fabric is configured, `LIKU_MATTER_FABRIC`); the matter.js lib is
+ required lazily, with a `_setMatterLibForTest` seam for the real path.
+
+### Anomaly severity tiers
+
+`power-anomaly-consumer.js` now maps anomaly **type → advisory tier**
+(`ANOMALY_TIERS`), driving differentiated task priority, escalation routing and
+dedup window — while staying strictly advisory:
+
+| Type | Severity → task | Escalation | Cooldown |
+| --- | --- | --- | --- |
+| `over-budget` | `critical` → **high** | `escalate` | 15 s (surfaces fastest, never auto-acked) |
+| `sustained` | `warning` → medium | `notify` | 90 s (persistent → dedups longer) |
+| `spike` | `warning` → medium | `notify` | 60 s |
+| _(other)_ | `info` → low | `log` | 120 s |
+
+An explicit `cooldownMs` option / `LIKU_PERIPHERAL_ANOMALY_COOLDOWN_MS` overrides
+all tiers. Higher severity only means **more visibility/priority** — every
+anomaly task remains `pending-review`, `autonomousAction:false`, and modelled on
+a read-only Class C synthetic device (never an actuation path). CLI:
+`liku peripherals power --anomalies` shows the tier; `tasks --anomaly` shows the
+resulting priority.
+
+## Commissioning / pairing + tier task metadata (Phase 16)
+
+### Pairing / commissioning state machine
+
+`pairing.js` provides a reusable, testable state machine
+(`unpaired → pairing → paired`, with backed-off retries → `failed` once attempts
+exhaust). **Every connection-oriented real driver — BLE, Zigbee, ROS2 and
+Matter — uses it through the shared `driver-pairing.js` surface**, so pairing is
+consistent across the whole fleet (Phase 17):
+
+- **Retry + backoff** — `canAttempt()` gates on an exponential-backoff
+ `nextRetryAt`; after `maxAttempts` the device is `failed`. Tunable per driver
+ via `LIKU__PAIR_MAX_ATTEMPTS` / `LIKU__PAIR_BACKOFF_MS`
+ (`MATTER` / `BLE` / `ZIGBEE` / `ROS2`).
+- **HIL is isolated** — in HIL mode pairing is *virtual* (`{ state:'paired',
+ simulated:true }`); no real fabric/adapter/coordinator/graph is touched.
+- **Never bypasses safety** — pairing is transport bookkeeping only; a paired
+ device still flows through DCP → class gate → pending/confirm for every action.
+
+Each driver exposes `pair(id)` / `unpair(id)` / `pairingStatus()`. The PAL
+aggregates them uniformly via `pairDevice(id)`, `unpairDevice(id)` and
+`getPairingStatus()` — and reports **connectionless** drivers (mock / MQTT /
+serial) as `ready` so the surface is uniform. CLI:
+`liku peripherals pair `, `liku peripherals unpair `,
+`liku peripherals drivers` (per-device pairing state), and `status`
+(paired/failed summary).
+
+### Tier metadata on tasks + differentiated behaviour
+
+Anomaly tasks carry `anomalyType` + `severityTier`. The tier drives **real,
+visible differences** (Phase 15–17):
+
+- **Priority / visibility** — over-budget → `critical` → **high** priority /
+ `escalate`; spike/sustained → `warning` → medium / `notify`; other → `info` /
+ low. `Supervisor.getNotificationsBySeverity()` lets a surface prioritise the
+ inbox by tier.
+- **Escalation channel routing** — the notification's tier severity gates which
+ additive channels (`log`/`file`/`webhook`) it reaches (each channel has a
+ min-severity), so critical anomalies fan out further than routine ones.
+- **Dedup / cooldown** — over-budget surfaces fastest (15 s), sustained dedups
+ longest (90 s).
+- **CLI** — `tasks --anomaly` shows a `⚡` badge; `tasks --anomaly --severity
+ ` filters by tier priority.
+
+All tier behaviour is strictly advisory + human-gated; no tier ever actuates.
+
+## Token lifecycle + advisory auto-scheduling (Phase 18)
+
+### Capability-token lifecycle bound to pairing
+
+Capability tokens (`dcp-protocol.js`) are stateless HMAC artifacts, so
+`token-store.js` adds the lifecycle state that makes revocation possible, bound
+to the pairing lifecycle:
+
+- **Issue on pair** — a successful `pair()` mints generation 1 with a stable
+ per-device **identity fingerprint** (HMAC over the deviceId; works signed OR
+ unsigned/local).
+- **Rotate on re-pair** — re-pairing after revoke bumps the generation; a stale
+ token's `gen` no longer verifies (`generation-mismatch`).
+- **Revoke on unpair** — `unpair()` marks the device revoked + bumps the
+ generation, invalidating any outstanding token.
+- **Enforcement** — the PAL's `execute()` refuses a command for any **REMOTE**
+ driver whose token is revoked (`code:'token-revoked'`, "re-pair to restore").
+ HIL is isolated (virtual pairing never revokes); connectionless/local drivers
+ are exempt.
+
+DCP additions: `issueCapabilityToken({ gen, identity })` and
+`verifyCapabilityToken({ gen, identity })` (both optional + backward compatible).
+PAL: `getTokenStatus()`, `rotateToken(id)`, `revokeToken(id)`. CLI:
+`liku peripherals token [status|rotate |revoke ]`; `drivers` shows
+`token:gen`.
+
+### Advisory auto-schedule suggestions
+
+`power-schedule-advisor.js` turns **recurring** anomalies into human-reviewable
+proposed schedules — never auto-applied:
+
+- **Detect** — `recordAnomaly()` buckets occurrences by `device:type` within a
+ window (`LIKU_PERIPHERAL_ADVISOR_WINDOW_MS`, default 24 h). Once the count
+ crosses `LIKU_PERIPHERAL_ADVISOR_MIN_OCCURRENCES` (default 3), `proposeSchedules()`
+ emits a proposal (recurring hour → a time-boxed cap).
+- **Deduplicate** — one open proposal per `device:type` until confirmed/dismissed.
+- **Confirm (pending/confirm rail)** — a proposal is `status:'proposed'`,
+ `autonomousAction:false`, and is **only** activated by an explicit
+ `confirm(id)`, which writes it to the confirmed schedule store
+ (`peripheral-schedules.json`) that `power-schedule.js` reads *in addition to*
+ env. Nothing is enforced before confirmation.
+
+The anomaly consumer feeds the advisor and re-emits new proposals as
+`supervisor:schedule-suggestion` events (advisory). PAL:
+`getScheduleSuggestions()`, `confirmScheduleSuggestion(id)`,
+`dismissScheduleSuggestion(id)`. CLI: `liku peripherals suggestions`,
+`liku peripherals apply-schedule `, `liku peripherals dismiss-schedule `.
+
+## Forecasting + attribution + token rotation (Phase 19)
+
+### Power forecasting
+
+`power-forecast.js` turns the rolling history into **per-hour-of-day baselines**
+(total + per device) and a **short-horizon forecast**:
+
+- `hourlyBaselines()` / `deviceHourlyBaselines()` — mean / peak / count per hour.
+- `forecast({ horizonHours })` — predicted draw for upcoming hours (falls back to
+ the overall mean for hours with no history; needs `≥ FORECAST_MIN_SAMPLES`).
+- `forecastExceedsBudget({ budgetW })` — **early warning** list of upcoming hours
+ whose predicted/peak draw would exceed the budget (predictive, before it
+ happens). PAL: `getPowerForecast()`, `getForecastWarnings()`. CLI:
+ `liku peripherals power --forecast`.
+
+The advisor uses the device's per-hour baseline **peak** to set a smarter cap
+(`basis:'forecast-baseline'`) — letting normal operation continue while capping
+the anomalous excess.
+
+### Per-device anomaly attribution
+
+`power-anomaly.detect()` now attributes each anomaly to the **driving device** —
+the one whose current draw rose most above its own baseline (falling back to the
+biggest current consumer). Anomalies carry `attributedDevice` / `attributedDeltaW`,
+and the consumer targets that real device (instead of the aggregate
+`power-budget`) in notifications, tasks and schedule suggestions. CLI:
+`power --anomalies` shows `→ (Δ)`.
+
+### Scheduled token rotation + grace window
+
+`token-store.js` completes the lifecycle:
+
+- **Scheduled rotation** — `LIKU_DCP_TOKEN_ROTATE_MS` arms a `rotateDueAt` on
+ pair; the PAL calls `rotateIfDue()` lazily on use, so tokens rotate on a
+ schedule without a background timer.
+- **Grace window** — on rotation the immediately-previous generation stays valid
+ for `LIKU_DCP_TOKEN_GRACE_MS` (default 60 s) so a command signed just before
+ rotation is not abruptly rejected. `isTokenValid(id, gen, now)` encodes this
+ (current gen always valid; previous gen valid only within grace; revoked →
+ nothing valid). Revocation still overrides everything.
+
+## Confident forecasts + multi-device coordination + anomaly→action (Phase 20)
+
+### Forecast confidence + longer horizons
+
+`power-forecast.js` now attaches a **confidence interval** and a qualitative
+**confidence label** to every forecast hour, and supports **day-ahead horizons**:
+
+- Each `hourlyBaselines()` bucket carries a `std` (per-hour standard deviation).
+- Each `forecast()` horizon entry adds `lowW` / `highW` (`mean ± 1.28·std`),
+ `stdW`, `confidence` (`high`/`medium`/`low` from sample count + coefficient of
+ variation, decayed for far-ahead hours) and `stepsAhead`.
+- `horizonHours` is clamped to `MAX_HORIZON_HOURS` (24) — the per-hour-of-day
+ baselines wrap, so a full day ahead is a natural extension, never a runaway.
+- Early warnings (`forecastExceedsBudget`) now include the band + confidence.
+ CLI: `power --forecast` prints `~ [low–high] conf`.
+
+### Multi-device coordinated schedule proposals
+
+`power-forecast.contributorsAtHour({ hour, budgetW })` ranks the devices by their
+per-hour baseline **peak** and reports whether their **combined** typical draw
+exceeds the budget. When it does *and* **2+ devices** jointly drive it,
+`power-schedule-advisor.proposeMultiDeviceSchedule()` proposes a **coordinated**
+set of per-device caps, allocated proportionally to each device's share so the
+caps **sum within the budget**. `confirm()` writes **one restrict-only rule per
+device** (`source:'advisor-confirmed-multi'`). Strictly advisory + human-gated,
+deduped one open proposal per hour. The consumer fires this on over-budget
+anomalies and emits `supervisor:schedule-suggestion`.
+
+### Advisory anomaly→action patterns (proactive self-healing)
+
+`anomaly-action-advisor.js` watches for **persistently anomalous devices** and
+escalates an advisory action up a fixed ladder (all **non-actuating** and already
+human-gated CLI operations):
+
+| Occurrences (window) | Advisory action | Severity | Directive |
+| --- | --- | --- | --- |
+| 3× | `reduce-schedule` | warning | cap power via a confirmed schedule |
+| 6× | `rotate-token` | warning | `liku peripherals token rotate ` |
+| 10× | `unpair` | critical | `liku peripherals unpair ` |
+
+`proposeActions()` surfaces the **highest** rung met (monotonic supersede);
+`confirm()` **records the human's approval and returns the exact command to run —
+it never executes the action** (no autonomous actuation path). The synthetic
+`power-budget` aggregate is skipped (no single device to act on). PAL:
+`getAnomalyActions()`, `confirmAnomalyAction()`, `dismissAnomalyAction()`. CLI:
+`liku peripherals anomalies [--attributed]` and
+`liku peripherals anomaly-action [confirm|dismiss ]`. The consumer emits
+`supervisor:anomaly-action` for new proposals.
+
+### Phase 20 safety invariants
+
+- **forecast-confidence-only-informs** — confidence intervals / longer horizons /
+ contributor analysis are pure observation. They inform smarter (still
+ human-confirmed) suggestions and never actuate.
+- **multi-device-caps-only-restrict** — a coordinated proposal only ever writes
+ restrict-only schedule rules that sum within the budget, and only after an
+ explicit `confirm()`. It never turns a device on/off or raises any cap.
+- **anomaly-actions-are-advisory** — every anomaly→action suggestion is a
+ reviewable proposal; confirmation returns a command for a human to run. No
+ suggestion (including `unpair`/`rotate-token`) is auto-executed, and none
+ actuates the physical device.
+
+## Lock observability + cross-host coordination + cron scheduling (Phase 21)
+
+### Lock observability over time
+
+`src/shared/atomic-file.js` now tracks **per-file** contention (not just global
+counters): `getPerFileLockMetrics()` maps each store's basename → `{ acquired,
+contended, steals, fallbacks, retries }`. `src/main/peripherals/lock-history.js`
+**persists** periodic snapshots to `~/.liku/lock-history.jsonl` (rolling, atomic,
+flag-gated) and computes **trends** — the delta between the first and last
+snapshot, the current contention rate, and the hottest files. Snapshots accrue
+naturally (a best-effort `record()` fires after each power sample) or on demand.
+PAL: `getLockHistory()`, `recordLockSnapshot()`, `getLockTrends()`. CLI:
+`liku peripherals locks [--record]` and an enriched `status` (hottest lock).
+
+### Cross-host coordination foundation
+
+`src/main/peripherals/coordination.js` adds a dependency-free **TTL-lease** layer
+for multi-node fleets. A node identity (`LIKU_NODE_ID` or `hostname:pid`) takes a
+lease on a resource (`device:`, a task, a token) by atomically creating a
+directory under a **shared** `LIKU_CLUSTER_DIR/leases/`. mkdir is atomic across
+hosts on a shared filesystem, giving mutual exclusion; an **expired** lease
+(crashed holder) is stolen; only the owner can release early.
+
+- **Single-machine is the default** — with `LIKU_CLUSTER_DIR` unset, cluster mode
+ is OFF and every lease is granted locally, so the single-machine path is
+ completely unchanged (no new files, no new behaviour).
+- The PAL `execute()` gate consults `coordination.canAct('device:')` for
+ REMOTE drivers only when cluster mode is on: a device leased by another node is
+ rejected with `device-leased-elsewhere`. Best-effort + non-fatal.
+- Resource ids are strictly allow-list sanitized (`..` collapsed, `/`/`\`
+ stripped) — no path traversal, no new attack surface.
+- PAL: `getCoordinationStatus()`, `acquireDeviceLease()`, `releaseDeviceLease()`.
+ CLI: `liku peripherals coordination [status|lease |release ]`.
+
+### Cron-based device scheduling (stretch)
+
+`src/main/peripherals/device-schedule.js` adds optional **5-field cron** rules
+(`LIKU_DEVICE_CRON`) for recurring device actions. A due cron rule NEVER
+actuates — it produces an **advisory, human-gated proposed task** (`status:
+'pending-review'`, `autonomousAction:false`); Class A devices are flagged
+`requiresHuman` and remain confirm-gated at `execute` time regardless.
+
+- **Sandboxed parser** — split → bounded numeric ranges only (`*`, `a`, `a-b`,
+ `a-b/n`, `*/n`, comma lists). No eval, no dynamic code, no catastrophic-
+ backtracking regex; every field is strictly range-checked and malformed rules
+ are dropped. Actions are restricted to a conservative allow-list
+ (`on/off/toggle/lock/unlock/open/close/check/status`).
+- Vixie-cron day-of-month/day-of-week OR semantics when both are restricted.
+- Additive + backward-compatible: existing time-boxed power schedules are
+ untouched. PAL: `getCronSchedules()`, `getDueCronTasks(now)`. CLI:
+ `liku peripherals cron [--at ]`.
+
+### Phase 21 safety invariants
+
+- **lock-observability-is-pure** — persisting metrics/trends never changes locking
+ behaviour or actuates anything; recording is on-demand (no background timer).
+- **cross-host-preserves-single-machine** — cluster mode is opt-in; with no
+ `LIKU_CLUSTER_DIR` the single-machine path is byte-for-byte unchanged. Leases
+ are advisory coordination bookkeeping and never bypass the PAL safety chain.
+- **cron-triggers-are-advisory** — a cron match yields a reviewable proposed task,
+ never an actuation. Class A stays human-gated, and the parser adds no new
+ attack surface.
+
+## Token hardening + cron productionization + cluster lock aggregation (Phase 22)
+
+### Token lifecycle refinements
+
+`token-store.js` gains two security refinements plus cross-host propagation:
+
+- **Per-action (least-privilege) tokens** — `issueActionToken(deviceId, action)`
+ mints a capability token scoped to EXACTLY ONE action (refused if the action is
+ not in the device's granted capability set). `verifyDeviceToken(deviceId,
+ action, token)` validates against the current lifecycle state (revocation,
+ effective generation, identity binding, action scope) with grace-window support.
+- **Human-gated auto-revoke on persistent anomalies** — when the anomaly→action
+ advisor escalates to `rotate-token` / `unpair` and a human CONFIRMS,
+ `PAL.confirmAnomalyAction()` PERFORMS the approved security operation
+ (`rotateToken` / `unpairDevice`→token revoke). These are non-actuating security
+ ops; the confirmation is the human gate. `{ execute:false }` records approval
+ without performing.
+- **Cross-host propagation** — in cluster mode a device's lifecycle record (gen /
+ revoked / identity) is MIRRORED to `LIKU_CLUSTER_DIR/tokens/.json`. The
+ effective state merges local + shared (REVOCATION-WINS, generation = max), so a
+ revocation or rotation on one node is honoured fleet-wide. Single-machine
+ (cluster off) → unchanged.
+
+### Cron productionization
+
+- **Real tick/consumer** — `src/main/agents/cron-scheduler.js` `attachCronScheduler(orch)`
+ exposes a `tick(now)` that turns DUE cron rules into bounded, human-gated
+ Supervisor tasks (via `createPeripheralTask` → `supervisor:task` +
+ `supervisor:cron-task`), with per-`device:action` **dedup + cooldown**
+ (`LIKU_PERIPHERAL_CRON_COOLDOWN_MS`, default 5 min). TIMER-FREE by default; an
+ optional `intervalMs` timer is off unless requested and is `unref`'d.
+- **Confirm flow (persist rules)** — `device-schedule.js` adds
+ `proposeRule` → `confirmRule` (writes to `device-cron.json`, read by
+ `loadRules`) / `dismissRule` / `removeConfirmedRule`. A proposed rule is NEVER
+ active until confirmed. CLI: `liku peripherals cron [propose|confirm|dismiss|rules|tick|remove]`.
+- A cron task is still ADVISORY: actuating it requires `PAL.execute` (Class A
+ stays confirm-gated); `autonomousAction` is always false.
+
+### Distributed lock/metrics aggregation
+
+`lock-history.js` mirrors each node's latest snapshot to
+`LIKU_CLUSTER_DIR/lock-metrics/.json`. `clusterAggregate()` rolls up
+fleet totals + per-node breakdown + combined per-file hotspots (folding in this
+node's live counters). PAL: `getClusterLockMetrics()`. CLI `locks` shows the
+cluster view when a shared cluster dir is configured.
+
+### Phase 22 safety invariants
+
+- **least-privilege-tokens** — per-action tokens narrow (never widen) a device's
+ capability; verification honours revocation + generation + identity + scope.
+- **auto-revoke-is-human-gated** — a token rotation / unpair only happens after an
+ explicit human confirmation of the escalated anomaly→action; it is a security
+ operation (no physical actuation) and never bypasses the PAL chain.
+- **cron-tasks-stay-advisory** — a productionized cron tick creates reviewable
+ `pending-review` tasks only; Class A stays confirm-gated, nothing auto-runs.
+- **cluster-propagation-is-additive** — token / lock cluster mirroring only runs
+ when `LIKU_CLUSTER_DIR` is set; single-machine behaviour is byte-for-byte
+ unchanged.
+
+## Seasonal forecasts + advanced anomaly→action (Phase 23)
+
+### Forecast refinements
+
+`power-forecast.js` sharpens prediction with weekly seasonality + per-device attribution:
+
+- **Day-of-week seasonality** — `dowHourlyBaselines()` buckets by day-of-week ×
+ hour-of-day. `seasonalForecast()` prefers the dow×hour baseline for each
+ upcoming hour (when it has `≥ LIKU_PERIPHERAL_FORECAST_DOW_MIN` samples),
+ falling back to the hour-of-day baseline, then the overall mean. The plain
+ `forecast()` is unchanged.
+- **Per-device forecast warnings** — `deviceForecastWarnings({ budgetW })` names,
+ for each upcoming over-budget hour, the device MOST LIKELY to drive it (highest
+ per-hour baseline peak). PAL: `getSeasonalForecast()`,
+ `getDeviceForecastWarnings()`. CLI: `power --forecast [--seasonal]`.
+- **Multi-hour coordinated scheduling** — `power-schedule-advisor.proposeMultiHourSchedule()`
+ scans the forecast for the longest CONTIGUOUS run of hours whose confidence
+ upper band exceeds budget and proposes ONE window `[from..to]` with per-device
+ caps (allocated by each contributor's share). Confirmation writes a restrict-only
+ rule per device across the whole window. PAL: `getMultiHourProposal()`.
+
+### Advanced anomaly→action patterns
+
+- **Auto-create + confirm reduce-schedule** — when a human confirms a
+ `reduce-schedule` anomaly→action, `PAL.confirmAnomalyAction()` calls
+ `power-schedule-advisor.createConfirmedSchedule()` to write a restrict-only
+ schedule for the device (cap derived from its forecast baseline peak, falling
+ back to the power budget). The confirmation IS the human gate; nothing actuates.
+- **Fleet-wide rotate-all** — `anomaly-action-advisor.proposeFleetAction()`
+ proposes a single advisory `rotate-all` when `≥ LIKU_PERIPHERAL_FLEET_MIN_DEVICES`
+ distinct devices are persistently anomalous. Confirming it runs
+ `token-store.rotateAll()` (rotate every ACTIVE token; skip revoked), mirrored
+ across the cluster. Human-gated; reuses the `supervisor:anomaly-action` event.
+ CLI: `token rotate-all`, `anomaly-action confirm `.
+
+### Phase 23 safety invariants
+
+- **forecast-refinements-only-observe** — seasonality, per-device warnings, and
+ multi-hour analysis are pure prediction; they sharpen (still human-confirmed)
+ suggestions and never actuate.
+- **auto-heal-is-human-gated** — reduce-schedule / rotate-token / unpair /
+ rotate-all all execute ONLY on an explicit human confirmation of the escalated
+ anomaly→action, and are non-actuating (restrict / crypto / pairing) operations
+ that never bypass the PAL safety chain.
+- **fleet-actions-are-advisory** — a fleet rotate-all is a proposal until
+ confirmed; it rotates tokens (a security op), never actuates a device.
+
+## Multi-device auto-heal + anomaly-aware forecasts (Phase 24)
+
+### Anomaly→action refinements
+
+- **Multi-device coordinated reduce on confirm** — when a human confirms a
+ `reduce-schedule` anomaly→action, `PAL.confirmAnomalyAction()` first tries
+ `power-schedule-advisor.createConfirmedMultiSchedule()`: if the current-hour
+ breach is jointly driven by 2+ devices, it writes ONE restrict-only rule per
+ contributor (caps proportional to peak share, sum ≤ budget). A single
+ contributor falls back to the single-device `createConfirmedSchedule()`. The
+ confirmation IS the human gate; nothing actuates.
+- **Per-device auto-heal policies** — `anomaly-action-advisor` supports
+ per-device occurrence thresholds for each ladder action (`reduce-schedule` /
+ `rotate-token` / `unpair`). Sources (later overrides earlier): default ladder →
+ `LIKU_PERIPHERAL_AUTOHEAL_POLICIES` env (a `*` key sets a fleet default) → the
+ persisted store (`setPolicy`). `proposeActions` uses each device's effective
+ thresholds. PAL: `getAutoHealPolicies()`, `setAutoHealPolicy()`. CLI:
+ `anomaly-action policy [list|set reduce=N rotate=N unpair=N]`.
+
+### Power forecasting refinements
+
+- **Confidence-weighted multi-hour caps** — `proposeMultiHourSchedule` allocates
+ per-device caps from a confidence-weighted reference `mean + w·(peak−mean)`,
+ where `w` grows as the run's confidence drops (high→mean-leaning, low→peak-
+ leaning). Shares still sum to budget, so caps never exceed it (restrict-only).
+- **Holiday / anomaly-aware baselines** — `seasonalForecast({ excludeAnomalous:true })`
+ drops known-anomalous samples (flagged `overBudget`/`anomalous`, or dates in
+ `LIKU_PERIPHERAL_FORECAST_HOLIDAYS`) so a one-off spike or atypical day doesn't
+ skew normal-operation predictions. The plain `forecast()` stays byte-identical.
+- **Improved day-of-week handling** — a weekend/weekday GROUP baseline sits
+ between the dow×hour baseline and the hour-of-day fallback, filling a specific
+ weekday's gaps from the broader group. CLI: `power --forecast [--seasonal] [--exclude-anomalous]`.
+
+### Phase 24 safety invariants
+
+- **multi-device-caps-only-restrict** — a coordinated reduce writes only
+ restrict-only rules whose caps sum within budget, and only on explicit human
+ confirmation. It never turns a device on/off or raises a cap.
+- **policies-only-gate-visibility** — auto-heal policies change WHEN a proposal is
+ surfaced (per-device thresholds); they never make an action autonomous.
+- **forecast-refinements-only-observe** — confidence weighting, anomaly-aware
+ exclusion, and group baselines are pure prediction; they sharpen (still
+ human-confirmed) suggestions and never actuate.
+
+## Cross-host refinements + deeper self-healing (Phase 25)
+
+### Cross-host coordination refinements
+
+- **Lease-aware pairing** — in cluster mode `driver-pairing.pair()` first acquires
+ the device lease (`device:`, the SAME key the PAL execute gate checks); only
+ the lease holder may complete pairing (and bind its token). A blocked node
+ returns `error:'leased-elsewhere'`. `unpair()` releases the lease. Lease TTL:
+ `LIKU_PERIPHERAL_PAIR_LEASE_TTL_MS` (default 300000) — auto-expires so a crashed
+ node can't block forever. Single-machine (cluster off) → unchanged.
+- **Distributed cron dedup** — `coordination.claimOnce(resourceId, {ttlMs})` claims
+ a short-lived lease so exactly ONE fleet node fires a given rule per minute
+ bucket. The cron scheduler claims `cron:::`
+ before creating a task; a losing node skips (no duplicate Supervisor task).
+- **Cluster GC / TTL sweeper** — `token-store.sweepClusterTokens({ttlMs})` removes
+ cluster token records not updated within `LIKU_DCP_CLUSTER_TOKEN_TTL_MS`
+ (default 7 days; a live revocation keeps mirroring `updatedAt`, so it is never
+ GC'd out from under other nodes). `coordination.pruneExpiredLeases()` removes
+ expired lease dirs. PAL `sweepCluster()` drives both lazily (no background
+ timer). CLI: `coordination sweep`.
+
+### Anomaly→action refinements
+
+- **Auto multi-hour coordinated reduce on confirm** — confirming a
+ `reduce-schedule` anomaly→action now prefers the STRONGEST coordinated response:
+ a **multi-hour** window (`createConfirmedMultiHourSchedule`, contiguous
+ over-budget run, confidence-weighted per-device caps) → else a single-hour
+ **multi-device** cap → else a single-device schedule. All restrict-only, caps
+ sum ≤ budget, human-approved via the confirm.
+- **Per-device auto-heal escalation cooldown** — a device with a lower-rung
+ proposal does not escalate to the next rung until
+ `LIKU_PERIPHERAL_AUTOHEAL_ESCALATION_COOLDOWN_MS` (default 3600000) elapses. It
+ NEVER suppresses the first proposal, and NEVER suppresses a CRITICAL rung
+ (e.g. `unpair`) — safety paths always surface immediately.
+
+### Forecast refinements
+
+- **Longer seasonal windows** — `LIKU_PERIPHERAL_FORECAST_LOOKBACK_MS` sets the
+ default history lookback (e.g. 7–14 days). Unset → all history (byte-identical).
+- **Data-driven special-day detection** — `detectSpecialDays()` flags dates whose
+ daily-mean draw deviates from the cross-day distribution by `>`
+ `LIKU_PERIPHERAL_FORECAST_SPECIAL_SIGMA` σ (default 2). With
+ `LIKU_PERIPHERAL_FORECAST_AUTO_SPECIAL=1`, `seasonalForecast({excludeAnomalous})`
+ also excludes those auto-detected days (in addition to the
+ `LIKU_PERIPHERAL_FORECAST_HOLIDAYS` override list). PAL: `getSpecialDays()`.
+ CLI: `power --forecast --special-days`.
+
+### New environment variables (all default OFF / inert)
+
+| Variable | Default | Purpose |
+| --- | --- | --- |
+| `LIKU_PERIPHERAL_PAIR_LEASE_TTL_MS` | `300000` | Lease TTL for lease-aware pairing |
+| `LIKU_DCP_CLUSTER_TOKEN_TTL_MS` | `604800000` | Cluster token GC age threshold |
+| `LIKU_PERIPHERAL_AUTOHEAL_ESCALATION_COOLDOWN_MS` | `3600000` | Auto-heal escalation cooldown (0 = off) |
+| `LIKU_PERIPHERAL_FORECAST_LOOKBACK_MS` | unset | Rolling seasonal window |
+| `LIKU_PERIPHERAL_FORECAST_AUTO_SPECIAL` | unset | Enable data-driven special-day exclusion |
+| `LIKU_PERIPHERAL_FORECAST_SPECIAL_SIGMA` | `2` | Special-day detection sensitivity |
+
+### Phase 25 safety invariants
+
+- **lease-owned-pairing** — in cluster mode pairing/token binding only proceeds
+ on the node holding the device lease; single-machine is byte-for-byte unchanged.
+- **cron-fires-once-per-fleet** — a cron rule creates at most one Supervisor task
+ per minute bucket across the fleet (claim-once); losers skip, never duplicate.
+- **token-gc-is-conservative** — only records stale beyond a generous TTL are
+ GC'd; an active revocation keeps refreshing `updatedAt` and is never removed.
+- **cooldown-never-suppresses-safety** — escalation cooldown holds only non-critical
+ rung upgrades; first proposals and critical rungs always surface.
+- **coordinated-reduce-only-restricts** — multi-hour/multi-device reduce writes
+ only restrict-only rules whose caps sum ≤ budget, and only on human confirm.
+
+## If a human decides to act
+
+Any physical response still travels the full PAL safety chain — the alert path
+never shortcuts it:
+
+```
+ PAL.execute(deviceId, action, params)
+ → DCP evaluateCommand() (capability scoping, param validation, power budget)
+ → class gate (C: free · B: auto-gated 0.95 · A: guard.peripheral.* 0.5 → QUEUES)
+ → pending/confirm (`liku system-context confirm guard.peripheral. --apply`)
+ → one-shot auth consumed after success
+```
+
+## Signal-quality controls (Phase 7)
+
+`PeripheralMonitor` filters noise before an alert is ever raised:
+
+| Control | Default | Override | Behavior |
+| --- | --- | --- | --- |
+| Cooldown (debounce) | `60000` ms | `cooldownMs` option / `LIKU_PERIPHERAL_ALERT_COOLDOWN_MS` | Min gap between alerts for the same `device:metric`. |
+| Hysteresis (deadband) | `0.05` (5% of threshold) | `hysteresisFraction` option / `LIKU_PERIPHERAL_HYSTERESIS_FRACTION`, or per-metric absolute `{ hysteresis }` | Once breached, no re-alert until the value returns safely past the margin. |
+
+Together these implement a per-`device:metric` state machine: a breach fires
+once, is held while the value hovers, and only re-arms after recovery + cooldown.
+
+## DCP wire format + capability tokens (Phase 8)
+
+`src/main/peripherals/dcp-protocol.js` formalizes the Device Control Protocol
+that was previously implicit in `peripheral-policy.js`. It is a **pure** module
+(crypto + structure only).
+
+### Command envelope (wire format)
+
+```json
+{
+ "dcp": "1.0",
+ "type": "command",
+ "id": "",
+ "ts": 1751700000000,
+ "nonce": "",
+ "device": "",
+ "action": "unlock",
+ "params": {},
+ "capability": "."
+}
+```
+
+- `ts` + `nonce` provide a **freshness/replay window** (default 30 s) and
+ **replay protection** (caller-owned `seenNonces` map).
+- `capability` is an optional **signed capability token** (HMAC-SHA256) scoping
+ the token to `device` + `action(s)` with an `exp`. Set `LIKU_DCP_SECRET` to
+ sign/verify; without a secret, tokens are an explicit `unsigned` local-mode
+ marker (backward compatible for the mock + trusted local links).
+
+### API
+
+| Function | Purpose |
+| --- | --- |
+| `issueCapabilityToken({ deviceId, actions, ttlSec })` | Mint a scoped, expiring token. |
+| `verifyCapabilityToken(token, { deviceId, action })` | Check signature, expiry, device + action scope. |
+| `buildCommandEnvelope({ device, action, params, token })` | Construct a versioned envelope. |
+| `parseCommandEnvelope(env)` | Structural validation (version/type/fields). |
+| `verifyEnvelope(env, { secret, seenNonces, requireCapability })` | Full verify: structure + freshness + replay + capability. |
+
+`peripheral-policy.js` exposes `evaluateCommandEnvelope(device, envelope, ctx)`
+which verifies the envelope **then** runs the same host-side capability/param/
+power validation as `evaluateCommand()` — so inbound wire commands get the full
+safety treatment. Local callers keep using `evaluateCommand()` unchanged.
+
+The **serial** and **MQTT** drivers now emit DCP envelopes (with a scoped
+capability token) on `perform()`, replacing their ad-hoc payloads. The mock
+driver stays local/in-process and needs no wire format.
+
+## Safety invariants (do not regress)
+
+- **Advisory-only.** Notifications carry `autonomousAction: false` and, for Class A
+ (actuation/lock-capable) devices, `requiresHuman: true`. The consumer never
+ calls the LLM and never actuates hardware.
+- **Tasks are reviewable, not executable.** Peripheral tasks start at
+ `pending-review`, are always `requiresHuman: true` / `autonomousAction: false`,
+ and have no execution path. Approving one still goes through `PAL.execute()`.
+- **Best-effort + non-blocking.** Every callback is wrapped; a bad reading or a
+ consumer error can never crash the monitor or the orchestrator.
+- **Bounded.** The Supervisor inbox is capped so alerts cannot overwhelm the
+ workflow.
+- **Gated actuation only.** The only path to a physical action is
+ `PAL.execute()` → DCP → class gate → pending/confirm. The alert loop is purely
+ observational.
+- **Power fails safe.** Over-budget actions are blocked, never allowed through.
+- **Persistence is corruption-tolerant + flag-gated.** A bad store file loads as
+ empty; no disk is touched unless peripherals are enabled.
+- **Concurrency-safe.** All `~/.liku/*.json` writes are atomic (tmp + rename)
+ under a best-effort advisory lock; locking never blocks operation.
+- **HIL is isolated.** Simulation is off by default and never touches real
+ hardware; the safety chain is identical in HIL and real modes.
+- **Escalation never escalates authority.** Channels are advisory SINKS;
+ auto-acknowledge and flapping-cooldown NEVER apply to Class A / `requiresHuman`
+ / critical items; no escalation control opens an autonomous actuation path.
+- **New drivers inherit the full safety chain.** Zigbee (like BLE/MQTT/serial)
+ emits signed DCP envelopes and is gated by DCP → class gate → pending/confirm;
+ Class A stays confirm-gated even in HIL.
+- **Real transport never bypasses the gate.** The real BLE write path only fires
+ AFTER `isPhysicalActionAllowed`; a live connection does not weaken Class A —
+ the write happens only once a human has confirmed.
+- **Power schedules only restrict.** A per-device schedule can lower a device's
+ allowed draw (or force it off outside its window) but can NEVER grant power or
+ bypass the class gate; with no schedules configured it has zero effect.
+- **Power history is pure observation.** Recording a power sample never actuates
+ anything; the log is bounded, atomic, flag-gated, and corruption-tolerant.
+- **Anomaly detection only observes.** Spike/sustained/over-budget detection
+ reads history and emits an advisory `power-anomaly` event; it never actuates,
+ never gates, and is flag-gated + additive (quiet with insufficient history).
+- **Advanced schedules only restrict.** Per-day + sunrise/sunset rules can only
+ lower a device's allowed draw (or force it off outside its window); a device
+ with no rule governing "now" is unrestricted, and schedules never actuate.
+- **Anomaly-driven tasks stay advisory.** A `power-anomaly` becomes a bounded,
+ `pending-review`, `autonomousAction:false` task modelled on a read-only Class C
+ synthetic device; it never actuates, and consumer-level dedup/cooldown prevents
+ flapping-signal spam.
+- **New drivers inherit the safety chain.** The ROS2 bridge (like BLE/Zigbee)
+ publishes signed DCP envelopes and is gated by DCP → class gate →
+ pending/confirm; Class A stays confirm-gated even in HIL.
+- **Matter stays gated.** The Matter bridge invokes cluster commands only after
+ `isPhysicalActionAllowed`; a live fabric never weakens Class A (invoke happens
+ only post-confirmation), and discover/gating work without the matter.js lib.
+- **Anomaly tiers only re-prioritise.** Higher tiers (e.g. over-budget→critical)
+ raise visibility/priority and shorten the dedup window but never actuate, never
+ bypass the human gate, and keep `autonomousAction:false` on a read-only Class C
+ synthetic device.
+- **Pairing never actuates.** Commissioning/pairing is transport bookkeeping with
+ bounded retry + backoff; a paired device still flows through DCP → class gate →
+ pending/confirm, and HIL pairing is virtual (no real fabric/adapter touched).
+- **Tokens are lifecycle-bound + revocable.** A capability token is issued on
+ pair, rotated on re-pair and revoked on unpair; a REMOTE driver refuses to send
+ for a revoked device. Token operations never actuate and HIL stays virtual.
+- **Auto-schedules never self-activate.** Recurring anomalies only *propose*
+ schedules (`autonomousAction:false`); a proposal is enforced ONLY after an
+ explicit human `confirm`, and even then can only ever restrict power.
+- **Forecasts + attribution only observe.** Per-hour forecasting and per-device
+ attribution read history to sharpen advisory suggestions/warnings; they never
+ actuate and never bypass the human gate.
+- **Token rotation preserves safety.** Scheduled rotation + grace window keep
+ in-flight commands valid without weakening revocation (a revoked device rejects
+ every generation); rotation never actuates and HIL stays virtual.
+- **Cognitive budget unchanged.** `sensor.*`/`hardware.*.alert` facts are
+ evidence-excluded from the default fragment; the default prompt stays
+ byte-identical.
diff --git a/docs/RUNTIME_REGRESSION_WORKFLOW.md b/docs/RUNTIME_REGRESSION_WORKFLOW.md
new file mode 100644
index 00000000..f460842c
--- /dev/null
+++ b/docs/RUNTIME_REGRESSION_WORKFLOW.md
@@ -0,0 +1,217 @@
+# Runtime Regression Workflow
+
+## Goal
+
+Turn a real `liku chat` runtime finding into a checked-in, repeatable regression with as little friction as possible.
+
+Important: if a live run disagrees with a green synthetic suite, the live run wins. Convert the live behavior into a transcript or runtime-proof fixture instead of assuming the suite is the deeper truth.
+
+This first N5 slice intentionally reuses the existing inline-proof transcript evaluator instead of introducing a second transcript engine. It now also supports proof-aware runtime trace fixtures. The workflow is:
+
+1. capture a runtime transcript or reuse an inline-proof `.log`
+2. sanitize it down to the smallest useful snippet or runtime trace
+3. generate a transcript or runtime-proof fixture skeleton
+4. tighten the generated expectations
+5. run transcript regressions and the nearest focused behavior test
+6. commit the fixture and the behavioral fix together
+
+## Inputs supported in this slice
+
+- plaintext `liku chat` transcripts
+- inline-proof logs from `~/.liku/traces/chat-inline-proof/*.log`
+- runtime action/proof JSONL traces from `~/.liku/traces/*.jsonl`
+- pasted transcript text over stdin
+
+Out of scope for this first slice:
+
+- automatic replay of arbitrary telemetry or agent-trace files beyond the runtime proof trace shape
+- full transcript-to-test generation without manual expectation review
+- broad redaction/policy redesign for runtime capture
+
+## Fixture format
+
+Checked-in transcript fixtures live under:
+
+- `scripts/fixtures/transcripts/`
+
+The fixture bundle format is JSON with multiple named cases at the top level. Each case can include:
+
+- `description`
+- `source`
+ - `capturedAt`
+ - `tracePath` when relevant
+ - observed provider/model metadata when available
+- `transcriptLines`
+- optional derived fields such as `prompts`, `assistantTurns`, and `observedHeaders`
+- `notes`
+- `expectations`
+
+Proof-aware runtime cases may additionally include:
+
+- `traceMeta`
+- `actions`
+- `proofExpectations`
+
+Proof expectations can assert stable runtime-proof invariants such as:
+
+- `minProofLevel`
+- `status`
+- `classification`
+- `verifyKind`
+- `targetId`
+- `requiredCheckKind`
+- `requiredCheckStatus`
+
+Expectation semantics intentionally mirror the inline-proof harness:
+
+- `scope: transcript` for whole-transcript checks
+- `turn` for assistant-turn-specific checks
+- `include`
+- `exclude`
+- `count`
+
+Pattern entries are stored as JSON regex specs:
+
+- `{ "regex": "Provider:\\s+copilot", "flags": "i" }`
+
+## Commands
+
+List transcript fixtures:
+
+- `npm run regression:transcripts -- --list`
+
+Run all transcript fixtures:
+
+- `npm run regression:transcripts`
+
+Run a single transcript fixture:
+
+- `npm run regression:transcripts -- --fixture repo-boundary-clarification-runtime`
+
+Generate a fixture skeleton from a transcript file:
+
+- `npm run regression:extract -- --transcript-file C:\path\to\runtime.log --fixture-name repo-boundary-clarification`
+
+Print a fixture skeleton without writing a file:
+
+- `npm run regression:extract -- --transcript-file C:\path\to\runtime.log --stdout-only`
+
+Generate a runtime-proof fixture from a JSONL runtime trace:
+
+- `node scripts/extract-runtime-trace-regression.js --trace-file %USERPROFILE%\.liku\traces\runtime-123.jsonl --fixture-name runtime-proof-panel-open`
+
+Print a runtime-proof fixture without writing it:
+
+- `node scripts/extract-runtime-trace-regression.js --trace-file %USERPROFILE%\.liku\traces\runtime-123.jsonl --fixture-name runtime-proof-panel-open --print`
+
+Capture the canonical persisted runtime-proof traces and refresh the checked-in runtime fixture bundle:
+
+- `npm run regression:runtime:fixtures`
+
+Capture only one canonical runtime-proof fixture:
+
+- `npm run regression:runtime:fixtures -- --fixture runtime-proof-timeframe-updated`
+
+## Recommended loop
+
+### 1. Capture the failure
+
+Prefer one of these sources:
+
+- a fresh `liku chat` transcript
+- an inline-proof log already saved under `~/.liku/traces/chat-inline-proof/`
+- a runtime action/proof trace JSONL session under `~/.liku/traces/`
+- a small hand-curated transcript excerpt from a runtime session
+
+Keep only the lines that prove the invariant you care about. Smaller fixtures are easier to review and less brittle.
+
+### 2. Generate a fixture skeleton
+
+Run `regression:extract` against the sanitized transcript, or `extract-runtime-trace-regression.js` against a runtime trace JSONL.
+
+The helpers derive:
+
+- a fixture name
+- prompts
+- assistant turns
+- observed provider/model headers
+- placeholder expectations
+- proof-aware action summaries and `proofExpectations` when the input is a runtime trace, including `verifyKind` and preferred proof checks when present
+
+Treat those expectations as a draft, not finished truth.
+
+### 3. Tighten expectations manually
+
+Before checking in the fixture:
+
+- remove incidental wording matches
+- keep only invariants that prove the bug fix or safety behavior
+- add `exclude` or `count` checks when they make the regression sharper
+
+Good transcript fixtures assert the behavior that matters, not every line in the transcript.
+
+### 4. Run the transcript regression and the nearest focused seam test
+
+Minimum validation:
+
+- `npm run regression:transcripts`
+- `node scripts/test-transcript-regression-pipeline.js`
+
+If the new fixture came from runtime action/proof traces, also run:
+
+- `node scripts/test-ai-service-proof-trace.js`
+
+Then run the nearest behavioral regression for the feature you touched, for example:
+
+- `node scripts/test-windows-observation-flow.js`
+- `node scripts/test-chat-actionability.js`
+- `node scripts/test-bug-fixes.js`
+
+### 5. Commit the fixture with the fix
+
+The preferred N5 habit is:
+
+- runtime finding
+- transcript fixture
+- focused code/test fix
+- commit
+
+That keeps new hardening work grounded in observed runtime behavior instead of reconstructed memory.
+
+## Practical guidelines
+
+1. Prefer sanitized transcript snippets over full raw dumps.
+2. Use one fixture bundle with several named cases when the domain is closely related.
+3. Keep transcript fixtures deterministic and stable enough to survive harmless wording drift.
+4. If a transcript fixture starts growing broad, add or retain a narrower behavior test alongside it.
+5. For runtime-proof fixtures, prefer asserting stable proof invariants such as `minProofLevel`, `status`, `classification`, `targetId`, or required check kinds instead of mirroring every trace field.
+
+## Live-vs-Suite Escalation Rule
+
+Use this escalation rule for TradingView and other focus-sensitive desktop automations:
+
+- **green suite + bad live run** -> investigate the live run first
+- **unexpected VS Code UI during chat execution** -> suspect misrouted keyboard input or foreground drift
+- **highlighted-but-not-cleared search field** -> treat as a real bug or missing proof, not a cosmetic state
+
+Common examples that should be captured as runtime regressions:
+
+- VS Code Accessibility View opens during a real `liku chat` command
+- TradingView quick-search keeps the previous query highlighted and the workflow stops early
+- the action stream halts around action 2 even though the target PID exists
+- the app process is present but focus-lock cannot prove the correct window/input surface
+
+For TradingView runtime changes, start with the opt-in dry-run lane:
+
+```powershell
+npm run smoke:tradingview-live -- --dry-run
+```
+
+When a real TradingView window is available, live smoke runs write summaries and runtime traces under `artifacts\live-validation\`. Attach those artifacts, or explain why live validation was not applicable, on PRs that change TradingView foreground routing, Pine Editor workflows, chart state workflows, observation checkpoints, or safety/resume behavior.
+
+When these happen:
+
+1. save the transcript or runtime trace
+2. write down the real foreground app/window sequence if known
+3. add the smallest fixture that proves the failure mode
+4. only then generalize the fix into synthetic characterization coverage
diff --git a/docs/TRADINGVIEW_AUTOMATION_MODERNIZATION_BACKLOG.md b/docs/TRADINGVIEW_AUTOMATION_MODERNIZATION_BACKLOG.md
new file mode 100644
index 00000000..6f29cd92
--- /dev/null
+++ b/docs/TRADINGVIEW_AUTOMATION_MODERNIZATION_BACKLOG.md
@@ -0,0 +1,479 @@
+# TradingView Automation Modernization Backlog
+
+> Repo-specific implementation backlog derived from the May 2026 TradingView workflow optimization PDF and grounded in the current `main` branch.
+>
+> **Concrete implementation stance:**
+> Preserve behavior at the API layer, modernize the execution layer.
+>
+> **Strategic direction:**
+> Steer the project toward an industry-standard automation-driver architecture by evolving the existing Windows UIA host, preserving current facades/contracts, and prioritizing host unification plus watcher/focus parity before broader TradingView batching.
+
+## Goal
+
+Modernize TradingView automation so the runtime behaves like a persistent desktop automation driver instead of a collection of high-latency per-action PowerShell invocations, while preserving:
+
+- existing public automation contracts in `src/main/system-automation.js`
+- current AI-service proof/verification flows in `src/main/ai-service.js`
+- current watcher state shape consumed by context builders
+- existing TradingView safety rails, confirmation boundaries, and recovery behavior
+
+## Current source of truth on `main`
+
+### Existing host / automation seams that already exist
+- `src/native/windows-uia-dotnet/Program.cs`
+- `src/native/windows-uia-dotnet/WindowsUIA.csproj`
+- `src/main/ui-automation/core/uia-host.js`
+- `src/main/ui-watcher.js`
+- `src/main/system-automation.js`
+- `src/main/ai-service.js`
+- `src/main/ai-service/observation-checkpoints.js`
+- `src/main/tradingview/runtime/recovery.js`
+- `src/main/tradingview/pine-workflows.js`
+
+### Important repo-specific interpretation
+- Do **not** implement the PDF literally if it points at stale paths such as `src/dotnet/AutomationHost/` or `src/main/automation-host-client.js`.
+- Do **not** create a second competing automation daemon if the existing `WindowsUIA.exe` seam can be extended.
+- Do **not** bypass `ai-service` verification/recovery logic to chase latency wins.
+- Do **not** move to generic screenshot-only computer-use loops when Windows UIA semantics already exist and current TradingView safety logic depends on them.
+
+## Scope
+
+### In scope
+- persistent host unification for UIA, window, focus, and clipboard operations
+- watcher/event parity and foreground-lock hardening
+- TradingView quick-search and Pine Editor read/write reliability
+- bounded micro-batching after correctness improves
+- rollout flags, parity harnesses, and live proof workflow
+
+### Out of scope
+- big-bang replacement of all legacy PowerShell paths
+- removal of existing fallbacks before parity proof
+- screenshot-only automation as the primary interaction layer
+- batching across focus boundaries, confirmation boundaries, or high-risk actions
+- weakening TradingView safety rails to make smoke tests pass faster
+
+## Guardrails
+
+1. **Preserve signatures and return shapes.**
+ - `src/main/system-automation.js` remains the compatibility facade.
+ - `src/main/ui-watcher.js` state shape remains stable for current consumers.
+
+2. **Keep fallback behavior explicit.**
+ - New host-backed paths must sit behind feature flags and preserve legacy fallbacks until proven.
+
+3. **Use repo-style feature flags.**
+ - Prefer `LIKU_*` environment variables, not generic `USE_*` names.
+
+4. **Treat clipboard as bounded fallback infrastructure, not the primary state channel.**
+ - Use semantic UIA / TextPattern / ValuePattern first whenever possible.
+
+5. **Treat stale terminals and VS Code notifications as untrusted foregrounds.**
+ - A foreground steal by Code/terminal surfaces should block or refocus; it should not silently continue a TradingView workflow.
+
+6. **Batch only after correctness.**
+ - Fast wrong actions are worse than slow safe actions.
+
+7. **Keep generated artifacts out of commits.**
+ - Live smoke artifacts, runtime traces, and hook artifacts remain uncommitted.
+
+## Proposed rollout flags
+
+These names are proposed to match existing repo conventions and should default to off until proof is recorded.
+
+- `LIKU_USE_AUTOMATION_HOST=1`
+ - routes supported low-level automation calls through the persistent host first
+- `LIKU_USE_WATCHER_V2=1`
+ - enables the event-first watcher path or adapter path once parity is proven
+- `LIKU_USE_ACTION_BATCHING=1`
+ - enables narrowly scoped same-surface compound actions
+- `LIKU_USE_TRADINGVIEW_ELEMENT_MAP=1`
+ - enables a TradingView-specific semantic element registry once it exists
+
+## Backlog overview
+
+| Tranche | Outcome | Primary targets | Exit gate |
+| --- | --- | --- | --- |
+| 0 | Baseline parity inventory and latency proof | `src/main/system-automation.js`, `src/main/ui-watcher.js`, `scripts/live-tradingview-smoke.js` | **Closed**: current costs, process pressure, and helper parity are measurable |
+| 1 | Existing host becomes the real execution substrate for low-level ops | `src/native/windows-uia-dotnet/Program.cs`, `src/main/ui-automation/core/uia-host.js`, `src/main/system-automation.js` | **Closed**: host-backed window/focus/clipboard ops are flag-gated with legacy fallbacks |
+| 2 | Watcher/focus parity becomes event-first and foreground-safe | `src/main/ui-watcher.js`, `src/main/ai-service.js`, `src/main/ai-service/observation-checkpoints.js` | **Closed**: watcher shape remains compatible and focus steals fail/refocus boundedly |
+| 3 | TradingView quick-search and Pine flows become semantic-first and bounded | `src/main/tradingview/runtime/recovery.js`, `src/main/tradingview/pine-workflows.js`, `src/main/system-automation.js` | **Closed**: quick-search/Pine paths prefer semantic proof and bounded fallback |
+| 4 | Same-surface sequencing reduces fragile round trips without hiding proof | `src/main/ai-service.js`, `src/main/system-automation.js`, host wrapper | **Closed**: conservative same-surface microflows preserve per-step proof metadata |
+| 5 | Gradual rollout and cleanup | docs, flags, telemetry, fallback tracking | Host-backed path runs stably before any legacy removal |
+
+---
+
+## Tranche 0 — Baseline parity inventory and proof harness
+
+### Objective
+Build a measurement and parity baseline before changing execution plumbing.
+
+### File-level targets
+- `src/main/system-automation.js`
+- `src/main/ui-watcher.js`
+- `src/main/tradingview/runtime/recovery.js`
+- `scripts/live-tradingview-smoke.js`
+- new future tests/scripts under `scripts/`
+
+### Tasks
+- [x] Inventory every low-level function in `src/main/system-automation.js` that still spawns PowerShell.
+- [x] Capture golden input/output pairs for:
+ - `focusWindow(...)`
+ - foreground window info
+ - `pressKey(...)`
+ - `typeText(...)`
+ - click / double click / drag / scroll
+ - any clipboard read/write helpers added during current recovery work
+- [x] Add instrumentation for per-action latency and fallback counts.
+- [x] Extend live smoke summaries to record:
+ - foreground steals
+ - clipboard touch count
+ - recovery path chosen
+ - time spent between visible action steps
+- [x] Add a simple “PowerShell process pressure” baseline note to the docs.
+
+### Suggested implementation files
+- new: `scripts/test-system-automation-parity.js`
+- new: `scripts/profile-tradingview-latency.js`
+- update: `scripts/live-tradingview-smoke.js`
+
+### Acceptance criteria
+- We can compare legacy and host-backed implementations with the same inputs.
+- Live smoke artifacts show where latency is actually being spent.
+- The team can name the top 5 slowest automation steps with evidence.
+
+### Current status
+
+Tranche 0 is implemented and closed for the current helper surface.
+
+Completed:
+- static PowerShell inventory generation
+- deterministic low-level helper parity fixtures
+- live-smoke latency/fallback profiling
+- structured failure bundles
+- baseline documentation
+
+Closure evidence:
+- `scripts/test-system-automation-parity.js`
+- `scripts/profile-tradingview-latency.js`
+- `scripts/live-tradingview-smoke.js`
+- `docs/TRANCHE0_POWERSHELL_PROCESS_PRESSURE_BASELINE.md`
+
+### Why first
+This prevents speculative optimization and gives us a safe way to prove that host migration preserves behavior.
+
+---
+
+## Tranche 1 — Evolve the existing UIA host into the primary automation driver
+
+### Objective
+Use the existing `WindowsUIA.exe` seam as the single long-lived automation driver instead of creating a second competing host.
+
+### File-level targets
+- `src/native/windows-uia-dotnet/Program.cs`
+- `src/native/windows-uia-dotnet/WindowsUIA.csproj`
+- `src/main/ui-automation/core/uia-host.js`
+- `src/main/system-automation.js`
+
+### Tasks
+- [x] Extend the host with **window operations**:
+ - foreground window info
+ - window enumeration / resolve helpers
+ - reliable focus/bring-to-front behavior
+- [x] Extend the host with **clipboard operations**:
+ - `getText`
+ - `setText`
+ - `save`
+ - `restore`
+ - optional monitor/wait support
+- [x] Introduce request correlation IDs and queued request handling in `uia-host.js`.
+- [x] Replace fragile single pending-request behavior with correlated queued dispatch in the Node wrapper without weakening error reporting.
+- [x] Route selected `system-automation.js` calls through the host under `LIKU_USE_AUTOMATION_HOST=1`.
+- [x] Preserve current legacy PowerShell paths in explicit fallback helpers.
+
+### Current status
+
+Tranche 1 is implemented and closed. The source-of-truth host seam now supports foreground/window inspection, focus/restore, process lookup, clipboard text/state operations, request IDs, queued dispatch, and event messages. `src/main/system-automation.js` routes selected calls through the host only when `LIKU_USE_AUTOMATION_HOST=1`, and retains legacy PowerShell fallbacks when the flag is disabled or the host path cannot satisfy the call.
+
+Closure evidence:
+- `src/native/windows-uia-dotnet/Program.cs`
+- `src/main/ui-automation/core/uia-host.js`
+- `src/main/system-automation.js`
+- `scripts/test-system-automation-host-bridge.js`
+- `scripts/test-uia-host-request-queue.js`
+- `scripts/test-host-native-clipboard-state.js`
+
+### Important repo-specific notes
+- Keep `src/main/system-automation.js` as the public facade.
+- Do **not** rename the current host seam until rollout proves parity.
+- Preserve current TradingView-specific input behavior, especially shortcut routing that already distinguishes SendInput vs SendKeys paths.
+
+### Immediate priorities inside this tranche
+1. Host-backed clipboard save/restore
+2. Host-backed foreground / focus queries
+3. Host-backed window focus parity
+4. Only then consider host-backed input primitives
+
+### Acceptance criteria
+- Host-backed window/focus operations produce the same result shape as legacy calls.
+- Clipboard operations can preserve mixed clipboard state across TradingView workflows.
+- Recovery code in `src/main/tradingview/runtime/recovery.js` can stop falling back to PowerShell clipboard scripts when the host path is enabled.
+- With `LIKU_USE_AUTOMATION_HOST=0`, behavior remains unchanged.
+
+### Validation proof
+- `node scripts/test-ai-service-contract.js`
+- `node scripts/test-windows-observation-flow.js`
+- `node scripts/test-tradingview-runtime-recovery.js`
+- `npm run smoke:tradingview-live -- --scenarios focus,pine-editor`
+
+---
+
+## Tranche 2 — Watcher parity and focus-lock hardening
+
+### Objective
+Make the watcher and focus verification event-first, faster, and safer against foreground steals.
+
+### File-level targets
+- `src/main/ui-watcher.js`
+- optional new: `src/main/ui-watcher-adapter.js`
+- `src/main/ai-service.js`
+- `src/main/ai-service/observation-checkpoints.js`
+- `src/native/windows-uia-dotnet/Program.cs`
+
+### Tasks
+- [x] Expand host event payloads so they can support current watcher consumers more faithfully.
+- [x] Preserve active-window fields used by AI context and verification:
+ - `processName`
+ - `ownerHwnd`
+ - `isTopmost`
+ - `isToolWindow`
+ - `isMinimized`
+ - `isMaximized`
+ - `windowKind`
+- [x] Shift watcher behavior to **event-first + heartbeat fallback** instead of heavy poll-first logic.
+- [x] Add explicit classification for foreground steals caused by:
+ - VS Code terminal notifications
+ - stale background terminal exits
+ - non-TradingView popups
+- [x] Tighten focus-lock rules so untrusted foregrounds block TradingView input/readback instead of encouraging loops.
+- [x] Keep the watcher in place; no adapter layer was needed for the current parity surface.
+
+### Current status
+
+Tranche 2 is implemented and closed. `src/main/ui-watcher.js` keeps the existing consumer shape while exposing richer active-window topology, event freshness, and heartbeat fallback behavior. `src/main/ai-service.js` and `src/main/ai-service/observation-checkpoints.js` now use watcher freshness and foreground proof to refocus or fail closed before keyboard input/readback can route to VS Code, stale terminals, or non-TradingView foregrounds.
+
+Closure evidence:
+- `src/main/ui-watcher.js`
+- `src/main/ai-service.js`
+- `src/main/ai-service/observation-checkpoints.js`
+- `scripts/test-windows-observation-flow.js`
+- `scripts/test-observation-checkpoint-host-proof.js`
+- `scripts/test-live-tradingview-smoke-window-selection.js`
+
+### Acceptance criteria
+- Watcher consumers continue to receive the same shape they expect.
+- Event mode reduces blind polling pressure without making state staler.
+- TradingView workflows detect VS Code/terminal focus steals quickly and refocus or fail boundedly.
+- Verification loops stop burning time on foreground states that are clearly not trusted.
+
+### Validation proof
+- `node scripts/test-windows-observation-flow.js`
+- `node scripts/test-live-tradingview-smoke-window-selection.js`
+- runtime-trace review from live smoke artifacts
+
+---
+
+## Tranche 3 — TradingView semantic-first recovery, readback, and write paths
+
+### Objective
+Fix the current user-visible pain points in TradingView without weakening existing safety rails.
+
+### File-level targets
+- `src/main/tradingview/runtime/recovery.js`
+- `src/main/tradingview/pine-workflows.js`
+- `src/main/system-automation.js`
+- `src/main/ai-service/observation-checkpoints.js`
+- optional new: `src/main/tradingview/element-map.js`
+
+### Tasks
+- [x] Convert quick-search handling to a semantic-first path:
+ - semantic focus/input if available
+ - bounded clipboard fallback only when semantic input is unavailable
+- [x] Make Pine readback **TextPattern-first** and clipboard fallback second.
+- [x] Make Pine write/update paths explicit and bounded:
+ - semantic set/focus when available
+ - clipboard save/restore-backed paste fallback when necessary
+ - immediate bounded readback/verification after write
+- [x] Capture high-value TradingView surfaces in the existing TradingView registries/contracts:
+ - quick-search input
+ - Pine editor anchors
+ - Pine logs/profiler/version history anchors
+ - symbol/timeframe surfaces
+- [x] Reduce “wait then sample again” loops where semantic event-backed confirmation is possible.
+- [x] Prevent stale terminal notifications from being mistaken for valid readback surfaces.
+
+### Current status
+
+Tranche 3 is implemented and closed. Quick-search text replacement now prefers host/UIA `ValuePattern` write plus readback before falling back to keyboard typing. Pine Editor readback and authoring paths are bounded by foreground/surface proof, TextPattern/ValuePattern reads, clipboard save/restore fallbacks, and immediate lifecycle/readback verification. The high-value TradingView surfaces are represented through the existing tool facade, shortcut profile, verification, system contract, and observation-provider registries instead of a new competing element-map file.
+
+Closure evidence:
+- `src/main/system-automation.js`
+- `src/main/tradingview/runtime/recovery.js`
+- `src/main/tradingview/pine-workflows.js`
+- `src/main/tools/tradingview-tool.js`
+- `scripts/test-system-automation-quick-search.js`
+- `scripts/test-tradingview-pine-workflows.js`
+- `scripts/test-tradingview-pine-data-workflows.js`
+- `scripts/test-tradingview-runtime-recovery.js`
+
+### Pain points this tranche must directly address
+- text not landing reliably in the TradingView quick-search input
+- Pine Editor paste/write not being atomic or trustworthy enough
+- minutes-long delays between visible actions
+- TradingView lockup while the runtime loops trying to re-verify state
+- stale terminals or notifications stealing focus and poisoning readback
+
+### Acceptance criteria
+- Live Pine opener/readback no longer depends on repeated clipboard sentinel loops as the primary path.
+- Pine readback succeeds semantically when the editor is visible and only falls back to clipboard when necessary.
+- Search-input replace flows are bounded and measurably faster.
+- The runtime fails or refocuses quickly when Code/terminal steals foreground, instead of spiraling through long verification loops.
+
+### Validation proof
+- `node scripts/test-tradingview-pine-workflows.js`
+- `node scripts/test-tradingview-pine-data-workflows.js`
+- `node scripts/test-tradingview-runtime-recovery.js`
+- `node scripts/test-windows-observation-flow.js`
+- `npm run smoke:tradingview-live -- --scenarios focus,pine-editor`
+
+---
+
+## Tranche 4 — Narrow action batching and sequencer support
+
+### Objective
+Reduce fragile round trips only after correctness and state trust improve. The landed scope is intentionally conservative: keep sequencing inside the existing action executor and host wrapper, collapse only same-surface microflows where semantic proof is available, and preserve per-step metadata instead of introducing a broad batch daemon.
+
+### File-level targets
+- `src/main/ui-automation/core/uia-host.js`
+- `src/native/windows-uia-dotnet/Program.cs`
+- optional new: `src/main/automation-sequencer.js`
+- `src/main/system-automation.js`
+- `src/main/tradingview/runtime/recovery.js`
+
+### Tasks
+- [x] Add support for narrowly scoped same-surface microflows:
+ - focus/readiness preflight through focus-lock and active-input guards
+ - quick-search replacement through semantic `setValue` plus `getText` readback
+ - Pine readback preparation through bounded TextPattern/ValuePattern and fallback lanes
+ - semantic Pine click proof through post-invoke surface verification
+- [x] Preserve per-step proof metadata even when a microflow uses a collapsed semantic host path.
+- [x] Restrict collapsed behavior to same-surface microflows.
+- [x] Do **not** batch across:
+ - focus boundary changes
+ - confirmation boundaries
+ - high-risk actions
+ - uncertain foreground states
+- [x] Keep legacy and non-batched paths available as fallback.
+
+### Current status
+
+Tranche 4 is implemented and closed for the safe current scope. The repo does **not** add a second automation sequencer daemon or a broad host-side action batcher. Instead, `src/main/ai-service.js` preserves intelligible action/checkpoint proof, `src/main/system-automation.js` collapses trusted same-surface semantic operations where safe, and risky/focus-changing/high-uncertainty paths remain explicit non-batched steps with fallback behavior.
+
+### Acceptance criteria
+- Same-surface microflows require fewer fragile keyboard/process round trips where semantic host proof exists.
+- Error reporting still identifies which sub-step failed.
+- Existing AI-service proof and checkpoint metadata remain intelligible.
+
+### Validation proof
+- `scripts/test-system-automation-quick-search.js`
+- `scripts/test-windows-observation-flow.js`
+- `scripts/test-observation-checkpoint-host-proof.js`
+- `scripts/test-decision-trace.js`
+- live smoke timing comparisons from `scripts/live-tradingview-smoke.js` manifests
+
+---
+
+## Tranche 5 — Controlled rollout, telemetry, and cleanup
+
+### Objective
+Enable the modernized path gradually and remove legacy only after stable proof.
+
+### File-level targets
+- docs and runbooks in `docs/`
+- feature flag handling in runtime/config seams
+- telemetry and runtime trace summaries
+
+### Tasks
+- [ ] Record fallback-trigger counts for every host-backed action family.
+- [ ] Roll out by capability, not by subsystem all at once.
+- [ ] Keep legacy paths until the host-backed path has stable live evidence.
+- [ ] Update validation docs and smoke runbooks as each tranche lands.
+- [ ] Archive old seams only after sustained success, not after first green run.
+
+### Acceptance criteria
+- Flags can be toggled independently.
+- Live smoke and focused regressions stay green across both legacy and host-backed modes.
+- Fallbacks become rare enough to justify pruning only after extended proof.
+
+---
+
+## Suggested first three implementation slices
+
+These implementation slices have landed as part of Tranches 1-3; Tranche 5 owns controlled rollout and cleanup.
+
+### Slice A — Host-backed clipboard safety and foreground APIs
+**Files:**
+- `src/native/windows-uia-dotnet/Program.cs`
+- `src/main/ui-automation/core/uia-host.js`
+- `src/main/system-automation.js`
+- `src/main/tradingview/runtime/recovery.js`
+
+**Outcome:**
+- clipboard save/restore and foreground/focus queries move off PowerShell first
+
+### Slice B — Foreground-steal classification and watcher parity hardening
+**Files:**
+- `src/main/ui-watcher.js`
+- `src/main/ai-service.js`
+- `src/main/ai-service/observation-checkpoints.js`
+
+**Outcome:**
+- Code/terminal notifications stop poisoning TradingView loops and readbacks
+
+### Slice C — Pine semantic readback/write modernization
+**Files:**
+- `src/main/system-automation.js`
+- `src/main/tradingview/runtime/recovery.js`
+- `src/main/tradingview/pine-workflows.js`
+
+**Outcome:**
+- Pine and quick-search flows become semantic-first and bounded, with clipboard fallback preserved safely
+
+## Do-not-regress checklist
+
+Before enabling any tranche by default, re-verify:
+
+- [ ] `system-automation.js` exports and return shapes remain compatible
+- [ ] watcher state shape stays compatible with current AI context consumers
+- [ ] TradingView focus/safety boundaries remain explicit
+- [ ] clipboard contents are restored after any fallback path
+- [ ] stale terminals and VS Code notifications are rejected as trusted TradingView foregrounds
+- [ ] runtime trace artifacts still explain why a workflow passed, recovered, or failed
+- [ ] live smoke artifacts under `artifacts/live-validation/` still tell the story of the scenario clearly
+
+Current closure note: Tranches 0-4 are complete, but they remain intentionally guarded. Tranche 5 is where fallback-trigger trending, default-on rollout decisions, and any legacy pruning should happen.
+
+## Suggested validation cadence per slice
+
+1. Focused deterministic tests first
+ - `node scripts/test-tradingview-runtime-recovery.js`
+ - `node scripts/test-windows-observation-flow.js`
+2. Related focused module tests
+ - `node scripts/test-tradingview-pine-workflows.js`
+ - `node scripts/test-tradingview-pine-data-workflows.js`
+ - `node scripts/test-live-tradingview-smoke-window-selection.js`
+3. Then live proof only when runtime behavior changes
+ - `npm run smoke:tradingview-live -- --scenarios focus,pine-editor`
+
+## Recommendation in one sentence
+
+Modernize this repo by converging its existing host, watcher, and TradingView verification seams into a persistent automation-driver architecture, not by replacing them with a generic computer-use loop or by weakening the current safety/proof model.
diff --git a/docs/TRADINGVIEW_VALIDATION_RUNBOOK.md b/docs/TRADINGVIEW_VALIDATION_RUNBOOK.md
new file mode 100644
index 00000000..49a0a039
--- /dev/null
+++ b/docs/TRADINGVIEW_VALIDATION_RUNBOOK.md
@@ -0,0 +1,180 @@
+# TradingView validation runbook
+
+This runbook keeps delegated TradingView work grounded in the current codebase instead of stale branch state or blueprint assumptions.
+
+For the repo-specific automation-driver modernization backlog grounded in the current `main`-branch seams, see [TRADINGVIEW_AUTOMATION_MODERNIZATION_BACKLOG.md](./TRADINGVIEW_AUTOMATION_MODERNIZATION_BACKLOG.md).
+
+## Current source of truth
+
+The TradingView modularization slices represented by planning issues #10-#16 are complete and merged through implementation PRs #19-#25. Those issues are closed; future changes should use implementation PRs directly unless a separate planning issue is explicitly needed.
+
+The automation-driver modernization backlog is closed through Tranche 4 on `main`: Tranche 0 parity/profiling, Tranche 1 host-backed window/focus/clipboard operations, Tranche 2 watcher/focus-lock hardening, Tranche 3 semantic quick-search/Pine paths, and Tranche 4 conservative same-surface sequencing/proof support. Tranche 5 remains open for controlled rollout, fallback trending, and legacy cleanup only after stable proof.
+
+The canonical TradingView facade is `src\main\tools\tradingview-tool.js`. It owns registration for:
+
+- rewrite handlers through `src\main\ai-service\rewrite-registry.js`
+- risk assessors through `src\main\ai-service\risk-registry.js`
+- Pine authoring system contracts through `src\main\ai-service\system-contract-registry.js`
+- observation/checkpoint providers through `src\main\ai-service\observation-provider-registry.js`
+- Pine resume lifecycle hooks through `src\main\ai-service\lifecycle-hooks.js`
+
+Tool rewrite/risk registries are default-on. Use `LIKU_USE_TOOL_REGISTRY_REWRITES=0` or `LIKU_USE_TOOL_REGISTRY_RISKS=0` only as temporary legacy-path escape hatches during compatibility checks.
+
+## Source-of-truth workflow
+
+1. Start from the GitHub branch or PR named by the task.
+2. Cite the current owning files/functions before editing.
+3. Check whether related implementation already exists on another branch, especially PR #7 / `feature/automation-host-migration`, before copying assumptions into `main`.
+4. Keep generated outputs and local artifacts out of commits.
+5. Implement locally, run focused validation first, commit/push passing changes, open the implementation PR, and merge/close it promptly once validated.
+
+For GPT-5.2 coding/cloud-agent work, choose GPT-5.2 for the parent/session model. Do not rely only on subagent `model:` frontmatter. For Pine create/save live validation or operator-guidance runs that explicitly consult model guidance, prefer `gpt-4o` only for that validation lane instead of changing broader repo defaults.
+
+## Validation layers
+
+Use the narrowest deterministic test first, then broaden only when the touched behavior crosses runtime boundaries.
+
+| Layer | Use for | Examples |
+| --- | --- | --- |
+| Focused Node tests | Module contracts, rewrite parity, safety rules | `node scripts\test-ai-service-contract.js`, `node scripts\test-system-automation-parity.js`, `node scripts\test-tradingview-paper-workflows.js` |
+| AI/runtime bundle | Cross-module AI-service behavior | `npm run test:ai-focused` |
+| Automation-host bundle | Host bridge, clipboard/save guards, watcher shutdown, decision trace, bounded quick-search | `npm run test:automation-host` |
+| TradingView runtime bundle | Pine surface summaries, Pine workflows, runtime recovery, create/save, smoke window selection | `npm run test:tradingview-runtime` |
+| TradingView launch bundle | Launch profile, capability, contract, and relaunch executor seams | `npm run test:tradingview-launch` |
+| TradingView modernization bundle | Tranche 0-4 deterministic closure check before live proof | `npm run test:tradingview-modernization` |
+| Windows observation flow | Focus lock, watcher/checkpoint semantics, bounded TradingView workflows | `npm run test:windows-observation-flow` |
+| Live `liku chat` | Real foreground/input routing and final app state | Manual command through `npm run liku -- chat` |
+| Browser/Playwright proof | Browser-visible TradingView state after Liku actions | Optional, artifact-oriented, not a direct DOM trading executor |
+
+For a broader deterministic TradingView modernization regression pass before live smoke, use:
+
+```powershell
+npm run test:tradingview-modernization
+```
+
+## TradingView live checks
+
+Use the opt-in live smoke harness when a PR changes TradingView foreground routing, Pine Editor workflows, chart state workflows, observation checkpoints, or safety/resume behavior:
+
+```powershell
+npm run smoke:tradingview-live -- --dry-run
+npm run smoke:tradingview-live -- --scenarios focus,pine-editor
+```
+
+The harness requires an already-open TradingView session and intentionally runs one scenario sequence at a time to avoid foreground/window contention. It is not part of `npm test`; run it only when live Windows UIA evidence is relevant.
+
+By default, live output is written to `artifacts\live-validation\` and includes per-scenario `*.summary.json` files plus a run `*.manifest.json`. When runtime tracing is available, summaries also link the exported trace artifact for the scenario.
+
+When a PR changes TradingView runtime behavior, include evidence for:
+
+1. TradingView is the actual foreground target before input begins.
+2. Quick-search text is empty or authoritatively replaced before typing.
+3. Input does not route to VS Code or another foreground app.
+4. Final chart, panel, Pine Editor, or alert state matches the requested workflow.
+5. Runtime trace or summary artifacts are attached if behavior diverges from deterministic tests.
+
+Unexpected VS Code Accessibility View popups are evidence that keyboard input may have routed to VS Code instead of TradingView. Treat any result after that as suspicious until reproduced with correct focus.
+
+## Automation-ready launcher contract
+
+For Pine/CDP scenarios, use an explicit automation-ready launcher/wrapper contract instead of relying on generic Start-menu launch behavior.
+
+Current source of truth on Windows:
+- the official TradingView MSIX install is usable for automation when relaunched through the packaged AppUserModelId route
+- the wrapper can now launch that install with `--remote-debugging-port=` and `--force-renderer-accessibility`
+- the preferred official-install path is packaged AppID activation, not unpacking the MSIX and launching `TradingView.exe` directly
+
+Supported contract entry points:
+
+- `LIKU_TRADINGVIEW_AUTOMATION_LAUNCH_CONTRACT`
+ Inline JSON object describing the wrapper command.
+- `LIKU_TRADINGVIEW_AUTOMATION_LAUNCH_CONTRACT_FILE`
+ Path to a JSON file describing the wrapper command.
+- `LIKU_TRADINGVIEW_AUTOMATION_LAUNCH_COMMAND`
+ Minimal env-field form for the wrapper command.
+
+Minimal contract shape:
+
+```json
+{
+ "kind": "command",
+ "displayName": "TradingView automation wrapper",
+ "command": "C:\\tools\\launch-tradingview-automation.cmd",
+ "args": ["--remote-debugging-port=9222", "--force-renderer-accessibility"],
+ "workdir": "C:\\tools",
+ "expected": {
+ "cdpPort": 9222,
+ "rendererAccessibility": true,
+ "processNames": ["TradingView", "TradingView.exe"]
+ }
+}
+```
+
+Use `node scripts\inspect-tradingview-launch-capability.js` to inspect the current launch profile, install capability, launch contract, and Pine precondition message together.
+
+To generate a local wrapper-contract file under ignored artifacts, use:
+
+```powershell
+npm run tradingview:write-launch-contract
+```
+
+Useful writer options:
+
+- `--app-user-model-id `
+ Preferred for the official Windows MSIX install. Pins the packaged TradingView AppUserModelId that the wrapper should activate.
+- `--executable-path `
+ Target an explicit TradingView build/profile instead of the default packaged launch target.
+- `--allow-force-kill`
+ Let the wrapper escalate from graceful close to `Stop-Process -Force` if TradingView does not exit within the configured timeout.
+- `--close-timeout-ms `
+ Bound the graceful close window before the wrapper either fails closed or force-kills.
+- `--launch-settle-ms `
+ Add a short bounded settle delay after `Start-Process`.
+
+Preferred official-MSIX example:
+
+```powershell
+$appId = 'TradingView.Desktop_n534cwy3pjxzj!TradingView.Desktop'
+npm run tradingview:write-launch-contract -- --app-user-model-id $appId --cdp-port 9333
+$env:LIKU_TRADINGVIEW_CDP_PORT = '9333'
+$env:LIKU_TRADINGVIEW_AUTOMATION_LAUNCH_CONTRACT_FILE = (Resolve-Path .\artifacts\tmp\tradingview-automation-launch-contract.local.json).Path
+node scripts\inspect-tradingview-launch-capability.js
+```
+
+Explicit binary example:
+
+```powershell
+npm run tradingview:write-launch-contract -- --executable-path "C:\Tools\TradingView\TradingView.exe" --allow-force-kill
+$env:LIKU_TRADINGVIEW_AUTOMATION_LAUNCH_CONTRACT_FILE = (Resolve-Path .\artifacts\tmp\tradingview-automation-launch-contract.local.json).Path
+node scripts\inspect-tradingview-launch-capability.js
+```
+
+The live smoke harness will not relaunch TradingView automatically just because a contract is configured. Opt in explicitly when you want the harness to consume the contract:
+
+```powershell
+$env:LIKU_TRADINGVIEW_AUTOMATION_RELAUNCH = '1'
+node scripts\live-tradingview-smoke.js --scenarios pine-editor --relaunch-tradingview-via-contract
+```
+
+Optional relaunch controls:
+
+- `LIKU_TRADINGVIEW_AUTOMATION_RELAUNCH_TIMEOUT_MS`
+ Bound the total wrapper-wait budget.
+- `LIKU_TRADINGVIEW_AUTOMATION_RELAUNCH_POLL_INTERVAL_MS`
+ Control how often the harness rechecks the TradingView launch profile.
+
+The harness does not force-kill the current TradingView session on its own. The wrapper always attempts graceful close first. Force-kill is opt-in and should be enabled only in the generated wrapper contract when that restart policy is acceptable.
+
+## Browser/Playwright proof
+
+Playwright evidence is optional and secondary. Use it only to inspect browser-visible TradingView state after Liku has performed the workflow; do not use Playwright to directly mutate TradingView DOM state, place orders, bypass confirmations, or replace the Windows UIA/native execution path.
+
+If browser proof is attached, include the Liku live smoke manifest or summary alongside the Playwright artifact so reviewers can confirm the browser state was produced by Liku-controlled actions.
+
+## Safety boundaries
+
+- Live TradingView order-entry and unknown trading mode remain fail-closed.
+- High-confidence Paper Trading DOM order-entry requires explicit confirmation and resumes only through the Liku confirmation flow.
+- TradingView position-management controls such as flatten, reverse, cancel all, and close position remain blocked.
+- Pine Editor opening uses `Ctrl+E` only when TradingView chart focus is established; otherwise use the verified quick-search fallback.
+- Playwright may validate browser-visible outcomes, but it must not replace Liku's execution path for order-entry or position-management actions.
diff --git a/docs/TRANCHE0_POWERSHELL_PROCESS_PRESSURE_BASELINE.md b/docs/TRANCHE0_POWERSHELL_PROCESS_PRESSURE_BASELINE.md
new file mode 100644
index 00000000..bd4605c5
--- /dev/null
+++ b/docs/TRANCHE0_POWERSHELL_PROCESS_PRESSURE_BASELINE.md
@@ -0,0 +1,83 @@
+# Tranche 0 PowerShell Process Pressure Baseline
+
+This note captures the baseline tooling added for **Tranche 0** in the TradingView automation modernization backlog.
+
+## Current status
+
+Implemented in the repo today:
+- `scripts/test-system-automation-parity.js` inventories PowerShell-backed `system-automation` helpers, replays deterministic low-level helper fixtures, and writes both `artifacts/tranche0/system-automation-powershell-inventory.json` and `artifacts/tranche0/system-automation-low-level-parity-report.json`.
+- `scripts/profile-tradingview-latency.js` summarizes live-smoke manifests into the slowest action gaps, slowest automation methods, clipboard touches, quick-search timeout counts, and off-app foreground transitions.
+- `scripts/live-tradingview-smoke.js` records per-scenario latency/fallback telemetry and writes structured `*.summary.json` plus run `*.manifest.json` artifacts.
+- failure bundles are emitted through `scripts/lib/failure-artifacts.js` for focused tests and live scenarios.
+- deterministic golden parity fixtures now cover `focusWindow(...)`, foreground/window info, clipboard read/write, `pressKey(...)`, `typeText(...)`, click, double click, drag, and scroll helper contracts.
+
+Tranche 0 is now closed for the current low-level helper surface. Future helper additions should extend the same fixture harness instead of reopening ad hoc parity questions.
+
+## Why this exists
+
+Before replacing legacy PowerShell-heavy automation paths, we need evidence for:
+- which `system-automation` helpers still spawn PowerShell,
+- where latency accumulates during live TradingView runs,
+- and what failure context was present when a bounded live run failed.
+
+## New baseline tools
+
+### 1. System automation PowerShell inventory
+Lists `src/main/system-automation.js` functions that still call `executePowerShell(...)` or `executePowerShellScript(...)`, then replays deterministic low-level helper fixtures against mocked host/PowerShell paths.
+
+Run:
+
+```bash
+node scripts/test-system-automation-parity.js
+```
+
+Artifact:
+- `artifacts/tranche0/system-automation-powershell-inventory.json`
+- `artifacts/tranche0/system-automation-low-level-parity-report.json`
+
+Note:
+- `artifacts/tranche0/` is generated evidence and is kept out of commits by `.gitignore`.
+
+### 2. Live TradingView latency profile
+Reads a live-smoke manifest and prints the slowest action gaps, slowest automation methods, quick-search timeout counts, clipboard touches, and foreground-transition signals.
+
+Run the latest manifest automatically:
+
+```bash
+node scripts/profile-tradingview-latency.js
+```
+
+Or target a specific manifest:
+
+```bash
+node scripts/profile-tradingview-latency.js --manifest artifacts/live-validation/-tradingview-live-smoke.manifest.json
+```
+
+## Live smoke artifacts now include
+
+`node scripts/live-tradingview-smoke.js` now records, per scenario:
+- action timing gaps between visible steps,
+- profiled `systemAutomation` call counts and latency,
+- foreground sampling / off-app transition telemetry,
+- clipboard touch count,
+- quick-search preflight timeout and fallback counts,
+- and a structured failure bundle when the scenario fails.
+
+## Failure bundles
+
+Focused TradingView tests and the live smoke harness now write failure bundles under `artifacts/test-failures/` or the live validation artifact directory. These bundles include:
+- the thrown error,
+- runtime trace summary and exported trace path,
+- a tail excerpt of the runtime JSONL,
+- watcher capability snapshot when available,
+- foreground window snapshot when available,
+- and the scenario/test-specific extra context passed by the harness.
+
+## Current pressure hotspots to watch
+
+The inventory and live latency profile should be used to validate these known pressure points:
+- process enumeration (`getRunningProcessesByNames(...)`),
+- semantic search probes (`findElementByText(...)`),
+- foreground / window discovery,
+- clipboard reads/writes used by bounded quick-search fallback,
+- and SendKeys-based keyboard/text helpers when no semantic path is available.
diff --git a/docs/VSCODE_SLASH_COMMAND_IMPLEMENTATION.md b/docs/VSCODE_SLASH_COMMAND_IMPLEMENTATION.md
new file mode 100644
index 00000000..dc70108f
--- /dev/null
+++ b/docs/VSCODE_SLASH_COMMAND_IMPLEMENTATION.md
@@ -0,0 +1,620 @@
+# VS Code / Codespaces Slash Command Implementation
+
+Date: 2026-05-27
+
+## Purpose
+
+GitHub Codespaces and VS Code tunnel workflows are editor-first and often headless. In this repo, the GitHub surface now includes the Phase 2 read-only capability, the Phase 5.1 reviewed context-bundle bridge, the current branch-associated PR status/view slice, the reviewed write path for issue-comment/PR create/comment/review/reversible close-reopen, the Phase 8 workflow tranche (workflow validate/permissions/requirements inspection plus repo-content and run-operation previews with explicit CLI-only apply), and the Phase 9A repo-governance inventory tranche (rulesets, environments, Actions secret/variable metadata, CODEOWNERS/templates, webhooks, and GitHub App installation posture). This document records how slash commands work in the root runtime today and how future VS Code-style `/github ...` commands should be implemented without duplicating logic or bypassing the shared adapter layer.
+
+This is intentionally grounded in the current CommonJS root runtime, not the separate `ultimate-ai-system` workspace.
+
+## Current status
+
+The shared `/github ...` slash-command path is now implemented in the root runtime.
+
+Current implementation seams:
+
+- `src/main/github/slash-command-handler.js` — shared `/github ...` parsing, adapter dispatch, and chat-friendly formatting
+- `src/main/github/command-executor.js` — shared GitHub capability executor used by CLI and slash-command surfaces
+- `src/main/github/capability-registry.js` — declared GitHub capability metadata (schema, risk, side-effect, sources)
+- `src/main/github/capability-policy.js` — shared GitHub policy evaluation before capability execution
+- `src/main/ai-service/commands.js` — routes `/github ...` through the shared GitHub slash handler
+- `src/main/ai-service/slash-command-helpers.js` — now includes reusable long-option parsing for slash commands
+- `scripts/test-ai-service-github-slash-commands.js` — proof that `aiService.handleCommand('/github ...')` works through the real facade
+
+## Current source of truth in this repo
+
+### Root runtime
+
+The shipped root runtime is the CommonJS Node/Electron CLI app:
+
+- `src/cli/liku.js` — top-level CLI entrypoint
+- `src/cli/command-seam.js` — typed CLI request/execution seam
+- `src/main/index.js` — Electron main-process chat IPC and Electron-only slash commands
+- `src/main/ai-service.js` — shared AI service facade used by Electron and CLI chat
+- `src/main/ai-service/commands.js` — shared slash command handler
+- `src/main/ai-service/slash-command-helpers.js` — tokenizer/model-key normalization helpers
+
+### Current slash-command routing
+
+There are already three slash-command ownership zones.
+
+#### 1. Shared slash commands via `aiService.handleCommand()`
+
+`src/main/ai-service.js` creates:
+
+- `slashCommandHelpers` via `createSlashCommandHelpers({ modelRegistry })`
+- `commandHandler` via `createCommandHandler({...})`
+
+and delegates `aiService.handleCommand(command)` to `commandHandler.handleCommand(command)`.
+
+Current shared commands are implemented in `src/main/ai-service/commands.js` and include:
+
+- `/help`
+- `/login` / `/logout`
+- `/model`
+- `/provider`
+- `/setkey`
+- `/status`
+- `/state`
+- `/clear`
+- `/vision`
+- `/capture`
+
+These commands already work across both Electron chat and terminal chat because both surfaces eventually call `aiService.handleCommand()`.
+
+#### 2. Electron-only slash commands in `src/main/index.js`
+
+The Electron main process listens for `ipcMain.on('chat-message', ...)` and intercepts a separate set of commands before falling back to `aiService.handleCommand(message)`.
+
+Current Electron-only command handling includes:
+
+- `/agentic` or `/agent`
+- `/orchestrate `
+- `/research `
+- `/build `
+- `/verify `
+- `/agents` or `/agent-status`
+- `/agent-reset`
+- experimental `/produce `
+
+These commands are Electron/chat-window specific and should stay separate from headless-safe GitHub slash commands.
+
+#### 3. CLI-chat-only local controls in `src/cli/commands/chat.js`
+
+The terminal chat loop handles a few commands locally before delegating the rest to `ai.handleCommand(line)`:
+
+- `/trace`
+- `/sequence`
+- `/recipes`
+- interactive `/model` picker behavior
+
+That means any future shared `/github ...` slash commands should be implemented in the shared ai-service command layer, not duplicated only in CLI chat.
+
+## Current parser model
+
+`src/main/ai-service/slash-command-helpers.js` currently provides:
+
+- `tokenize(input)` — splits on whitespace while preserving quoted spans
+- `normalizeModelKey(raw)` — normalizes model ids / display strings
+
+Important constraint: the helper currently tokenizes arguments, but it does **not** provide a full `--flag value` parser. Existing shared commands in `src/main/ai-service/commands.js` manually interpret their token arrays.
+
+That matters for GitHub slash commands because the current GitHub surface needs options such as:
+
+- `--slug owner/repo`
+- `--state all`
+- `--limit 20`
+- `--labels bug,triage`
+- `--workflow ci.yml`
+- `--branch main`
+- `--branch feature/demo` for branch-associated PR status lookups
+- `--head owner:feature/demo` for explicit PR head matching
+- `--status completed`
+- `--event push`
+- `--api false`
+
+## Current GitHub implementation (Phase 2 read-only + Phase 5.1 bundles + Phase 7 reviewed writes + Phase 8 workflows + Phase 9A governance inventory)
+
+The GitHub capability now lives in dedicated adapters under `src/main/github/`.
+
+### Shared repo/target context
+
+`src/main/github/context.js` centralizes:
+
+- local project identity resolution
+- git remote parsing
+- explicit `--slug owner/repo` overrides
+- token detection from `GH_TOKEN` / `GITHUB_TOKEN`
+- shared GitHub API metadata scaffolding
+
+Use `resolveGitHubRepoContext()` instead of re-implementing remote/token parsing per command.
+
+### Read-only adapters
+
+Current adapter entrypoints:
+
+- `resolveGitHubAuthStatus()` — `src/main/github/auth-status.js`
+- `buildGitHubContextBundle()` — `src/main/github/context-bundle.js`
+- `writeGitHubContextBundleArtifact()` — `src/main/github/context-bundle-artifacts.js`
+- `inspectGitHubRepository()` — `src/main/github/repo-inspect.js`
+- `listGitHubRulesets()` — `src/main/github/ruleset-list.js`
+- `inspectGitHubRuleset()` — `src/main/github/ruleset-inspect.js`
+- `listGitHubEnvironments()` — `src/main/github/environment-list.js`
+- `inspectGitHubEnvironment()` — `src/main/github/environment-inspect.js`
+- `listGitHubSecrets()` — `src/main/github/secret-list.js`
+- `inspectGitHubSecret()` — `src/main/github/secret-inspect.js`
+- `listGitHubVariables()` — `src/main/github/variable-list.js`
+- `inspectGitHubVariable()` — `src/main/github/variable-inspect.js`
+- `inspectGitHubCodeowners()` — `src/main/github/codeowners-inspect.js`
+- `inspectGitHubTemplates()` — `src/main/github/template-inspect.js`
+- `listGitHubWebhooks()` — `src/main/github/webhook-list.js`
+- `inspectGitHubWebhook()` — `src/main/github/webhook-inspect.js`
+- `inspectGitHubAppStatus()` — `src/main/github/app-status.js`
+- `inspectGitHubAppInstallation()` — `src/main/github/app-installation-inspect.js`
+- `inspectGitHubAppPermissions()` — `src/main/github/app-permissions-inspect.js`
+- `inspectGitHubIssue()` — `src/main/github/issue-inspect.js`
+- `listGitHubIssues()` — `src/main/github/issues-list.js`
+- `listGitHubPullRequests()` — `src/main/github/pr-list.js`
+- `inspectGitHubPullRequestStatus()` — `src/main/github/pr-status.js`
+- `inspectGitHubPullRequestFeedback()` — `src/main/github/pr-feedback.js`
+- `inspectGitHubPullRequestDiff()` — `src/main/github/pr-diff-summary.js`
+- `inspectGitHubPullRequest()` — `src/main/github/pr-inspect.js`
+- `inspectGitHubRelease()` — `src/main/github/release-inspect.js`
+- `listGitHubReleases()` — `src/main/github/releases-list.js`
+- `inspectGitHubWorkflowRun()` — `src/main/github/workflow-inspect.js`
+- `listGitHubWorkflowRuns()` — `src/main/github/workflow-runs.js`
+
+### Reviewed write-preview/apply adapters
+
+Current low-risk write-preview/apply entrypoints:
+
+- `draftGitHubIssueComment()` — `src/main/github/issue-comment-draft.js`
+- `draftGitHubPullRequestCreate()` — `src/main/github/pr-create-draft.js`
+- `draftGitHubPullRequestComment()` — `src/main/github/pr-comment-draft.js`
+- `draftGitHubPullRequestReview()` — `src/main/github/pr-review-draft.js`
+- `draftGitHubPullRequestClose()` / `draftGitHubPullRequestReopen()` — `src/main/github/pr-state-draft.js`
+- `validateGitHubWorkflow()` — `src/main/github/workflow-validate.js`
+- `inspectGitHubWorkflowPermissions()` — `src/main/github/workflow-permissions-inspect.js`
+- `inspectGitHubWorkflowRequirements()` — `src/main/github/workflow-requirements-inspect.js`
+- `draftGitHubWorkflowCreate()` / `draftGitHubWorkflowUpdate()` — `src/main/github/workflow-content-draft.js`
+- `draftGitHubWorkflowDispatch()` / `draftGitHubWorkflowRerun()` / `draftGitHubWorkflowCancel()` — `src/main/github/workflow-run-draft.js`
+- `applyGitHubWritePreview()` — `src/main/github/write-apply.js` (compatibility re-export remains at `issue-comment-apply.js`)
+- `createGitHubWritePreviewArtifacts()` and related readers/writers — `src/main/github/write-artifacts.js`
+
+### Current shipped CLI surface
+
+These adapters are exposed today through `src/cli/commands/github.js` as:
+
+- `liku github auth status`
+- `liku github capabilities list`
+- `liku github capabilities inspect `
+- `liku github context bundle pr [--slug owner/repo] [--api false] [--out-file ]`
+- `liku github context bundle issue [--slug owner/repo] [--api false] [--out-file ]`
+- `liku github context bundle repo [--slug owner/repo] [--limit N] [--api false] [--out-file ]`
+- `liku github issues comment draft (--body | --body-file ) [--slug owner/repo]`
+- `liku github pr create draft --title [--body | --body-file ] [--base branch] [--head branch|owner:branch] [--draft true|false] [--slug owner/repo] [--api false]`
+- `liku github pr comment draft (--body | --body-file ) [--slug owner/repo]`
+- `liku github pr review draft --event [--body | --body-file ] [--slug owner/repo]`
+- `liku github pr close draft [--slug owner/repo]`
+- `liku github pr reopen draft [--slug owner/repo]`
+- `liku github apply --approve [--apply-token | --approval-file ]`
+- `liku github plan build [args...]`
+- `liku github plan execute [args...]`
+- `liku github plan execute --plan-file `
+- `liku github plan resume --guidance-file --resume-token --answers-file `
+- `liku github plan resume --guidance-file --resume-token --answers-json '{"field":"value"}'`
+- `liku github plan runs [--slug owner/repo] [--limit N] [--state completed|blocked|aborted|all]`
+- `liku github plan inspect [--slug owner/repo] [--plan-file ] [--event-log-file ]`
+- `liku github repo inspect`
+- `liku github ruleset list [--slug owner/repo] [--limit N] [--api false]`
+- `liku github ruleset inspect [--slug owner/repo] [--api false]`
+- `liku github environment list [--slug owner/repo] [--limit N] [--api false]`
+- `liku github environment inspect [--slug owner/repo] [--api false]`
+- `liku github secret list [--slug owner/repo] [--limit N] [--api false]`
+- `liku github secret inspect [--slug owner/repo] [--api false]`
+- `liku github variable list [--slug owner/repo] [--limit N] [--api false]`
+- `liku github variable inspect [--slug owner/repo] [--api false]`
+- `liku github codeowners inspect [--slug owner/repo] [--api false]`
+- `liku github codeowners create draft [--path ] [--body | --body-file ] [--base branch] [--head branch] [--slug owner/repo] [--api false]`
+- `liku github codeowners update draft [--path ] [--body | --body-file ] [--base branch] [--head branch] [--slug owner/repo] [--api false]`
+- `liku github template inspect [--slug owner/repo] [--api false]`
+- `liku github webhook list [--slug owner/repo] [--limit N] [--api false]`
+- `liku github webhook inspect [--slug owner/repo] [--api false]`
+- `liku github webhook create draft --events a,b --target-url --secret-ref repo: [--content-type json|form] [--active true|false] [--slug owner/repo]`
+- `liku github webhook update draft [--events a,b] [--target-url ] [--secret-ref repo:] [--content-type json|form] [--active true|false] [--slug owner/repo]`
+- `liku github webhook ping draft [--slug owner/repo]`
+- `liku github event list [--slug owner/repo] [--limit N] [--event ]`
+- `liku github event inspect [--slug owner/repo]`
+- `liku github app status [--slug owner/repo] [--probe false] [--api false]`
+- `liku github app installation inspect [--slug owner/repo] [--api false]`
+- `liku github app permissions inspect [--slug owner/repo] [--api false]`
+- `liku github issues list`
+- `liku github issues inspect `
+- `liku github pr list`
+- `liku github pr status [--branch name] [--slug owner/repo]`
+- `liku github pr view [--branch name] [--slug owner/repo]`
+- `liku github pr feedback [] [--branch name] [--head owner:branch] [--state open|closed|all] [--limit N] [--slug owner/repo]`
+- `liku github pr inspect `
+- `liku github pr diff `
+- `liku github workflow runs`
+- `liku github workflow inspect `
+- `liku github workflow validate [--body | --body-file ] [--slug owner/repo]`
+- `liku github workflow permissions inspect [--body | --body-file ] [--slug owner/repo]`
+- `liku github workflow requirements inspect [--body