feat(claude-code): add output-style validator (CC-OS-001..005) - #757
Conversation
Surfaced during the Claude Code v2.1.117 triage (#745) but kept out of that PR per "no follow-ups" rule -- output-style files are a green-field new file type, not a fix. ## What's new - New \`FileType::ClaudeOutputStyle\` for \`.claude/output-styles/*.md\` and \`~/.claude/output-styles/*.md\` - New \`OutputStyleSchema\` and \`OutputStyleValidator\` in agnix-core - 5 new validation rules: - CC-OS-001 (LOW): Missing or empty \`description\` - CC-OS-002 (HIGH): \`keep-coding-instructions\` is not a YAML boolean (the spec requires bool; reject string \`"yes"\`, number \`1\`, \`null\`) - CC-OS-003 (MEDIUM): Unknown frontmatter key (signals typo or wrong file type -- valid keys are \`name\`, \`description\`, \`keep-coding-instructions\`) - CC-OS-004 (MEDIUM): Empty body after closing \`---\` (style with no instructions is dead config) - CC-OS-005 (LOW): \`name\` exceeds 64 characters (truncates in UI) - All rules: non-autofix (no obvious correct rewrite for any of them) - 6 fixtures (1 valid + 5 invalid, one per rule) Total rules: 399 -> 404. Validator count: 73 -> 74. FileType variants: 42 -> 43. ## Why now Claude Code added the \`keep-coding-instructions\` field in v2.1.94 and the output-style file type itself predates that. agnix has been silently ignoring these files. With the v2.1.117 catch-up complete (PR #756), this fills the last gap surfaced by that triage. ## Touchpoints Primary (12): file_types/{types,detection}, schemas/{output_style,mod}, rules/{output_style,mod}, registry, rules.json (both copies), VALIDATION-RULES.md, CHANGELOG, fixtures. Secondary (3): config.rs (\`RuleConfig.output_styles\`), config/rule_filter.rs (\`CC-OS-\` prefix branch), rule_parity.rs (3 separate insertions). Generated (forced regen): website/docs/rules/generated/cc-ag-{009,010,011,014,019}.md and cc-sk-018.md picked up the verified_on bumps from PR #756; cdx-pl-005 through cdx-pl-013 picked up other pre-existing drift. The \`docs_website_parity\` test forces regen on any rules.json edit. ## Verified locally - \`cargo check --workspace\`: clean - \`cargo test -p agnix-core --lib\`: 3440 passed (+32 new) - \`cargo test --workspace --all-targets\`: every suite ok (iai bench needs iai-callgrind-runner; pre-existing local env issue; CI has it) - \`cargo clippy --workspace --all-targets --all-features -- -D warnings\`: clean - \`cargo test -p agnix-rules --tests\`: 47 + 4 parity tests pass
CI's format check caught fmt drift in crates/agnix-core/src/{rules,schemas}/output_style.rs
(the implementation didn't run `cargo fmt` before reporting done).
Pure mechanical reformat, no behavior change.
There was a problem hiding this comment.
Code Review
This pull request introduces a new validator for Claude Code output-style files, implementing five validation rules (CC-OS-001 through CC-OS-005) to check frontmatter fields and body content. The changes include file type detection logic, schema parsing, and integration into the core registry and configuration systems, along with updated documentation and test fixtures. Review feedback identifies that rule CC-OS-002 is being incorrectly used to report general YAML syntax errors and recommends simplifying the frontmatter parsing logic by leveraging serde's renaming attributes instead of manual mapping.
| path.to_path_buf(), | ||
| parsed.start_line, | ||
| 0, | ||
| "CC-OS-002", |
There was a problem hiding this comment.
The rule ID CC-OS-002 is being overloaded here to report general YAML parsing errors (like a missing closing ---). According to the rule definitions in rules.json and VALIDATION-RULES.md, CC-OS-002 is specifically for validating that the keep-coding-instructions field is a boolean. Using it for structural syntax errors is misleading and can result in confusing diagnostic messages in UIs that display the rule name alongside the message.
| /// The hyphenated key `keep-coding-instructions` is extracted manually from | ||
| /// the `serde_yaml::Mapping` because `#[derive(Deserialize)]` field names can | ||
| /// not contain hyphens. |
There was a problem hiding this comment.
The comment stating that #[derive(Deserialize)] field names cannot contain hyphens is incorrect. The serde crate provides the #[serde(rename = "...")] attribute specifically to handle cases where the serialized key name (like keep-coding-instructions) is not a valid Rust identifier. Using manual extraction from a serde_yaml::Mapping adds unnecessary complexity and verbosity to the parsing logic.
| fn parse_schema(raw: &str) -> (Option<OutputStyleSchema>, Option<String>) { | ||
| if raw.trim().is_empty() { | ||
| return (Some(OutputStyleSchema::default()), None); | ||
| } | ||
|
|
||
| let value: serde_yaml::Value = match serde_yaml::from_str(raw) { | ||
| Ok(v) => v, | ||
| Err(e) => return (None, Some(e.to_string())), | ||
| }; | ||
|
|
||
| let mapping = match value.as_mapping() { | ||
| Some(m) => m, | ||
| None => return (Some(OutputStyleSchema::default()), None), | ||
| }; | ||
|
|
||
| let mut schema = OutputStyleSchema::default(); | ||
|
|
||
| if let Some(v) = mapping.get(serde_yaml::Value::String("name".to_string())) { | ||
| if let Some(s) = v.as_str() { | ||
| schema.name = Some(s.to_string()); | ||
| } | ||
| } | ||
|
|
||
| if let Some(v) = mapping.get(serde_yaml::Value::String("description".to_string())) { | ||
| if let Some(s) = v.as_str() { | ||
| schema.description = Some(s.to_string()); | ||
| } | ||
| } | ||
|
|
||
| if let Some(v) = mapping.get(serde_yaml::Value::String( | ||
| "keep-coding-instructions".to_string(), | ||
| )) { | ||
| schema.keep_coding_instructions = Some(v.clone()); | ||
| } | ||
|
|
||
| (Some(schema), None) | ||
| } |
There was a problem hiding this comment.
The manual extraction of fields from serde_yaml::Mapping is verbose and error-prone. You can significantly simplify this by defining a temporary helper struct with #[derive(serde::Deserialize)] and using #[serde(rename)] for the hyphenated key. This also maintains the ability to distinguish between a missing field and a field with an invalid type by using Option<serde_yaml::Value> for the keep-coding-instructions field.
fn parse_schema(raw: &str) -> (Option<OutputStyleSchema>, Option<String>) {
if raw.trim().is_empty() {
return (Some(OutputStyleSchema::default()), None);
}
#[derive(serde::Deserialize)]
struct RawSchema {
name: Option<String>,
description: Option<String>,
#[serde(rename = "keep-coding-instructions")]
keep_coding_instructions: Option<serde_yaml::Value>,
}
match serde_yaml::from_str::<RawSchema>(raw) {
Ok(s) => (
Some(OutputStyleSchema {
name: s.name,
description: s.description,
keep_coding_instructions: s.keep_coding_instructions,
}),
None,
),
Err(e) => (None, Some(e.to_string())),
}
}There was a problem hiding this comment.
Pull request overview
Adds first-class validation support for Claude Code output-style markdown files (.claude/output-styles/*.md) across the core validator registry, rule metadata, fixtures, and generated website docs.
Changes:
- Introduce
FileType::ClaudeOutputStyleplus detection/registry wiring and a newOutputStyleValidatorimplementing CC-OS-001..005. - Add new output-style fixtures and update parity/integrity tests to recognize CC-OS-* and the new category.
- Regenerate rules/docs outputs to reflect total rules 399 → 404 (including website rule pages and indices).
Reviewed changes
Copilot reviewed 26 out of 46 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| website/src/data/siteData.json | Updates website rule counts metadata. |
| website/docs/rules/index.md | Adds CC-OS-* rows to the rules reference index. |
| website/docs/rules/generated/cdx-pl-013.md | Regenerated Codex rule doc example snippets. |
| website/docs/rules/generated/cdx-pl-012.md | Regenerated Codex rule doc example snippets. |
| website/docs/rules/generated/cdx-pl-011.md | Regenerated Codex rule doc example snippets. |
| website/docs/rules/generated/cdx-pl-010.md | Regenerated Codex rule doc example snippets. |
| website/docs/rules/generated/cdx-pl-009.md | Regenerated Codex rule doc example snippets. |
| website/docs/rules/generated/cdx-pl-008.md | Regenerated Codex rule doc example snippets. |
| website/docs/rules/generated/cdx-pl-007.md | Regenerated Codex rule doc example snippets. |
| website/docs/rules/generated/cdx-pl-006.md | Regenerated Codex rule doc example snippets. |
| website/docs/rules/generated/cdx-pl-005.md | Regenerated Codex rule doc example snippets. |
| website/docs/rules/generated/cc-sk-018.md | Bumps Verified On date in generated docs. |
| website/docs/rules/generated/cc-os-005.md | New generated doc page for CC-OS-005. |
| website/docs/rules/generated/cc-os-004.md | New generated doc page for CC-OS-004. |
| website/docs/rules/generated/cc-os-003.md | New generated doc page for CC-OS-003. |
| website/docs/rules/generated/cc-os-002.md | New generated doc page for CC-OS-002. |
| website/docs/rules/generated/cc-os-001.md | New generated doc page for CC-OS-001. |
| website/docs/rules/generated/cc-ag-019.md | Bumps Verified On date in generated docs. |
| website/docs/rules/generated/cc-ag-014.md | Bumps Verified On date in generated docs. |
| website/docs/rules/generated/cc-ag-011.md | Bumps Verified On date in generated docs. |
| website/docs/rules/generated/cc-ag-010.md | Bumps Verified On date in generated docs. |
| website/docs/rules/generated/cc-ag-009.md | Bumps Verified On date in generated docs. |
| tests/fixtures/valid/output-styles/.claude/output-styles/concise.md | Adds a valid output-style fixture. |
| tests/fixtures/invalid/output-styles/.claude/output-styles/unknown-key.md | Adds an invalid fixture (unknown frontmatter key). |
| tests/fixtures/invalid/output-styles/.claude/output-styles/non-bool-keep.md | Adds an invalid fixture (keep-coding-instructions wrong type). |
| tests/fixtures/invalid/output-styles/.claude/output-styles/missing-description.md | Adds an invalid fixture (missing description). |
| tests/fixtures/invalid/output-styles/.claude/output-styles/long-name.md | Adds an invalid fixture (name too long). |
| tests/fixtures/invalid/output-styles/.claude/output-styles/empty-body.md | Adds an invalid fixture (empty body). |
| knowledge-base/rules.json | Adds CC-OS-001..005 rules and bumps totals. |
| knowledge-base/VALIDATION-RULES.md | Documents CC-OS-001..005 requirements/detections. |
| crates/agnix-rules/rules.json | Mirrors knowledge-base rules.json updates for distribution. |
| crates/agnix-core/tests/api_contract.rs | Updates FileType contract test for new variant. |
| crates/agnix-core/src/schemas/output_style.rs | Implements output-style frontmatter parsing + helpers. |
| crates/agnix-core/src/schemas/mod.rs | Exposes the new output_style schema module. |
| crates/agnix-core/src/rules/output_style.rs | Implements OutputStyleValidator (CC-OS-001..005) + unit tests. |
| crates/agnix-core/src/rules/mod.rs | Exposes the new output_style rules module. |
| crates/agnix-core/src/registry.rs | Registers OutputStyleValidator and updates expected counts/tests. |
| crates/agnix-core/src/file_types/types.rs | Adds FileType::ClaudeOutputStyle and updates Display/tests. |
| crates/agnix-core/src/file_types/detection.rs | Detects .claude/output-styles/*.md as ClaudeOutputStyle + tests. |
| crates/agnix-core/src/config/rule_filter.rs | Enables category toggling for CC-OS-* rules. |
| crates/agnix-core/src/config.rs | Adds output_styles toggle to RuleConfig. |
| crates/agnix-cli/tests/rule_parity.rs | Teaches parity tests about CC-OS-* prefix/category + fixtures. |
| crates/agnix-cli/tests/cli_integration.rs | Adjusts SARIF rule-count upper bound threshold. |
| CHANGELOG.md | Documents the new output-style validator and rule IDs. |
| .exploration-report-pr2.md | Adds PR exploration report artifact describing implementation steps. |
| .claude/flow.json | Adds Claude workflow/task metadata for this work. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| "totalRules": 404, | ||
| "categoryCount": 36, | ||
| "autofixCount": 126, |
There was a problem hiding this comment.
categoryCount is still 36 even though the PR adds a new rule category (claude-output-styles). This suggests the categories index in knowledge-base/rules.json wasn’t updated, so the generated website metadata is now inconsistent. Update the categories map in rules.json and re-run the docs generator so siteData.json reflects the new category count.
| Output-style files (`.claude/output-styles/*.md` or `~/.claude/output-styles/*.md`) were added in Claude Code v2.1.94. Frontmatter has 3 known optional fields: `name`, `description`, `keep-coding-instructions`. | ||
|
|
There was a problem hiding this comment.
This sentence says output-style files “were added in Claude Code v2.1.94”, but the PR description says the file type predates that and that v2.1.94 specifically added the keep-coding-instructions field. Please reconcile this so the docs don’t conflict (e.g., describe v2.1.94 as the field addition / spec update rather than the file type introduction, if that’s the intended claim).
| assert!( | ||
| rules.len() <= 400, | ||
| "Expected at most 400 validation rules, found {} (unexpected rule explosion)", | ||
| rules.len() <= 450, | ||
| "Expected at most 450 validation rules, found {} (unexpected rule explosion)", |
There was a problem hiding this comment.
The assertion upper bound was bumped here, but the nearby comment still says “~385 rules” (it’s now 404). Please update that comment to match current reality so the threshold rationale stays accurate.
| "id": "CC-OS-001", | ||
| "name": "Output Style Missing Description", | ||
| "severity": "LOW", | ||
| "category": "claude-output-styles", | ||
| "evidence": { |
There was a problem hiding this comment.
The new CC-OS-* rules introduce a new category value (claude-output-styles), but knowledge-base/rules.json’s categories map at the end of the file doesn’t include an entry for it. This causes generated website metadata (e.g., siteData.json’s categoryCount) and category labels to be incorrect/missing. Add a claude-output-styles entry to the categories object (prefix CC-OS, count 5, description), then re-run the docs generator so the website uses the proper label.
| "id": "CC-OS-001", | ||
| "name": "Output Style Missing Description", | ||
| "severity": "LOW", | ||
| "category": "claude-output-styles", | ||
| "evidence": { |
There was a problem hiding this comment.
CC-OS-* rules use the new claude-output-styles category, but the categories object in the rules index doesn’t appear to define that category. This breaks downstream generated metadata (category counts/labels) and makes the website fall back to the raw slug. Add a claude-output-styles entry to the categories map (prefix CC-OS, count 5, description) and keep this file in sync with knowledge-base/rules.json.
| | [CC-OS-001](./generated/cc-os-001.md) | Output Style Missing Description | LOW | claude-output-styles | No | | ||
| | [CC-OS-002](./generated/cc-os-002.md) | Output Style Invalid keep-coding-instructions Type | HIGH | claude-output-styles | No | | ||
| | [CC-OS-003](./generated/cc-os-003.md) | Output Style Unknown Frontmatter Key | MEDIUM | claude-output-styles | No | | ||
| | [CC-OS-004](./generated/cc-os-004.md) | Output Style Empty Body | MEDIUM | claude-output-styles | No | | ||
| | [CC-OS-005](./generated/cc-os-005.md) | Output Style Name Exceeds Length | LOW | claude-output-styles | No | |
There was a problem hiding this comment.
The new CC-OS-* rows render the Category as the raw slug claude-output-styles instead of a human-friendly label (other rows show e.g. “Claude Memory”). Add a category label for claude-output-styles in the docs generator so the generated index uses a consistent display name.
|
Per-comment reply table for round 1
Plus CI fix: `cargo fmt --check` failed on the new code (`30accd2`). Re-ran `cargo fmt --all`; clippy still clean. Verified locally: `cargo clippy --workspace --all-targets --all-features -- -D warnings` clean; 3443 lib tests pass (+3 for CC-OS-006); rules.json parity tests pass. Pushing now. |
Reviewer feedback (PR #757 round 1, all 9 comments addressed): Gemini medium x3: - output_style.rs: split CC-OS-002 (was overloaded) - parse errors now use new CC-OS-006 [HIGH] Invalid Output Style Frontmatter Syntax. CC-OS-002 back to its narrow scope (non-bool keep-coding-instructions). +3 tests. - schemas/output_style.rs: refactored manual serde_yaml::Mapping extraction into #[derive(Deserialize)] + #[serde(rename = "keep-coding-instructions")]. Added a tiny deserialize_with helper to preserve YAML null as Some(Value::Null) for CC-OS-002 detection (otherwise serde collapses null into None, indistinguishable from absent). - Removed stale comment about hyphens in serde rename. Copilot x6 (all about the missing categories.claude-output-styles entry): - Added claude-output-styles to the categories map in rules.json (both copies); regenerated website docs picked up the friendly label and the bumped categoryCount. - Reworded VALIDATION-RULES.md output-styles section: file type predates v2.1.94; v2.1.94 specifically added keep-coding-instructions. - Updated cli_integration.rs comment '~385 rules' -> '~405 rules'. Plus a fmt fix that landed in the previous commit (30accd2). Verified: cargo clippy --workspace --all-targets --all-features -- -D warnings clean; 3443 lib tests pass (+3 new for CC-OS-006); rules.json parity tests pass; docs regenerated to 405 pages. Total rules: 404 -> 405. Validators: 73 -> 74 (unchanged from previous push).
…758) * fix(codex): catch up to rust-v0.122.0 (8 new top-level config keys) Closes #747. Triage of openai/codex rust-v0.118.0 -> rust-v0.122.0 (~150 PRs in the window) surfaced ONE structural change agnix needs to absorb: The upstream config-schema.json (verified 2026-04-22) gained 8 new top-level keys that agnix's CDX-CFG-006 (unknown top-level config key) would false-positive on: - experimental_realtime_start_instructions - experimental_realtime_ws_startup_context - include_apps_instructions - include_environment_context - include_permissions_instructions - marketplaces - realtime - tool_suggest Added all 8 to KNOWN_CONFIG_TOP_LEVEL_KEYS in crates/agnix-core/src/rules/codex.rs. Added a regression test (test_cdx_cfg_006_codex_v0_122_keys_accepted) that uses every one of the 8 keys in a single config and asserts zero CDX-CFG-006 diagnostics. Other inspected upstream changes that need NO agnix changes: - MCP server env config (#18085) - env/env_vars already known - AGENTS.md discovery refactor (#18035) - file format unchanged - Filesystem deny-read globs (#15979, #17740, #18096) - permissions already a known top-level key; agnix doesn't drill into sub-objects - PermissionRequest hooks (#17563) - agnix doesn't validate Codex hooks - models.json removal (#18585) - CDX-CFG-014 is a type-only check, no static model list to update - All TUI/runtime/refactor/CI PRs - no config-schema impact Documentation: - Bumped verified_on to 2026-04-22 for all 58 CDX-* rules in knowledge-base/rules.json (+ synced crates/agnix-rules/rules.json) - Bumped RESEARCH-TRACKING.md "Last Reviewed" for Codex CLI from 2026-02-05 to 2026-04-22 - Regenerated website/docs/rules/generated/*.md via scripts/generate-docs-rules.py (forced by docs_website_parity test) Verified locally: - cargo clippy --workspace --all-targets --all-features -- -D warnings clean - cargo test -p agnix-core --lib: 3444 passed (+1 new regression test) - cargo test -p agnix-rules --tests: 47 + 4 parity tests pass - cargo fmt --all clean * fix(codex): TOML CDX-004 path also needs the 8 v0.122 keys + better test Reviewer feedback (PR #758 round 1, Copilot - both spot-on): #1: My initial fix only updated KNOWN_CONFIG_TOP_LEVEL_KEYS in rules/codex.rs (the JSON/YAML config validation path). But CDX-004 (the rule that fires for unknown TOML top-level keys, which is the primary path users hit for .codex/config.toml) uses a SEPARATE allow-list: KNOWN_TOP_LEVEL_KEYS in schemas/codex.rs. Without this additional fix, agnix would still false-positive CDX-004 on every affected TOML config. Added all 8 keys to schemas::codex::KNOWN_TOP_LEVEL_KEYS: - experimental_realtime_start_instructions - experimental_realtime_ws_startup_context - include_apps_instructions - include_environment_context - include_permissions_instructions - marketplaces - realtime - tool_suggest Also added 'realtime' and 'marketplaces' to KNOWN_TABLE_KEYS since upstream allows both inline values and TOML tables ([realtime] / [[marketplaces]]). #2: My regression test was vacuous. validate_config() enables CDX-004 by default, and the skip_top_level branch (rules/codex.rs:1164) makes CDX-CFG-006 silently skip top-level TOML keys when CDX-004 is active. So my test passed even without any code change. Renamed and rewrote (test_codex_v0_122_top_level_keys_accepted) to assert against CDX-004 with two configs: (a) inline-value form using all 8 keys, (b) section-table form for [realtime] and [[marketplaces]]. The test now genuinely fails without the schemas/codex.rs fix. CHANGELOG updated to mention both files and the CDX-004 primary path. Verified: cargo clippy --workspace --all-targets --all-features -- -D warnings clean; 3444 lib tests pass; rules.json parity tests pass. * style: cargo fmt the round-1 review fix CI's format check (cargo fmt --check) caught two compact-line opportunities in the rewritten regression test. Mechanical reformat, no behavior change. Net: -10 LOC. Same lesson as PR #757 commit 30accd2 - run cargo fmt --all after every code edit, even small ones.
Reviewer feedback (PR #763 round 1): - Copilot caught that the cursor URL migration was incomplete: 10 AGM-*/XP-* rule entries in rules.json (both copies) also cite the old cursor.com/docs/context/rules URL, but my initial Python pass only matched CUR-* prefix. Migrated all 10 additional refs. - Copilot also caught .github/spec-baselines.json with 3 stale baseline URLs (cursor-rules/cursor-hooks/cursor-subagents). Without this fix, the next spec-drift.yml workflow run would 404. Migrated all 3. Additionally fixed (caught by my own grep, not flagged by reviewers but would have caused a parity test failure): - knowledge-base/VALIDATION-RULES.md: 10 'Source:' lines (bulk sed) - knowledge-base/MONTHLY-REVIEW.md: 3 URL refs - knowledge-base/agent-config-optional-fields.md: 1 markdown link Versioned docs at website/versioned_docs/version-0.12.0/ are frozen historical snapshots and were deliberately not modified. Verified cursor.com/docs/context/skills (a different URL used by some XP-SK rules) is STILL valid - only the rules/hooks/subagents URLs migrated upstream. Not touching that one. Reply-with-reason on Gemini's rules.json duplication concern: this is the project-wide pattern enforced by test_rules_json_parity (same answer as PR #757). Refactor to a build-script-driven approach is out of scope for a triage PR. Verified locally: - cargo clippy --workspace --all-targets --all-features -- -D warnings clean - cargo test -p agnix-rules --tests: parity tests pass
…1.17 (#763) * docs(cursor): refresh dates + migrate doc URLs for Cursor 3.0.0 -> 3.1.17 Closes #749. Triage of Cursor 3.0.0 -> 3.1.17. Re-verified all 19 CUR-* rules against the upstream changelog (cursor.com/changelog) and docs. The 3.0/3.1 features are all UX-side or cloud-side - none touch any on-disk file agnix validates: - 3.0 (2026-04-02): Agents Window, /worktree, /best-of-n, Design Mode, Agent Tabs, Await tool, multi-root hooks bug fix - 04-08 patch: Bugbot learned rules + MCP support (cloud-only) - 3.1 (2026-04-13): Tiled layout, voice input, diff-to-file nav, file search filters, perf - 04-15 patch: Canvases (interactive responses) Schema verification: - MDC frontmatter: description, globs, alwaysApply (matches agnix KNOWN_KEYS exactly) - Cursor agents: name, description, model, readonly, is_background (matches agnix exactly) - Hook events: 20 events documented, matches agnix CURSOR_HOOK_EVENTS exactly (no events added or removed) No code changes required. Cursor's docs URL structure migrated upstream (this is the fix users will see): - /docs/context/rules -> /docs/rules (CUR-001..009) - /docs/agent/hooks -> /docs/hooks (CUR-010..013, 017..019) - /docs/context/subagents -> /docs/subagents (CUR-014, CUR-015) - /docs/cloud-agent/setup is unchanged (CUR-016) Updated 18 source URL references across 18 rule entries. Documentation: - Bumped verified_on to 2026-04-22 for all 19 CUR-* rules in knowledge-base/rules.json and synced crates/agnix-rules/rules.json - Bumped RESEARCH-TRACKING.md 'Last Reviewed' for Cursor from 2026-02-26 to 2026-04-22 - Expanded the Config Format column to list all 5 file types Cursor validates (was only listing .cursor/rules/*.mdc and .cursorrules; added .cursor/hooks.json, .cursor/agents/*.md, .cursor/environment.json) - Updated the per-doc-source breakdown rows to use the new URLs - Regenerated website/docs/rules/generated/*.md Verified locally: - cargo clippy --workspace --all-targets --all-features -- -D warnings clean - cargo test -p agnix-rules --tests: 47 + 4 parity tests pass - No source code changed; no new tests needed * fix: complete cursor URL migration across all files (round 1 review) Reviewer feedback (PR #763 round 1): - Copilot caught that the cursor URL migration was incomplete: 10 AGM-*/XP-* rule entries in rules.json (both copies) also cite the old cursor.com/docs/context/rules URL, but my initial Python pass only matched CUR-* prefix. Migrated all 10 additional refs. - Copilot also caught .github/spec-baselines.json with 3 stale baseline URLs (cursor-rules/cursor-hooks/cursor-subagents). Without this fix, the next spec-drift.yml workflow run would 404. Migrated all 3. Additionally fixed (caught by my own grep, not flagged by reviewers but would have caused a parity test failure): - knowledge-base/VALIDATION-RULES.md: 10 'Source:' lines (bulk sed) - knowledge-base/MONTHLY-REVIEW.md: 3 URL refs - knowledge-base/agent-config-optional-fields.md: 1 markdown link Versioned docs at website/versioned_docs/version-0.12.0/ are frozen historical snapshots and were deliberately not modified. Verified cursor.com/docs/context/skills (a different URL used by some XP-SK rules) is STILL valid - only the rules/hooks/subagents URLs migrated upstream. Not touching that one. Reply-with-reason on Gemini's rules.json duplication concern: this is the project-wide pattern enforced by test_rules_json_parity (same answer as PR #757). Refactor to a build-script-driven approach is out of scope for a triage PR. Verified locally: - cargo clippy --workspace --all-targets --all-features -- -D warnings clean - cargo test -p agnix-rules --tests: parity tests pass * fix(ci): untrack accidentally-committed triage report; gitignore future ones CI's agnix self-lint failed with 'Unclosed XML tag <String> [XML-001]' because my round-1 fix commit (43b71fa) used 'git add -A' and inadvertently added the gitignored-by-convention working-memory file '.triage-report-749.md' (the exploration report). The report contains prose snippets like 'Option<String>' from Rust type discussions, which agnix's XML-001 rule flagged as unclosed XML tags. Fix: - git rm --cached .triage-report-749.md (file kept locally, removed from index) - Added '.triage-report-*.md' and '.exploration-report-*.md' to .gitignore so future triage PRs (#753, #744, #750, #754) won't accidentally re-include them. Verified locally: cargo run --release -p agnix-cli --bin agnix -- . returns 0 errors, 1 info (the pre-existing XP-SK-001 on plugin/skills/agnix/SKILL.md, unrelated to this PR). Lesson for future triage PRs: use 'git add <file>' explicitly instead of 'git add -A' so untracked working-memory files don't sneak in.
…view) Reviewer feedback (PR #767 round 1): - Copilot CRITICAL: typed Hook enum still rejected mcp_tool, so even with the raw-JSON allow-list fix, CC-HK-012 (schema mismatch) would fire on every mcp_tool hook. Added Hook::McpTool variant with tool, if, timeout fields (per release notes). Updated all match arms (command(), prompt(), type_name(), the per-variant typed-validation block in rules/hooks/mod.rs); added is_mcp_tool() for symmetry. - Copilot: test was vacuous (only checked CC-HK-016 absence; would have passed with CC-HK-012 still firing). Strengthened to assert diagnostics.is_empty() (zero diagnostics of any rule). Now genuinely fails without the typed-enum addition. - Copilot: comment said 'follows the docs' but docs page hasn't been updated yet; reworded to 'follows the release notes' with a caveat about the doc gap as of 2026-04-23. - Gemini MEDIUM: KNOWN_KEYS dedup -> won't fix (project-wide pattern, same answer as PR #757/#763). Plus fmt drift caught by CI (cargo fmt forgotten after round-1 edit; same lesson as PR #758/#762).
* fix(claude-code): catch up to v2.1.118 (mcp_tool hook type) Closes #764. Triage of Claude Code v2.1.117 -> v2.1.118. The release added `type: "mcp_tool"` so hooks can invoke MCP tools directly. Without this fix, agnix would false-positive CC-HK-016 (unknown hook type) on every v2.1.118+ user following the release notes. Added `"mcp_tool"` to BOTH allow-lists in crates/agnix-core/src/rules/hooks/helpers.rs: - `valid_types` (lines 607, 973): used by CC-HK-016 strict check - `known_non_command` (lines 532, 974): used to flag async on non-command hook types Plus a regression test (`test_cc_hk_016_mcp_tool_type_valid`) asserting `mcp_tool` doesn't trigger CC-HK-016. Doc gap caveat: as of 2026-04-23 the docs at code.claude.com/docs/en/hooks only list 4 types (command, prompt, agent, http). The v2.1.118 release notes explicitly mention mcp_tool and are authoritative until the docs catch up. Other v2.1.118 changes need no agnix change: - Vim visual mode, /cost+/stats -> /usage, custom themes (UI/CLI) - Plugin themes/ directory: agnix's plugin.rs has no strict fields check, no false positive - DISABLE_UPDATES env var: not a config file - wslInheritsWindowsSettings policy key: settings.json field; agnix doesn't validate settings.json fields directly - autoMode.* '\$defaults' token: settings.json field - claude plugin tag (CLI), OAuth/credential bug fixes (runtime) Documentation: - Bumped verified_on to 2026-04-23 for all 25 CC-HK-* rules in knowledge-base/rules.json + synced crates/agnix-rules/rules.json - Regenerated website/docs/rules/generated/*.md Verified locally: - cargo clippy --workspace --all-targets --all-features -- -D warnings clean - cargo test -p agnix-core --lib: passes (+1 new regression test) * fix: expand mcp_tool to typed Hook enum + strengthen test (round 1 review) Reviewer feedback (PR #767 round 1): - Copilot CRITICAL: typed Hook enum still rejected mcp_tool, so even with the raw-JSON allow-list fix, CC-HK-012 (schema mismatch) would fire on every mcp_tool hook. Added Hook::McpTool variant with tool, if, timeout fields (per release notes). Updated all match arms (command(), prompt(), type_name(), the per-variant typed-validation block in rules/hooks/mod.rs); added is_mcp_tool() for symmetry. - Copilot: test was vacuous (only checked CC-HK-016 absence; would have passed with CC-HK-012 still firing). Strengthened to assert diagnostics.is_empty() (zero diagnostics of any rule). Now genuinely fails without the typed-enum addition. - Copilot: comment said 'follows the docs' but docs page hasn't been updated yet; reworded to 'follows the release notes' with a caveat about the doc gap as of 2026-04-23. - Gemini MEDIUM: KNOWN_KEYS dedup -> won't fix (project-wide pattern, same answer as PR #757/#763). Plus fmt drift caught by CI (cargo fmt forgotten after round-1 edit; same lesson as PR #758/#762).
Summary
Adds a brand-new validator for Claude Code output-style files (
.claude/output-styles/*.md). Surfaced during the v2.1.117 triage (#745, merged as #756) but kept out of that PR per the "no follow-ups" rule, since output-style is a green-field new file type rather than a fix.What's new
FileType::ClaudeOutputStylefor.claude/output-styles/*.mdand~/.claude/output-styles/*.mdOutputStyleSchema(parses 3 known fields:name,description,keep-coding-instructions) andOutputStyleValidatordescriptionis missing or whitespace-onlykeep-coding-instructionsis present but not a YAML boolean (rejects string"yes", number1,null){name, description, keep-coding-instructions}---is empty/whitespace-onlynameexceeds 64 charactersAll rules are non-autofix - none of the failure modes have an obvious correct rewrite.
Why now
Claude Code added the
keep-coding-instructionsfield in v2.1.94 and the output-style file type itself predates that. agnix has been silently ignoring these files. With the v2.1.117 catch-up complete (PR #756), this fills the last gap surfaced by that triage.Total counts
Source
https://code.claude.com/docs/en/output-styles (verified 2026-04-22;
keep-coding-instructionsfield added in Claude Code v2.1.94)Test plan
cargo check --workspacecleancargo test -p agnix-core --lib: 3440 passed (+32 new tests for this validator)cargo test --workspace --all-targets: every suite okcargo clippy --workspace --all-targets --all-features -- -D warningscleancargo test -p agnix-rules --tests: 47 + 4 parity tests pass (rules.json byte-equality + VALIDATION-RULES.md parity)Generated docs ride-along
docs_website_paritytest enforcesmarkdown_count == rules.total_rules, which forces re-runningscripts/generate-docs-rules.pyon anyrules.jsonedit. The regen picked up some pre-existing drift in the website's generated docs:cc-ag-{009,010,011,014,019}.mdandcc-sk-018.md:verified_onupdated to 2026-04-22 (matches PR fix(claude-code): catch up to v2.1.117 + tighten clippy gate #756's rules.json edits)cdx-pl-{005..013}.md: minorgood_example/bad_examplesnippet driftThese are pre-existing drift, not new content for this PR. They have to ride along because the parity test would block this PR otherwise.