# Changelog

## [Unreleased]

### Added

- **Claude Code bash sandbox integration (silent secure default).** `/hatch` now configures the Claude Code sandbox (OS-level bash isolation via `bwrap` on Linux/WSL2 and `sandbox-exec` on macOS) automatically when the system supports it — no wizard question, no operator-facing knob. When the capability probe passes, the standard profile is written to the operator's target settings file (filesystem denies for `~/.aws`, `~/.ssh`, `~/.gnupg`; network unrestricted so `gh`/`npm`/`pip` work on first run). When the probe fails (missing `bubblewrap`/`socat` on Linux), hatch prints a one-line install hint and proceeds without enabling the sandbox. Existing operator-set `sandbox.*` keys are preserved unconditionally. Operators can disable at any time by setting `sandbox.enabled: false` in their settings file.
- **Capability probe** (`scripts/sandbox-probe.py`). Shared by `/hatch`, `hermit-start`, and `/hermit-doctor`. Returns `pass/warn/fail` with a package-manager-aware install hint on Linux/WSL2 failure. Result is cached per boot (keyed on kernel release + bwrap/socat path mtimes) to avoid subprocess overhead on every start.
- **Docker base image now ships `bubblewrap` + `socat`** — required for the sandbox to actually start inside unprivileged containers. `hermit-start` automatically writes `sandbox.enableWeakerNestedSandbox: true` to `.claude/settings.local.json` on container boots and removes it on non-container boots; all other `sandbox.*` keys are left for the operator to manage.
- **`/hermit-doctor` sandbox check** (ninth check). Runs the capability probe and cross-references `sandbox.enabled` in settings files. Reports `pass/warn/fail` with remediation.
- **FAQ entry** for bash sandboxing — explains the macOS/Linux split, custom-CA tooling edge cases, and the WSL2 prerequisite.
- **`sandbox-profiles.json`** in `state-templates/` defines the `off` and `standard` profiles. `deny-patterns.json` gains a `sandbox.filesystem.denyRead` section as the canonical source for credential-path denies.
- **Sandbox test coverage and doc clarification.** Added contract tests for `_sandbox_probe_cached` (cache hit, cache miss, corrupted-cache reprobe) and `check_sandbox_capability` warn/fail probe paths (5 new tests, contract suite now 81). Clarified in `/hermit-doctor` SKILL.md that the ninth (sandbox) check is computed by the skill orchestrator, not by `doctor-check.js` — the sandbox line is therefore not present in `state/doctor-report.json`. Tools consuming the JSON report should call `scripts/sandbox-probe.py` separately if they need the sandbox status.

### Changed

- **Default `permission_mode` is now `auto` (CC 2.1.148+).** `auto` mode lets a classifier review each action before it runs, which is safer than `bypassPermissions` and more reviewed than `acceptEdits`. It is now the default for both Docker and non-Docker hermits, including new Docker installs via `/hatch` Quick mode (previously `bypassPermissions`). `bypassPermissions` remains available as an explicit opt-in for operators who need zero prompts for fully unattended Docker operation. `config.json.template`, `hermit-start.py`, `hatch`, `hermit-settings`, and `hermit-evolve` all updated. **Requires Claude Code 2.1.148+** (Sonnet 4.6 or Opus 4.6/4.7 on Max/Team/Enterprise/API plan). Operators on Pro, Haiku, or non-Anthropic providers will see CC report `auto` unavailable at launch and should switch via `/hermit-settings permissions`. `min_claude_code_version` bumped to `>=2.1.148` in `hermit-meta.json`.

### Fixed

- **Sandbox probe message corrected for Ubuntu 24.04+.** The warn branch in `scripts/sandbox-probe.py` for failed user-namespace detection (`unshare --user --pid true`) previously suggested `sysctl -w kernel.userns_restrict=0` on Ubuntu 24.04+ — that sysctl does not exist. Per upstream Claude Code sandbox docs, the actual remediation is to install an AppArmor profile granting `bwrap` the `userns` capability. The message now points operators at the AppArmor profile path for Ubuntu 24.04+ while preserving the correct `kernel.unprivileged_userns_clone=1` sysctl for older kernels. The actionable command is also populated in the probe result's `install_hint` field (previously `None`) so callers like `/hatch` step 9a, `/hermit-doctor`, and `hermit-start` surface it consistently.
- **`/proposal-act` applies `/code-review` findings.** Step e.5 `quality` and `balanced(RUN)` tiers now parse `/code-review`'s JSON output, Edit-apply findings whose fix is unambiguous from the summary, and surface the rest. Both tiers report `applied N/M findings (K surfaced)` instead of a hallucinated `M cleanups`. The no-post-apply-test-gate risk is documented inline; `/claude-code-dev-hermit:dev-quality` is the follow-up for verification.

### Upgrade Instructions

1. **Migrate `permission_mode` to `auto` (optional).** If `config.json` has `permission_mode` set to `acceptEdits` or `bypassPermissions`, ask the operator:

   > Your hermit's `permission_mode` is currently `"<current_value>"`. The new default is `"auto"`: classifier-reviewed autonomy. Eligibility depends on plan and model (Max plan → Opus 4.7 only; Team/Enterprise/API → Sonnet 4.6 or Opus 4.6/4.7; not available on Pro, Haiku, or non-Anthropic providers). Switch to `"auto"`? (y/N)

   - If `y`: write `permission_mode: "auto"` directly to `.claude-code-hermit/config.json` now. Do not rely on the silent-defaults pass later in evolve, which only adds missing keys and will not overwrite the existing value.
   - If `n`: leave `permission_mode` unchanged.
   - If `permission_mode` is already `"auto"`, `"default"`, `"plan"`, or `"dontAsk"`: skip this prompt.

   **Note for `bypassPermissions` operators:** `auto` pauses for confirmation on risky actions, so it will not work for fully unattended hermits that cannot tolerate any pause. Only switch if your workload can handle occasional confirmation prompts, or if a human is present when the hermit runs.

   **Note for Pro, Haiku, or non-Anthropic-provider operators:** `auto` mode is not available on your plan/model/provider. CC will report it as unavailable at launch and the hermit will not enter `auto`. Answer `n` here and keep your current mode, or pick another mode via `/hermit-settings permissions` after upgrade.

2. **Apply the standard sandbox profile when supported** (or confirm existing config).

   Read the target settings file — resolve via `hatch_target` (`local` → `.claude/settings.local.json`; `committed` → `.claude/settings.json`; fallback chain: `.claude-code-hermit/state/hatch-options.json` → marker scan → scope detection).

   - If the file already contains any *operator-intent* sandbox key (`enabled`, `filesystem`, `network`, `failIfUnavailable`, `autoAllowBashIfSandboxed`, or `allowUnsandboxedCommands`): tell the operator "Your existing sandbox config was preserved." **Skip the rest of this step.** Note: `enableWeakerNestedSandbox` is hermit-managed (auto-written by `hermit-start` in Docker) and does NOT count as operator config — ignore it when deciding whether to skip.
   - **If running inside a container** (`/.dockerenv` or `/run/.containerenv` exists): skip the probe (it would fail unconditionally on `unshare --user --pid true` in unprivileged containers). Apply the standard profile directly (see below). Tell the operator: "Sandbox enabled (standard profile, container — enableWeakerNestedSandbox auto-managed by hermit-start)."
   - Otherwise (not a container, no operator config): run `python3 ${CLAUDE_PLUGIN_ROOT}/scripts/sandbox-probe.py`.
     - If status is `"pass"`: apply the standard profile (see below). Tell the operator: "Sandbox enabled (standard profile, written to {file})."
     - If status is `"warn"`: surface the probe `message` to the operator first, then apply the standard profile. Tell the operator: "Sandbox configured (may degrade silently per warning above; written to {file})."
     - If status is `"fail"`: do NOT write any sandbox block. Print one line: "Sandbox unavailable: {message} — run `{install_hint}` to enable later, then re-run this migration."

   **Apply the standard profile** means: read `${CLAUDE_PLUGIN_ROOT}/state-templates/sandbox-profiles.json` (select the `standard` entry — includes `enabled`, `failIfUnavailable`, `autoAllowBashIfSandboxed`, `allowUnsandboxedCommands`); read `${CLAUDE_PLUGIN_ROOT}/state-templates/deny-patterns.json` and extract `sandbox.filesystem.denyRead`; merge `profile.filesystem = { "denyRead": <that array> }`; merge into the target settings file under the `sandbox` key.

3. **Rebuild the Docker image** (Docker operators only).

   The base image now includes `bubblewrap` and `socat`. Without a rebuild the sandbox silently degrades inside the container. Run `hermit-docker update` or `docker compose build` to pick up the new packages. Note: the entrypoint itself did not change, so no template refresh is needed.

4. **Note on custom-CA tooling** (informational, no action required unless affected).

   Tools that use a MITM proxy with a custom certificate authority (e.g. `gcloud` with a corporate proxy, `terraform` with a company CA) may require `"enableWeakerNetworkIsolation": true` in the `sandbox` block. See [Claude Code sandbox docs](https://code.claude.com/docs/en/settings#sandbox-settings) and the FAQ entry in `docs/faq.md`.

## [1.1.1] - 2026-05-21

### Added

- **Scope-aware hatch output routing (GH #111).** `hatch` now detects the core plugin's install scope (`claude plugin list --json`) and routes operator-personal outputs accordingly: `scope=local` → `CLAUDE.local.md` + `.claude/settings.local.json`; `scope=project` → `CLAUDE.md` + `.claude/settings.json` (current behavior); `scope=user` → `.local` (safer default). The choice is shown in Quick Turn 5's confirm bundle and Step 10 report. Advanced users get a Visibility question in Phase 6 to override the scope-derived default. The chosen target is persisted to `.claude-code-hermit/state/hatch-options.json` for downstream skills (`hermit-evolve`, `docker-setup`, `claude-code-dev-hermit:hatch`). `hermit-evolve` Steps 6, 7, and 8 are now target-aware — they no longer re-add committed files on every evolve run after a `.local` migration. `docker-setup` Step 6.4 is likewise target-aware. Step 7 (.gitignore) now checks missing entries per-line rather than skipping the whole append if any marker is already present.

### Changed

- **Adapted to CC 2.1.146 `/simplify` → `/code-review` rename.** All runtime invocations (`proposal-act` step e.5 quality-gate tiers, `quality-gate-judge` agent), injected templates (`state-templates/CLAUDE-APPEND.md`), and descriptive references updated. `min_claude_code_version` bumped to `>=2.1.146` in `hermit-meta.json` so `/hermit-evolve` Step 0 blocks upgrades on stale CC versions. **Requires Claude Code 2.1.146+.** After upgrading, run `/claude-code-hermit:hermit-evolve` to refresh existing project CLAUDE-APPEND content (state-templates only seed new installs via `/hatch`).
- **Hatch routing review-pass refinements (GH #111).** `docker-setup` Step 6.4 now uses the same fallback chain as `hermit-evolve` step 2a (state file → marker scan → scope detection) instead of silently defaulting to committed when `hatch-options.json` is absent — prevents leaking operator-personal hardening into the repo. `hatch` Step 9b preserves the original `stamped_by` and `stamped_at` when re-stamping an existing `hatch-options.json` (e.g. one written by `claude-code-dev-hermit:hatch` first), recording the new writer under `last_updated_by`/`last_updated_at` instead. Upgrade Instructions step 2 collapses three sequential per-step migration prompts into a single Visibility prompt with a "Stay on committed (skip migration)" shortcut option. New contract test `tests/test-hatch-options-contract.sh` asserts the canonical state-file path and `"target"` field name are referenced consistently across all five consumers — guards against rename drift.

### Fixed

- **AUTO_CLOSE defeated by routine SHELL.md writes (#109).** The `AUTO_CLOSE` signal in `heartbeat-precheck.js` read SHELL.md mtime, which routine writes (reflect, scheduled-checks, heartbeat alerts) bump on a sub-12h cadence — causing auto-close to never fire on always-on hermits with default routines. Fix: new `scripts/record-operator-action.js` hook writes `state/last-operator-action.json` on UserPromptSubmit and SessionStart, filtering out cron-delivered routine prompts (`[hermit-routine:` prefix), any bare slash-command with no `<command-message>` wrapper (covers all `/loop` re-fires, cron injections, and programmatic slash calls), and inbound channel messages. SessionStart only seeds the file on cold start (existing timestamps are preserved across container restarts, so unattended boots can't mask a vanished operator). Channel-responder runs the script with `--force` after authorization passes (the hook deliberately skips `<channel` prompts because it can't see the allowlist). Heartbeat-precheck now gates AUTO_CLOSE on this file when present, falling back to SHELL.md mtime for pre-upgrade installs.

### Upgrade Instructions

0. **Append local-file entries to `.gitignore`** (always, regardless of target).
   - Read `.gitignore` at the project root.
   - If it does not contain the line `CLAUDE.local.md`, append it.
   - If it does not contain the line `.claude/settings.local.json`, append it.

1. **Migration preflight — detect deny patterns that block this migration.**
   - Read `permissions.deny` from both `.claude/settings.json` and `.claude/settings.local.json` (if they exist).
   - If any of the following appear: `Edit(.claude/settings.json)`, `Write(.claude/settings.json)`, `Edit(.claude/settings.local.json)`, `Write(.claude/settings.local.json)`, `Bash(*> .claude/settings.json*)`, `Bash(*> .claude/settings.local.json*)` — surface them to the operator with this message: "These deny patterns (from the hardened always_on set) will block the migration writes. Temporarily removing them is required to proceed."
   - Ask operator: **Temporarily remove for this migration** (stash, migrate, restore at end) / **Skip migration** (keep current layout, no file moves) / **Abort** (stop hermit-evolve entirely).
   - If "Skip migration": record `hatch_target = "committed"` and stamp `.claude-code-hermit/state/hatch-options.json`, then skip steps 3–5. Continue to step 6 (hermit-evolve will correctly write to committed files going forward).
   - If "Temporarily remove": remove the listed deny entries from the settings file(s) now. Note them for restoration in step 7.

2. **Decide hermit visibility for this project.**
   - Read `claude plugin list --json`. Apply precedence `local > project > user` for entries where plugin name is `claude-code-hermit` and `projectPath` matches the current project root. Map: project → committed; local/user/null → local.
   - Ask operator a single Visibility prompt with three options (scope-derived target at position 0 as recommended): **`.local` files** (gitignored — operator-personal) / **Committed files** (shared with teammates) / **Stay on committed (skip migration)** (no file moves; hermit-evolve writes to committed files going forward).
   - If "Stay on committed (skip migration)": record `hatch_target = "committed"`, stamp `.claude-code-hermit/state/hatch-options.json`, skip steps 3–5 entirely (do not prompt per-step). Continue to step 6.
   - Otherwise record the choice into `.claude-code-hermit/state/hatch-options.json`.

3. **Migrate hermit CLAUDE-APPEND block if target = .local.**
   - If `CLAUDE.md` contains the marker `<!-- claude-code-hermit: Session Discipline -->`: show diff (CLAUDE.md → CLAUDE.local.md). Ask operator: **Move** (diff-and-confirm) / **Keep in CLAUDE.md** / **Skip**.
   - If moving: check whether the block content differs from the canonical template at this plugin version. If hand-edits exist inside the marker, surface them — ask whether to carry them across or drop them.

4. **Migrate hermit-scoped hook allow entries if target = .local.**
   - Identify hermit-scoped entries in `.claude/settings.json` `permissions.allow` by matching: `Bash(git diff:*)`, `Bash(git status:*)`, `Bash(git log:*)`, `Bash(node */scripts/cost-tracker.js*)`, `Bash(node */scripts/suggest-compact.js*)`, `Bash(node */scripts/heartbeat-precheck.js*)`, `Bash(node */scripts/reflect-precheck.js*)`, `Bash(node */scripts/archive-shell.js*)`, `Bash(node */scripts/run-with-profile.js*)`, `Bash(node */scripts/evaluate-session.js*)`, `Bash(node */scripts/append-metrics.js*)`, `Bash(node */scripts/generate-summary.js*)`, `Bash(node */scripts/update-reflection-state.js*)`, `Bash(node */scripts/cron-tz-shift.js*)`, `Bash(bash -c 'AGENT_DIR=\".claude-code-hermit\"*)`, `Edit(.claude-code-hermit/**)`, `Write(.claude-code-hermit/**)`.
   - Show diff (`.claude/settings.json` → `.claude/settings.local.json`). Ask operator: **Move** / **Keep in settings.json** / **Skip**.

5. **Migrate hermit deny patterns if target = .local.**
   - Identify by matching entries against `state-templates/deny-patterns.json` (both `default` and `always_on` sets).
   - Show diff (`.claude/settings.json` → `.claude/settings.local.json`). Ask operator: **Move** / **Keep in settings.json** / **Skip**.

6. **If target = committed:** no marker or permission migration needed. hermit-evolve continues writing to committed files (current behavior) — steps 3–5 are skipped.

7. **Restore preflight denies** (only if step 1 stashed entries). Re-add the stashed deny entries to the operator's chosen target settings file.

## [1.1.0] - 2026-05-18

### Added

- **Operator-configurable primary outbound channel (`channels.primary`, PROP-041).** Adds `scripts/resolve-outbound-channel.js`, a testable CLI that resolves the best eligible channel for proactive sends: checks `channels.primary` first (if set and reachable), then falls back to the first eligible entry in `channels` in operator's config order. **No hardcoded slug list** — a freshly installed channel plugin becomes eligible the moment its config block lands in `config.json`, with no resolver change needed. A channel is eligible if `enabled !== false`, `allowed_users` is not `[]`, and `dm_channel_id` is set. `weekly-review` now calls this resolver instead of inlining the priority list. `validate-config.js` special-cases `channels.primary` as an optional string that must reference an existing channel-config object (not a string or array). On `config_read_failed`, the resolver now includes `detail` and `path` in its JSON error payload so callers parsing stdout-only can diagnose. The Operator Notification protocol in `CLAUDE-APPEND.md` is updated to document the resolver and the revised eligibility rule (the prior "exactly one allowed user" requirement is replaced by the dm_channel_id-based unambiguity check, which is accurate for how proactive sends actually work); the matching prose in `docs/troubleshooting.md` (Channel Sends Not Working section) is also corrected. `/hermit-settings channels` gains `primary <name>` and `primary clear` verbs so the new field can be set without hand-editing `config.json`; the channels list shows the current `Primary:` value on its own line, and removing a channel that `channels.primary` points at also clears the pointer (so the config never holds a dangling reference). Propagates to existing installs via `hermit-evolve` step 6 (atomic CLAUDE-APPEND block sync); no `### Upgrade Instructions` needed.

- **New skills: `/hermit-brain`, `/hermit-evolution`, `/hermit-health`.** Three on-demand channel-friendly analytics skills that replace the retired Obsidian Cortex surface. `/brain` synthesises fragile zones, stale accepted proposals, and recent learnings from session history and reflect output. `/evolution` shows cost trend, autonomy delta, and proposal-resolution times across recent weekly reviews. `/health` shows alert state, proposal queue depth, routine engagement, and channel availability. All three emit ≤1500-char channel-optimised markdown. When invoked via a channel-arrived message they reply via the channel (per PROP-037 §0); terminal invocations emit to conversation.
- **Automatic session close (PROP-040).** Heartbeat archives sessions whose SHELL.md has been idle for more than 12h, silently and without configuration. Auto-closed session reports carry `closed_via: auto` frontmatter; reflect skips them when scanning archive evidence (preventing mtime-triggered false compute-phase runs); weekly-review includes them in cost/session totals but excludes them from the autonomy rate denominator with an inline "(N auto-archived excluded)" note. The trigger fires via a new `AUTO_CLOSE` verdict from `heartbeat-precheck.js` (SHELL.md mtime check, testable with `HERMIT_NOW` env var); the `--auto` flag on `/session-close` bypasses the operator-summary prompt, skips reflect, and preserves the heartbeat loop.
- **Reactive channel-reply reminder (PROP-037).** Inbound `<channel source="..." chat_id="..." ...>` messages now get a per-prompt hook-injected reminder naming the exact reply tool and `chat_id`, plus a documented contract in the channel-responder skill. Two reinforcement surfaces: (1) `skills/channel-responder/SKILL.md` §0 promotes the reply-via-channel rule to step 0 of the canonical handler — using the generic tool-name pattern `mcp__plugin_<source>_<source>__reply` so it stays accurate for all channel plugins; (2) new `scripts/channel-reply-reminder.js` UserPromptSubmit hook parses the channel envelope at the start of the prompt (order-independent attribute extraction, `>` inside quoted values handled correctly, `safeForLLM` sanitization, length caps) and emits an `additionalContext` reminder. Hook is a no-op when no channel envelope is present, so non-channel installs pay zero cost. Addresses the downstream silent-stranding bug observed when MCP-level guidance alone proved insufficient. No operator `CLAUDE.md` changes.

### Fixed

- **PROP-040 auto-close: SHELL.md Monitoring append now runs before `/session-close --auto`.** Previously the heartbeat skill appended the `[HH:MM] Heartbeat: auto-closed after 12h quiet.` line to `## Monitoring` after invoking session-close, but session-close replaces SHELL.md with a fresh template — the trace landed in the new (empty) session's Monitoring section instead of the archived report, where it belonged as evidence the auto-close fired. Step ordering in `skills/heartbeat/SKILL.md` AUTO_CLOSE branch reversed so the append happens first.

### Changed

- **Cortex generation moves from cron-driven file regeneration to on-demand skill dispatch.** `/hermit-brain`, `/hermit-evolution`, `/hermit-health` read hermit state directly and emit fresh analyses per invocation. No pre-built file artifact.
- **`weekly-review` now appends a "This week's evolution" block** (cost, autonomy, proposal counts with week-over-week Δ) and sends the combined summary via the configured channel. The block is computed from `compiled/review-weekly-*.md` frontmatter — no extra LLM call at routine time, though the LLM still formats the prose.
- **`weekly-review` routine default changed to `enabled: true`** for new installs. Existing operators retain their current setting; to receive the new channel-friendly weekly evolution summary, enable the `weekly-review` routine via `/claude-code-hermit:hermit-settings`.
- **Frontmatter contract softened from strict (enforced by `validate-frontmatter.js`) to convention.** Include `title`, `created`, `tags`, `source`, `session` (if applicable) on artifacts you create. See `docs/frontmatter-contract.md`.
- **`/reflect`: Tier 1 + `Evidence Source: current-session` is now accepted at any hermit phase (PROP-036 Stage 1).** Previously only `newborn` (age < 3d) allowed it; juvenile and adult required 2+ archived sessions for every tier, leaving reflect silent on long-running daemons whose operators rarely close sessions and never accumulate `S-NNN-REPORT.md` archives. Tier 1 + `archived-session` still requires 2+ archived sessions, and Tier 2 / Tier 3 are unchanged. The Evidence Integrity Rule (reflect must not inject pattern text into Findings/Blockers pre-judge) is unchanged and remains the guardrail against self-certification. Stage 2 (candidate_history ledger for the empty-Findings subcase) is deferred pending empirical signal.

### Fixed

- **`hermit-evolve` migration check uses the correct `obsidian/` path (PR #102 review).** The retired Cortex was always written to the project root (`<project-root>/obsidian/`), not inside `.claude-code-hermit/`. The earlier `hermit-evolve` step 5a checked the wrong path, so the upgrade-time Findings note ("obsidian/ no longer maintained by hermit; safe to delete") would never have fired for existing operators. Also: `weekly-review` channel selection now uses an explicit fixed priority (`discord`, `telegram`) instead of relying on JSON object key order; `/brain` trigger list no longer collides with `/knowledge`'s "knowledge health" phrase; `weekly-review` SKILL.md step numbering normalised; new contract tests in `tests/run-contracts.py` (`TestAnalyticsSkillsContract`) guard against copy-paste drift in the three new analytics skills.

### Removed

- **Skills:** `/claude-code-hermit:obsidian-setup`, `:cortex-refresh`, `:cortex-sync`
- **Scripts:** `build-cortex.js`, `cortex-refresh-stage.js`, `validate-frontmatter.js`
- **Templates:** `state-templates/obsidian/` (six Cortex page templates)
- **Templates:** `state-templates/cortex-manifest.json.template`
- **Docs:** `docs/obsidian-setup.md`
- **Stop-hook stage:** cortex-refresh stage removed from `scripts/stop-pipeline.js`
- **Weekly-review:** `Latest Review.md` pointer write removed from `scripts/weekly-review.js`

### Files affected

| File | Change |
|------|--------|
| `scripts/resolve-outbound-channel.js` | New: primary channel resolver CLI |
| `scripts/channel-reply-reminder.js` | New: UserPromptSubmit hook for channel reply reminder |
| `scripts/weekly-review.js` | Evolution block; use resolver for channel send |
| `scripts/heartbeat-precheck.js` | New `AUTO_CLOSE` verdict; SHELL.md mtime check |
| `scripts/validate-config.js` | Special-case `channels.primary` optional string |
| `skills/hermit-brain/SKILL.md` | New analytics skill |
| `skills/hermit-evolution/SKILL.md` | New analytics skill |
| `skills/hermit-health/SKILL.md` | New analytics skill |
| `skills/heartbeat/SKILL.md` | AUTO_CLOSE branch; step ordering fix |
| `skills/session-close/SKILL.md` | `--auto` flag to bypass operator-summary prompt |
| `skills/hermit-settings/SKILL.md` | `primary` verbs; channels list shows primary |
| `skills/hermit-evolve/SKILL.md` | Fix `obsidian/` path; add `min_claude_code_version` gate |
| `skills/reflect/SKILL.md` | Tier 1 + `current-session` accepted at any hermit phase |
| `skills/weekly-review/SKILL.md` | Evolution block; channel send |
| `skills/channel-responder/SKILL.md` | §0 reply-via-channel contract |
| `state-templates/CLAUDE-APPEND.md` | Updated Operator Notification protocol; Quick Reference |
| `state-templates/config.json.template` | Add `channels.primary` optional string |
| `hooks/hooks.json` | Add `channel-reply-reminder` UserPromptSubmit entry |
| `skills/obsidian-setup/`, `cortex-refresh/`, `cortex-sync/` | Removed |
| `scripts/build-cortex.js`, `cortex-refresh-stage.js`, `validate-frontmatter.js` | Removed |
| `state-templates/obsidian/`, `cortex-manifest.json.template` | Removed |

### Upgrade Instructions

Run `/claude-code-hermit:hermit-evolve` after `/plugin update`. The evolve skill handles:

1. **Detect** existing project-root `obsidian/` directory — leave it untouched. Log a Findings note: `"obsidian/ no longer maintained by hermit; safe to delete or keep as personal vault."` Leave `.claude-code-hermit/cortex-manifest.json` in place.
2. **Inform** the operator: `weekly-review` template default is now `enabled: true` for new installs; your current setting is not auto-flipped. To receive the new channel-friendly weekly evolution summary, run `/claude-code-hermit:hermit-settings` and enable the `weekly-review` routine.
3. **Note** that `/obsidian-setup`, `/cortex-refresh`, and `/cortex-sync` have been removed — no migration required.

No config.json changes required.

## [1.0.40] - 2026-05-16

### Added

- **Token counts co-displayed alongside USD on all cost-reporting surfaces (GH #77).** `pulse`, `brief`, `hermit-doctor`, `weekly-review`, and session frontmatter now show `(12.3K tokens)` next to every USD figure. Motivation: with prompt caching active, USD is a noisy proxy for work done — a cache-read-heavy session costs very little but represents real model work. Token counts give a stable, pricing-independent signal and make cache-miss regressions visible at the next `/pulse` or `/brief` rather than the next billing cycle. Implementation: `cost-tracker.js` accumulates `total_tokens` per date and writes `total_tokens` / `avg_session_tokens` to `cost-summary.md` frontmatter (schema-aware same-day skip so upgraded installs gain the new fields immediately); the trend table gains a Tokens column so `brief --morning` can source yesterday's tokens; `weekly-review.js` aggregates session frontmatter tokens with a date-range fallback to `cost-log.jsonl`; `doctor-check.js` co-displays token count and cache-read tokens alongside the budget check; `session-mgr` writes `tokens` from `.status.json` to session report frontmatter (optional field, silent-on-absence in validator). Format helper `kStr`/`formatTokens` shared via `scripts/lib/format.js` (`kStr(0)` short-circuits to `'0'` so zero-activity sections render as `0K` not `0.0K`; `cost-summary.md` body lines use bare `K` rather than re-appending `tokens` next to the `Tokens:` label). Pulse corrected to read live cost/tokens from `.status.json` rather than the stale `## Cost` SHELL.md section that `cost-tracker.js` no longer writes back.

- **GitHub CLI (`gh`) installed in hermit Docker baseline image (PROP-028, GH #82).** `gh` is now installed in every freshly built hermit container via the official `cli.github.com` apt source. No authentication is wired by default — the container runs `gh` anonymously (subject to GitHub's 60 req/hr unauth rate limit). Operators who need authenticated calls set `HERMIT_GH_TOKEN` in their `.env`; the Compose template maps it to `GH_TOKEN` inside the container so `gh` and git read it natively. The `HERMIT_*` namespace is intentional: future runtime shims (e.g. auto-minting an install token from `HERMIT_GH_APP_*` credentials, mirroring `hermit-scribe`'s pattern) can swap the token source without operators changing their env var names. See `docs/docker-security.md#gh-cli-authentication` for details.

- **Calibration rule in `state-templates/CLAUDE-APPEND.md`.** New `Rules` bullet that flips the default posture from "answer confidently, get corrected" to "verify or label." Fires on specificity of the claim (version-pinned behavior, external system state, recalled API/function signatures, menu paths, prices/dates/counts), not on topic. Carve-out keeps general domain knowledge (principles, patterns, semantics) answerable directly. Resolves PROP-025 (downstream operator surfaced confident-but-wrong UI navigation steps; cause is training-data sparsity + menu drift on named devices, addressable in prose by changing default response posture). Propagates to existing installs via `hermit-evolve` step 6 (atomic block sync); no `### Upgrade Instructions` needed.

### Changed

- **`Proposals mandatory` rule in CLAUDE-APPEND.md tightened.** Added explicit "Never hand-write `proposals/PROP-*.md` files" clause with rationale (NNN-assignment, slug, timestamp, collision-guard logic only runs through the skill; manual ids reuse NNNs across parallel sessions and produce short-form ids violating the canonical `PROP-NNN-<slug>-HHMMSS` schema).

### Removed

- **`hermit-takeover` and `hermit-hand-back` skills removed.** Both duplicated the `bin/hermit-docker down` / `up` flow already documented in `docs/always-on.md`, but skipped the SIGTERM-triggered `/session-close --shutdown` that `down` provides. Follow-up work queuing is already covered by `/proposal-act accept` writing `NEXT-TASK.md`. The `operator_takeover` SHELL.md status disappears with them (closed loop: only takeover wrote it, only hand-back read it). Doc references in `docs/always-on.md`, `docs/always-on-ops.md`, `docs/skills.md`, `docs/how-to-use.md`, and `docs/troubleshooting.md` were repointed at `bin/hermit-docker` / `bin/hermit-start` directly.

### Upgrade Instructions

**`gh` baseline install (Dockerfile + Compose).** Existing hermits have on-disk `Dockerfile.hermit` and `docker-compose.hermit.yml` at the project root that plugin updates won't touch. Two surgical patches are needed before rebuilding:

**Step 1 — Dockerfile.** Open `Dockerfile.hermit` at the project root. If the file does not exist, Docker has not been set up — skip to Step 3. Check whether it already contains `apt-get install -y --no-install-recommends gh`. If it does, skip to Step 2. If not, locate the line:

```
    apt-get install -y --no-install-recommends nodejs && \
```

Replace the continuation (the `rm -rf` line that follows it) with:

```
    apt-get install -y --no-install-recommends nodejs && \
    mkdir -p -m 755 /etc/apt/keyrings && \
    curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
      -o /etc/apt/keyrings/githubcli-archive-keyring.gpg && \
    chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg && \
    echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
      > /etc/apt/sources.list.d/github-cli.list && \
    apt-get update && apt-get install -y --no-install-recommends gh && \
    rm -rf /var/lib/apt/lists/*
```

**Step 2 — docker-compose.hermit.yml.** Open `docker-compose.hermit.yml` at the project root. If the file does not exist, skip to Step 3. Check whether it already contains `GH_TOKEN=`. If it does, skip to Step 3. If not, find the line starting with `      - AGENT_HOOK_PROFILE=` and add immediately after it:

```
      - GH_TOKEN=${HERMIT_GH_TOKEN:-}
```

**Step 3 — Rebuild.** Run `hermit-docker update` to rebuild the image with the patched Dockerfile. On next start, `gh --version` should succeed inside the container.

**Netguard note:** No dnsmasq allowlist change is required. The existing `server=/github.com/` rule already covers `api.github.com` and `cli.github.com` as subdomains — do not add redundant entries.

**Authenticated calls (optional):** To use `gh` with a GitHub PAT, add `HERMIT_GH_TOKEN=<your-pat>` to `.claude-code-hermit/.env`, then `hermit-docker down && hermit-docker up`. The Compose template maps it to `GH_TOKEN` inside the container. Without a token, `gh` runs anonymously (60 req/hr rate limit).

**`hermit-takeover` / `hermit-hand-back` removal.** No operator action needed. The canonical Quick Reference line now lives in `state-templates/CLAUDE-APPEND.md`, and `hermit-evolve` step 6 (atomic block sync) refreshes the operator's project `CLAUDE.md` on the next evolve run.

## [1.0.39] - 2026-05-14

### Fixed

- **`skills/proposal-act/SKILL.md` frontmatter parse error.** The new description added in this release introduces an unquoted internal colon (`how to proceed: start implementing`), which YAML treats as a mapping-key delimiter inside a plain scalar. The native `claude plugin validate` command failed with `YAML frontmatter failed to parse`, and at runtime the skill would have loaded with empty metadata (description silently dropped, breaking activation phrases). Wrapping the description in single quotes restores parseability without changing the prose.

### Changed

- **Accept flow adds "Start implementing now" as the default third option.** When chosen, the hermit handles session lifecycle (idle: delegates to `session-mgr` to go `in_progress`; in_progress: confirms task switch with operator; waiting: falls back silently to queue) then reads the proposal body and executes the Proposed Solution in the current turn. On completion it runs `/proposal-act resolve` and notifies the operator with a one-line summary. The option is marked as the default/typical answer to guide Discord replies.
- **"Create a session task" branch preserves an existing `NEXT-TASK.md` rather than overwriting it.** If one is already pending, the branch skips the write, marks the proposal `accepted`, and tells the operator to consume the existing task first via `/session-start`.
- **Resolve Flow drops the hardcoded "Pattern confirmed absent" suffix.** `Resolved on <date>.` is now the default append. Reflect's auto-resolve path may still add the pattern-absence note in SHELL.md Findings (unchanged); the proposal file itself stays generic.
- **`HEARTBEAT.md.template`: scope proposal review to `status: proposed`.** Accepted proposals were re-surfaced as actionable by the LLM-evaluated checklist item. New wording explicitly skips accepted, resolved, deferred, and dismissed.
- **Accept-flow wording tightened (review pass).** Step 3a now explicitly reads both `session_id` and `session_state` so step 4(a)'s back-reference is real. Step 4(a) clarifies the read is used "to branch". Waiting branch reads "without asking, then notify" instead of the contradictory "silently; notify". The NEXT-TASK collision notify now points at a concrete recovery path (`/session-start` to consume, then re-run `/proposal-act accept PROP-NNN`) instead of the impossible "re-accept".
- **3-tier `quality_gate` at step (e.5) of `/proposal-act` accept flow with new `quality-gate-judge` haiku subagent.** New top-level config key `quality_gate.tier` with three values: `budget` (default; `/simplify` never runs), `balanced` (the new `quality-gate-judge` subagent reads the proposal body + touched files and returns `RUN` or `SKIP` per implementation; only the RUN path invokes `/simplify`), `quality` (`/simplify` always runs). Default-safe fallback: any missing key or value outside the enum resolves to `budget` at runtime with a one-line SHELL.md Findings warning. Closes the quality-review gap downstream hermits hit (they have no equivalent of this monorepo's `/commit → /simplify` chain), while keeping default spend at zero until the operator opts in to `balanced` or `quality` via `/hermit-settings quality-gate`. Closes #66, resolves PROP-019.
- **NEXT-TASK numbered-bullet append simplified (review pass).** Replaced the brittle "pick `4.` or `5.` depending on whether the prior bullet was appended" instruction with "append in order, numbered sequentially from `4.`" plus `(if ...)` prefixes on each conditional bullet.

### Files affected

| File | Change |
|------|--------|
| `skills/proposal-act/SKILL.md` | Three-option accept step 4; resolve wording; description string (single-quoted to escape internal colon); tier-branched step (e.5); NEXT-TASK numbered-bullet ordering |
| `agents/quality-gate-judge.md` | **New** haiku subagent: reads proposal body + touched files, returns RUN/SKIP verdict for step (e.5) balanced tier |
| `agents/hermit-config-validator.md` | Mirror enum check for `quality_gate.tier` in the validator's prose checklist |
| `scripts/validate-config.js` | Add `quality_gate` to `REQUIRED_KEYS`; add enum validator for `quality_gate.tier` (matches existing `escalation` pattern) |
| `state-templates/HEARTBEAT.md.template` | Scope proposal review to `status: proposed` |
| `state-templates/config.json.template` | New `quality_gate.tier` key (default `"budget"`) |
| `state-templates/CLAUDE-APPEND.md` | Subagents section: add `quality-gate-judge` entry |
| `skills/hermit-settings/SKILL.md` | `quality-gate` subcommand: 3-option tier picker (Budget/Balanced/Quality), display row |
| `skills/hatch/SKILL.md` | `quality_gate` added to Template-only fields list |
| `docs/config-reference.md` | Top-Level Keys row + Complete Example for `quality_gate.tier`; dev-quality overlap note |
| `docs/skills.md` | `quality-gate` appended to hermit-settings subcommand list |
| `tests/test-proposal-act-accept-flow.sh` | New regression test + tier-branched asserts; subagent-file existence/frontmatter asserts |
| `tests/run-contracts.py` | `TestConfigContract`: assert `quality_gate.tier` enum in template |
| `tests/run-all.sh` | Register new test |

### Upgrade Instructions

**HEARTBEAT.md**: existing hermits have an on-disk `HEARTBEAT.md` that template updates won't touch. Check line 5 of `.claude-code-hermit/HEARTBEAT.md`. If it still reads exactly:

```
- Review proposals/ for any needing attention
```

replace it with:

```
- Review `proposals/` for any with `status: proposed` needing operator review. Skip `accepted` (operator-owned, implementation underway), `resolved`, `deferred`, and `dismissed`.
```

If you have customised this line, skip and update manually.

**config.json (`quality_gate.tier`)**: if `quality_gate.tier` is already present in `.claude-code-hermit/config.json`, skip. Otherwise ask the operator via `AskUserQuestion`:

> *"Pick the quality-gate tier for autonomous proposal implementations. The hermit runs `/simplify` at step (e.5) of `/proposal-act` based on this setting."*

Options:
- **Budget** (default; recommended): write `"quality_gate": { "tier": "budget" }`. `/simplify` never runs at step (e.5). Cheapest. No post-implementation review.
- **Balanced**: write `"quality_gate": { "tier": "balanced" }`. The new `quality-gate-judge` haiku subagent decides per implementation (reads proposal body + touched files, returns `RUN` or `SKIP`). Costs ~$0.005 per judge call plus an occasional ~$0.25 `/simplify` run on RUN verdicts.
- **Quality**: write `"quality_gate": { "tier": "quality" }`. `/simplify` runs on every implementation. ~$0.25-$0.35 per implementation in Sonnet pricing.

If the operator has `claude-code-dev-hermit:dev-quality` installed and uses it to gate commits, recommend **Budget**: `/dev-quality` already runs `/simplify` pre-commit, and any non-Budget tier here would double-fire `/simplify` (~$0.40-$0.70 of duplicated spend per committed implementation).

Operators can flip later via `/claude-code-hermit:hermit-settings quality-gate`.

**CLAUDE.md — Subagents section**: open `.claude-code-hermit/CLAUDE.md` (created during `/hatch` from the CLAUDE-APPEND template). Locate the `## Subagents` section. If `quality-gate-judge` is already listed, skip. Otherwise, after the line that starts with `` - `hermit-config-validator` (Haiku) — ``, insert exactly:

```
- `quality-gate-judge` (Haiku) — decides whether `/simplify` should run at step (e.5) of `/proposal-act` accept flow; reads proposal body + touched files, returns RUN/SKIP verdict. Only invoked when `quality_gate.tier: "balanced"`.
```

If the Subagents section has been customised or reordered such that the anchor line isn't found, surface a manual note: "Add `quality-gate-judge` to your Subagents section in CLAUDE.md."

### Known Limitations

- **"Create a session task" does not queue multiple proposals.** `session-start` always deletes `NEXT-TASK.md` after presenting it, so appending would lose unselected items. The preserve-and-notify guard is the safe minimum. Making `session-start` understand a queue of suggested tasks is a separate follow-up.

## [1.0.38] - 2026-05-12

### Added

- **`safeForLLM()` sanitizer for LLM-bound rejection text (PROP-008).** Extends `scripts/lib/sanitize.js` with a second export that chains the existing `safe()` control-char strip with a tag-name allowlist guard. Known Claude/CC context marker tags (`system-reminder`, `system`, `assistant`, `user`, `tool_use`, `tool_result`, `thinking`, `function_calls`) are bracket-wrapped (`<system-reminder>` → `[system-reminder]`) so they are readable for debugging but cannot be interpreted as injected system context. Non-injection angle brackets (e.g. `3 < 5`, unrecognised tag names) are untouched.

### Changed

- **`validate-config.js` routes rejection text through `safeForLLM` (PROP-008).** Error and warning strings emitted to stderr now have user-controlled fields (escalation value, routine schedule, channel name, heartbeat time) sanitized before they reach Claude's context. This pairs with the `continueOnBlock` change below: without the sanitizer, a malicious `config.json` could inject fake `<system-reminder>` directives into Claude's context via the rejection message.
- **`continueOnBlock: true` on the `validate-config.js` PostToolUse hook (PROP-008).** A config validation failure used to halt the turn entirely — in autonomous goal-loops this kills the whole loop and requires operator recovery. With `continueOnBlock`, the validation error is surfaced to Claude as feedback and the turn continues; Claude can read the specific errors and fix the config. Verified empirically against CC 2.1.139 before shipping. The flag sits next to `command`/`args`/`timeout` on the hook entry and is form-agnostic, so the move to exec form (below) preserves the behavior.
- **`min_claude_code_version` gate in `hermit-evolve`.** A new Step 0 reads `${CLAUDE_PLUGIN_ROOT}/.claude-plugin/hermit-meta.json` and checks the running `claude --version` against a `min_claude_code_version` semver range (supports `>=X.Y.Z`). If the CLI is below the declared minimum, the skill aborts with an upgrade message before any config or template writes occur. If the field is absent the gate skips silently (forward-compat). Also adds `.claude-plugin/hermit-meta.json` to the core plugin with `min_claude_code_version: ">=2.1.139"` — the first core-side hermit-meta.json, mirroring the pattern sibling plugins use for `required_core_version`.
- **Hooks: converted shell-form commands to exec form (`args: []`).** All 8 convertible hook entries (PreToolUse, PostToolUse, UserPromptSubmit, SessionStart, Stop) now use exec form. The dev-mode contract runner stays in shell form (uses stdin/jq/pipes); its `description` explains why. Fixes path-with-spaces fragility: installs at paths containing a space previously broke because `${CLAUDE_PLUGIN_ROOT}` expanded unquoted in shell form.
- Added `tests/test-hook-registration-form.sh` contract test — guards against future regressions to naked shell-form interpolation across the plugin fleet. Also fails loudly when the path-resolution glob returns zero hook entries, so a future refactor that breaks `MONOREPO_ROOT` resolution cannot silently pass the test vacuously.

### Fixed

- **`hermit-docker update` now actually updates the Claude Code binary (cache-bust fix).** `docker compose build --pull` only re-pulls the `FROM ubuntu:24.04` base layer; subsequent `RUN` layers — including `RUN npm install -g @anthropic-ai/claude-code` — were content-addressed by command string and silently reused. A running hermit self-updates its binary on the writable container layer; after `hermit-docker update` rebuilt with a cached layer and bounced the container, the new container reverted to the older baked version. Live measurement: `2.1.132 → 2.1.114` (an 18-patch rollback) on a hermit claiming it upgraded to `2.1.139`. Fixed by adding a `CLAUDE_CODE_VERSION` build arg to `Dockerfile.hermit.template` that pins the npm install to a specific version. `hermit-docker update` resolves the desired version via `npm view @anthropic-ai/claude-code version` (already fetched before the build for the plan output) and passes it as `--build-arg CLAUDE_CODE_VERSION=<resolved>`. BuildKit includes the arg value in the `RUN` layer's cache key, so the layer is invalidated exactly when the version changes — fast rebuilds when nothing changed, correct rebuilds when it did. Falls back to `latest` if the registry lookup fails, preserving today's behavior on registry hiccups.

- **`hermit-docker update` no longer reports a false downgrade.** `CC_BEFORE` captured the running container's `claude --version` (the self-updated binary on the writable layer); `CC_AFTER` captured the new container's `claude --version` (the baked image version, before any self-update). The fix sources `CC_AFTER` directly from the resolved build-arg version when known, which is the ground truth — no container query needed.

- **`/reload-plugins` is now gated on the Claude Code prompt being ready.** The previous wait loop only confirmed `tmux has-session` succeeded. In the full update path (container bounced), `tmux has-session` returns true as soon as the session is created by the entrypoint, but `claude` inside the pane needs additional boot time before it reaches its interactive REPL. Keys sent before then went to the bash shell launching claude, not to the Claude Code skill queue. Fixed by polling `tmux capture-pane -p` for the Claude Code input box (detected by the `╭─` / `╰─` box-drawing characters that frame the prompt). Polls up to 60 seconds; if not detected, logs a warning with the manual fallback command and skips the send rather than silently misfiring. The `update-history.jsonl` log field is renamed from `reload_plugins_queued` to `reload_plugins_sent` to reflect the actual outcome.

### Files affected

| File | Change |
|------|--------|
| `state-templates/docker/Dockerfile.hermit.template` | `ARG CLAUDE_CODE_VERSION=latest`; npm install pinned to `@${CLAUDE_CODE_VERSION}` |
| `state-templates/docker/docker-compose.hermit.yml.template` | `CLAUDE_CODE_VERSION: ${CLAUDE_CODE_VERSION:-latest}` under `build.args` |
| `state-templates/bin/hermit-docker` | `update` arm: `--build-arg` pass-through; `CC_AFTER` from pinned value; `_wait_for_claude_prompt` readiness gate; log field renamed |

### Upgrade Instructions

- **Requires Claude Code 2.1.139 or newer.** The `args: []` exec form was introduced in CC 2.1.139. Update Claude Code before pulling this release, or hooks will fail to register.

Docker users only. Non-Docker (tmux/local) hermits are unaffected.

The `hermit-docker update` wrapper (`bin/hermit-docker`) is refreshed wholesale by `hermit-evolve` step 5b — no manual wrapper edit needed.

The on-disk `Dockerfile.hermit` and `docker-compose.hermit.yml` in the project root need two surgical patches to pass the build arg through. Apply them idempotently:

1. **Idempotency check for `Dockerfile.hermit`.** If the file does not exist in the project root, skip (Docker not set up). If the file already contains `ARG CLAUDE_CODE_VERSION`, skip this file — already patched.

2. **Patch `Dockerfile.hermit`.** Anchor on the exact line `ARG HOST_UID=1000`. If this line is not found (operator-customized Dockerfile), tell the operator: "Auto-patch skipped for Dockerfile.hermit — anchor `ARG HOST_UID=1000` not found. Add these two lines manually after the HOST_UID ARG: `ARG CLAUDE_CODE_VERSION=latest` and change the npm install line to `RUN npm install -g bun @anthropic-ai/claude-code@${CLAUDE_CODE_VERSION}`." Then skip to step 4.

   Use the Edit tool with:

   `old_string`:
   ```
   ARG HOST_UID=1000
   ```

   `new_string`:
   ```
   ARG HOST_UID=1000
   ARG CLAUDE_CODE_VERSION=latest
   ```

3. **Pin the npm install in `Dockerfile.hermit`.** Anchor on `@anthropic-ai/claude-code` NOT already followed by `@` (i.e. the line doesn't yet have `@${CLAUDE_CODE_VERSION}`). If the npm install line has already been version-pinned, skip.

   Use the Edit tool with:

   `old_string`:
   ```
   RUN npm install -g bun @anthropic-ai/claude-code
   ```

   `new_string`:
   ```
   RUN npm install -g bun @anthropic-ai/claude-code@${CLAUDE_CODE_VERSION}
   ```

   If the old_string is not found (operator added extra packages to the line), tell the operator: "Auto-patch skipped for the npm install line — it has been customized. Update it manually to include `@${CLAUDE_CODE_VERSION}` after `@anthropic-ai/claude-code`."

4. **Idempotency check for `docker-compose.hermit.yml`.** If the file does not exist, skip. If it already contains `CLAUDE_CODE_VERSION:`, skip — already patched.

5. **Patch `docker-compose.hermit.yml`.** Anchor on `HOST_UID: ${UID:-1000}`. If not found, tell the operator: "Auto-patch skipped for docker-compose.hermit.yml — anchor `HOST_UID: ${UID:-1000}` not found. Add `CLAUDE_CODE_VERSION: ${CLAUDE_CODE_VERSION:-latest}` manually under the `build.args:` block."

   Use the Edit tool with:

   `old_string`:
   ```
         HOST_UID: ${UID:-1000}
   ```

   `new_string`:
   ```
         HOST_UID: ${UID:-1000}
         CLAUDE_CODE_VERSION: ${CLAUDE_CODE_VERSION:-latest}
   ```

6. **Rebuild.** Tell the operator: "Run `.claude-code-hermit/bin/hermit-docker update` once to bake the version-pinned image. The first rebuild after this patch will reinstall claude-code (cache is invalidated by the new `RUN` command string); subsequent same-version runs reuse the cache as expected."

## [1.0.37] - 2026-05-11

### Added

- **`capability-brainstorm` skill (PROP-007).** On-demand hermit-voice brainstorm that synthesizes memory, available capabilities (MCPs, channels, fleet plugins), recent compiled artifacts, and codebase shape into at most 2 capability ideas. Each idea routes through the standard `proposal-triage` gate via a single `/proposal-create` call (`Evidence Source: capability-brainstorm`) before becoming a PROP — no double-triage. Emit-zero is a valid outcome. Zero-emit runs log a one-line note to SHELL.md Findings and skip the compiled artifact; non-empty runs write `compiled/capability-brainstorm-YYYY-MM-DD-HHMM.md`. Routing relies on Claude Code's skill-description auto-trigger (no explicit `channel-responder` block), with the description narrowed to capability-anchored phrases ("brainstorm capabilities", "what could you be doing for me?", "any capability ideas?") to avoid firing on bare "any ideas?" mid-task. Kill criteria documented in the skill: after ≥8 invocations, if triage-survival < 25% or PROP-acceptance < 30%, cut rather than tune.

### Changed

- **Collision-safe proposal IDs for multi-user hermits (PROP-008).** Proposal IDs now use the composite form `PROP-NNN-<slug>-HHMMSS` where the ID equals the filename stem (e.g. ID `PROP-009-capability-brainstorm-103612`, file `PROP-009-capability-brainstorm-103612.md`). The slug (up to 5 content words, kebab-cased; falls back to `proposal` if the title is all punctuation or non-ASCII) is part of the identity. It appears in Discord notifications, session-report cross-references, and channel messages so the operator can identify a proposal at a glance without opening it. The `HHMMSS` timestamp gives collision-safety. If a same-second collision fires, an `a` suffix is appended after the timestamp (`PROP-009-capability-brainstorm-103612a`); further collisions continue through `b`, `c`, …. The change is merge-safe in practice: operators on separate machines almost always produce different filenames (different slugs or different seconds), and both land cleanly. Identical-second collisions on offline machines surface as a normal git conflict, not a silent overwrite.
- **`/proposal-act` uses anchored prefix-glob resolution** instead of exact-match file reads. `accept PROP-009` resolves both legacy `PROP-009.md` and new-format `PROP-009-*.md` files using two anchored patterns, never a bare `PROP-009*.md` (which would also match `PROP-0091.md` if proposal counts ever cross 1000). The operator can also type `accept PROP-009-103612` (timestamp-only) and the slug is bracketed with wildcards. If multiple files match (e.g. after a concurrent-creation merge), a disambiguation prompt lists each match's title. Existing short-form muscle memory (`accept PROP-NNN`) is unchanged.
- **Legacy `PROP-NNN.md` files continue to work** — no migration, no rename. All resolution, listing, and cortex scripts accept both the old and new filename forms.

### Fixed

- **`knowledge-schema.md.template`: declare `review` as a Work Product type (PROP-011).** The `weekly-review` routine writes compiled artifacts with `type: review`, but the template only declared `note` — so every freshly-hatched hermit running the weekly-review routine reported a permanent `Type \`review\` not declared in knowledge-schema.md ## Work Products` finding from the Knowledge Health checker. Adds a single bullet to the template's `## Work Products` section and provides upgrade instructions for `hermit-evolve` to surgical-patch existing hermits' on-disk schemas.

### Files affected

| File | Change |
|------|--------|
| `skills/proposal-create/SKILL.md` | New ID generation (ID = filename stem `PROP-NNN-slug-HHMMSS`), slug algorithm with `proposal` fallback for empty slugs, collision `a`/`b`/… fallback |
| `skills/proposal-act/SKILL.md` | Anchored prefix-glob resolution algorithm with disambiguation prompt (two-pattern no-suffix glob; bracketed-suffix glob) |
| `docs/frontmatter-contract.md` | `id`/`proposal` field docs and proposal file pattern note both legacy and new forms |
| `docs/artifact-naming.md` | Proposals naming entry notes both legacy and new forms |
| `state-templates/CLAUDE-APPEND.md` | Quick-reference table updated to show the new proposal filename form |
| `plugins/claude-code-hermit/CLAUDE.md` | Repo-internal quick-reference bullet updated to show the new form |
| `skills/proposal-list/SKILL.md` | Example table updated to show new-format IDs alongside legacy |
| `state-templates/PROPOSAL.md.template` | `id:` placeholder updated to `PROP-NNN-slug-HHMMSS` |
| `agents/session-mgr.md` | `proposals_created` scan regex updated to capture full `PROP-NNN-slug-HHMMSS[a]` form |
| `scripts/reflect-precheck.js` | Widened proposal filename regex to accept new-format files |
| `scripts/build-cortex.js` | Widened proposal filename regex |
| `scripts/cortex-refresh-stage.js` | Widened proposal filename regex |
| `scripts/weekly-review.js` | Widened proposal filename regex |
| `scripts/validate-frontmatter.js` | Widened proposal filename regex |
| `scripts/doctor-check.js` | Widened proposal filename regex |
| `state-templates/knowledge-schema.md.template` | Added `- review:` bullet under `## Work Products` |
| `tests/run-scripts.sh` | Extended schema-empty fixture sed to also strip `- review:` starter bullet |

### Upgrade Instructions

The on-disk `knowledge-schema.md` is operator-editable, so apply this as a surgical, idempotent patch.

1. **File check.** If `.claude-code-hermit/knowledge-schema.md` does not exist, skip — `/hatch` has not been run for this project yet, and the new template already contains the entry for first-time setups.

2. **Idempotency check.** Read `.claude-code-hermit/knowledge-schema.md`. If the `## Work Products` section already contains a bullet starting with `- review:`, skip — patch already applied (or the operator already declared it themselves).

3. **Anchor check.** Confirm the file contains the unmodified `- note:` anchor line:

   ```
   - note: general-purpose compiled note. location: compiled/note-<slug>-<date>.md
   ```

   If this line is missing or customized, tell the operator: "Auto-patch failed — anchor not found. The `- note:` bullet in `knowledge-schema.md` has been customized. Add the following bullet manually under `## Work Products`: `- review: weekly review report from the weekly-review routine. location: compiled/review-weekly-<YYYY>-W<NN>.md`"

4. **Propose the patch.** Tell the operator what will be inserted and ask for confirmation:

   > "Patching `.claude-code-hermit/knowledge-schema.md` to declare the `review` type under `## Work Products` (fixes PROP-011 — Knowledge Health false positive after every weekly-review). Apply? [Yes / Skip]"

5. **On Yes — apply.** Use the Edit tool on `.claude-code-hermit/knowledge-schema.md` with:

   `old_string`:
   ```
   - note: general-purpose compiled note. location: compiled/note-<slug>-<date>.md
   ```

   `new_string`:
   ```
   - note: general-purpose compiled note. location: compiled/note-<slug>-<date>.md
   - review: weekly review report from the weekly-review routine. location: compiled/review-weekly-<YYYY>-W<NN>.md
   ```

## [1.0.36] - 2026-05-10

### Added

- **`hermit-start`: support third-party channel plugins via `channels.<name>.marketplace` (#47).** `CHANNEL_PLUGINS` previously hardcoded a closed dict of three official channels (`discord`, `telegram`, `imessage`). Configuring any other channel name appended a bare token to `--channels`, which `claude` rejected as untagged and killed the launched process — taking the tmux session with it before any useful error reached the operator. `build_claude_command` now falls back to `channels.<name>.marketplace` from `config.json` and emits `plugin:<channel>@<marketplace>`. The hardcoded path is unchanged, so existing discord/telegram/imessage configs behave identically.
- **`cache-edit-guard.js`: warn (or block) Edit/Write to marketplace cache copies (#48).** Project-local marketplaces declare `"source": "<relative-path>"` in `marketplace.json` and the runtime loads plugin code from that source. The directory under `.claude/plugins/cache/<marketplace>/<plugin>/<version>/` is a stale snapshot — silent edits there produce no-ops at runtime and look like the change worked until something restarts. New PreToolUse hook detects Edit/Write on a cache path, resolves the canonical source via the project's `marketplace.json`, and prints a stderr warning naming the source path. Default behaviour is warn-only (`exit 0`); setting `HERMIT_CACHE_GUARD=block` upgrades the warning into a hard block (`exit 2`). Stdlib-only, fail-open on stdin or `marketplace.json` parse errors.
- **`hermit-start`: marketplace pre-flight for `--channels` (PROP-005).** Boot now runs `claude plugin marketplace list --json` once per `build_claude_command` call and validates every `plugin:<name>@<marketplace>` token. Unregistered marketplaces produce a loud `[hermit] WARNING` naming the missing marketplace and remediation (`claude plugin marketplace add <repo>`), and the channel is dropped from `--channels` rather than silently passed to claude — which would ignore the unknown marketplace and boot with no channels active, leaving the operator wondering why DMs go nowhere. The pre-flight also catches the common operator-confusion mode where `channels.<name>.marketplace` is set to a *repo* (e.g. `someone/matrix-plugin`) instead of a marketplace *name*: when the configured value matches a registered repo, the warning names the correct marketplace name to use in `config.json`. Fail-soft — if `claude` is missing or returns unexpected output, pre-flight is skipped and behavior is byte-identical to before.
- **`hermit-start`: refuse channel names starting with `-` as bare args.** Defense-in-depth against future `claude` versions that might loosen flag-vs-arg validation. Today claude rejects untagged channel args downstream, so this guard isn't fixing a live bug — it just keeps the contract local to `hermit-start` rather than relying on downstream validation.

### Fixed

- **Sanitize control characters in hermit hook stderr (PROP-006, terminal log-injection hardening).** `cache-edit-guard.js` and `channel-hook.js` interpolated values from `tool_input` (file paths, chat IDs) directly into `process.stderr.write`. An adversarial path of the form `foo\n\x1b[32m[hermit] OK proceed\x1b[0m` could render a forged green all-clear line below the real warning, hiding the warning's signal value from the operator or from a model reading transcript output. Added `scripts/lib/sanitize.js` with a `safe(s)` helper that replaces C0 controls + DEL + C1 controls (`U+0000..U+001F`, `U+007F`, `U+0080..U+009F`) with `?`, and routed every `tool_input`-derived stderr interpolation through it: `safe(filePath)` and `safe(canonical)` in `cache-edit-guard.js`; `safe(chatId)` in `channel-hook.js`. No behaviour change for benign paths — sanitization is a no-op on normal ASCII. Persisted values (`config.json` `dm_channel_id`) keep the raw bytes; sanitization applies only at the operator-visible stderr boundary. Added `TestStderrSanitization` contract class with four cases covering newline (C0), ANSI ESC (C0), C1 single-byte CSI, and channel-hook chat_id sanitization, verified via a negative-control protocol where each `safe()` wrap independently fails its corresponding test when removed. Hooks deliberately *not* hardened: `enforce-deny-patterns.js` and `dev-hermit/git-push-guard.js` interpolate operator-controlled config strings, not `tool_input`/`tool_response` values, so they fall outside this proposal's threat model.

### Files affected

| File | Change |
|------|--------|
| `scripts/hermit-start.py` | Marketplace pre-flight (PROP-005); third-party channel marketplace fallback (#47); reject bare args starting with `-` |
| `scripts/cache-edit-guard.js` | New PreToolUse hook (#48); routed `safe()` over `tool_input`-derived stderr (PROP-006) |
| `scripts/channel-hook.js` | Routed `safe()` over `chat_id` stderr interpolation (PROP-006) |
| `scripts/lib/sanitize.js` | New helper — replaces C0/DEL/C1 control chars with `?` for stderr-bound strings (PROP-006) |
| `hooks/hooks.json` | Registers `cache-edit-guard.js` for Edit\|Write (#48) |
| `skills/channel-setup/SKILL.md` | Install/enable/manual commands now resolve marketplace from config rather than hardcoding `claude-plugins-official` (#47) |
| `docs/config-reference.md` | Documents the new `channels.<name>.marketplace` field (#47) |
| `tests/run-contracts.py` | New `TestCacheEditGuard` (7 cases), `TestStderrSanitization` (4 cases), and 3 third-party-channel cases |
| `CLAUDE.md` | Dev note: `hermit-docker update` rebuilds with on-disk entrypoint, not the template |

### Upgrade Instructions

Run `/claude-code-hermit:hermit-evolve`. The evolve skill handles:

1. **No state-file changes required.** All four items in this release ship inside the plugin (`scripts/`, `hooks/`, `skills/`, `docs/`) — they take effect on the next boot without modifying anything under `.claude-code-hermit/`.

2. **No `config.json` changes required.** The new `channels.<name>.marketplace` field is purely additive and only consulted when an operator configures a non-built-in channel; existing discord/telegram/imessage configs need no edits.

3. **Optional: enable cache-edit hard block.** Operators who want Edit/Write attempts on `.claude/plugins/cache/...` to fail rather than warn can export `HERMIT_CACHE_GUARD=block` in their shell environment. Default behaviour (warn-only) is the safer choice for most operators.

**Note:** The PROP-005 marketplace pre-flight and PROP-006 stderr sanitization are silent on benign input — operators should see no behavioural difference unless they have a misconfigured channel marketplace or an adversarial tool_input value.

## [1.0.35] - 2026-05-09

### Fixed

- **Docker entrypoint: post-recovery sanity check + npm reinstall for corrupted claude binary ([#44](https://github.com/gtapps/claude-code-hermit/issues/44)).** The existing orphan-recovery block renames `.claude-<rand>` back to `claude` but never verifies the result is functional. When the orphan is the half-uninstalled pre-update binary, `claude --version` returns `0.0.0` and every downstream invocation fails. With `restart: unless-stopped`, Docker retries the same broken container layer indefinitely and no log line tells the operator what to do. The fix adds a sanity check immediately after recovery: if `claude --version` reports `0.0.0`, the entrypoint runs `npm install -g @anthropic-ai/claude-code` to reinstall from npm, which writes to the container layer and persists across subsequent `restart` calls — unwedging the loop without requiring `docker compose down && up`. If npm itself fails, the entrypoint exits 1 with an explicit `Recreate the container: hermit-docker down && hermit-docker up` message so each restart cycle logs an actionable next step instead of silent failure.

### Files affected

| File | Change |
|------|--------|
| `state-templates/docker/docker-entrypoint.hermit.sh.template` | Added post-recovery sanity-check block; reinstalls `claude` from npm when `claude --version` reports `0.0.0`; exits 1 with actionable message if npm itself fails |

### Upgrade Instructions

Docker users only — this patch updates the rendered `docker-entrypoint.hermit.sh`, which is baked into the container image at build time. Non-Docker (tmux/local) hermits are unaffected.

1. **File check.** If `docker-entrypoint.hermit.sh` does not exist at the project root, skip all steps — `/docker-setup` has not been run for this project yet, and the new template already contains the fix for first-time setups.

2. **Idempotency check.** Read `docker-entrypoint.hermit.sh` at the project root. If it already contains the string `claude binary is non-functional`, skip all steps — the patch is already applied.

3. **Propose the patch.** Tell the operator what will be inserted and ask for confirmation:

   > "Patching `docker-entrypoint.hermit.sh` to add a self-heal block after the orphan-recovery code (fixes #44 — corrupted `claude` binary after mid-install self-update). The block is inserted immediately after the orphan-recovery `fi`, before `--- 0. Wait for auth credentials ---`. Apply? [Yes / Skip]"

4. **On Yes — apply.** Use the Edit tool on `docker-entrypoint.hermit.sh` with:

   `old_string` (closing lines of the orphan-recovery block + blank line + next section header — unique since v1.0.20):
   ```
     fi
   fi
   
   # --- 0. Wait for auth credentials ---
   ```

   `new_string`:
   ```
     fi
   fi
   
   # Sanity-check: if the recovered (or pre-existing) binary reports 0.0.0, the
   # orphan was non-functional. Reinstall from npm to self-heal so a `restart`
   # unwedges the container without needing `docker compose down && up -d`.
   # Two-step form (capture then default) avoids the empty-vs-"0.0.0" pitfall
   # under set -euo pipefail when head -1 exits 0 on empty input.
   _CLAUDE_VER="$(claude --version 2>/dev/null | grep -oP '[0-9.]+' | head -1 || true)"
   _CLAUDE_VER="${_CLAUDE_VER:-0.0.0}"
   if [ "$_CLAUDE_VER" = "0.0.0" ]; then
     echo "[docker-entrypoint] claude binary is non-functional (version: 0.0.0) — reinstalling from npm..."
     if npm install -g @anthropic-ai/claude-code; then
       _CLAUDE_VER="$(claude --version 2>/dev/null | grep -oP '[0-9.]+' | head -1 || true)"
       _CLAUDE_VER="${_CLAUDE_VER:-0.0.0}"
       if [ "$_CLAUDE_VER" = "0.0.0" ]; then
         echo "[docker-entrypoint] ERROR: claude still reports 0.0.0 after reinstall."
         echo "[docker-entrypoint] Recreate the container: .claude-code-hermit/bin/hermit-docker down && .claude-code-hermit/bin/hermit-docker up"
         exit 1
       fi
       echo "[docker-entrypoint] Reinstall succeeded (v${_CLAUDE_VER})."
     else
       echo "[docker-entrypoint] ERROR: npm install failed — cannot recover the claude binary."
       echo "[docker-entrypoint] Recreate the container: .claude-code-hermit/bin/hermit-docker down && .claude-code-hermit/bin/hermit-docker up"
       exit 1
     fi
   fi
   unset _CLAUDE_VER
   
   # --- 0. Wait for auth credentials ---
   ```

   If the anchor does not match (operator has customized this file), tell them: "Auto-patch failed — anchor not found. Re-run `/claude-code-hermit:docker-setup` and choose 'Yes — back up' when prompted, or apply the block manually between the orphan-recovery `fi` and the `--- 0. Wait for auth credentials ---` comment."

5. **Rebuild the container.** Run `.claude-code-hermit/bin/hermit-docker update`. The patched entrypoint is baked into the image on rebuild.

## [1.0.34] - 2026-05-08

### Fixed

- **Plugin detection scoped to project/local only across five skills.** `/hatch` Step 1.5, `/docker-setup` Step 7b.1, `/docker-security` Step 3a, `/hermit-evolve` Step 7, and `/channel-setup` Step 3 all enumerated installed plugins without pinning `projectPath` to the current project root. `claude plugin list --json` returns entries for all projects in the operator's config, so a bare `scope == "local"` predicate leaked plugins from sibling repos. All five sites now apply the canonical filter: `enabled == true AND (scope == "project" OR scope == "local") AND projectPath == cwd`. User-scope plugins are explicitly dropped. `/hatch` and `/hermit-evolve` also replace the `${CLAUDE_PLUGIN_ROOT}/../*` disk glob with the JSON list so the install root is read from `installPath` rather than inferred by path proximity. `/hatch`'s `detected_hermits` stash now carries `installPath` so downstream Steps 3, 5a, 6, and Quick Turn 1 read `CLAUDE-APPEND.md`, `plugin.json`, and `OPERATOR-QUESTIONS.md` from the resolved install path instead of re-globbing.

- **`docker.recommended_plugins.marketplace` is always `org/repo`; entrypoint resolves the canonical name at boot.** The entrypoint was deriving the install target and on-disk cache key from `marketplace.split('/')[-1]`, which breaks when the marketplace `name` field differs from the repo basename (e.g. `openai/codex-plugin-cc` → name `openai-codex`). Official entries also stored the literal `"claude-plugins-official"` instead of an `org/repo`, mixing identifier types in the same field. The fix normalizes `marketplace` to always be `org/repo` (official is now `anthropics/claude-plugins-official`) and resolves the canonical marketplace name at boot via a single `claude plugin marketplace list --json` lookup — no schema bifurcation, no duplicated source of truth. The `is_safelisted` predicate folds back to a single arg (`marketplace`) since the literal-name shortcut is gone. Pre-v1.0.34 entries whose `marketplace` is not an `org/repo` get one warning at boot and are skipped; re-running `/docker-setup` rebuilds the entries cleanly from the host plugin list. The already-installed check in the entrypoint switches from bare substring to `❯ {target}` prefix match, preventing false positives where a short plugin name is a substring of a longer entry. Affects `/docker-setup` Step 7b, `/hermit-settings` docker section, docs, and entrypoint.

### Files affected

| File | Change |
|------|--------|
| `skills/hatch/SKILL.md` | Step 1.5 uses `claude plugin list --json` + filter; stash carries `installPath`; downstream steps read from `installPath` |
| `skills/docker-setup/SKILL.md` | Step 7b.1 filter tightened; dedupe rule added; Step 7b.3/5/10 updated to write `marketplace` as `org/repo` only |
| `skills/docker-security/SKILL.md` | Step 3a filter tightened; `path` field → `installPath` |
| `skills/hermit-evolve/SKILL.md` | Step 7 replaces disk glob with `claude plugin list --json` + filter; reads from `installPath` |
| `skills/channel-setup/SKILL.md` | Step 3 replaces unstructured grep with JSON + filter; adds `marketplace_name` gate; adds explicit `plugin enable` |
| `skills/hermit-settings/SKILL.md` | Display renders `org/repo`; `add` action writes `marketplace` as `org/repo` with dedupe-by-(plugin, marketplace) |
| `state-templates/docker/docker-entrypoint.hermit.sh.template` | Install loop resolves marketplace name from `claude plugin marketplace list --json` once at start; single warn-and-skip path for pre-v1.0.34 legacy entries; `❯` prefix match for already-installed guard |
| `docs/config-reference.md` | `recommended_plugins` schema documents `marketplace` as `org/repo` (canonical name resolved at boot); example entry updated |
| `docs/recommended-plugins.md` | Config format table documents `marketplace` as `org/repo` |

### Upgrade Instructions

Run `/claude-code-hermit:hermit-evolve`. The evolve skill handles:

1. **Refresh skill spec** — the updated skill text loads on the next invocation of each affected skill. No state files or templates change.

If you use Docker:

2. **Rebuild your container** — run `.claude-code-hermit/bin/hermit-docker update`. The new entrypoint resolves marketplace names at boot from the CLI's source of truth (`claude plugin marketplace list --json`) instead of guessing from the repo basename.

3. **Re-run `/claude-code-hermit:docker-setup` once** — only required if your existing `docker.recommended_plugins` has any entry where `marketplace` is not an `org/repo` (the most common case is pre-v1.0.34 official entries that store the literal `"claude-plugins-official"`). The new entrypoint warns once at boot and skips such entries; re-running `/docker-setup` rebuilds the entries cleanly from the current host plugin list. Skip this step if every `marketplace` value in your config already contains a `/`.

## [1.0.33] - 2026-05-07

### Changed

- **Right-sized thinking budgets across `reflect`, `reflection-judge`, and `proposal-create`.** `agents/reflection-judge.md` drops the `ultrathink` keyword — the frontmatter `effort: medium` is now the sole, coherent control surface for that agent. `skills/reflect/SKILL.md` downgrades `ultrathink` to `think hard` (~10K vs ~32K token budget), appropriate for cross-session synthesis. `skills/proposal-create/SKILL.md` drops the keyword entirely from the body-writing step (structured drafting, no escalation needed) and downgrades to `think hard` for the capability-plan branch (designing new agents/skills). Reduces routine-path cost without compromising gating or synthesis quality.

### Files affected

| File | Change |
|------|--------|
| `agents/reflection-judge.md` | Replaces `ultrathink` line with plain "reason carefully" instruction |
| `skills/reflect/SKILL.md` | Downgrades `ultrathink` to `think hard` |
| `skills/proposal-create/SKILL.md` | Drops `ultrathink` from body-writing step; downgrades to `think hard` for capability-plan branch |

### Upgrade Instructions

No upgrade actions required. Skill and agent text changes propagate via plugin update — no `config.json`, `runtime.json`, `state-templates/`, or operator-editable file changes.

## [1.0.32] - 2026-05-07

### Added

- **Memory-first for suggestions.** Suggestion-generating skills (`brief`, `reflect`, `weekly-review`, `proposal-create`, `session-start`) and the `proposal-triage` / `reflection-judge` subagents now consult auto-memory before declaring a finding novel. Convention lives in `state-templates/CLAUDE-APPEND.md` and propagates to existing installs via the standard `/hermit-evolve` Step 6 anchored-block sync. Both subagents gain an explicit Memory cross-reference / cross-check step and a new canonical suppress code, `covered-by-memory`. Acting-on-decided-intent skills (`session-close`, `proposal-act`, `hermit-routines`, `hatch`) are exempt by design.

### Changed

- **`agents/proposal-triage.md`** — adds Step 1.5 (Memory cross-reference) between Deduplication and Session cross-reference; SUPPRESS code list extended with `covered-by-memory`; new `memory_ref: <filename>` metadata field emitted alongside that verdict.
- **`agents/reflection-judge.md`** — adds `### 1.5 Memory cross-check` between Evidence verification and Tier check; canonical suppress codes extended with `covered-by-memory`; reason includes `[memory: <filename>]` breadcrumb so operators can locate the source. The §0 Evidence-Source dispatch routes `scheduled-check/*` and `operator-request` through §1.5 before §2 Tier check, so the memory cross-check applies to every source — not only `archived-session` / `current-session`.
- **`agents/proposal-triage.md` Step 5 preamble** — clarifies that the Three-Condition Rule runs only when no duplicate *and* no memory match was found, matching the new Step 1.5 short-circuit.

### Files affected

| File | Change |
|------|--------|
| `agents/proposal-triage.md` | Adds Step 1.5 memory cross-reference, `covered-by-memory` code, `memory_ref` metadata |
| `agents/reflection-judge.md` | Adds §1.5 memory cross-check, `covered-by-memory` code, `[memory:]` breadcrumb |
| `state-templates/CLAUDE-APPEND.md` | Adds Memory-first paragraph in Knowledge Discipline section |

### Upgrade Instructions

Run `/claude-code-hermit:hermit-evolve`. The evolve skill executes:

1. **Refresh the CLAUDE-APPEND anchored block.** Step 6 reads `state-templates/CLAUDE-APPEND.md` and replaces the marker→EOF block in the project's `CLAUDE.md`. The new Memory-first paragraph propagates idempotently.

No `config.json` changes required.

## [1.0.31] - 2026-05-07

### Fixed

- **Remove quick-task gate from session-triggered scheduled checks** — the "no tasks created, under 5 minutes" skip caused `revise-claude-md` (when installed) to miss short declarative operator corrections (e.g. "from now on never X"), which are exactly the sessions with the highest-signal CLAUDE.md updates.

### Changed

- **`reflect` operator-value self-check now covers micro-proposals.** The dismiss-ratio tally in step 5 now counts `micro-resolved` events (approved / rejected / expired) alongside `responded` events. The self-check distinguishes `rejected` (noise signal — pare back output) from `expired` (timing signal — adjust question scheduling). Previously the check was blind to Tier 1/2 micro-approval traffic, which is reflect's highest-volume output.
- **`runtime.json` schema gains `last_shell_snapshot_at`** (ISO or null). Owned by `archive-shell.js`. Used for the 24h dedup gate on routine SHELL.md snapshots.

### Added

- **Guild/group channel setup in `docker-setup` and `channel-setup`.** After DM pairing completes, both skills now offer an optional step to register one or more Discord server channels or Telegram group chats via `/<plugin>:access group add`. Each channel gets its own `requireMention` choice (default: true — require @mention, safer for noisy shared channels). Supports multiple channels per run via a loop; verification reads `access.json` once after all channels are added rather than on every iteration.
- **Reflect lessons-to-memory pass.** Reflect's existing Memory update outcome now explicitly covers durable lessons (operator-stated rules, preferences that recurred, decision rationales) alongside sub-threshold patterns. Uses Claude's trained auto-memory flow ("remember it"). No new infrastructure — extension of existing reflect outcomes.
- **Mechanical SHELL snapshot.** When SHELL.md exceeds 400 lines AND ≥24h has elapsed since the last snapshot, `reflect-precheck.js` invokes `scripts/archive-shell.js` to snapshot SHELL.md to `sessions/snapshots/SHELL-YYYYMMDD-HHMM.md` and compact the archived portion of `## Progress Log` to a one-line pointer. Bounds always-on SHELL.md growth without operator action. Pure JS, no LLM path — when snapshot is the only work due, the reflect skill never runs. Lifecycle reports (`S-NNN-REPORT.md`, owned by session-mgr) and routine snapshots live in different namespaces and share no machinery.
- **`scripts/archive-shell.js`** — new helper that snapshots SHELL.md, inserts a marker, compacts the pre-marker Progress Log, and updates `runtime.json.last_shell_snapshot_at`. Snapshot stages to a per-PID tmp then `link()`s onto the final path; `link()` is atomic and fails with `EEXIST` if another writer raced — that doubles as the concurrency lock and guarantees no partial snapshots survive a crash. SHELL.md and `runtime.json` use `tmp + rename`. If SHELL.md lacks the `## Progress Log` heading, the snapshot still fires but the JSON returns `compacted: false` and a warning hits stderr (no silent no-op). Stdout returns one JSON object; exits 0 always.
- **`reflect-precheck` gates `phases.archive_due` on `archiveTaken`.** When the archive subprocess fails (concurrent collision, permission error), `archive_due` is omitted from the phases JSON so the LLM doesn't waste tokens reasoning about a snapshot that didn't land. The `onlyArchive` short-circuit still uses `archiveDue` for control flow.

### Upgrade Instructions

Run `/claude-code-hermit:hermit-evolve`. The evolve skill executes:

1. **Add `last_shell_snapshot_at` to `runtime.json`.** Read `.claude-code-hermit/state/runtime.json`. If the file exists and does not contain the key `last_shell_snapshot_at`, add `"last_shell_snapshot_at": null` and write back atomically (temp file + rename). Idempotent.
2. **Add `archive-shell.js` permission to project settings.** Read `.claude/settings.json` (or `.claude/settings.local.json`, whichever holds the plugin allowlist). If `permissions.allow` does not contain `Bash(node */scripts/archive-shell.js*)`, append it. Idempotent.

No routine changes. No `config.json` changes. `/session-close` behavior unchanged.

## [1.0.30] - 2026-05-05

### Removed

- **`/docker-security` Prompt 2 (read-only root filesystem).** The toggle was breaking Claude Code auth: hermits with `read_only: true` 401'd with `Invalid authentication credentials` once the OAuth access token expired (~8h after `/login`), because the credential refresh write path failed silently under the current tmpfs / named-volume layout. The other three toggles (LAN containment, resource bounds, audit log) are unaffected and remain in the wizard.

### Fixed

- **`hermit-start`: pass bootstrap as `claude` argv instead of `tmux send-keys`.**
  Eliminates a race where, on slow always-on boots (cold plugin cache, container
  first-run), the send-keys-injected bootstrap landed before Claude Code's TUI was
  ready and was silently swallowed — heartbeat, routines, and session-start all
  failed to register. `claude` accepts a positional prompt that runs as the first
  turn of an interactive REPL (per `claude --help`), so the bootstrap is now passed
  as argv. Same long-lived process, no inter-process timing window.

- **`hermit-docker restart` fails under the security overlay.** `docker compose restart` restarts services in parallel and ignores `depends_on`, so the hermit container tried to rejoin the netguard netns while netguard was briefly exited — producing "cannot join network namespace of a non running container". Fixed by replacing `compose restart` with `down && up -d`, which honors dependency order on start. Behavior is identical to `hermit-docker down && hermit-docker up`, which already worked.

- **`/docker-setup` Step 8 `ackReaction` race.** The `set ackReaction` tmux command was sent immediately before the Step 8b shutdown, leaving no time for the in-container LLM turn to write the value to `access.json`. Replaced the tmux send-keys round-trip with a direct host-side edit of `.claude.local/channels/<plugin>/access.json` — the bind-mount makes it visible in the container immediately, with no race.

- **PR-review polish on the three fixes above.** `hermit-docker restart` now explicitly rejects service args (the old `restart <service>` semantics aren't preserved by the new `down && up -d` chain — silent partial restart was a footgun). `hermit-start` bootstrap now only fires in always-on/tmux mode — interactive runs (`--no-tmux` or no tmux installed) are operator-driven and no longer auto-fire heartbeat/routines/session-start. `/docker-setup` Step 8 ackReaction instruction tightened to specify read-modify-write via `Read` + `Edit` (preserve all other keys, do not overwrite the file). `/docker-security` SKILL step renumbering completed: subheadings `7a/b/c/d` → `6a/b/c/d` and one stale "Step 10" reference fixed.

### Upgrade Instructions

Run `/claude-code-hermit:hermit-evolve`. The evolve skill handles:

1. **Delete `docker.security.read_only` from `.claude-code-hermit/config.json`** — the key is now inert and surfaces as a stale `/hermit-doctor` warning.

**Note:** If that key had `enabled: true`, the container is still running with `read_only: true`. Re-run `/claude-code-hermit:docker-security`, answer through the remaining prompts, then `hermit-docker down && hermit-docker up`. Existing `claude-config` volume and credentials are preserved.

No other `config.json` changes required.

## [1.0.29] - 2026-05-04

### Added

- **`/hatch` Quick mode** — opt-in fast path that collapses the full ~9-question wizard + OPERATOR.md questionnaire into 5 batched turns (identity batch, sign-off+deployment+channel batch, OPERATOR.md questionnaire, confirm bundle), then auto-chains into the next deployment skill (`/docker-setup quick`, `/channel-setup`, or `/session`) based on the operator's deployment + channel choices. Advanced wizard remains available unchanged for power users — picked from a setup-mode gate at the top of hatch. Customize from the Quick confirm screen restarts Advanced from scratch (no prefill). Re-init forces Advanced (Quick is for first-time install).
- **`/docker-setup quick` positional arg** — invoke `/claude-code-hermit:docker-setup quick` to skip the setup-mode gate and run Quick directly. Quick defaults: OAuth + bridge networking, auto-mirror SAFE plugins (`claude-plugins-official` + `gtapps/*`), auto-accept apt packages from safelisted sources, build immediately. **Security non-negotiables preserved**: third-party plugins still confirmed per-entry, public-repo pre-flight still runs, write-time assertion still fires, safelist boundary unchanged.
- **`tests/test-template-skill-sync.sh`** — monorepo-internal contract test that asserts every top-level key in `state-templates/config.json.template` is referenced in `skills/hatch/SKILL.md`. Catches the failure mode where a new template field lands without hatch knowing about it (which would silently drop the field from operator configs at hatch time). Does NOT enforce a schema on operator-owned `.claude-code-hermit/config.json` — operators are free to add custom keys, remove fields, or hand-edit anytime. Wired into `tests/run-all.sh`.

### Changed

- **`/channel-setup` Step 5: collapse two-question batch into single 3-option question.** Replaces the awkward "Yes — ready to pair / Skip" + "Not yet / Already paired" pair with one question: `Already paired` / `Ready to pair` / `Skip`. Removes the "Yes + Already paired" combo (which today silently skipped pairing despite the operator answering "ready"). Saves one round trip per pairing.
- **`/hatch` Step 5: source-of-truth refactor.** Step 5 now reads `state-templates/config.json.template` as the base config and overlays operator choices, instead of duplicating an inline default JSON object. The inline JSON had drifted (missing `model`, `always_on`, `chrome`, `monitors`, `compact`, `knowledge`, `auto_session`, `idle_budget`, `env`). Quick mode and Advanced wizard both use the same overlay-on-template algorithm. The new template-skill sync test (above) prevents future drift.
- **`/hatch` resequenced.** Setup-mode gate moved to before Step 2 (file writes), so the operator's mode choice precedes any disk writes. Step 3 (hermit detection) split into silent detection (pre-flight, no prompt) and activation prompt (Advanced flow only — Quick handles activation in Quick Turn 1 from the cached candidate list).
- **`/docker-setup` Step 4 template rendering deferred to new Step 7b.6.** Template rendering (Dockerfile.hermit, docker-compose.hermit.yml, docker-entrypoint.hermit.sh) now runs AFTER plugin resolution + apt-package union (Step 7b.packages), instead of at Step 4 — fixing a latent ordering concern where `{{PACKAGES_BLOCK}}` substitution today consumes `docker.packages` before it's finalized. Applies to both Quick and Advanced.

### Fixed

- **`/docker-security`: DNS containment hardening.** Four bugs that broke strict DNS policy: (1) `dnsmasq.allowlist.template` was missing `no-resolv`, so blocked queries fell back to `/etc/resolv.conf → 127.0.0.11` (Docker DNS, unreachable inside the sidecar's netns), turning NXDOMAIN blocks into ~5s timeouts instead of instant rejections; (2) `claude.ai` and `claude.com` were absent from the allowlist, causing `ECONNREFUSED` in Node during OAuth login; (3) the DNS-block verifier in the verification script used a plain `grep-on-stderr` pattern that silently misclassified timeouts as "NOT ENFORCED" instead of surfacing the DNS leak; (4) the RO-write canary wrote to `/home/claude/.hermit-canary` (on the read-only root) instead of `/home/claude/.cache/.hermit-canary` (on the writable tmpfs mount). Verification script also now uses `mktemp + trap EXIT` instead of a fixed `/tmp/verify-dns.err` path to avoid concurrent-run collisions.
- **`/docker-security` step 7c: force `--no-cache` netguard build.** The wizard previously relied on `hermit-docker up` to rebuild `hermit-netguard`, but Docker's layer cache silently preserved stale images across hermit upgrades. Wizard now runs an explicit `timeout 180s docker compose ... build --no-cache hermit-netguard` after rendering the overlay files and stops with surfaced logs on non-zero exit.
- **`/docker-security` tune instruction: `hermit-docker down && hermit-docker up`.** All three surfaces (SKILL.md, docs, allowlist template) previously told operators to `hermit-docker restart hermit-netguard` after editing the DNS allowlist. Restarting only the sidecar leaves hermit with stale resolver/conntrack state — DNS fails for all domains until hermit itself is bounced. Updated everywhere to `hermit-docker down && hermit-docker up`.
- **`tests/test-docker-security-templates.sh`: expanded coverage.** Added 12 new assertions covering the `no-resolv` directive, `claude.ai`/`claude.com` allowlist entries, DNS-block timeout classification, RO-write canary path, `--no-cache` rebuild, and the down-and-up tune instruction (both SKILL.md and docs). Also removed a stale `(line 37 area)` annotation from one test label.
- **`tests/test-template-skill-sync.sh`: explicit `exit 1` + cached skill read.** Parse-failure branch previously relied on `print_results`'s side-effect `exit 1` rather than declaring intent — added explicit `exit 1` after the call. Also cached `SKILL_CONTENT` into a variable so the loop reads the skill file once instead of forking a `grep` subprocess per key.
- **`hatch/SKILL.md` Phase 6 trailing comma.** The `AskUserQuestion` block had a trailing comma after the last question object before `]` — an invalid JSON payload that any strict executor would reject.
- **`hatch/SKILL.md` Quick defaults table: corrected cross-references.** Source column used "Step 4 Phase X" labels that don't exist in the Quick branch (Quick never runs Step 4). Changed to "Advanced Phase X equivalent".
- **`docker-setup/SKILL.md`: removed sub-step number collision.** "3. Project dependencies:" inside Step 2's body shadowed the top-level `### 3.` heading — renamed to `**Project dependencies scan:**`.
- **`channel-setup/SKILL.md` Step 5: removed redundant parenthetical** from the question text (restart instructions already appear in the prose immediately above).
- **`channel-setup/SKILL.md` Step 6 → 6b: added routing.** Step 6 had no "continue to step 6b" exit line — a model executing the skill could skip 6b entirely.
- **`hatch/SKILL.md` Quick Turn 5: removed stale CHANGELOG cross-reference.** The "full mock in CHANGELOG `[Unreleased]`" pointer was inaccurate (the section contains a description, not a full mock) and would go stale after release. The inline template block is self-contained.

### Verification

End-to-end manual verification (run before release):

- Fresh project, Quick + Docker + Discord (most common path): `/hatch` → pick Quick → answer 5 batched turns → confirm → auto-chains to `/docker-setup quick`. Verify `.claude-code-hermit/` is NOT created before the gate is answered (ls before answering). Total: ≤14 round trips end-to-end.
- Fresh project, Advanced unchanged: `/hatch` → pick Advanced → expect every current question still asked, every file still written.
- Re-init guard: in a project with existing `.claude-code-hermit/`, `/hatch` → pick re-initialize → verify the setup-mode gate is NOT shown; Advanced wizard runs directly. OPERATOR.md preserved unless operator chose regenerate.
- Customize escape hatch: in Quick at the confirm screen, pick Customize → expect Advanced wizard runs from scratch with no prefill.
- Channel-setup Step 5: `/channel-setup` after Quick hatch → expect single 3-option question (Already paired / Ready to pair / Skip).
- Docker template render ordering: in Advanced mode, choose project apt packages that differ from defaults → verify rendered `Dockerfile.hermit` `{{PACKAGES_BLOCK}}` substitution matches the FINAL `docker.packages` array.
- Security regression checks (Docker Quick mode): per-plugin yes/no still asked for any third-party marketplace plugin (not bulk-accepted); validator regex still rejects malformed `org/repo`; public-repo pre-flight curl still gates private GitHub repos.
- Existing tests: `cd plugins/claude-code-hermit && bash tests/run-all.sh` → all suites pass including the new `test-template-skill-sync.sh` (28 assertions).

### Files affected

| File | Change |
|------|--------|
| `skills/hatch/SKILL.md` | Resequence (gate before writes), split silent detection from activation prompt, replace inline default JSON with template-overlay algorithm, fix scheduled_checks accounting, add Quick Branch section (5 turns + confirm + auto-chain), add Quick-mode adjustment to Step 10 report |
| `skills/docker-setup/SKILL.md` | Add Step 1.5 setup-mode gate (positional `quick` arg supported), apply Quick defaults silently throughout (auth, network, SAFE plugin mirror, apt auto-accept, build-now), add Step 7b.6 deferred template rendering (fixes latent `{{PACKAGES_BLOCK}}` sequencing concern; applies to both modes) |
| `skills/channel-setup/SKILL.md` | Step 5 batched question collapsed to single 3-option `AskUserQuestion` |
| `skills/docker-security/SKILL.md` | DNS-block verifier: timeout + exit-code 124 classification + mktemp/trap; RO-write canary path; --no-cache rebuild step; tune instruction updated |
| `docs/docker-security.md` | Tune instruction: `hermit-docker down && hermit-docker up`; mDNS wording simplified |
| `state-templates/docker/security/dnsmasq.allowlist.template` | Add `no-resolv`; add `claude.ai` and `claude.com`; update catchall comment |
| `tests/test-template-skill-sync.sh` | New: monorepo-internal contract test for template ↔ hatch sync |
| `tests/run-all.sh` | Wire new test into the suite |
| `tests/test-docker-security-templates.sh` | 12 new assertions: no-resolv, OAuth domains, DNS-block timeout, canary path, --no-cache rebuild, tune instruction |

### Upgrade Instructions

Run `/claude-code-hermit:hermit-evolve`. The evolve skill handles:

1. **Skip if no docker-security overlay** — if `docker-compose.security.yml` does not exist at the project root, steps 2–4 are no-ops.
2. **Re-render the security overlay** — run `/claude-code-hermit:docker-security` and accept the same toggles already enabled. The wizard re-renders `dnsmasq.allowlist` with `no-resolv` and the new `claude.ai`/`claude.com` entries, and forces a `--no-cache` netguard rebuild.
3. **Restart hermit** — run `hermit-docker down && hermit-docker up` after the wizard completes (the wizard prompts for this).
4. **Verify** — `/claude-code-hermit:docker-security` verification block should show `DNS-block: OK` (NXDOMAIN, not timeout) and `DNS-allow: OK`.

No `config.json` changes required.

## [1.0.28] - 2026-05-04

### Fixed

- **docker-security netguard: rootless Docker startup is finally clean.** Four interlocking bugs (the first masking the others) prevented `hermit-netguard` from coming up cleanly under rootless Docker. Operators with the v1.0.27 overlay saw cryptic "dnsmasq failed to stay up" errors even when dnsmasq was alive, plus 30-second startup latency.
  - **Volume mount.** Removed the `state:/var/log/netguard` bind mount. Under rootless Docker the host UID does not match the container UID, and the host's `state/` directory (mode 0775) is unwritable by the container — so the entrypoint's `tee -a /var/log/netguard/netguard.log` died on first write. The sidecar now logs to stdout only, viewable via `docker compose ... logs hermit-netguard`.
  - **PID capture.** Replaced `DNSMASQ_PID=$!; kill -0 "$DNSMASQ_PID"` with `pgrep dnsmasq`. After `dnsmasq | tee &`, `$!` returns the PID of `tee`, not `dnsmasq` — so the "stay up" check was inadvertently checking tee. Worse, when bug 1 killed tee on log-open, this check fired a false negative.
  - **Capability set.** `cap_add: [NET_ADMIN]` was insufficient. Added `NET_BIND_SERVICE` (dnsmasq retains this cap across its post-bind drop), `SETUID`, and `SETGID` (Alpine's dnsmasq drops to UID/GID 100 — needs both). Without these, dnsmasq exited 5 with "failed to set capability vector".
  - **Healthcheck latency.** Added `start_period: 5s` and lowered `interval` from 30s to 10s. Container now reaches `healthy` in ~10s instead of waiting a full 30s for the first probe.
- **docker-security netguard entrypoint: dnsmasq queries now reach `docker logs`.** Added `--log-facility=-` to both dnsmasq invocations. Without it, `--log-queries` defaults to syslog, which silently drops in an Alpine container with no syslogd — making log-only mode useless for tuning the allowlist.

### Files affected

| File | Change |
|------|--------|
| `state-templates/docker/security/netguard-entrypoint.sh.template` | Drop tee + file logging + `$!` PID capture; add `--log-facility=-`; replace `kill -0` with `pgrep dnsmasq` |
| `skills/docker-security/SKILL.md` | cap_add list (4 caps), drop `state:/var/log/netguard` volume, healthcheck `start_period: 5s` + `interval: 10s` |
| `docs/docker-security.md` | DNS tuning section: stdout instead of `state/dns.log`; correct "blocked-by-policy" wording |
| `tests/test-docker-security-templates.sh` | New: contract test for entrypoint + SKILL.md regression patterns |

### Upgrade Instructions

1. **Skip if no docker-security overlay.** If `docker-compose.security.yml` does not exist at the project root, this entry is a no-op.
2. **Re-render the overlay.** Run `/claude-code-hermit:docker-security` and accept the same toggles already enabled — the wizard re-renders the overlay with the corrected `cap_add` list, no `state:/var/log/netguard` bind, and the new healthcheck.
3. **Rebuild the netguard image.** Run `bin/hermit-docker down && bin/hermit-docker up`. Compose rebuilds `hermit-netguard` because the entrypoint template content changed.
4. **Verify.** `docker compose -f docker-compose.hermit.yml -f docker-compose.security.yml ps` should show `hermit-netguard` as `healthy` within ~10s.

No `config.json` changes required.

## [1.0.27] - 2026-05-04

### Fixed
- **docker-security: port-conflict guard** — detect operator-added `ports:` on `hermit` and offer to move them to `hermit-netguard` (the netns owner) when LAN containment is enabled. Wizard hard-gates `hermit-docker up` until the operator removes the base `ports:` block, preventing the `conflicting options: port publishing and the container type network mode` error.
- **docker-security: auto-pick free subnet** — scan all host Docker networks and walk `172.28-31` then `10.244-247` before prompting, instead of hardcoding `172.28.0.0/24`. Eliminates `Pool overlaps with other one on this address space` on hosts with multiple hermit projects or colliding networks.
- **docker-security: `publish_ports` survives reruns** — operators who removed the base `ports:` block on a previous run no longer lose the netguard publish mapping on the next wizard pass.
- **docker-security: early daemon guard** — `docker info` preflight now exits with a clear message instead of cryptic subprocess errors when the Docker daemon is unreachable.

### Added
- **hermit-doctor: expanded docker-security check** — now flags subnet collisions (`warn`) and hermit-side `ports:` blocks that conflict with LAN containment (`fail`). Daemon-unreachable degrades to `warn` rather than `fail`. Existing 8-check structure unchanged.
- **Container guard for host-only skills** — `docker-setup`, `docker-security`, `hermit-takeover`, and `hermit-hand-back` each detect `/.dockerenv` / `/run/.containerenv` at step 0 and refuse to run inside the container, printing a redirect to the correct vantage point. Prevents partial-success file writes that corrupt host scaffolding when invoked from inside the hermit container.

### Changed
- **docker-security: design rationale relocated** — limitations, DNS allowlist tuning, and reversal prose moved from the skill body into `docs/docker-security.md`. SKILL.md trimmed from 572 → 552 lines; a pointer to the docs URL is the only reference kept in the skill.

### Files affected

| File | Change |
|------|--------|
| `skills/docker-security/SKILL.md` | Container guard, port-conflict + subnet fix, rationale relocated |
| `skills/docker-setup/SKILL.md` | Container guard at step 0 |
| `skills/hermit-takeover/SKILL.md` | Container guard at step 0 |
| `skills/hermit-hand-back/SKILL.md` | Container guard at step 0 |
| `scripts/doctor-check.js` | docker-security check: subnet collision + ports-conflict branches |
| `docs/docker-security.md` | New: design rationale, limitations, DNS tuning, reversal guide |
| `tests/run-hooks.sh` | New docker-security check test cases |

### Upgrade Instructions

1. Run `/claude-code-hermit:hermit-doctor`.
2. If the `docker-security` check surfaces a WARN or FAIL, run `/claude-code-hermit:docker-security` and accept the defaults.
3. Run `hermit-docker down && hermit-docker up`.

**Note:** Operators without a docker-security overlay need no action.

No `config.json` changes required.

## [1.0.26] - 2026-05-03

### Fixed

- **hermit-routines: routine schedules now shift from `config.timezone` to machine timezone** before CronCreate registration. Previously the routines pipeline ignored `config.timezone`, so a UTC server with `timezone: "Europe/Lisbon"` would fire daily routines at the wrong wall-clock hour during BST. A new `scripts/cron-tz-shift.js` helper handles the conversion with minute-granularity offsets (30/45-minute IANA zones work), automatic DOW adjustment on day-wrap, and fail-open pass-through for patterns that can't collapse into a single cron. DST self-corrects within 24h via the daily `heartbeat-restart` reload.

- **`hermit-doctor` `docker-security` check: anchor overlay path to the agent dir, not `process.cwd()`.** The check was resolving `docker-compose.security.yml` relative to the current working directory, which diverged from the rest of doctor-check (which derives every path from `hermitDir` / argv). Invoking doctor with an explicit agent-dir argument from a different CWD would have looked in the wrong place and falsely reported "not configured." Now uses `path.join(hermitDir, '..', 'docker-compose.security.yml')` for consistency with the file's other path constants.

### Added

- **Container hardening**: docker-compose template now blocks setuid escalation (`security_opt: no-new-privileges:true`), drops all Linux capabilities (`cap_drop: ALL`), and caps process count (`pids_limit: 2048`). Defense in depth for `bypassPermissions` containers. The load-bearing stanza is `no-new-privileges` — it closes the setuid-escalation vector against future supply-chain compromise of any installed plugin; the other two are incremental ambient-surface reductions. Verified against entrypoint, hermit-start, and plugin-install paths; nothing in the runtime needs the dropped capabilities or setuid binaries, and steady-state PID usage sits well under the cap.

- **`/claude-code-hermit:docker-security` advanced wizard**: opt-in hardening for already-deployed hermit containers. Four toggles, each with honest cost/benefit framing: (1) **LAN containment + DNS policy** — firewall + DNS sidecar (`hermit-netguard`) sharing hermit's network namespace, with nftables-driven port-53 redirect for *actual* DNS-policy enforcement (not just resolver hints); (2) **read-only root filesystem** with concrete smoke test (real `npm install` + plugin add/remove + claude-owned canary write before persisting); (3) **resource bounds + kernel hygiene** (`mem_limit`, `cpus`, network sysctls — sysctl placement is conditional on whether the netguard sidecar owns the netns); (4) **boot-time plugin install audit log**. Applied as a `docker-compose.security.yml` overlay — never modifies the base compose. Hard-skips the LAN containment prompt when `docker.network_mode: "host"` (would break host-bound HA workflows). Fleet-aware: scans installed fleet plugins for `## Docker network requirements` declarations and offers their domains/LAN suggestions for per-entry confirmation. Documented limitations: public-IP egress is not blocked (v1.1 may add nftset-driven IP allowlisting); Docker default bridge falls in the LAN drop range; Compose service-name DNS and mDNS don't work through dnsmasq.

- **`hermit-doctor`** gains an eighth check, `docker-security`, that flags drift between declared `docker.security.*` posture in `config.json` and the presence of `docker-compose.security.yml`. Two-state presence check; no YAML parsing.

- **`hermit-docker`** wrapper: pins `SERVICE="hermit"` explicitly (was deriving from `docker compose config --services | head -1`, which becomes ambiguous once the security overlay introduces a netguard sidecar — `bash`/`login`/`attach` could land on the wrong service). Also auto-detects `docker-compose.security.yml` and chains it onto every compose command. No effect when the overlay is absent.

- **Per-fleet-plugin contract**: plugins can declare network requirements in a `## Docker network requirements` section in `skills/hatch/SKILL.md` or `DOCKER.md` (mirrors the existing `## Docker apt dependencies` pattern). The `/docker-security` wizard reads these and offers per-entry confirmation. Special token `ASK_OPERATOR_FOR_<NAME>_IP` triggers an operator IP prompt for plugin-specific LAN endpoints. Backward-compatible: plugins without the section contribute nothing.

### Files affected

| File | Change |
|------|--------|
| `scripts/cron-tz-shift.js` | New: shifts 5-field cron between IANA timezones |
| `scripts/lib/time.js` | Extended `currentHHMM` to accept optional `ref` date |
| `skills/hermit-routines/SKILL.md` | `load` now invokes `cron-tz-shift.js` per routine |
| `skills/hatch/SKILL.md` | Added `cron-tz-shift.js` to Bash allowlist |
| `skills/hermit-evolve/SKILL.md` | Added `cron-tz-shift.js` to required node entries list |
| `skills/smoke-test/SKILL.md` | Added step 4b: helper availability check |
| `skills/docker-security/SKILL.md` | New: opt-in hardening wizard (LAN, ro-fs, resource bounds, audit log) |
| `scripts/doctor-check.js` | Eighth check: `docker-security` posture vs overlay drift |
| `state-templates/bin/hermit-docker` | Pins `SERVICE=hermit`, chains security overlay when present |
| `docs/config-reference.md` | Updated `routines.schedule` TZ description |
| `tests/cron-tz-shift.test.sh` | New: 20 deterministic cron-shift tests |
| `tests/run-hooks.sh` | Updated doctor-check test: 7 → 8 checks |

### Upgrade Instructions

Run `/claude-code-hermit:hermit-evolve`. The evolve skill handles:

1. **Add `cron-tz-shift.js` to the Bash allowlist.** Add `Bash(node */scripts/cron-tz-shift.js*)` to `.claude/settings.json` `permissions.allow` alongside the other `node */scripts/...` entries.

2. **Re-register routines.** Run `/claude-code-hermit:hermit-routines load` to apply the timezone shift to existing CronCreate registrations.

3. **Skip if no Docker scaffolding.** If `docker-compose.hermit.yml` does not exist at the project root, this entry is a no-op.

4. **Ask the operator for consent.** Use `AskUserQuestion` (header: `"Container hardening"`):
   - **Yes — apply hardening** (Recommended): proceed to step 5.
   - **Skip**: do nothing for this entry; continue with the rest of evolve.

   Hardening is opt-in because operators may have customized their compose for special workloads (privileged ports, larger PID needs).

5. **Idempotency check.** Read the rendered `docker-compose.hermit.yml`. If it contains the literal string `no-new-privileges`, skip — already migrated. Tell the operator: "Container hardening already in place, skipping." (If it contains `cap_drop` or `pids_limit` but not `no-new-privileges`, a partial previous attempt may have stalled — show the operator the current file and ask them to patch it manually or re-run `/docker-setup` with backup.)

6. **Locate the insertion point.** Find the `hermit:` service block. Within it, locate the `restart:` line at 4-space indent. If either is missing or the structure is ambiguous (e.g. service renamed, restart removed, indentation drift), do NOT attempt the patch — fall through to step 8.

7. **Patch.** Insert the following three stanzas immediately before the `restart:` line, indented to match adjacent service keys (4 spaces in the standard template). Show the diff to the operator and ask for final confirmation before writing:

   ```yaml
   cap_drop:
     - ALL
   security_opt:
     - no-new-privileges:true
   pids_limit: 2048
   ```

   *(Shown unindented for clarity — in the file each line gets 4 leading spaces, same level as `restart:` and `stop_grace_period:`.)*

   On confirm: write the file. Then jump to step 9.

8. **Fallback for unrecognized structure.** Tell the operator:

   > "Your `docker-compose.hermit.yml` has been customized — I can't patch it safely. Re-run `/claude-code-hermit:docker-setup` and choose **'Yes — back up'** when prompted to regenerate it cleanly with the new hardening defaults. Your customizations will be preserved in `docker-backup/` so you can re-apply them on top."

   No further action.

9. **Container recreation reminder (CRITICAL).** Tell the operator:

   > "**`hermit-docker restart` is NOT enough** — Docker only applies `cap_drop`, `security_opt`, and `pids_limit` at container creation, not on restart. To activate the new settings, run:
   >
   > ```
   > .claude-code-hermit/bin/hermit-docker down
   > .claude-code-hermit/bin/hermit-docker up
   > ```
   >
   > The named config volume preserves credentials, plugins, and onboarding state."

No `config.json` changes required.

10. **Inform the operator about the new advanced wizard (no automatic action).** After steps 1–9 complete (or are skipped), tell them:

   > "v1.0.26 also ships an opt-in advanced wizard, `/claude-code-hermit:docker-security`, for stronger isolation than the baseline. The headline gain is blocking your container from reaching your local network — meaningful if you run hermit on a home or office machine alongside HA, NAS, printer, etc. Run `/claude-code-hermit:docker-security` when you're ready; nothing changes until you do. See [`docs/docker-security.md`](docs/docker-security.md) for the full toggle reference and documented limitations."

   This step is informational only — the wizard is opt-in by design, never invoked automatically by `/hermit-evolve`.

## [1.0.25] - 2026-05-01

### Changed

- **`reflect`, `cortex-sync`: delegate recon-heavy scans to the built-in `Explore` subagent.** `reflect` delegates three inline Glob+Read sequences to `Explore`: the full proposal scan (step 5), the resolution-check session fetch (step 6d), and the session-citation half of the routine-silence check. `cortex-sync` delegates its Step 1 gap scan and Step 3 tag-vocabulary scan. The orchestrating context receives compact summaries instead of raw file contents. All callers of the Explore delegation still hold the interpret/act logic locally — `Explore` is a read-only recon layer. The step 6d delegation prompt instructs Explore to return session bodies verbatim and report (rather than silently trim) any file that exceeds its read window; the orchestrator falls back to inline `Read` for any truncated file before evaluating step e, since the resolution check is correctness-sensitive.

- **`proposal-triage`: extended evidence scope, richer verdict output.** Three new pre-gate steps run between dedup and the three-condition check: (1) session cross-reference — the 3 most recent session reports are scanned for prior discussion of the candidate; (2) OPERATOR.md lexical alignment check — explicit "don't/avoid/decided not to" language near candidate title keywords surfaces as `aligned: false` + `operator_excerpt`; (3) compiled artifact overlap scan — compiled/ frontmatter is checked for artifacts that already address the topic. `SUPPRESS` verdicts now include a quoted excerpt from the candidate evidence. Additive metadata lines after the verdict (`closest_prop`, `aligned`, `operator_excerpt`, `overlap_compiled`, `prior_discussion`, `failed_condition`) give callers actionable context without changing the branching contract. `maxTurns` bumped from 8 → 14.

- **`reflect`, `reflect-scheduled-checks`, `proposal-create`: triage verdict counters.** All three callers now append a `triage-verdict` event to `state/proposal-metrics.jsonl` after each triage call. `reflect`'s Component Health section now reads these counts from the already-tailed `proposal-metrics.jsonl` window and flags if `SUPPRESS` dominates `CREATE` at the same 2× threshold used for `reflection-judge` — closing the "proposal-triage has no verdict counters" gap. All callers updated to treat triage output as line 1 = verdict, lines 2+ = metadata.

- **`channel-setup` and `docker-setup`: default `ackReaction` to 👀 during pairing.** Channel plugins (`discord`, `telegram`) ship `ackReaction` empty by default, so freshly paired hermits had no inbound emoji feedback — operators only saw the 5–10s typing indicator before silence until the actual reply landed (often a minute+ for `session-start`, `proposal-create`, etc.). Both setup skills now run `/<channel>:access set ackReaction 👀` on first pair (with the same state-dir hint pattern used for pair/policy), skipping if the operator has already customized the value. `👀` is in Telegram's reaction whitelist and works on Discord. (`channel-setup/SKILL.md`, `docker-setup/SKILL.md`)

- **Recommended plugins: added `feature-dev` (Anthropic-official)** — orchestrated 7-phase implementation workflow (`/feature-dev:feature-dev`) for designing, exploring, and reviewing code changes. Surfaces in `/hatch` Phase 4 for opt-in install; operators invoke it manually during sessions when implementing accepted proposals.

### Fixed

- **`proposal-triage` agent: YAML frontmatter parse error** — the `description` field contained a bare colon-space sequence (`<code>: <reason>`) which YAML interprets as a key-value separator, causing all frontmatter fields (model, effort, maxTurns, tools, disallowedTools) to be silently dropped at load time. Quoted the description string to fix the parse error.

### Files affected

| File | Change |
|------|--------|
| `agents/proposal-triage.md` | Extended evidence scope, richer verdict output, YAML fix |
| `skills/reflect/SKILL.md` | Explore delegation; triage verdict counters |
| `skills/cortex-sync/SKILL.md` | Explore delegation; manifest flag fix |
| `skills/reflect-scheduled-checks/SKILL.md` | Triage verdict counters |
| `skills/proposal-create/SKILL.md` | Triage verdict counters |
| `skills/channel-setup/SKILL.md` | Default ackReaction to 👀 on first pair |
| `skills/docker-setup/SKILL.md` | Default ackReaction to 👀; feature-dev recommended plugin |
| `skills/hatch/SKILL.md` | feature-dev opt-in in Phase 4 |
| `docs/recommended-plugins.md` | Added feature-dev entry |

### Upgrade Instructions

For each entry in `.claude-code-hermit/config.json` `channels` where `enabled !== false` and the channel name is `discord` or `telegram`:

1. Resolve the `access.json` path:
   - Try `channels.<channel>.state_dir/access.json` (relative paths anchored at project root).
   - Fall back to `~/.claude/channels/<channel>/access.json`.
2. If the file does not exist: skip this channel — defaults will be applied next time `/claude-code-hermit:channel-setup` or `/claude-code-hermit:docker-setup` runs.
3. Parse the JSON. If `ackReaction` is missing or set to `""`:
   - Set `ackReaction` to `"👀"` and write the file back, preserving every other key exactly and matching existing formatting (typically 2-space indent, trailing newline).
   - Log: "Set default `ackReaction` (👀) for `<channel>`."
4. If `ackReaction` is already a non-empty string: skip — preserve operator customization.

Use direct JSON edit, not the slash command. The setup skills route through `/<channel>:access set ackReaction` with a state-dir hint because they run during interactive pairing; for the migration, a direct edit is simpler for batch backfill across multiple channels and doesn't depend on channel plugins being loaded at upgrade time.

**Offer `feature-dev` install for existing hermits.** Read `config.json`. If `docker.recommended_plugins` exists and contains `"feature-dev"`, skip silently — already adopted. If `docker.recommended_plugins` is absent (tmux/local hermit without docker setup), run `claude plugin list` — if `feature-dev` appears, skip silently.

Otherwise, ask the operator with `AskUserQuestion` (header: "feature-dev"):

- Question: "claude-code-hermit's recommended-plugin set now includes `feature-dev` (Anthropic-official) — orchestrated 7-phase implementation workflow (`/feature-dev:feature-dev`) for designing, exploring, and reviewing code changes. Install it?"
- Options: **Yes — install** (default) / **No — skip**

On **Yes**: run `claude plugin install feature-dev@claude-plugins-official --scope project` (idempotent if already installed). If `config.json` has a `docker.recommended_plugins` array, append `"feature-dev"` to it and write the file back, preserving existing formatting (2-space indent, trailing newline). If the key is absent (tmux/local hermits without docker setup), skip the config.json edit. Log: "Installed `feature-dev`@`claude-plugins-official`."

On **No**: skip — operator can install later via `/claude-code-hermit:hermit-settings` or by re-running `/claude-code-hermit:hatch`.

## [1.0.24] - 2026-04-29

### Added

- **Heartbeat and reflect precheck scripts for token cost reduction** — adds `scripts/heartbeat-precheck.js` and `scripts/reflect-precheck.js`. The heartbeat precheck runs before each tick and emits `SKIP` (outside active hours / empty checklist), `OK` (all checklist items already suppressed and stable), or `EVALUATE` (anything requiring LLM judgment). It is the sole writer of `total_ticks`; the skill remains the sole writer of `alerts{}` and `self_eval{}`. The reflect precheck determines which phases are due (compute, resolution_check, cost_spike, digest, newborn) and on `EMPTY` owns the audit trail: it calls `update-reflection-state.js` and appends the mandatory Progress Log line to SHELL.md before short-circuiting. Both scripts are zero-dependency (Node stdlib only) with full fail-open error handling. Heartbeat `SKILL.md` is thinned from 209 → 94 lines by extracting the alert dedup and self-eval detail into `skills/heartbeat/reference.md`, which is loaded on demand only on the `EVALUATE` path. Shared timezone helpers extracted to `scripts/lib/time.js`.

- **`GITIGNORE-APPEND.txt`: complete local-scope coverage** — added `templates/`, `bin/`, `HEARTBEAT.md`, `IDLE-TASKS.md`, `knowledge-schema.md`, and `.claude.local/` (channel state dir). Previously hatch's gitignore append left bin/ and operator-editable files unignored, so `.claude-code-hermit/` kept showing as untracked in projects with local scope.

- **`hatch`: operator consent before `.gitignore` writes** — step 7 now shows the entries to be appended and waits for `AskUserQuestion` confirmation before modifying or creating the project `.gitignore`.

### Removed

- **`scope` config field and `project` scope** — the `scope` field (`"local"` | `"project"`) has been removed. Hermit state is now always gitignored (local scope only). `project` scope caused LLM-generated session reports, `raw/`, and `compiled/` artifacts — which may contain credentials or sensitive context encountered during work — to be committed to git history. The credential scan in `/migrate` is pattern-based and cannot reliably catch novel secret formats in LLM prose. The migration-via-git-clone convenience is already covered by the `/migrate` skill itself. `GITIGNORE-APPEND-PROJECT.txt` has been deleted.

### Fixed

- **`channel-setup`: inject `<CHANNEL>_STATE_DIR` into `settings.local.json`** — the skill wrote the bot token to the project-local `state_dir` but never wired the MCP server subprocess to read from there. Without `DISCORD_STATE_DIR` / `TELEGRAM_STATE_DIR` in the session env, channel servers defaulted to `~/.claude/channels/<channel>/` even when `state_dir` pointed elsewhere, causing "Failed to reconnect" errors and misplaced `access.json` files. The token write and the `settings.local.json` update (stale-token cleanup + `STATE_DIR` injection) are now a single read-modify-write in step 6, and the fix also runs when the token was already configured.

- **`hatch`: add `heartbeat-precheck.js` and `reflect-precheck.js` to required permissions** — both scripts are called on every heartbeat tick and reflect run but were missing from the `permissions.allow` block, causing operators to be prompted on every invocation.

### Files affected

| File | Change |
|------|--------|
| `scripts/heartbeat-precheck.js` | New — heartbeat precheck script |
| `scripts/reflect-precheck.js` | New — reflect precheck script |
| `scripts/lib/time.js` | New — shared timezone helpers |
| `skills/heartbeat/SKILL.md` | Thinned to 94 lines; precheck integration |
| `skills/heartbeat/reference.md` | New — alert dedup and self-eval detail, loaded on demand |
| `skills/reflect/SKILL.md` | Precheck integration in step 1 |
| `skills/hatch/SKILL.md` | Gitignore consent gate; precheck permissions added |
| `skills/channel-setup/SKILL.md` | STATE_DIR injection in step 6 |
| `skills/hermit-evolve/SKILL.md` | Upgrade instruction execution |
| `skills/migrate/SKILL.md` | Scope removal references updated |
| `state-templates/GITIGNORE-APPEND.txt` | Extended local-scope entries |
| `state-templates/GITIGNORE-APPEND-PROJECT.txt` | Deleted |
| `state-templates/config.json.template` | `scope` field removed |
| `scripts/hermit-start.py` | `scope` handling and worktree setup removed |
| `docs/config-reference.md` | `scope` field entry removed |
| `tests/run-scripts.sh` | Precheck test cases added (34 → 52 script tests) |

### Upgrade Instructions

Run `/claude-code-hermit:hermit-evolve`. The evolve skill handles:

1. If `config.json` contains `"scope": "project"`:
   - **Warn the operator** (channel in always-on mode; inline in interactive mode):
     > "claude-code-hermit v1.0.24 removed the `project` scope. Your hermit state is currently tracked in git. To clean it up: (a) run `/claude-code-hermit:migrate` for a credential audit and manifest, (b) manually run `git rm --cached -r .claude-code-hermit/sessions/ .claude-code-hermit/proposals/ .claude-code-hermit/state/ .claude-code-hermit/raw/ .claude-code-hermit/compiled/ .claude-code-hermit/config.json`, then (c) replace the `.claude-code-hermit` block in `.gitignore` with the local-scope template. Skipping this leaves session reports and raw artifacts in your git history. Reply (1) to continue anyway (drop `scope` field only, no git changes), or (2) to abort so you can clean up first."
   - On **(2)**: abort hermit-evolve entirely with a message directing the operator to run `/claude-code-hermit:migrate` first. The `scope` key remains in `config.json`, so the next `/hermit-evolve` run will re-enter this instruction automatically.
   - On **(1)**: drop the `scope` field from `config.json` and proceed. Write a `compiled/` artifact `compiled/decision-scope-removal-<YYYY-MM-DD>.md` recording the operator's acknowledged decision to skip migration for now (frontmatter: `title`, `type: decision`, `created`, `tags: [scope, security]`).
2. If `config.json` contains `"scope": "local"` or no `scope` key: silently remove the `scope` key from `config.json` and continue.
3. Add missing precheck script permissions to `.claude/settings.json`. Check if `permissions.allow` contains `"Bash(node */scripts/heartbeat-precheck.js*)"` and `"Bash(node */scripts/reflect-precheck.js*)"`. If either is missing, show the operator the entries to add and ask with `AskUserQuestion` (header: "Precheck permissions") — options: **Yes — add** (default) / **No — skip**. If confirmed, merge the missing entries into `permissions.allow`.
4. Apply extended `.gitignore` coverage to existing projects. Read the project `.gitignore`. Check if it contains `.claude-code-hermit/bin/`. If not, show the operator the lines that will be appended (from `${CLAUDE_PLUGIN_ROOT}/state-templates/GITIGNORE-APPEND.txt`, filtered to entries not already present) and ask with `AskUserQuestion` (header: "Update .gitignore") — options: **Yes — append** (default) / **No — skip**. Append only if confirmed.

## [1.0.23] - 2026-04-28

### Removed

- **hermit-start: agent worktree setup** — deleted `setup_agent_worktree()` and the `HERMIT_AGENT_WORKTREE` env-file export from `scripts/hermit-start.py`. The function was added in v1.0.22 to give dev-hermit's always-on worktree topology a persistent `.claude/worktrees/agent/` checkout to operate in; dev-hermit v0.3.0 dropped that topology entirely (no more `$HERMIT_AGENT_WORKTREE` consumers anywhere in the fleet). The setup ran on every boot, made up to 3 git subprocess calls (15s timeout each → up to 45s worst-case), and produced a stale `.claude/worktrees/agent/` directory that nothing reads. Removing it speeds up boot and shrinks the per-session tmux env file by one variable.

### Changed

- **`docs/architecture.md`: agent layer description** — the line claiming dev-hermit "adds repo-mapper, implementer, reviewer" was always wrong (dev-hermit historically shipped only an `implementer`; v0.3.0 ships zero agents). Replaced with a generic statement pointing operators at each plugin's CLAUDE.md for its actual agent set.

### Files affected

| File | Change |
|------|--------|
| `plugins/claude-code-hermit/scripts/hermit-start.py` | Removed `setup_agent_worktree()` (~60 lines), call site, and `HERMIT_AGENT_WORKTREE` env export |
| `plugins/claude-code-hermit/docs/architecture.md` | Generalized agent-layer line (no concrete plugin example) |

### Upgrade Instructions

Run `/claude-code-hermit:hermit-evolve`. The evolve skill handles:

1. **Remove the stale agent worktree** if it exists. Run `git -C "$PROJECT_ROOT" worktree list --porcelain` to detect a registered worktree at `.claude/worktrees/agent/`. If present, run `git -C "$PROJECT_ROOT" worktree remove --force .claude/worktrees/agent`. If absent from `git worktree list` but the directory still exists on disk, `rm -r .claude/worktrees/agent` (no `-f` per repo rules) and `git worktree prune` to clear any stale ref. Best-effort: do not block the upgrade if the operation fails — print a one-line warning telling the operator to clean up manually.
2. **Note the env-var removal.** Tell the operator: "v1.0.23 removes the `HERMIT_AGENT_WORKTREE` env var from the per-session tmux env file. Any custom skill or script you wrote that reads this var (none ship in the official hermit fleet) will see it as unset — adjust to either drop the dependency or read project root from `Path.cwd()` instead."

The `.claude/worktrees/` line in `state-templates/GITIGNORE-APPEND.txt` and `GITIGNORE-APPEND-PROJECT.txt` is intentionally preserved — it covers Claude Code's native `isolation: worktree` agent feature (used by other plugins), not just the deleted hermit-managed subpath.

No `config.json` changes required.

## [1.0.22] - 2026-04-28

### Added

- **hermit-start: persistent agent worktree setup** — `setup_agent_worktree()` creates `.claude/worktrees/agent/` before the tmux env file is written and sets `HERMIT_AGENT_WORKTREE` so dev-hermit skills operate in the agent worktree rather than the operator's main checkout. Three-way idempotent: creates on first boot, re-registers after a stale-pruned ref, leaves an existing registered worktree untouched (preserving any feature branch from the prior session). All git calls have a 15 s timeout and fail open with a warning.
- **gitignore templates: `.claude/worktrees/`** — added to `GITIGNORE-APPEND.txt` and `GITIGNORE-APPEND-PROJECT.txt` so agent worktree directories are excluded from project git history.

### Changed

- **hermit-start: `auto` permission mode** — `hermit-start.py` now passes `--permission-mode auto` to Claude Code instead of treating it as unknown. Max plan → Opus 4.7 only; Team/Enterprise/API → Sonnet 4.6 or Opus 4.6/4.7. Not available on Pro, Haiku, or non-Anthropic providers.
- **hatch + hermit-settings: `auto` surfaced in permission mode options** — replaces the outdated "Teams/Enterprise only" note with accurate plan/model requirements.
- **channel-setup: Docker-mode guard** — step 1 reads `state/runtime.json` and redirects to `/docker-setup` if `runtime_mode == "docker"`, with a fallback check for `docker/Dockerfile.hermit` for scaffolded-but-unbooted projects.
- **hatch: deployment-mode next-steps** — Step 10 next-steps restructured into "Pick a mode / After picking / Anytime" groups so `/channel-setup` is visible for tmux and interactive users; channel-save note now names all three modes (Docker/tmux/interactive) with their activation paths.
- **hatch: config.json leak prevention** — Phase 2 draft rule now explicitly prohibits restating config fields (`routines`, `channels`, `permission_mode`, `agent_name`, `sign_off`, `escalation`, `idle_behavior`, `boot_skill`, `_hermit_versions`) in OPERATOR.md. Phase 4 scrub step added: re-scans the draft and removes any sentence that mirrors a config field before writing the final file. config.json is intentionally excluded from the Phase 1 scan to prevent the model from mining it for content. proposal-create's "Do NOT include" list extended to redirect config-mirroring proposals to `/hermit-settings` instead.
- **OPERATOR.md template: four-question scaffold** — comment rewritten to give operators a clearer mental model (Focus / Constraints / Approval / Comms style) and explicitly warn against restating config fields.
- **CLAUDE.md: CLAUDE-APPEND contract** — documents the rule that state-templates/CLAUDE-APPEND.md must not restate config.json values (schedules, channel IDs, flags) — those are loaded structurally; CLAUDE-APPEND describes behavior and workflow shape only.

### Files affected

| File | Change |
|------|--------|
| `plugins/claude-code-hermit/scripts/hermit-start.py` | Agent worktree setup; `auto` permission mode support |
| `plugins/claude-code-hermit/state-templates/GITIGNORE-APPEND.txt` | `.claude/worktrees/` added |
| `plugins/claude-code-hermit/state-templates/GITIGNORE-APPEND-PROJECT.txt` | `.claude/worktrees/` added |
| `plugins/claude-code-hermit/skills/channel-setup/SKILL.md` | Docker-mode guard at step 1 |
| `plugins/claude-code-hermit/skills/hatch/SKILL.md` | Deployment-mode next-steps; `auto` permission mode option; config.json leak prevention (Phase 2 rule, Phase 4 scrub, scan exclusion comment) |
| `plugins/claude-code-hermit/skills/proposal-create/SKILL.md` | "Do NOT include" extended: config-mirroring proposals redirected to `/hermit-settings` |
| `plugins/claude-code-hermit/state-templates/OPERATOR.md` | Comment rewritten with four-question scaffold; config field warning added |
| `plugins/claude-code-hermit/CLAUDE.md` | CLAUDE-APPEND contract: must not restate config.json values |
| `plugins/claude-code-hermit/skills/hermit-settings/SKILL.md` | `auto` added to permission mode options |
| `plugins/claude-code-hermit/docs/config-reference.md` | `permission_mode` entry updated for `auto` with plan requirements |

### Upgrade Instructions

Run `/claude-code-hermit:hermit-evolve`. The evolve skill handles:

1. **Refresh** `scripts/hermit-start.py` from the updated plugin.
2. **Refresh** `skills/channel-setup/SKILL.md`, `skills/hatch/SKILL.md`, and `skills/hermit-settings/SKILL.md` from the updated plugin.
3. **Append** `.claude/worktrees/` to the project `.gitignore` if dev-hermit agent worktrees are in use.

No `config.json` changes required.

## [1.0.21] - 2026-04-27

### Changed

- **doctor-check: read `required_core_version` from `hermit-meta.json` sidecar only.** Drops the `plugin.json` fallback — domain plugins store hermit-internal manifest fields in the validator-invisible sidecar so `claude plugin tag --push` passes cleanly.
- **docs: bump Claude Code prerequisite to v2.1.110+.** The dependency resolver and `claude plugin tag` both require v2.1.110+; operators on v2.1.98–v2.1.109 would hit a broken install flow. Updated across `docs/how-to-use.md`, `docs/always-on.md`, `docs/always-on-ops.md`, and `skills/channel-responder/SKILL.md`.
- **docs: update `boot_skill` declaration guidance to `hermit-meta.json`.** `config-reference.md` and `creating-your-own-hermit.md` previously said to declare `hermit.boot_skill` in `plugin.json`; both now point to `hermit-meta.json` to match the sidecar migration.

### Files affected

| File | Change |
|------|--------|
| `plugins/claude-code-hermit/scripts/doctor-check.js` | Reads `required_core_version` from hermit-meta.json; drops plugin.json fallback |
| `plugins/claude-code-hermit/docs/how-to-use.md` | Claude Code prerequisite: v2.1.98+ → v2.1.110+ |
| `plugins/claude-code-hermit/docs/always-on.md` | Claude Code prerequisite: v2.1.98+ → v2.1.110+ |
| `plugins/claude-code-hermit/docs/always-on-ops.md` | Claude Code prerequisite: v2.1.98+ → v2.1.110+ |
| `plugins/claude-code-hermit/skills/channel-responder/SKILL.md` | Channels preview version note: v2.1.98+ → v2.1.110+ |
| `plugins/claude-code-hermit/docs/config-reference.md` | `boot_skill` declaration: plugin.json → hermit-meta.json |
| `plugins/claude-code-hermit/docs/creating-your-own-hermit.md` | `boot_skill` guide: plugin.json → hermit-meta.json |

### Upgrade Instructions

Run `/claude-code-hermit:hermit-evolve`. The evolve skill handles:

1. **Refresh** `scripts/doctor-check.js` from the updated plugin.

No `config.json` changes required.

## [1.0.20] - 2026-04-26

### Changed

- **CHANGELOG: clarify v1.0.19 upgrade for always-on operators.** The v1.0.19 fix replaces `bin/hermit-run`, but `bin/hermit-stop` shares that same dispatcher — so always-on operators (tmux or Docker) couldn't cleanly stop the hermit before upgrading. v1.0.19's Upgrade Instructions block now leads with a `tmux kill-session` / `hermit-docker down` step.
- **`release-auditor` agent: slug-aware refactor for monorepo.** The agent now takes a plugin slug, derives `$PLUGIN_DIR = plugins/<slug>/`, and looks up the version in the repo-root `.claude-plugin/marketplace.json` via `.plugins[] | select(.name == $slug)`. Pre-refactor it produced two false-positive FAILs on every release because it was reading paths from the pre-monorepo layout — releases that ignore noisy auditor output erode the signal over time.

### Files affected

| File | Change |
|------|--------|
| `plugins/claude-code-hermit/CHANGELOG.md` | v1.0.19 Upgrade Instructions: new step 1 for always-on operators (existing 1→2, 2→3) |
| `.claude/agents/release-auditor.md` | Slug-aware refactor: input contract, all path references rewritten under `plugins/<slug>/`, repo-root marketplace lookup, core-only DEFAULT_CONFIG check gate |

### Upgrade Instructions

Run `/claude-code-hermit:hermit-evolve`. No operator action required — this is a documentation-only patch on top of v1.0.19.

No `config.json` changes required.

## [1.0.19] - 2026-04-26

### Fixed

- **`smoke-test`: scheduled-check skill resolution uses the harness's loaded-skills list instead of path-walking the plugin cache.** The previous approach checked `${CLAUDE_PLUGIN_ROOT}/../<plugin>/skills/<skill-name>/SKILL.md`, which only finds siblings under the same marketplace. Cross-marketplace plugins (e.g. `claude-code-setup` and `claude-md-management` installed from `claude-plugins-official`) always produced false-negative WARNs even when fully installed and loaded. The skill now looks up `<plugin>:<skill-name>` in the available-skills list from the harness system-reminder, which is authoritative across all marketplaces — matching the documented best practice in `reflect-scheduled-checks/SKILL.md`.

- **`hermit-evolve` step 7: gate sibling upgrades on `_hermit_versions` key existence.** Under the monorepo marketplace cache layout, `${CLAUDE_PLUGIN_ROOT}/../*` now surfaces every sibling plugin in the same clone regardless of which the operator actually installed. Step 7's prior logic (`default "0.0.0" if missing`) treated every detected sibling as a fresh install: it executed the sibling's `### Upgrade Instructions` (e.g. dev-hermit's `[Unreleased]` writes `.worktreeinclude` at the repo root) and appended the sibling's full CLAUDE-APPEND block to operator `CLAUDE.md` — without consent, on every run (step 9's "never add new keys" rule prevented persistence, so the migrations re-fired indefinitely). Step 7 now skips any hermit not already keyed in `_hermit_versions`; initial activation belongs to that hermit's own `hatch`.

- **`hermit-doctor` and `dev-doctor`: stale "six-check" doc references after the seven-check sweep.** `skills/hermit-doctor/SKILL.md:39` (silence policy) said "return the full **six-line** summary" while every other count in the same file said seven. `claude-code-dev-hermit/skills/dev-doctor/SKILL.md:29` described `/hermit-doctor` as a "six-check health report" and enumerated only the original six items, omitting `dependencies`. Both copy fixes now align with the seven-check report.

- **`plugins/claude-code-hermit`: missing `LICENSE` and stale dev-hermit install snippet in plugin README.** When the plugin source moved to `plugins/claude-code-hermit/` the LICENSE didn't come with it (both sibling plugins shipped their own); the badge and License-section links in the new plugin README 404'd. Restored a copy of the repo-root MIT LICENSE under `plugins/claude-code-hermit/`. Also updated the *Creating Your Own Hermit* install snippet from the pre-monorepo `gtapps/claude-code-dev-hermit` / `@claude-code-dev-hermit` to the canonical `gtapps/claude-code-hermit` / `@claude-code-hermit` form, matching the top-level README.

- **`Test Hooks` GitHub Actions workflow: re-point at the core plugin's monorepo path.** The workflow's `paths:` triggers and the two `run:` invocations both still expected the pre-monorepo layout (`tests/run-hooks.sh`, `tests/run-contracts.py` at repo root), so the first push on `feat/monorepo` after the migration failed with `bash: tests/run-hooks.sh: No such file or directory`. Filters now watch `plugins/claude-code-hermit/**` (plus the workflow file itself); the two test steps now set `working-directory: plugins/claude-code-hermit`. `CONTRIBUTING.md` updated to match — Testing block and PR Workflow step 3 now wrap the invocation in `( cd plugins/claude-code-hermit && bash tests/run-all.sh )` so contributors run literally what CI runs.

### Changed

- **Monorepo layout — `gtapps/claude-code-hermit` now ships as a multi-plugin marketplace.** The repo's plugin source moved from the repo root to `plugins/claude-code-hermit/`. CC's per-plugin `${CLAUDE_PLUGIN_ROOT}` resolves to the new path automatically; sibling-scan patterns (`${CLAUDE_PLUGIN_ROOT}/../*/.claude-plugin/plugin.json`) keep working and now reliably find sibling hermits since they're guaranteed siblings under `plugins/`. The marketplace cache layout is no longer flat — the cached marketplace dir contains `plugins/<name>/` subdirs.

- **`bin/hermit-run`: scan for plugin under monorepo layout.** The plugin-root scan that powers `hermit-start` / `hermit-stop` was looking at `~/.claude/plugins/marketplaces/*/` (one level deep, legacy flat layout). It now scans `~/.claude/plugins/marketplaces/*/plugins/*/` (monorepo layout). Operators on already-hatched target projects must replace their `.claude-code-hermit/bin/hermit-run` to pick up the fix — see Upgrade Instructions.

- **`docker/docker-entrypoint.hermit.sh.template`: monorepo-aware HERMIT_PLUGIN_ROOT export.** The Docker entrypoint used to `find ... -maxdepth 2 -name plugin.json -path "*/.claude-plugin/*"` against the marketplace cache, which (a) was too shallow for monorepo (depth 4 now) and (b) could match dev-hermit's or HA-hermit's manifest first instead of core's. Replaced with a direct check at `${MARKETPLACE_DIR}/claude-code-hermit/plugins/claude-code-hermit/.claude-plugin/plugin.json`. Docker deployments rebuilt from this template will export the correct `HERMIT_PLUGIN_ROOT` automatically.

- **`hermit-doctor` adds a seventh check, `dependencies`.** Reads `required_core_version` from each sibling plugin's `plugin.json` and warns if the running core version doesn't satisfy the declared semver range. Unknown range forms are treated as ok (no false fails). The `dependencies` ID is inserted between `proposals` and `permissions` in the report.

- **Docker entrypoint: survive interrupted Claude CLI self-update.** The Claude Code CLI self-updates by atomically renaming `~/.npm-global/bin/claude` → `.claude-<rand>` then writing the replacement. If the entrypoint died between those steps (e.g. a `subprocess.run(['claude', ...])` raised `FileNotFoundError` mid-update and `set -euo pipefail` propagated), the container would wedge — `claude` gone, only an orphan temp symlink left, every restart crashing the same way. Two-layer fix: (1) a boot-time recovery shim at the top of the entrypoint detects the missing-symlink + orphan pattern and renames the orphan back to `claude` before any downstream invocation; (2) the Python recommended-plugins block now wraps each `subprocess.run(['claude', ...])` in `try/except FileNotFoundError` and the heredoc ends with `|| echo "..."` so a non-zero Python exit no longer tears down the entrypoint under `set -e`. CLI auto-update remains enabled (operator policy: keep CC current); the fix makes the boot script resilient to the mid-flight window. Manual unwedge for an already-broken container that can't reach the new entrypoint: `docker compose -f .claude-code-hermit/docker/docker-compose.hermit.yml run --rm --entrypoint bash hermit -c 'mv /home/claude/.npm-global/bin/.claude-* /home/claude/.npm-global/bin/claude'` then `.claude-code-hermit/bin/hermit-docker up`.

### Files affected

| File | Change |
|------|--------|
| `state-templates/bin/hermit-run` | Marketplace cache scan glob: `marketplaces/*/` → `marketplaces/*/plugins/*/` |
| `state-templates/docker/docker-entrypoint.hermit.sh.template` | (a) Replaced shallow `find -maxdepth 2` plugin.json discovery with a direct path check at `marketplaces/claude-code-hermit/plugins/claude-code-hermit/`. (b) Added boot-time orphan-symlink recovery for `~/.npm-global/bin/claude`. (c) Hardened the Python recommended-plugins block with `try/except FileNotFoundError` per `subprocess.run` plus a trailing `|| echo` after the heredoc so the entrypoint cannot be killed by a transient missing `claude` mid-self-update. |
| `scripts/doctor-check.js` | New `checkDependencies()` function and `satisfiesRange()` helper; added to `runAllChecks()` between `checkProposals` and `checkPermissions` |
| `skills/hermit-doctor/SKILL.md` | Description, body, and check table updated from "six checks" to "seven checks"; new `dependencies` row added |
| `tests/run-hooks.sh` | doctor-check minimal-install assertion bumped from 6 to 7 expected checks; expected ID list includes `dependencies` |

### Upgrade Instructions

Run `/claude-code-hermit:hermit-evolve`. The evolve skill handles:

1. **Always-on operators only: stop the running hermit cleanly before continuing.** If a hermit is currently running for this project (`bin/hermit-status` reports it active), tear it down manually first — `bin/hermit-stop` is broken in this version because it shares the broken `bin/hermit-run` dispatcher with `bin/hermit-start`. For tmux operators: `tmux kill-session -t <session-name>` (find it via `tmux ls`). For Docker operators: `.claude-code-hermit/bin/hermit-docker down`. Then continue with step 2 below; restart with `bin/hermit-start` (or `bin/hermit-docker up`) after `hermit-evolve` finishes. If no hermit is currently running for this project, this step is a no-op.

2. **Replace `.claude-code-hermit/bin/hermit-run` with the new template.** Read `${CLAUDE_PLUGIN_ROOT}/state-templates/bin/hermit-run` and overwrite the target project's `.claude-code-hermit/bin/hermit-run`. Preserve executable bit (`chmod +x`). Without this step, `bin/hermit-start` will continue to fail with `[hermit] Plugin root not found or invalid:` because the old scan glob doesn't match the monorepo cache layout.

3. **For Docker-deployed hermits: rebuild the container.** The `docker-entrypoint.hermit.sh.template` baked into the image carries a stale `find` invocation that won't locate the plugin under the monorepo cache. Run `.claude-code-hermit/bin/hermit-docker update --cc-only` (or a full `update`) so the next boot picks up the new entrypoint. Non-Docker (tmux/local) operators can skip this step.

No `config.json` changes required. The `dependencies` doctor check requires no operator action — it's read-only and reports ok by default.

## [1.0.18] - 2026-04-24

### Changed

- **hermit-doctor: rename from doctor** — avoids collision with Claude Code's built-in `/doctor` command; follows the `hermit-*` naming convention.
- **hermit-start: align DEFAULT_CONFIG model with template** — `model` fallback was `None`; now `'sonnet'` to match `config.json.template`.

### Files affected

| File | Change |
|------|--------|
| `skills/hermit-doctor/SKILL.md` | Renamed from `skills/doctor/`; heading, name, and activation keyword updated |
| `state-templates/CLAUDE-APPEND.md` | `/doctor` → `/hermit-doctor` in quick-reference |
| `CLAUDE.md` | `doctor` → `hermit-doctor` in skills list |
| `docs/artifact-naming.md` | `/doctor` → `/hermit-doctor` |
| `scripts/hermit-start.py` | `DEFAULT_CONFIG model: None` → `'sonnet'` |

### Upgrade Instructions

Run `/claude-code-hermit:hermit-evolve`. The evolve skill handles:

1. **Patch `/doctor` → `/hermit-doctor` in target-project `CLAUDE.md`:** find the Quick Reference line containing the backtick-quoted token `` `/doctor` ``. If `` `/hermit-doctor` `` is already present, or if neither token appears, skip without error. Otherwise replace `` `/doctor` `` with `` `/hermit-doctor` `` on that line only and write the file back.

No `config.json` changes required.

## [1.0.17] - 2026-04-24

### Added

- **`scripts/prompt-context.js` — UserPromptSubmit hook that injects `[Now: <Day>, <date> <HH:MM> <TZ>]` before every prompt** — hermits were occasionally losing track of the day across long sessions because CC's `# currentDate` is TZ-naive and contains no weekday. Passive injection per prompt means the model always has a fresh, TZ-correct timestamp without needing to infer or remember. Falls back to UTC if `config.timezone` is absent or invalid; emits nothing on unexpected errors (fail-open).

- **`bin/hermit-attach` helper** — one short command (`.claude-code-hermit/bin/hermit-attach`) to reconnect to the running hermit in either tmux or docker mode. Reads `state/runtime.json` and dispatches to `tmux attach` or `hermit-docker attach`. `hermit-start` now prints `bin/hermit-attach` as the primary attach hint; `hermit-status` echoes it for non-docker runtimes.

- **`/create-pr` skill at `.claude/skills/create-pr/SKILL.md`** — project-local skill that opens a PR for the current branch: detects base + commits ahead, pushes with upstream if needed, drafts a Conventional Commits title and Summary/Test-plan body (or fills `.github/PULL_REQUEST_TEMPLATE.md` if present), auto-links `#N` / `closes #N` references, and gates on AskUserQuestion (Approve / Open as draft / Edit / Cancel) before calling `gh pr create`. Guards against running on main, dirty tree, detached HEAD, zero commits ahead, or an already-open PR.

- **`hermit-docker update` subcommand** — explicit command to update the Claude Code CLI and refresh plugin marketplace catalogs. Three modes: full (image rebuild + marketplace refresh), `--cc-only` (rebuild only), `--plugins-only` (marketplace refresh + `/reload-plugins` into the live tmux session, zero downtime). Includes `--dry-run`, `--yes`, and preview output. Logs each run to `state/update-history.jsonl`.

### Changed

- **`state-templates/GITIGNORE-APPEND.txt`: ignore `.claude-code-hermit/cost-log.jsonl`** — cost log was only ignored under `.claude/`; the hermit-prefixed path was missing. Entries reordered so all `.claude/` lines precede `.claude-code-hermit/` lines.

- **heartbeat: stale-session alert includes recovery hint** — updated alert text to name context-compaction desync as a cause and give the operator two direct recovery commands (`resume` via `/claude-code-hermit:session-start`, or `idle` to drop the session). Avoids adding state-machine scaffolding to a subsystem scheduled for retirement post-KAIROS GA.

- **channel-responder: recognize slash commands** — added a `Slash command` branch at the top of step 2 classification. Messages starting with `/` (e.g. `/simplify`, `/plugin:command`) are now routed to the matching skill, slash command, or subagent via the appropriate tool instead of being misclassified and drawing an improvised "don't recognize this command" reply.

- **`/doctor` skill — six-check installation health report** — new `skills/doctor/` skill backed by `scripts/doctor-check.js`. Runs six read-only checks (config validity, hook registration, state file integrity, cost budget, proposal health, file permissions), writes `.claude-code-hermit/state/doctor-report.json`, and surfaces a summary block in SHELL.md. Exits 0 always (fail-open); individual check failures are recorded in the report. Three new tests in `tests/run-hooks.sh` (minimal install, corrupt state, missing config).

- **`/doctor` → `/hermit-doctor` skill rename** — avoids collision with Claude Code's built-in `/doctor` command. Skill directory moved from `skills/doctor/` to `skills/hermit-doctor/`; `CLAUDE-APPEND.md` quick-reference and `CLAUDE.md` skills list updated. Internal `scripts/doctor-check.js` and `state/doctor-report.json` are unchanged.

- **`docs/artifact-naming.md`** — new reference doc covering the four-bucket layout (`raw/`, `compiled/`, `state/`, `proposals/`), naming conventions, and frontmatter requirements for new domains and skills. Added to README docs table.

- **Weekly reviews migrated to `compiled/`** — `scripts/weekly-review.js` now writes to `.claude-code-hermit/compiled/review-weekly-YYYY-Www.md` instead of the special-cased `.claude-code-hermit/reviews/` directory. Frontmatter gains `type: review`, `title`, `created` (ISO 8601 generation timestamp), `tags: [weekly, review]`; `generated: true` is preserved as an orthogonal machine-produced marker. Session-start injection now surfaces the latest review automatically via `newestByType`, and `knowledge-lint.js` stale-flags reviews after 60 days. The `reviews/` tolerance in `KNOWN_DIRS` (`scripts/startup-context.js`) and `state-templates/GITIGNORE-APPEND.txt` is removed. Obsidian `Latest Review.md` embed path updated. Fixes a pre-existing doc bug in `docs/frontmatter-contract.md` that declared the path as `reviews/W-YYYY-WNN.md` while code wrote `reviews/weekly-YYYY-WNN.md`.

- **Session reports gain `## Artifacts` section** — new section in `state-templates/SESSION-REPORT.md.template` (between `## Changed` and `## Blockers`) for citing durable outputs a session wrote to `compiled/`. If a session produces a research note, decision doc, or audit summary, write it to `compiled/<type>-<slug>-<date>.md` with `session: S-NNN` in the frontmatter and cite the wikilink from this section. `agents/session-mgr.md` and `skills/session-close/SKILL.md` updated to document the convention.

- **`ultrathink` keyword at planning-heavy reasoning steps** — added to `agents/reflection-judge.md` (per-candidate verdict block), `skills/reflect/SKILL.md` (open-ended reasoning block), and `skills/proposal-create/SKILL.md` (body synthesis and capability Suggested Plan). Per-turn reasoning boost at the three decisive judgment points in the proposal pipeline. No model or effort-level changes; cost delta should be minor. Phase B (potential `reflection-judge` bump to opus) is deferred pending observation of Phase A suppress/accept ratios and operator feedback.

- **`config.model` defaults to `"sonnet"` for new hatches** — was `null` (deferred to CC's tier default, which is sonnet in most cases). Explicit default makes the launch model visible and reproducible, especially for always-on docker deployments. Operators can set `"opus"`, `"haiku"`, or any CC alias. Existing hermits with `model: null` are prompted interactively via `hermit-evolve` — the flip is opt-in.

- **Model and effort tuning documented in `docs/how-to-use.md`** — new section covering `config.model` and the optional `CLAUDE_CODE_EFFORT_LEVEL` env var (set via `config.env`), with a direct link to the CC model-config docs. Values are not mirrored inline since that doc updates frequently.

### Files affected

| File | Change |
|------|--------|
| `state-templates/bin/hermit-attach` | New helper — reconnect to tmux or docker session |
| `state-templates/bin/hermit-docker` | `update` subcommand added |
| `state-templates/bin/hermit-status` | Attach hint added for tmux runtime |
| `scripts/hermit-start.py` | Attach hint now shows `bin/hermit-attach` |
| `skills/docker-setup/SKILL.md` | `update` added to step 9 command reference |
| `skills/hatch/SKILL.md` | `update-history.jsonl` added to state init list |
| `skills/channel-responder/SKILL.md` | Slash command branch added to classifier |
| `skills/hermit-doctor/SKILL.md` | Renamed from `skills/doctor/`; name and activation keyword updated |

### Upgrade Instructions

Run `/claude-code-hermit:hermit-evolve`. The evolve skill handles:

1. **bin/hermit-docker update support:** rewrite `.claude-code-hermit/bin/hermit-docker` from the updated template (step 5b already does this wholesale). No operator action needed.
2. **update-history.jsonl:** create empty `.claude-code-hermit/state/update-history.jsonl` if it does not exist.
3. **bin/hermit-attach:** copy `state-templates/bin/hermit-attach` to `.claude-code-hermit/bin/hermit-attach` and `chmod +x`. Overwrite if present.
4. **bin/hermit-status:** rewrite `.claude-code-hermit/bin/hermit-status` from the updated template (step 5b already does this).
5. **Migrate weekly reviews to compiled/:** if `.claude-code-hermit/reviews/` exists, for each `weekly-<weekKey>.md`:
   - Compute target path `.claude-code-hermit/compiled/review-weekly-<weekKey>.md`. If the target already exists and its frontmatter contains `type: review`, skip this file (idempotence guard — safe to re-run).
   - Read the source file's frontmatter. Synthesize the new frontmatter by prepending these fields in order: `type: review`, `title: "Weekly Review: <weekKey>"`, `created: <source file mtime as ISO 8601>`, `tags: [weekly, review]`. Preserve `generated: true` and all existing metric fields (`week`, `sessions_count`, `proposals_created`, `proposals_accepted`, `proposals_resolved`, `total_cost_usd`, `avg_session_cost_usd`, `self_directed_rate`). Preserve the body verbatim.
   - Write to the target path. Delete the source file.
   - After processing all files, remove the `.claude-code-hermit/reviews/` directory if now empty.
6. **Update project `.gitignore`:** if `.claude-code-hermit/reviews/` appears as a line in the project's `.gitignore`, remove it. The migration eliminates the directory.
7. **SESSION-REPORT.md.template drift:** the template now includes a `## Artifacts` section between `## Changed` and `## Blockers`. If `.claude-code-hermit/templates/SESSION-REPORT.md.template` exists in the project state dir and lacks this section, add it. The evolve template-diff machinery already handles this — just confirm the diff includes the `## Artifacts` insertion.
8. **Obsidian `Latest Review.md`:** if `obsidian/Latest Review.md` exists and still embeds `reviews/weekly-<weekKey>`, rewrite the embed to `compiled/review-weekly-<weekKey>`. Otherwise the next weekly-review run regenerates it.

Declaring a `review` type in `knowledge-schema.md` is left to the operator — the schema is project-owned, so hermit-evolve does not append.

9. **Patch `/doctor` → `/hermit-doctor` in target-project `CLAUDE.md`:** read the project's `CLAUDE.md` (not the plugin's). Find the line in the Quick Reference section that lists backtick-quoted hermit skill commands — it will contain `` `/doctor` `` (the form used by `state-templates/CLAUDE-APPEND.md`). If `` `/hermit-doctor` `` is already present, or if neither token appears in the file, skip without error (fail-open). Otherwise replace the token `` `/doctor` `` with `` `/hermit-doctor` `` on that line and write the file back. Do not alter any other line.

10. **Interactive model migration:** if `config.model` is `null` or missing, ask the operator:
   > "Hermit now defaults to `"sonnet"` for new installs (matches the effective default on most tiers). Your hermit currently inherits the Claude Code default at launch. Pin it to `"sonnet"` (recommended), keep `null` (continue inheriting), or set another alias (`opus`, `haiku`, `best`)?"
   Apply the operator's choice. If they choose "keep null", write `null` explicitly. If they skip or close without answering, leave the key as-is.

## [1.0.16] - 2026-04-22

### Changed

- **reflect-scheduled-checks: decoupled from reflect** — now a self-contained routine skill; `reflect` no longer runs or adjusts scheduled checks. New `scheduled-checks` routine fires at `5 9 * * *`.
- **reflect-scheduled-checks: split unavailable/error gating** — `unavailable` backs off 4 hours only; `error` backs off `interval_days`. Adds `last_error_at` field to state.
- **micro-proposals: drop single-slot constraint** — schema changes from `{active: null}` to `{pending: []}`, allowing multiple concurrent proposals; channel-responder matches by ID.
- **hermit-start.py: export `CLAUDE_PLUGIN_ROOT` to always-on tmux session** — Bash tool calls inside cron-triggered skills now have the variable available.

### Fixed

- **reflect-scheduled-checks: false-negative unavailable classification** — uses loaded-skills list instead of filesystem grep for presence checks.
- **proposal-triage: prevent turn exhaustion on multi-file dedup runs** — `maxTurns` raised from 5 to 8; verdict directive added to prevent early exit.

### Files affected

| File | Change |
|------|--------|
| `skills/reflect-scheduled-checks/SKILL.md` | Standalone routine; unavailable/error gating split; loaded-skills presence check |
| `skills/reflect/SKILL.md` | Removed scheduled-checks section and interval-adjustment logic |
| `agents/proposal-triage.md` | maxTurns 5→8; terminal verdict directive added |
| `skills/channel-responder/SKILL.md` | Multi-slot micro-proposals: ID-based answer matching |
| `skills/heartbeat/SKILL.md` | Multi-slot micro-proposals: pending array |
| `skills/brief/SKILL.md` | Multi-slot micro-proposals: pending array |
| `skills/smoke-test/SKILL.md` | Multi-slot micro-proposals: pending array |
| `scripts/generate-summary.js` | Multi-slot micro-proposals: pending array |
| `scripts/hermit-start.py` | CLAUDE_PLUGIN_ROOT export; scheduled-checks routine in DEFAULT_CONFIG |
| `state-templates/config.json.template` | scheduled-checks routine added |
| `state-templates/micro-proposals.json.template` | Schema: `{active: null}` → `{pending: []}` |
| `CLAUDE.md` | `reflect-scheduled-checks` added to skills list |
| `state-templates/CLAUDE-APPEND.md` | `reflect-scheduled-checks`, `channel-responder` added to quick reference |

### Upgrade Instructions

Run `/claude-code-hermit:hermit-evolve`. The evolve skill handles:

1. **Migrate `micro-proposals.json`:** Read `micro-proposals.json`. If it has a top-level `active` key: if value is null, rewrite as `{"pending": []}`. If value is an object, rewrite as `{"pending": [<that object with tier: 1 if missing>]}`. Remove `active` in both cases.

2. **Add `scheduled-checks` routine:** If `config.json` routines contains no entry with `id: "scheduled-checks"`, append `{"id": "scheduled-checks", "schedule": "5 9 * * *", "skill": "claude-code-hermit:reflect-scheduled-checks", "run_during_waiting": true, "enabled": true}`. Skip if already present.

3. **Add `last_error_at` field:** For each entry under `state/reflection-state.json → scheduled_checks`, if `last_error_at` is absent, add `"last_error_at": null`.

4. **Restart always-on session:** Run `hermit-stop` then `hermit-start` so the new `CLAUDE_PLUGIN_ROOT` export takes effect in the tmux environment.

No `config.json` schema changes required beyond the routine addition in step 2.

## [1.0.15] - 2026-04-22

### Added

- **iMessage channel support in channel-hook** — `scripts/channel-hook.js` now recognizes `imessage` tool names (via both `SERVER_TO_CHANNEL` and the tool-name regex), so `dm_channel_id` persistence works for iMessage MCP bots the same way it does for Discord and Telegram. `hooks/hooks.json` PostToolUse matcher extended to `(discord|telegram|imessage).*reply`. Test added to `tests/run-hooks.sh` (17b).
- **plugin-validator: native `claude plugin validate` as Check 0** — the agent now runs the official Claude Code validator first and treats its findings as authoritative for schema compliance; hermit-specific checks (1–7) layer cross-references on top.
- **release-auditor: marketplace.json version cross-check** — audits `plugins[0].version` in marketplace.json against `plugin.json.version`. The plugin manifest wins silently when they differ, so a mismatch is a FAIL.

### Changed

- **marketplace.json: full metadata** — adds top-level `metadata.description`, and per-plugin `author`, `license`, `homepage`, `repository`, and `keywords` so marketplace listings render correctly.
- **release skill: native validator + marketplace version sync** — step 1 now runs `/plugin validate .` before tests; step 4 verifies plugin.json and marketplace.json versions agree via `jq`; step 6 derives the tag name from `jq` instead of a typed literal so the tag can't drift from the bumped version.
- **docs/security.md: Docker plugin trust model** — reflects the current policy: the entrypoint installs every enabled entry in `docker.recommended_plugins` regardless of marketplace; the trust gate is at configuration time (explicit operator confirmation during `/docker-setup` or `/hermit-settings docker`), with preselection restricted to `claude-plugins-official` and `gtapps/*`.
- **brief skill: no longer auto-closes sessions** — if SHELL.md is `in_progress`, brief notes "run /session-close to archive" and lets the operator decide instead of delegating to `/session-close --idle`. Idle transitions are owned by the `session` skill and `session-mgr`. Output cap relaxed to 6 lines (5 content + optional proposal line).
- **smoke-test skill: cron schedule validation** — routine validator now requires the `schedule` key (5-field cron) and FAILs on legacy `time`/`days` fields, matching the routines schema in config.

### Fixed

- **hermit-stop in interactive mode no longer corrupts runtime state** — when the operator is driving Claude in a terminal (no tmux session), `hermit-stop.py` prints the "terminate Claude manually" message and exits early instead of falling through to `update_runtime_field({session_state: 'idle', ...})`. The Stop hook owns the idle transition when Claude actually exits; preempting it left `runtime.json` claiming `idle` while Claude was still running.
- **docs/skills.md: smoke-test vs test-run descriptions swapped** — the table had the two descriptions transposed; smoke-test is post-hatch validation, test-run is the full test suite.
- **docs/testing.md: frontmatter validator path** — script moved from `tests/` to `scripts/`; doc updated to match.
- **README.md: `/claude-code-hermit:evolve` → `/claude-code-hermit:hermit-evolve`** — upgrade instructions referenced the old skill name.
- **SHELL.md.template: `/monitor` → `/watch`** — monitoring section pointed to the old skill name.

### Added

- **knowledge-lint: `schema-empty` and `schema-missing` findings** — previously, a freshly-hatched hermit with an all-commented `knowledge-schema.md` silently disabled all type enforcement (the template's example bullets are inside `<!-- -->`, so `parseSchema` returned `null`). Both new findings now emit at normal verbosity (no `--verbose` required). Findings are suppressed when the hermit has no artifacts yet (empty hermit).
- **knowledge-schema.md template: starter bullets** — the template now ships with one uncommented entry under `## Work Products` (`note`) and one under `## Raw Captures` (`input`). Fresh hermits start with type enforcement active; operators replace these with their real types.
- **startup-context: `---Storage Drift---` section** — at session start, scans `.claude-code-hermit/` for artifacts in paths invisible to session injection and archival: unknown top-level dirs, and subdirs under `raw/`/`compiled/`. Emits a capped warning only when drift is present; completely silent when the hermit is clean (zero recurring context cost).

### Changed

- **knowledge-lint: `parseSchema` sentinel split** — `parseSchema` now returns `false` for a missing file and `null` for a present-but-empty schema (previously both returned `null`). Removes the `fs.accessSync` TOCTOU pre-check that existed only to distinguish those two cases, and drops the redundant `verbose && !schemaPresent` info line in the findings-present path (covered by the `schema-missing` finding and advice line).
- **update-reflection-state: simplified `last_sparse_nudge` fallback** — the fallback `state.last_sparse_nudge ?? null` was unreachable when `mergedNudge` is empty (empty merge implies existing state was also empty); simplified to `null`.
- **`plugin_checks` renamed to `scheduled_checks`** — the config key, state key, `/hermit-settings` subcommand, and `reflect-plugin-checks` sub-skill were named for their original use case (running installed plugin skills on a cadence), but the execution path is fully generic: any skill that conforms to the contract (idempotent, returns findings or nothing, no self-scheduling, safe during reflect cadence) can be registered. The "plugin" framing misled hermit authors into thinking custom skills needed a separate mechanism. Rename surfaces:
  - Config key: `config.json.plugin_checks` → `config.json.scheduled_checks`
  - State key: `state/reflection-state.json.plugin_checks` → `state/reflection-state.json.scheduled_checks`
  - Subcommand: `/hermit-settings plugin-checks` → `/hermit-settings scheduled-checks`
  - Sub-skill: `claude-code-hermit:reflect-plugin-checks` → `claude-code-hermit:reflect-scheduled-checks`
  - Evidence Source tag: `plugin-check/<id>` → `scheduled-check/<id>` (proposal pipeline provenance)
  - Operator-facing copy: "Plugin Checks" → "Scheduled Checks" in docs and `/hermit-settings` output
  - The check execution pipeline is unchanged; only names change.

### Added

- **reflection-judge: `ACCEPT (operator-request)` verdict tag** — adds `operator-request` as a valid source tag in the judge's output grammar, completing coverage alongside `current-session` and `scheduled-check`. Test suite (section 4 of `recurrence-gate-matrix.sh`) now validates all three tags have example verdict lines in the agent definition.
- **tests: DOWNGRADE grammar and verdict-tag coverage checks** — `recurrence-gate-matrix.sh` gains two new sections: section 3 verifies `reflection-judge.md` contains a `DOWNGRADE` example; section 4 verifies all source tags (`current-session`, `scheduled-check`, `operator-request`) have example verdict lines.
- **docs: `source` field semantics clarified in frontmatter-contract** — `source:` is documented as origin-only; gate bypass is governed by the candidate-level `Evidence Source:` field, not by `source:`. The `session` field exemption for `operator-request` is now annotated as a structural legacy rule with a pointer to the validating code.
- **CLAUDE.md: "Avoid overengineering" constraint** — added to development constraints.
- **.gitignore: `.codex` entry** — excludes Codex CLI working directory from version control.

- **reflect/proposal pipeline: Evidence Source provenance tags** — `reflection-judge`, `proposal-triage`, `proposal-create`, and `reflect` now accept an optional `Evidence Source:` field (`archived-session` | `current-session` | `scheduled-check/<id>` | `operator-request`). Scheduled-check and operator-request sources bypass the cross-session recurrence check (Three-Condition Rule #1) at every gate; conditions #2 and #3 still apply. Structured suppress codes (`no-evidence`, `no-sessions`, `weak-recurrence`, `weak-consequence`, `not-actionable`) replace free-text reasons for machine-parseable audit trails.
- **reflect: evidence integrity rule** — for `current-session` candidates, reflect must not inject evidence into `SHELL.md` before `reflection-judge` reads it; doing so would make the system self-certifying. Inferred patterns (cost, timing, token counts) are ineligible for `current-session` sourcing in the same run.
- **reflect: suppression detail in Progress Log** — when suppressions occur, the progress-log line now appends a `suppressed: [<slug>: <code>, ...]` suffix (capped at 3 entries) for compact audit.
- **tests: recurrence-gate-matrix test suite** — `tests/recurrence-gate-matrix.sh` added to `run-all.sh`; validates Evidence Source bypass behaviour across all pipeline gates.

### Upgrade Instructions

Run `/claude-code-hermit:hermit-evolve`. The evolve skill executes these steps:

1. **Rename config key in `.claude-code-hermit/config.json`:** if a top-level `plugin_checks` array exists, rename it to `scheduled_checks`. If both `plugin_checks` and `scheduled_checks` exist, merge entries by `id` (scheduled_checks wins on conflict); log the merge to stderr and continue. Preserve all other top-level keys unchanged. If neither exists, no-op.
2. **Rename state key in `.claude-code-hermit/state/reflection-state.json`:** if a top-level `plugin_checks` object exists, rename it to `scheduled_checks` (pure key move, values unchanged). If `reflection-state.json` is missing, no-op. Preserve all other top-level keys unchanged.
3. **Evidence Source tag in proposals:** no automated migration. If `.claude-code-hermit/proposals/PROP-*.md` contains the string `plugin-check/`, it refers to historical provenance and can be left as-is — the tag is human-readable and does not affect gate behavior for accepted/resolved proposals. Operators may manually search-replace to `scheduled-check/` if desired.
4. **Operators invoking `/hermit-settings plugin-checks` will get "unknown subcommand"** after upgrade. Use `/hermit-settings scheduled-checks` instead.
5. **Seed starter bullets if `knowledge-schema.md` parses empty:** if `.claude-code-hermit/knowledge-schema.md` exists and has no uncommented bullet lines under `## Work Products` or `## Raw Captures` (all bullets inside HTML comments), append `- note: general-purpose compiled note. location: compiled/note-<slug>-<date>.md` under `## Work Products` and `- input: general-purpose raw capture. location: raw/input-<slug>-<date>.md` under `## Raw Captures`. Preserve all existing content and comments. If the section headers are missing, append them with the bullets. If the file is missing, no-op (hatch creates it on first run).

## [1.0.14] - 2026-04-20

### Added

- **docker-setup: plugin-declared apt dependencies (step 7b.packages)** — domain plugins can now declare the apt packages their own scripts require by adding a `## Docker apt dependencies` section to their `hatch` SKILL.md or a `DOCKER.md` file at the plugin root. `docker-setup` reads these declarations for every confirmed mirrored plugin (step 7b.packages), unions them with the project-level scan results, validates each name against `^[a-z0-9][a-z0-9+\-.]+$`, and presents a single unified confirmation prompt with origin labels before baking the approved set into `Dockerfile.hermit` via `{{PACKAGES_BLOCK}}`. Packages installed at image build time eliminate the need for runtime venvs or post-install scripts inside ephemeral container volumes.
- **boot_skill: domain hermits can override the always-on bootstrap skill** — `hermit-start.py` now reads an optional top-level `boot_skill` field from `config.json`. When set (e.g. `"/claude-code-homeassistant-hermit:ha-boot"`), it replaces the default `/claude-code-hermit:session` bootstrap the core boot script sends into the tmux REPL. The domain boot skill is responsible for invoking `/claude-code-hermit:session-start` itself before running domain-specific setup (HA probes, context refresh, etc.). Domain hermits declare their skill once in `.claude-plugin/plugin.json` under `hermit.boot_skill`; `hatch` reads that field when activating the hermit and writes it into the project config. No new bin scripts, no shim-swapping — boot stays core-owned, composition lives in the skill layer. Operators can view/clear/change via `/claude-code-hermit:hermit-settings boot-skill`.

### Changed

- **docker-setup: package confirmation deferred to after plugin selection** — the project-signal apt scan (step 2.3) now collects candidates without immediately writing `docker.packages`; final confirmation happens in new step 7b.packages after the plugin list is finalized, so plugin-declared deps can be included in a single unified prompt.

### Fixed

- **hermit-docker: revert login to REPL `/login`** — `claude auth login` can't complete OAuth in Docker/tmux (no browser callback path); reverted to `docker compose exec` REPL with post-exit credential verification.
- **docker-setup: setup-mode bootstrap suppression** — first boot now lands on an idle REPL prompt; `hermit-start.py` reads-and-deletes `.setup-mode` marker, skipping bootstrap send (one-shot).
- **docker-setup: channel pairing confirmation gates** — skill blocks with `AskUserQuestion` before pair command and before `access.json` verification; eliminates race past unfinished pairing.
- **docker-setup: login gate** — skill asks "Done / Failed" after `hermit-docker login`; on failure surfaces logs and stops.
- **docker-setup: drop `/reload-plugins` pre-pair** — was a workaround for bootstrap-turn collision; no longer needed.
- **docker-setup step 9: clarify no-session on fresh setup** — explicit note prevents LLM adding sleep loops waiting for a session.
- **docker-setup: pre-create channel state dirs before compose up** — if `.claude.local/channels/<plugin>/` doesn't exist on the host when `docker compose up` runs, Docker creates it as root; the `claude` user inside the container then can't write to the bind-mount. Skill now runs `mkdir -p .claude.local/channels/<plugin>` for each channel before `docker compose up -d --build`.
- **tmux send-keys: split text and Enter into two calls** — Claude Code's TUI treated one-shot `send-keys '<text>' Enter` as bracketed paste, turning `Enter` into a literal newline instead of submit. Pair commands, policy commands, and graceful-shutdown requests now send text and `Enter` as separate `send-keys` calls with a 0.5s pause between them (same fix already applied in `scripts/hermit-start.py`). Affects `state-templates/bin/hermit-docker`, `state-templates/docker/docker-entrypoint.hermit.sh.template`, and `skills/docker-setup/SKILL.md` (manual deployment + channel pairing steps).
- **docker-setup: verify channel token before pairing** — before asking for a pairing code, step 8 now checks that `.claude.local/channels/<plugin>/.env` exists and contains the expected `*_BOT_TOKEN` var; if missing, pairing is skipped for that channel with a clear next-step message instead of prompting for a code that can't be used.
- **config template: add boot_skill field** — `boot_skill` was used by `hermit-start.py` but absent from `config.json.template` and `DEFAULT_CONFIG`; new projects now have the field populated as `null`.

### Files affected

| File | Change |
|------|--------|
| `state-templates/bin/hermit-docker` | Login reverts to REPL via `compose exec`; post-exit credential verification; use `CLAUDE_CONFIG_DIR` env var |
| `state-templates/docker/docker-entrypoint.hermit.sh.template` | Banner updated to match REPL-based login flow |
| `scripts/hermit-start.py` | Setup-mode marker check: read-and-delete `.setup-mode`, skip bootstrap if present; `boot_skill` config field overrides the default `/claude-code-hermit:session` bootstrap |
| `skills/hatch/SKILL.md` | Step 3 reads `hermit.boot_skill` from activated hermit's `plugin.json`; step 5 writes it to `config.boot_skill` |
| `skills/hermit-settings/SKILL.md` | New `boot-skill` argument to view/clear/change `config.boot_skill` |
| `skills/docker-setup/SKILL.md` | Login gate; setup-mode touch before build; blank-prompt note; drop reload-plugins; pairing gates; step 9 no-session note; step 2.3 defers confirmation; new step 7b.packages unions project + plugin-declared apt deps |
| `docs/creating-your-own-hermit.md` | New Docker dependencies section documenting the `## Docker apt dependencies` convention (hermit-owned vs project-owned scope split); simplified hatch naming to just `hatch` (plugin namespace disambiguates) |
| `state-templates/config.json.template` | Added `boot_skill: null` top-level key |
| `scripts/hermit-start.py` | Added `boot_skill: None` to `DEFAULT_CONFIG` |

### Upgrade Instructions

Run `/claude-code-hermit:hermit-evolve`. The evolve skill handles:

1. **Replace** `state-templates/bin/hermit-docker` with the updated version from the plugin.
2. **Replace** `state-templates/docker/docker-entrypoint.hermit.sh.template` with the updated version from the plugin.
3. **Sync `boot_skill` from any activated domain hermit.** For each hermit recorded in `_hermit_versions` (excluding `claude-code-hermit`):
   - Locate the hermit's `plugin.json` via the same sibling-plugin scan used at init (`${CLAUDE_PLUGIN_ROOT}/../*/.claude-plugin/plugin.json`).
   - If its manifest declares `hermit.boot_skill` (e.g. `"/claude-code-homeassistant-hermit:ha-boot"`):
     - If the project's `config.boot_skill` is `null` or missing: set it to the declared value.
     - If `config.boot_skill` is already set and matches the declared value: no-op.
     - If `config.boot_skill` is set to a skill in a *different* plugin's namespace: leave it alone and warn the operator about the conflict.
   - If the hermit's manifest omits `hermit.boot_skill` but `config.boot_skill` currently points at a skill in that hermit's namespace: clear `config.boot_skill` to `null` so the default `/claude-code-hermit:session` takes over.
   - If no domain hermit is activated: leave `config.boot_skill` as-is (likely `null`).

**If you have root-owned `.claude.local/channels/` dirs from a previous setup:** fix them on the host with `sudo chown -R $USER .claude.local/` from the project root, then restart the container.

`config.json` gains an optional top-level `boot_skill` field (string or `null`). Step 3 above handles population for existing projects with a domain hermit; core-only projects need no manual change.

---

## [1.0.13] - 2026-04-20

### Added

- **reflect: adaptive phase gates** — `newborn` (<3d) / `juvenile` (3–13d) / `adult` (14+d) gate recurrence and sub-threshold surfacing; closes the cold-start silence on fresh installs. Tier 2/3 still require real cross-session evidence in every phase.
- **reflect: operator-value self-check** — reflection questions now include dismiss-ratio and deferred-proposal-buildup checks from `proposal-metrics.jsonl`.
- **reflect: cost-spike detection** — today's cost vs 7-day median; `>2×` records a sub-threshold observation eligible for recurrence graduation.
- **reflect: Component Health agent check** — flags `reflection-judge` when `judge_suppress > 2× judge_accept` with ≥5 verdicts.
- **reflect: mandatory Progress Log entry** — every run (including empty) appends `[HH:MM] reflect (<phase>) — ...` to SHELL.md.

### Changed

- **reflect: silent by default** — unconditional top-of-skill operator notification removed; notify only on outcomes.
- **reflect: Three-Condition Rule hoisted** — defined once before first reference.
- **reflect: sub-threshold → project memory** — recorded with pattern label + session_id so recurrence can graduate them.
- **reflect: Resolution Check 14-day guard** — requires both pattern absence from 3 sessions AND ≥14 days since `accepted_date`.
- **reflect: Skill Health → Component Health** — broadened to agents and hooks (hooks out-of-scope pending telemetry).
- **reflection-judge: `(current-session)` verdict variants** — explicit trigger for SHELL.md fallback when no archived report exists; callers can tell evidence isn't archived yet.
- **CLAUDE-APPEND.md quick reference** — added `/session-start`, `/reflect`, `/channel-setup`, `/hatch`, `/smoke-test`.

### Fixed

- **heartbeat: reflect no longer inline** — long reflect runs (30–40 min) occupied the REPL and delayed CronCreate routines 90+ min. Reflect is now routine-only (default `0 9 * * *`, seeded in `config.json.template` and `DEFAULT_CONFIG`). Heartbeat reverts to a pure health tick.

### Files affected

| File | Change |
|------|--------|
| `skills/reflect/SKILL.md` | Adaptive phase gates, cost-spike detection, operator-value self-check, mandatory Progress Log entry, Component Health broadened, Resolution Check 14-day guard |
| `agents/reflection-judge.md` | Current-session fallback path and `(current-session)` verdict variants |
| `scripts/update-reflection-state.js` | `preserve()` helper extracted; `last_digest_at` passthrough added |
| `skills/heartbeat/SKILL.md` | Reflection section removed from Idle Agency |
| `state-templates/config.json.template` | `reflect` routine added (`0 9 * * *`, enabled by default) |
| `scripts/hermit-start.py` | `reflect` routine added to `DEFAULT_CONFIG` |
| `state-templates/CLAUDE-APPEND.md` | Quick reference updated with 5 previously missing skills |

### Upgrade Instructions

Run `/claude-code-hermit:hermit-evolve`. The evolve skill handles:

1. **Add reflect routine** — append `{"id": "reflect", "schedule": "0 9 * * *", "skill": "claude-code-hermit:reflect", "enabled": true}` to the `routines` array in `config.json` if no entry with `"id": "reflect"` exists.
2. **Register the new routine** — invoke `/claude-code-hermit:hermit-routines load` after updating `config.json`.
3. **Refresh CLAUDE-APPEND** — copy the updated `state-templates/CLAUDE-APPEND.md` into the project's `.claude/` injection point.

No other config changes required.

**Note:** Hermits <14 days old will start surfacing sub-threshold observations in SHELL.md Findings — `Noticed: <pattern>` in `newborn` phase, `Noticed (digest): ...` weekly in `juvenile`. Hermits ≥14 days old land in `adult` (identical to prior behavior). To opt out: set `"enabled": false` on the reflect routine.

---

## [1.0.12] - 2026-04-20

### Changed

- **`routines` skill renamed to `hermit-routines`** — avoids collision with Claude Code's native schedule/routines concepts. The slash command is now `/claude-code-hermit:hermit-routines` (and bare `/hermit-routines`). The `config.json` `routines` array key, `hermit-settings routines` subcommand, `routine-metrics.jsonl`, and `[hermit-routine:<id>]` CronCreate tags are unchanged.
- **Stale routine-watcher prose removed** — several docs and skills still referenced the old bash watcher (removed in 0.0.9). Cleaned up `docs/always-on-ops.md`, `docs/architecture.md`, `docs/testing.md`, `skills/proposal-act/SKILL.md`, `hooks/hooks.json`.
- **Cortex Portal.md is now a live Dataview template** — replaced the generated `obsidian/Cortex Portal.md` (rewritten by `build-cortex.js` on every refresh) with a static Dataview/dataviewjs template. Recent sessions, active proposals, reflect health, and recent artifacts now update live in Obsidian without any rebuild trigger.
- **Connections.md refreshes automatically** — a new mtime-gated stage in the Stop hook (`scripts/cortex-refresh-stage.js`) rebuilds `Connections.md` at the end of any turn that modified sessions, proposals, or artifact manifest. Cost on no-change turns is a handful of `stat()` calls. The nightly `cortex-refresh` routine remains as a safety net.

### Files affected

| File | Change |
|------|--------|
| `skills/hermit-routines/SKILL.md` | Renamed from `skills/routines/SKILL.md`; updated all internal invocation references |
| `state-templates/obsidian/Cortex Portal.md.template` | Rewritten as Dataview/dataviewjs template |
| `scripts/build-cortex.js` | Removed Portal generation; now writes `Connections.md` only |
| `scripts/cortex-refresh-stage.js` | New: mtime-gated Stop hook stage |
| `scripts/stop-pipeline.js` | Added cortex-refresh as stage 5 (standard+ only) |
| `skills/cortex-refresh/SKILL.md` | Updated description to reflect Portal is now live Dataview |
| `skills/cortex-sync/SKILL.md` | Updated step 4 to note Portal is live Dataview |
| `skills/obsidian-setup/SKILL.md` | Updated step 5 to clarify script scope |
| `state-templates/obsidian/Brain.md.template` | Added **Recent Inputs** Dataview section — live view of `raw/` sorted by recency, excluding `.archive/` |
| `state-templates/cortex-manifest.json.template` | Seeded `artifact_paths` with `"compiled"` so new hermits index durable outputs out of the box |

### Upgrade Instructions

Run `/claude-code-hermit:hermit-evolve`. The evolve skill handles:

1. **Update `/routines` invocations** — replace any manual invocations of `/claude-code-hermit:routines` with `/claude-code-hermit:hermit-routines`. If you have custom scripts or notes that reference `/routines load` or `/routines status`, update those references. No config or state changes required — the `routines` array in `config.json` is unchanged.

2. **Replace `obsidian/Cortex Portal.md`** with the new Dataview template — copy `${PLUGIN_ROOT}/state-templates/obsidian/Cortex Portal.md.template` to `obsidian/Cortex Portal.md` (overwrite). The file was previously machine-generated; it is now a static template that Obsidian's Dataview plugin renders live.

3. **Seed `cortex-manifest.json` with `compiled/`** — read `.claude-code-hermit/cortex-manifest.json`. If `artifact_paths` does not already contain `"compiled"`, append it. Write the file back. Skip if the file does not exist.

4. **Replace `obsidian/Brain.md`** with the new template — copy `${PLUGIN_ROOT}/state-templates/obsidian/Brain.md.template` to `obsidian/Brain.md` (overwrite). Adds the **Recent Inputs** Dataview section so operators can see what the hermit has been reading without `raw/` polluting the relationship map.

No `config.json` changes required.

---

## [1.0.11] - 2026-04-19

### Fixed

- **Always-on bootstrap prompt never submitted** — after v1.0.10 collapsed the startup skills into a single composite prompt, `tmux send-keys -t <session> <bootstrap> Enter` in one call still delivered the text and Enter back-to-back. Claude Code's TUI treated the burst as bracketed paste, so the trailing Enter became a literal newline inside the composer rather than a submit — the bootstrap prompt sat visible in the input box but was never processed. Split into two `send-keys` calls with a 0.5s gap so the paste window closes before Enter is registered.

### Files affected

| File | Change |
|------|--------|
| `scripts/hermit-start.py` | Bootstrap send split into two `tmux send-keys` calls (text, 0.5s sleep, Enter) so Claude Code's paste detection doesn't swallow the submit keystroke |

### Upgrade Instructions

`hermit-evolve` executes these steps in order:

1. **No `bin/hermit-start` regeneration needed** — `bin/hermit-start` is a thin wrapper that invokes the plugin's `scripts/hermit-start.py`. The fix lands automatically when the plugin updates; run `.claude-code-hermit/bin/hermit-stop && .claude-code-hermit/bin/hermit-start` to pick it up. Verify by attaching (`tmux attach -t <session>`) and confirming the composite bootstrap prompt is auto-submitted rather than sitting unprocessed in the composer.

No `config.json` changes required.

---

## [1.0.10] - 2026-04-19

### Fixed

- **Always-on bootstrap silently dropped `/heartbeat start` and `/routines load`** — `hermit-start.py` was sending three slash commands via separate back-to-back `tmux send-keys` calls (`/session`, then `/heartbeat start`, then `/routines load`) with zero delay between them. `/session` runs the `session-start` skill, which is heavyweight (can take 30+ seconds and pauses for "What should I help with?"). The follow-up keystrokes landed inside the still-running `/session` turn and were silently swallowed — the same root cause as the original `routine-watcher.sh` bug. Heartbeat and routines never registered, so always-on hermits had no scheduled work and no health checks. Replaced with a single composite bootstrap prompt that asks Claude to invoke heartbeat-start, routines-load, then session in order — one tmux send, one Claude turn, no race possible.

- **`/routines` missing from `CLAUDE-APPEND.md` Quick Reference** — the routines skill landed in v1.0.9 but was not listed in the quick-reference line, so operators reading the appendix could not discover it.

### Files affected

| File | Change |
|------|--------|
| `scripts/hermit-start.py` | Bootstrap rewritten — three racing `tmux send-keys` replaced with one composite prompt that orders heartbeat-start → routines-load → session in a single Claude turn; respects existing `auto_session` / `heartbeat.enabled` / `routines` config gates |
| `state-templates/CLAUDE-APPEND.md` | `/routines` added to Quick Reference line |

### Upgrade Instructions

`hermit-evolve` executes these steps in order:

1. **Refresh `CLAUDE-APPEND.md`** — re-append the updated appendix to the project's `.claude/CLAUDE.md` so operators see `/routines` in the Quick Reference. The skill itself has been usable since v1.0.9; this only fixes discoverability.
2. **No `bin/hermit-start` regeneration needed** — `bin/hermit-start` is a thin wrapper that invokes the plugin's `scripts/hermit-start.py`. The fix lands automatically when the plugin updates; just run `.claude-code-hermit/bin/hermit-stop && .claude-code-hermit/bin/hermit-start` to pick up the new bootstrap behavior. Verify by checking the operator-visible log shows `Bootstrap: ... queued` lines AND that `/claude-code-hermit:routines status` reports active CronCreate registrations after launch.

No `config.json` changes required.

---

## [1.0.9] - 2026-04-19

### Fixed

- **Routine delivery silently dropped in `--remote-control` + channels mode** — `routine-watcher.sh` used `tmux send-keys` to invoke skills, which is event-displaced when Claude is in remote-control mode (the keystrokes land in the input buffer but are silently dropped between turns). The bash watcher and queue file are removed entirely. Each enabled routine is now a per-session `CronCreate` registered by the new `/claude-code-hermit:routines` skill (mirrors `/watch`). CronCreate is idle-gated: routines defer until the REPL is between turns and never interrupt mid-task. `hermit-start.py` invokes `/routines load` automatically on always-on launches. `routine-metrics.jsonl` adds a `delivery: "cron-create"` field on `fired` events.

### Added

- **`/claude-code-hermit:routines` skill** — manages per-session CronCreate registrations. Subcommands: `load` (register all enabled config.routines), `list` (show configured routines), `status` (show active CronCreate entries via CronList), `stop [id]` / `stop --all` (CronDelete). Changes take effect immediately — `hermit-settings routines` auto-runs `/routines load` after writing config.

- **`scripts/log-routine-event.sh`** — helper invoked by routine cron prompts to append timestamped fire events to `state/routine-metrics.jsonl` without asking the LLM to construct JSON.

### Removed

- `scripts/routine-watcher.sh`, `scripts/cron-match.py`, `scripts/routine-queue-flush.js`, `state-templates/routine-queue.json.template`, the `routines` tmux window in `hermit-start.py`, `routine-queue-flush` Stage 5 in `stop-pipeline.js`, the `routine-stale:<id>` heartbeat alert, and corresponding tests.

### Files affected

| File | Change |
|------|--------|
| `skills/routines/SKILL.md` | New skill — load/list/status/stop subcommands |
| `scripts/log-routine-event.sh` | New helper — append `fired` events to `routine-metrics.jsonl` |
| `scripts/hermit-start.py` | Removed routine-watcher tmux window; auto-sends `/routines load` after `/heartbeat start` when `config.routines` is set |
| `scripts/stop-pipeline.js` | Removed Stage 5 `routine-queue-flush` call |
| `skills/heartbeat/SKILL.md` | Removed stale-queue check and `routine-stale:<id>` alert; footer updated |
| `skills/hermit-settings/SKILL.md` | Routines section auto-invokes `/routines load` after config write — changes apply live, no restart needed |
| `skills/hatch/SKILL.md` | No longer copies `routine-queue.json.template`; metrics log description updated |
| `skills/smoke-test/SKILL.md` | Removed `routine-queue.json` shape repair |
| `skills/session-start/SKILL.md` | Removed `routine-watcher` from advisory-lock holders list |
| `agents/session-mgr.md` | Removed routine-watcher from `session_state` writers note |
| `tests/run-hooks.sh`, `tests/run-contracts.py` | Removed routine-queue-flush and cron-match tests |
| Docs | `always-on-ops.md`, `troubleshooting.md`, `architecture.md`, `skills.md`, `how-to-use.md`, `state-templates/CLAUDE-APPEND.md` updated |

### Upgrade Instructions

`hermit-evolve` executes these steps in order:

1. **Delete the obsolete queue file.** `rm -f .claude-code-hermit/state/routine-queue.json` — the bash watcher no longer exists, the file is orphaned.
2. **Register routines via CronCreate.** Invoke `/claude-code-hermit:routines load`. This replaces the old bash-watcher behavior live with no session restart. Confirm the result line shows `Routines registered: <ids> (<N> ok, 0 failed)`.
3. **Verify.** Invoke `/claude-code-hermit:routines status` and confirm one `[hermit-routine:<id>]` entry per enabled routine in `config.json`.
4. **Tell the operator about residual tmux noise.** If the hermit is running in always-on / tmux mode, the old `routines` tmux window keeps a defunct `routine-watcher.sh` process alive (POSIX keeps the process running even though the script file was deleted). It will print `python3: can't open file '.../cron-match.py'` errors on its loop until next `.claude-code-hermit/bin/hermit-stop`. **Harmless — ignore.** Operators who want a clean tmux state can do a one-time `.claude-code-hermit/bin/hermit-stop && .claude-code-hermit/bin/hermit-start` at their convenience.

No `config.json` changes required. Interactive `/session` users who want routines active in interactive mode must run `/claude-code-hermit:routines load` themselves — `hermit-start.py` only auto-loads in always-on mode.

---

## [1.0.8] - 2026-04-18

### Fixed

- **docker: hermit plugin installed but not enabled** — entrypoint now runs idempotent `claude plugin enable` every boot so containers self-heal on restart.
- **docker-setup: stale REPL swallowed channel pairing** — sends `/reload-plugins` once before first pair command.
- **docker-compose: `stop_grace_period` raised to 60s** — 10s SIGKILL'd mid graceful session-close.
- **docker-setup: avoids `hermit-docker up` echo hints** — uses `docker compose up -d` directly during setup so the outer LLM doesn't follow the trailing "attach" suggestion.
- **docker-setup: recommended plugins mirror host install** — step 7b reads host project/local plugins instead of a canned list; entrypoint adds marketplace before install; safelist preselects `claude-plugins-official` + `gtapps/*` only, third-party requires explicit opt-in; `org/repo` regex validator rejects malformed values.
- **entrypoint: recommended-plugin re-install loop** — `install_target in installed` set-membership check never matched raw line output; switched to substring match.
- **hermit-docker login: double-OAuth race** — REPL's auth check + `/login` opened two URLs racing on `.credentials.json`. Now uses one-shot `claude auth login` gated by `claude auth status --json`.

### Added

- **docker-setup step 8b: clean restart** — `hermit-docker down` + `up -d` so first real session has plugins loaded and no setup chatter in transcript.
- **routine fire metrics** — `routine-watcher.sh` appends `queued`/`fired`/`dequeued` to `state/routine-metrics.jsonl`; reflect uses it to propose retiming idle routines.

### Files affected

| File | Change |
|------|--------|
| `state-templates/bin/hermit-docker` | `login` subcommand replaced `claude /login` with auth-status-gated `claude auth login` |
| `state-templates/docker/docker-entrypoint.hermit.sh.template` | Banner warns against manual `claude` invocation; timeout error message updated; third-party marketplace auto-add on boot; unconditional idempotent `claude plugin enable claude-code-hermit` on every boot |
| `skills/docker-setup/SKILL.md` | Step 7b now mirrors host-installed plugins; login guidance updated; `/reload-plugins` sent once before channel pairing; mid-setup uses raw `docker compose up -d` to avoid LLM-misleading echo hints; new step 8b clean-restart at end |
| `state-templates/docker/docker-compose.hermit.yml.template` | `stop_grace_period: 60s` added so SIGTERM graceful session-close has time to complete |
| `skills/hermit-settings/SKILL.md` | Removed third-party plugin warning; unified restart instruction |
| `skills/reflect/SKILL.md` | Routine health check reads `routine-metrics.jsonl`; idle routine proposal path added |
| `skills/hatch/SKILL.md` | Initializes `state/routine-metrics.jsonl` |
| `scripts/routine-watcher.sh` | Emits `queued`/`fired`/`dequeued` events to `routine-metrics.jsonl` |
| `docs/config-reference.md` | Updated `enabled` field description; security model note rewritten |
| `docs/recommended-plugins.md` | Third-party section rewritten to reflect auto-install |

### Upgrade Instructions

Run `/claude-code-hermit:hermit-evolve`. The evolve skill handles:

1. **Create `state/routine-metrics.jsonl`** — If `.claude-code-hermit/state/routine-metrics.jsonl` does not exist, create it as an empty file. No content required — `routine-watcher.sh` appends to it on the next routine fire.

2. **Add domain hermit or third-party plugins to container** — Re-run `/claude-code-hermit:docker-setup` (or `/hermit-settings docker`) to mirror host-installed plugins into `config.json docker.recommended_plugins`, then rebuild the container. Only `project` and `local`-scope plugins are mirrored; user-scope plugins are ignored (install them at `project` scope on the host first if you want them in the container). If a domain hermit is already installed on the host but missing from `docker.recommended_plugins`, add it manually: `add <plugin> <org/repo-marketplace>`.

3. **Update `bin/hermit-docker` login subcommand** — Replace the `login)` case body in `.claude-code-hermit/bin/hermit-docker` with the new auth-status-gated form. Find the block starting with `login)` and ending with `;;`, and replace the body so `hermit-docker login` runs `claude auth status --json` first, then `claude auth login` only if not already authenticated.

4. **Regenerate Docker scaffolding** — the entrypoint self-heal fix is COPY'd into the image at build time, and the `stop_grace_period: 60s` fix is in `docker-compose.hermit.yml`. Both apply only after regeneration. Re-run `/claude-code-hermit:docker-setup` (which regenerates `docker-entrypoint.hermit.sh` and `docker-compose.hermit.yml` and triggers rebuild on next up), OR for the hermit-enable fix alone, remediate now without rebuild: `docker compose -f docker-compose.hermit.yml exec hermit claude plugin enable claude-code-hermit@claude-code-hermit --scope project`.

No `config.json` changes required.

---

## [1.0.7] - 2026-04-17

### Added

- **Baseline audit offer (first session)** — on the first session of a new hermit in an existing codebase, operator is offered a one-time audit using the plugins accepted at hatch (`claude-md-improver`, `claude-automation-recommender`). One proposal per plugin invocation. One-shot, marker-gated (`.baseline-pending`).

- **Reflect diagnostic counters** — `state/reflection-state.json` now tracks per-hermit reflect metrics under a `counters` key. No behavioral change to reflect itself.

  Tracked: `total_runs`, `empty_runs`, `runs_with_candidates`, `judge_accept`, `judge_downgrade`, `judge_suppress`, `proposals_created`, `micro_proposals_queued`, `last_run_at`, `last_output_at`, `since`.

  `pulse --full` surfaces a Reflect Health summary. `cortex-refresh` injects it into Cortex Portal.md.

### Changed

- **`GITIGNORE-APPEND.txt` (local scope): ignore `tasks-snapshot.md`** — `tasks-snapshot.md` is regenerated every turn by the `cost-tracker` hook from the native Tasks store, same category as `cost-summary.md` (already ignored). Adding it eliminates per-turn churn in `git status` for local-scope hermits. Project-scope gitignore unchanged — its "everything else is versioned" contract still applies.

- **`CLAUDE-APPEND.md`: `hermit-config-validator` added to Subagents section** — the agent was present in `agents/` and listed in `CLAUDE.md` but missing from the template injected into target projects. Deployed hermits had no LLM-visible documentation for this agent.

### Files affected

| File | Change |
|------|--------|
| `skills/reflect/SKILL.md` | `## State Update` extended with counter increment rules (single write per run) |
| `skills/hatch/SKILL.md` | Inline `reflection-state.json` init extended with `counters` object; Phase 4b baseline audit marker added |
| `skills/pulse/SKILL.md` | `--full` flag extended with Reflect Health section |
| `skills/session-start/SKILL.md` | Step 5b added: baseline audit offer on first session |
| `scripts/build-cortex.js` | Reads `counters` from `reflection-state.json`; injects Reflect Health block into Cortex Portal.md |
| `scripts/update-reflection-state.js` | New script — handles reflect state-update (counter increments, timestamps, missing-counters fallback) |
| `skills/reflect/SKILL.md` | `## State Update` replaced with single `update-reflection-state.js` call |
| `skills/hatch/SKILL.md` | `update-reflection-state.js` permission added to required allow list |
| `state-templates/GITIGNORE-APPEND-PROJECT.txt` | Ignore `.claude-code-hermit/.baseline-pending` |
| `state-templates/GITIGNORE-APPEND.txt` | Ignore `tasks-snapshot.md` for local-scope hermits |
| `state-templates/CLAUDE-APPEND.md` | `hermit-config-validator` added to Subagents section |
| `docs/always-on-ops.md` | Step 4 updated to mention baseline audit offer in always-on mode |

### Upgrade Instructions

Run `/claude-code-hermit:hermit-evolve`. The evolve skill handles:

1. **`tasks-snapshot.md` gitignore** — For local-scope hermits, append the new line to `.gitignore` and run `git rm --cached .claude-code-hermit/tasks-snapshot.md` if the file was previously tracked. Project-scope hermits are unaffected.

2. **Backfill `counters` on existing `reflection-state.json`** — Read `.claude-code-hermit/state/reflection-state.json`. If `counters` is absent, add it with all integer fields = `0`, both timestamp fields (`last_run_at`, `last_output_at`) = `null`, and `since` = current ISO timestamp (with offset). Preserve all other keys (`last_reflection`, `last_resolution_check`, `plugin_checks`, etc.). If the file is missing entirely, initialize with the full schema from `skills/hatch/SKILL.md`. If `counters` already exists, leave it untouched — no reset on upgrade.

3. **Add `update-reflection-state.js` permission** — In `.claude/settings.json`, add `"Bash(node */scripts/update-reflection-state.js*)"` to `permissions.allow`. Without this, reflect's state-update call will prompt for approval on every run.

4. **Refresh CLAUDE-APPEND** — Re-run `hatch` step that appends `CLAUDE-APPEND.md` to the project's `.claude/CLAUDE.md`, or manually append the `hermit-config-validator` entry to the `## Subagents` section.

No `config.json` changes required.

## [1.0.6] - 2026-04-17

### Changed

- **Storage convention tightened for plugin hermits** — `type` in frontmatter is now the explicit discriminator; subdirectories inside `raw/` or `compiled/` and new top-level folders inside `.claude-code-hermit/` are prohibited. This fixes silent breakage where artifacts in ad-hoc paths (e.g. `audits/`, `reports/`, `raw/audits/`) were invisible to session-start injection and retention archival. `CLAUDE-APPEND.md`, `knowledge-schema.md.template`, `docs/creating-your-own-hermit.md` updated with explicit do/don't rules. New `docs/plugin-hermit-storage.md` is the canonical reference for plugin authors.
- **`CLAUDE-APPEND.md`: stale `reviews/` row removed from Agent State table** — `reviews/` was listed as a first-class directory but is prohibited by the storage rules in the same file. Removed to eliminate the contradiction.
- **`CLAUDE-APPEND.md`: `memory/` added to prohibited top-level directory list** — Matches the prohibition list in `docs/creating-your-own-hermit.md`.
- **`knowledge-schema.md.template`: `location:` field casing normalized** — Per-example `Location:` entries (capitalized) normalized to lowercase `location:` to match the field declaration style in the section headers.

### Files affected

| File | Change |
|------|--------|
| `state-templates/CLAUDE-APPEND.md` | Storage rules updated; `reviews/` row removed; `memory/` added to prohibited list |
| `state-templates/knowledge-schema.md.template` | `location:` fields added to Work Products and Raw Captures sections; casing normalized |
| `docs/creating-your-own-hermit.md` | Knowledge outputs section rewritten with explicit path format and prohibitions |
| `docs/plugin-hermit-storage.md` | New canonical reference for plugin hermit storage convention |

### Upgrade Instructions

Run `/claude-code-hermit:hermit-evolve`. The evolve skill handles:

1. **CLAUDE-APPEND.md refresh** — Replace the existing `CLAUDE-APPEND.md` appendix in the target project's `.claude/` directory with the updated template. This picks up the corrected Agent State table (no `reviews/` row) and the expanded prohibited-directory list (now includes `memory/`).

No `config.json` changes required. The `knowledge-schema.md.template` change only affects new hermits hatched from this version onward — existing `knowledge-schema.md` files in target projects are operator-editable and are not overwritten by `hermit-evolve`.

## [1.0.5] - 2026-04-16

### Fixed

- **docker-entrypoint: channel schema + silent marketplace failure** — channels read as list instead of object so `enabled: false` was ignored; `marketplace add` failures swallowed by `|| true`. Now filters disabled channels and surfaces marketplace errors explicitly.
- **docker-entrypoint: plugins installed but left disabled** — `claude plugin install` leaves plugins dormant; now calls `claude plugin enable` after each channel/recommended install.
- **`claude login` → `claude /login`** — correct CLI invocation; updated across `hermit-docker`, entrypoint, skills, and docs.
- **hermit-docker: `_require_running` preflight** — `attach`/`bash`/`login`/`restart` now check `$SERVICE` is up before `docker compose exec` and print a clear start-it-first message.
- **docker-setup step 8: readiness gates** — manual branch skips exec'd steps; "build now" polls `docker compose ps` 10s; workspace trust + channel pairing gate on `tmux has-session` to avoid "no server running" races.
- **docker-setup step 8: `access.json` verification** — channel pairing polls `.claude.local/channels/<plugin>/access.json` (~3s, retry ~8s) and shows `tmux capture-pane` on miss instead of declaring success.
- **docker-setup: broken doc link** — `recommended-plugins.md` path fixed to `../../docs/...`.

### Changed

- **hatch completion message** — "Go always-on" leads with `docker-setup`; `smoke-test` moved to troubleshooting note; `bypassPermissions` promoted to first permissions option.
- **migrate: scope confirmation gate (step 0)** — reads `config.json.scope` as authoritative, surfaces divergence with `.gitignore`, prompts to switch. Switching reconciles `config.json`, `.gitignore`, and `git rm --cached` for newly-ignored tracked paths behind one confirmation.

### Files affected

| File | Change |
|------|--------|
| `skills/migrate/SKILL.md` | Step 0 scope confirmation + reconciliation sub-flow; Step 1 scope detection updated |
| `state-templates/bin/hermit-docker` | `_require_running` helper; `claude login` → `claude /login` |
| `state-templates/docker/docker-entrypoint.hermit.sh.template` | `claude login` → `claude /login` in echo messages and comment |
| `skills/docker-setup/SKILL.md` | Step 8 readiness gates; doc link fix; `claude login` → `claude /login` |
| `skills/hatch/SKILL.md` | Completion message reorder; permissions option order; formatting |
| `docs/faq.md` | `claude login` → `claude /login` |
| `docs/troubleshooting.md` | `claude login` → `claude /login` |
| `docs/always-on.md` | `claude login` → `claude /login` |
| `docs/config-reference.md` | `claude login` → `claude /login` |
| `docs/architecture.md` | `claude login` → `claude /login` |
| `scripts/hermit-start.py` | `claude login` → `claude /login` in comment |
| `README.md` | Restructured introduction and quick start |

### Upgrade Instructions

Run `/claude-code-hermit:hermit-evolve`. The evolve skill handles:

1. **`hermit-docker` script** — Copy updated `state-templates/bin/hermit-docker` to `.claude-code-hermit/bin/hermit-docker`. This picks up the `_require_running` helper and the `claude /login` fix.
2. **`docker-entrypoint.hermit.sh`** — If Docker is in use, patch the rendered entrypoint at the project root: replace `claude login` with `claude /login` (two echo lines in the timeout paths). This is a cosmetic fix; the container still works — it only affects the error message shown when the 10-minute credential wait times out.

No `config.json` changes required.

## [1.0.4] - 2026-04-16

### Fixed

- **runtime.json: `waiting_reason` field** — records why session entered `waiting` (`unclean_shutdown`/`dead_process`/`conservative_pickup`/`operator_input`) so `channel-responder` routes `(1)`/`(2)` replies to archive-or-resume instead of treating them as task instructions.
- **session-mgr: `session_id` patched into SHELL.md on open** — header now correct from first tick instead of holding the placeholder until close.
- **session-mgr: `cost_usd` reads `.status.json` first** — hook-written cost was silently discarded when SHELL.md parse won; now status file takes precedence.
- **session-start fast-path: patches SHELL.md ID placeholder** — updates in-context without spawning session-mgr when runtime has the ID.
- **routine-watcher: drains stale queue entries on startup** — prunes entries >2h old to prevent phantom stale-routine alerts across restarts.
- **heartbeat: micro-proposal pending alert** — step 6 flags tier-1 entries in `micro-proposals.json` via `micro-proposal-pending:<id>` so they don't silently expire; stale queue message now includes elapsed time.

### Changed

- **proposal-act: accept no longer stamps `resolved_date`** — only sets `accepted_date`. `reflect` stamps `resolved_date` later once the pattern is absent from 3 sessions. Fixes `weekly-review.js` resolution count always being zero.
- **reflect: concrete Resolution Check procedure** — bounded round-robin (≤5/cycle) reads each accepted proposal's evidence, scans last 3 reports, marks resolved if absent. Position tracked in `reflection-state.json.last_resolution_check`.
- **reflection-judge: explicit `Sessions: none` gate** — step 0 short-circuits to `SUPPRESS` without evidence verification; reflect notes the suppression in SHELL.md Findings for revisit.
- **proposal-create: `source` + `category` in `created` events** — metrics now distinguish manual / auto-detected / operator-request and improvement / routine / capability / constraint / bug.
- **generate-summary.js: per-source acceptance + resolved count** — new `proposals_resolved` and `auto_detect_accept_rate` frontmatter fields answer "are autonomous proposals good?".
- **reflect + session-start: notification routing de-duplicated** — "Always-On Notification Rule" block replaced with one-liner deferring to CLAUDE-APPEND's Operator Notification section.
- **reflect: preserves micro-proposal `question` text in JSONL + active slot** — enables post-hoc analysis of what was asked vs operator response.
- **heartbeat: `noise_ticks` self-eval field** — counters increment when a dismissed-proposal-linked alert fires; at 20+ across 3+ sessions, proposes retuning or removing the check (mirrors `clean_ticks`).
- **docs/frontmatter-contract.md** — `resolved_date` writer updated to `reflect (pattern absence)`.

### Files affected

| File | Change |
|------|--------|
| `agents/session-mgr.md` | `waiting_reason` field docs; `session_id` → SHELL.md on open; `cost_usd` reads `.status.json` first |
| `scripts/routine-watcher.sh` | Drain stale queue entries older than 2h on startup |
| `skills/channel-responder/SKILL.md` | Route `waiting_reason` for unclean shutdown / dead process replies |
| `skills/heartbeat/SKILL.md` | `micro-proposal-pending` alert key; step 6 micro-proposal check; `noise_ticks` self-eval field; stale queue message includes elapsed time; `waiting_reason` on NEXT-TASK.md conservative pickup |
| `skills/session-start/SKILL.md` | Set `waiting_reason` on unclean shutdown / dead process; fast-path patches SHELL.md ID; de-duplicate notification routing |
| `skills/proposal-act/SKILL.md` | Remove `resolved_date` stamp from accept flow |
| `skills/reflect/SKILL.md` | Add resolution check procedure; de-duplicate notification routing; preserve micro-proposal `question` in JSONL |
| `agents/reflection-judge.md` | Add explicit `Sessions: none` suppression gate |
| `skills/proposal-create/SKILL.md` | Add `source` and `category` to `created` metrics events |
| `scripts/generate-summary.js` | Per-source acceptance rates and resolved count |
| `docs/frontmatter-contract.md` | Update `resolved_date` lifecycle table |

### Upgrade Instructions

Run `/claude-code-hermit:hermit-evolve`. The evolve skill handles:

1. **No config.json changes required** — all changes are in skill/agent files.
2. **`state/reflection-state.json`** — if it exists and lacks a `last_resolution_check` key, no action needed; the resolution check procedure initializes it on first run.
3. **`state/alert-state.json` self_eval entries** — existing entries lack `noise_ticks`. The heartbeat self-eval step initializes missing fields as 0 on first read; no manual migration needed.
4. **Existing `proposal-metrics.jsonl` events** — old `created` events without `source`/`category` fields are handled by `generate-summary.js` bucketing them as `unknown`. No backfill required.
5. **Accepted proposals with `resolved_date` already set** — these were stamped at accept time under the old behavior. They may show a `resolved_date` even though `status` is `accepted`, not `resolved`. On first reflect run, the resolution check will re-evaluate them. If the pattern is gone, they'll be promoted to `resolved` (updating `resolved_date` to the current time). If not, `resolved_date` stays set but `status` remains `accepted` — a cosmetically odd but non-breaking state that will self-heal.

## [1.0.3] - 2026-04-16

### Added

- **`proposal-triage` agent (Haiku)** — pre-creation gate for the proposal pipeline. Deduplicates against existing PROP-NNN files and applies the three-condition rule before any proposal is queued. Called by both `proposal-create` and `reflect` (Tier 1/2 micro-approvals). Returns `CREATE | SUPPRESS:<reason> | DUPLICATE:<id>`.
- **`reflection-judge` agent (Sonnet)** — post-reflect validator that verifies cross-session evidence citations actually exist in S-NNN-REPORT.md before proposals or micro-approvals are queued. Returns `ACCEPT | DOWNGRADE:<tier> | SUPPRESS` per candidate. Prevents phantom proposals from reflect runs with weak or fabricated evidence.
- **`knowledge` skill** — read-only lint of `raw/` and `compiled/`. Flags stale, unreferenced, missing-type, and oversized artifacts with actionable advice. Delegates to `scripts/knowledge-lint.js`. Activates on "check knowledge", "lint knowledge", "knowledge health".
- **`scripts/knowledge-lint.js`** — shared lint module extracted from `weekly-review.js`. Called by the `knowledge` skill and imported by the weekly review script. Eliminates the duplicate inline logic that previously lived only in the weekly review.
- **Test infrastructure: `tests/run-all.sh`, `tests/lib.sh`, `tests/run-scripts.sh`** — unified test entry point running hook, contract, and script suites in sequence. Script suite covers `knowledge-lint.js`, `check-upgrade.sh`, `deny-patterns.json`, bin executability, and knowledge lint scenarios. `lib.sh` provides shared assertions for shell test scripts.

### Changed

- **`reflect`: evidence validation pipeline** — before acting on any proposal candidate, `reflect` now delegates to `claude-code-hermit:reflection-judge` to verify that cited sessions actually describe the claimed pattern. Only ACCEPT and DOWNGRADE verdicts proceed. Additionally, all Tier 1/2 candidates pass through `claude-code-hermit:proposal-triage` before micro-approval queuing. Tier 3 candidates also pass through triage before calling `proposal-create`.
- **`proposal-create`: pre-creation gate** — calls `claude-code-hermit:proposal-triage` before writing any file. Stops with a caller-facing message on DUPLICATE or SUPPRESS. Eliminates redundant proposals without requiring the operator to review them.
- **`pulse --full`** — new flag that appends infrastructure health sections after the session block: proposal counts by status, pending micro-proposals, routines on/off, last reflect/heartbeat timestamps, and knowledge file counts (`raw/`, `compiled/`, `raw/.archive/`).
- **`heartbeat`: IDLE-TASKS management** — when the operator asks about idle tasks (add, remove, manage), heartbeat now reads/writes `.claude-code-hermit/IDLE-TASKS.md` instead of HEARTBEAT.md. Creates the file from template if absent. Warns if `idle_behavior` is not `"discover"`.
- **`weekly-review.js`: simplified via shared lint** — knowledge health section now calls `knowledgeLint()` from `knowledge-lint.js` instead of duplicating the logic inline. Output format updated to per-finding lines with file, age, and reason.
- **`HEARTBEAT.md.template`: removed two redundant built-in checks** — "Check for NEXT-TASK.md" and "Check if current task has blocked items that may have resolved" are handled natively by the heartbeat skill. Removed to reduce LLM reasoning load per tick.
- **Test runner unified** — `tests/run-hooks.sh` refactored to use shared lib. All suites now accessible via `bash tests/run-all.sh`. Smoke-test-runner agent updated to use the unified entry point.
- **`CLAUDE.md` and `CLAUDE-APPEND.md`** — `proposal-triage` and `reflection-judge` added to agent listings. `/knowledge` added to CLAUDE-APPEND.md Quick Reference. Subagent section in CLAUDE-APPEND.md expanded with descriptions for all four agents.

### Files affected

| File | Change |
|------|--------|
| `agents/proposal-triage.md` | New agent |
| `agents/reflection-judge.md` | New agent |
| `skills/knowledge/SKILL.md` | New skill |
| `scripts/knowledge-lint.js` | New shared lint module |
| `tests/run-all.sh` | New unified test entry point |
| `tests/lib.sh` | New shared test assertions library |
| `tests/run-scripts.sh` | New script/static test suite |
| `tests/run-hooks.sh` | Refactored to use lib.sh |
| `skills/reflect/SKILL.md` | Evidence validation + triage gate |
| `skills/proposal-create/SKILL.md` | Pre-creation triage gate |
| `skills/pulse/SKILL.md` | `--full` infrastructure health flag |
| `skills/heartbeat/SKILL.md` | IDLE-TASKS management subcommand |
| `scripts/weekly-review.js` | Delegates knowledge lint to shared module |
| `state-templates/HEARTBEAT.md.template` | Removed redundant built-in check items |
| `state-templates/CLAUDE-APPEND.md` | Added `/knowledge`, agent descriptions |
| `CLAUDE.md` | Added proposal-triage and reflection-judge to agent list |
| `docs/skills.md` | knowledge skill entry; pulse --full documented |
| `docs/architecture.md` | Minor updates |
| `README.md` | Updated |
| `.claude/agents/smoke-test-runner.md` | Updated to use run-all.sh |
| `.claude/skills/test-run/SKILL.md` | Updated for unified test runner |
| `.claude/skills/release/SKILL.md` | Release process improvements |

### Upgrade Instructions

Run `/claude-code-hermit:hermit-evolve`. The evolve skill handles:

1. **CLAUDE-APPEND refresh** — refresh CLAUDE-APPEND.md to pick up `/knowledge` in Quick Reference and the updated Subagents section (replaces the old single-line `## Subagent: session-mgr` entry).
2. **No config.json changes required** — all new behavior is in skill/agent files.
3. **New agents are available immediately** — `proposal-triage` and `reflection-judge` ship with the plugin; no per-project action needed. Skills that call them (`reflect`, `proposal-create`) will use them automatically.

**Clean up HEARTBEAT.md (if applicable):**

1. Read `.claude-code-hermit/HEARTBEAT.md`. If the file does not exist, skip these steps.
2. Remove the line `- Check for NEXT-TASK.md` if present. Remove the line `- Check if current task has blocked items that may have resolved` if present.
3. After both removals, for each of `## Idle Checks` and `## Task Checks`: if the header now has no remaining checklist items beneath it, remove that header too.
4. If any changes were made, write the file back and report what was cleaned up. If nothing changed, skip silently.

## [1.0.2] - 2026-04-15

### Fixed

- **Fully qualified agent/skill names enforced throughout skill instructions** — Bare names (e.g., `:session-mgr`) were silently misrouted by the harness. All skill instruction files now use the canonical `claude-code-hermit:<name>` form. Affects every skill that spawns a subagent or invokes another skill.
- **session-mgr: null `session_id` fallback on runtime.json write** — If `session_id` was null or missing when setting `session_state` to `in_progress`, the session would archive under `S-null`. Step 7 now pre-computes the ID in the same write if it wasn't set in step 6.
- **session-mgr: invocation payload takes precedence over stale SHELL.md** — On both close and idle-transition, if the caller passes structured task data (status, blockers, lessons, changed files), those values are used directly instead of re-reading potentially stale SHELL.md fields.

### Changed

- **session-start: fast-path gate skips session-mgr on normal startup** — When `runtime.json` is healthy (`session_state` ∈ {`in_progress`, `idle`, `waiting`}, no transition, no last_error) and SHELL.md exists, session-mgr is not spawned. SHELL.md content is already injected by the startup hook. This eliminates a full agent spawn on every normal session start.
- **session / session-close: compile final data in-context before handing off to session-mgr** — Callers now gather status, blockers, lessons, and changed files in-context and pass a compact structured payload to session-mgr. This removes the previous pattern where session-mgr had to re-read SHELL.md fields that the caller already knew, and prevents stale reads from overwriting in-context data.
- **session-mgr: maxTurns reduced from 15 to 12** — Consistent with actual observed turn counts; the previous ceiling was never reached.
- **hermit-settings: improved guidance** — Clearer instructions for configuring hermit behavior.

### Files affected

| File | Change |
|------|--------|
| `agents/session-mgr.md` | maxTurns 15→12; null session_id fallback; payload-precedence rule on close/idle |
| `skills/session-start/SKILL.md` | Fast-path gate: skip session-mgr when runtime state is clean |
| `skills/session/SKILL.md` | Compile final data in-context; structured compact payload to session-mgr |
| `skills/session-close/SKILL.md` | Compile final data in-context; structured compact payload to session-mgr |
| `skills/hermit-settings/SKILL.md` | Improved configuration guidance |
| All skill/agent instruction files | Bare agent/skill names replaced with fully qualified `claude-code-hermit:` form |

### Upgrade Instructions

Run `/claude-code-hermit:hermit-evolve`. The evolve skill handles:

1. **CLAUDE-APPEND refresh** — No changes to CLAUDE-APPEND.md in this release; refresh is not required.
2. **No template changes** — State templates and `config.json` are unchanged.
3. **Behavioral changes are in skill/agent instruction files only** — These take effect immediately via the plugin; no per-project migration needed.

No `config.json` changes required.

## [1.0.1] - 2026-04-15

### Fixed

- **State JSON files now copied from templates during hatch** — `alert-state.json`, `routine-queue.json`, and `micro-proposals.json` were previously created by the LLM writing inline JSON from memory. This could produce malformed content (e.g. `[]` instead of `{"queued": []}`) that silently broke routine queuing. They are now copied from canonical templates in `state-templates/`, matching the pattern used for all other hatch-created files.
- **Smoke-test now validates and repairs state file schema** — New step 6 checks all three schema-sensitive state files. If a file is missing, unparseable, or has the wrong shape, it is repaired (backfilling missing keys, overwriting wrong-type keys) without discarding existing data. Each repaired file emits a WARN.

### Added

- `state-templates/routine-queue.json.template` — canonical initial content `{"queued": []}`
- `state-templates/alert-state.json.template` — canonical initial content with `alerts`, `self_eval`, `total_ticks`, `last_digest_date`
- `state-templates/micro-proposals.json.template` — canonical initial content `{"active": null}`

### Files affected

| File | Change |
|------|--------|
| `state-templates/routine-queue.json.template` | New template |
| `state-templates/alert-state.json.template` | New template |
| `state-templates/micro-proposals.json.template` | New template |
| `skills/hatch/SKILL.md` | Copy 3 state files from templates instead of inline LLM JSON |
| `skills/smoke-test/SKILL.md` | Add step 6: state file validation and repair |

### Upgrade Instructions

Run `/claude-code-hermit:hermit-evolve`. The evolve skill handles:

1. **CLAUDE-APPEND refresh** — No changes to CLAUDE-APPEND.md in this release; refresh is not required.
2. **Template copy** — The three new `.template` files are only used during `hatch`. Existing hermit state files are not touched automatically; if you suspect a malformed state file, run `/claude-code-hermit:smoke-test` to detect and repair it.

No `config.json` changes required.

## [1.0.0] - 2026-04-14

Initial public release.
