use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Tool {
Cursor,
Claude,
Copilot,
}
impl Tool {
pub fn label(&self) -> &'static str {
match self {
Tool::Cursor => "Cursor",
Tool::Claude => "Claude Code",
Tool::Copilot => "GitHub Copilot",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Subsystem {
Instructions,
Prompts,
Agents,
ChatModes,
Skills,
}
impl Subsystem {
pub fn label(&self) -> &'static str {
match self {
Subsystem::Instructions => "instructions",
Subsystem::Prompts => "prompts",
Subsystem::Agents => "agents",
Subsystem::ChatModes => "chatmodes",
Subsystem::Skills => "skills",
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Scope {
pub globs: Vec<String>,
pub always_apply: bool,
pub path_prefix: Option<String>,
pub model: Option<String>,
pub tools: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Source {
pub tool: Tool,
pub subsystem: Subsystem,
pub path: PathBuf,
pub label: String,
pub name: Option<String>,
pub description: Option<String>,
pub scope: Scope,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Statement {
pub source_index: usize,
pub text: String,
pub byte_start: usize,
pub byte_end: usize,
pub line: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Rule {
pub source_index: usize,
pub text: String,
pub tokens: usize,
pub fingerprint: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Polarity {
Prefer,
Forbid,
Allow,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "kind", content = "scope")]
pub enum Axis {
Naming(NamingScope),
Indentation,
QuoteStyle,
PackageManager,
AsyncStyle,
TestColocation,
TypeStrictness,
CommentDensity,
ErrorHandling,
ImportStyle,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum NamingScope {
Variables,
Functions,
Types,
Constants,
Files,
Any,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "v", content = "x")]
pub enum AxisValue {
CamelCase,
SnakeCase,
PascalCase,
KebabCase,
ScreamingSnakeCase,
Tabs,
Spaces2,
Spaces4,
Spaces8,
SingleQuote,
DoubleQuote,
Backtick,
Npm,
Pnpm,
Yarn,
Bun,
AsyncAwait,
PromiseChain,
Callbacks,
BesideSource,
DedicatedDir,
Strict,
Loose,
Heavy,
Minimal,
Throw,
ResultType,
NamedImport,
DefaultImport,
NamespaceImport,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Condition {
pub raw: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Assertion {
pub statement_index: usize,
pub axis: Axis,
pub value: AxisValue,
pub polarity: Polarity,
pub condition: Option<Condition>,
pub confidence: f32,
pub origin: ExtractionOrigin,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ExtractionOrigin {
Pattern,
Embedding,
CrossEncoder,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Conflict {
pub kind: ConflictKind,
pub left: usize,
pub right: usize,
pub axis: Option<Axis>,
pub note: String,
pub severity: Severity,
pub confidence: f32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ConflictKind {
Duplicate,
Clash,
PolarityConflict,
AgentToolMismatch,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Severity {
Low,
High,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContextBundle {
pub root: PathBuf,
pub sources: Vec<Source>,
pub statements: Vec<Statement>,
pub assertions: Vec<Assertion>,
pub rules: Vec<Rule>,
pub conflicts: Vec<Conflict>,
pub total_tokens: usize,
pub stale_tokens: usize,
}
impl ContextBundle {
pub fn waste_pct(&self) -> u32 {
if self.total_tokens == 0 {
return 0;
}
((self.stale_tokens as f64 / self.total_tokens as f64) * 100.0).round() as u32
}
pub fn high_severity_conflicts(&self) -> impl Iterator<Item = &Conflict> {
self.conflicts
.iter()
.filter(|c| c.severity == Severity::High)
}
}