Skip to main content

agentstatedeveloper_cli/commands/
onboard.rs

1//! `asd onboard` — one-shot setup for a freshly-cloned project (Plan K t-005).
2//!
3//! Runs `init → index → conclusions import → mcp install` in the right order,
4//! idempotent. A new developer (or their agent) doesn't need to know the
5//! sequence — they run `asd onboard` and get:
6//!
7//!   1. A live SQLite ASG (`asd init` — installs hooks, updates .gitignore)
8//!   2. A fresh semantic index from source (`asd index .`)
9//!   3. Inherited ledger entries from the committed sidecar
10//!      (`asd conclusions import` — pulls `.asd/conclusions/*.jsonl` in)
11//!   4. A project-scoped MCP registration so the user's agent can reach asd
12//!      (`asd mcp install --project` — writes `.mcp.json` in the repo). This
13//!      mirrors `ctx init`'s one-command ergonomics; pass `--no-mcp` to skip.
14//!
15//! Steps that have already completed are no-ops. Steps that have nothing
16//! to do (e.g. no `.asd/conclusions/` directory yet, fresh-from-scratch
17//! project) skip cleanly with a one-line note instead of failing. The MCP
18//! step is best-effort: if the `asd-mcp` binary isn't installed it warns and
19//! continues, since the repo is already usable without it.
20//!
21//! Composing existing commands rather than re-implementing keeps the
22//! onboard surface a thin orchestrator — it inherits future
23//! init/index/conclusions improvements for free.
24
25use anyhow::Result;
26use clap::Args;
27use std::path::PathBuf;
28
29use crate::commands::{
30    conclusions::{self, ConclusionsCmd, ImportArgs},
31    index::{self, IndexArgs},
32    init::{self, InitArgs},
33    mcp::{self, InstallArgs, McpCmd},
34};
35use crate::config::Config;
36
37#[derive(Debug, Args)]
38pub struct OnboardArgs {
39    /// Directory to index. Defaults to the current working directory.
40    #[arg(default_value = ".")]
41    pub path: PathBuf,
42
43    /// Skip git hook installation in the init step. Mirrors the
44    /// `asd init --no-hooks` flag.
45    #[arg(long, default_value_t = false)]
46    pub no_hooks: bool,
47
48    /// Tee the full index log to stderr in real time. Mirrors the
49    /// `asd index --verbose` flag.
50    #[arg(short, long)]
51    pub verbose: bool,
52
53    /// Skip the MCP-registration step. By default `onboard` finishes by
54    /// registering a project-scoped asd-mcp server (`.mcp.json` in the repo)
55    /// so the user's agent can reach asd in one command.
56    #[arg(long, default_value_t = false)]
57    pub no_mcp: bool,
58}
59
60pub fn run(cfg: &Config, args: OnboardArgs) -> Result<()> {
61    eprintln!("== asd onboard ==\n");
62
63    // -- Step 1: init -----------------------------------------------------
64    eprintln!("[1/4] asd init …");
65    let init_args = InitArgs {
66        no_hooks: args.no_hooks,
67    };
68    init::run(cfg, init_args)?;
69
70    // -- Step 2: index ---------------------------------------------------
71    eprintln!("\n[2/4] asd index {} …", args.path.display());
72    let index_args = IndexArgs {
73        path: args.path.clone(),
74        verbose: args.verbose,
75    };
76    index::run(cfg, index_args)?;
77
78    // -- Step 3: conclusions import -------------------------------------
79    // Skip cleanly when there's nothing to import (fresh project, no
80    // committed sidecar yet). conclusions::import resolves its default
81    // input dir relative to the database parent; we mirror that here
82    // for the existence check so the skip message references the same
83    // directory the import would have read from.
84    let conclusions_dir = cfg
85        .db_path
86        .parent()
87        .map(|p| p.join(".asd").join("conclusions"))
88        .unwrap_or_else(|| PathBuf::from(".asd/conclusions"));
89    if conclusions_dir.is_dir() {
90        eprintln!("\n[3/4] asd conclusions import …");
91        let import_args = ImportArgs {
92            in_dir: Some(conclusions_dir),
93        };
94        conclusions::run(cfg, ConclusionsCmd::Import(import_args))?;
95    } else {
96        eprintln!(
97            "\n[3/4] asd conclusions import — skipped (.asd/conclusions/ doesn't exist; fresh project)"
98        );
99    }
100
101    // -- Step 4: mcp install (project-scoped) ----------------------------
102    // Best-effort: registration is what makes asd reachable from the user's
103    // agent in one command (matching `ctx init`). Project scope writes a
104    // `.mcp.json` in the repo rather than editing global tool configs, so it's
105    // safe to run repeatedly and never clobbers a cooperating tool's global
106    // registration. If the `asd-mcp` binary isn't installed we warn and keep
107    // going — the repo is already usable; only the agent wiring is missing.
108    if args.no_mcp {
109        eprintln!("\n[4/4] asd mcp install — skipped (--no-mcp)");
110    } else {
111        eprintln!("\n[4/4] asd mcp install --project …");
112        let install_args = InstallArgs {
113            db: None,
114            tool: None,
115            follow_active: false,
116            project: true,
117        };
118        if let Err(e) = mcp::run(cfg, McpCmd::Install(install_args)) {
119            eprintln!(
120                "  warning: MCP registration skipped — {e}\n  \
121                 (repo is set up; run `asd mcp install` once asd-mcp is available)"
122            );
123        }
124    }
125
126    eprintln!("\n== onboard complete ==");
127    eprintln!(
128        "Your ASD project is ready. Try:\n  asd status\n  asd search <query>\n  asd think bootstrap"
129    );
130    Ok(())
131}