Repository navigation
kcap setup: one agent-install prompt + import current-repo history - #335
Conversation
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.
PR Summary by Qodokcap setup: single agent-install prompt + import current-repo history
AI Description
Diagram
High-Level Assessment
Files changed (22)
|
…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>
Code Review by Qodo
1.
|
…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>
What & why
Two UX changes to the
kcap setuponboarding 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.
InstallAgentsdecision (SetupDecisions.DecideInstallAgents) is threaded intoCodingAgentsStep.Options.RunAsynchas two ordered early-returns: no-agents (existing warning) and!InstallAgents(zero mutations — before any installer,SweepLegacyCodexSkills, orselectedassignment).--skip-*semantic is preserved unchanged (incl. the Kiro flag coupling). The Codex network-access and provider-API-key prompts remain separate.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.Noneor a usable token).!ForcePrivate; chain zeroes it under force-private in the orchestrator to avoid a partial-failure leak). Existing per-source--privatebehavior is untouched.AppConfig.SetResolvedStaterefreshes the in-process server URL + profile to the exact saved values before the import, andHandleImportbinds auth to the passedbaseUrl.Console.ReadLine).kcap setup --no-promptnow 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-importflag. Documented in README +help-setup.txt.Testing
ImportVisibilityTests,SetupDecisionsTests,AppConfigResolvedStateTests, plusCodingAgentsStepTests/SetupCommandTestsadditions.IL3050/IL2026warnings.Reviewer note
Unit runs occasionally log a post-summary
ObjectDisposedExceptionfrom the pre-existing title-generationbackgroundTasks/concurrencyLimitorchestration inHandleImport(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