agent_rules_tool/lib.rs
1#![warn(missing_docs)]
2
3//! Lint and migrate AI agent rule files per
4//! [agent-rules-spec](https://github.com/rameshsunkara/agent-rules-spec).
5//!
6//! # Quick start
7//!
8//! ```
9//! use agent_rules_tool::{lint_string, migrate_string, LintOptions, MigrateOptions};
10//! use agent_rules_tool::format::RuleFormat;
11//!
12//! # fn example(content: &str) -> Result<(), agent_rules_tool::Error> {
13//! let report = lint_string(content, &LintOptions::default())?;
14//! assert!(report.valid);
15//!
16//! let migrated = migrate_string(
17//! content,
18//! &MigrateOptions {
19//! from: RuleFormat::Cursor,
20//! to: RuleFormat::Agents,
21//! ..Default::default()
22//! },
23//! )?;
24//! # let _ = migrated;
25//! # Ok(())
26//! # }
27//! ```
28//!
29//! # Modules
30//!
31//! - [`lint`] — validate rule content against schema and RFC semantics
32//! - [`migrate`] — convert between tool-native and canonical formats
33//! - [`mod@format`] — format detection and tool directory defaults
34//! - [`spec`] — vendored spec URLs, embedded schema, and constants
35//! - [`report`] — YAML lint report serialization
36
37pub mod cli;
38pub mod discover;
39pub mod error;
40pub mod format;
41pub mod io;
42pub mod lint;
43pub mod migrate;
44pub mod parse;
45pub mod report;
46pub mod schema;
47pub mod spec;
48pub mod walk;
49
50/// Error type returned by library operations.
51pub use error::Error;
52/// Rule format identifiers for migration and detection.
53pub use format::{RuleFormat, RuleFormatArg};
54/// Lint a single rule string or directory of rule files.
55pub use lint::{exceeds_threshold, lint_directory, lint_string};
56/// Migrate rule files between formats.
57pub use migrate::{
58 InputRule, MigrateOptions, MigrateResult, MigrateSummary, MigrateWarning,
59 build_inputs_from_dirs, migrate_paths, migrate_string,
60};
61
62use serde::{Deserialize, Serialize};
63use std::path::PathBuf;
64
65/// Violation severity for lint results and exit-code thresholds.
66#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
67pub enum Severity {
68 /// Non-fatal issue; fails only when threshold is [`Severity::Warn`].
69 Warn,
70 /// Schema or RFC violation.
71 Error,
72}
73
74impl Severity {
75 /// Returns `true` when `self` is at or above `threshold`.
76 pub fn meets_threshold(self, threshold: Self) -> bool {
77 self >= threshold
78 }
79}
80
81/// A single lint finding with a citation into the spec or schema.
82#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct Violation {
84 /// How severe this finding is.
85 pub severity: Severity,
86 /// Frontmatter field or path associated with the violation.
87 pub field: String,
88 /// Human-readable description of the problem.
89 pub message: String,
90 /// URL to the relevant RFC section or schema document.
91 pub spec_ref: &'static str,
92}
93
94/// Options controlling [`lint_string`] and [`lint_directory`].
95#[derive(Debug, Clone)]
96pub struct LintOptions {
97 /// Minimum severity that should fail a lint run (CLI exit code).
98 pub severity_threshold: Severity,
99 /// Filename stem used for semantic checks such as `name` vs filename.
100 pub filename_hint: Option<String>,
101}
102
103impl Default for LintOptions {
104 fn default() -> Self {
105 Self {
106 severity_threshold: Severity::Error,
107 filename_hint: None,
108 }
109 }
110}
111
112/// Result of linting one rule file's content.
113#[derive(Debug, Clone)]
114pub struct LintReport {
115 /// All violations found in the content.
116 pub violations: Vec<Violation>,
117 /// `true` when `violations` is empty.
118 pub valid: bool,
119}
120
121/// Lint result for one file within a directory scan.
122#[derive(Debug, Clone)]
123pub struct FileLintResult {
124 /// Path relative to the directory passed to [`lint_directory`].
125 pub path: PathBuf,
126 /// Lint outcome for this file.
127 pub report: LintReport,
128}
129
130/// Vendored [agent-rules-spec](https://github.com/rameshsunkara/agent-rules-spec) index manifest.
131#[derive(Debug, Clone, Deserialize)]
132pub struct SpecIndex {
133 /// Upstream repository URL.
134 pub repository: String,
135 /// Pinned upstream git commit.
136 pub commit: String,
137 /// Vendored file manifest entries.
138 pub files: Vec<SpecIndexEntry>,
139}
140
141/// One entry in [`SpecIndex::files`].
142#[derive(Debug, Clone, Deserialize)]
143pub struct SpecIndexEntry {
144 /// Path under `spec/` in this repository.
145 pub vendored: String,
146 /// Corresponding path in the upstream repository.
147 pub upstream: String,
148}
149
150/// Load the embedded spec index from [`spec::EMBEDDED_INDEX`].
151pub fn load_spec_index() -> Result<SpecIndex, Error> {
152 serde_saphyr::from_str(spec::EMBEDDED_INDEX).map_err(|e| Error::Yaml(e.to_string()))
153}