Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/spec-baselines.json
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@
]
},
"github-copilot": {
"url": "https://docs.github.com/en/copilot/customizing-copilot",
"url": "https://docs.github.com/en/copilot/how-tos/copilot-on-github/customize-copilot/add-custom-instructions/add-repository-instructions",
"hash": "668c75acb7948484e1334aeb040332961836fbe187561fa830219afcde8ae289",
"rules": [
"COP-001",
Expand Down Expand Up @@ -213,7 +213,7 @@
]
},
"cline-rules": {
"url": "https://docs.cline.bot/features/cline-rules/overview",
"url": "https://docs.cline.bot/customization/cline-rules",
"hash": "3a42acacd88e62655c440c04214bed9e622793007489f1d9ef68a2ea85360063",
"rules": [
"CL-SK-001",
Expand Down
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Fixed
- **COP-005 rejected the documented `excludeAgent` value and autofixed it wrong**. The valid values are `code-review` and `cloud-agent`; agnix listed `coding-agent`, which was never upstream - so the doc's own value errored, and the unsafe autofix rewrote a *correct* value into the invalid one. `coding-agent` is retained as a deprecated alias so configs written against agnix's previous wrong advice keep validating, reported at info level rather than accepted silently, and kept out of the autofix candidate list so the fixer can never write it. Fixed across the validator, all 12 locale files, both `rules.json` copies, and the knowledge-base docs.
- **COP-003 rejected comma-separated `applyTo`**. "You can specify multiple patterns by separating them with commas", with `applyTo: "**/*.ts,**/*.tsx"` as the doc's own example. The whole string went to `glob::Pattern`, so every such config errored. A correct brace/bracket-aware splitter already existed with 18 passing tests but was marked `#[allow(dead_code)] // reserved for future use` and never called.
- **CUR-004 rejected comma-separated `globs`**. Same class as COP-003, same upstream wording ("Separate multiple patterns with commas"). This one was *intermittently* wrong, which is why it survived: the doc's literal example `docs/**/*.md, docs/**/*.mdx` happens to parse as one pattern, while `src/**, tests/**` does not.
- **CUR-010 required `version` in `.cursor/hooks.json`**. It is documented with a default of `1` and several of the doc's own examples omit it. Now optional, and type-checked as a number (not an integer) only when present.
- **CUR-012 required `command` on prompt hooks**. "Prompt hooks use an LLM to evaluate a natural language condition" and carry `prompt` instead - the doc's example has only `type`, `prompt`, `timeout`. CUR-018 already checked `prompt` for those.
- **CUR-016 required `install` in `.cursor/environment.json`**. The published schema has no `required` array at all (`definitions.common`) and `required: []` (`definitions.container`), and the setup page's own snapshot example omits `install`. Terminal entries now require only `command` (`name`/`description` are optional per both `oneOf` branches, and the array branch is accepted), and `update` - absent from the schema, which sets `unevaluatedProperties: false` - is reported as renamed to `install` rather than accepted as valid.
- **Cursor hook `timeout` suggestions said milliseconds**. The option is "Execution timeout in **seconds**".
- **CC-MEM-014 fired on Cursor `.mdc` files**. `ClaudeMdValidator` is registered for `FileType::CursorRule` for its generic prose checks, so a `.cursor/rules/*.mdc` file was reported as `CLAUDE.md has N non-empty lines, exceeding the recommended 200 line limit` - wrong filename, wrong threshold (Cursor documents 500), wrong tool attribution. Now scoped to Claude memory files.
- **Stale evidence URLs for 18 rules**. `docs.github.com/en/copilot/customizing-copilot` 301-redirects and `docs.cline.bot/features/cline-rules/overview` 308-redirects; both are updated in `rules.json` and `.github/spec-baselines.json`, so those two sentinel sources can match again.

### Added
- **`args` on command hooks**. The exec-form field - "When present, `command` is resolved as an executable and spawned directly with `args` as the argument vector, with no shell involved" - was absent from the schema, so it was silently ignored and two rules could not tell exec form from shell form.

Expand Down
16 changes: 10 additions & 6 deletions crates/agnix-cli/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -422,9 +422,11 @@ rules:
message: Unknown frontmatter key '%{key}' in Copilot instruction file
suggestion: Remove unknown key '%{key}'. Only 'applyTo' and 'excludeAgent' are recognized.
cop_005:
message: 'Invalid excludeAgent value ''%{value}''. Valid values: ''code-review'', ''coding-agent'''
suggestion: Use 'code-review' or 'coding-agent' for excludeAgent
message: 'Invalid excludeAgent value ''%{value}''. Valid values: ''code-review'', ''cloud-agent'''
suggestion: Use 'code-review' or 'cloud-agent' for excludeAgent
fix: Change excludeAgent to '%{fixed}'
legacy_coding_agent: "'coding-agent' is not a documented excludeAgent value - Copilot uses 'cloud-agent'. Accepted for compatibility with earlier agnix advice."
legacy_suggestion: Change 'coding-agent' to 'cloud-agent'
cop_006:
message: Copilot instruction file exceeds recommended length (%{len} characters, limit ~4000)
suggestion: Reduce content length or split into scoped instruction files
Expand Down Expand Up @@ -463,14 +465,14 @@ rules:
message: Agent-requested rule has no description - the agent cannot determine when to apply it
suggestion: Add a description field explaining when this rule should apply, or set alwaysApply or globs to control activation
cur_010:
message: .cursor/hooks.json must define an integer version and a hooks object
missing_version: .cursor/hooks.json is missing required integer field 'version'
message: .cursor/hooks.json must define a hooks object, and a numeric version when present
invalid_version: "Field 'version' must be a number (it defaults to 1 when omitted)"
missing_hooks: .cursor/hooks.json is missing required object field 'hooks'
invalid_hooks: Field 'hooks' must be an object, got %{got}
invalid_event_hooks: hooks.%{event} must be an array of hook definitions, got %{got}
invalid_hook_entry: hooks.%{event}[%{index}] must be an object
parse_error: 'Failed to parse .cursor/hooks.json: %{error}'
suggestion: 'Define {"version": <integer>, "hooks": {"eventName": [{"type": "command", "command": "..."}]}}'
suggestion: 'Define {"hooks": {"eventName": [{"type": "command", "command": "..."}]}}; "version" is optional and defaults to 1'
cur_011:
message: Unknown Cursor hook event '%{event}'
suggestion: Use one of the documented Cursor hook events in .cursor/hooks.json
Expand Down Expand Up @@ -501,13 +503,15 @@ rules:
parse_error: 'Failed to parse .cursor/environment.json: %{error}'
install: Field 'install' must be a string
missing_install: Field 'install' is required and must be a string
update_renamed: "Field 'update' is not part of the environment schema - it was renamed to 'install'"
update_renamed_suggestion: Rename 'update' to 'install'; the published schema sets unevaluatedProperties=false, so unknown keys are invalid
start: Field 'start' must be a string when present
update: Field 'update' must be a string when present
invalid_build: Field 'build' must be an object when present
build_dockerfile: Field 'build.dockerfile' must be a string when present
build_context: Field 'build.context' must be a string when present
invalid_terminals: Field 'terminals' must be an array, got %{got}
terminal: terminals[%{index}] must be an object with string fields 'name' and 'command'
terminal: "terminals[%{index}] must have a string 'command' field"
terminal_not_object: terminals[%{index}] must be a JSON object
suggestion: 'Use schema: {"install":"...","start":"...","build":{"dockerfile":"...","context":"..."},"terminals":[{"name":"...","command":"..."}]}'
cln_001:
Expand Down
4 changes: 2 additions & 2 deletions crates/agnix-cli/locales/es.yml
Original file line number Diff line number Diff line change
Expand Up @@ -338,8 +338,8 @@ rules:
message: Clave de frontmatter desconocida '%{key}' en archivo de instrucciones de Copilot
suggestion: Elimina la clave desconocida '%{key}'. Solo se reconocen 'applyTo' y 'excludeAgent'.
cop_005:
message: 'Valor de excludeAgent invalido ''%{value}''. Valores validos: ''code-review'', ''coding-agent'''
suggestion: Usa 'code-review' o 'coding-agent' para excludeAgent
message: 'Valor de excludeAgent invalido ''%{value}''. Valores validos: ''code-review'', ''cloud-agent'''
suggestion: Usa 'code-review' o 'cloud-agent' para excludeAgent
cop_006:
message: El archivo de instrucciones de Copilot excede la longitud recomendada (%{len} caracteres, limite ~4000)
suggestion: Reduce la longitud del contenido o divide en archivos de instrucciones con alcance
Expand Down
4 changes: 2 additions & 2 deletions crates/agnix-cli/locales/zh-CN.yml
Original file line number Diff line number Diff line change
Expand Up @@ -322,8 +322,8 @@ rules:
message: Copilot 指令文件中未知的 frontmatter 键 '%{key}'
suggestion: 删除未知键 '%{key}'。只认可 'applyTo' 和 'excludeAgent'。
cop_005:
message: '无效的 excludeAgent 值 ''%{value}''。有效值: ''code-review''、''coding-agent'''
suggestion: 为 excludeAgent 使用 'code-review' 或 'coding-agent'
message: '无效的 excludeAgent 值 ''%{value}''。有效值: ''code-review''、''cloud-agent'''
suggestion: 为 excludeAgent 使用 'code-review' 或 'cloud-agent'
cop_006:
message: Copilot 指令文件超过推荐长度(%{len} 个字符,限制约 4000)
suggestion: 减少内容长度或拆分为范围限定的指令文件
Expand Down
16 changes: 10 additions & 6 deletions crates/agnix-core/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -422,9 +422,11 @@ rules:
message: Unknown frontmatter key '%{key}' in Copilot instruction file
suggestion: Remove unknown key '%{key}'. Only 'applyTo' and 'excludeAgent' are recognized.
cop_005:
message: 'Invalid excludeAgent value ''%{value}''. Valid values: ''code-review'', ''coding-agent'''
suggestion: Use 'code-review' or 'coding-agent' for excludeAgent
message: 'Invalid excludeAgent value ''%{value}''. Valid values: ''code-review'', ''cloud-agent'''
suggestion: Use 'code-review' or 'cloud-agent' for excludeAgent
fix: Change excludeAgent to '%{fixed}'
legacy_coding_agent: "'coding-agent' is not a documented excludeAgent value - Copilot uses 'cloud-agent'. Accepted for compatibility with earlier agnix advice."
legacy_suggestion: Change 'coding-agent' to 'cloud-agent'
cop_006:
message: Copilot instruction file exceeds recommended length (%{len} characters, limit ~4000)
suggestion: Reduce content length or split into scoped instruction files
Expand Down Expand Up @@ -463,14 +465,14 @@ rules:
message: Agent-requested rule has no description - the agent cannot determine when to apply it
suggestion: Add a description field explaining when this rule should apply, or set alwaysApply or globs to control activation
cur_010:
message: .cursor/hooks.json must define an integer version and a hooks object
missing_version: .cursor/hooks.json is missing required integer field 'version'
message: .cursor/hooks.json must define a hooks object, and a numeric version when present
invalid_version: "Field 'version' must be a number (it defaults to 1 when omitted)"
missing_hooks: .cursor/hooks.json is missing required object field 'hooks'
invalid_hooks: Field 'hooks' must be an object, got %{got}
invalid_event_hooks: hooks.%{event} must be an array of hook definitions, got %{got}
invalid_hook_entry: hooks.%{event}[%{index}] must be an object
parse_error: 'Failed to parse .cursor/hooks.json: %{error}'
suggestion: 'Define {"version": <integer>, "hooks": {"eventName": [{"type": "command", "command": "..."}]}}'
suggestion: 'Define {"hooks": {"eventName": [{"type": "command", "command": "..."}]}}; "version" is optional and defaults to 1'
cur_011:
message: Unknown Cursor hook event '%{event}'
suggestion: Use one of the documented Cursor hook events in .cursor/hooks.json
Expand Down Expand Up @@ -501,13 +503,15 @@ rules:
parse_error: 'Failed to parse .cursor/environment.json: %{error}'
install: Field 'install' must be a string
missing_install: Field 'install' is required and must be a string
update_renamed: "Field 'update' is not part of the environment schema - it was renamed to 'install'"
update_renamed_suggestion: Rename 'update' to 'install'; the published schema sets unevaluatedProperties=false, so unknown keys are invalid
start: Field 'start' must be a string when present
update: Field 'update' must be a string when present
invalid_build: Field 'build' must be an object when present
build_dockerfile: Field 'build.dockerfile' must be a string when present
build_context: Field 'build.context' must be a string when present
invalid_terminals: Field 'terminals' must be an array, got %{got}
terminal: terminals[%{index}] must be an object with string fields 'name' and 'command'
terminal: "terminals[%{index}] must have a string 'command' field"
terminal_not_object: terminals[%{index}] must be a JSON object
suggestion: 'Use schema: {"install":"...","start":"...","build":{"dockerfile":"...","context":"..."},"terminals":[{"name":"...","command":"..."}]}'
cln_001:
Expand Down
4 changes: 2 additions & 2 deletions crates/agnix-core/locales/es.yml
Original file line number Diff line number Diff line change
Expand Up @@ -338,8 +338,8 @@ rules:
message: Clave de frontmatter desconocida '%{key}' en archivo de instrucciones de Copilot
suggestion: Elimina la clave desconocida '%{key}'. Solo se reconocen 'applyTo' y 'excludeAgent'.
cop_005:
message: 'Valor de excludeAgent invalido ''%{value}''. Valores validos: ''code-review'', ''coding-agent'''
suggestion: Usa 'code-review' o 'coding-agent' para excludeAgent
message: 'Valor de excludeAgent invalido ''%{value}''. Valores validos: ''code-review'', ''cloud-agent'''
suggestion: Usa 'code-review' o 'cloud-agent' para excludeAgent
cop_006:
message: El archivo de instrucciones de Copilot excede la longitud recomendada (%{len} caracteres, limite ~4000)
suggestion: Reduce la longitud del contenido o divide en archivos de instrucciones con alcance
Expand Down
4 changes: 2 additions & 2 deletions crates/agnix-core/locales/zh-CN.yml
Original file line number Diff line number Diff line change
Expand Up @@ -322,8 +322,8 @@ rules:
message: Copilot 指令文件中未知的 frontmatter 键 '%{key}'
suggestion: 删除未知键 '%{key}'。只认可 'applyTo' 和 'excludeAgent'。
cop_005:
message: '无效的 excludeAgent 值 ''%{value}''。有效值: ''code-review''、''coding-agent'''
suggestion: 为 excludeAgent 使用 'code-review' 或 'coding-agent'
message: '无效的 excludeAgent 值 ''%{value}''。有效值: ''code-review''、''cloud-agent'''
suggestion: 为 excludeAgent 使用 'code-review' 或 'cloud-agent'
cop_006:
message: Copilot 指令文件超过推荐长度(%{len} 个字符,限制约 4000)
suggestion: 减少内容长度或拆分为范围限定的指令文件
Expand Down
13 changes: 11 additions & 2 deletions crates/agnix-core/src/rules/claude_md.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,8 +266,17 @@ impl Validator for ClaudeMdValidator {
}
}

// CC-MEM-014: CLAUDE.md exceeds 200-line recommended limit
if config.is_rule_enabled("CC-MEM-014") {
// CC-MEM-014: CLAUDE.md exceeds 200-line recommended limit.
//
// Claude-specific: the 200-line figure, the message and the
// CLAUDE.local.md suggestion are all from the Claude Code memory
// reference. This validator is also registered for `FileType::CursorRule`
// (for the generic prose checks), which meant a `.cursor/rules/*.mdc`
// file was reported as "CLAUDE.md has N lines" against Claude's limit -
// wrong filename, wrong threshold (Cursor documents 500), wrong tool.
// Gated on the file actually being a Claude memory file.
let is_claude_memory = matches!(crate::detect_file_type(path), crate::FileType::ClaudeMd);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The gate is correct today, but it is the second, differently-derived notion of "Claude memory file" in this one function.

Line 69 already computed is_claude_md = matches!(filename, "CLAUDE.md" | "CLAUDE.local.md"), and the early return at line 71 is the only reason this new gate agrees with it: detect_file_type also maps AGENTS.md, AGENTS.local.md and AGENTS.override.md to FileType::ClaudeMd (file_types/detection.rs:351), and the comment at line 67 says CC-MEM rules deliberately skip AGENTS.*. If line 69 ever gains a filename, CC-MEM-014's scope changes silently in the opposite direction. if is_claude_md && config.is_rule_enabled(...) says the same thing with no second source of truth and no path re-parse.

More important: there is no test for this change in either direction. Nothing asserts that a .cursor/rules/*.mdc over 200 lines no longer reports CC-MEM-014, and nothing asserts CLAUDE.local.md still does - the three existing tests (lines 1099-1153) all pass Path::new("CLAUDE.md"), which was passing before the change too. Per CLAUDE.md rule 7 this needs the pair.

if is_claude_memory && config.is_rule_enabled("CC-MEM-014") {
const MAX_RECOMMENDED_LINES: usize = 200;
let non_empty_lines = content
.lines()
Expand Down
Loading