# `detect` ## Purpose Runs a plugin's optional `detect` stage inside an OpenTelemetry span. The plugin reports what is already present in the target — existing skill directories, conflicting files, configuration to migrate — so the host can show that information to the user before prompts run, and so plugins can tailor their plans accordingly. Detect is read-only by contract: it inspects `ctx.fs` but never mutates state. ## Features - Wraps the plugin call in `installer.detect` span carrying `plugin.id`, `plugin.version`, and `dry_run` so traces correlate with apply. - No-op when the plugin omits `detect` — returns `{}` without opening a span, so plugins that don't need detection pay zero observability overhead. - Surfaces plugin-thrown errors unchanged — the host's outer `try/catch` is responsible for wrapping these in `INSTALLER.PLAN_FAILED` if they occur post-detect, but detect-stage exceptions propagate raw so plugins can model their own detection-time error codes. ## Basic Example ```ts import { runDetect } from "@suaveplan/installer"; const result = await runDetect(myPlugin, ctx); if (result.alreadyInstalled) { ctx.cli.write("Plugin is already installed"); } ``` ## Advanced Example ```ts import type { InstallerPlugin } from "@suaveplan/installer"; const plugin: InstallerPlugin = { id: "auditor", version: "0.1.0", detect: async (ctx) => { const skillsDir = ctx.fs.pathOf(ctx.cwd, ".suaveplan/skills"); const exists = await ctx.fs.exists(skillsDir); if (!exists) { return { alreadyInstalled: false, notes: ["fresh target"], }; } const entries = await ctx.fs.listDir(skillsDir); return { alreadyInstalled: true, conflicts: entries.filter((e) => e !== "index.json"), notes: [`${entries.length} entries already present`], }; }, plan: async () => [], apply: async () => undefined, }; ``` ## API Reference - `runDetect(plugin, ctx): Promise` - `DetectResult` — `{ alreadyInstalled?: boolean, conflicts?: string[], notes?: string[] }`. All fields optional. ## Implementation Notes The span is opened only when `plugin.detect` exists; this is a small optimisation but it matters when the host is called in a tight loop (e.g. `list` discovers many plugins). The default empty `DetectResult` (`{}`) ensures downstream stages never see `undefined` and lets the `alreadyInstalled` boolean be used in CLI rendering without an extra guard. Detect doesn't receive answers — those don't exist yet — so any configuration-dependent detection must happen lazily during plan instead.