use crate::Severity;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RuleCategory {
Structure,
References,
Context,
Security,
Commands,
Consistency,
}
impl std::fmt::Display for RuleCategory {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
formatter,
"{}",
match self {
Self::Structure => "structure",
Self::References => "references",
Self::Context => "context",
Self::Security => "security",
Self::Commands => "commands",
Self::Consistency => "consistency",
}
)
}
}
#[derive(Debug, Clone, Copy)]
pub struct RuleDefinition {
pub id: &'static str,
pub name: &'static str,
pub description: &'static str,
pub default_severity: Severity,
pub category: RuleCategory,
pub why: &'static str,
pub bad_example: &'static str,
pub good_example: &'static str,
}
macro_rules! rule {
($id:literal, $name:literal, $description:literal, $severity:expr, $category:expr, $why:literal, $bad:literal, $good:literal) => {
RuleDefinition {
id: $id,
name: $name,
description: $description,
default_severity: $severity,
category: $category,
why: $why,
bad_example: $bad,
good_example: $good,
}
};
}
pub static RULES: &[RuleDefinition] = &[
rule!(
"AGL001",
"invalid-syntax",
"Configuration syntax is malformed.",
Severity::Error,
RuleCategory::Structure,
"Malformed configuration cannot be loaded reliably by an agent.",
"{ \"mcpServers\":",
"{ \"mcpServers\": {} }"
),
rule!(
"AGL002",
"invalid-frontmatter",
"Markdown frontmatter is malformed or uses unsupported fields.",
Severity::Error,
RuleCategory::Structure,
"Broken metadata can silently disable scoped instructions and skills.",
"---\ndescription: [\n---",
"---\ndescription: Backend rules\n---"
),
rule!(
"AGL003",
"duplicate-key",
"A configuration object contains a duplicate key.",
Severity::Error,
RuleCategory::Structure,
"Parsers disagree about which duplicate value wins.",
"{ \"command\": \"a\", \"command\": \"b\" }",
"{ \"command\": \"a\" }"
),
rule!(
"AGL101",
"invalid-mcp",
"An MCP configuration or server definition is invalid.",
Severity::Error,
RuleCategory::Structure,
"Invalid MCP servers fail at agent startup or tool invocation.",
"{ \"mcpServers\": { \"fs\": {} } }",
"{ \"mcpServers\": { \"fs\": { \"command\": \"npx\", \"args\": [] } } }"
),
rule!(
"AGL102",
"unknown-field",
"A configuration contains an obviously unsupported field.",
Severity::Warning,
RuleCategory::Structure,
"Misspelled fields are usually ignored without an error.",
"commandd: npx",
"command: npx"
),
rule!(
"AGL103",
"invalid-url",
"An MCP URL is malformed or uses an unsafe scheme.",
Severity::Error,
RuleCategory::Structure,
"Malformed endpoints cannot connect; non-HTTP schemes are unexpected here.",
"url: ht!tp://host",
"url: https://mcp.example.com"
),
rule!(
"AGL104",
"missing-executable",
"A referenced executable is unavailable.",
Severity::Error,
RuleCategory::Structure,
"The configured command will fail immediately.",
"command: npxx",
"command: npx"
),
rule!(
"AGL105",
"undocumented-environment",
"An MCP environment variable is not documented in an environment template.",
Severity::Warning,
RuleCategory::Structure,
"Undocumented variables make MCP setup difficult to reproduce.",
"env: { API_TOKEN: ${API_TOKEN} } with no .env.example",
"Document API_TOKEN= in .env.example."
),
rule!(
"AGL201",
"invalid-glob",
"An instruction glob is malformed.",
Severity::Error,
RuleCategory::References,
"A malformed glob can prevent instructions from applying.",
"globs: '[src/**'",
"globs: 'src/**'"
),
rule!(
"AGL202",
"missing-path",
"A repository path referenced by instructions does not exist.",
Severity::Warning,
RuleCategory::References,
"Agents waste time or make wrong assumptions when guidance names stale paths.",
"Read `src/removed/auth.ts` first.",
"Read `src/auth.ts` first."
),
rule!(
"AGL203",
"missing-script",
"A referenced script file does not exist.",
Severity::Error,
RuleCategory::References,
"Required validation or deployment commands cannot run.",
"Run scripts/check.sh",
"Run scripts/verify.sh"
),
rule!(
"AGL301",
"large-context",
"An instruction file consumes excessive context.",
Severity::Warning,
RuleCategory::Context,
"Large instruction files reduce context available for useful work.",
"An instruction file over 6,000 approximate tokens",
"Keep durable instructions concise and scoped."
),
rule!(
"AGL302",
"duplicate-instruction",
"Substantially duplicate instructions appear in multiple files.",
Severity::Warning,
RuleCategory::Context,
"Repeated instructions consume context and drift independently.",
"Repeat the same test requirement in AGENTS.md and CLAUDE.md.",
"Keep shared guidance in AGENTS.md and link to it."
),
rule!(
"AGL303",
"verbose-block",
"A single instruction block is excessively verbose.",
Severity::Info,
RuleCategory::Context,
"Dense blocks are harder for people and agents to follow.",
"A paragraph longer than 1,200 characters",
"Split instructions into concise, actionable sections."
),
rule!(
"AGL401",
"hardcoded-secret",
"A likely credential is embedded in agent configuration.",
Severity::Error,
RuleCategory::Security,
"Agent configuration is commonly committed and broadly read.",
"OPENAI_API_KEY=sk-proj-example-secret",
"OPENAI_API_KEY=${OPENAI_API_KEY}"
),
rule!(
"AGL501",
"destructive-command",
"An instruction contains a destructive shell command.",
Severity::Warning,
RuleCategory::Commands,
"An agent may run the command without understanding unrecoverable effects.",
"git clean -fdx",
"Review untracked files, then remove explicit paths."
),
rule!(
"AGL502",
"unknown-command",
"A shell command is misspelled or unavailable.",
Severity::Warning,
RuleCategory::Commands,
"An unavailable command makes the documented workflow fail.",
"nmp test",
"npm test"
),
rule!(
"AGL503",
"missing-package-script",
"A package-manager command references a missing script.",
Severity::Error,
RuleCategory::Commands,
"The command cannot run against the repository package metadata.",
"npm run chek",
"npm run check"
),
rule!(
"AGL601",
"package-manager-conflict",
"Instructions conflict with the repository package manager.",
Severity::Warning,
RuleCategory::Consistency,
"Mixing package managers changes lockfiles and dependency resolution.",
"Use npm in a pnpm repository.",
"Use pnpm in a pnpm repository."
),
rule!(
"AGL602",
"contradictory-instruction",
"Two high-confidence instructions contradict each other.",
Severity::Warning,
RuleCategory::Consistency,
"Conflicting guidance makes agent behavior unpredictable.",
"Always use npm. Never use npm.",
"Always use pnpm."
),
rule!(
"AGL603",
"tooling-mismatch",
"Instructions require tooling unsupported by repository metadata.",
Severity::Warning,
RuleCategory::Consistency,
"Stale commands distract agents and fail in clean environments.",
"Run pytest in a repository with no Python project.",
"Run cargo test in a Rust repository."
),
];
pub fn definitions() -> &'static [RuleDefinition] {
RULES
}
pub fn definition(id: &str) -> Option<&'static RuleDefinition> {
RULES.iter().find(|rule| rule.id.eq_ignore_ascii_case(id))
}
pub fn default_severity(id: &str) -> Severity {
definition(id).map_or(Severity::Warning, |rule| rule.default_severity)
}