1 #![forbid(unsafe_code)]2 //! Dispatch: one match arm + one module per subcommand.3 //! Feature agents: replace the stub body in their file, add nothing here4 //! except the single registration line (already present).5 6 pub mod ci;7 pub mod init;8 pub mod issue;9 pub mod label;10 pub mod merge;11 pub mod milestone;12 pub mod pr;13 pub mod release;14 pub mod skill;15 pub mod spec;16 pub mod task;17 pub mod wiki;18 19 use crate::args::Global;20 use crate::CliError;21 22 /// Root help: <=12 lines, <=300 bytes. Whole surface from here.23 /// Command map only; contract lives in `f0rge --skill`.24 pub const ROOT_HELP: &str = "f0rge: local forge\n\25 usage: f0rge [--skill] <cmd> [--json|--jq E] [args]\n\26 issue pr task label milestone ci wiki\n\27 release merge init skill spec\n\28 'cmd -h' for help\n\29 contract: f0rge --skill\n";30 31 /// Dispatch over subcommand names (mirror of folder tree).32 pub fn dispatch(33 sub: &str,34 args: &[String],35 fg: &f0rge_core::Forge,36 _g: &Global,37 ) -> Result<(), CliError> {38 // -h anywhere in sub args prints help: per-verb const when the39 // first bare token names a verb (#36), else the domain block.40 if args.iter().any(|a| a == "-h" || a == "--help") {41 if let Some(h) = verb_help_for(sub, args) {42 crate::output::out(h);43 return Ok(());44 }45 crate::output::out(help_for(sub).unwrap_or(ROOT_HELP));46 return Ok(());47 }48 match sub {49 "issue" => issue::run(args, fg),50 "pr" => pr::run(args, fg),51 "task" => task::run(args, fg),52 "label" => label::run(args, fg),53 "milestone" => milestone::run(args, fg),54 "ci" => ci::run(args, fg),55 "wiki" => wiki::run(args, fg),56 "release" => release::run(args, fg),57 "merge" => merge::run(args, fg),58 "init" => init::run(args, fg),59 "skill" => skill::run(args, fg),60 "spec" => spec::run(args, fg),61 _ => Err(CliError::Usage(format!("unknown command: {sub}"))),62 }63 }64 65 /// Two-arg help (#36): first bare token names the verb. None when the66 /// subcommand has no verbs (merge/skill) or the verb is unknown, so the67 /// domain block prints. Only fix-help verbs route here; other commands68 /// keep domain help until their owners add verb_help().69 fn verb_help_for(sub: &str, args: &[String]) -> Option<&'static str> {70 let verb = args.first().map(|s| s.as_str()).filter(|s| !s.starts_with('-'))?;71 Some(match sub {72 "issue" => issue::verb_help(verb)?,73 "pr" => pr::verb_help(verb)?,74 "ci" => ci::verb_help(verb)?,75 "wiki" => wiki::verb_help(verb)?,76 "release" => release::verb_help(verb)?,77 "merge" => merge::verb_help(verb)?,78 "skill" => skill::verb_help(verb)?,79 _ => return None,80 })81 }82 83 fn help_for(sub: &str) -> Option<&'static str> {84 Some(match sub {85 "issue" => issue::help(),86 "pr" => pr::help(),87 "task" => task::help(),88 "label" => label::help(),89 "milestone" => milestone::help(),90 "ci" => ci::help(),91 "wiki" => wiki::help(),92 "release" => release::help(),93 "merge" => merge::help(),94 "init" => init::help(),95 "skill" => skill::help(),96 "spec" => spec::help(),97 _ => return None,98 })99 }100 101 /// All sub helps for budget tests (exactly 12).102 #[allow(dead_code)]103 pub fn all_helps() -> Vec<(&'static str, &'static str)> {104 vec![105 ("issue", issue::help()),106 ("pr", pr::help()),107 ("task", task::help()),108 ("label", label::help()),109 ("milestone", milestone::help()),110 ("ci", ci::help()),111 ("wiki", wiki::help()),112 ("release", release::help()),113 ("merge", merge::help()),114 ("init", init::help()),115 ("skill", skill::help()),116 ("spec", spec::help()),117 ]118 }