Skip to main content

agentstatedeveloper_cli/commands/
scopes.rs

1//! `asd scopes [list]` — discoverability for named scope aliases defined in
2//! `.asd/scopes.toml`. Most field users have not realized that `--scope foo`
3//! and `--paths "Packages/AudioEngine/**"` already work on search,
4//! prepare-change, investigate, impact, checklist, and since. This command
5//! makes the available scopes visible and prints a usage hint when the file
6//! is missing.
7//!
8//! See Plan A, t-005.
9
10use anyhow::Result;
11use clap::{Args, Subcommand};
12use serde_json::json;
13
14use agentstatedeveloper_core::load_scope_aliases;
15
16use crate::config::Config;
17
18#[derive(Debug, Subcommand)]
19pub enum ScopesCmd {
20    /// List named scope aliases defined in `.asd/scopes.toml`.
21    List(ListArgs),
22}
23
24#[derive(Debug, Args)]
25pub struct ListArgs {
26    /// Emit JSON instead of the default human-readable table.
27    #[arg(long)]
28    pub json: bool,
29}
30
31pub fn run(cfg: &Config, cmd: ScopesCmd) -> Result<()> {
32    match cmd {
33        ScopesCmd::List(args) => run_list(cfg, args),
34    }
35}
36
37fn run_list(cfg: &Config, args: ListArgs) -> Result<()> {
38    let aliases = load_scope_aliases(&cfg.db_path);
39
40    if args.json {
41        let payload = json!({
42            "scopes": aliases,
43            "count": aliases.len(),
44            "scopes_file": cfg
45                .db_path
46                .parent()
47                .unwrap_or(std::path::Path::new("."))
48                .join("scopes.toml")
49                .display()
50                .to_string(),
51        });
52        println!("{}", serde_json::to_string_pretty(&payload)?);
53        return Ok(());
54    }
55
56    if aliases.is_empty() {
57        println!("No named scopes defined.");
58        println!();
59        println!("Define them in .asd/scopes.toml to make `--scope <name>` work across");
60        println!("search, prepare-change, investigate, impact, checklist, and since:");
61        println!();
62        println!("    # .asd/scopes.toml");
63        println!("    audio-engine   = [\"Packages/AudioEngine/**\"]");
64        println!("    drift-pad      = [\"App/**/DriftPad*\", \"Packages/SequencerCore/**\"]");
65        println!();
66        println!("Without a scope file you can still pass globs directly:");
67        println!("    asd search 'master volume' --paths 'Packages/AudioEngine/**'");
68        return Ok(());
69    }
70
71    println!("Named scopes ({}):", aliases.len());
72    let mut names: Vec<&String> = aliases.keys().collect();
73    names.sort();
74    for name in names {
75        let globs = &aliases[name];
76        println!("  {name}");
77        for g in globs {
78            println!("    - {g}");
79        }
80    }
81    println!();
82    println!("Use with: --scope <name>  (works on search, prepare-change, investigate,");
83    println!("impact, checklist, since.)");
84    Ok(())
85}