Skip to content

kcap setup: one agent-install prompt + import current-repo history - #335

Merged
realtonyyoung merged 34 commits into
mainfrom
worktree-import-and-unified-install
Jul 21, 2026
Merged

realtonyyoung merged 34 commits into
mainfrom
worktree-import-and-unified-install

Conversation

@realtonyyoung

Copy link
Copy Markdown
Collaborator

What & why

Two UX changes to the kcap setup onboarding wizard.

1. One agent-install prompt instead of nine

Step 4 previously asked a separate yes/no per detected harness. It now shows the detected agents and asks one default-yes prompt — "Install kcap for these agents (hooks, skills, instructions, MCP)?" — installing artifacts for every detected harness.

  • A single InstallAgents decision (SetupDecisions.DecideInstallAgents) is threaded into CodingAgentsStep.Options.
  • RunAsync has two ordered early-returns: no-agents (existing warning) and !InstallAgents (zero mutations — before any installer, SweepLegacyCodexSkills, or selected assignment).
  • Every existing per-vendor --skip-* semantic is preserved unchanged (incl. the Kiro flag coupling). The Codex network-access and provider-API-key prompts remain separate.
  • Kiro is annotated in the detected list ("installing sets kcap as your default Kiro agent") to keep consent informed.

2. New Step 6 — import current-repo history

After config is saved, setup offers (default-yes) to import the current repository's past sessions across all detected harnesses via the existing ImportCommand.HandleImport.

  • Gated on a resolved current repo (owner+name from origin) and satisfied auth (provider None or a usable token).
  • Imported new sessions honor the Step-3 default visibility, sent client-side like live recording (routed sources New-only + guarded by !ForcePrivate; chain zeroes it under force-private in the orchestrator to avoid a partial-failure leak). Existing per-source --private behavior is untouched.
  • AppConfig.SetResolvedState refreshes the in-process server URL + profile to the exact saved values before the import, and HandleImport binds auth to the passed baseUrl.
  • The embedded import auto-skips excluded repos/paths (never blocks on Console.ReadLine).

⚠️ Behavior change

kcap setup --no-prompt now auto-imports the current repo's history (consistent default-yes). Existing unattended/scripted invocations will begin uploading current-repo session history unless they pass the new --skip-import flag. Documented in README + help-setup.txt.

Testing

  • Unit suite: 3480 total, 0 failed (2 skipped). New: ImportVisibilityTests, SetupDecisionsTests, AppConfigResolvedStateTests, plus CodingAgentsStepTests/SetupCommandTests additions.
  • Integration suite: 142/142.
  • AOT publish: no IL3050/IL2026 warnings.

Reviewer note

Unit runs occasionally log a post-summary ObjectDisposedException from the pre-existing title-generation backgroundTasks/concurrencyLimit orchestration in HandleImport (semaphore disposed before a fire-and-forget title task finishes). It fails no tests and is untouched by this PR; flagged for awareness — happy to address separately if wanted.

Closes #334
Linear: AI-1437

Design spec + plan: docs/superpowers/specs/2026-07-20-unified-agent-install-and-import-design.md, docs/superpowers/plans/2026-07-20-unified-agent-install-and-import.md.

🤖 Generated with Claude Code

realtonyyoung and others added 27 commits July 20, 2026 13:51
Design for two kcap setup changes: (1) a single default-yes prompt that
installs all kcap artifacts for every detected coding-agent harness,
replacing the nine per-vendor prompts; (2) a final step offering to import
the current repo's past sessions across detected harnesses.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Address all 4 P1 + 5 P2 findings: hard zero-mutation gate on InstallAgents=false,
preserve per-vendor skip/downstream semantics (truth table), AppConfig
resolved-state refresh before embedded import, currentRepo+auth eligibility
gates, auto-skip import exclusions (no ReadLine), pinned HandleImport contract
with return-code handling, corrected visibility claim, Kiro consent annotation,
extracted decision helpers for testability, --no-prompt behavior-change rollout
notes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Address round-2 findings: exact AppConfig.SetResolvedState setter (not
precedence re-resolution) + bind auth to CreateAuthenticatedClientAsync(baseUrl);
honor Step 3 visibility by sending default_visibility from import (symmetric with
live hooks) via new HandleImport defaultVisibility param; ordered RunAsync
early-returns separating no-agents warning from InstallAgents=false zero-mutation
return; correct type name CodingAgentsStep.Result; precise Kiro flag-coupling
truth table (hooks+mcp skip suppresses skills via selected=false).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Address round-3 findings: thread DefaultVisibility through ImportContext + all
seven routed import sources (not just 3) + chain path; scope visibility to
newly-created sessions and document Partial/AlreadyLoaded retain prior visibility;
correct the live-symmetry claim (Cursor live hook omits default_visibility) and
record Cursor live-path gap as out-of-scope follow-up; correct shared-skills
eligible set to the six ~/.agents/skills consumers (Kiro/Antigravity excluded).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Address round-4 finding: routed import sources re-post session-start on
Partial/AlreadyLoaded for lifecycle healing, so gate the Step 3 visibility on a
per-session Status==New guard (effectiveVisibility = ForcePrivate ? private :
status==New ? DefaultVisibility : null), preserving forcePrivate's bulk rewrite
on all statuses. Require tests that drive each of the eight session-start paths
through New/Partial/AlreadyLoaded via the source/orchestrator entry point.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Make the import visibility contract topology-specific: routed sources apply the
effectiveVisibility formula across New/Partial/AlreadyLoaded; the Claude/Codex
chain stamps default_visibility only in the New session-start payload while
forcePrivate stays its existing post-hoc SetVisibilityNoneForAll over imported
IDs (covering New + resumed Partial, never file-based AlreadyLoaded). Rewriting
file-based AlreadyLoaded under forcePrivate is called out as a separate,
out-of-scope standalone-import change. De-duplicated the ImportContext bullet and
made the test matrix topology-specific.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Make the import visibility change purely additive and forcePrivate-preserving:
add a New-session default_visibility stamp guarded by !ForcePrivate, and leave
each source's existing (inconsistent) force-private handling untouched — avoids a
standalone 'kcap import --private' semantic expansion (added as a Non-goal).
Correct ImportContext location (IImportSource.cs, not ImportCommand.cs) and
specify the chain threading seam (HandleImport.defaultVisibility -> both
ImportChainsAsync calls -> ImportSingleSessionAsync with null default). Fix
files-touched wording to compute the status-aware value in ImportSessionAsync.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fix chain visibility seam: drop the unimplementable !forcePrivate guard (the
chain path receives neither ImportContext nor forcePrivate) and state the
production invariant (setup: forcePrivate=false + default set; standalone:
default null; post-hoc SetVisibilityNoneForAll wins if both). Put the null
default on the internal ImportChainsAsync param (six unit-test callers), note
ImportSingleSessionAsync is private, add ImportContext.DefaultVisibility=null
default for the ~25 existing 3-arg test constructions, and qualify the
'null => no field' acceptance with forcePrivate:false.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Enforce forcePrivate precedence in the orchestrator instead of relying on the
caller invariant or post-hoc privatization: derive chainDefaultVisibility =
forcePrivate ? null : defaultVisibility in HandleImport before threading into
ImportChainsAsync. This closes a partial-failure leak where a New chain session
POSTs session-start with a default visibility then fails before session-end, so
it never enters importedSessionIds and SetVisibilityNoneForAll never privatizes
it. Added a chain test for forcePrivate + failure-after-session-start.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Specify explicitly how --no-prompt sets the unified InstallAgents gate: in
SetupCommand, InstallAgents = options.NoPrompt || PromptYesNo(unifiedPrompt) for
>=1 detected agent, so unattended setup keeps auto-installing agents after the
per-vendor NoPrompt gates are removed; Options.NoPrompt still drives the retained
Codex-network / provider-key sub-prompts. Added an acceptance case: --no-prompt +
detected => no unified prompt, InstallAgents=true, skips and non-interactive
Codex-network handling preserved.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Correct SetupCommand seam naming: the unified decision is made before
CodingAgentsStep.Options is constructed, so InstallAgents = noPrompt ||
PromptYesNo(...) uses the local noPrompt; then Options is built with InstallAgents
and NoPrompt:noPrompt. Fix the sub-prompt attribution: Codex network-access is
driven via CodingAgentsStep.Options.NoPrompt, while the provider-API-key prompt
branches on the local noPrompt inside SetupCommand (not through CodingAgentsStep).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…cision

CodingAgentsStep.RunAsync now short-circuits before any Handle*/selected
work: no supported agent detected -> existing warning + zero-value Result;
otherwise !options.InstallAgents -> "skipping agent setup" note + zero-value
Result. Only when InstallAgents is true does installation proceed, and each
of the nine per-vendor primary gates (plus the shared agent-skills gate)
now installs unconditionally instead of re-prompting -- the single consent
decision (wired in SetupCommand next) already covers it. Existing
--skip-<vendor> / per-artifact --skip-* semantics, `selected` out-params,
SweepLegacyCodexSkills, and all downstream success/selected gates are
unchanged. The Codex network-access prompt stays separate.

Updated ~19 existing CodingAgentsStepTests that asserted the old
per-vendor "declined via prompt -> skipped" behavior (that path no longer
exists); added new coverage for the InstallAgents=false zero-mutation
path, the no-agents early return, the Kiro --skip-kiro-* flag-coupling
truth table, and shared ~/.agents/skills eligibility.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add SetupDecisions (new pure helper): DetectedAgentsSummary builds the
comma-joined, human-readable list of detected harnesses (annotating Kiro,
since installing for it clones the user's default agent and sets kcap as
default), and DecideInstallAgents is the single unified install-consent
decision -- false when nothing is detected (no prompt), otherwise noPrompt
short-circuits to true, else the caller's yes/no prompt decides.

Wire it into SetupCommand's Step 4: after building `detected`, print the
summary line, then compute InstallAgents from the LOCAL `noPrompt` (before
CodingAgentsStep.Options exists) via the existing PromptYesNo delegate, and
thread it into Options. This replaces the nine "Install <vendor>?" prompts
from the previous commit with one "Install kcap for these agents (hooks,
skills, instructions, MCP)?" prompt, while preserving `kcap setup --no-prompt`
installing all detected agents with no prompt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rofile

Add AppConfig.SetResolvedState(serverUrl, profile), which assigns the
static ResolvedServerUrl/ResolvedProfile directly instead of re-running
ResolveServerUrl's CLI/env/repo precedence (which could pick a
different value than what setup just saved, e.g. a stale KCAP_URL or a
raw scheme-less --server-url re-resolving to its unnormalized form).
kcap setup now calls it immediately after saving the profile config so
same-process work afterward (e.g. the upcoming Step 6 import) observes
the exact just-saved state.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
SetResolvedState now takes the active profile name so ResolvedProfile.ProfileName
matches the saved profile instead of being null (read by update/ignore/daemon
commands via ?? fallback).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…eUrl auth

Scaffold for threading the Step-3 default visibility through import: ImportContext
gains DefaultVisibility (default null), HandleImport gains autoSkipExclusions and
defaultVisibility params, and auth discovery is bound to the passed baseUrl.
Behavior wiring (exclusion auto-skip, chain/routed visibility stamping) follows.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Gate the excluded-repo/path prompt loop's canPrompt check on
!autoSkipExclusions too, so callers (e.g. the embedded `kcap setup`
import) can force the existing non-interactive auto-skip path and
never hit Console.ReadLine, regardless of TTY state.
…vate-zeroed)

Derive chainDefaultVisibility = forcePrivate ? null : defaultVisibility in
HandleImport and thread it through both the TTY and non-TTY ImportChainsAsync
call sites, down to ImportSingleSessionAsync's New-branch session-start
payload. Enforcing forcePrivate precedence in the orchestrator (rather than
relying on the existing post-hoc SetVisibilityNoneForAll) guarantees a
force-private import never stamps a non-private default even if the session
fails mid-stream, before session-end/importedSessionIds. The chain's existing
post-hoc forcePrivate privatization is untouched.
… (New-only)

Construct the routed-phase ImportContext with DefaultVisibility: defaultVisibility
(ImportCommand.cs ~1349). In each of the 7 routed sources' ImportSessionAsync
(Cursor, Copilot, Gemini, Kiro, Pi, OpenCode, Antigravity), capture the
session-start payload into a local var and guard-stamp it:

  if (!ctx.ForcePrivate && status == New && ctx.DefaultVisibility is not null)
      payload["default_visibility"] = ctx.DefaultVisibility;

Partial/AlreadyLoaded replays get no default stamp (Decision 11 scope). Pi/
OpenCode/Antigravity's existing forcePrivate "private" stamp is untouched and
mutually exclusive with this guard (fires only when !ForcePrivate). Cursor gets
the new default-visibility mechanism on the import path for the first time
(its live hook has none); its existing post-hoc privateScopeSessionIds
force-private handling is unchanged.
Add ImportVisibilityTests.cs covering the Change 2 → Visibility topology
matrix, driven through each real entry point (never the private per-source
BuildSessionStartPayload builders in isolation):

- Chain path (ImportChainsAsync direct): New stamps the provided default,
  null omits it entirely, Partial never posts session-start at all.
- Orchestrator (HandleImport): the chainDefaultVisibility = forcePrivate ?
  null : defaultVisibility derivation, including the force-private
  precedence case — a WireMock 500 on the transcript batch AFTER
  session-start succeeds must never leak a non-private default.
- All 7 routed sources (Cursor, Copilot, Gemini, Kiro, Pi, OpenCode,
  Antigravity): New stamps the default; Partial/AlreadyLoaded omit it;
  forcePrivate:true suppresses the default while leaving each source's
  existing behavior (or lack thereof) for its own "private" stamp
  unchanged. Cursor gets an additional full WireMock round-trip through
  HandleImport per the spec's explicit call-out (its live hook has no
  default_visibility injection today).
- autoSkipExclusions: an excluded-path session completes through
  HandleImport within a bounded timeout and emits the auto-skip line,
  never the interactive prompt text.
First piece of Task 6 (Step 6 import). The flag is parsed alongside the
other --skip-* flags; wiring into the Step 6 decision follows in a later
commit.
Makes room for the new Step 6 (import past sessions) added in a
following commit.
Pure decision helper for the Step 6 import-past-sessions gate: no
resolvable current repo or unsatisfied auth requirements always skip
(with a reason), then --skip-import, then --no-prompt auto-runs, else
the caller's interactive yes/no decides. Mirrors DecideInstallAgents'
shape so the two Step decisions stay unit-testable without touching the
console or filesystem.

TDD: tests added first (failed on missing DecideImport/ImportOutcome),
then the implementation made them pass.
Adds Step 6/6 after the config save + AppConfig.SetResolvedState +
PingCliSetupAsync, before the completion summary:

- Resolves currentRepo via RepositoryDetection.DetectRepositoryAsync,
  mirroring Program.cs's `kcap import` construction exactly.
- Computes authSatisfied from the provider/token state this same run
  already tracked (provider None, or the just-loaded finalTokens being
  present and unexpired) — Decision 9, not merely provider != None.
- Runs SetupDecisions.DecideImport and, on Run, invokes the import
  runner with every HandleImport argument pinned (baseUrl, scope
  ImportScope.Repo(owner,name), skipConfirmation:true, forcePrivate:
  false, autoSkipExclusions:true, defaultVisibility: the Step-3 choice,
  the nine import sources built the same way Program.cs does).
- The runner call goes through RunImportStepAsync, a new extracted
  method with an injectable ImportRunnerOverride seam (ImportInvocation
  record) so the decision + pinned-args contract is unit-testable
  without running a real import or driving the whole wizard.
- Best-effort error handling: a thrown exception or non-zero exit code
  from the runner prints a warning + manual `kcap import` hint and
  setup continues to the completion summary — it never rethrows or
  fails setup.
SetupCommand.HandleAsync has no injectable I/O seam for its login/
discovery/config-save flow, so driving it end-to-end in a unit test
would require faking WireOS/GitHub discovery, TokenStore, and profile
persistence — impractical here (mirrors the design spec's own
"Testability" section, which anticipated this). Instead these tests
drive the extracted RunImportStepAsync directly and intercept the
import call via ImportRunnerOverride:

- Run decision invokes the runner with every pinned field (baseUrl,
  repo, defaultVisibility, autoSkipExclusions:true, forcePrivate:false,
  activeProfile).
- Interactive accept invokes the runner; --skip-import and no-repo
  decisions skip without invoking it or prompting.
- A non-zero return and a thrown exception from the runner both leave
  RunImportStepAsync completing normally (best-effort — setup must
  never fail because import failed).

Combined with the DecideImport unit tests added earlier, this covers
the eligibility/policy decision and the invocation contract without
needing a live server or the full wizard.
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

kcap setup: single agent-install prompt + import current-repo history

✨ Enhancement 📝 Documentation 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Replaces nine per-vendor install prompts with one default-yes “install for detected agents”
 decision.
• Adds Step 6 (default-yes) to import current-repo history via existing kcap import pipeline.
• Threads Step-3 default visibility into newly imported sessions (New-only, force-private safe).
• Introduces --skip-import; --no-prompt now auto-imports when eligible.
• Adds pure decision helpers and broad unit coverage for gating/visibility/resolved-state.
Diagram

flowchart TD
    A["SetupCommand.HandleAsync"] --> B{"DecideInstallAgents"} --> C["CodingAgentsStep.RunAsync"] --> D{"InstallAgents?"}
    D -->|"No"| E["Early return (no mutations)"]
    D -->|"Yes"| F["Per-vendor installers"]
    A --> G["Save profile/config"] --> H["AppConfig.SetResolvedState"] --> I{"DecideImport"}
    I -->|"Run"| J["RunImportStepAsync"] --> K["ImportCommand.HandleImport"] --> L["IImportSource (vendors)"] --> M[("Capacitor server")]

    subgraph Legend
        direction LR
        _p(["Process"]) ~~~ _d{"Decision"} ~~~ _e[("External")]
    end
Loading
High-Level Assessment

Reusing the existing CodingAgentsStep installers and ImportCommand.HandleImport pipeline is the right approach: it avoids duplicating import logic, preserves per-vendor --skip-* semantics by only changing the outer consent gate, and keeps the new behaviors testable via extracted pure decisions (SetupDecisions) and a single Import runner seam. The remaining key reviewer focus should be verifying the “zero mutations on decline” ordering and the --no-prompt behavioral change + --skip-import escape hatch.

Files changed (22) +2665 / -225

Enhancement (10) +263 / -28
AppConfig.csAdd 'SetResolvedState' to pin resolved server/profile in-process +18/-0

Add 'SetResolvedState' to pin resolved server/profile in-process

• Adds 'AppConfig.SetResolvedState(serverUrl, profileName, profile)' which directly assigns 'ResolvedServerUrl' and 'ResolvedProfile' without re-running precedence resolution. Used to ensure same-process follow-on actions (Step 6 import) observe the just-saved config.

src/Capacitor.Cli.Core/Config/AppConfig.cs

AntigravityImportSource.csStamp 'default_visibility' for New Antigravity imports +9/-2

Stamp 'default_visibility' for New Antigravity imports

• Builds the session-start payload once and conditionally injects 'default_visibility' for New sessions when not force-private. Preserves existing force-private behavior by guarding injection with '!ForcePrivate'.

src/Capacitor.Cli/Commands/AntigravityImportSource.cs

CopilotImportSource.csStamp 'default_visibility' for New Copilot imports +8/-1

Stamp 'default_visibility' for New Copilot imports

• Injects 'default_visibility' into the session-start payload for New sessions when not force-private, leaving existing force-private semantics unchanged.

src/Capacitor.Cli/Commands/CopilotImportSource.cs

CursorImportSource.csStamp 'default_visibility' for New Cursor imports +10/-1

Stamp 'default_visibility' for New Cursor imports

• Adds 'default_visibility' injection to the session-start payload for New sessions when not force-private. Keeps Cursor’s existing privatization approach (post-hoc/privateScope ids) from conflicting by guarding injection behind '!ForcePrivate'.

src/Capacitor.Cli/Commands/CursorImportSource.cs

GeminiImportSource.csStamp 'default_visibility' for New Gemini imports +8/-1

Stamp 'default_visibility' for New Gemini imports

• Adds New-only 'default_visibility' injection guarded by '!ForcePrivate' to align imported session starts with setup’s visibility selection.

src/Capacitor.Cli/Commands/GeminiImportSource.cs

ImportCommand.csAdd embedded-import parameters and propagate default visibility safely +30/-10

Add embedded-import parameters and propagate default visibility safely

• Extends 'HandleImport' with 'autoSkipExclusions' (prevents exclusion prompts from blocking in embedded setup) and 'defaultVisibility'. Binds auth client creation to the provided 'baseUrl', derives 'chainDefaultVisibility = forcePrivate ? null : defaultVisibility', and threads it through 'ImportChainsAsync'/'ImportSingleSessionAsync' to stamp New chain session-start payloads without leaking non-private defaults under force-private.

src/Capacitor.Cli/Commands/ImportCommand.cs

KiroImportSource.csStamp 'default_visibility' for New Kiro imports +8/-1

Stamp 'default_visibility' for New Kiro imports

• Injects 'default_visibility' on New session-start payloads when not force-private, preserving existing Kiro import lifecycle behavior.

src/Capacitor.Cli/Commands/KiroImportSource.cs

OpenCodeImportSource.csStamp 'default_visibility' for New OpenCode imports +8/-2

Stamp 'default_visibility' for New OpenCode imports

• Adds New-only 'default_visibility' injection to the session-start payload, guarded by '!ForcePrivate' to preserve existing private-stamp precedence.

src/Capacitor.Cli/Commands/OpenCodeImportSource.cs

PiImportSource.csStamp 'default_visibility' for New Pi imports +8/-1

Stamp 'default_visibility' for New Pi imports

• Injects 'default_visibility' for New Pi session-start payloads when not force-private, keeping existing force-private behavior authoritative.

src/Capacitor.Cli/Commands/PiImportSource.cs

SetupCommand.csAdd single agent-install prompt and Step 6 repo-history import +156/-9

Add single agent-install prompt and Step 6 repo-history import

• Adds '--skip-import', renumbers steps to 1/6..6/6, prints a detected-agent summary, and captures a single InstallAgents decision via 'SetupDecisions.DecideInstallAgents' to thread into 'CodingAgentsStep.Options'. After saving config, calls 'AppConfig.SetResolvedState' and runs a new Step 6 that detects current repo + checks auth, then best-effort invokes 'ImportCommand.HandleImport' via an 'ImportRunnerOverride' test seam and an 'ImportInvocation' record.

src/Capacitor.Cli/Commands/SetupCommand.cs

Refactor (3) +115 / -118
CodingAgentsStep.csReplace per-vendor prompts with unified 'InstallAgents' gate +41/-116

Replace per-vendor prompts with unified 'InstallAgents' gate

• Adds 'Options.InstallAgents' and introduces ordered early returns for (1) no agents detected and (2) 'InstallAgents=false', ensuring zero installer mutations. Removes the nine per-vendor confirmation prompts and the separate shared-skills prompt while keeping all '--skip-*' semantics and downstream gating behavior intact.

src/Capacitor.Cli/Commands/CodingAgentsStep.cs

IImportSource.csExtend 'ImportContext' with 'DefaultVisibility' +6/-2

Extend 'ImportContext' with 'DefaultVisibility'

• Adds optional 'DefaultVisibility' to 'ImportContext' so sources can stamp visibility on New sessions (while respecting 'ForcePrivate'). Updates documentation comments accordingly.

src/Capacitor.Cli/Commands/IImportSource.cs

SetupDecisions.csAdd 'SetupDecisions' (install + import decision helpers) +68/-0

Add 'SetupDecisions' (install + import decision helpers)

• New pure helper module providing (1) a stable detected-agent summary string (with Kiro consent annotation), (2) a unified install-consent decision, and (3) an import eligibility/policy decision used by Step 6.

src/Capacitor.Cli/Commands/SetupDecisions.cs

Tests (5) +1352 / -73
AppConfigResolvedStateTests.csAdd unit tests for 'AppConfig.SetResolvedState' +50/-0

Add unit tests for 'AppConfig.SetResolvedState'

• Verifies 'SetResolvedState' assigns exact server/profile values and that it wins over conflicting environment precedence (does not re-resolve). Tests are serialized due to global static state mutation.

test/Capacitor.Cli.Tests.Unit/AppConfigResolvedStateTests.cs

CodingAgentsStepTests.csUpdate tests for unified install gating semantics +252/-73

Update tests for unified install gating semantics

• Reworks tests that previously validated per-vendor declines to assert installs proceed regardless of prompt delegate output (since per-vendor prompts are removed). Adjusts fixtures to avoid the new no-agents early return and adds assertions around no-prompt counts where applicable.

test/Capacitor.Cli.Tests.Unit/CodingAgentsStepTests.cs

ImportVisibilityTests.csAdd comprehensive tests for default visibility stamping during import +777/-0

Add comprehensive tests for default visibility stamping during import

• Adds a large WireMock-driven suite validating 'default_visibility' stamping for New sessions across the chain path, orchestrator ('HandleImport'), and routed vendor sources. Covers partial/already-loaded cases, force-private precedence (including mid-stream failure scenario), and non-interactive exclusion handling expectations.

test/Capacitor.Cli.Tests.Unit/ImportVisibilityTests.cs

SetupCommandTests.csAdd tests for Step 6 'RunImportStepAsync' wiring and best-effort behavior +150/-0

Add tests for Step 6 'RunImportStepAsync' wiring and best-effort behavior

• Adds tests asserting that Step 6 pins the expected import invocation arguments, runs under '--no-prompt' without prompting, skips under missing repo / '--skip-import', and never fails setup on exceptions or non-zero import exit codes. Uses the global 'ImportRunnerOverride' seam with serialized tests.

test/Capacitor.Cli.Tests.Unit/SetupCommandTests.cs

SetupDecisionsTests.csAdd unit tests for 'SetupDecisions' +123/-0

Add unit tests for 'SetupDecisions'

• Covers 'DetectedAgentsSummary' (including Kiro annotation), 'DecideInstallAgents' (no agents/no-prompt/interactive), and 'DecideImport' (repo/auth/flags/no-prompt/interactive accept/decline) without invoking real installers or import code.

test/Capacitor.Cli.Tests.Unit/SetupDecisionsTests.cs

Documentation (4) +935 / -6
README.mdDocument unified install prompt, Step 6 import, and '--no-prompt' behavior change +9/-3

Document unified install prompt, Step 6 import, and '--no-prompt' behavior change

• Updates the setup wizard walkthrough to describe the single install prompt and new Step 6 import step. Explicitly documents that 'kcap setup --no-prompt' now auto-imports current-repo history unless '--skip-import' is passed.

README.md

2026-07-20-unified-agent-install-and-import.mdAdd implementation plan for unified install + repo import +309/-0

Add implementation plan for unified install + repo import

• Introduces a detailed task checklist describing the InstallAgents gate, Step 6 import wiring, and the visibility/auth gating rules. Serves as execution guidance for the implementation.

docs/superpowers/plans/2026-07-20-unified-agent-install-and-import.md

2026-07-20-unified-agent-install-and-import-design.mdAdd approved design spec for setup UX changes +598/-0

Add approved design spec for setup UX changes

• Adds a comprehensive design document capturing current behavior, constraints (skip semantics, zero-mutation requirement), and the intended new wizard flow including Step 6 import and visibility rules.

docs/superpowers/specs/2026-07-20-unified-agent-install-and-import-design.md

help-setup.txtUpdate 'kcap setup' help for Step 6 and '--skip-import' +19/-3

Update 'kcap setup' help for Step 6 and '--skip-import'

• Documents the new '--skip-import' flag, the single install-consent prompt behavior, and the new import-past-sessions step. Calls out the '--no-prompt' auto-import behavior change.

src/Capacitor.Cli.Core/Resources/help-setup.txt

…gibility

Address Codex review P3 findings: shared ~/.agents/skills installs for the six
consumers (Codex/Cursor/Copilot/Gemini/Pi/OpenCode) with Kiro AND Antigravity
each reading their own skills dir; and Step 6 import eligibility is 'auth
requirements satisfied' (which includes no-auth/provider-None servers), not
'authenticated'. README + help-setup.txt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@qodo-code-review

qodo-code-review Bot commented Jul 21, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Import skipped on expired token ✓ Resolved 🐞 Bug ☼ Reliability
Description
SetupCommand gates Step 6 import on TokenStore.LoadAsync() returning a non-expired token, but
ImportCommand/HttpClientExtensions can refresh expired tokens via
TokenStore.GetValidTokensAsync(). This can incorrectly skip the new default-yes auto-import for
users with refreshable expired credentials.
Code

src/Capacitor.Cli/Commands/SetupCommand.cs[R478-482]

+        // Auth requirements are satisfied when no login is required at all (provider
+        // None), or the login this run just did (or already had) produced a usable,
+        // non-expired token — not merely "provider != None" (Decision 9).
+        var authSatisfied = provider == AuthProvider.None || (finalTokens is not null && !finalTokens.IsExpired);
+
Evidence
The setup gate only checks for a non-expired stored token, while the HTTP client creation used by
import explicitly uses a refresh-capable token path (GetValidTokensAsync). This mismatch means
setup may skip import even though the subsequent import authentication path would succeed.

src/Capacitor.Cli/Commands/SetupCommand.cs[461-486]
src/Capacitor.Cli.Core/HttpClientExtensions.cs[37-57]
src/Capacitor.Cli.Core/Auth/TokenStore.cs[241-267]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`kcap setup` Step 6 computes `authSatisfied` using `TokenStore.LoadAsync()` and `!IsExpired`, which treats expired-but-refreshable tokens as unauthenticated and skips import.

### Issue Context
The import path itself authenticates via `HttpClientExtensions.CreateAuthenticatedClientAsync(baseUrl)`, which uses `TokenStore.GetValidTokensAsync()` and can refresh tokens.

### Fix Focus Areas
- src/Capacitor.Cli/Commands/SetupCommand.cs[473-486]

### Suggested fix
Replace the `finalTokens`/`IsExpired` gate with a refresh-aware check, e.g.:
- `authSatisfied = provider == AuthProvider.None || (await TokenStore.GetValidTokensAsync()) is not null;`

Optionally add a unit test covering an expired token that is refreshable (i.e., `GetValidTokensAsync()` returns non-null) to ensure Step 6 runs rather than skipping.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Setup import triggers PR detection ✓ Resolved 🐞 Bug ➹ Performance
Description
Step 6 calls RepositoryDetection.DetectRepositoryAsync(Environment.CurrentDirectory) with the
default detectPullRequest=true, even though it only needs owner/repo for gating import. This can
unnecessarily invoke provider probing/PR detection work (extra subprocesses/latency) during setup.
Code

src/Capacitor.Cli/Commands/SetupCommand.cs[R470-476]

+        // Step 6: Import past sessions
+        AnsiConsole.Write(new Rule("[yellow]Step 6/6 — Import past sessions[/]").LeftJustified());
+
+        var currentRepoDetected = await RepositoryDetection.DetectRepositoryAsync(Environment.CurrentDirectory);
+        (string Owner, string Name)? currentRepo = currentRepoDetected is { Owner: { } o, RepoName: { } n }
+            ? (o, n)
+            : null;
Evidence
The Step 6 call site uses the overload default (detectPullRequest=true). The repository detection
implementation documents and implements PR/provider probing behind that flag, and even notes that
import passes detectPullRequest:false to avoid wasted latency.

src/Capacitor.Cli/Commands/SetupCommand.cs[470-476]
src/Capacitor.Cli/RepositoryDetection.cs[119-216]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Setup Step 6 only needs `(owner, repo)` from origin, but it calls `DetectRepositoryAsync` with default PR detection enabled.

### Issue Context
`RepositoryDetection.DetectRepositoryAsync(..., detectPullRequest=true)` may run provider probing + PR/MR detection; the code explicitly notes import disables this because it’s wasted work.

### Fix Focus Areas
- src/Capacitor.Cli/Commands/SetupCommand.cs[470-476]

### Suggested fix
Change the Step 6 call to:
- `await RepositoryDetection.DetectRepositoryAsync(Environment.CurrentDirectory, detectPullRequest: false)`

This keeps setup import eligibility focused on owner/repo resolution and avoids unnecessary provider tooling round-trips.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread src/Capacitor.Cli/Commands/SetupCommand.cs
Comment thread src/Capacitor.Cli/Commands/SetupCommand.cs
realtonyyoung and others added 6 commits July 20, 2026 20:54
…wiring

Drives SetupCommand.HandleAsync end-to-end (flag parsing -> server
normalization/probe -> auth discovery -> profile save -> authSatisfied ->
Step 6) against a real WireMock server, intercepting only the final import
call via ImportRunnerOverride:

- --no-prompt + --server-url auto-imports with the pinned ImportInvocation
  (Repo/AutoSkipExclusions/ForcePrivate/DefaultVisibility), and proves auth
  provider None makes it eligible without any stored token.
- --skip-import suppresses the call entirely.
- a scheme-less --server-url reaches the runner already normalized to
  http://, exercising AppConfig.SetResolvedState + ServerUrlNormalizer.
- a conflicting KCAP_URL/KCAP_PROFILE in the environment throughout the call
  does not hijack the just-saved server/profile the import sees.

Adds HttpClientExtensions.ResetProviderCacheForTesting(), a minimal internal
test seam: DiscoverProviderAsync's in-process provider cache is keyed by
nothing but process lifetime (first caller wins for every baseUrl
afterward), so without a reset a test's own WireMock /auth/config stub could
silently become a no-op if anything else in the process had called it first.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…lreadyLoaded, forcePrivate replay, null default)

Extends ImportVisibilityTests.cs so every one of the 7 routed sources
(Cursor, Copilot, Gemini, Kiro, Pi, OpenCode, Antigravity) gets the full
status x forcePrivate x defaultVisibility matrix, not just New:

- AlreadyLoaded omits default_visibility — was missing for Gemini, Kiro, Pi,
  and Cursor (Copilot/Antigravity/OpenCode already had it).
- forcePrivate:true with a replay status (Partial) — previously only
  exercised against New for all 7 sources. Pins that the 3 sources with
  their own "private" stamp (Pi, Antigravity, OpenCode) apply it
  unconditionally regardless of status, while the other 4 (Copilot, Gemini,
  Kiro, Cursor) keep omitting the field entirely.
- defaultVisibility:null (forcePrivate:false) omits the field — had no
  routed-path coverage at all before this.

DRY via a RoutedSourceCase table + 3 shared assertion helpers across the 6
uniform-shaped sources; OpenCode gets its own two tests since its
ImportSessionAsync reads a real sqlite fixture rather than a hand-built
classification.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Address qodo review findings:
- authSatisfied now uses TokenStore.GetValidTokensAsync() (refresh-aware, same as
  the import path's own auth) instead of LoadAsync()+!IsExpired, so an expired but
  refreshable token no longer wrongly skips the auto-import.
- DetectRepositoryAsync is called with detectPullRequest:false — Step 6 only needs
  (owner, name), so PR/MR provider probing is wasted work during setup.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The refresh-aware GetValidTokensAsync() probe is awaited outside RunImportStepAsync's
best-effort try/catch and can throw real token/lock I/O errors (e.g.
UnauthorizedAccessException on a non-writable token dir), which would abort setup —
whereas the import path itself catches such errors. Extract testable
IsAuthSatisfiedAsync(provider, hasUsableToken) that treats a throwing probe as
not-satisfied (ineligible skip). Add tests: provider-None short-circuit, usable
token, no usable token, and probe-throws => false (never propagates).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reorder so IsAuthSatisfiedAsync's summary sits with it and RunImportStepAsync
keeps its own summary (the helper had been inserted between RunImportStepAsync's
doc and its signature). Comment-only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The excluded-path transcript embedded a raw absolute path in JSONL; on Windows the
backslashes produced invalid JSON, so cwd never parsed, the session wasn't
excluded, and the 'Auto-skipping' branch never ran (green on macOS/Linux, red on
windows-latest CI). Serialize the path via JsonSerializer so it's escaped on all
platforms.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@realtonyyoung
realtonyyoung merged commit c117ec5 into main Jul 21, 2026
6 checks passed
@realtonyyoung
realtonyyoung deleted the worktree-import-and-unified-install branch July 21, 2026 02:37
@linear-code

linear-code Bot commented Jul 21, 2026

Copy link
Copy Markdown

AI-1439

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

kcap setup: single agent-install prompt + import current-repo history

1 participant