1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
#![cfg_attr(coverage_nightly, coverage(off))]
//! Drift Detector - Detect documentation/code drift
//!
//! Detects when documentation (README, help text) diverges from actual code,
//! preventing the issue reported in GitHub #118 where `pmat mcp` was documented
//! but didn't exist.
//!
//! # Architecture (Toyota Way - Poka-yoke)
//!
//! Error-proofing through automated validation:
//! - Pre-commit hook validates all documentation references
//! - CI/CD blocks PRs with drift
//! - Build-time validation of examples
//!
//! # References
//!
//! - Specification: docs/specifications/unified-cli-mcp-help-integration.md
//! - GitHub Issue: #118
//! - Toyota Way: Poka-yoke (error-proofing), Jidoka (built-in quality)
use crate::cli::registry::CommandRegistry;
use regex::Regex;
use std::collections::HashSet;
use std::path::Path;
/// Drift detection errors
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DriftError {
/// Command referenced in docs doesn't exist
NonExistentCommand {
mentioned: String,
file: String,
line: usize,
suggestion: Option<String>,
},
/// Example in docs doesn't work
InvalidExample {
example: String,
file: String,
line: usize,
reason: String,
},
/// Command exists but not documented
UndocumentedCommand { command: String },
/// Deprecated command still documented without warning
DeprecatedWithoutWarning {
command: String,
file: String,
line: usize,
},
/// Broken link in documentation
BrokenLink {
url: String,
file: String,
line: usize,
},
}
impl std::fmt::Display for DriftError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NonExistentCommand {
mentioned,
file,
line,
suggestion,
} => {
write!(
f,
"{}:{}: command '{}' doesn't exist",
file, line, mentioned
)?;
if let Some(s) = suggestion {
write!(f, " (did you mean '{}'?)", s)?;
}
Ok(())
}
Self::InvalidExample {
example,
file,
line,
reason,
} => {
write!(
f,
"{}:{}: invalid example '{}': {}",
file, line, example, reason
)
}
Self::UndocumentedCommand { command } => {
write!(f, "command '{}' is not documented", command)
}
Self::DeprecatedWithoutWarning {
command,
file,
line,
} => {
write!(
f,
"{}:{}: deprecated command '{}' documented without deprecation notice",
file, line, command
)
}
Self::BrokenLink { url, file, line } => {
write!(f, "{}:{}: broken link '{}'", file, line, url)
}
}
}
}
impl std::error::Error for DriftError {}
/// Drift detection result
#[derive(Debug)]
pub struct DriftReport {
/// Detected errors
pub errors: Vec<DriftError>,
/// Commands found in documentation
pub documented_commands: HashSet<String>,
/// Commands in registry but not documented
pub undocumented_commands: HashSet<String>,
/// Total commands in registry
pub total_commands: usize,
/// Documentation coverage percentage
pub coverage: f64,
}
impl DriftReport {
/// Check if the report has any errors
pub fn has_errors(&self) -> bool {
!self.errors.is_empty()
}
/// Get error count
pub fn error_count(&self) -> usize {
self.errors.len()
}
/// Format as human-readable report
pub fn to_string_report(&self) -> String {
let mut report = String::new();
report.push_str("Drift Detection Report\n");
report.push_str("======================\n\n");
report.push_str(&format!(
"Commands: {} total, {} documented ({:.1}% coverage)\n",
self.total_commands,
self.documented_commands.len(),
self.coverage
));
if self.has_errors() {
report.push_str(&format!("\n❌ {} errors detected:\n\n", self.errors.len()));
for error in &self.errors {
report.push_str(&format!(" • {}\n", error));
}
} else {
report.push_str("\n✅ No drift detected\n");
}
if !self.undocumented_commands.is_empty() {
report.push_str("\n⚠️ Undocumented commands:\n");
for cmd in &self.undocumented_commands {
report.push_str(&format!(" • {}\n", cmd));
}
}
report
}
}
/// Detects documentation drift
pub struct DriftDetector {
registry: CommandRegistry,
/// Regex to find pmat command references
command_regex: Regex,
/// Regex to find code blocks
code_block_regex: Regex,
}
// Analysis methods (new, detect_in_file, detect_in_content, generate_report, helpers)
include!("drift_detector_analysis.rs");
// Tests
include!("drift_detector_tests.rs");