diff --git a/CLAUDE.md b/CLAUDE.md
index 365ac0b..e4c809d 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -12,7 +12,7 @@ plugins/
llm-security/ v6.0.0 — Security scanning, auditing, threat modeling
ms-ai-architect/ v1.8.0 — Microsoft AI architecture (Cosmo Skyberg persona)
okr/ v1.0.0 — OKR guidance for Norwegian public sector
- ultraplan-local/ v2.3.0 — Brief, research, architect, plan, execute (five-command pipeline + skill-factory Fase 1)
+ ultraplan-local/ v2.3.1 — Brief, research, architect, plan, execute (five-command pipeline + skill-factory Fase 1)
```
Hvert plugin er selvstendig med egen CLAUDE.md, README, hooks, agents og commands.
diff --git a/README.md b/README.md
index 63aa276..d6572be 100644
--- a/README.md
+++ b/README.md
@@ -61,7 +61,7 @@ Key commands: `/config-audit posture`, `/config-audit feature-gap`, `/config-aud
---
-### [Ultra {brief | research | architect | plan | execute} - local](plugins/ultraplan-local/) `v2.3.0`
+### [Ultra {brief | research | architect | plan | execute} - local](plugins/ultraplan-local/) `v2.3.1`
Deep requirements gathering, research, Claude Code feature matching, implementation planning, self-verifying execution, and skill-factory authoring with specialized agent swarms, adversarial review, IP-hygiene scoring, and failure recovery.
@@ -76,7 +76,7 @@ Five core commands plus an authoring command, one pipeline with clear division o
All artifacts land in one project directory: `.claude/projects/{YYYY-MM-DD}-{slug}/` contains `brief.md`, `research/NN-*.md`, `architecture/` *(v2.2)*, `plan.md`, `sessions/`, and `progress.json`. `--project
` works across `/ultraresearch-local`, `/ultra-cc-architect-local`, `/ultraplan-local`, and `/ultraexecute-local`.
-v2.3 (non-breaking) ships the skill-factory Fase 1 MVP: `/ultra-skill-author-local` plus four supporting agents (1 opus orchestrator + 3 sonnet workers) and `scripts/ngram-overlap.mjs` (pure Node stdlib, word-5-gram containment + longest-run secondary signal, calibrated against three source/draft fixture pairs). Catalog growth is now tractable without touching the architect's hallucination gate. Non-goals stay explicit: no automation, no batch, no decision-layer skills, no remote sources — manual `mv` from `.drafts/` to catalog root is the promotion mechanism.
+v2.3 (non-breaking) ships the skill-factory Fase 1 MVP: `/ultra-skill-author-local` plus four supporting agents (1 opus orchestrator + 3 sonnet workers) and `scripts/ngram-overlap.mjs` (pure Node stdlib, word-5-gram containment + longest-run secondary signal, calibrated against three source/draft fixture pairs). Catalog growth is now tractable without touching the architect's hallucination gate. Non-goals stay explicit: no automation, no batch, no decision-layer skills, no remote sources — manual `mv` from `.drafts/` to catalog root is the promotion mechanism. v2.3.1 adds a qualified-slug convention (`[-]-.md`) so one feature can host multiple named patterns at different abstraction levels without displacing the baseline — resolved a collision surfaced in v2.3.0 dogfood.
v2.2 (non-breaking) adds the optional `/ultra-cc-architect-local` step between research and planning. The architect phase is backed by a versioned catalog skill (`cc-architect-catalog`) with 10 seed entries across three layers (reference, pattern, decision). Gaps are captured as issue-ready drafts so the catalog grows from real usage rather than speculation. `/ultraplan-local` auto-discovers the architecture note — existing pipelines keep working unchanged.
diff --git a/plugins/ultraplan-local/.claude-plugin/plugin.json b/plugins/ultraplan-local/.claude-plugin/plugin.json
index 1f2cb11..d22502b 100644
--- a/plugins/ultraplan-local/.claude-plugin/plugin.json
+++ b/plugins/ultraplan-local/.claude-plugin/plugin.json
@@ -1,7 +1,7 @@
{
"name": "ultraplan-local",
"description": "Five-command context-engineering pipeline (brief → research → architect → plan → execute) with project folders, CC-feature matching, specialized agent swarms, external research triangulation, adversarial review, session decomposition, and headless execution.",
- "version": "2.3.0",
+ "version": "2.3.1",
"author": {
"name": "Kjell Tore Guttormsen"
},
diff --git a/plugins/ultraplan-local/CHANGELOG.md b/plugins/ultraplan-local/CHANGELOG.md
index f573ae3..f357a4d 100644
--- a/plugins/ultraplan-local/CHANGELOG.md
+++ b/plugins/ultraplan-local/CHANGELOG.md
@@ -4,6 +4,47 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
+## [2.3.1] - 2026-04-18
+
+### Added — Qualified slug convention for cc-architect-catalog
+
+Catalog files now follow `[-]-.md`. The
+unqualified slug (e.g., `hooks-pattern.md`) remains the canonical
+baseline for a `(feature, layer)` pair. Qualified slugs (e.g.,
+`hooks-observability-pattern.md`) cover specific sub-patterns without
+displacing the baseline.
+
+**Why.** v2.3.0 dogfood surfaced a design gap: the skill-factory
+produced a draft `hooks-pattern.md` from a specialized source (progressive-
+alert observability) that collided with the existing generic `hooks-pattern.md`
+seed. Promoting would have replaced the general pattern with a narrow
+one; discarding would have lost real catalog growth. Qualified slugs
+resolve this by letting one feature host multiple named patterns at
+different abstraction levels.
+
+**Changes.**
+
+- `skills/cc-architect-catalog/SKILL.md` — slug convention section added;
+ coverage table gains "qualified patterns" column; matcher logic
+ documented for N patterns per feature; modification rules cover
+ qualified-vs-canonical choice and slug-collision handling.
+- `agents/feature-matcher.md` — catalog map is now
+ `cc_feature → {layer → [skills]}`; new "Selecting among multiple
+ patterns per feature" section (baseline by default, qualified when
+ justified, multiple when non-overlapping, never purely cosmetic);
+ `supporting_skill` accepts one-or-more skill names.
+- `agents/gap-identifier.md` — adds `pattern_count[cc_feature]` signal
+ to the catalog coverage audit.
+- `agents/architecture-critic.md` — adds supporting-skill verification:
+ every cited skill name must exist in the catalog; blocker severity.
+- First qualified skill: `hooks-observability-pattern.md` (promoted from
+ `.drafts/`, sourced from `ai-psychosis/README.md`, ngram-overlap 0.01,
+ review_status approved).
+
+**Non-breaking.** Existing unqualified slugs keep working. No changes to
+`cc_feature` taxonomy. Hallucination gate unchanged (still validates
+against `cc_feature` values, not slugs).
+
## [2.3.0] - 2026-04-18
### Added — Skill-factory Fase 1 MVP (`/ultra-skill-author-local`)
diff --git a/plugins/ultraplan-local/CLAUDE.md b/plugins/ultraplan-local/CLAUDE.md
index 388af85..5a829ce 100644
--- a/plugins/ultraplan-local/CLAUDE.md
+++ b/plugins/ultraplan-local/CLAUDE.md
@@ -123,6 +123,16 @@ Architect sits between `/ultraresearch-local` and `/ultraplan-local`. It matches
**CC-feature catalog skill:** The architect phase loads the `cc-architect-catalog` skill, which indexes Claude Code primitives (hooks, subagents, skills, output styles, MCP, plan mode, worktrees, background agents) across three layers: `reference` (how a feature works), `pattern` (when to reach for it), `decision` (adoption heuristics). The `feature-matcher` agent only proposes features covered by the catalog *or* an explicit fallback list — a hallucination gate that `architecture-critic` enforces as BLOCKER severity. The `gap-identifier` agent emits issue-ready drafts for missing catalog entries so the catalog grows with real usage rather than speculation. The catalog lives at `skills/cc-architect-catalog/`.
+**Slug convention (v2.3.1):** catalog files follow `[-]-.md`.
+
+- **Unqualified slugs** (e.g., `hooks-pattern.md`) are the canonical baseline — one per `(feature, layer)` pair, covering generic shapes and decision-heuristics for the feature.
+- **Qualified slugs** (e.g., `hooks-observability-pattern.md`) cover specific named sub-patterns. Zero-or-more per `(feature, layer)` pair. The qualifier MUST be kebab-case and descriptive (`observability`, `migration`, `multi-tenant`).
+- **Matcher logic:** `feature-matcher` builds `cc_feature → {layer → [skills]}` and prefers the unqualified baseline when the brief does not specifically justify a variant. Multiple skills can be proposed together when they cover non-overlapping aspects of the same feature.
+- **Critic enforcement:** `architecture-critic` verifies every cited `supporting_skill` name exists as a real file in the catalog (blocker severity). The `cc_feature` hallucination gate is unchanged — still validates against the taxonomy, not slugs.
+- **Collision handling:** skill-factory drafts that would overwrite an approved slug are a hard error. Resolution is either to qualify the new slug or revise the existing baseline.
+
+Seeds v2.3.1: 11 skills across 8 features — one qualified pattern (`hooks-observability-pattern.md`, promoted from `ai-psychosis/README.md`, ngram-overlap 0.01, approved). Decision-layer intentionally empty pending skill-factory Fase 2.
+
## State
All artifacts in one project directory (default):
diff --git a/plugins/ultraplan-local/README.md b/plugins/ultraplan-local/README.md
index b93f905..b7ddea2 100644
--- a/plugins/ultraplan-local/README.md
+++ b/plugins/ultraplan-local/README.md
@@ -1,6 +1,6 @@
# ultraplan-local — Brief, Research, Architect, Plan, Execute
-
+


@@ -224,7 +224,9 @@ Downstream: `/ultraplan-local` auto-discovers `architecture/overview.md` in proj
### CC-feature catalog
-The architect command reads from `skills/cc-architect-catalog/`, a plugin-internal catalog of hand-written skills covering each CC feature at one or more layers (`reference`, `pattern`, `decision`). v2.2 ships 10 seeds covering all 8 features at the reference layer (plus pattern layer for hooks and subagents). The catalog is designed for later expansion via a separate skill-factory process — gaps surfaced by the architect command are the backlog for that work.
+The architect command reads from `skills/cc-architect-catalog/`, a plugin-internal catalog of hand-written skills covering each CC feature at one or more layers (`reference`, `pattern`, `decision`). v2.2 shipped 10 seeds covering all 8 features at the reference layer (plus pattern layer for hooks and subagents). The catalog is designed for expansion via the skill-factory process (v2.3) — gaps surfaced by the architect command are the backlog for that work.
+
+**Slug convention (v2.3):** files follow `[-]-.md`. Unqualified slugs (e.g., `hooks-pattern.md`) are the baseline/canonical entry for a `(feature, layer)` pair. Qualified slugs (e.g., `hooks-observability-pattern.md`) cover specific sub-patterns without displacing the baseline. `feature-matcher` prefers the baseline when the brief does not specifically justify a qualified variant; it may propose multiple supporting skills when they cover non-overlapping aspects. Slug collisions with approved skills are a hard error — skill-factory drafts must qualify or revise instead.
### Hallucination gate
diff --git a/plugins/ultraplan-local/agents/architecture-critic.md b/plugins/ultraplan-local/agents/architecture-critic.md
index 4baa8cf..b63bd5f 100644
--- a/plugins/ultraplan-local/agents/architecture-critic.md
+++ b/plugins/ultraplan-local/agents/architecture-critic.md
@@ -66,6 +66,14 @@ the catalog or fallback list.
catalog, this is a **major** finding (REVISE — the feature is real but
the catalog has a coverage gap worth surfacing), not a blocker.
+**Supporting-skill verification:** `supporting_skill` entries (one or
+more skill names per feature, following the
+`[-]-.md` convention) must match real files
+in the catalog. A cited skill that does not exist is a **blocker**.
+Multiple supporting skills for one feature are allowed when they cover
+non-overlapping aspects — but the `integration_note` must justify
+having more than one.
+
### 3. Contradiction detection
Scan for internal contradictions:
diff --git a/plugins/ultraplan-local/agents/feature-matcher.md b/plugins/ultraplan-local/agents/feature-matcher.md
index 95e2403..7296e1c 100644
--- a/plugins/ultraplan-local/agents/feature-matcher.md
+++ b/plugins/ultraplan-local/agents/feature-matcher.md
@@ -47,14 +47,16 @@ enough.
### 2. Consult the catalog
-Read `{catalog_root}/SKILL.md` to learn the `cc_feature` taxonomy and
-layer model.
+Read `{catalog_root}/SKILL.md` to learn the `cc_feature` taxonomy, the
+layer model, and the slug convention (`[-]-.md`).
Glob `{catalog_root}/*.md` excluding `SKILL.md`. Parse each skill's
frontmatter:
- `name`, `description`, `layer`, `cc_feature`, `source`, `concept`.
-Build an in-memory map: `cc_feature → [skill_names]`.
+Build an in-memory map: `cc_feature → {layer → [skills]}`. One feature
+can have multiple pattern-layer skills (one baseline plus zero-or-more
+qualified variants).
**Fallback when the catalog is empty or unreadable:** use this
hardcoded minimum list. Mark `fallback_used: true` in your output.
@@ -79,14 +81,37 @@ For each feature you propose, produce:
Goal / Constraint / NFR / Success Criterion) that motivates this
feature. Prefer verbatim quotes; paraphrase only when length forces
it.
-- **supporting_skill** — a skill name from the catalog that supports
- this choice, or `null` if only the fallback hint was used.
+- **supporting_skill** — one or more skill names from the catalog that
+ support this choice, or `null` if only the fallback hint was used.
+ When multiple pattern skills exist for the feature, apply the
+ selection rules below.
- **confidence** — `high` (direct brief anchor + skill), `medium`
(brief anchor without strong skill support, or skill match without a
strong anchor), `low` (inferred need with no explicit anchor).
- **integration_note** — one sentence on how this feature integrates
with the task at hand.
+#### Selecting among multiple patterns per feature
+
+A feature can have a baseline pattern (`-pattern.md`) plus
+zero-or-more qualified patterns (`--pattern.md`).
+When the feature is relevant to the brief:
+
+1. **Baseline by default.** If the brief's anchor is generic
+ ("need hooks for policy"), pick the unqualified `-pattern`.
+2. **Qualified when justified.** If the brief explicitly calls for the
+ qualified variant's concept (e.g., observability, migration,
+ multi-tenant), pick the qualified pattern and name it in
+ `supporting_skill`. The anchor must reference the specific aspect,
+ not just the feature.
+3. **Propose both when they cover non-overlapping aspects.** Example:
+ the brief needs both generic hook shapes *and* observability-style
+ cadence tracking — list `supporting_skill: [hooks-pattern, hooks-observability-pattern]`
+ and explain the split in `integration_note`.
+4. **Never pick a qualified pattern just because it looks fancier.**
+ If the brief does not justify the qualifier, the baseline is the
+ honest answer.
+
### 4. Propose feature composition
After the per-feature list, write a short (3–5 bullet) note on how the
@@ -112,7 +137,7 @@ Return your response as markdown, with this structure:
1. **** (confidence: )
- Brief anchor: ""
- - Supporting skill:
+ - Supporting skill:
- Integration:
2. ...
@@ -140,7 +165,8 @@ Return your response as markdown, with this structure:
fallback list.** That is a hallucination; `architecture-critic` will
block it.
- **Never invent skill names.** If you don't see a skill for a
- feature, say "none — fallback hint".
+ feature, say "none — fallback hint". Every skill name in
+ `supporting_skill` must match a real file in the catalog.
- **Quote the brief; don't paraphrase silently.** Reviewers need to
verify the anchor matches.
- **Rationale must trace to the brief.** "We should have hooks because
diff --git a/plugins/ultraplan-local/agents/gap-identifier.md b/plugins/ultraplan-local/agents/gap-identifier.md
index 76a994d..0411da8 100644
--- a/plugins/ultraplan-local/agents/gap-identifier.md
+++ b/plugins/ultraplan-local/agents/gap-identifier.md
@@ -38,12 +38,16 @@ enough" notes. You do not propose architecture — only gaps.
### 1. Catalog audit
-Read `{catalog_root}/SKILL.md` to learn the taxonomy + coverage table.
-Glob `{catalog_root}/*.md` (excluding `SKILL.md`) and parse
-frontmatter. Build:
+Read `{catalog_root}/SKILL.md` to learn the taxonomy, slug convention
+(`[-]-.md`), and coverage table. Glob
+`{catalog_root}/*.md` (excluding `SKILL.md`) and parse frontmatter.
+Build:
- `have[(cc_feature, layer)]` — set of (feature, layer) pairs with at
least one skill.
+- `pattern_count[cc_feature]` — number of pattern-layer skills per
+ feature (useful signal for the audit; one baseline plus zero-or-more
+ qualified variants).
### 2. Read the brief + research
@@ -111,7 +115,7 @@ They are not issues; they are informational.
### Catalog coverage audit
- Skills in catalog: N
- Features with reference: [list]
-- Features with pattern: [list]
+- Features with pattern: [list with (feature, pattern_count) when >1]
- Features with decision: [list]
- Features with no coverage: [list]
diff --git a/plugins/ultraplan-local/skills/cc-architect-catalog/SKILL.md b/plugins/ultraplan-local/skills/cc-architect-catalog/SKILL.md
index e73518d..ba4948b 100644
--- a/plugins/ultraplan-local/skills/cc-architect-catalog/SKILL.md
+++ b/plugins/ultraplan-local/skills/cc-architect-catalog/SKILL.md
@@ -83,32 +83,70 @@ skill-factory output will start as `pending` (channel 2) or
If a future skill covers a feature not in this list, add the row above
and surface the extension in the relevant CHANGELOG entry.
+## Slug convention
+
+Skill filenames follow this pattern:
+
+```
+[-]-.md
+```
+
+- **Unqualified slug** (`-.md`) — the baseline/canonical
+ entry for that (feature, layer) pair. One per pair. Example:
+ `hooks-pattern.md` = generic hook shapes and pitfalls.
+- **Qualified slug** (`--.md`) — a
+ specialized variant covering one specific sub-pattern or use-case.
+ Zero-or-more per pair. Example: `hooks-observability-pattern.md` =
+ progressive-alert observability pattern specifically.
+
+Qualifiers exist because one CC feature can support multiple
+non-overlapping patterns at different abstraction levels. Forcing
+everything into a single `-.md` either bloats the
+canonical entry or loses useful specialization. Qualified slugs let the
+catalog grow with concrete, named patterns without displacing the
+baseline.
+
+`feature-matcher` treats all skills with the same `cc_feature` + `layer`
+as candidates and picks the most relevant to the brief (or proposes
+several if they cover different aspects). See "How `feature-matcher`
+uses this file" below.
+
## Current seed coverage (v2.3.0 — see .drafts/ for skill-factory output)
-| Feature | reference | pattern | decision |
-|---------|-----------|---------|----------|
-| hooks | hooks-reference | hooks-pattern | — |
-| subagents | subagents-reference | subagents-pattern | — |
-| skills | skills-reference | — | — |
-| output-styles | output-styles-reference | — | — |
-| mcp | mcp-reference | — | — |
-| plan-mode | plan-mode-reference | — | — |
-| worktrees | worktrees-reference | — | — |
-| background-agents | background-agents-reference | — | — |
+| Feature | reference | pattern | qualified patterns | decision |
+|---------|-----------|---------|--------------------|----------|
+| hooks | hooks-reference | hooks-pattern | hooks-observability-pattern | — |
+| subagents | subagents-reference | subagents-pattern | — | — |
+| skills | skills-reference | — | — | — |
+| output-styles | output-styles-reference | — | — | — |
+| mcp | mcp-reference | — | — | — |
+| plan-mode | plan-mode-reference | — | — | — |
+| worktrees | worktrees-reference | — | — | — |
+| background-agents | background-agents-reference | — | — | — |
-Total: 10 seed skills, 8 features, 2 layers. Decision-layer is
+Total: 11 seed skills, 8 features, 2 layers. Decision-layer is
intentionally empty — decisions cross features and require broader
synthesis than a single seed pass can provide. Skill-factory populates
decision-layer later.
## How `feature-matcher` uses this file
-1. Read this file to learn the `cc_feature` taxonomy.
+1. Read this file to learn the `cc_feature` taxonomy and slug convention.
2. Glob the directory for `*.md` files (excluding SKILL.md).
3. Parse each skill's frontmatter.
4. For each feature mentioned in the brief or research, match against
- `cc_feature` field. Prefer `pattern` over `reference` when both exist
- (pattern is richer).
+ `cc_feature` field. Build the candidate set per feature, grouped by
+ layer. Selection rules:
+ - **Layer preference:** prefer `pattern` over `reference` when both
+ exist (pattern is richer).
+ - **Multiple patterns per feature:** when two or more pattern-layer
+ skills share the same `cc_feature`, read each `description` and
+ pick the one(s) most relevant to the brief. If two cover
+ non-overlapping aspects that both apply, propose both with clear
+ rationale. Prefer the unqualified baseline (`-pattern.md`)
+ when the brief does not specifically justify a qualified variant.
+ - **Be explicit:** name the chosen skill in `supporting_skill` so
+ `architecture-critic` can verify the match.
5. When no skill exists for a mentioned feature, fall back to the
hardcoded minimum-list inside the `feature-matcher` prompt and mark
the gap in stats (`fallback_used: true`).
@@ -132,9 +170,24 @@ decision-layer later.
## Modification rules
-- Adding a new skill: create `-.md` with the frontmatter
- above and bump the coverage table in this file.
-- Renaming `cc_feature` values: update both this file AND every skill
- using the old value in the same commit.
-- Removing a skill: document in CHANGELOG under the version that drops
- it.
+- **Adding a canonical skill:** create `-.md` with the
+ frontmatter contract above. Bump the coverage table in this file.
+- **Adding a qualified pattern skill:** create
+ `--.md` when the new pattern covers a
+ distinct sub-case that does not belong in the unqualified baseline.
+ The qualifier MUST be kebab-case and descriptive (e.g.,
+ `observability`, `migration`, `multi-tenant`). Add it to the
+ "qualified patterns" column in the coverage table.
+- **Choosing qualified vs. canonical:** if no unqualified skill exists
+ yet for a `(feature, layer)` pair, the new skill SHOULD be the
+ unqualified baseline — don't ship a qualified skill without a
+ canonical one, because `feature-matcher` prefers baseline when the
+ brief has no specific justification for a variant.
+- **Renaming `cc_feature` values:** update both this file AND every
+ skill using the old value in the same commit.
+- **Removing a skill:** document in CHANGELOG under the version that
+ drops it.
+- **Slug collision:** two skills with the same slug are a hard error.
+ Skill-factory (`/ultra-skill-author-local`) must refuse to promote a
+ draft that would overwrite an approved skill. Collision is resolved
+ either by qualifying the new slug or by revising the baseline.
diff --git a/plugins/ultraplan-local/skills/cc-architect-catalog/hooks-observability-pattern.md b/plugins/ultraplan-local/skills/cc-architect-catalog/hooks-observability-pattern.md
new file mode 100644
index 0000000..6f2ce04
--- /dev/null
+++ b/plugins/ultraplan-local/skills/cc-architect-catalog/hooks-observability-pattern.md
@@ -0,0 +1,50 @@
+---
+name: hooks-observability-pattern
+description: Observe user-interaction patterns across session lifecycle hooks and emit cooldown-gated nudges
+layer: pattern
+cc_feature: hooks
+source: ../../../../ai-psychosis/README.md
+concept: progressive alerts via lifecycle hooks
+last_verified: 2026-04-18
+ngram_overlap_score: 0.01
+review_status: approved
+---
+
+# Hooks — Progressive-Alert Observability Pattern
+
+## Use this when
+
+- Measure behaviour the model itself cannot see: cadence, duration, repetition, time-of-day.
+- Surface soft nudges rather than hard blocks — the operator keeps the final call.
+- Separate "what happened" (metrics) from "what was said" (prompt text) so no conversation content touches disk.
+
+## Shape
+
+- Wire four lifecycle events: a start handler for baseline counters, a prompt handler for language-category flags, a tool handler for cadence and burst detection, and an end handler for totals and state cleanup.
+- Keep per-session counters in a tiny JSON file under the plugin data dir; keep aggregate events in an append-only JSONL log for later reporting.
+- Gate every nudge behind two things: a threshold (hard or soft) and a cooldown window, so repeat alerts do not spam the transcript.
+- Deliver alerts as `additionalContext` injection, never as a tool block — the goal is awareness, not control.
+
+## Forces
+
+- **Privacy vs. signal.** Richer signal wants more content logged; the user wants none. Resolve by computing boolean flags in-memory and discarding the raw text before the handler returns.
+- **Latency budget.** Handlers fire on every prompt and every tool call. Stay well under 100 ms per invocation; append-only JSONL is sub-millisecond and safe.
+- **Portability.** Hooks that assume a shell, `jq`, or npm dependencies break on half the operator fleet. Stick to Node stdlib so the same script runs on macOS, Linux, and Windows.
+- **Instruction layer alone is not enough.** Behavioural rules in a skill file shape tone but cannot measure duration or frequency. Layer the hook observability on top of the skill — each compensates for the other.
+
+## Gotchas
+
+- A handler that crashes blocks the turn. Catch everything, log, and exit zero by default.
+- Cooldowns must be per-category, not global, or the most-triggered alert silences the rarer, more informative ones.
+- Late-night and rapid-fire thresholds are legitimate signals but also easy to over-tune; start with generous bands and tighten only with data.
+- `additionalContext` from an end-of-session handler is discarded — inject alerts on start, prompt, or tool events where the model will actually see them.
+
+## Anti-patterns
+
+- Storing prompt text or tool arguments "just for debugging" — once it is on disk, the privacy guarantee is gone.
+- Treating every elevated metric as an intervention. If the hook starts blocking, the operator works around it and loses the awareness benefit.
+- Hardcoding thresholds into the handler. Pull them from a single config so future tuning does not require a rewrite of four scripts.
+
+## Decision quick-check
+
+Reach for this pattern when you need visibility into *how* the user is interacting, not *what* they are saying, and when the response should be a gentle nudge rather than a gate. Otherwise use a PreToolUse denylist (hard limit) or a skill-only instruction layer (style, not cadence).