# Changelog

## 0.6.0 (2026-10-03)

**Fix: injected reminders accumulated and could never leave the context (dsh 0.2.0-rc.2).**

- **Guard backoff.** A guard whose condition stays true re-fired every `cooldownSteps` forever (4,686 guard fires in the live log). Each consecutive fire now doubles the cooldown (cap 16×); the streak resets once the condition is observed false.
- **One message per step.** Guards that fire on the same step are batched into a single injection (was one message each — 4 at once observed at step 890).
- **Compaction hint.** Every reminder ends with an "ephemeral — do not copy into the summary" line. The dsh 0.2 summarizer is told to capture explicit instructions faithfully, so binding-tone reminders were copied verbatim into every checkpoint and carried forward forever.
- **`session-start` triggers** (from the 0.5.4 WIP, now shipped): fired-set persisted to `~/.dsh/dsh-keyword-injector/session-start-fired.json`, so a dsh restart no longer re-injects them into every resumed session.
- Removed the duplicate sleep>5s deny in `tools/pre-execute` (read `exec.input`, never set on dsh 0.2); the `ctx.tools.guard` deny is the single enforcement path.
- **Configurable backoff:** per-guard `maxBackoff` (1..64, default `GUARD_DEFAULTS.MAX_BACKOFF` = 16; `1` = fixed cadence). All guard defaults now come from `GUARD_DEFAULTS` — no literal 12/24/16 left in engine or UI.
- **UI: scannable trigger list.** Stat strip (total/on/off/guards/agent/keywords), status + type filter chips, collapsible sections (Guards / Agent-activity / User keywords), enabled-first sort with dimmed disabled rows, two-line rows with a plain-English guard summary and a clamped payload preview, `max backoff` field in the guard editor.
- **UI: real theme tokens.** The panel referenced tokens that don't exist in the DSH shell (`--dsw-interactive-bg*`, `--dsw-alias-bg-elevated`) and always fell back to hardcoded hex. All colors now come from `--dsw-alias-*` tokens (tints via `color-mix`), so light/dark follow the GUI theme. No hex values remain in `lib/ui.js` / `lib/client.js`.
- Compatibility matrix: `0.2.0-rc.2`. `npm run sync` regenerates `v05/`; `npm test` runs `test/guards.test.mjs`.

## 0.5.3 (2026-09-27)

- **FIX: `anyOf` direction + NEW `requireAny` for `tool-window` guards.** The engine fired `anyOf` guards only when NONE of those tools appeared ("should have been used"). Guards that must fire when a tool WAS used (e.g. the bash law: bash ran, serena/LSP/edit/read didn't) had no way to express it. New `guard.requireAny: true` inverts `anyOf` to presence-required. Verified live: the bash-vs-serena guard now fires after bash-only tool windows.
- **FIX: `argsPattern` now passes schema validation** (was silently dropped by the guard whitelist).
- Live-fire evidence: sleep>5s deny blocked real agents (`sleep 15s`/`45s` rejected in production log); bash-vs-serena guard fired after 12 bash-only calls in window; 10/10 triggers verified (5 keyword/steer dry-run + 5 guards live).
- AGENTS.md added: full agent charter — persistence model, guard/deny channels, deploy laws (lib edits not hot-reloaded; a throw on the universal path kills every tool call), release checklist.

## 0.5.2 (2026-09-27)

- **NEW: `sleep > 5s` hard deny (Mark's law).** A monotonic `tools` guard rejects any `bash`/`pwsh` call whose command contains `sleep N` with N > 5 seconds. Rejection message commands the agent to use `schedule_create` (after_seconds) instead and end its turn. Registered via `ctx.tools.guard()` — the only deny channel that fires for agent tool calls on dsh 0.1.6-alpha (verified: `tools/pre-execute` never fires there).
- **NEW: own-file trigger store.** The trigger document moved out of loader-entry config / the profile patch into the plugin's own `~/.dsh/dsh-keyword-injector/triggers.json` (atomic tmp+rename writes, one-time migration from the legacy settings document). Fixes "Config field \"version\" is not volatile" on every mutating tool call under the dsh 0.1.7 settings model.
- **NEW: `argsPattern` option for `tool-window` guards** — regex tested against recorded bash command args within the window (args captured per session, last 64).
- **Guard payloads rewritten to command tone** (todo-compliance, sequentialthinking, skill-load, memory-write, bash-vs-serena) — injections are binding commands, not suggestions.
- Module loader: `main` → `./v05/index.js` (cache-proof copy; the lib/ original is kept in sync).

## 0.5.0 (2026-09-27)

- Same own-file store + versioned-module work as shipped in 0.5.2 (unpublished intermediate).

## 0.4.1 (2026-09-23)

- **FIX: `keyword_injector_create_trigger` and `keyword_injector_update_trigger` agent tools were unusable.** Both declared their object parameter (`trigger` / `patch`) with a STRING schema, so the harness dropped the object and every call failed with "trigger required". Params are now proper object schemas (create: keyword/matchType/mode/payload required; guard + kind accepted; update: free-form patch object). HTTP routes were never affected. Found while live-testing the tool surface 2026-09-23.

## 0.4.0 (2026-09-23)

- **NEW guard type `tool-window` — the generic dynamic rule.** Condition: { window: N, noneOf: [tool globs], anyOf?: [tool globs], cooldownSteps }. Over the LAST N tool calls: fires when no call matched any noneOf glob, and (if anyOf is set) none of those appeared either. Examples: todo guard as { window: 10, noneOf: ['todo_write','taskboard_*'] }; skill enforcement as { window: 15, anyOf: ['skill'] } with payload "load the skill matching THIS exact job". Tracker now keeps a bounded 64-name ring buffer per session.
- Engine verified against the dsh-tools source contract: exec.name (tool name, top-level) + exec.arguments (frozen args); the v0.3.1 reads normalized to those fields.
- Semantic exactness (did the loaded skill match THIS job?) stays OUT — that requires an LLM judge; deliberate scope cut.
- Legacy todo-compliance / orphan-process guards unchanged (regression-tested).
- 12/12 engine+schema scenarios pass; UI editor gains the tool-window fields (window number + noneOf/anyOf pattern inputs, comma-separated globs).
## 0.3.2 (2026-09-23)

- **UI readability fix**: the Keywords panel used a hardcoded dark palette (light-gray text on transparent backgrounds) — on the GUI's light theme this rendered near-invisible. All surfaces now prefer the shell's `--dsw-alias-*` theme tokens (follow light/dark automatically) with dark-text-on-light fallbacks; badges, banners, table header, and the edit modal reworked to theme-aware colors.
## 0.3.1 (2026-09-23)

- **CRITICAL FIX — guard engine and agent-source keyword triggers never fired on dsh 0.1.6-alpha.** The old design scanned `agent.session.events` inside `agent/pre-step`; the alpha session projection no longer exposes an `events` array, so the scanner silently no-op'd on every step (zero log evidence, ever — the v0.2.1 agent-source surface was equally dead in production).
- Rebuilt on a live-verified surface: `tools/pre-execute` (one event per real tool call, `exec.agent` always attached). The tracker now counts work calls / todo usage / reminder coverage / background spawns there; `agent/pre-step` remains only as the injection point for guards. Agent-source keyword triggers now match tool name + arguments at `tools/pre-execute` (assistant-reasoning text matching is dropped — that data is no longer reachable).
- Orphan-process semantics hardened: reminders claim jobs in the tracker on every call (not only when the guard is out of cooldown), and refire is per-job-seq (a claimed old job no longer suppresses a new unclaimed one).
## 0.3.0 (2026-09-22)

- **Guard triggers (behavioural, no keyword)**: new trigger `kind: "guard"` with two built-in condition types:
  - `todo-compliance` — fires when the agent has made N real work tool calls (default 12, configurable `guard.workCallsThreshold`) without ever calling `todo_write` / `taskboard_*`; the payload injects as a binding instruction (e.g. "create an organized todo list and keep it updated").
  - `orphan-process` — fires at a step boundary when background jobs exist (`bash` with `run_in_background: true`) but no `schedule_create` / `calendar_add` / `notify` call has covered them; refires only when NEW unclaimed jobs appear.
- Guards evaluate on `agent/pre-step` and deliver through the same `agent.inject()` Context-injection path as keyword triggers; per-trigger cooldown (`guard.cooldownSteps`, default 24 steps).
- Settings UI: trigger-kind selector, guard condition editor (type + thresholds), guard badge in the table.
- UI fixes: the trigger table previously rendered 8 body columns under a 7-column header (the Source column had no header — now "Source" column exists); filter now also matches kind and guard type.
- Compat verified against dsh 0.1.6-alpha.1 (agent/inbox/inserted, agent/pre-step, webServer, settings, skills, tools seams all present; legacy keyword documents validate unchanged).
- Tooling: `tools/build-client.mjs` restored (the bundle header referenced it but the script was never committed); esbuild added as devDependency.
## v0.2.1 (2026-09-07)

- Removed the dead harness-RPC binding (`bindHarness`): dsh 0.1.2 invokes
  host halves as `apply(ctx, config)` — the second argument is never a
  harness RPC service, so the binding could never activate and only emitted
  a misleading "harness not available" warning on every boot. The Client UI
  is HTTP-only by design (`POST /api/dsh-keyword-injector/*`); behavior is
  unchanged.

All notable changes to **dsh-keyword-injector** are recorded here. The format
follows [Keep a Changelog](https://keepachangelog.com/) and the project
adheres to [Semantic Versioning](https://semver.org/).

## [0.1.0] — 2026-09-02

### Added
- Initial release.
- Host plugin:
  - `agent/inbox/inserted` listener scans every user message for keyword
    triggers (exact / contains / regex).
  - `agent/pre-step` waterfall prepends a synthetic reminder `user`-role
    message to the next agent step when a match is fresh.
  - Persistence under the `dsh-keyword-injector` settings namespace with
    strict per-field validation.
  - Six Package-private RPC endpoints (`list`, `create`, `update`, `delete`,
    `test`, `skills`) consumed by the client UI.
  - Steer-skill targets validated against the live skills catalog at save
    time (saves rejected on unknown skill).
  - Per-session scope with `scopeSessionId` tagging.
  - TTL on pending matches (60 s) with periodic sweep and cleanup on
    `agent/error` and `session/disposed`.
- Client plugin:
  - `settings.section` entry registered as `dsh-keyword-injector`.
  - Full CRUD UI: filter, table, edit modal, live test panel, skill
    dropdown populated from `keyword_injector.skills`.
  - Plain `React.createElement`, no JSX, no bundler, no third-party UI
    library.
- Validation helpers in `src/schema.js` (pure module).
- Pure matching algorithm in `src/matcher.js` (no Cordis deps).
- Reminder text formatter in `src/reminder.js` with fixed
  `[KEYWORD-INJECTOR REMINDER]` delimiters to defend against prompt
  injection.

### Security
- All user-supplied strings rendered as React text children (auto-escaped).
- Reminder payload stripped of C0 control bytes and DEL.
- Regex triggers validated at save time; runtime samples capped at 10 KB.
- Per-field and per-document validation at every Cordis boundary.

### Known limitations
- The reminder injection uses a synthetic `user`-role message rather than a
  system-prompt section. A future version may switch to
  `system-prompt/assemble` once the assembly shape is documented.
- The "this-session only" scope is only available when the Settings page
  is opened from inside an active session (the `scopeSessionId` is sourced
  from `props.sessionId`).

## [0.2.0] — 2026-09-03

### Added
- **Trigger source** field: `user` (original behaviour) or `agent`.
  - `agent`-source triggers match the agent's OWN reasoning and actions
    (assistant messages incl. thinking, tool calls + arguments, tool
    results) at the `agent/pre-step` boundary.
  - Mid-turn delivery: a hit injects the reminder via `agent.inject()`
    so it joins the CURRENT turn's next model request — e.g. the agent
    thinks "I need to triage this" and the triage rules arrive at its
    next step.
  - Once per (session, turn, trigger); matcher enforces source isolation.
  - UI: "Trigger source" radio in the editor + cyan Source badge column.

### Changed
- Delivery (v0.1 pre-step message mutation never worked): reminders are
  durable plugin-source `user/message` events delivered through the inbox
  next-step queue — rendered as "Context injection · dsh-keyword-injector"
  rows, claimed together with the triggering message (reminder first).
- Client → host RPC moved from the non-existent `host.call` bridge to
  `POST /api/dsh-keyword-injector/*` HTTP routes (bundle-plugin path).
- Settings namespace now registered (`schemastery` schema) before first
  use — persistence works for the first time.
- Live-test dry run no longer fails full-trigger validation (fake payload
  / createdAt fields).
- Client is shipped as an esbuild ModuleLoader bundle
  (`lib/client.bundled.js`); sidebar "Keywords" panel added next to
  Task Board / SSH / Skill Center.
- Dedupe guard on repeated `agent/inbox/inserted` events.

### Removed
- Legacy v1 pending-injection state (TTL map, step keys, timer sweep).
