Lint Repository Markdown
codsen/codsen
Keep the repository's linted Markdown passing npm run lint:markdown.
Distill imperative markdown guidance (AGENTS.md, CLAUDE.md, .clinerules/, GEMINI.md, SKILL.md with hard imperatives) into deterministic Interlinked harness hook rules with verbatim source provenance.
$ npx skills add QuentinCody/interlinked-cli --skill enforce -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install QuentinCody/interlinked-cli enforce --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/QuentinCody/interlinked-cli.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/enforce .claude/skills/enforce && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "enforce" agent skill from https://github.com/QuentinCody/interlinked-cli/tree/main/skills/enforce into .claude/skills/enforce/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "enforce", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/QuentinCody/interlinked-cli/tree/main/skills/enforceType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add QuentinCody/interlinked-cli --skill enforce -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install QuentinCody/interlinked-cli enforce --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/QuentinCody/interlinked-cli.git skills-src && mkdir -p .agents/skills && cp -r skills-src/skills/enforce .agents/skills/enforce && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "enforce" agent skill from https://github.com/QuentinCody/interlinked-cli/tree/main/skills/enforce into .agents/skills/enforce/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "enforce", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add QuentinCody/interlinked-cli --skill enforce -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install QuentinCody/interlinked-cli enforce --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/QuentinCody/interlinked-cli.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/skills/enforce .cursor/skills/enforce && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "enforce" agent skill from https://github.com/QuentinCody/interlinked-cli/tree/main/skills/enforce into .cursor/skills/enforce/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "enforce", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/QuentinCody/interlinked-cli.git --path skills/enforce--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add QuentinCody/interlinked-cli --skill enforce -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install QuentinCody/interlinked-cli enforce --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/QuentinCody/interlinked-cli.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/skills/enforce .gemini/skills/enforce && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "enforce" agent skill from https://github.com/QuentinCody/interlinked-cli/tree/main/skills/enforce into .gemini/skills/enforce/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "enforce", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install QuentinCody/interlinked-cli enforceInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add QuentinCody/interlinked-cli --skill enforce -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/QuentinCody/interlinked-cli.git skills-src && mkdir -p .github/skills && cp -r skills-src/skills/enforce .github/skills/enforce && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "enforce" agent skill from https://github.com/QuentinCody/interlinked-cli/tree/main/skills/enforce into .github/skills/enforce/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "enforce", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add QuentinCody/interlinked-cli --skill enforce -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install QuentinCody/interlinked-cli enforce --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/QuentinCody/interlinked-cli.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/skills/enforce .opencode/skills/enforce && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "enforce" agent skill from https://github.com/QuentinCody/interlinked-cli/tree/main/skills/enforce into .opencode/skills/enforce/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "enforce", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
enforceDistill imperative markdown guidance (AGENTS.md, CLAUDE.md, .clinerules/, GEMINI.md, SKILL.md with hard imperatives) into deterministic Interlinked harness hook rules with verbatim source provenance.
Enforce is an agent skill from QuentinCody/interlinked-cli. Distill imperative markdown guidance (AGENTS.md, CLAUDE.md, .clinerules/, GEMINI.md, SKILL.md with hard imperatives) into deterministic Interlinked harness hook rules with verbatim source provenance. Invoke as /enforce followed by a local path, directory, GitHub shorthand, or URL — or with no arguments to walk the project. Lexical strength is binding: never/MUST NOT/forbidden distill to block; should not/avoid to ask; should/prefer to advisory; hedged language is skipped. Output goes to…
Its SKILL.md is about 19k tokens, which your agent loads only when the skill is triggered. The skill folder holds 2 other files (for example `agents/openai.yaml`).
It sits in Agent Workflows, covering Agent instruction files and Markdown. It works with GitHub. The repository describes itself as: The harness for your harness. Local hooks, taste enforcement, and developer observability for AI coding agents (Claude Code, Codex, Cursor, Copilot CLI). The licence is MIT.
12 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit 31b5259. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
npmFrom the folder's file list and the shell code blocks in SKILL.md.
Hosts in commands or code, which the agent is likely to contact:
raw.githubusercontent.comFrom URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Enforce loads about 19k tokens when it runs. Until then it costs about 204 tokens; SKILL.md has 7,486 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from QuentinCody/interlinked-cli at commit 31b5259, republished under its MIT licence (© QuentinCody). 7,486 words, ~19,212 tokens.
.claude/skills/enforce/SKILL.md (or your agent's skills folder). This skill also uses 1 other file; get the full folder from GitHub./enforce # distill every imperative .md in the project
/enforce AGENTS.md # distill just one file
/enforce .claude/skills/tdd/ # distill every .md under a directory
/enforce mattpocock/skills/tdd # fetch from GitHub (review-mode by default)
/enforce list # show what's distilled, grouped by source
/enforce show <id> # full detail for one rule
/enforce remove --source <group_id> # bulk-remove rules from a source
/enforce disable <id> # keep on disk but don't enforce
/enforce modify <id> --action ask # change a single rule's action/severity
/enforce --review # distill to a review file; nothing activates yet
/enforce --accept # promote a review file to live rulesManual invocation only. This skill never auto-fires — not on SessionStart, not on a file-watcher event, not from any hook. The user (or an agent acting on the user's explicit request) types /enforce .... If you are wiring this skill into automation, stop — that violates the design.
Where output goes. Live rules: .interlinked/distilled-rules.json (pristine, regenerated each run). User mods: .interlinked/distilled-rules.overrides.json (removals, disables, modifications — survives re-distillation). The harness watches both files via watchFile and reloads automatically within ~2s — there is no interlinked harness reload command. If a daemon is in a degraded state and not picking up changes, interlinked harness restart is the only recourse.
Execution order (distill path; lifecycle verbs jump straight to §14):
distilled-rules.json (§3) → 5. Discover / resolve targets (§2) → 6. Read each file once (§3) → 7. Classify paragraphs (§4) → 8. Lexical ladder (§5) → 9. Triggers (§6) → 10. Build rule object (§7) → 11. Apply user modifications (§9) → 12. Resolve conflicts (§10) → 13. Self-checks (§12) → 14. Write output (§8) → 15. Print summary (§13)./enforce # walk the tree, distill what's there, print a report
/enforce list # confirm the rule set looks right
# harness reloads automatically within ~2s (watchFile)/enforce gh:mattpocock/skills/tdd # fetched, distilled to review-mode by default
/enforce list # inspect what would activate
/enforce --accept # promote review → live/enforce show <id> # see what fired and why
/enforce modify <id> --action ask --severity medium # downgrade
# or, to keep it on disk but stop enforcing entirely:
/enforce disable <id>/enforce remove --source gh:someone/skills/qaAdds the group to removed_groups[]; stays gone across re-distillation. Undo with /enforce add --source <group_id>.
/enforce # unchanged files skipped via hash; user mods preserved
# harness reloads automatically within ~2srm .interlinked/distilled-rules.json .interlinked/distilled-rules.overrides.json
/enforceAgent-instruction markdown files (AGENTS.md, CLAUDE.md, .clinerules/, .windsurf/rules/, GEMINI.md, etc.) are loaded into the model's context window as hopeful prose. Today the model may or may not follow them. This skill walks the source tree (or just the targets the user named), extracts every concrete imperative from those files, and distills them into typed GuardRule entries that the Interlinked harness enforces deterministically at runtime — meaning the agent literally cannot bypass them, regardless of which underlying coding agent is running.
Distillation, not strict compilation. The §5 lexical ladder + §6 trigger inference + §12 validation are deterministic — typed inputs, typed outputs, no LLM calls. But §4 paragraph classification (is this prose a hard imperative, soft preference, hedged statement, descriptive context?) requires LLM judgment because there's no formal grammar for "is this an imperative." So the operation is honest extraction-and-codification with provenance, not parser-style mechanical translation. The earlier name "compile" was a generous shorthand; "distill" reflects what's actually happening. The lexical ladder is the deterministic gate: if a paragraph contains no §5 marker, the classifier MUST drop it. The distiller never invents a rule from unmarked prose, and every rule carries a verbatim source.quote so the entire pipeline is auditable.
The harness fans rules out across every configured runner via src/harness/adapters/. Your job is to produce one canonical artifact at .interlinked/distilled-rules.json plus an overrides file at .interlinked/distilled-rules.overrides.json. The harness handles distribution.
Coupling and scope. This skill produces an artifact whose schema (GuardRule, defined at src/harness/types.ts in interlinked-cli) is Interlinked's. Enforcement at runtime requires a hook engine that understands that schema — Interlinked is the canonical one, and currently the only one expressive enough to encode regex on tool_input fields, the six action types, severity, keyword quick-reject, role scoping, and exception patterns. The skill does NOT ship multi-backend adapters (Claude Code permissions, Cursor .mdc, Codex deny configs) because every alternative is strictly less expressive — lossy translation creates user confusion ("why doesn't my rule work in X?"). However, distillation itself does not require Interlinked installed: the artifact is a standalone JSON file you can audit, version-control, share, or wire into any compatible engine. See §11 for fallback-mode behavior when .interlinked/ is missing.
/enforce <args> means)| Form | Behavior |
|---|---|
/enforce | Walk the whole project — discover all .md files per Step 1, extract from imperative-bearing ones |
/enforce AGENTS.md | Distill only that file |
/enforce AGENTS.md CLAUDE.md | Distill that exact set |
/enforce .claude/skills/tdd/ | Distill every .md under the directory |
/enforce mattpocock/skills/tdd | Treat as gh:mattpocock/skills/tdd — fetch + distill (review-mode) |
/enforce https://raw.githubusercontent.com/.../SKILL.md | Fetch + distill (review-mode) |
/enforce list | Lifecycle: print rules grouped by source — see §13 |
/enforce show <id> | Lifecycle: full detail for one rule |
/enforce remove --source <group_id> | Lifecycle: bulk-remove from one source |
/enforce remove <id> | Lifecycle: remove a single rule |
/enforce disable <id> | Lifecycle: keep but don't enforce |
/enforce enable <id> | Lifecycle: re-enable |
/enforce modify <id> --action ask --severity medium | Lifecycle: change action/severity |
/enforce add --source <group_id> | Lifecycle: undo a removed group; re-distill to add back |
/enforce reset <id> | Lifecycle: clear all overrides for this rule |
/enforce --review | Distill-then-pause: write to .interlinked/distilled-rules.review.json, no activation until accepted |
/enforce --accept | Activate review-mode output |
If the first argument is one of list, show, remove, disable, enable, modify, add, reset, treat it as a lifecycle verb (jump to §14). Otherwise treat arguments as distill targets.
/enforce .... Do not wire it into SessionStart, PreCompact, file watchers, hook events, scheduled jobs, or any other auto-trigger. Surprise enforcement — the model suddenly being unable to do something it could yesterday because a doc changed — is the failure mode this rule prevents.source.quote does not appear in source.file at source.lines is hallucination — drop it.source.quote verbatim) is what makes the distill-time step auditable rather than opaque..interlinked/distilled-rules.json and .interlinked/distilled-rules.overrides.json. The user's hand-curated rules live in guard-rules.json and guard-rules.local.json. Never touch those.Parse the invocation arguments. The first arg, if it's one of the lifecycle verbs (list, show, remove, disable, enable, modify, add, reset), routes to §14. Otherwise:
For each remaining argument, classify it:
| Form | Detection | Resolved as |
|---|---|---|
Bare name like AGENTS.md | exists as file relative to CWD | local file |
Path with / like .claude/skills/tdd/SKILL.md | exists as file | local file |
Directory path like .claude/skills/tdd/ | exists as dir | walk the dir for .md and .prompt files |
Glob like docs/**/*.md | contains * or ? | glob expand |
<owner>/<repo> or <owner>/<repo>/<subpath> | matches ^[\w.-]+/[\w.-]+(/.+)?$ and not a local path | github shorthand → fetch from https://raw.githubusercontent.com/<owner>/<repo>/HEAD/<subpath or SKILL.md> |
https://... or http://... | URL prefix | fetch directly |
--review, --accept, --source <x>, --action <x>, --severity <x> | flag | parse separately |
Resolution rules:
<subpath>/SKILL.md, then <subpath> directly, then <subpath>/AGENTS.md, in that order.github.com, raw.githubusercontent.com, gitlab.com. Refuse any other host with a clear error.--review mode (write to distilled-rules.review.json instead of distilled-rules.json); local paths can distill straight through unless --review was passed..git/ (or treat CWD itself as the root if there is no git repo). If the target's resolved absolute path doesn't have that root as a prefix, abort with a clear message: "<target> is outside the current project (<root>). cd into that project and re-run /enforce." Reasoning: the artifact + overrides files live under the target's .interlinked/, the harness running in this session only watches the current project's .interlinked/, and writing into a sibling project from this session is the kind of cross-project surprise the skill exists to prevent.Print the resolved target list before doing any work. Form: Distilling: AGENTS.md, CLAUDE.md, gh:mattpocock/skills/tdd (fetched sha256:abc…)
When no argument is given, walk these locations and record each file's absolute path, kind, and SHA-256 hash.
For each ancestor directory from CWD up to the repo root (the directory containing .git/), look for:
| Filename / pattern | Kind | Notes |
|---|---|---|
AGENTS.override.md | imperative | Highest project precedence |
*.local.md (e.g. CLAUDE.local.md) | imperative | Personal, gitignored, beats shared |
AGENTS.md | imperative | Cross-tool source of truth |
AGENT.md | imperative | Singular variant (Amp/community) |
CLAUDE.md | imperative | Claude Code |
GEMINI.md | imperative | Gemini CLI |
WARP.md | imperative | Warp legacy |
.github/copilot-instructions.md | imperative | Repo-wide Copilot |
.github/instructions/*.instructions.md | imperative | Path-scoped via frontmatter applyTo |
.cursor/rules/*.mdc | imperative | Cursor; frontmatter globs/description is machine-readable |
.cursorrules | imperative | Cursor legacy |
.clinerules | imperative | Cline single-file legacy |
.clinerules/*.md | imperative | Cline modular |
.windsurfrules | imperative | Windsurf legacy |
.windsurf/rules/*.md | imperative | Windsurf modular |
.continue/rules/*.md | imperative | Continue.dev — frontmatter globs/alwaysApply |
.augment/rules/*.md | imperative | Augment |
.tabnine/guidelines/*.md | imperative | Tabnine |
.tabnine/guidelines.md | imperative | Tabnine single-file |
.kilocoderules | imperative | Kilo Code |
.claude/skills/*/SKILL.md | scan-only | See §2c |
.agents/skills/*/SKILL.md | scan-only | Shared native skill location used by Codex and other runners; see §2c |
~/.claude/skills/*/SKILL.md | scan-only | See §2c |
CONVENTIONS.md | imperative-likely | Aider-style |
code_review.md | imperative-likely | Often referenced from AGENTS.md |
CONTRIBUTING.md | mixed (scan only) | Pull only paragraphs with hard imperatives |
SECURITY.md | mixed (scan only) | Pull only paragraphs with hard imperatives |
STYLEGUIDE.md | mixed (scan only) | Pull only paragraphs with hard imperatives |
PLANS.md | scan only | Mostly procedural |
Look in ~/.claude/, ~/.codex/, ~/.gemini/, ~/.config/copilot/, ~/.continue/, ~/.windsurf/ for the same patterns. Treat global rules as lower precedence than project rules unless they appear in *.override.md form.
SKILL.md files are mostly procedural (capability bundles). They are scan-only: extract paragraphs that hit the §4a/§4b lexical markers (MUST NOT, bans, forbids, never, MUST, always); ignore the rest. Every rule from a skill body has its group_id formed as skill:<skill-name> (extracted from the SKILL.md frontmatter name field) instead of local: or gh:.
Skill rules ARE scope-gated at runtime via active_when.skill. The harness's SessionTrajectory.active_skills map is populated by interlinked skill enter <name> (called from a slash-command preamble) and read by the active-when evaluator. A distilled skill rule with active_when.skill = "<skill-name>" is dormant unless that skill is currently active in the session. This means:
Never X distills to action: "block" per §5a, not the previous safety-downgrade to ask. The runtime scope makes the strength safe.active_when.skill populated they only collide within the same skill or with always-on rules.interlinked skill enter <skill-name> (and a matching leave on completion). Skills that don't opt in still distill correctly but their rules are always-on (active_when omitted), which is the pre-active_when behavior.See docs/design/harness-active-when-scoping.md for the full design and §7 below for distiller population rules.
The skill: group_id is also used for lifecycle ops (/enforce remove --source skill:migrate-to-shoehorn).
| File | Why skipped |
|---|---|
SOUL.md, IDENTITY.md, STYLE.md, USER.md, HERMES.md | Persona/voice. Not enforceable as hooks. |
MEMORY.md | Memory index; agent's concern, not the harness's. |
HEARTBEAT.md, BOOTSTRAP.md | Lifecycle/initialization, not gating. |
ARCHITECTURE.md, DESIGN.md, RUNBOOK.md, TESTING.md, BUILD.md, DEPLOYMENT.md, RELEASE.md, TROUBLESHOOTING.md, CONTEXT.md | Descriptive context, not imperative. |
PRD.md, SPEC.md, ROADMAP.md, TASKS.md, TODO.md | Forward-looking. |
README.md | Human-facing overview. |
TOOLS.md | Tool inventory. |
.agent.md, .prompt.md, *.prompt, .github/agents/*.agent.md, .github/prompts/*.prompt.md | Capability bundles — same treatment as SKILL.md per §2c. *.prompt covers bare-extension role/capability prompts (e.g. SwarmForge <role>.prompt). |
Any SKILL.md whose frontmatter name is enforce | Self-reference. The distiller must not distill its own imperatives — they describe how to distill, not what the agent should do. Drop the file silently regardless of which install path it lives at (skills/, .claude/skills/, .agents/skills/, .interlinked/skills/, .gemini/skills/, .github/skills/, .cursor/skills/, etc.). |
Any SKILL.md whose frontmatter name is tdd, starts with tdd-, or ends with -tdd; any path matching **/tdd/SKILL.md or **/*-tdd/SKILL.md | TDD enforcement is owned by the harness's native primitives — tdd_new_file_gate, tdd_cycle_violation, tdd_regression, tdd_commit_gate, tdd_green_confirmation — driven by the deterministic tdd_cycles state machine in SessionTrajectory. Distilling a competing TDD skill (Matt Pocock's, gstack's, anyone else's) would either over-fire (always-on rules duplicating our gates) or shadow (action downgraded to ask). Drop these files silently. If a project legitimately needs a different TDD policy, edit structural_checks.test_first_mode in guard-rules.local.json, not via /enforce. |
After discovery, print the file inventory before any extraction so the user can see the surface.
Use the Read tool with the full path. Cache contents. SHA-256 each file.
If .interlinked/distilled-rules.json already exists, compare each file's hash to the previous run's source_hashes map. Unchanged files: skip extraction; copy their previous distilled rules verbatim into the new output. Only re-extract files whose hash changed or which are new.
Subset distillation preserves other sources. When the user runs /enforce <single-target> (a path, a directory, a remote URL), the distiller MUST merge with the prior distilled-rules.json: rules from sources NOT in the current invocation are copied through unchanged, with their original source_hashes entries preserved. This is the natural extension of the unchanged-files rule: out-of-scope sources are unchanged by definition. Only /enforce (no arg, project walk) is allowed to fully regenerate. If you need a hard rebuild from a subset invocation, the user must rm .interlinked/distilled-rules.json first — the §"Throw it all away and start fresh" workflow.
For files with frontmatter, parse it and use machine-readable fields directly:
.mdc: globs / description.continue/rules/*.md: globs / alwaysApply / description.github/instructions/*.instructions.md: applyToname (becomes part of group_id), descriptionFrontmatter scope is machine-readable — do not re-extract it from the prose.
Iterate paragraph-by-paragraph (split on blank lines and heading boundaries). For each paragraph:
| Paragraph kind | Action |
|---|---|
| Hard imperative with concrete trigger | extract |
Soft preference (should, prefer, usually) | extract as advisory |
Hedged statement (we usually try to, ideally, if possible) | skip → log to skipped[] |
| Description / context | skip silently (not imperative) |
| Narrative / persona | skip silently (not enforceable) |
| Procedure / step-by-step | skip — agent guidance, not gates. Exception: a single step phrased as you MUST run tests first extracts as one rule. |
| Architecture / dependency-graph fact | skip silently |
| Forbidden tool / command list | extract per item |
| Required tool / command list | extract as block-on-inverse |
Apply mechanically. Do not adjust. If multiple markers appear in one paragraph, use the strongest.
block (severity: critical or high)Lexical markers (case-insensitive unless explicitly ALL CAPS, which strengthens):
MUST NOT, must never, never, forbidden, prohibited, not allowed, do not ever, may not, shall not, banned, outlawed, under no circumstances, at no time, bansCRITICAL:, BLOCKING:, FATAL:, DO NOT:Severity: critical if marker is CRITICAL, MUST NOT, never, forbidden, prohibited, shall not, under no circumstances. Otherwise high.
block via positive form (block on inverse trigger)Positive imperatives with concrete trigger:
must, MUST, required, is required, is mandatory, has to, shall, always, every time, before X you must YFor these, the trigger fires when the missing precondition is detected. Severity: high.
If you cannot model the precondition (no observable session state), downgrade to ask and note the gap in distilled_action_reason.
ask (severity: medium)should not, avoid, don't, prefer not to, try not to, discouragedThe ask primitive prompts the user before allowing. It collapses to deny on runners that lack confirmation (Copilot CLI, Codex). The harness handles that translation.
advisory (severity: low; surfaces only under verify --all-checks)should, prefer, usually, consider, recommend, ideally, try to, encourage, we like to, aim toDistilled as action: "warn" with enabled: true. The verify pipeline gates these per its own advisory list.
we may, we might, we sometimes, possibly, feel free to, if you want, optionally, maybeblock and ask are decisions made before the tool call runs — they require trigger: "PreToolUse". The harness has no post-rule evaluation: a PostToolUse rule cannot block, undo, or ask permission for a write that already happened. So:
| Action | Allowed triggers |
|---|---|
block, ask, soft_block, rewrite | PreToolUse only |
warn | PreToolUse or PostToolUse |
When the imperative's signal is only observable post-write (e.g., scanning produced file content for a pattern), the rule MUST distill as warn, not ask. Use PostToolUse + warn for advisories about content that just landed; reserve ask for PreToolUse checks where the agent can still back out. The trigger cookbook below routes Don't commit secret Z to source through PostToolUse + content regex precisely because that's the one place ask-vs-warn doesn't matter — content scanning IS warning, not blocking.
For each imperative, produce the trigger fields. If you cannot, downgrade to advisory — never emit a block or ask rule with no observable trigger.
The harness's GuardRule shape (from src/harness/types.ts):
interface GuardRule {
id: string;
enabled: boolean;
trigger: "PreToolUse" | "PostToolUse" | "both";
tool_match: string[]; // tool names; "*" for all
action: "block" | "warn" | "rewrite" | "soft_block" | "ask";
patterns: RulePattern[]; // see semantics below
reason: string; // shown to the agent
suggestion?: string;
severity: "critical" | "high" | "medium" | "low";
category?: string;
applies_to_roles?: AgentRole[];
keywords?: string[]; // PreToolUse quick-reject tokens
}
interface RulePattern {
field: string; // dot-path into tool_input
regex: string;
flags?: string; // default "i"
negate?: boolean; // exception when true
}Distilled rules ALSO carry a source sidecar field — see §7. The harness ignores unknown fields; the CLI lifecycle ops use them.
The runtime evaluator splits patterns[] by negate:
negate absent or false) — OR. ANY one positive pattern matching makes the rule fire. With zero positive patterns, the OR is vacuously true: the rule fires whenever its tool_match matches.negate: true) — exceptions. If ANY negated pattern matches, the rule is suppressed for that call.The combined contract: a rule fires when (at least one positive matches OR there are no positive patterns) AND no negated pattern matches.
Practical consequence: you cannot AND two positive patterns together. If your imperative needs both "this file path" AND "this content", encode it as one positive pattern (a regex that captures both signals on one field) plus optional negated exceptions. Don't write the rule as two positive patterns and assume they intersect — that gives you a strict OR. The trigger cookbook below picks the smallest field that captures intent; when in doubt, single-pattern rules are easier to reason about than multi-pattern ones.
| Imperative shape | Distilled fields |
|---|---|
| "Never run X" / "Don't use X" | trigger: "PreToolUse", tool_match: ["Bash"], patterns: [{ field: "command", regex: "<X>" }], keywords: ["<token>"] |
| "Don't edit files in path/" | trigger: "PreToolUse", tool_match: ["Edit","Write","MultiEdit","apply_patch"], patterns: [{ field: "file_path", regex: "<glob-as-regex>" }] |
| "Always do X before Y" | trigger fires on Y; harness session state required (see ‡) |
| "Use X instead of Y" | tool_match: ["Bash"], patterns: [{ field: "command", regex: "<Y>" }], suggestion: "use X" |
| "Don't commit secret Z to source" | trigger: "PostToolUse", tool_match: ["Edit","Write","MultiEdit","apply_patch"], patterns: [{ field: "content", regex: "<Z>" }] |
| MCP tool prohibition | trigger: "PreToolUse", tool_match: ["<exact-mcp-tool-name>"], patterns: [] (exact tool_match alone fires the rule via the vacuous-OR; field: "*" + .* would fail self-check #3) |
| Tool-class prohibition | tool_match: ["Bash"], keywords: [<token>], patterns: [{ field: "command", regex: "<pattern>" }] |
‡ Sequential preconditions ("Always X before Y") are not directly representable in GuardRule. Emit a Pass 2 policy.md entry (see §15.2) where trigger_signal describes the precondition in natural language; the Tier 2 LLM gate evaluates it from trajectory. Do NOT downgrade to ask in distilled-rules.json — ask is removed per §15.1 (PreToolUse user-prompts created fatigue; the Tier 2 gate handles the same job without interrupting).
\b word boundaries — never bare git (matches gitlab, git-credential).^git\s+push\b.flags: "i" on patterns matching DROP\s+TABLE.new RegExp(p).test("")).(.*)*, (.+)+, nested unbounded quantifiers.tool_match: ["Edit", "Write", "MultiEdit", "apply_patch"] (apply_patch is Codex CLI's edit tool — include it for cross-runner coverage).db/migrations/** becomes ^db/migrations/.* (anchored at field start).Most real AGENTS.md content is sequential ("always run tests before commit," "always read before edit") or scoped ("during /ship, never X," "while migrate-to-shoehorn is active, …"). The distiller has two mechanisms for these:
(A) Typed active_when axes (preferred when applicable). Recognized prose patterns map directly to a typed scope axis on the distilled rule. Wired into the harness; deterministic at runtime; composable.
(B) session_predicate escape hatch. For prose patterns that don't match a typed axis, emit a predicate entry inside active_when with the predicate name and args. Predicate-using rules must still set action: "ask" so they degrade to a no-op on harness builds that don't recognize the predicate name (the harness fails-safe — unknown predicates keep the rule dormant, never fire).
| Prose phrase | Distilled as | Reads from | Notes |
|---|---|---|---|
"during /<skill>", "while <skill> is active", source is a SKILL.md body | active_when.skill = "<skill-name>" (typed axis) | SessionTrajectory.active_skills | Default for every rule extracted from a <name>/SKILL.md body. Skill-author opt-in via interlinked skill enter preamble. |
| "after running /ship", "after the user invoked X" | active_when.after_command = { pattern, window_steps } (typed axis) | SessionTrajectory.commands_run (last N) | Default window 10. Pattern is a regex matched against recent command entries. |
| "only when editing files matching X" | active_when.file_scope = "<regex>" (typed axis) | event.tool_input.file_path | AND-ed with rule.patterns; an extra path filter beyond tool_match. |
| "only on Codex" / "only on Claude" / model-overlay file home | active_when.agent_source = "<runner>" or active_when.overlay = "<runner>" (typed axes) | event.agent_source | Use overlay when the source is a model-overlays/<runner>.md file; use agent_source when prose explicitly names the runner. Both resolve identically in v1. |
"always run tests before <X>" | predicate: { name: "tests_passed_recently", args: { window_steps: N } } | test_runs.last_pass.at_step vs tool_call_count | N defaults to 5. Action MUST be "ask". |
| "after running tests" | predicate: { name: "tests_run_in_session" } | test_runs.size > 0 | Action MUST be "ask". |
<X>" | predicate: { name: "tdd_state", args: { value: "red" } } | tdd_cycles[file].state | Harness-internal only — not emitted by the distiller. TDD enforcement is owned by the harness's native primitives (see §2d skip-list entry for TDD skills). The tdd_cycles state machine exists in the runtime, but distilled rules MUST NOT reference it — drop any imperative that would. If you need a TDD policy change, edit structural_checks.test_first_mode, not /enforce. |
| "before pushing" | predicate: { name: "last_command_was", args: { pattern: "^git\\s+push\\b" } } | commands_run (last entry) | Negate-form: trigger when X happens AND last_command was NOT a push. Action MUST be "ask". |
| "before editing, read the file" | predicate: { name: "file_read_in_session" } | files_read set | Trigger Edit when target file isn't in the set. Action MUST be "ask". |
| "after seeing N consecutive failures" | predicate: { name: "consecutive_failures", args: { tool, n } } | consecutive_tool_failures | Self-throttling rules. Action MUST be "ask". |
| "during cleanup" / "after compaction" | predicate: { name: "last_event", args: { name: "PreCompact" } } | tool_sequence | Phase-scoped. Action MUST be "ask". |
If a predicate isn't in this table, the imperative's "always X before Y" form must route to a Pass 2 policy.md entry per §6 ‡ + §15.1 — and the imperative's "no observable session-state primitive" gap should be recorded in the Pass 2 entry's rationale so future-you can spot which predicates would benefit from being added to the table (so the LLM-side judgment can be replaced with a deterministic predicate later).
Schema addition (§7) when a predicate is used:
"session_predicate": {
"name": "tests_passed_recently",
"args": { "window_steps": 5 }
}The harness evaluator must short-circuit allow when the predicate is satisfied (the precondition holds) and fall through to the rule's action when it's not. Until the harness side wires this up — track via the implementation tracker — distilled rules carrying session_predicate MUST also carry action: "ask" and the predicate description in reason, so they degrade gracefully on harness builds that don't understand the field.
The distilled rule object is a real GuardRule with a source sidecar field added. The harness ignores source; the CLI uses it for lifecycle operations.
{
"id": "enforce-<group-stem>-<short-kebab-summary>",
"enabled": true,
"trigger": "PreToolUse",
"tool_match": ["Bash"],
"action": "block",
"patterns": [
{ "field": "command", "regex": "^git\\s+push\\b.*\\bmain\\b" }
],
"reason": "BLOCKED by AGENTS.md:42 — \"Never push to main without code review.\"",
"suggestion": "Open a PR and request review, then merge through the standard flow.",
"severity": "critical",
"category": "distilled-from-md",
"keywords": ["git"],
"source": {
"group_id": "local:AGENTS.md",
"group_label": "AGENTS.md",
"file": "AGENTS.md",
"lines": [42, 42],
"quote": "Never push to main without code review.",
"lexical_marker": "Never",
"marker_class": "block-direct"
},
"distilled_action_reason": "lexical 'Never' → action=block per §5a",
"confidence": 0.95
}ID slug rule: enforce-<group-stem>-<short-kebab-summary>. Group-stem is derived from group_id (drop scheme prefix, replace //./: with -, lowercase). Summary is ≤6 words from the imperative's intent, kebab-case.
group_id schemes:
| Scheme | Format | Example |
|---|---|---|
local: | local:<path> — repo-relative when a git root exists, otherwise CWD-relative | local:AGENTS.md, local:.clinerules/style.md |
home: | home:<home-relative-path> | home:.claude/CLAUDE.md |
gh: | gh:<owner>/<repo>/<subpath> | gh:mattpocock/skills/tdd |
url: | url:<host><path> | url:example.com/foo.md |
skill: | skill:<skill-name> (from SKILL.md frontmatter) | skill:tdd, skill:grill-me |
Skill-sourced rules ALWAYS use the skill: scheme; they carry their physical install path in source.file but are grouped by skill-name so cross-install moves don't fragment the group.
confidence: 0.95 for clean direct-prohibition. 0.85 for positive-form. 0.7 for cases where the trigger required interpretation. Below 0.7 → downgrade to advisory.
active_when population (binding): every distilled rule with a known scope MUST carry an active_when field per the table below. Rules from project-wide imperative sources (root CLAUDE.md, AGENTS.md, etc.) omit active_when (always-on). Rules from skill bodies, model-overlay files, or path-scoped sources populate it deterministically. Population is purely structural — no LLM judgment.
| Source location | active_when populated as |
|---|---|
<path>/AGENTS.md, <path>/CLAUDE.md, <path>/AGENTS.override.md, *.local.md, .clinerules/, .cursor/rules/, .continue/rules/, .windsurf/rules/, .augment/rules/, .tabnine/, .kilocoderules | omitted (always-on) — these are the project-wide gates |
<skill-name>/SKILL.md body (any install path) | { skill: "<skill-name>" } — skill-name from frontmatter |
model-overlays/<runner>.md (or any sibling persona-by-runner directory) | { overlay: "<runner>" } |
.github/instructions/<file>.instructions.md with frontmatter applyTo: "<glob>" | { file_scope: "<glob-as-regex>" } |
.cursor/rules/*.mdc with frontmatter globs: [...] | { file_scope: "<joined-globs-as-regex>" } |
.continue/rules/*.md with frontmatter alwaysApply: false + globs: [...] | { file_scope: "<joined-globs-as-regex>" } |
Skill body imperative referencing "after running /<X>" or "after the user invoked X" | merge { after_command: { pattern: "^/<X>\\b", window_steps: 20 } } into the existing scope |
| Skill body imperative referencing TDD state | not emitted — TDD is owned by harness primitives; rule is dropped per §2d skip list |
When multiple axes are inferred from one source (e.g., a skill body that also mentions "after /ship"), all axes are AND-ed in active_when. The distiller emits the union; confidence drops by 0.05 per inferred axis beyond the first.
applies_to_roles population (binding). A source file is role-scoped when its basename is <role>.prompt / <role>.agent.md, or its body opens with You are the <role>.. The harness AgentRole type (src/harness/types.ts) is exactly "lead" | "worker" | "subagent" | "unknown":
<role> is one of those four, set applies_to_roles: ["<role>"] on every rule distilled from that file.<role> is NOT — e.g. a bespoke multi-agent cohort such as SwarmForge's architect / coder / refactorer / reviewer / specifier — the harness has no axis to scope the rule, so a Pass 1 rule from one role's prompt applies to every agent. That is a correctness bug, not noise: refactorer.prompt's "Do not run mutation tests" emitted globally directly contradicts reviewer.prompt's "Run … mutation tests." Such imperatives MUST NOT be written to distilled-rules.json. Route them to Pass 2 (policy.md) with the role named in trigger_signal ("…when the acting agent's role is refactorer"); the Tier-2 gate resolves role from trajectory. If Pass 2 is unavailable (local-only mode), log to skipped.report.md with skip class skip-role-unmodellable. §10 conflict resolution does NOT catch this — only the prohibition carries a lexical marker, so the contradicting permission never distills and no conflict pair forms.Schema attribution. The shape above is the Interlinked harness's GuardRule, defined at src/harness/types.ts in the interlinked-cli package. The four sidecar fields (source, distilled_action_reason, confidence, user_modified) are the only additions this skill makes; the harness ignores them at evaluation. The active_when field is a real GuardRule field (added 2026-04 alongside the active-when scoping work) — the harness reads and evaluates it at runtime via evaluator/active-when.ts. Other hook engines that want to consume the same artifact must implement matching evaluation semantics — regex on tool_input fields, the six action types (block / warn / rewrite / soft_block / ask / advisory), severity, keyword quick-reject, role scoping, exception patterns, AND active_when scope evaluation. The skill targets GuardRule because it's the most expressive open enforcement primitive today; downgrading to a less-expressive runtime (Claude Code's permissions.deny[], Cursor .mdc globs, Codex deny configs) would lose the regex/PostToolUse/severity/active_when dimensions, so this skill does not ship multi-backend adapters. If you want to use the artifact outside Interlinked, write your own evaluator against this schema.
Write .interlinked/distilled-rules.json:
{
"version": 1,
"distilled_at": "2026-04-27T18:30:00Z",
"distiller": "skill:enforce@1",
"source_hashes": {
"AGENTS.md": "sha256:abc123…",
"CLAUDE.md": "sha256:def456…",
".clinerules/style.md": "sha256:789abc…"
},
"rules": [ /* distilled rule objects per §7 */ ],
"skipped": [
{
"file": "CLAUDE.md",
"lines": [78, 80],
"quote": "We usually try to write tests before code.",
"reason": "hedged: 'usually try to'",
"marker_class": "skip-hedged"
}
],
"conflicts": [
{
"id": "enforce-agents-no-force-push",
"winning_source": "AGENTS.override.md:12",
"overridden_sources": ["AGENTS.md:42"],
"resolution": "AGENTS.override.md > AGENTS.md per precedence stack"
}
],
"removed": [
{
"id": "enforce-claude-no-cypress",
"reason": "source quote no longer present in CLAUDE.md as of 2026-04-27"
}
],
"stats": {
"files_scanned": 9,
"imperatives_found": 28,
"distilled_block": 12,
"distilled_ask": 4,
"distilled_advisory": 7,
"skipped_hedged": 5,
"conflicts": 1,
"removed": 0
}
}The harness's rules-loader.ts reads this file alongside guard-rules.json and guard-rules.local.json and applies the overrides file (next section). The harness's watchRulesFiles() polls both distilled paths every ~2s, so changes are picked up automatically — no manual reload command exists. If the daemon appears stuck, confirm with interlinked harness status before guessing: it round-trips the socket and reports ZOMBIE when a live PID is answering nothing (the state that looks exactly like "rules stopped reloading"). .interlinked/daemon-events.jsonl records why the last daemon left — a pre-listen startup failure exits 78 with reason: "startup-failed" — and .interlinked/logs/daemon.log holds any crash output. Either way the fix is interlinked harness restart.
For --review mode: write to .interlinked/distilled-rules.review.json instead. The harness does not load .review.json; the user must run /enforce --accept to promote it.
.interlinked/distilled-rules.overrides.json survives re-distillation:
{
"version": 1,
"removed_groups": ["gh:mattpocock/skills/qa"],
"removed_rule_ids": ["enforce-claude-no-cypress"],
"disabled_rule_ids": ["enforce-agents-prefer-named-exports"],
"modifications": {
"enforce-skill-tdd-no-bulk-tests": {
"action": "ask",
"severity": "medium",
"note": "downgraded — too noisy on legacy tests/ dir"
}
}
}The harness applies these on every load. Removed groups stay removed across re-distillation (so deleted things don't whack-a-mole back). Modifications layer on top of the pristine distilled rule.
On distill, the skill MUST read this file before extracting and:
group_id is in removed_groups[]. Never even fetch a removed group.id is in removed_rule_ids[]. The pristine rule still appears in the output but with enabled: false and a note.modifications{} after distillation and mark those rules with user_modified: true for the report.Precedence stack (highest to lowest):
AGENTS.override.md*.local.md (CLAUDE.local.md, etc.).claude/rules/, .continue/rules/, .windsurf/rules/, .augment/rules/, .clinerules/, .cursor/rules/)AGENTS.md / CLAUDE.md / GEMINI.md / WARP.md.github/instructions/*.instructions.md)skill:<name>; no runtime scope — collisions are real but resolved by precedence here)CONTRIBUTING.md / SECURITY.md / STYLEGUIDE.md / CONVENTIONS.md~/ global counterparts of any of the aboveWhen two distilled rules collide on (trigger, tool_match, patterns[].regex):
block > ask > soft_block > warn).winning_source; record the lower-precedence ones in overridden_sources[].conflicts[]..interlinked/ directory check.Preferred path (Interlinked installed): .interlinked/ exists. Output goes to .interlinked/distilled-rules.json; the harness watches the file and picks up changes within ~2s automatically.
Fallback path (Interlinked not installed): .interlinked/ is missing. Warn — do not abort. Write the artifact to ./distilled-rules.json next to the user's CWD (the same location for the overrides file: ./distilled-rules.overrides.json). Print a clear message:
No
.interlinked/directory found. Distilled rules written to./distilled-rules.json. To enforce them, install Interlinked (npm i -g interlinked-cli && interlinked enable) — Interlinked's harness reads exactly this file from.interlinked/. Or wire the JSON into your own hook engine using theGuardRuleschema documented in §7. Distillation does NOT require Interlinked; enforcement does.
This separates producing the artifact (this skill's scope, no runtime dependency) from enforcing it (Interlinked's scope, runtime dependency). Users without the harness still get a tangible, version-controllable, auditable file they can share or migrate later.
local:<path> relative-path computation..interlinked/config.json if present; warn if version < 2 (distilled rules may not load on older harness builds)..interlinked/guard-rules.json or .interlinked/guard-rules.local.json..md file. Read-only..interlinked/distilled-rules.json (or .review.json in review mode) and .interlinked/distilled-rules.overrides.json. In fallback mode, the same names land at ./distilled-rules.json and ./distilled-rules.overrides.json at the user's CWD.Run all of these. Abort the write if any fail.
source.quote that, when grepped against the source file content (case-sensitive, whitespace-normalized), returns at least one match.block or ask rule has at least one of: a regex on command, a regex on file_path, a regex on content, or an exact tool_match.new RegExp(pattern).test("").stats match the actual array lengths.id is unique within the file.id matches ^enforce-[a-z0-9-]+$.tool_match array is non-empty (["*"] is OK as catch-all).If any check fails, abort and report the specific failure with the offending rule ID.
End every run with this tabular summary:
Distilled-rules report
Sources scanned 9 files
imperative-bearing 6
scan-only 2
persona/skipped 1
Imperatives found 28
Distilled to:
block 12 (criticals from MUST NOT / never / forbidden / shall not)
ask 4 (should not / avoid)
advisory 7 (should / prefer / consider)
Skipped:
hedged 5 (we usually try / ideally)
no trigger 0
out-of-scope 0
Conflicts 1 (AGENTS.override.md beat AGENTS.md on git-push-main)
Removed 0
User-disabled 0
User-modified 1
Output: .interlinked/distilled-rules.json
Run `/enforce list` to inspect. The harness picks up the new rules within ~2s automatically.
Top distilled rules:
✗ enforce-agents-override-no-push-this-week AGENTS.override.md:12 block
✗ enforce-claude-no-prod-deletes CLAUDE.md:88 block
✗ enforce-clinerules-no-cypress .clinerules/style.md:5 block
⚠ enforce-claude-prefer-named-exports CLAUDE.md:152 advisory
…For --review mode: replace "Output:" with "Review-mode output:" and add: "Run /enforce --accept to activate, or edit .interlinked/distilled-rules.review.json first."
/enforce <verb>)When the first argument is a lifecycle verb, run that operation instead of distilling.
/enforce listRead .interlinked/distilled-rules.json + .interlinked/distilled-rules.overrides.json. Print:
Source Rules Block Ask Advisory Disabled
──────────────────────────────────────────────── ──── ───── ──── ─────── ────────
local:AGENTS.md 8 5 1 2 0
local:AGENTS.override.md 2 2 0 0 0
local:CLAUDE.md 5 2 1 2 1
local:.clinerules/style.md 3 2 0 1 0
gh:mattpocock/skills/tdd 2 2 0 0 0
skill:tdd 2 2 0 0 0
──────────────────────────────────────────────── ──── ───── ──── ─────── ────────
Total 22 14 2 6 1
Removed groups: gh:mattpocock/skills/qa (3 rules suppressed)
Run `/enforce list <group_id>` to drill in.
Run `/enforce remove --source <group_id>` to bulk-remove from a source./enforce list <group_id>Drill into a single source:
Source: gh:mattpocock/skills/tdd
✗ enforce-skill-tdd-no-bulk-tests block high
Source: gh:mattpocock/skills/tdd:23-25
Quote : "explicitly bans the horizontal anti-pattern"
Rule : trigger=PreToolUse, tool_match=[Edit,Write], patterns=[command~/^...$/]
Lexical: bans → block-direct
✗ enforce-skill-tdd-test-before-source block high
Source: gh:mattpocock/skills/tdd:30-31
…/enforce show <id>Print one rule's full JSON, plus its source-file context (3 lines before, the quote, 3 lines after).
/enforce remove --source <group_id>Read overrides; add <group_id> to removed_groups[]. Save. Print: Removed group <group_id> (N rules suppressed). Run /enforce add --source <group_id> to undo.
/enforce remove <id>Read overrides; add <id> to removed_rule_ids[]. Save. Print confirmation.
/enforce disable <id>Read overrides; add <id> to disabled_rule_ids[]. Save. Rule stays in distilled-rules.json but loads with enabled: false.
/enforce enable <id>Read overrides; remove <id> from disabled_rule_ids[]. Save.
/enforce modify <id> --action ask --severity mediumRead overrides; set modifications[<id>] = { action: "ask", severity: "medium", note: <user-provided or auto> }. Save.
Allowed flags: --action <block|warn|ask|soft_block|rewrite>, --severity <critical|high|medium|low>, --note <text>.
/enforce add --source <group_id>Read overrides; remove <group_id> from removed_groups[]. Save. Print: Group restored. Run /enforce <group_id> to re-distill and pull rules from it.
/enforce reset <id>Read overrides; clear all entries for <id>: remove from removed_rule_ids, disabled_rule_ids, and modifications{}. Save.
/enforce --reviewDistill-with-pause: write to .interlinked/distilled-rules.review.json instead of distilled-rules.json. The harness does not load .review.json. Default mode for remote sources.
/enforce --acceptPromote distilled-rules.review.json → distilled-rules.json. Validate first; abort if validation fails.
Added 2026-05. The single-pass model (every imperative → one GuardRule in
distilled-rules.json) was too lossy. Imperatives whose triggers couldn't
be expressed as tool_input regex were either downgraded to ask (noisy)
or skipped (lost). Skills that are mostly methodology (Matt Pocock's
improve-codebase-architecture, most audit-style cybersecurity skills, etc.)
yielded ~5-15% coverage at best.
/enforce now runs three passes over the same source markdown and emits
artifacts for the three enforcement tiers of the Interlinked architecture:
| Tier | Layer | Consumer | Artifact | Cadence |
|---|---|---|---|---|
| 1 | Local deterministic | Interlinked harness (~sub-10ms) | distilled-rules.json | Every tool call |
| 2 | Cloud LLM policy gate | gpt-oss-safeguard-120b (~3-6s) | policies/<group_id>.policy.md + Cedar companions | Most tool calls (post-filter) |
| 3 | Cloud architectural review | Sonnet/Opus on staged commits (~30-120s) | policies/<group_id>.prose.md | Pre-push / on-demand /review / /security-review |
| — | Audit | Humans tightening source markdown | policies/skipped.report.md | After /enforce runs |
For each imperative extracted under §4-§6, the distiller routes it to ONE or MORE of the four artifact families based on trigger shape and FP risk:
| Imperative shape | Pass 1 (deterministic) | Pass 2 (LLM gate) | Pass 3 (prose) | Skipped |
|---|---|---|---|---|
| Concrete regex-shape trigger, low FP | ✓ block/warn | (mirror, for trajectory context) | — | — |
| Concrete trigger + skill-scoped | ✓ block/warn | (mirror) | — | — |
| Concrete trigger + sequential precondition | — | ✓ warn (LLM evaluates trajectory) | — | — |
| Abstract / intent / semantic trigger | — | ✓ block/warn | — | — |
| Quality-of-output rule (vocabulary, required fields) | (optional content regex, FP-prone) | ✓ warn | rationale → prose | — |
| Definition / methodology / principle | — | — | ✓ | — |
| §5e SKIP (hedged, no signal, no imperative) | — | — | — | ✓ |
A single source imperative can route to multiple passes. Example: the
disk-forensics "Never modify source evidence" imperative routes to Pass 1
(file_path regex on *.E01/etc.) AND Pass 2 (LLM-side mirror for trajectory
context AND the residual intent rule "Never suggest evidence tampering").
Critical: §6 ‡ ask-downgrade is removed. Previously, sequential
preconditions ("Always X before Y") downgraded to action: "ask" because the
harness couldn't model the precondition. With Tier 2, the LLM evaluates the
precondition from trajectory. Emit a Pass 2 entry instead — no ask.
<group_id>.policy.md)Markdown with structured per-imperative entries. The Tier 2 LLM loads this
file as system-prompt context when its active_when scope matches.
# Policy: <group_id>
**Source:** <source file path>
**Active when:** <scope condition>
**Generated by:** `/enforce` Pass 2, <iso date>
## P<n> — <short title>
- **id**: `enforce-<group-stem>-<short-kebab-summary>`
- **severity**: critical | high | medium | low
- **active_when**: <scope axes>
- **action_on_violation**: block | warn
- **trigger_signal**: <natural-language description of what the LLM
should look for in the current tool call + recent trajectory>
- **source**: <file>:<line range> — "<verbatim quote>"
- **lexical_marker**: <which §5 marker fired>
- **rationale**: <one line — why this lives at Pass 2, not Pass 1>
- **deterministic_companion**: <rule id in distilled-rules.json, or "none">
- **cedar_companion**: <cedar file + clause id, or "none">
- **suggested_remediation**: "<what the agent should do instead>"Per-entry id format matches GuardRule ids: enforce-<group-stem>-<summary>.
Action vocabulary is block or warn only — ask is removed (15.1).
PostToolUse warns and PreToolUse blocks are the only two states.
Two flavors of Cedar emitted per group, both derived from the policy entries
in <group>.policy.md:
<group>.cedar — Sondera-compatible (default; drops into Sondera's
policies/ directory without modification). Uses only fields in Sondera's
base.cedarschema:
ShellCommand, ShellCommandOutput, FileRead/Write/Edit/Delete,
WebFetch, WebFetchOutput, Prompt, ToolOutputcommand, path, operation, url, signature.*,
policy.*, label, workspace.*Trajectory.step_count, Trajectory.label,
Trajectory.taints, File.label<group>.interlinked.cedar — Interlinked-extended (only emitted when at
least one policy entry needs extensions). Adds:
context.skill_active: String — current skill scopeTrajectory.commands_run: Set<String> — accumulated shell commandsTrajectory.files_read: Set<String> — accumulated file reads@action_on_violation("warn"|"block") annotation (extends Cedar's
binary permit/forbid)@requires_extension("<feature>") annotation (engines without the
extension must reject the file)See docs/design/interlinked-cedar-extensions.cedarschema for the full
schema delta.
Regex → like translation. Cedar like is glob-only, not regex. The
distiller best-effort expands:
^pattern$ → pattern (Cedar like is implicitly anchored if no *)\bword\b → * word * and word * and * word (drops boundary precision)like exclusions (loses some precision)[ab] → *a* || *b*)+ / ? quantifiers → drop (covered by *)When translation is lossy, emit the policy with @confidence("approximate")
and rely on distilled-rules.json's actual-regex GuardRule as the precise
floor. The Cedar version is for portability + Sondera compatibility.
When translation is impossible (e.g., trajectory state references on Sondera
side), emit a comment block in the .cedar file explaining why and pointing
to the .interlinked.cedar or .policy.md companion.
<group_id>.prose.md)Pass 3 captures imperatives that aren't gates at all — definitions, methodologies, principles — but that should inform the after-the-fact review of staged commits by the Tier 3 cloud agent.
Tier 3 has wider scope than Tier 2: it sees the full repo, the staged
commits, the agent's session log, and the prose artifacts for every skill
that was active during the session. It evaluates the staged work against
the principles in prose.md using full context.
# Prose policies: <group_id>
This file is consumed by the Tier 3 cloud review agent (pre-push, /review,
/security-review). The agent evaluates staged commits against these
principles using full repo context — NOT single-tool-call trajectory.
## Definitions (canonical vocabulary)
<verbatim definitions from the source markdown, e.g. LANGUAGE.md "Terms"
section. Used so the Tier 3 agent uses consistent vocabulary in its review.>
## Principles
### <principle name>
<verbatim principle text from source>
**Tier 3 evaluation:** <natural-language guidance for the cloud agent on
how to evaluate the staged work against this principle. E.g., "For each
new or significantly-modified module in the staged commits, apply the
deletion test. Flag any module where deletion would not concentrate
complexity.">
## Methodology checkpoints
<numbered list of per-commit / per-session questions the Tier 3 agent asks.
E.g., "Did the agent follow the Explore → Present → Grilling flow? Look
for evidence of subagent_type=Explore invocations in the session log.">
## Source
<full verbatim source markdown for traceability>Pass 3 entries are not enforced at edit time. They're context for the cloud reviewer. The distiller never blocks or warns on Pass 3 content.
skipped.report.md)Anything that didn't route to any of Passes 1-3. One file shared across
all groups distilled in a run. Format per existing §8 skipped[] array,
serialized as Markdown:
# Skipped imperatives — <iso date>
## <group_id>
### L<n> — "<quote>"
- **Lexical marker:** <marker or "none">
- **Skip class:** `skip-hedged` | `skip-no-trigger` | `skip-abstract-trigger`
| `skip-no-imperative` | `skip-out-of-scope` | `skip-role-unmodellable`
- **Skip reason:** <one line>
- **Could be enforced if:** <suggested rewrite of source for the user>Use this report to tighten source markdown — adding a concrete trigger or stronger lexical marker would move an entry into one of the enforced passes.
For users who never touch our cloud or self-host Sondera:
| Artifact | Local-only role |
|---|---|
distilled-rules.json | Tier 1 floor — works as today, deterministic enforcement |
policy.md | Loaded into agent context (advisory, not enforced) |
.cedar (Sondera-compatible) | Drop into self-hosted Sondera installation |
.interlinked.cedar | Inactive unless the Interlinked harness adds Cedar evaluation in v1.1+ |
prose.md | Loaded into agent context (definitions, principles) |
skipped.report.md | Human audit, identical to cloud mode |
The deterministic floor + advisory loading covers ~30-40% of what cloud adds. Cloud unlocks the LLM-evaluable trajectory rules (Pass 2) and the wide-scope after-the-fact review (Pass 3).
§6 ‡ previously read: "Sequential preconditions (Always X before Y) are
not directly representable in GuardRule. Distill to a single PreToolUse
rule on Y with severity: "medium" and action: "ask"."
This is removed. New rule:
policy.md entry, not a
Pass 1 ask rule.trigger_signal describes the precondition in
natural language; the Tier 2 LLM evaluates from trajectory.ask
was, but the FP cost of ask (user fatigue) outweighed the benefit.The trigger cookbook in §6 stays. Just the ‡ footnote is rewritten:
‡ Sequential preconditions ("Always X before Y") are not directly representable in
GuardRule. Emit a Pass 2policy.mdentry wheretrigger_signaldescribes the precondition. The Tier 2 LLM gate evaluates from trajectory.
The §13 final-summary table grows by one section to reflect three-pass routing:
Distilled to:
Pass 1 — deterministic 12 (block/warn in distilled-rules.json)
Pass 2 — LLM policy gate 8 (block/warn in <group>.policy.md)
├── Sondera-compat .cedar 5 (translates to vanilla Cedar)
└── Interlinked .cedar 3 (requires schema extensions)
Pass 3 — prose for review 4 (definitions / principles for Tier 3)
Skipped 5 (hedged / no trigger / not imperative)The existing distilled-rules.json v1 schema (§8) gains an optional
companions sidecar on each rule, naming its Pass 2 / Pass 3 siblings:
"companions": {
"policy_md": ".interlinked/policies/<group_id>.policy.md#<entry_id>",
"cedar": ".interlinked/policies/<group_id>.cedar#<clause_id>",
"interlinked_cedar": ".interlinked/policies/<group_id>.interlinked.cedar#<clause_id>",
"prose_md": ".interlinked/policies/<group_id>.prose.md#<section_anchor>"
}And new top-level keys:
"policies_emitted": [".interlinked/policies/<group_id>.policy.md", ...],
"prose_emitted": [".interlinked/policies/<group_id>.prose.md", ...],Schema version stays at 1 — additions are backward-compatible (existing consumers ignore unknown fields).
When emitting Pass 2 or Pass 3 artifacts, ensure .interlinked/policies/
exists. Create it on first emission. Never overwrite a .local.cedar
file the user has hand-authored — these are the user's extension layer
and live alongside generated .cedar files.
In fallback mode (no .interlinked/ directory), Pass 2/3 artifacts land
at ./policies/<group_id>.{policy.md,cedar,interlinked.cedar,prose.md} at
the user's CWD. Same fallback message as §11 for distilled-rules.json.
See docs/examples/policies/disk-forensics/ for a complete worked
example: one source SKILL.md, three deterministic rules, five policy
entries (3 mirror + 2 LLM-only), 2 Cedar files (Sondera + Interlinked),
and a skipped report. The example also demonstrates the Pass 3 prose
output for the skill's "Evidence Handling Principles" methodology
section.
Source (AGENTS.md:42): "Never push to main without code review."
{
"id": "enforce-local-agents-md-no-push-main",
"enabled": true,
"trigger": "PreToolUse",
"tool_match": ["Bash"],
"action": "block",
"patterns": [{ "field": "command", "regex": "^git\\s+push\\b.*\\bmain\\b" }],
"reason": "BLOCKED by AGENTS.md:42 — \"Never push to main without code review.\"",
"severity": "critical",
"category": "distilled-from-md",
"keywords": ["git"],
"source": {
"group_id": "local:AGENTS.md",
"group_label": "AGENTS.md",
"file": "AGENTS.md",
"lines": [42, 42],
"quote": "Never push to main without code review.",
"lexical_marker": "Never",
"marker_class": "block-direct"
}
}Source (CLAUDE.md:88): "Always run npm test before committing."
{
"id": "enforce-local-claude-md-test-before-commit",
"enabled": true,
"trigger": "PreToolUse",
"tool_match": ["Bash"],
"action": "ask",
"patterns": [{ "field": "command", "regex": "^git\\s+commit\\b" }],
"reason": "CLAUDE.md:88 — \"Always run `npm test` before committing.\" The harness can't verify a recent test run; please confirm.",
"severity": "medium",
"category": "distilled-from-md",
"keywords": ["git"],
"source": { ... "lexical_marker": "Always", "marker_class": "block-positive" },
"distilled_action_reason": "positive imperative, no session-state primitive → downgraded block→ask"
}Source (.clinerules/style.md:5): "MUST NOT edit files under db/migrations/ directly."
{
"id": "enforce-local-clinerules-style-md-no-direct-migrations",
"enabled": true,
"trigger": "PreToolUse",
"tool_match": ["Edit", "Write", "MultiEdit", "apply_patch"],
"action": "block",
"patterns": [{ "field": "file_path", "regex": "(^|/)db/migrations/" }],
"reason": "BLOCKED by .clinerules/style.md:5 — \"MUST NOT edit files under db/migrations/ directly.\"",
"suggestion": "Use `npm run migrate:create` instead.",
"severity": "critical",
"category": "distilled-from-md",
"source": { ... "lexical_marker": "MUST NOT", "marker_class": "block-direct" }
}warn)Source (CLAUDE.md:152): "Prefer named exports over default exports for new modules."
{
"id": "enforce-local-claude-md-prefer-named-exports",
"enabled": true,
"trigger": "PostToolUse",
"tool_match": ["Edit", "Write", "MultiEdit", "apply_patch"],
"action": "warn",
"patterns": [
{ "field": "content", "regex": "^export\\s+default\\b", "flags": "m" }
],
"reason": "CLAUDE.md:152 prefers named exports over default exports.",
"severity": "low",
"category": "distilled-from-md",
"source": { ... "lexical_marker": "Prefer", "marker_class": "advisory" }
}Note: an earlier draft of this example carried a second positive pattern on file_path (\.tsx?$) intended as an AND-style scope filter. Per §6 "Pattern semantics", positive patterns OR — that filter would have made the rule fire on every .tsx edit regardless of content. Single-pattern advisory rules are usually right; the verify pipeline gates noisy advisories at the report layer, not the rule layer.
Source (CLAUDE.md:201): "We usually try to keep PRs small."
→ Skipped entry only:
{ "file": "CLAUDE.md", "lines": [201, 201], "quote": "We usually try to keep PRs small.", "reason": "hedged: 'usually try to'", "marker_class": "skip-hedged" }Source (AGENTS.md:71): "Never use the railway-mcp__volumeDelete tool from agent sessions."
{
"id": "enforce-local-agents-md-no-railway-volume-delete",
"enabled": true,
"trigger": "PreToolUse",
"tool_match": ["railway-mcp__volumeDelete"],
"action": "block",
"patterns": [],
"reason": "BLOCKED by AGENTS.md:71 — \"Never use railway-mcp__volumeDelete from agent sessions.\"",
"severity": "critical",
"category": "distilled-from-md",
"source": { ... "marker_class": "block-direct" }
}Source (.claude/skills/migrate-to-shoehorn/SKILL.md:12): "Test code only. Never use shoehorn in production code."
{
"id": "enforce-skill-migrate-to-shoehorn-no-prod-shoehorn",
"enabled": true,
"trigger": "PreToolUse",
"tool_match": ["Edit", "Write", "MultiEdit", "apply_patch"],
"action": "block",
"patterns": [
{ "field": "content", "regex": "from\\s+[\"']@total-typescript/shoehorn[\"']" },
{ "field": "file_path", "regex": "\\.test\\.|\\.spec\\.|/__tests__/|/__fixtures__/", "negate": true }
],
"reason": "BLOCKED by migrate-to-shoehorn skill: shoehorn is a test-only library — never import it from production code paths.",
"severity": "high",
"category": "distilled-from-md",
"active_when": {
"skill": "migrate-to-shoehorn"
},
"source": {
"group_id": "skill:migrate-to-shoehorn",
"group_label": "skill:migrate-to-shoehorn",
"file": ".claude/skills/migrate-to-shoehorn/SKILL.md",
"lines": [12, 12],
"quote": "Never use shoehorn in production code.",
"lexical_marker": "Never",
"marker_class": "block-direct"
},
"distilled_action_reason": "lexical 'Never' + concrete content+file_path triggers (positive on import line, negate on test paths) → action=block per §5a; scope-gated to skill:migrate-to-shoehorn per §7 active_when population — rule is dormant until `interlinked skill enter migrate-to-shoehorn` fires"
}Three things to notice about this example:
active_when.skill is populated automatically because the source is <skill-name>/SKILL.md. The distiller doesn't ask; per §7's source-location table, every skill body rule gets active_when.skill = "<skill-name>".block, not downgraded to ask. With pre-active-when distillation, skill rules had to soften because they were always-on and would over-fire outside their domain. With scope, full-strength enforcement is safe — the rule simply doesn't fire when the skill isn't active.interlinked skill enter migrate-to-shoehorn in the slash-command preamble (and leave on completion). Skills that don't opt in distill correctly but their active_when.skill marker is never set, so the rule stays dormant. This is intentional — it makes activation explicit.Note: TDD-themed skills (Matt Pocock's tdd/SKILL.md, gstack's TDD prose, etc.) are not distilled by /enforce — see §2d skip list. TDD enforcement is owned by the harness's native primitives. This example uses a non-TDD skill so the worked example doesn't conflict with that policy.
AGENTS.md:42: "Never push to main without code review."
AGENTS.override.md:12: "MUST NOT push to ANY remote branch this week — release freeze."
The override is strictly broader. Output:
rules[] contains the override-derived rule with tool_match=["Bash"] + command~/^git\s+push\b/.conflicts[] records winning_source: "AGENTS.override.md:12", overridden_sources: ["AGENTS.md:42"].| Failure | Detection | Fix |
|---|---|---|
| Hallucinated rule | source.quote does not appear verbatim in source.file | Drop the rule; log to extraction_errors[]. |
| Hedged distilled to block | marker is usually/prefer/try but action=block | Re-classify per §5. |
| Trigger too broad | regex is .* with no anchors | Reject; downgrade to advisory. |
| Trigger too narrow | matches only literal command without escaping | Add \b boundaries. |
| Persona file got distilled | source path matches §2d skip list | Drop everything from that file. |
| Same imperative in two files | both distilled with same trigger | Merge per §10. |
| Out-of-date previous distillation | source hash mismatch | Re-extract that file; preserve unchanged. |
| User-removed group reappeared | overrides not applied | Always read overrides BEFORE extracting. |
| Catastrophic-backtracking regex | nested unbounded quantifiers | Reject; rewrite. |
| Empty-matching regex | new RegExp(p).test("") is true | Reject; rewrite. |
| Remote URL not on allowlist | host not github.com / raw.githubusercontent.com / gitlab.com | Refuse with clear error. |
IDENTITY.md, SOUL.md, STYLE.md, MEMORY.md, HEARTBEAT.md, or any other persona/memory/architecture file..interlinked/guard-rules.json or .interlinked/guard-rules.local.json..md file. Reading only.src/harness/adapters/.The harness fans rules out across every configured coding agent (Claude Code, Codex, Copilot CLI, Cursor, Gemini CLI) through src/harness/adapters/. One distilled-rules.json produces enforcement across all of them. Do not write per-runner variants. The runner-specific decision primitives (ask vs deny vs block vs ctx vs stderr) are translated by the adapter layer.
If the user wants per-runner overrides ("only enforce this in Codex, not Claude"), use the optional applies_to_runners field on the distilled rule. Otherwise, omit it (applies to all).
never / MUST NOT / forbidden / shall not → block, no exceptions.active_when population is binding (§7 source-location table). Skill-body rules ALWAYS carry active_when.skill = "<skill-name>". Project-wide rules (CLAUDE.md, AGENTS.md, etc.) ALWAYS omit active_when (always-on). Skip is always safer than mis-scoping..interlinked/distilled-rules.json (or .review.json) and .interlinked/distilled-rules.overrides.json.removed_groups, removed_rule_ids, disabled_rule_ids, and modifications.© QuentinCody, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 1 other file in skills/enforce of QuentinCody/interlinked-cli.
Open the folder on GitHubat commit 31b5259
Enforce next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Enforce this skillQuentinCody/interlinked-cli | 178 | — | ~19k | Automated safety check: Pass | MIT | |
| Lint Repository Markdowncodsen/codsen | 214 | — | ~832 | Automated safety check: Pass | MIT | |
| Create SkillPINA-org/PINA | 798 | — | ~1.8k | Automated safety check: Pass | MIT | |
| AI Self ImprovementJocysCom/FocusLogger | 213 | — | ~1.1k | Automated safety check: Pass | GPL-3.0 | |
| Setup Matt Pocock Skillsywwynm/EverythingDone | 144 | 9 repos | ~1.7k | Automated safety check: Pass | GPL-3.0 | |
| Writing Docsc15t/c15t | 1.9k | — | ~872 | Automated safety check: Pass | Apache-2.0 |
codsen/codsen
Keep the repository's linted Markdown passing npm run lint:markdown.
PINA-org/PINA
Create, modify, and improve PINA skills. An agent skill from PINA-org/PINA.
JocysCom/FocusLogger
Update, create, improve, and synchronise this repository's AI agent instructions and related assets (including skills).
ywwynm/EverythingDone
Sets up an Agent skills block in AGENTS.md/CLAUDE.md and docs/agents/ so the engineering skills know this repo's issue tracker (GitHub or local markdown), triage label vocabulary, and domain doc…
c15t/c15t
Author or edit c15t documentation in docs//.mdx — the source for both the c15t.com site and the docs bundled into published packages.
johnpapa/ai-ready
ANALYSIS SKILL — Analyze any repository and generate AI-ready configuration — a canonical AGENTS.md, thin per-tool pointer files, skills, CI workflows, issue templates.
QuentinCody/interlinked-cli
Package, test, and operate the experimental Interlinked Cowork plugin.
QuentinCody/interlinked-cli
Overview and router for the Interlinked CLI — a local guard, quality-enforcement, simplification-review, semantic-code-search, and observability layer for AI coding agents.
QuentinCody/interlinked-cli
Coordinate with other agents/humans via the optional Interlinked MCP Server, and use local checkpoints & file reservations.
QuentinCody/interlinked-cli
Investigate agent activity and JSONL/gzip evidence. An agent skill from QuentinCody/interlinked-cli.
QuentinCody/interlinked-cli
Install and operate Interlinked's optional local semantic function index.
QuentinCody/interlinked-cli
Keep prose specs and design docs honest against the code using Interlinked's spec-audit system.
Works with
Categories
Distill imperative markdown guidance (AGENTS.md, CLAUDE.md, .clinerules/, GEMINI.md, SKILL.md with hard imperatives) into deterministic Interlinked harness hook rules with verbatim source provenance. Enforce is an agent skill from QuentinCody/interlinked-cli.md with hard imperatives) into deterministic Interlinked harness hook rules with verbatim source provenance.
Enforce fits situations like: tasks that involve Agent instruction files; tasks that involve Markdown.
Run `npx skills add QuentinCody/interlinked-cli --skill enforce -a claude-code`. Or copy the skill folder (skills/enforce in QuentinCody/interlinked-cli) into .claude/skills/enforce in your project. Claude Code loads it when a task matches its description.
Run `npx skills add QuentinCody/interlinked-cli --skill enforce -a codex`. Or copy the skill folder (skills/enforce in QuentinCody/interlinked-cli) into .agents/skills/enforce in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add QuentinCody/interlinked-cli --skill enforce -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/enforce, .gemini/skills/enforce, .github/skills/enforce and .opencode/skills/enforce in your project.
Going by SKILL.md and its folder, Enforce needs the command-line tools its instructions call (npm).
SKILL.md names 1 domain. In commands or code: raw.githubusercontent.com; the agent is likely to contact it when it follows the instructions. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Enforce is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 19k tokens (SKILL.md is roughly 77k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Enforce: Lint Repository Markdown (codsen/codsen, 214 stars), Create Skill (PINA-org/PINA, 798 stars), AI Self Improvement (JocysCom/FocusLogger, 213 stars) and Setup Matt Pocock Skills (ywwynm/EverythingDone, 144 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
QuentinCody (a GitHub user) maintains it in QuentinCody/interlinked-cli, which has 178 GitHub stars. The repository holds 12 skills in this directory. The repository was last updated on October 2, 2026.
Source: QuentinCody/interlinked-cli on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.