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}