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
//! Cross-Stack Kaizen Example
//!
//! Demonstrates `pmat kaizen --cross-stack`, which scans all batuta stack crates
//! in a single invocation, applies fixes per-crate, and produces a unified report.
//!
//! Run with: `cargo run --example kaizen_cross_stack_demo`
//!
//! # Features Demonstrated
//!
//! 1. Cross-stack discovery via `discover_workspace_crates()`
//! 2. Per-crate scanning (clippy, fmt, comply, defects, github issues)
//! 3. Per-crate auto-fix with cargo check verification
//! 4. Per-crate commit and push with branch detection
//! 5. Grouped report output (text, markdown, JSON)
//!
//! # CLI Usage
//!
//! ```bash
//! # Dry-run scan across entire stack
//! pmat kaizen --cross-stack --dry-run
//!
//! # Cross-stack with auto-fix, no issues filed
//! pmat kaizen --cross-stack --no-issues
//!
//! # Cross-stack with push per-crate
//! pmat kaizen --cross-stack --push
//!
//! # JSON output for CI/CD
//! pmat kaizen --cross-stack --dry-run -f json
//!
//! # Markdown report to file
//! pmat kaizen --cross-stack --dry-run -f markdown -o kaizen-report.md
//!
//! # Skip specific scanners
//! pmat kaizen --cross-stack --skip-github --skip-defects --dry-run
//!
//! # Single-project kaizen (unchanged behavior)
//! pmat kaizen --dry-run
//! ```
//!
//! # Cross-Stack Discovery
//!
//! Crate discovery uses a 5-priority chain:
//! 1. Explicit `--crates` paths (not yet exposed in kaizen)
//! 2. `Cargo.toml` workspace members
//! 3. `batuta oracle --local` (batuta stack auto-discovery)
//! 4. `.pmat/workspace.toml` siblings
//! 5. Single-crate fallback
//!
//! # Report Grouping
//!
//! In cross-stack mode, findings are grouped by crate name:
//!
//! ```text
//! === Kaizen Cross-Stack Report ===
//!
//! --- pmat (12 findings, 8 fixed) ---
//! [FIXED] [MED ] clippy::needless_return src/lib.rs
//! [TODO ] [LOW ] rustfmt::unformatted src/main.rs
//!
//! --- trueno (3 findings, 1 fixed) ---
//! [FIXED] [LOW ] rustfmt::unformatted src/lib.rs
//! [AGENT] [HIGH] defect::RUST-UNWRAP-001 src/tensor.rs
//! ```
use std::process::Command;
fn main() {
println!("=== pmat kaizen --cross-stack Demo ===\n");
// Single-project dry-run
println!("1. Single-project kaizen (default):");
let output = Command::new("pmat")
.args(["kaizen", "--dry-run", "--skip-github", "--skip-defects"])
.output();
match output {
Ok(o) => {
let stdout = String::from_utf8_lossy(&o.stdout);
let stderr = String::from_utf8_lossy(&o.stderr);
for line in stderr.lines().take(3) {
println!(" {line}");
}
for line in stdout.lines().take(5) {
println!(" {line}");
}
}
Err(e) => println!(" pmat not available: {e}"),
}
println!();
// Cross-stack dry-run
println!("2. Cross-stack kaizen (--cross-stack):");
let output = Command::new("pmat")
.args([
"kaizen",
"--cross-stack",
"--dry-run",
"--skip-github",
"--skip-defects",
])
.output();
match output {
Ok(o) => {
let stderr = String::from_utf8_lossy(&o.stderr);
let stdout = String::from_utf8_lossy(&o.stdout);
for line in stderr.lines().take(5) {
println!(" {line}");
}
for line in stdout.lines().take(10) {
println!(" {line}");
}
}
Err(e) => println!(" pmat not available: {e}"),
}
println!();
// JSON output
println!("3. Cross-stack JSON output:");
let output = Command::new("pmat")
.args([
"kaizen",
"--cross-stack",
"--dry-run",
"--skip-github",
"--skip-defects",
"--skip-clippy",
"--skip-comply",
"-f",
"json",
])
.output();
match output {
Ok(o) => {
let stdout = String::from_utf8_lossy(&o.stdout);
// Parse and show summary
if let Ok(json) = serde_json::from_str::<serde_json::Value>(&stdout) {
let findings = json["findings"].as_array().map(|a| a.len()).unwrap_or(0);
let crates = json["crates_scanned"]
.as_array()
.map(|a| a.len())
.unwrap_or(0);
println!(" Crates: {crates}, Findings: {findings}");
}
}
Err(e) => println!(" pmat not available: {e}"),
}
println!("\nDone.");
}