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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **Kiro CLI 1.28.3 -> 2.0.1 doc refresh** (#751) - re-verified all 51 Kiro rules (KIRO-* steering, KR-AG-* agents, KR-HK-* hooks, KR-MCP-* MCP, KR-PW-* powers, KR-SK-* skills) against the upstream `https://kiro.dev/changelog/cli/` HTML changelog and the relevant pages under `https://kiro.dev/docs/`. The 1.28 -> 2.0 jump was platform expansion (Windows native, headless mode `KIRO_API_KEY`, TUI graduation, granular tool trust, session settings tool, simplified agent creation) - none of those add fields to any of the 5 Kiro file types agnix validates. Two ambiguous changelog mentions (`availableAgents`/`trustedAgents` from v1.25, `knowledgeIndex` resource type from v1.23/v1.24) were verified against `kiro.dev/docs/cli/custom-agents/configuration-reference` and confirmed NOT to appear in `.kiro/agents/*.json` field lists - they're either global settings or were never shipped as documented config. No code changes required. Bumped `verified_on` for all 51 Kiro rules to 2026-04-22; bumped `RESEARCH-TRACKING.md` "Last Reviewed" for Kiro CLI from 2026-02-05 to 2026-04-22 (also added `.kiro/powers/*/POWER.md` to the Config Format column - it was missing). Discovered: agnix has FIVE Kiro validators, not four - `kiro_power.rs` (KR-PW-001..008) for `.kiro/powers/*/POWER.md` was undocumented in earlier triage briefings.

### Fixed
- **Claude Code v2.1.117 -> v2.1.118 catch-up** (#764) - upstream v2.1.118 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 that follows the release notes. Added `"mcp_tool"` to both `valid_types` arrays AND `known_non_command` arrays in `crates/agnix-core/src/rules/hooks/helpers.rs` (4 sites total — two for the strict-fields pass, two for the async-on-non-command pass) plus a regression test asserting `mcp_tool` doesn't trigger CC-HK-016. Note: as of 2026-04-23 the docs at `code.claude.com/docs/en/hooks` only list 4 types; the v2.1.118 release notes are authoritative for the new `mcp_tool` type until the docs catch up. Bumped `verified_on` for all 25 CC-HK-* rules to 2026-04-23. Other v2.1.118 changes (vim visual mode, `/cost`+`/stats`->`/usage`, custom themes, plugin `themes/` directory, `DISABLE_UPDATES`, `wslInheritsWindowsSettings` policy key, `autoMode.*` `"$defaults"` token, `claude plugin tag`, OAuth and credential bug fixes) need no agnix changes — they're runtime/UI/CLI behavior or settings.json fields agnix doesn't validate directly.
- **GitHub Copilot v0.42.2 -> v0.43.0 catch-up** (#748) - upstream PR `microsoft/vscode-copilot-chat#4964` (in v0.43.0) makes the `description` frontmatter field on `.instructions.md` files a user-visible feature in the VS Code Chat Customizations UI. Without this fix, agnix would false-positive **COP-004** (unknown frontmatter key) and offer an auto-fix to delete the line on every v0.43.0+ user that follows the docs. Added `"description"` to `KNOWN_KEYS` and a typed `description: Option<String>` field on `CopilotScopedSchema` in `crates/agnix-core/src/schemas/copilot.rs` plus two regression tests (parse + unknown-key check). Documentation: bumped `verified_on` for all 25 COP-* rules to 2026-04-22; bumped `RESEARCH-TRACKING.md` "Last Reviewed" for GitHub Copilot from 2026-02-05 to 2026-04-22. Verified other v0.43.0 changes (hooks/plugins UI wiring, AGENTS.md/CLAUDE.md multi-root discovery fix, internal NES/session/telemetry refactors) need no agnix changes - they're UI/runtime only with no schema impact.
- **OpenCode v1.3.13 -> v1.14.20 catch-up** (#752) - upstream PR `anomalyco/opencode#13748` (in v1.3.16) added the `tui.mouse: bool` config key to disable terminal mouse capture. Without this fix, agnix would false-positive **OC-TUI-001** (unknown TUI key) on any current OpenCode config that includes `{"tui": {"mouse": false}}`. Added `"mouse"` to `KNOWN_TUI_KEYS` in `crates/agnix-core/src/schemas/opencode.rs` plus two regression tests (`mouse: false` and `mouse: true`). Documentation: bumped `verified_on` for all 46 OC-* rules to 2026-04-22; bumped `RESEARCH-TRACKING.md` "Last Reviewed" for OpenCode from 2026-02-05 to 2026-04-22. Verified other schema additions in the bump window (MCP `oauth_redirect_uri` in v1.4.3, `compaction.autocontinue` in v1.4.4, `compaction.preserve_recent_tokens` in v1.14.19, LLM Gateway provider in v1.4.9) need no agnix changes - they extend optional sub-fields agnix doesn't enforce. Note: as of v1.14.x, upstream `config.ts` deprecates the in-config `tui` block in favour of a separate `tui.json` file; agnix's TUI validator continues to help users on v1.3-v1.4 and users who keep the deprecated form. Migrating to validate `tui.json` is a future enhancement, not a blocker for this triage.
- **Codex CLI rust-v0.118.0 -> rust-v0.122.0 catch-up** (#747) - upstream `config-schema.json` (verified 2026-04-22) gained 8 new 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`). Without this fix, agnix would false-positive **CDX-004** (unknown TOML top-level key, primary path for `.codex/config.toml`) and CDX-CFG-006 (its JSON/YAML equivalent) on any current Codex config that uses them. Added all 8 to `KNOWN_TOP_LEVEL_KEYS` in `crates/agnix-core/src/schemas/codex.rs` (the TOML path used by CDX-004) AND `KNOWN_CONFIG_TOP_LEVEL_KEYS` in `crates/agnix-core/src/rules/codex.rs` (the JSON/YAML path). Also added `realtime` and `marketplaces` to `KNOWN_TABLE_KEYS` since both can appear as TOML tables (`[realtime]`, `[[marketplaces]]`). Plus a regression test that runs an inline-table config and a section-table config and asserts zero CDX-004 diagnostics. Documentation: bumped `verified_on` for all 58 CDX-* rules to 2026-04-22; bumped `RESEARCH-TRACKING.md` "Last Reviewed" for Codex CLI from 2026-02-05 to 2026-04-22. Verified MCP server config (`env`, `env_vars`), AGENTS.md discovery refactor (#18035), filesystem deny-read globs (#15979), and PermissionRequest hooks (#17563) require no agnix changes - their upstream effects are runtime-only, additive, or already covered by existing validators.
Expand Down
8 changes: 4 additions & 4 deletions crates/agnix-core/src/rules/hooks/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -529,7 +529,7 @@ pub(super) fn validate_cc_hk_013_async_field(
content: &str,
diagnostics: &mut Vec<Diagnostic>,
) {
let known_non_command = ["prompt", "agent", "http"];
let known_non_command = ["prompt", "agent", "http", "mcp_tool"];
for_each_raw_hook(raw_value, |event, matcher_idx, hook_idx, hook| {
if hook.get("async").is_some() {
if let Some(hook_type) = hook.get("type").and_then(|t| t.as_str()) {
Expand Down Expand Up @@ -604,7 +604,7 @@ pub(super) fn validate_cc_hk_016_unknown_type(
content: &str,
diagnostics: &mut Vec<Diagnostic>,
) {
let valid_types = ["command", "prompt", "agent", "http"];
let valid_types = ["command", "prompt", "agent", "http", "mcp_tool"];

Copilot AI Apr 23, 2026

Copy link

Choose a reason for hiding this comment

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

mcp_tool is added to the raw JSON allow-list, but typed parsing still uses schemas::hooks::Hook (internally-tagged enum) which currently only supports command|prompt|agent|http. That means configs containing {"type":"mcp_tool"...} will still fail serde_json::from_value and emit CC-HK-012, so this change likely shifts the failure from CC-HK-016 to CC-HK-012 rather than fully supporting v2.1.118. Consider adding an mcp_tool variant to Hook (with at least a tool field and a #[serde(flatten)] _extra map if you want to avoid strict-field coupling) or otherwise adjusting typed parsing to accept this new type.

Suggested change
let valid_types = ["command", "prompt", "agent", "http", "mcp_tool"];
// Keep this list aligned with the currently supported typed hook variants.
// Until `schemas::hooks::Hook` accepts `mcp_tool`, raw validation must not
// allow it here or the config will fail later during typed deserialization.
let valid_types = ["command", "prompt", "agent", "http"];

Copilot uses AI. Check for mistakes.
for_each_raw_hook(raw_value, |event, matcher_idx, hook_idx, hook| {
if let Some(type_value) = hook.get("type") {
let hook_type_str;
Expand Down Expand Up @@ -970,8 +970,8 @@ pub(super) fn validate_all_raw_hooks(
let check_024 = config.is_rule_enabled("CC-HK-024");
let check_025 = config.is_rule_enabled("CC-HK-025");

let valid_types = ["command", "prompt", "agent", "http"];
let known_non_command = ["prompt", "agent", "http"];
let valid_types = ["command", "prompt", "agent", "http", "mcp_tool"];
let known_non_command = ["prompt", "agent", "http", "mcp_tool"];
Comment on lines +973 to +974

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 valid_types and known_non_command arrays are duplicated across multiple functions in this file (lines 532, 607, 973, and 974). Following the 'Single Source of Truth' general rule, these should be refactored into shared constants (e.g., at the module level) to ensure consistency and simplify future updates when new hook types are introduced.

References
  1. Instead of merging and deduplicating data from multiple sources, refactor the code to have a single source of truth to avoid duplication in the first place.

let valid_shells = ["bash", "powershell"];
let tool_events = HooksSchema::TOOL_EVENTS;

Expand Down
8 changes: 8 additions & 0 deletions crates/agnix-core/src/rules/hooks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,14 @@ impl Validator for HooksValidator {
// CC-HK-020 url validation, CC-HK-024 headers env vars.
// No additional typed validation needed here.
}
Hook::McpTool { .. } => {
// MCP tool hooks (added Claude Code v2.1.118) are
// validated at the raw JSON level via CC-HK-016
// (string allow-list). Schema details for required
// fields are not yet documented at
// code.claude.com/docs/en/hooks (as of 2026-04-23);
// re-tighten when upstream docs land.
}
}
}
}
Expand Down
34 changes: 34 additions & 0 deletions crates/agnix-core/src/rules/hooks/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2950,6 +2950,40 @@ fn test_cc_hk_016_agent_type_valid() {
assert_eq!(cc_hk_016.len(), 0);
}

#[test]
fn test_cc_hk_016_mcp_tool_type_valid() {
// Claude Code v2.1.118 added `type: "mcp_tool"` so hooks can invoke
// MCP tools directly. Without this fix, agnix would false-positive
// CC-HK-016 (unknown hook type) AND CC-HK-012 (typed-schema mismatch)
// on every v2.1.118+ user that uses the new type per the release notes.
// (As of 2026-04-23 the docs page at code.claude.com/docs/en/hooks
// still lists only 4 types; the release notes are authoritative until
// the docs catch up.)
let content = r#"{
"hooks": {
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "mcp_tool", "tool": "mcp__server__do_thing" }
]
}
]
}
}"#;

let diagnostics = validate(content);
// Strict assertion: NO diagnostics should fire on a valid mcp_tool hook.
// This catches both CC-HK-016 (string allow-list) and CC-HK-012
// (typed Hook enum schema match) -- without the typed-enum addition the
// raw JSON allow-list fix alone would not be enough.
assert!(
diagnostics.is_empty(),
"valid mcp_tool hook should produce zero diagnostics, got: {:?}",
diagnostics
);
}

#[test]
fn test_cc_hk_016_unknown_type() {
let content = r#"{
Expand Down
29 changes: 27 additions & 2 deletions crates/agnix-core/src/schemas/hooks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,23 @@ pub enum Hook {
#[serde(skip_serializing_if = "Option::is_none")]
timeout: Option<u64>,
},
/// MCP tool hook: invoke an MCP tool directly (Claude Code v2.1.118+).
/// Schema details for required/optional fields are not yet documented at
/// `code.claude.com/docs/en/hooks` (as of 2026-04-23); the typed schema
/// here uses `serde(other)` semantics so unknown future fields are
/// tolerated. Re-tighten when upstream docs land.
#[serde(rename = "mcp_tool")]
McpTool {
/// MCP tool reference, e.g. `mcp__<server>__<tool>` (per release notes)
#[serde(skip_serializing_if = "Option::is_none")]
tool: Option<String>,
/// Optional `if` filter (same as other types)
#[serde(skip_serializing_if = "Option::is_none")]
r#if: Option<String>,
/// Request timeout in seconds
#[serde(skip_serializing_if = "Option::is_none")]
timeout: Option<u64>,
},
}

impl SettingsSchema {
Expand All @@ -106,15 +123,17 @@ impl Hook {
pub fn command(&self) -> Option<&str> {
match self {
Hook::Command { command, .. } => command.as_deref(),
Hook::Prompt { .. } | Hook::Agent { .. } | Hook::Http { .. } => None,
Hook::Prompt { .. } | Hook::Agent { .. } | Hook::Http { .. } | Hook::McpTool { .. } => {
None
}
}
}

#[allow(dead_code)] // schema-level API; validation uses Validator trait
pub fn prompt(&self) -> Option<&str> {
match self {
Hook::Prompt { prompt, .. } | Hook::Agent { prompt, .. } => prompt.as_deref(),
Hook::Command { .. } | Hook::Http { .. } => None,
Hook::Command { .. } | Hook::Http { .. } | Hook::McpTool { .. } => None,
}
}

Expand Down Expand Up @@ -145,8 +164,14 @@ impl Hook {
Hook::Prompt { .. } => "prompt",
Hook::Agent { .. } => "agent",
Hook::Http { .. } => "http",
Hook::McpTool { .. } => "mcp_tool",
}
}

#[allow(dead_code)] // schema-level API; validation uses Validator trait
pub fn is_mcp_tool(&self) -> bool {
matches!(self, Hook::McpTool { .. })
}
}

impl HooksSchema {
Expand Down
50 changes: 25 additions & 25 deletions crates/agnix-rules/rules.json
Original file line number Diff line number Diff line change
Expand Up @@ -1332,7 +1332,7 @@
"source_urls": [
"https://code.claude.com/docs/en/hooks"
],
"verified_on": "2026-02-13",
"verified_on": "2026-04-23",
"applies_to": {
"tool": "claude-code"
},
Expand Down Expand Up @@ -1360,7 +1360,7 @@
"source_urls": [
"https://code.claude.com/docs/en/hooks"
],
"verified_on": "2026-02-04",
"verified_on": "2026-04-23",
"applies_to": {
"tool": "claude-code"
},
Expand All @@ -1387,7 +1387,7 @@
"source_urls": [
"https://code.claude.com/docs/en/hooks"
],
"verified_on": "2026-02-04",
"verified_on": "2026-04-23",
"applies_to": {
"tool": "claude-code"
},
Expand All @@ -1414,7 +1414,7 @@
"source_urls": [
"https://code.claude.com/docs/en/hooks"
],
"verified_on": "2026-02-04",
"verified_on": "2026-04-23",
"applies_to": {
"tool": "claude-code"
},
Expand Down Expand Up @@ -1442,7 +1442,7 @@
"source_urls": [
"https://code.claude.com/docs/en/hooks"
],
"verified_on": "2026-02-04",
"verified_on": "2026-04-23",
"applies_to": {
"tool": "claude-code"
},
Expand Down Expand Up @@ -1470,7 +1470,7 @@
"source_urls": [
"https://code.claude.com/docs/en/hooks"
],
"verified_on": "2026-02-04",
"verified_on": "2026-04-23",
"applies_to": {
"tool": "claude-code"
},
Expand All @@ -1497,7 +1497,7 @@
"source_urls": [
"https://code.claude.com/docs/en/hooks"
],
"verified_on": "2026-02-04",
"verified_on": "2026-04-23",
"applies_to": {
"tool": "claude-code"
},
Expand All @@ -1524,7 +1524,7 @@
"source_urls": [
"https://code.claude.com/docs/en/hooks"
],
"verified_on": "2026-02-04",
"verified_on": "2026-04-23",
"applies_to": {
"tool": "claude-code"
},
Expand All @@ -1551,7 +1551,7 @@
"source_urls": [
"https://github.com/anthropics/claude-code/tree/main/.claude/commands"
],
"verified_on": "2026-02-09",
"verified_on": "2026-04-23",
"applies_to": {
"tool": "claude-code"
},
Expand All @@ -1578,7 +1578,7 @@
"source_urls": [
"https://code.claude.com/docs/en/hooks"
],
"verified_on": "2026-02-04",
"verified_on": "2026-04-23",
"applies_to": {
"tool": "claude-code"
},
Expand Down Expand Up @@ -1606,7 +1606,7 @@
"source_urls": [
"https://code.claude.com/docs/en/hooks"
],
"verified_on": "2026-02-04",
"verified_on": "2026-04-23",
"applies_to": {
"tool": "claude-code"
},
Expand Down Expand Up @@ -1634,7 +1634,7 @@
"source_urls": [
"https://code.claude.com/docs/en/hooks"
],
"verified_on": "2026-02-04",
"verified_on": "2026-04-23",
"applies_to": {
"tool": "claude-code"
},
Expand All @@ -1661,7 +1661,7 @@
"source_urls": [
"https://code.claude.com/docs/en/hooks"
],
"verified_on": "2026-02-07",
"verified_on": "2026-04-23",
"applies_to": {
"tool": "claude-code"
},
Expand Down Expand Up @@ -1689,7 +1689,7 @@
"source_urls": [
"https://code.claude.com/docs/en/hooks"
],
"verified_on": "2026-02-07",
"verified_on": "2026-04-23",
"applies_to": {
"tool": "claude-code"
},
Expand Down Expand Up @@ -1717,7 +1717,7 @@
"source_urls": [
"https://code.claude.com/docs/en/hooks"
],
"verified_on": "2026-02-07",
"verified_on": "2026-04-23",
"applies_to": {
"tool": "claude-code"
},
Expand Down Expand Up @@ -1745,7 +1745,7 @@
"source_urls": [
"https://code.claude.com/docs/en/hooks"
],
"verified_on": "2026-02-07",
"verified_on": "2026-04-23",
"applies_to": {
"tool": "claude-code"
},
Expand Down Expand Up @@ -1773,7 +1773,7 @@
"source_urls": [
"https://code.claude.com/docs/en/hooks"
],
"verified_on": "2026-02-07",
"verified_on": "2026-04-23",
"applies_to": {
"tool": "claude-code"
},
Expand Down Expand Up @@ -1801,7 +1801,7 @@
"source_urls": [
"https://code.claude.com/docs/en/hooks"
],
"verified_on": "2026-02-07",
"verified_on": "2026-04-23",
"applies_to": {
"tool": "claude-code"
},
Expand Down Expand Up @@ -1829,7 +1829,7 @@
"source_urls": [
"https://code.claude.com/docs/en/hooks"
],
"verified_on": "2026-02-14",
"verified_on": "2026-04-23",
"applies_to": {
"tool": "claude-code",
"file_types": [
Expand Down Expand Up @@ -1861,7 +1861,7 @@
"source_urls": [
"https://code.claude.com/docs/en/hooks"
],
"verified_on": "2026-03-28",
"verified_on": "2026-04-23",
"applies_to": {
"tool": "claude-code",
"file_types": [
Expand Down Expand Up @@ -1892,7 +1892,7 @@
"source_urls": [
"https://code.claude.com/docs/en/hooks"
],
"verified_on": "2026-03-28",
"verified_on": "2026-04-23",
"applies_to": {
"tool": "claude-code",
"file_types": [
Expand Down Expand Up @@ -1923,7 +1923,7 @@
"source_urls": [
"https://code.claude.com/docs/en/hooks"
],
"verified_on": "2026-03-28",
"verified_on": "2026-04-23",
"applies_to": {
"tool": "claude-code",
"file_types": [
Expand Down Expand Up @@ -1955,7 +1955,7 @@
"source_urls": [
"https://code.claude.com/docs/en/hooks"
],
"verified_on": "2026-03-28",
"verified_on": "2026-04-23",
"applies_to": {
"tool": "claude-code",
"file_types": [
Expand Down Expand Up @@ -1987,7 +1987,7 @@
"source_urls": [
"https://code.claude.com/docs/en/hooks"
],
"verified_on": "2026-03-28",
"verified_on": "2026-04-23",
"applies_to": {
"tool": "claude-code",
"file_types": [
Expand Down Expand Up @@ -2019,7 +2019,7 @@
"source_urls": [
"https://code.claude.com/docs/en/hooks"
],
"verified_on": "2026-03-28",
"verified_on": "2026-04-23",
"applies_to": {
"tool": "claude-code",
"file_types": [
Expand Down
Loading
Loading