Skip to content
This repository was archived by the owner on Apr 11, 2026. It is now read-only.

Commit 06b74c0

Browse files
z23ccclaude
andcommitted
merge: CE-inspired upgrades — Findings Schema, Knowledge Compounding, Brainstorm
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2 parents 494b83a + 3fcc750 commit 06b74c0

11 files changed

Lines changed: 1073 additions & 72 deletions

File tree

commands/flow-code/brainstorm.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
name: flow-code:brainstorm
3+
description: Explore and pressure-test an idea before planning
4+
argument-hint: "<idea or problem>"
5+
---
6+
7+
# IMPORTANT: This command MUST invoke the skill `flow-code-brainstorm`
8+
9+
The ONLY purpose of this command is to call the `flow-code-brainstorm` skill. You MUST use that skill now.
10+
11+
**User request:** $ARGUMENTS
12+
13+
Pass the user request to the skill. The skill handles all brainstorm logic.

flowctl/crates/flowctl-cli/src/commands/admin/review.rs

Lines changed: 112 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,10 @@ pub fn cmd_parse_findings(
184184
_register: bool,
185185
_source: String,
186186
) {
187+
use flowctl_core::review_protocol::{
188+
filter_by_confidence, AutofixClass, FindingOwner, ReviewFinding, Severity,
189+
};
190+
187191
// Read input from file or stdin
188192
let text = if file == "-" {
189193
use std::io::Read;
@@ -203,9 +207,10 @@ pub fn cmd_parse_findings(
203207
}
204208
};
205209

206-
let mut findings: Vec<serde_json::Value> = Vec::new();
210+
let mut findings: Vec<ReviewFinding> = Vec::new();
207211
let mut warnings: Vec<String> = Vec::new();
208-
let required_keys = ["title", "severity", "location", "recommendation"];
212+
// CE schema required keys
213+
let required_keys = ["severity", "file", "line", "confidence", "evidence"];
209214

210215
// Tiered extraction:
211216
// 1. <findings>...</findings> tag
@@ -239,12 +244,11 @@ pub fn cmd_parse_findings(
239244

240245
if let Some(raw) = raw_json {
241246
// Remove trailing commas before ] or }
242-
let cleaned = raw
243-
.replace(",]", "]")
244-
.replace(",}", "}");
247+
let cleaned = raw.replace(",]", "]").replace(",}", "}");
245248

246249
match serde_json::from_str::<serde_json::Value>(&cleaned) {
247250
Ok(serde_json::Value::Array(arr)) => {
251+
let mut raw_findings: Vec<ReviewFinding> = Vec::new();
248252
for (i, item) in arr.iter().enumerate() {
249253
if !item.is_object() {
250254
warnings.push(format!("Finding {} is not an object, skipping", i));
@@ -263,8 +267,90 @@ pub fn cmd_parse_findings(
263267
));
264268
continue;
265269
}
266-
findings.push(item.clone());
270+
271+
let severity = match item.get("severity").and_then(|v| v.as_str()) {
272+
Some("P0") | Some("critical") => Severity::P0,
273+
Some("P1") | Some("warning") => Severity::P1,
274+
Some("P2") => Severity::P2,
275+
_ => Severity::P3,
276+
};
277+
let category = item
278+
.get("category")
279+
.and_then(|v| v.as_str())
280+
.unwrap_or("general")
281+
.to_string();
282+
let description = item
283+
.get("description")
284+
.or_else(|| item.get("title"))
285+
.and_then(|v| v.as_str())
286+
.unwrap_or("")
287+
.to_string();
288+
let file_path = item.get("file").and_then(|v| v.as_str()).map(String::from);
289+
let line = item.get("line").and_then(|v| v.as_u64()).map(|n| n as u32);
290+
let confidence = item
291+
.get("confidence")
292+
.and_then(|v| v.as_f64())
293+
.unwrap_or(0.8);
294+
let autofix_class =
295+
match item.get("autofix_class").and_then(|v| v.as_str()) {
296+
Some("safe_auto") => AutofixClass::SafeAuto,
297+
Some("gated_auto") => AutofixClass::GatedAuto,
298+
Some("advisory") => AutofixClass::Advisory,
299+
_ => AutofixClass::Manual,
300+
};
301+
let owner = match item.get("owner").and_then(|v| v.as_str()) {
302+
Some("review-fixer") => FindingOwner::ReviewFixer,
303+
Some("downstream-resolver") => FindingOwner::DownstreamResolver,
304+
Some("human") => FindingOwner::Human,
305+
Some("release") => FindingOwner::Release,
306+
_ => FindingOwner::ReviewFixer,
307+
};
308+
let evidence = item
309+
.get("evidence")
310+
.and_then(|v| v.as_array())
311+
.map(|arr| {
312+
arr.iter()
313+
.filter_map(|v| v.as_str().map(String::from))
314+
.collect()
315+
})
316+
.unwrap_or_default();
317+
let pre_existing = item
318+
.get("pre_existing")
319+
.and_then(|v| v.as_bool())
320+
.unwrap_or(false);
321+
let requires_verification = item
322+
.get("requires_verification")
323+
.and_then(|v| v.as_bool())
324+
.unwrap_or(false);
325+
let suggested_fix = item
326+
.get("suggested_fix")
327+
.and_then(|v| v.as_str())
328+
.map(String::from);
329+
let why_it_matters = item
330+
.get("why_it_matters")
331+
.and_then(|v| v.as_str())
332+
.map(String::from);
333+
334+
raw_findings.push(ReviewFinding {
335+
severity,
336+
category,
337+
description,
338+
file: file_path,
339+
line,
340+
confidence,
341+
autofix_class,
342+
owner,
343+
evidence,
344+
pre_existing,
345+
requires_verification,
346+
suggested_fix,
347+
why_it_matters,
348+
});
267349
}
350+
351+
// Apply confidence filtering
352+
findings = filter_by_confidence(raw_findings);
353+
268354
// Cap at 50
269355
if findings.len() > 50 {
270356
warnings.push(format!(
@@ -286,8 +372,12 @@ pub fn cmd_parse_findings(
286372
}
287373

288374
if json_mode {
375+
let findings_json: Vec<serde_json::Value> = findings
376+
.iter()
377+
.map(|f| serde_json::to_value(f).unwrap_or(json!({})))
378+
.collect();
289379
json_output(json!({
290-
"findings": findings,
380+
"findings": findings_json,
291381
"count": findings.len(),
292382
"registered": 0,
293383
"warnings": warnings,
@@ -298,10 +388,21 @@ pub fn cmd_parse_findings(
298388
eprintln!(" Warning: {}", w);
299389
}
300390
for f in &findings {
301-
let sev = f["severity"].as_str().unwrap_or("unknown");
302-
let title = f["title"].as_str().unwrap_or("");
303-
let location = f["location"].as_str().unwrap_or("");
304-
println!(" [{}] {} \u{2014} {}", sev, title, location);
391+
let loc = f
392+
.file
393+
.as_deref()
394+
.map(|fp| {
395+
if let Some(ln) = f.line {
396+
format!("{}:{}", fp, ln)
397+
} else {
398+
fp.to_string()
399+
}
400+
})
401+
.unwrap_or_default();
402+
println!(
403+
" [{}] {} \u{2014} {} (confidence: {:.0}%)",
404+
f.severity, f.description, loc, f.confidence * 100.0
405+
);
305406
}
306407
}
307408
}

flowctl/crates/flowctl-cli/src/commands/codex.rs

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ use regex::Regex;
1111
use serde_json::json;
1212

1313
use flowctl_core::review_protocol::{
14-
compute_consensus, ConsensusResult, ModelReview, ReviewFinding, ReviewVerdict, Severity,
14+
compute_consensus, filter_by_confidence, AutofixClass, ConsensusResult, FindingOwner,
15+
ModelReview, ReviewFinding, ReviewVerdict, Severity,
1516
};
1617

1718
use crate::output::{error_exit, json_output};
@@ -770,9 +771,10 @@ fn parse_findings_from_output(output: &str) -> (Vec<ReviewFinding>, f64) {
770771
if let Some(arr) = data.get("findings").and_then(|v| v.as_array()) {
771772
for item in arr {
772773
let severity = match item.get("severity").and_then(|v| v.as_str()) {
773-
Some("critical") => Severity::Critical,
774-
Some("warning") => Severity::Warning,
775-
_ => Severity::Info,
774+
Some("P0") | Some("critical") => Severity::P0,
775+
Some("P1") | Some("warning") => Severity::P1,
776+
Some("P2") => Severity::P2,
777+
_ => Severity::P3,
776778
};
777779
let category = item
778780
.get("category")
@@ -786,6 +788,48 @@ fn parse_findings_from_output(output: &str) -> (Vec<ReviewFinding>, f64) {
786788
.to_string();
787789
let file = item.get("file").and_then(|v| v.as_str()).map(String::from);
788790
let line = item.get("line").and_then(|v| v.as_u64()).map(|n| n as u32);
791+
let item_confidence = item
792+
.get("confidence")
793+
.and_then(|v| v.as_f64())
794+
.unwrap_or(0.8);
795+
let autofix_class = match item.get("autofix_class").and_then(|v| v.as_str()) {
796+
Some("safe_auto") => AutofixClass::SafeAuto,
797+
Some("gated_auto") => AutofixClass::GatedAuto,
798+
Some("advisory") => AutofixClass::Advisory,
799+
_ => AutofixClass::Manual,
800+
};
801+
let owner = match item.get("owner").and_then(|v| v.as_str()) {
802+
Some("review-fixer") => FindingOwner::ReviewFixer,
803+
Some("downstream-resolver") => FindingOwner::DownstreamResolver,
804+
Some("human") => FindingOwner::Human,
805+
Some("release") => FindingOwner::Release,
806+
_ => FindingOwner::ReviewFixer,
807+
};
808+
let evidence = item
809+
.get("evidence")
810+
.and_then(|v| v.as_array())
811+
.map(|arr| {
812+
arr.iter()
813+
.filter_map(|v| v.as_str().map(String::from))
814+
.collect()
815+
})
816+
.unwrap_or_default();
817+
let pre_existing = item
818+
.get("pre_existing")
819+
.and_then(|v| v.as_bool())
820+
.unwrap_or(false);
821+
let requires_verification = item
822+
.get("requires_verification")
823+
.and_then(|v| v.as_bool())
824+
.unwrap_or(false);
825+
let suggested_fix = item
826+
.get("suggested_fix")
827+
.and_then(|v| v.as_str())
828+
.map(String::from);
829+
let why_it_matters = item
830+
.get("why_it_matters")
831+
.and_then(|v| v.as_str())
832+
.map(String::from);
789833

790834
if !description.is_empty() {
791835
findings.push(ReviewFinding {
@@ -794,10 +838,21 @@ fn parse_findings_from_output(output: &str) -> (Vec<ReviewFinding>, f64) {
794838
description,
795839
file,
796840
line,
841+
confidence: item_confidence,
842+
autofix_class,
843+
owner,
844+
evidence,
845+
pre_existing,
846+
requires_verification,
847+
suggested_fix,
848+
why_it_matters,
797849
});
798850
}
799851
}
800852
}
853+
854+
// Apply confidence filtering
855+
findings = filter_by_confidence(findings);
801856
}
802857

803858
(findings, confidence)

0 commit comments

Comments
 (0)