feat(setup): one-command first-run flow + agent-narrowing defaults (closes #103)#104
Conversation
…ange 1) Add `adopt/detect_agents.rs` with machine-level agent probing (`~/.claude/`, `~/.codex/`, `~/.aider*`). Update `writer::write_klasp_toml` to accept an `agents: Option<&[String]>` param — `Some(list)` writes a narrowed list, `None` falls through to today's three-agent fallback with an "edit me" comment. Wire detection into `klasp init --adopt --mode mirror` via `cmd/init.rs` and add `fs_util::home_dir()` helper. Unit tests cover claude-only, codex-only, aider-conf, all-three, and fallback scenarios. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
change 2) When two `ProposedCheck`s from different gate detectors share the same name (e.g. both Husky and Lefthook emit a check named "lint"), the second check is suffixed with its gate type: "lint-husky", "lint-lefthook". The first keeps the bare name. Collision detection uses a `HashMap<String, usize>` of seen names; the `gate_suffix()` helper maps `GateType` variants to short strings. Unit tests cover: collision produces suffix on second, single occurrence stays bare, and the round-trip TOML parses cleanly after deduplication. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nts (closes #103 change 3) When the user runs `klasp install --agent <name>` against a klasp.toml whose `[gate].agents` includes additional agents, emit a WARN to stderr listing the uncovered agents ("klasp.toml lists agents codex, aider that this install will NOT cover"). The install itself still exits 0. The check is skipped for `--agent all` (which already covers everything in the config). Wired as `warn_if_narrower_than_config` called from `try_run` immediately after the surface selection resolves. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…#103 change 4) Add `klasp setup` subcommand that runs the full detect → narrow → write → install → doctor sequence in order. Behaviour: - Non-interactive default: detect gates, detect machine agents, write klasp.toml with narrowed [gate].agents, install all declared surfaces, run doctor, print green summary or actionable failures. - `--interactive`: y/n prompts before write + install; multi-select for gate mirroring (answer n = skip gate adoption, write empty-checks config). - `--dry-run`: prints plan and computed agents list, writes nothing. `klasp setup` is additive sugar: `init`, `install`, `doctor` remain unchanged as scriptable primitives. Force-write (true) makes setup re-runnable on existing repos. Adds integration tests in `tests/setup.rs` covering: subcommand exists, dry- run writes nothing, claude-only → narrowed to claude_code, all-three → all three, Lefthook fixture, Husky fixture, interactive n (graceful abort), interactive y/y (writes file), duplicate gate names get suffix, install warns on narrower config, install-all does not warn. Adds `docs/setup.md` with copy-paste first-run example, flag reference, agent detection logic table, and duplicate-name suffix documentation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- dedup run_doctor_inline → reuse cmd::doctor checks (fixes missing error arms) - dedup print_hook_warning → restore dropped actionable hint lines - dedup install dispatch → extract install_one_surface - hoist config load out of install loop (single read) - fix narrowed_agents_arg dead None branch (restores AC #6 fallback) - drop narrating module-level doc comments + redundant alias + duplicate step label - replace stringly-typed agent IDs with AGENT_ID constants - inline probe_aider iterator (drop Vec alloc) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| None | ||
| } else { | ||
| Some(detected) | ||
| } |
There was a problem hiding this comment.
🟢 Suggestion (cosmetic — AC #6 literal text): narrowed_agents_arg's None branch is unreachable from real callers. detect_installed_agents never returns an empty Vec — when home-dir lookup fails OR no probes match, it returns all_agents_fallback() (the 3-element ["claude_code", "codex", "aider"]). So this None arm only fires from the unit test on line 172.
User-visible effect: the no-detection path correctly writes the 3-agent default, but without the # Comment out any you don't use hint that the writer's None branch emits. AC #6 reads literally as "falls through to today's default with the 'edit me' comment" — the comment is missing.
Fix (smallest delta — keep detect_installed_agents returning a Vec but compare against the fallback at the call site):
use crate::adopt::detect_agents::{detect_installed_agents, ALL_AGENTS};
let detected_agents = detect_installed_agents(home.as_deref());
let agents_arg = if detected_agents.iter().map(String::as_str).eq(ALL_AGENTS.iter().copied()) {
None
} else {
Some(detected_agents.as_slice())
};(Requires making ALL_AGENTS pub(crate) if not already.) Or change detect_installed_agents to return Option<Vec<String>> and have callers map None. Either way, also apply in cmd/setup.rs line 115 which has the same dead Some(...) wrap.
Low severity — functional 3-agent fallback works; just the helper comment is lost.
Code reviewSummaryReviewed 5 commits / 1600 LOC across 12 files: 4 feature commits implementing issue #103 (agent-narrowing defaults, duplicate-name suffix, install warn-on-narrower, Critical Issues
Suggestions
What Looks Good
Verdict🟡 One non-blocking suggestion. The flagged Reviewed by |
…t (AC #6) `detect_installed_agents` now returns `(Vec<String>, bool)` where the bool flags whether the result is the all-three fallback (no agents detected). Callers thread the flag into `narrowed_agents_arg` so the writer's None-arm fires only on real fallbacks — not when the user genuinely has all three agents installed. Closes the AC #6 literal-text drift flagged in PR #104 review: #104 (comment) - detect_installed_agents: return (list, fell_back) tuple - detect_agents tests: assert fell_back flag in all 7 cases - init.rs::narrowed_agents_arg: take fell_back flag instead of is_empty check - init.rs tests: 3 cases (fallback, narrowed, user-with-all-three) - setup.rs: destructure tuple, pass agents_arg=None on fallback, prefix the detected-agents print with "(none — falling back to ...)" on fallback Tests 695 → 696, clippy + fmt clean.
Review remediationAddressed inline in commit Suggestion: AC #6 literal-text drift on
|
- README.md: replace 3-command quickstart with `klasp setup`; add v0.4.0 callout near top; mark #97 + #103 as shipped in feature table; link docs/setup.md from quickstart and Documentation sections; keep 3-command flow as "scriptable / CI" sub-section. - docs/recipes.md: add `klasp setup` as canonical first-run section above per-tool recipes; reference docs/setup.md; update version header to include v0.4. - docs/roadmap.md: update status blurb to 2026-05-08; add v0.4.0 section with shipped deliverables for #97 (PR #101) and #103 (PR #104). - docs/adopt.md: add Agent narrowing section reflecting that [gate].agents now defaults to detected agents (not 3-agent default); update example session Next: block accordingly; link to docs/setup.md. - Cargo.toml + npm/pypi manifests: bump all versions to 0.4.0. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
This PR implements all 4 changes from issue #103, landing in the suggested order (each as a separate commit).
Changes
Default
[gate].agentsto detected agents (feat/adopt: default [gate].agents to detected agents) — newadopt/detect_agents.rsprobes~/.claude/,~/.codex/,~/.aider*;write_klasp_tomlgainsagents: Option<&[String]>param;klasp init --adopt --mode mirroruses the narrowed list.Auto-suffix duplicate check names (
feat/adopt: auto-suffix duplicate check names on collision) — when twoProposedChecks collide on the same name, the second gets a gate-type suffix:lint→lint-lefthook. First keeps bare name.klasp installwarn-on-narrower (feat/install: warn when --agent <single> is narrower than [gate].agents) — single-agent install against a multi-agent klasp.toml emits a WARN to stderr; install still exits 0.klasp setuporchestrator (feat/setup: one-command first-run orchestrator) — new subcommand runs detect → narrow → write → install → doctor in one command.--interactiveadds y/n prompts.--dry-runwrites nothing.docs/setup.mdadded.Acceptance criteria mapping
klasp setupexists and runs full sequencetests/setup.rs::setup_help_exits_successfully,claude_only_home_narrows_agents_to_claude_code~/.claude/only →agents = ["claude_code"]+ doctor exits 0tests/setup.rs::claude_only_home_narrows_agents_to_claude_code(smoke-tested end-to-end manually)--interactiveprompts before write + installtests/setup.rs::interactive_n_to_mirror_skips_write,interactive_y_y_writes_file--dry-runprints plan, writes nothingtests/setup.rs::dry_run_prints_plan_writes_nothingklasp/src/adopt/writer.rs::tests::duplicate_name_suffix_on_collision,first_occurrence_keeps_bare_nameklasp init --adopt --mode mirrorwith~/.claude/only →agents = ["claude_code"]klasp/src/adopt/writer.rs::tests::agents_some_uses_narrowed_list;klasp/src/adopt/detect_agents.rs::tests::claude_only_home_returns_claude_codeklasp install --agent <single>against multi-agent config emits WARN; install succeedstests/setup.rs::install_single_agent_warns_about_uncovered,install_all_does_not_warn_narrowertests/setup.rs(11 tests)docs/setup.mddocuments new flow with copy-paste first-run exampledocs/setup.mdTest plan
cargo build --workspace— cleancargo fmt --all -- --check— cleancargo clippy --all-targets --workspace -- -D warnings— cleancargo test --workspace— 693 passed (up from 682 before this PR, +11 new tests)~/.claude/only →klasp setup→doctor: all checks passed,agents = ["claude_code"]Non-goals (not changed)
init/install/doctor🤖 Generated with Claude Code