Skip to content

New-harness detection and setup nudges (surfaces 1–2) - #626

Merged
realtonyyoung merged 12 commits into
mainfrom
claude-tyoung/kcap-harness-detection-c9f55a
Aug 20, 2026
Merged

realtonyyoung merged 12 commits into
mainfrom
claude-tyoung/kcap-harness-detection-c9f55a

Conversation

@realtonyyoung

Copy link
Copy Markdown
Collaborator

Detects, after initial kcap setup, that a supported coding harness (Claude, Codex, Cursor, Copilot, Gemini, Kiro, Pi, OpenCode, Antigravity) is installed but kcap is not wired into it, and prompts the user to set it up — so a harness installed later (or switched to) starts being recorded without a manual re-run of setup.

This is PR 1 of the spec's 3-PR slicing (the independently-shippable client surfaces). Surface 3 (daemon/hook-ingest harness inventory → Capacitor UI notification) and surface 4 (server-side machine-went-quiet backstop) follow in PR 2 (kcap-cli client fragment) and PR 3 (kcap-server). Design spec rides this PR: docs/superpowers/specs/2026-08-19-new-harness-detection-nudge-design.md (reviewed clean by Copilot + Kiro spec-review flows).

What's here

Core substrate

  • HarnessCatalog — the single Core table binding vendor id + label + install flag + detection selector. The desktop app's AgentVendors now re-derives from it, and a conformance test pins it against VendorSelection.KnownVendorFlags so a tenth harness fails a test rather than silently missing every surface.
  • HarnessIntegrationProbe.IsWired(vendorId, AgentDetectionInputs) — "is kcap wired into vendor X?", callable from CLI and (later) daemon. To avoid kcap status ever contradicting the nudge, Claude's enabled-plugin check and Codex's hooks-reference check moved to Core as the shared source of truth; StatusCommand delegates to them (behavior-preserving).
  • HarnessOfferLedger / HarnessOfferStore — per-machine ~/.config/kcap/harness-offers-v1.json (atomic write, corrupt→empty) plus the shared 6-hour evaluation throttle stamp.
  • HarnessNudge.Nudgeable — pure predicate: detected ∧ not wired ∧ not declined ∧ past the 7-day re-offer floor.

Surfaces

  • 1 — SessionStart nudge: HarnessNudgeEmitter fragment wired into all 9 hook commands next to the work-items nudge; tells the agent to offer kcap plugin install --<vendor>.
  • 2 — CLI stderr notice: HarnessSetupNotice, an exit-time one-liner (TTY-gated, human-facing commands only), mirroring the "update available" notice and sharing surface 1's throttle.
  • kcap status gains a passive "‹Vendor› installed but kcap not configured — run …" line (ledger-independent: status always tells the truth, even for dismissed vendors).
  • kcap harness list | dismiss | reset to inspect / silence / re-enable the nudges (all bypass the throttle).
  • kcap setup stamps the harnesses it offered, so the nudge never re-offers a vendor just seen at setup; it never writes or overwrites a dismissal.
  • Profile.DisableHarnessNudge (kcap config set disable_harness_nudge true) turns off surfaces 1–2 entirely.

Testing

  • Catalog conformance; predicate per-vendor; ledger/store incl. the dismissal-preservation invariant; IsWired; emitter (fragment / notice / throttle / multi-vendor fold / opt-out / exception→null).
  • AOT publish is clean (no IL2026/IL3050).
  • Full local suites green for the touched surface (Core Harness*, CLI Harness.* namespace, SessionStartMemory, App Agents step). The Commands.Harness Cursor tests that read the real ~/.cursor are the pre-existing local env-coupling (they fail identically on main on a machine with kcap set up for Cursor); under an isolated HOME they pass 42/42, matching CI.

README, help-usage.txt, and config set help updated in this PR.

Closes #625
AI-2118

🤖 Generated with Claude Code

realtonyyoung and others added 6 commits August 19, 2026 18:56
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Detects, after initial setup, that a supported coding harness is installed
but kcap is not wired into it, and prompts to set it up. Implements the
spec's PR 1 (independently shippable client surfaces); surfaces 3–4
(daemon/ingest inventory → server notification, and the server-side
machine-went-quiet backstop) follow in PRs 2–3.

Core substrate:
- HarnessCatalog: single Core table binding vendor id + label + install flag
  + detection selector; the App's AgentVendors re-derives from it, and a
  conformance test pins it against VendorSelection.KnownVendorFlags.
- HarnessIntegrationProbe.IsWired: "is kcap wired into vendor X?", the single
  source of truth now shared by the kcap status Hooks line (Claude's
  enabled-plugin and Codex's hooks-reference checks moved to Core; status
  delegates to them, behavior-preserving).
- HarnessOfferLedger/Store: per-machine harness-offers-v1.json (atomic write,
  corrupt→empty) plus the shared 6h evaluation throttle stamp.
- HarnessNudge.Nudgeable: pure predicate (detected ∧ !wired ∧ !declined ∧
  past the 7-day re-offer floor).

Surfaces:
- 1: HarnessNudgeEmitter fragment wired into all 9 hook commands' SessionStart
  additionalContext, next to the work-items nudge.
- 2: HarnessSetupNotice — exit-time interactive stderr notice (TTY-gated,
  human-facing commands only), mirroring the update-available notice and
  sharing surface 1's throttle.
- kcap status: passive "installed but kcap not configured" line (ledger-
  independent — always tells the truth).
- kcap harness list|dismiss|reset: inspect/silence/re-enable the nudges;
  all bypass the throttle.
- kcap setup stamps offered harnesses so it never re-nudges a vendor just
  seen at setup; never writes/overwrites a dismissal.
- Profile.DisableHarnessNudge (config key disable_harness_nudge) opts out
  surfaces 1–2 entirely.

Tests: catalog conformance, predicate per-vendor, ledger/store (incl.
dismissal-preservation), IsWired, emitter (fragment/notice/throttle/fold/
opt-out/exception→null). README + help + config help updated.

Closes #625
AI-2118

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

Copy link
Copy Markdown

PR Summary by Qodo

Detect unwired coding harnesses and nudge users to configure them

✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Detects installed, unwired coding harnesses and offers consented integration setup.
• Adds throttled session and interactive CLI notices with per-vendor dismissal controls.
• Centralizes vendor metadata and wired-state probing across setup, status, and desktop onboarding.
Diagram

graph TD
  Catalog["Harness Catalog"] --> State["Detection + Probe"] --> Predicate["Nudge Predicate"] --> Emitter["Nudge Emitter"] --> Hooks["SessionStart Hooks"]
  Store[("Offer Ledger")] --> Predicate
  Emitter --> Notice["CLI Notice"]
  State --> Commands["CLI Commands"] --> Store
Loading
High-Level Assessment

The centralized Core catalog, shared wired-state probe, and small per-machine ledger are appropriate for keeping every surface consistent without introducing a resident watcher. Separate per-surface state would risk contradictory notices, while filesystem watching or a background coordinator would add lifecycle complexity disproportionate to an occasional, inexpensive check.

Files changed (32) +1410 / -62

Enhancement (20) +635 / -50
HarnessCatalog.csCentralize supported harness metadata +35/-0

Centralize supported harness metadata

• Introduces the canonical nine-vendor catalog containing IDs, labels, install flags, and detection selectors. Provides vendor lookup for CLI management commands.

src/Capacitor.Cli.Core/Setup/HarnessCatalog.cs

HarnessIntegrationProbe.csCentralize per-vendor integration checks +74/-0

Centralize per-vendor integration checks

• Adds Core-level wired-state checks for all supported harnesses. Moves Claude plugin and Codex hook parsing behind shared helpers so status and nudges use identical semantics.

src/Capacitor.Cli.Core/Setup/HarnessIntegrationProbe.cs

HarnessNudge.csDefine the shared nudge eligibility predicate +36/-0

Define the shared nudge eligibility predicate

• Selects detected, unwired, non-dismissed harnesses whose previous offer is at least seven days old.

src/Capacitor.Cli.Core/Setup/HarnessNudge.cs

HarnessOfferLedger.csModel persistent per-harness offer state +40/-0

Model persistent per-harness offer state

• Defines versioned ledger and vendor entry models recording first-seen, last-offered, and dismissal state with source-generated JSON serialization.

src/Capacitor.Cli.Core/Setup/HarnessOfferLedger.cs

HarnessOfferStore.csPersist offers and throttle harness evaluation +96/-0

Persist offers and throttle harness evaluation

• Adds corrupt-tolerant ledger loading, atomic best-effort saves, dismissal-preserving offer stamping, and a shared filesystem-backed evaluation throttle.

src/Capacitor.Cli.Core/Setup/HarnessOfferStore.cs

AntigravityHookCommand.csInject harness nudges into Antigravity sessions +3/-2

Inject harness nudges into Antigravity sessions

• Combines new-harness setup guidance with the existing work-items nudge during SessionStart output.

src/Capacitor.Cli/Commands/Harness/AntigravityHookCommand.cs

ClaudeHookCommand.csInject harness nudges into Claude sessions +2/-1

Inject harness nudges into Claude sessions

• Adds the harness setup fragment as another component of Claude's SessionStart additional-context envelope.

src/Capacitor.Cli/Commands/Harness/ClaudeHookCommand.cs

CodexHookCommand.csInject harness nudges into Codex sessions +3/-2

Inject harness nudges into Codex sessions

• Combines harness and work-items guidance before writing Codex SessionStart output.

src/Capacitor.Cli/Commands/Harness/CodexHookCommand.cs

CopilotHookCommand.csInject harness nudges into Copilot sessions +3/-2

Inject harness nudges into Copilot sessions

• Adds harness setup guidance to Copilot's optional SessionStart JSON result alongside existing nudges.

src/Capacitor.Cli/Commands/Harness/CopilotHookCommand.cs

CursorHookCommand.csInject harness nudges into Cursor sessions +5/-3

Inject harness nudges into Cursor sessions

• Reads both nudge opt-outs from the active profile and combines harness guidance with Cursor's existing SessionStart output.

src/Capacitor.Cli/Commands/Harness/CursorHookCommand.cs

GeminiHookCommand.csInject harness nudges into Gemini sessions +3/-2

Inject harness nudges into Gemini sessions

• Combines new-harness setup guidance with Gemini's existing work-items nudge payload.

src/Capacitor.Cli/Commands/Harness/GeminiHookCommand.cs

KiroHookCommand.csInject harness nudges into Kiro sessions +3/-2

Inject harness nudges into Kiro sessions

• Adds harness setup guidance to Kiro agent-spawn output without changing the existing memory-fragment flow.

src/Capacitor.Cli/Commands/Harness/KiroHookCommand.cs

OpenCodeHookCommand.csInject independently throttled nudges into OpenCode +5/-1

Inject independently throttled nudges into OpenCode

• Adds harness guidance independently of OpenCode's once-per-session memory lease and combines it with work-items output when available.

src/Capacitor.Cli/Commands/Harness/OpenCodeHookCommand.cs

PiHookCommand.csInject harness nudges into Pi sessions +3/-2

Inject harness nudges into Pi sessions

• Combines new-harness setup guidance with Pi's existing SessionStart nudge content.

src/Capacitor.Cli/Commands/Harness/PiHookCommand.cs

HarnessCommand.csAdd harness list, dismiss, and reset commands +139/-0

Add harness list, dismiss, and reset commands

• Introduces commands to inspect detection, wiring, and dismissal state; dismiss currently relevant vendors; and clear prior offer state. Management operations intentionally bypass the evaluation throttle.

src/Capacitor.Cli/Commands/HarnessCommand.cs

SetupCommand.csStamp harnesses offered during setup +17/-0

Stamp harnesses offered during setup

• Records detected vendors shown during setup so they are not immediately re-offered. Vendors skipped by specific flags are excluded, and existing dismissals remain intact.

src/Capacitor.Cli/Commands/SetupCommand.cs

StatusCommand.csReport installed but unwired harnesses +21/-33

Report installed but unwired harnesses

• Adds ledger-independent remediation lines for detected, unconfigured harnesses. Delegates Claude and Codex integration checks to the shared Core probe.

src/Capacitor.Cli/Commands/StatusCommand.cs

HarnessNudgeEmitter.csBuild and coordinate harness setup nudges +100/-0

Build and coordinate harness setup nudges

• Implements the shared claim, detection, eligibility, stamping, and formatting pipeline for SessionStart fragments and CLI notices. Failures degrade silently to protect hooks and commands.

src/Capacitor.Cli/HarnessNudgeEmitter.cs

HarnessSetupNotice.csEmit interactive CLI harness setup notices +44/-0

Emit interactive CLI harness setup notices

• Adds a best-effort exit-time stderr notice for eligible human-facing commands. Suppresses output for redirected stderr, machine-oriented commands, status, and harness management.

src/Capacitor.Cli/HarnessSetupNotice.cs

Program.csRoute harness commands and flush setup notices +3/-0

Route harness commands and flush setup notices

• Registers the new 'harness' command group and emits eligible harness setup notices alongside update notices during command cleanup.

src/Capacitor.Cli/Program.cs

Refactor (1) +5 / -12
AgentsStepViewModel.csDerive desktop agent vendors from the Core catalog +5/-12

Derive desktop agent vendors from the Core catalog

• Replaces the app-local vendor table with projections from 'HarnessCatalog', keeping onboarding synchronized with CLI-supported harnesses.

src/Capacitor.App/ViewModels/Onboarding/AgentsStepViewModel.cs

Tests (6) +392 / -0
HarnessCatalogTests.csValidate catalog coverage and selectors +52/-0

Validate catalog coverage and selectors

• Tests vendor uniqueness, install-flag conventions, and one-to-one mapping between catalog entries and detection fields.

test/Capacitor.Cli.Core.Tests.Unit/Setup/HarnessCatalogTests.cs

HarnessIntegrationProbeTests.csTest shared harness integration probes +44/-0

Test shared harness integration probes

• Covers unknown vendors and representative Cursor and Claude wired-state detection, including absent and disabled configurations.

test/Capacitor.Cli.Core.Tests.Unit/Setup/HarnessIntegrationProbeTests.cs

HarnessNudgeTests.csTest harness nudge eligibility rules +67/-0

Test harness nudge eligibility rules

• Verifies detection, wiring, dismissal, seven-day re-offer timing, multi-vendor ordering, and per-vendor probe behavior.

test/Capacitor.Cli.Core.Tests.Unit/Setup/HarnessNudgeTests.cs

HarnessOfferStoreTests.csTest offer-ledger persistence and throttling +114/-0

Test offer-ledger persistence and throttling

• Covers missing and corrupt ledgers, persistence, throttle claims, offer timestamps, first-seen preservation, and dismissal preservation.

test/Capacitor.Cli.Core.Tests.Unit/Setup/HarnessOfferStoreTests.cs

HarnessCatalogConformanceTests.csPin the catalog to CLI vendor flags +23/-0

Pin the catalog to CLI vendor flags

• Ensures every known vendor flag has exactly one corresponding Core catalog entry, preventing future harnesses from silently missing nudge surfaces.

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

HarnessNudgeEmitterTests.csTest nudge formatting, throttling, and failure handling +92/-0

Test nudge formatting, throttling, and failure handling

• Verifies fragment and notice content, opt-out behavior, multi-vendor folding, throttle enforcement, ledger stamping, exception suppression, and nudge combination.

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

Documentation (3) +366 / -0
README.mdDocument new-harness detection and management commands +31/-0

Document new-harness detection and management commands

• Adds the 'kcap harness' command to the command index and explains session, CLI, and status notices. Documents dismissal, reset, and global opt-out behavior.

README.md

2026-08-19-new-harness-detection-nudge-design.mdSpecify the multi-surface harness nudge design +330/-0

Specify the multi-surface harness nudge design

• Defines the shared catalog, wired-state predicate, offer ledger, throttling, four delivery surfaces, rollout slicing, and testing expectations. This PR implements the first two client surfaces described by the specification.

docs/superpowers/specs/2026-08-19-new-harness-detection-nudge-design.md

help-usage.txtAdd harness nudge commands to CLI help +5/-0

Add harness nudge commands to CLI help

• Documents the 'harness list', 'dismiss', and 'reset' command forms in top-level usage output.

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

Other (2) +12 / -0
ProfileConfig.csAdd the harness-nudge profile opt-out +9/-0

Add the harness-nudge profile opt-out

• Adds the nullable 'disable_harness_nudge' profile setting to suppress both SessionStart and interactive CLI notices.

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

ConfigCommand.csExpose the harness-nudge configuration switch +3/-0

Expose the harness-nudge configuration switch

• Allows 'kcap config set disable_harness_nudge true|false' and documents the setting in command usage.

src/Capacitor.Cli/Commands/ConfigCommand.cs

@qodo-code-review

qodo-code-review Bot commented Aug 20, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. HarnessIntegrationProbe centralizes vendor logic ✓ Resolved 📘 Rule violation ⚙ Maintainability
Description
HarnessIntegrationProbe places vendor-specific wiring implementations in the shared Setup
directory and creates a second Core registration site alongside HarnessCatalog. The additional
vendor enumeration in SetupCommand also means adding a harness requires editing shared code beyond
one registration site per assembly.
Code

src/Capacitor.Cli.Core/Setup/HarnessIntegrationProbe.cs[R29-32]

+            "claude"      => ClaudePluginEnabled(Path.Combine(ClaudePaths.Home(home), "settings.json")),
+            "codex"       => CodexHooksReferenced(Path.Combine(CodexPaths.Home(home), "hooks.json")),
+            "cursor"      => CursorHooksInstaller.IsInstalled(CursorPaths.UserHooksJson(home)),
+            "copilot"     => CopilotHooksInstaller.IsInstalled(CopilotPaths.KcapHooksJson(home, inputs.CopilotHome)),
Evidence
Rules 2 and 3 require vendor behavior to remain vendor-local and permit only one registration site
per assembly. The new shared probe embeds all nine vendor mappings and Claude/Codex implementations,
while HarnessCatalog and SetupCommand separately enumerate the same vendors.

CLAUDE.md: Keep Vendor-Specific Code Isolated Under Harness/<Vendor>/ and Keep Shared Code Outside Harness/
CLAUDE.md: Adding a New Harness Should Only Require a New Harness/<Vendor>/ Directory Plus One Registration Site Per Assembly
src/Capacitor.Cli.Core/Setup/HarnessIntegrationProbe.cs[25-65]
src/Capacitor.Cli.Core/Setup/HarnessCatalog.cs[21-34]
src/Capacitor.Cli/Commands/SetupCommand.cs[423-434]

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

## Issue description
Vendor-specific wiring checks are centralized under `Setup`, creating cross-vendor coupling and multiple registration sites for each harness.

## Issue Context
Keep each wiring-check implementation under its corresponding `Harness/<Vendor>/` directory. Retain only generic dispatch outside `Harness/`, consolidate Core registration into `HarnessCatalog`, and derive setup stamping from the catalog rather than enumerating vendors again.

## Fix Focus Areas
- src/Capacitor.Cli.Core/Setup/HarnessIntegrationProbe.cs[25-73]
- src/Capacitor.Cli.Core/Setup/HarnessCatalog.cs[11-34]
- src/Capacitor.Cli/Commands/SetupCommand.cs[423-434]

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


2. Setup stamping fails compilation ✗ Dismissed 🐞 Bug ≡ Correctness
Description
OfferedIf accepts a bool, but every new call passes a DetectedAgent record rather than its
Detected boolean. This prevents the CLI project from compiling.
Code

src/Capacitor.Cli/Commands/SetupCommand.cs[R425-428]

+        OfferedIf(detected.Claude,      skipClaude,          "claude");
+        OfferedIf(detected.Codex,       skipCodexFlag,       "codex");
+        OfferedIf(detected.Cursor,      skipCursorFlag,      "cursor");
+        OfferedIf(detected.Copilot,     skipCopilotFlag,     "copilot");
Evidence
The local helper explicitly declares bool wasDetected, while the added calls pass record-valued
vendor properties. DetectedAgent defines the required boolean as its Detected property and has
no implicit boolean conversion.

src/Capacitor.Cli/Commands/SetupCommand.cs[423-433]
src/Capacitor.Cli.Core/Setup/AgentDetection.cs[35-42]

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

## Issue description
The setup offer-stamping calls pass `DetectedAgent` values to a helper that requires `bool`, causing a compilation failure.

## Issue Context
`AgentDetectionResult` stores one `DetectedAgent` per vendor; its `Detected` property is the boolean detection result.

## Fix Focus Areas
- src/Capacitor.Cli/Commands/SetupCommand.cs[423-433]

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


3. Harness commands require server ✓ Resolved 🐞 Bug ≡ Correctness
Description
harness is not in the offline-command allowlist, so on a fresh or local-only installation `kcap
harness list|dismiss|reset` exits with “No server configured” before dispatching to the local
command. This prevents users from managing the new per-machine nudge ledger unless an unrelated
server URL is configured.
Code

src/Capacitor.Cli/Program.cs[R335-336]

+    case "harness":
+        return await HarnessCommand.HandleAsync(args);
Evidence
The gate rejects any command omitted from offlineCommands when baseUrl is null. The newly
dispatched command is omitted, although its handler only constructs local detection inputs and a
local offer store.

src/Capacitor.Cli/Program.cs[159-169]
src/Capacitor.Cli/Program.cs[332-336]
src/Capacitor.Cli/Commands/HarnessCommand.cs[12-25]}]}},{: TBD

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

## Issue description
The new harness command is dispatched after the no-server gate but is absent from that gate's offline allowlist, making this purely local command unusable without `KCAP_URL`.

## Issue Context
`HarnessCommand` only performs local filesystem detection and reads/writes the local offer store.

## Fix Focus Areas
- src/Capacitor.Cli/Program.cs[159-169]
- src/Capacitor.Cli/Program.cs[332-336]

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


View high (2)
4. Agent files use exclusive reads ⊘ Outdated 📘 Rule violation ☼ Reliability
Description
The new Claude and Codex probes read harness-owned configuration with File.ReadAllText, which
denies write sharing on Windows. Concurrent agent updates to settings.json or hooks.json can
therefore be blocked while these probes run.
Code

src/Capacitor.Cli.Core/Setup/HarnessIntegrationProbe.cs[R46-47]

+            if (!File.Exists(settingsPath)) return false;
+            if (JsonNode.Parse(File.ReadAllText(settingsPath)) is not JsonObject root) return false;
Evidence
Rule 14 prohibits File.ReadAllText for agent-owned files and requires a shared-read
implementation. Both newly added probes call the prohibited API, while the repository's approved
helper explicitly uses FileShare.ReadWrite.

CLAUDE.md: Never Read Agent-Owned Files Using APIs That Deny Write Sharing (Use Shared-Read Implementations)
src/Capacitor.Cli.Core/Setup/HarnessIntegrationProbe.cs[44-61]
src/Capacitor.Cli/Commands/WatchCommand.cs[147-163]

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

## Issue description
The new integration probes use `File.ReadAllText` for files owned and written by coding harnesses, denying write sharing on Windows.

## Issue Context
Read both files through a reusable Core helper backed by `FileStream` with `FileShare.ReadWrite`. The existing CLI `WatchCommand.ReadAllTextShared` demonstrates the required semantics but cannot be referenced directly from Core.

## Fix Focus Areas
- src/Capacitor.Cli.Core/Setup/HarnessIntegrationProbe.cs[44-62]

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


5. Concurrent updates lose dismissals ✓ Resolved 🐞 Bug ☼ Reliability
Description
HarnessOfferStore.Update performs an unlocked load-modify-save, so simultaneous hook, setup,
dismiss, or reset processes can overwrite each other's ledger changes; in particular, a successfully
reported dismissal can disappear and the user will be nudged again. The shared .tmp filename also
lets concurrent Save calls interfere before the final rename.
Code

src/Capacitor.Cli.Core/Setup/HarnessOfferStore.cs[R51-54]

+    public HarnessOfferLedger Update(Func<HarnessOfferLedger, HarnessOfferLedger> mutate) {
+        var next = mutate(Load());
+        Save(next);
+        return next;
Evidence
Update reads the current file and later saves a complete replacement without synchronization, while
Save uses one deterministic temporary path. The new emitter, dismiss, and reset paths all invoke
this update mechanism from independently running processes, so stale snapshots can overwrite newer
state.

src/Capacitor.Cli.Core/Setup/HarnessOfferStore.cs[35-54]
src/Capacitor.Cli/HarnessNudgeEmitter.cs[61-68]
src/Capacitor.Cli/Commands/HarnessCommand.cs[68-94]

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

## Issue description
Concurrent offer-ledger updates use an unlocked read-modify-write sequence, allowing one process to overwrite another process's dismissal, reset, or offer stamp.

## Issue Context
Hooks are separate processes and can update the same ledger concurrently with setup and `kcap harness` commands. The deterministic temporary filename also needs protection or replacement.

## Fix Focus Areas
- src/Capacitor.Cli.Core/Setup/HarnessOfferStore.cs[35-54]
- src/Capacitor.Cli.Core/Setup/HarnessOfferStore.cs[62-75]
- src/Capacitor.Cli/Commands/HarnessCommand.cs[68-94]

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



Remediation recommended

6. Null ledger crashes commands ✓ Resolved 🐞 Bug ☼ Reliability
Description
Load accepts {"version":1,"vendors":null} as a valid HarnessOfferLedger, but list, dismiss, reset,
and StampOffered immediately dereference Vendors. This violates the documented corrupt-to-empty
behavior: management commands can crash, while nudge surfaces silently stop emitting after catching
the resulting exception.
Code

src/Capacitor.Cli.Core/Setup/HarnessOfferStore.cs[R28-29]

+            return JsonSerializer.Deserialize(File.ReadAllText(_ledgerPath), HarnessOfferLedgerJsonContext.Default.HarnessOfferLedger)
+                   ?? new HarnessOfferLedger();
Evidence
Load only checks whether the root object is null, but the JSON property can explicitly deserialize
Vendors to null despite its initializer. Every listed consumer assumes the dictionary is non-null,
and the command path has no surrounding recovery.

src/Capacitor.Cli.Core/Setup/HarnessOfferStore.cs[24-32]
src/Capacitor.Cli.Core/Setup/HarnessOfferLedger.cs[27-35]
src/Capacitor.Cli/Commands/HarnessCommand.cs[28-35]
src/Capacitor.Cli/Commands/HarnessCommand.cs[74-93]

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

## Issue description
A syntactically valid ledger containing a null `vendors` member is returned from `Load` and later causes null dereferences.

## Issue Context
The store promises malformed/corrupt state degrades to an empty ledger. Validate required members after deserialization, or normalize a null vendor dictionary to an empty dictionary before returning.

## Fix Focus Areas
- src/Capacitor.Cli.Core/Setup/HarnessOfferStore.cs[24-32]
- src/Capacitor.Cli.Core/Setup/HarnessOfferLedger.cs[27-35]
- test/Capacitor.Cli.Core.Tests.Unit/Setup/HarnessOfferStoreTests.cs[17-23]

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


7. Predicate comments restate code ✓ Resolved 📘 Rule violation ⚙ Maintainability
Description
The inline clause comments merely translate each immediately adjacent condition into prose,
duplicating the method summary and ordinary control flow. This adds commentary without explaining a
non-obvious constraint or rationale.
Code

src/Capacitor.Cli.Core/Setup/HarnessNudge.cs[R26-29]

+            if (!h.Select(detected).Detected) continue;   // clause 1: installed harness
+            if (isWired(h.VendorId)) continue;             // clause 2: kcap not wired in
+            var entry = ledger.Entry(h.VendorId);
+            if (entry is { Declined: true }) continue;     // clause 3a: not dismissed
Evidence
Rule 12 requires comments to explain non-obvious reasons rather than ordinary control flow. The
added comments label direct checks as installed, wired, and dismissed without providing additional
rationale.

CLAUDE.md: Comments Must Be Concise and Explain Non-Obvious "Why" (Avoid Paraphrasing Code)
src/Capacitor.Cli.Core/Setup/HarnessNudge.cs[25-30]

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

## Issue description
The predicate's inline comments paraphrase straightforward conditions instead of documenting non-obvious rationale.

## Issue Context
Keep the existing high-level method documentation, but remove or substantially shorten comments that only restate the adjacent code.

## Fix Focus Areas
- src/Capacitor.Cli.Core/Setup/HarnessNudge.cs[25-30]

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


Grey Divider

Context sources
Review mode: 🧠 Deep: This is a broad, behavior-changing CLI/Core feature spanning detection, persistence, throttling, nine hook surfaces, setup/status flows, and new commands, creating many independent opportunities for subtle defects.

Grey Divider

Tip of the day
💡 Did you know, you can copy the agent prompt from any finding and feed it to your IDE agent

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread src/Capacitor.Cli.Core/Setup/HarnessIntegrationProbe.cs Outdated
Comment thread src/Capacitor.Cli.Core/Setup/HarnessIntegrationProbe.cs Outdated
Comment thread src/Capacitor.Cli.Core/Setup/HarnessNudge.cs Outdated
Comment thread src/Capacitor.Cli.Core/Setup/HarnessOfferStore.cs Outdated
Comment thread src/Capacitor.Cli.Core/Setup/HarnessOfferStore.cs Outdated
Comment thread src/Capacitor.Cli/Commands/SetupCommand.cs
Comment thread src/Capacitor.Cli/Program.cs
realtonyyoung and others added 6 commits August 19, 2026 23:28
…ness, catalog delegate

- Offer ledger updates now serialize across processes via ConfigFileLock and
  write through a per-process-unique temp file, so a concurrent hook/setup/
  command can no longer overwrite another's change (notably lose a dismissal). (qodo #4)
- Load normalizes a null `vendors` member to an empty dictionary, honoring the
  corrupt-to-empty contract so management commands can't null-deref. (qodo #5)
- Claude/Codex wired-checks moved into their own installers
  (ClaudePluginInstaller.IsPluginEnabled / CodexHooksInstaller.ReferencesKcapHook)
  and read via a new Core SharedFileText (FileShare.ReadWrite), so probing never
  blocks an agent writing its own settings.json/hooks.json on Windows. (qodo #2)
- Wired-check folded into a per-entry delegate on HarnessCatalog (the single Core
  registration site); HarnessIntegrationProbe is now thin generic dispatch, no
  per-vendor switch in shared code. (qodo #1)
- `harness` added to Program.cs offline-command allowlist, so
  `kcap harness list|dismiss|reset` works with no server configured. (qodo #7)
- Trimmed predicate comments that restated the code. (qodo #3)
- Added null-vendors ledger test.

(qodo #6 — "setup stamping fails compilation" — is a false positive: `detected`
is CodingAgentsStep.DetectedAgents whose members are bool, and CI's build passed.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…osed throttle, honest dismiss, snapshot-consistent wiring

- Offer-ledger Update takes a lock timeout; the hook/best-effort StampOffered path
  uses a short 1s wait so a contended ledger can't stall a SessionStart hook past a
  host's ~5s budget (management commands keep the 5s wait). (Codex P1 #1)
- SharedFileText opens with FileShare.Delete too, so a concurrent read can't make
  the ledger's atomic File.Move-overwrite fail with a sharing violation on Windows
  (which would silently drop a dismissal). (Codex P1 #2)
- TryClaimCheck now fails CLOSED: if the throttle stamp can't be written the same
  failure blocks the ledger stamp, so returning true would nudge on every hook —
  suppress instead. (Codex P1 #3)
- Update returns whether the change persisted; `kcap harness dismiss|reset` surface
  a false as exit 1 instead of falsely reporting success (a lost dismissal would
  otherwise silently revive the nudge). (Codex P1 #4)
- Kiro/Pi/OpenCode wired-probes now thread the injected KiroHome/PiAgentDir/
  OpenCodeConfigDir overrides (new optional params on the *Paths helpers) so
  detection and wiring consume the same AgentDetectionInputs snapshot, per the
  Core API contract. (Codex P2 #5)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ck, pure snapshot wiring

- On lock-acquire failure Update no longer does a lockless read-modify-write
  (which could recreate the lost-dismissal race); it returns false without
  mutating. Hook callers ignore it (best-effort); dismiss/reset already report
  false as exit 1. (Codex R2 P1 #1)
- Hook-path stamping uses a zero-wait (non-blocking) lock try, so a SessionStart
  hook spends none of its exit-budget safety reserve waiting on the mutex; on
  contention it simply skips stamping. (Codex R2 P1 #2)
- Kiro/Pi/OpenCode wired-probes resolve paths via new pure helpers
  (KcapAgentJsonPure / KcapExtensionPure / KcapPluginPure) built from the existing
  *Pure roots, so a null injected override means "unset → home default" and never
  re-reads ambient env — honoring AgentDetectionInputs' null-as-unset contract and
  keeping detection and wiring on the same snapshot. Reverted the round-1 non-pure
  override params (superseded). Added a Kiro override-threading test. (Codex R2 P2 #3)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- StampOffered takes a lockTimeout; the hook path passes TimeSpan.Zero while
  setup omits it and gets the normal serialized 5s wait, so a momentary
  concurrent writer no longer makes setup silently skip stamping the offered
  vendors (which would drop the 7-day floor). (Codex R3 P2 #1)
- Copilot/Gemini/Antigravity wired-probes now use pure path helpers
  (KcapHooksJsonPure / SettingsJsonPure / GlobalHooksJsonPure) built from the
  existing *Pure roots, so every override the snapshot carries is resolved
  without an ambient env re-read. Claude/Codex necessarily read
  CLAUDE_CONFIG_DIR/CODEX_HOME from ambient because those roots aren't part of
  AgentDetectionInputs (detection is PATH-only for them) — documented as the sole,
  production-coincident exception on HarnessCatalog. (Codex R3 P2 #2)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
GlobalHooksJsonPure built from RootPure (<gemini>/antigravity, the data root),
but production GlobalHooksJson resolves through GuiConfigRoot (<gemini>/config),
so a normally-wired Antigravity install was probed at the wrong path and reported
unwired — spurious nudges while `kcap status` said configured. Add GuiConfigRootPure
(<gemini>/config) and build the pure hooks path from it, mirroring production.
Add HarnessWiredPathParityTests pinning every pure wiring path to its production
layout so a wrong root/segment fails a test. (Codex R4 P1)

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

# Conflicts:
#	src/Capacitor.Cli.Core/Harness/Kiro/KiroPaths.cs
@realtonyyoung
realtonyyoung merged commit 11b6865 into main Aug 20, 2026
5 checks passed
@realtonyyoung
realtonyyoung deleted the claude-tyoung/kcap-harness-detection-c9f55a branch August 20, 2026 12:25
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.

Detect newly installed harnesses and nudge to set up kcap for them

1 participant