Skip to content

Commit 732b409

Browse files
feat(query): panic-attack query subcommand (issue #33 S3) (#57)
## Summary Third slice of [#33](#33). Adds a \`panic-attack query\` subcommand that evaluates a small S-expression query language over the per-finding hexads from #55 (S1) joined with the campaign-state hexads from #56 (S2). > Stacked on #56 — diff against \`main\` includes the S1+S2 changes until those land; this PR rebases cleanly. ## Supported forms (S3 initial) \`\`\`scheme (category UnsafeCode) (rule-id PA004) (severity Critical) (repo <name-substring>) ; case-insensitive substring (file <path-substring>) ; case-insensitive substring (pr-state pr-filed|pr-merged|pr-closed|dismissed|nil) (and <expr> <expr> ...) (or <expr> <expr> ...) (not <expr>) \`\`\` \`(pr-state nil)\` matches any finding **without a campaign hexad** — i.e. the operationally important "open work not yet PR'd" view that the estate-sweep campaign needs most. ## CLI \`\`\` panic-attack query "(and (category UnsafeCode) (pr-state nil))" panic-attack query "(severity Critical)" --format json panic-attack query "(repo alpha)" --verisimdb-dir verisimdb-data \`\`\` Default output: fixed-width table. JSON via \`--format json\`. ## Deferred to S3 follow-ups Three follow-ups will land in the next PRs in this stack: - \`(crosslang :from FFI :to ProofDrift)\` — needs integration with \`src/kanren/crosslang.rs\`. - \`(diff :since <date> :category <X>)\` — needs an explicit baseline-run cursor. - \`panic-attack campaign poll\` (was S2 scope cut) — GitHub PR-state polling. ## Implementation notes - Small hand-rolled S-expression tokenizer/parser (~170 LOC) — doesn't depend on the a2ml parser since the query surface is narrower. - Evaluator pre-joins findings with their latest campaign event (newest-by-\`created_at\` wins per \`finding_id\`) before filtering. \`(pr-state ...)\` is a free clause inside \`and\`/\`or\` rather than a special case. ## Test plan - [x] \`cargo test --lib\` — 239 green (19 new in \`src/query/\`). - [x] \`cargo clippy --all-targets -- -D warnings\` — clean. - [x] \`cargo fmt --all\` — clean. - [x] End-to-end CLI smoke: hand-crafted finding hexad + \`campaign register-pr\` + \`query (and (category UnsafeCode) (pr-state pr-filed)) --format json\` returns the expected JSON with the joined campaign state. Refs #33. Stacked on #56 (S2) → #55 (S1). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 016791c commit 732b409

3 files changed

Lines changed: 759 additions & 0 deletions

File tree

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ pub mod kanren;
3232
pub mod mass_panic;
3333
pub mod notify;
3434
pub mod panll;
35+
pub mod query;
3536
pub mod report;
3637
pub mod signatures;
3738
pub mod storage;

src/main.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ mod kin;
2727
mod mass_panic;
2828
mod notify;
2929
mod panll;
30+
mod query;
3031
mod report;
3132
mod signatures;
3233
mod storage;
@@ -778,6 +779,28 @@ enum Commands {
778779
#[arg(long, group = "sweep_shape", default_value_t = false)]
779780
by_category: bool,
780781
},
782+
783+
/// Query persisted findings + campaign state with a small S-expression
784+
/// language (issue #33 S3). See `panic-attack query --help` for syntax.
785+
Query {
786+
/// Query expression, e.g. `(and (category UnsafeCode) (pr-state nil))`.
787+
#[arg(value_name = "EXPR")]
788+
expr: String,
789+
790+
/// VeriSimDB data directory (default: `verisimdb-data`).
791+
#[arg(long, value_name = "DIR", default_value = "verisimdb-data")]
792+
verisimdb_dir: PathBuf,
793+
794+
/// Output format.
795+
#[arg(long, value_enum, default_value_t = QueryFormatArg::Table)]
796+
format: QueryFormatArg,
797+
},
798+
}
799+
800+
#[derive(clap::ValueEnum, Clone, Debug)]
801+
enum QueryFormatArg {
802+
Table,
803+
Json,
781804
}
782805

783806
#[derive(Subcommand)]
@@ -2429,6 +2452,24 @@ fn run_main() -> Result<()> {
24292452
}
24302453
},
24312454

2455+
Commands::Query {
2456+
expr,
2457+
verisimdb_dir,
2458+
format,
2459+
} => {
2460+
let q = query::parse(&expr)?;
2461+
let hits = query::run(&q, &verisimdb_dir)?;
2462+
match format {
2463+
QueryFormatArg::Table => {
2464+
print!("{}", query::render_table(&hits));
2465+
}
2466+
QueryFormatArg::Json => {
2467+
println!("{}", serde_json::to_string_pretty(&hits)?);
2468+
}
2469+
}
2470+
return Ok(());
2471+
}
2472+
24322473
Commands::Campaign { action } => {
24332474
match action {
24342475
CampaignAction::RegisterPr {

0 commit comments

Comments
 (0)