continuous-improvement 3.25.0 → 3.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,7 +8,7 @@
8
8
  {
9
9
  "name": "continuous-improvement",
10
10
  "description": "The persistent-memory and runtime-discipline layer for Claude Code. It remembers the corrections you already gave, grounds every edit in real facts before it lands, and — through the Mulahazah engine — turns each fix into a reusable instinct, so a lesson learned once is applied automatically next time with no re-teaching. Built on the 7 Laws of AI Agent Discipline (research, plan, verify, reflect, learn) and shipped as 28 bundled skills, instinct-aware hooks, an MCP toolset for recall and reflection, and a GitHub Action transcript linter that feeds real work history back into sharper instincts.",
11
- "version": "3.25.0",
11
+ "version": "3.26.0",
12
12
  "source": "./plugins/continuous-improvement",
13
13
  "author": {
14
14
  "name": "naimkatiman"
package/CHANGELOG.md CHANGED
@@ -4,6 +4,17 @@ All notable changes to this skill are documented here.
4
4
 
5
5
  ---
6
6
 
7
+ ## [3.26.0] — 2026-11-14
8
+
9
+ ### Added
10
+
11
+ - **`CI_ROUTE_PROMPT_DISABLE=1` turns the prompt router off** — `hooks/route-prompt.mjs` had no off switch, so a project with its own plan, test or review skills got a competing `planning-with-files` / `tdd-workflow` hint on every prompt containing those words. Set the variable in that project's `.claude/settings.json` `env` block and the hook emits nothing and writes no telemetry. Default unchanged. (#321)
12
+
13
+ ### Fixed
14
+
15
+ - **`/ship` no longer falls through to a project's own `ship` skill** — the command forwarded to "the native `ship` skill", and in a repo that defines `.claude/skills/ship` a bare `ship` resolves to that project workflow, which can merge and deploy. The command now names where the native skill lives and stops to ask when the project defines its own. (#321)
16
+ - **gateguard's cleared-files cap no longer stops real multi-slice work** — the 50-file cap halted a session touching source, tests and docs across a few PRs. The default is now 1000, which still bounds a runaway loop, and a positive-integer `GATEGUARD_MAX_CLEARED_FILES` overrides it. (#320)
17
+
7
18
  ## [3.25.0] — 2026-09-07
8
19
 
9
20
  ### Added
package/README.md CHANGED
@@ -171,7 +171,7 @@ You should see Claude **blocked** by the bundled `gateguard` PreToolUse hook (`h
171
171
 
172
172
  The 7 Laws are enforced at **two layers**:
173
173
 
174
- - **Runtime layer (hooks).** `gateguard` ships as a PreToolUse hook (`hooks/gateguard.mjs`) that physically blocks Edit / Write / MultiEdit / destructive Bash on the first mutation per file until the agent presents the facts named in [skills/gateguard.md § Gate Types](skills/gateguard.md). Destructive Bash (`rm -rf`, `git push --force`, `--force-with-lease`, `DROP DATABASE`, Windows `Remove-Item -Recurse`, etc.) is gated on every call, not just first. Read-only and exploratory tools (Read, Grep, Glob, routine Bash like `git status`) bypass the gate. Per-session state at `~/.claude/instincts/<project-hash>/gateguard-session.json` caps cumulative clearances at 50 distinct files to bound stuck-loop damage. A second runtime hook, `goal-drift-stop` (`hooks/goal-drift-stop.mjs`), fires on `Stop`: it scores each turn against the stated `## Goal` and, on a substantive wrap-up that has drifted off-goal, warns by default (or re-prompts under `CLAUDE_GOAL_DRIFT_GATE=block`) — so a drifted session can't quietly declare "done". Fail-open.
174
+ - **Runtime layer (hooks).** `gateguard` ships as a PreToolUse hook (`hooks/gateguard.mjs`) that physically blocks Edit / Write / MultiEdit / destructive Bash on the first mutation per file until the agent presents the facts named in [skills/gateguard.md § Gate Types](skills/gateguard.md). Destructive Bash (`rm -rf`, `git push --force`, `--force-with-lease`, `DROP DATABASE`, Windows `Remove-Item -Recurse`, etc.) is gated on every call, not just first. Read-only and exploratory tools (Read, Grep, Glob, routine Bash like `git status`) bypass the gate. Per-session state at `~/.claude/instincts/<project-hash>/gateguard-session.json` caps cumulative clearances at 1000 distinct files per session by default (`GATEGUARD_MAX_CLEARED_FILES` overrides it) to bound stuck-loop damage. A second runtime hook, `goal-drift-stop` (`hooks/goal-drift-stop.mjs`), fires on `Stop`: it scores each turn against the stated `## Goal` and, on a substantive wrap-up that has drifted off-goal, warns by default (or re-prompts under `CLAUDE_GOAL_DRIFT_GATE=block`) — so a drifted session can't quietly declare "done". Fail-open.
175
175
  - **Model layer (skills).** Once the runtime gate clears for a file, the rest of the discipline (`tdd-workflow`, `verification-loop`, `proceed-with-the-recommendation`, etc.) runs model-side — the agent reads each skill and applies it. `observe.sh` / `observe.mjs` records every tool call into the Mulahazah feed for instinct extraction; that surface is observational, not enforcement.
176
176
 
177
177
  V1 honest limitations: the runtime gate is honor-system once the agent flips `_gateguard_facts_presented: true` (the hook can't verify the investigation actually happened); the state file is deletable and parallel hook invocations can race. Documented in `src/hooks/gateguard.mts` and `src/lib/gateguard-state.mts` headers.
@@ -226,6 +226,7 @@ The framework has documented operator-level modes that change hook behavior with
226
226
  | `CI_GATEGUARD_EXCLUDE` | Comma-separated path fragments that `hooks/gateguard.mjs` skips for Edit / Write / MultiEdit (case-insensitive substring match on the forward-slash path). Meant for a prose wiki or a generated scratch dir. The hook prints one stderr line naming the fragment whenever an exclusion fires, and a catch-all fragment such as `/` or `.` matches every path and is reported as the file gate being off. Destructive Bash is never excluded. Unset (default) changes nothing. | bash/zsh: `export CI_GATEGUARD_EXCLUDE=docs/wiki,scratch/`. PowerShell: `$env:CI_GATEGUARD_EXCLUDE='docs/wiki,scratch/'` (session). |
227
227
  | `CI_GATEGUARD_TARGET_LOCK=block` | `hooks/gateguard.mjs` refuses any Edit / Write / MultiEdit whose absolute target canonicalizes outside the session project root, before the fact gate and independent of clearance, so a wrong-repo or wrong-worktree write is denied even with perfect facts. Off by default. | bash/zsh: `export CI_GATEGUARD_TARGET_LOCK=block`. PowerShell: `$env:CI_GATEGUARD_TARGET_LOCK='block'` (session). |
228
228
  | `CI_CONFIG_GUARD` | `hooks/config-guard.mjs` (a PreToolUse hook on Bash / Edit / Write / MultiEdit / NotebookEdit) watches the files that wire the guardrails: `.claude/settings*.json`, `.mcp.json`, `hooks.json`, `.claude/hooks/`, `.claude/plugins/`, `.claude-plugin/`, and the `claude plugin\|mcp\|config` CLI forms that edit them. `warn` (default) prints one stderr line and never blocks; `block` denies the call with the reason and the bypass; `off` disables it. Reads never trigger it. One-call bypass: `CI_CONFIG_GUARD_ALLOW=true`. Fails open. | bash/zsh: `export CI_CONFIG_GUARD=block` in `~/.bashrc` / `~/.zshrc`. PowerShell: `$env:CI_CONFIG_GUARD='block'` (session) or `[Environment]::SetEnvironmentVariable('CI_CONFIG_GUARD','block','User')` (persistent). |
229
+ | `CI_ROUTE_PROMPT_DISABLE=1` | `hooks/route-prompt.mjs` (a UserPromptSubmit hook) emits no skill suggestion and writes no telemetry. Use it in a project that routes plan, test or review prompts to its own skills, so the generic hints do not compete with them. Set it per project in that project's `.claude/settings.json` `env` block. `true` also works; unset or any other value changes nothing. | Per project: `"env": { "CI_ROUTE_PROMPT_DISABLE": "1" }` in `.claude/settings.json`. bash/zsh: `export CI_ROUTE_PROMPT_DISABLE=1`. PowerShell: `$env:CI_ROUTE_PROMPT_DISABLE=1` (session). |
229
230
  | `CLAUDE_QUERY_COST_NUDGE=on` | `hooks/query-cost-nudge.mjs` (a `Stop` hook) guards against surprise DB bills: when the working tree has changed DB/query files (`.sql`, `.prisma`, `migrations/`, `/db/`, `schema.*`, `drizzle`) at turn end, it injects a once-per-session `additionalContext` reminder to run a D1-aware cost audit — dispatch the `database-reviewer` agent or check EXPLAIN QUERY PLAN, index coverage, N+1, and D1 `rows_read` billing before finishing. `on` enables it; default (unset) is off. Opt-in amplifier, never a gate; dedupes per session and fails open. | bash/zsh: `export CLAUDE_QUERY_COST_NUDGE=on` in `~/.bashrc` / `~/.zshrc`. PowerShell: `$env:CLAUDE_QUERY_COST_NUDGE='on'` (session) or `[Environment]::SetEnvironmentVariable('CLAUDE_QUERY_COST_NUDGE','on','User')` (persistent). |
230
231
 
231
232
  </details>
package/commands/ship.md CHANGED
@@ -10,4 +10,6 @@ Invoke the native `ship` skill with this complete defect request:
10
10
 
11
11
  $ARGUMENTS
12
12
 
13
+ The native skill is the one continuous-improvement installs: `skills/ship.md` in the plugin, or `~/.claude/skills/ship/SKILL.md` from the npm installer. If the current project defines its own `.claude/skills/ship`, that is a different workflow with its own merge and deploy rules, and a bare `ship` resolves to it. In that case stop and ask the operator which workflow they meant before doing anything.
14
+
13
15
  Follow that skill exactly. Do not reconstruct or duplicate its Git, worktree, TDD, return, cleanup, merge, or deploy rules here. If the native skill is unavailable, stop and tell the operator to update or reinstall `continuous-improvement`.
@@ -13,6 +13,10 @@
13
13
  * OR empty when there is no match.
14
14
  * Exit : 0 always — routing decisions live in stdout, never in exit code.
15
15
  *
16
+ * Opt-out: CI_ROUTE_PROMPT_DISABLE=1 (or `true`) emits nothing and writes no
17
+ * telemetry. Set it in a project's `.claude/settings.json` `env` block when
18
+ * that project routes plan / test / review prompts to its own skills.
19
+ *
16
20
  * Fail-open: any unexpected error reading stdin / parsing the route table /
17
21
  * compiling a pattern → emit nothing and exit 0. The hook never blocks a
18
22
  * prompt on its own bugs.
@@ -44,6 +48,10 @@ function readStdin() {
44
48
  return "";
45
49
  }
46
50
  }
51
+ function isRouterDisabled() {
52
+ const value = (process.env.CI_ROUTE_PROMPT_DISABLE ?? "").trim().toLowerCase();
53
+ return value === "1" || value === "true";
54
+ }
47
55
  function resolveHome() {
48
56
  return process.env.HOME ?? process.env.USERPROFILE ?? homedir();
49
57
  }
@@ -129,6 +137,10 @@ function emit(output) {
129
137
  process.exit(0);
130
138
  }
131
139
  function main() {
140
+ if (isRouterDisabled()) {
141
+ emit(null);
142
+ return;
143
+ }
132
144
  const raw = readStdin();
133
145
  let payload;
134
146
  try {
@@ -27,7 +27,18 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
27
27
  import { execFileSync } from "node:child_process";
28
28
  import { homedir } from "node:os";
29
29
  import { join } from "node:path";
30
- export const MAX_CLEARED_FILES = 50;
30
+ const DEFAULT_MAX_CLEARED_FILES = 1000;
31
+ /**
32
+ * The per-session cap. 50 stopped real multi-slice work mid-task (a session that
33
+ * touches a few dozen source, test and doc files hit it); 1000 still bounds a
34
+ * runaway loop. GATEGUARD_MAX_CLEARED_FILES (a positive integer) overrides it.
35
+ */
36
+ export function resolveMaxClearedFiles(env) {
37
+ const raw = env.GATEGUARD_MAX_CLEARED_FILES ?? "";
38
+ const n = /^\d+$/.test(raw.trim()) ? Number(raw.trim()) : Number.NaN;
39
+ return Number.isSafeInteger(n) && n > 0 ? n : DEFAULT_MAX_CLEARED_FILES;
40
+ }
41
+ export const MAX_CLEARED_FILES = resolveMaxClearedFiles(process.env);
31
42
  // A clearance file whose created_at is older than this self-heals to empty on
32
43
  // the next load, so a stale gate never needs a manual rm. Long enough not to
33
44
  // reset an active session mid-work; the real cross-session fix is the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "continuous-improvement",
3
- "version": "3.25.0",
3
+ "version": "3.26.0",
4
4
  "description": "Claude Code that gets sharper every session: the persistent-memory and runtime-discipline layer built on the 7 Laws of AI Agent Discipline. It grounds every edit in real facts before it lands and, through the Mulahazah engine, turns each fix into a reusable instinct, so a lesson learned once is applied automatically next time with no re-teaching. Shipped as 28 bundled skills, instinct-aware hooks, an MCP toolset for recall and reflection, and a GitHub Action transcript linter that feeds real work history back into sharper instincts. Beginner: one /plugin install command. Expert: adds MCP tools and session hooks.",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "continuous-improvement",
3
- "version": "3.25.0",
3
+ "version": "3.26.0",
4
4
  "mode": "beginner",
5
5
  "description": "Beginner mode: see what your agent learned, list its instincts, and request a session reflection. Bundles the ship fast path plus grounding skills (gateguard, tdd-workflow, verification-loop) so one-defect delivery, research, tests, and verification happen by default — every edit starts from facts, not guesses.",
6
6
  "tools": [
@@ -8,7 +8,7 @@
8
8
  {
9
9
  "name": "continuous-improvement",
10
10
  "description": "The persistent-memory and runtime-discipline layer for Claude Code. It remembers the corrections you already gave, grounds every edit in real facts before it lands, and — through the Mulahazah engine — turns each fix into a reusable instinct, so a lesson learned once is applied automatically next time with no re-teaching. Built on the 7 Laws of AI Agent Discipline (research, plan, verify, reflect, learn) and shipped as 28 bundled skills, instinct-aware hooks, an MCP toolset for recall and reflection, and a GitHub Action transcript linter that feeds real work history back into sharper instincts.",
11
- "version": "3.25.0",
11
+ "version": "3.26.0",
12
12
  "source": "./",
13
13
  "author": {
14
14
  "name": "naimkatiman"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "continuous-improvement",
3
- "version": "3.25.0",
3
+ "version": "3.26.0",
4
4
  "description": "The persistent-memory and runtime-discipline layer for Claude Code. It remembers the corrections you already gave, grounds every edit in real facts before it lands, and — through the Mulahazah engine — turns each fix into a reusable instinct, so a lesson learned once is applied automatically next time with no re-teaching. Built on the 7 Laws of AI Agent Discipline (research, plan, verify, reflect, learn) and shipped as 28 bundled skills, instinct-aware hooks, an MCP toolset for recall and reflection, and a GitHub Action transcript linter that feeds real work history back into sharper instincts.",
5
5
  "author": {
6
6
  "name": "naimkatiman",
@@ -10,4 +10,6 @@ Invoke the native `ship` skill with this complete defect request:
10
10
 
11
11
  $ARGUMENTS
12
12
 
13
+ The native skill is the one continuous-improvement installs: `skills/ship.md` in the plugin, or `~/.claude/skills/ship/SKILL.md` from the npm installer. If the current project defines its own `.claude/skills/ship`, that is a different workflow with its own merge and deploy rules, and a bare `ship` resolves to it. In that case stop and ask the operator which workflow they meant before doing anything.
14
+
13
15
  Follow that skill exactly. Do not reconstruct or duplicate its Git, worktree, TDD, return, cleanup, merge, or deploy rules here. If the native skill is unavailable, stop and tell the operator to update or reinstall `continuous-improvement`.
@@ -13,6 +13,10 @@
13
13
  * OR empty when there is no match.
14
14
  * Exit : 0 always — routing decisions live in stdout, never in exit code.
15
15
  *
16
+ * Opt-out: CI_ROUTE_PROMPT_DISABLE=1 (or `true`) emits nothing and writes no
17
+ * telemetry. Set it in a project's `.claude/settings.json` `env` block when
18
+ * that project routes plan / test / review prompts to its own skills.
19
+ *
16
20
  * Fail-open: any unexpected error reading stdin / parsing the route table /
17
21
  * compiling a pattern → emit nothing and exit 0. The hook never blocks a
18
22
  * prompt on its own bugs.
@@ -44,6 +48,10 @@ function readStdin() {
44
48
  return "";
45
49
  }
46
50
  }
51
+ function isRouterDisabled() {
52
+ const value = (process.env.CI_ROUTE_PROMPT_DISABLE ?? "").trim().toLowerCase();
53
+ return value === "1" || value === "true";
54
+ }
47
55
  function resolveHome() {
48
56
  return process.env.HOME ?? process.env.USERPROFILE ?? homedir();
49
57
  }
@@ -129,6 +137,10 @@ function emit(output) {
129
137
  process.exit(0);
130
138
  }
131
139
  function main() {
140
+ if (isRouterDisabled()) {
141
+ emit(null);
142
+ return;
143
+ }
132
144
  const raw = readStdin();
133
145
  let payload;
134
146
  try {
@@ -27,7 +27,18 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
27
27
  import { execFileSync } from "node:child_process";
28
28
  import { homedir } from "node:os";
29
29
  import { join } from "node:path";
30
- export const MAX_CLEARED_FILES = 50;
30
+ const DEFAULT_MAX_CLEARED_FILES = 1000;
31
+ /**
32
+ * The per-session cap. 50 stopped real multi-slice work mid-task (a session that
33
+ * touches a few dozen source, test and doc files hit it); 1000 still bounds a
34
+ * runaway loop. GATEGUARD_MAX_CLEARED_FILES (a positive integer) overrides it.
35
+ */
36
+ export function resolveMaxClearedFiles(env) {
37
+ const raw = env.GATEGUARD_MAX_CLEARED_FILES ?? "";
38
+ const n = /^\d+$/.test(raw.trim()) ? Number(raw.trim()) : Number.NaN;
39
+ return Number.isSafeInteger(n) && n > 0 ? n : DEFAULT_MAX_CLEARED_FILES;
40
+ }
41
+ export const MAX_CLEARED_FILES = resolveMaxClearedFiles(process.env);
31
42
  // A clearance file whose created_at is older than this self-heals to empty on
32
43
  // the next load, so a stale gate never needs a manual rm. Long enough not to
33
44
  // reset an active session mid-work; the real cross-session fix is the
@@ -136,7 +136,7 @@ This gate is what catches the squash-merge / ahead-of-origin trap recorded in th
136
136
 
137
137
  ### Today: runtime hook + skill (zero install beyond the plugin)
138
138
 
139
- `hooks/gateguard.mjs` is bundled with this plugin and wired as the first PreToolUse hook in `plugins/continuous-improvement/hooks/hooks.json`. When you install the plugin, the runtime gate is live — no extra config, no opt-in. The hook reads tool input from stdin, classifies it through a data-driven routing table (Read/Grep/Glob → allow, Write/Edit/MultiEdit → mutating-file gate, Bash → destructive-pattern check), and emits `{decision, reason?}` on stdout. Per-session state lives at `~/.claude/instincts/<project-hash>/sessions/<session-id>/gateguard-session.json` — scoped by the stdin `session_id` so the cap never bleeds across concurrent sessions (it falls back to the unscoped `<project-hash>/` dir when no session id is present, and `GATEGUARD_SESSION_DIR` overrides it for tests). The cap is `MAX_CLEARED_FILES = 50` per session, and the state self-heals after `STATE_TTL_MS` so a stale gate never needs a manual `rm`.
139
+ `hooks/gateguard.mjs` is bundled with this plugin and wired as the first PreToolUse hook in `plugins/continuous-improvement/hooks/hooks.json`. When you install the plugin, the runtime gate is live — no extra config, no opt-in. The hook reads tool input from stdin, classifies it through a data-driven routing table (Read/Grep/Glob → allow, Write/Edit/MultiEdit → mutating-file gate, Bash → destructive-pattern check), and emits `{decision, reason?}` on stdout. Per-session state lives at `~/.claude/instincts/<project-hash>/sessions/<session-id>/gateguard-session.json` — scoped by the stdin `session_id` so the cap never bleeds across concurrent sessions (it falls back to the unscoped `<project-hash>/` dir when no session id is present, and `GATEGUARD_SESSION_DIR` overrides it for tests). The cap is `MAX_CLEARED_FILES`, 1000 distinct files per session by default (set `GATEGUARD_MAX_CLEARED_FILES` to a positive integer to change it), and the state self-heals after `STATE_TTL_MS` so a stale gate never needs a manual `rm`.
140
140
 
141
141
  Smoke-test the runtime gate after install: ask Claude to write a throwaway file with no research first. The hook should return a `block` decision with a fact-list reason; Claude should pause rather than write.
142
142
 
@@ -174,7 +174,7 @@ If you previously opted into `safety-guard` for autonomous or production-adjacen
174
174
 
175
175
  ### Limitations and guarantees
176
176
 
177
- - **Honor system.** Clearance is recorded by `ci_gateguard_clear`, the `gateguard-clear.mjs` CLI, a manual state-file write, or the inline `_gateguard_facts_presented` flag where the harness allows it (see "Clearing the gate" above). The hook can't verify the investigation actually happened; the 50-file cap — counted per session — bounds damage from stuck loops or rogue agents.
177
+ - **Honor system.** Clearance is recorded by `ci_gateguard_clear`, the `gateguard-clear.mjs` CLI, a manual state-file write, or the inline `_gateguard_facts_presented` flag where the harness allows it (see "Clearing the gate" above). The hook can't verify the investigation actually happened; the per-session file cap (1000 by default) bounds damage from stuck loops or rogue agents.
178
178
  - **State-file deletion / self-heal.** `rm`-ing the session state resets every gate. Because state is scoped per session (`sessions/<session-id>/`), the session is a real trust boundary, not one shared across concurrent runs. A stale state file also self-heals once its `created_at` ages past `STATE_TTL_MS`, so a manual `rm` is rarely needed.
179
179
  - **Parallel-hook race.** Two simultaneous hook invocations can race the read+write of the state file. Acceptable trade-off vs Windows atomic-rename complexity.
180
180
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "continuous-improvement",
3
- "version": "3.25.0",
3
+ "version": "3.26.0",
4
4
  "mode": "expert",
5
5
  "description": "Expert mode: tune confidence, manage instincts, and persist plans on disk. Adds safety, token-budget, and strategic-compact skills plus the /learn-eval command so long sessions stay sharp and learnings survive context resets.",
6
6
  "tools": [
@@ -136,7 +136,7 @@ This gate is what catches the squash-merge / ahead-of-origin trap recorded in th
136
136
 
137
137
  ### Today: runtime hook + skill (zero install beyond the plugin)
138
138
 
139
- `hooks/gateguard.mjs` is bundled with this plugin and wired as the first PreToolUse hook in `plugins/continuous-improvement/hooks/hooks.json`. When you install the plugin, the runtime gate is live — no extra config, no opt-in. The hook reads tool input from stdin, classifies it through a data-driven routing table (Read/Grep/Glob → allow, Write/Edit/MultiEdit → mutating-file gate, Bash → destructive-pattern check), and emits `{decision, reason?}` on stdout. Per-session state lives at `~/.claude/instincts/<project-hash>/sessions/<session-id>/gateguard-session.json` — scoped by the stdin `session_id` so the cap never bleeds across concurrent sessions (it falls back to the unscoped `<project-hash>/` dir when no session id is present, and `GATEGUARD_SESSION_DIR` overrides it for tests). The cap is `MAX_CLEARED_FILES = 50` per session, and the state self-heals after `STATE_TTL_MS` so a stale gate never needs a manual `rm`.
139
+ `hooks/gateguard.mjs` is bundled with this plugin and wired as the first PreToolUse hook in `plugins/continuous-improvement/hooks/hooks.json`. When you install the plugin, the runtime gate is live — no extra config, no opt-in. The hook reads tool input from stdin, classifies it through a data-driven routing table (Read/Grep/Glob → allow, Write/Edit/MultiEdit → mutating-file gate, Bash → destructive-pattern check), and emits `{decision, reason?}` on stdout. Per-session state lives at `~/.claude/instincts/<project-hash>/sessions/<session-id>/gateguard-session.json` — scoped by the stdin `session_id` so the cap never bleeds across concurrent sessions (it falls back to the unscoped `<project-hash>/` dir when no session id is present, and `GATEGUARD_SESSION_DIR` overrides it for tests). The cap is `MAX_CLEARED_FILES`, 1000 distinct files per session by default (set `GATEGUARD_MAX_CLEARED_FILES` to a positive integer to change it), and the state self-heals after `STATE_TTL_MS` so a stale gate never needs a manual `rm`.
140
140
 
141
141
  Smoke-test the runtime gate after install: ask Claude to write a throwaway file with no research first. The hook should return a `block` decision with a fact-list reason; Claude should pause rather than write.
142
142
 
@@ -174,7 +174,7 @@ If you previously opted into `safety-guard` for autonomous or production-adjacen
174
174
 
175
175
  ### Limitations and guarantees
176
176
 
177
- - **Honor system.** Clearance is recorded by `ci_gateguard_clear`, the `gateguard-clear.mjs` CLI, a manual state-file write, or the inline `_gateguard_facts_presented` flag where the harness allows it (see "Clearing the gate" above). The hook can't verify the investigation actually happened; the 50-file cap — counted per session — bounds damage from stuck loops or rogue agents.
177
+ - **Honor system.** Clearance is recorded by `ci_gateguard_clear`, the `gateguard-clear.mjs` CLI, a manual state-file write, or the inline `_gateguard_facts_presented` flag where the harness allows it (see "Clearing the gate" above). The hook can't verify the investigation actually happened; the per-session file cap (1000 by default) bounds damage from stuck loops or rogue agents.
178
178
  - **State-file deletion / self-heal.** `rm`-ing the session state resets every gate. Because state is scoped per session (`sessions/<session-id>/`), the session is a real trust boundary, not one shared across concurrent runs. A stale state file also self-heals once its `created_at` ages past `STATE_TTL_MS`, so a manual `rm` is rarely needed.
179
179
  - **Parallel-hook race.** Two simultaneous hook invocations can race the read+write of the state file. Acceptable trade-off vs Windows atomic-rename complexity.
180
180