Skip to content

feat(claude-code): add output-style validator (CC-OS-001..005) - #757

Merged
avifenesh merged 3 commits into
mainfrom
feat/output-style-validator
Apr 22, 2026
Merged

feat(claude-code): add output-style validator (CC-OS-001..005)#757
avifenesh merged 3 commits into
mainfrom
feat/output-style-validator

Conversation

@avifenesh

Copy link
Copy Markdown
Collaborator

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

  • New FileType::ClaudeOutputStyle for .claude/output-styles/*.md and ~/.claude/output-styles/*.md
  • New OutputStyleSchema (parses 3 known fields: name, description, keep-coding-instructions) and OutputStyleValidator
  • 5 new validation rules:
ID Sev Normative Triggers when
CC-OS-001 LOW SHOULD description is missing or whitespace-only
CC-OS-002 HIGH MUST keep-coding-instructions is present but not a YAML boolean (rejects string "yes", number 1, null)
CC-OS-003 MEDIUM SHOULD Top-level frontmatter key not in {name, description, keep-coding-instructions}
CC-OS-004 MEDIUM SHOULD Body after closing --- is empty/whitespace-only
CC-OS-005 LOW SHOULD name exceeds 64 characters

All rules are non-autofix - none of the failure modes have an obvious correct rewrite.

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.

Total counts

  • Rules: 399 -> 404
  • Built-in validators: 73 -> 74
  • FileType variants: 42 -> 43

Source

https://code.claude.com/docs/en/output-styles (verified 2026-04-22; keep-coding-instructions field added in Claude Code v2.1.94)

Test plan

  • cargo check --workspace clean
  • cargo test -p agnix-core --lib: 3440 passed (+32 new tests for this validator)
  • cargo test --workspace --all-targets: every suite ok
  • cargo clippy --workspace --all-targets --all-features -- -D warnings clean
  • cargo test -p agnix-rules --tests: 47 + 4 parity tests pass (rules.json byte-equality + VALIDATION-RULES.md parity)
  • Pre-push hook (clippy + test) passes
  • CI: Verify Changelog
  • CI: Security Audit
  • CI: Tests (ubuntu, macos, windows)
  • CI: claude-review
  • Address all reviewer comments

Generated docs ride-along

docs_website_parity test enforces markdown_count == rules.total_rules, which forces re-running scripts/generate-docs-rules.py on any rules.json edit. The regen picked up some pre-existing drift in the website's generated docs:

These are pre-existing drift, not new content for this PR. They have to ride along because the parity test would block this PR otherwise.

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
Copilot AI review requested due to automatic review settings April 22, 2026 23:22
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.

@gemini-code-assist gemini-code-assist Bot left a comment

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.

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",

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.

medium

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.

Comment on lines +128 to +130
/// The hyphenated key `keep-coding-instructions` is extracted manually from
/// the `serde_yaml::Mapping` because `#[derive(Deserialize)]` field names can
/// not contain hyphens.

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.

medium

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.

Comment on lines +131 to +167
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)
}

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.

medium

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())),
    }
}

Copilot AI left a comment

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.

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::ClaudeOutputStyle plus detection/registry wiring and a new OutputStyleValidator implementing 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.

Comment thread website/src/data/siteData.json Outdated
Comment on lines 2 to 4
"totalRules": 404,
"categoryCount": 36,
"autofixCount": 126,

Copilot AI Apr 22, 2026

Copy link

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment thread knowledge-base/VALIDATION-RULES.md Outdated
Comment on lines +898 to +899
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`.

Copilot AI Apr 22, 2026

Copy link

Choose a reason for hiding this comment

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

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).

Copilot uses AI. Check for mistakes.
Comment on lines 196 to +198
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)",

Copilot AI Apr 22, 2026

Copy link

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment thread knowledge-base/rules.json
Comment on lines +2367 to +2371
"id": "CC-OS-001",
"name": "Output Style Missing Description",
"severity": "LOW",
"category": "claude-output-styles",
"evidence": {

Copilot AI Apr 22, 2026

Copy link

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +2367 to +2371
"id": "CC-OS-001",
"name": "Output Style Missing Description",
"severity": "LOW",
"category": "claude-output-styles",
"evidence": {

Copilot AI Apr 22, 2026

Copy link

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +92 to +96
| [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 |

Copilot AI Apr 22, 2026

Copy link

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
@avifenesh

Copy link
Copy Markdown
Collaborator Author

Per-comment reply table for round 1

# Reviewer File Status Resolution
1 Gemini output_style.rs:86 (CC-OS-002 overload for parse errors) Fixed Added new rule CC-OS-006 [HIGH] Invalid Output Style Frontmatter Syntax. Parse errors (unclosed `---`, invalid YAML, etc.) now emit CC-OS-006 instead of overloading CC-OS-002. CC-OS-002 is back to its narrow scope: `keep-coding-instructions` non-bool. Added 3 tests. Total rules: 404 -> 405.
2 Gemini schemas/output_style.rs:130 (incorrect comment) Fixed Refactored as below (#3) — comment removed since it no longer applies.
3 Gemini schemas/output_style.rs:167 (manual extraction verbose) Fixed Schema now uses `#[derive(Deserialize)]` with `#[serde(rename = "keep-coding-instructions")]`. Eliminated the manual mapping extraction. Subtle catch: serde collapses YAML `null` into `Option::None`, which would break CC-OS-002's "present-but-null" detection. Added a tiny custom `deserialize_with` helper that wraps the value as `Some(Value::Null)` for YAML null and lets `#[serde(default)]` produce `None` for absent fields. Net code: same behavior, ~25 LOC simpler than the manual mapping walk.
4 Copilot siteData.json:4 (`categoryCount` 36) Fixed (regenerated) Added `claude-output-styles` to the `categories` map in `rules.json` (per #7/#8 below); re-running `scripts/generate-docs-rules.py` regenerated `siteData.json` with the new category count.
5 Copilot VALIDATION-RULES.md:899 (wording) Fixed Updated to: "Output-style files customise Claude's response tone/format. The `keep-coding-instructions` frontmatter field was added in Claude Code v2.1.94." Now matches the PR description.
6 Copilot cli_integration.rs:198 (stale comment "~385 rules") Fixed Updated to "~405 rules documented in VALIDATION-RULES.md; the upper bound has ~10% headroom against accidental explosion."
7 Copilot rules.json:2371 (missing categories entry) Fixed Added `claude-output-styles: { prefix: "CC-OS", count: 6, description: "Claude Code Output Styles rules" }` to the categories map.
8 Copilot crates/agnix-rules/rules.json:2371 (same as #7) Fixed Same edit as #7; `cp`'d to keep parity.
9 Copilot website/docs/rules/index.md:96 (raw slug not friendly label) Fixed (regenerated) The docs generator (`scripts/generate-docs-rules.py`) reads category labels from `rules.json`'s categories map. With the new `claude-output-styles` entry (per #7), the regenerated index uses "Claude Output Styles" as the friendly label.

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).
@avifenesh
avifenesh merged commit fc2c2ca into main Apr 22, 2026
9 checks passed
@avifenesh
avifenesh deleted the feat/output-style-validator branch April 22, 2026 23:46
avifenesh added a commit that referenced this pull request Apr 23, 2026
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.
avifenesh added a commit that referenced this pull request Apr 23, 2026
…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.
avifenesh added a commit that referenced this pull request Apr 23, 2026
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
avifenesh added a commit that referenced this pull request Apr 23, 2026
…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.
avifenesh added a commit that referenced this pull request Apr 23, 2026
…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).
avifenesh added a commit that referenced this pull request Apr 23, 2026
* 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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants