--- name: skillforcer-config description: Use when configuring skillforcer — writing or editing .skillforcer.toml rules, choosing freshness windows (session/minutes/turns/tokens), using presets, or installing/uninstalling its hooks (Claude Code and Codex CLI). --- ## Overview skillforcer forces a skill to have loaded recently enough before it lets a governed write through. It runs as a `PreToolUse` hook in your coding agent — Claude Code and Codex CLI are both supported: when a write matches a rule and none of the required skills loaded within the rule's freshness window, skillforcer denies the write and returns a message telling the agent which skill to load. A `PostToolUse` hook records skill loads (Claude's `Skill` tool; in Codex, reads of a skill's `SKILL.md`) so the next write can check freshness. In Codex, explicit `$skill` invocations are detected from the session rollout. ## Install Install the binary, then register the hooks in the project. The install scripts fetch a prebuilt release binary: - macOS, Linux, or Windows Git Bash: `curl -fsSL https://raw.githubusercontent.com/strowk/skillforcer/main/install.sh | sh` - Windows PowerShell: `irm https://raw.githubusercontent.com/strowk/skillforcer/main/install.ps1 | iex` - From source: `cargo install --git https://github.com/strowk/skillforcer` (or `cargo install --path .` from a local checkout) Then, from the project's root directory: ``` skillforcer install ``` `install` adds a `PreToolUse` entry (matcher `Write|Edit|MultiEdit|NotebookEdit`) and a `PostToolUse` entry (matcher `Skill`) to Claude settings, and scaffolds `.skillforcer.toml` in the project if one doesn't exist. By default it targets `.claude/settings.json`; `--local` targets `.claude/settings.local.json`, `--user` targets `~/.claude/settings.json`. `--dry-run` prints the resulting settings without writing them. `skillforcer uninstall` (same target flags) removes only the hook entries it added. `install` detects the harness from the project (`.claude/` → Claude Code, `.codex/` or `.agents/skills/` → Codex; both → both; neither → Claude Code). Force one with `--claude` or `--codex`. For Codex, hooks go to `.codex/hooks.json` (project) or `~/.codex/hooks.json` (`--user`); `--local` is Claude-only. **Codex trust gate:** after installing, run `/hooks` inside Codex and approve the skillforcer hook. Until it is trusted it enforces nothing, and `codex exec` skips untrusted hooks silently. ## Rule anatomy Rules live in `.skillforcer.toml` at the project root. `[defaults]` sets `fail_open` (default `true`) and `combine_freshness` (`"all"` or `"any"`, default `"all"`). Each `[[rule]]` needs a `name`, a `requires`, and enough of `path`/`content`/`extends` to match the writes it governs. ```toml [defaults] fail_open = true combine_freshness = "all" [[rule]] name = "comments-need-tech-writing" extends = ["code-comments"] path = ["src/**/*.rs", "**/*.ts"] requires = { any_skill = ["tech-writing:technical-writing"], minutes = 15 } message = "Editing comments in {file}. Load {skills} first — context may have rotted." ``` - `path` — glob patterns; the rule applies only to files that match one of them. - `content` — a regex tested against the new file content; omit to match on path alone. - `extends` — names of presets (see below) to merge in. - `requires` — exactly one of `any_skill = [...]` (fresh if any listed skill is fresh) or `all_skills = [...]` (all listed skills must be fresh), plus at least one freshness window. A rule with neither or both of `any_skill`/`all_skills`, or with no freshness window, fails to load. - `message` — the denial text; omit to use the built-in default. Skill names in rules match by suffix across harnesses: `tech-writing:technical-writing` (Claude plugin:skill form) also matches a Codex load of the `technical-writing` skill directory, and vice versa. A project rule and a global rule (`~/.config/skillforcer/config.toml`, or the platform-equivalent config dir) with the same `name` do not merge — the project rule replaces the global one entirely. A personal `.skillforcer.local.toml` (git-ignored, added by `install`) layers over `.skillforcer.toml` with the same schema and highest precedence (global → project → local). Set `enabled = false` on a rule there to silence one inherited from the project or global config. ## Freshness windows | Key | Satisfied when | | --- | --- | | `session = true` | the skill has loaded at least once this session | | `minutes = N` | the skill loaded within the last N minutes | | `turns = N` | the skill loaded within the last N transcript turns | | `tokens = N` | the skill loaded within the last N cumulative output tokens | A rule can set more than one window. `combine_freshness = "all"` requires every set window to hold; `"any"` requires only one to hold. ## Presets `skillforcer list-presets` lists the bundled presets: - `code-comments` — matches comment syntax (`//`, `/* */`, `