@@ -16,6 +16,7 @@ use autophagy_adapter_codex::{
1616} ;
1717use autophagy_core:: { ImportOptions , ImportSummary , import_jsonl} ;
1818use autophagy_events:: Event ;
19+ use autophagy_mutations:: { GenerationOutcome , generate_candidates} ;
1920use autophagy_patterns:: { DetectorConfig , EvidencePacket , detect} ;
2021use autophagy_store:: {
2122 DeleteAllSummary , DeleteSummary , EventStore , PruneSummary , SearchHit , SessionSummary ,
@@ -150,6 +151,16 @@ enum Commands {
150151 thresholds : ThresholdArgs ,
151152 } ,
152153
154+ /// Propose review-only, zero-permission mutation candidates from findings.
155+ Mutations {
156+ /// Limit candidate generation to one exact project path.
157+ #[ arg( long, value_name = "PATH" ) ]
158+ project : Option < String > ,
159+
160+ #[ command( flatten) ]
161+ thresholds : ThresholdArgs ,
162+ } ,
163+
153164 /// Export redacted canonical AEP events as JSONL to standard output.
154165 Export {
155166 /// Limit export to one exact project path.
@@ -228,6 +239,7 @@ enum CommandReport {
228239 Search ( Vec < SearchHit > ) ,
229240 Digest ( DigestReport ) ,
230241 Patterns ( Vec < EvidencePacket > ) ,
242+ Mutations ( Vec < GenerationOutcome > ) ,
231243 Export ( Vec < Event > ) ,
232244 Prune ( PruneSummary ) ,
233245 DeleteSession ( DeleteSummary ) ,
@@ -270,6 +282,7 @@ impl CommandReport {
270282 | Self :: Search ( _)
271283 | Self :: Digest ( _)
272284 | Self :: Patterns ( _)
285+ | Self :: Mutations ( _)
273286 | Self :: Export ( _)
274287 | Self :: Prune ( _)
275288 | Self :: DeleteSession ( _)
@@ -446,6 +459,16 @@ fn execute(cli: Cli) -> Result<CommandReport, CliError> {
446459 let events = store. list_events_for_detection ( project. as_deref ( ) ) ?;
447460 Ok ( CommandReport :: Patterns ( detect ( & events, thresholds. into ( ) ) ) )
448461 }
462+ Commands :: Mutations {
463+ project,
464+ thresholds,
465+ } => {
466+ let database = resolve_database_path ( cli. database ) ?;
467+ let store = open_store ( & database) ?;
468+ let events = store. list_events_for_detection ( project. as_deref ( ) ) ?;
469+ let findings = detect ( & events, thresholds. into ( ) ) ;
470+ Ok ( CommandReport :: Mutations ( generate_candidates ( & findings) ) )
471+ }
449472 Commands :: Export { project } => {
450473 let database = resolve_database_path ( cli. database ) ?;
451474 let store = open_store ( & database) ?;
@@ -580,6 +603,7 @@ fn write_report(
580603 write_findings ( & mut writer, & report. findings ) ?;
581604 }
582605 CommandReport :: Patterns ( findings) => write_findings ( & mut writer, findings) ?,
606+ CommandReport :: Mutations ( outcomes) => write_mutations ( & mut writer, outcomes) ?,
583607 CommandReport :: Prune ( summary) => writeln ! (
584608 writer,
585609 "{} sessions · {} events · {} artifacts{}" ,
@@ -631,6 +655,27 @@ fn write_findings(writer: &mut impl Write, findings: &[EvidencePacket]) -> io::R
631655 Ok ( ( ) )
632656}
633657
658+ fn write_mutations ( writer : & mut impl Write , outcomes : & [ GenerationOutcome ] ) -> io:: Result < ( ) > {
659+ if outcomes. is_empty ( ) {
660+ writeln ! ( writer, "no mutation candidates above evidence threshold" ) ?;
661+ }
662+ for outcome in outcomes {
663+ match outcome {
664+ GenerationOutcome :: Candidate { package } => writeln ! (
665+ writer,
666+ "{}\t {}\t {} evidence\t zero permissions\t candidate" ,
667+ package. mutation_id,
668+ package. title,
669+ package. hypothesis. supporting_event_ids. len( )
670+ ) ?,
671+ GenerationOutcome :: InsufficientEvidence { finding_id, reason } => {
672+ writeln ! ( writer, "{finding_id}\t insufficient evidence\t {reason}" ) ?;
673+ }
674+ }
675+ }
676+ Ok ( ( ) )
677+ }
678+
634679fn write_codex_import_summary (
635680 writer : & mut impl Write ,
636681 summary : & CodexImportSummary ,
0 commit comments