Skip to content

[AI-1887] Emit judge-fact guidelines at session start for all harnesses - #538

Merged
realtonyyoung merged 1 commit into
mainfrom
ai-1887-guideline-injection-all-vendors
Aug 12, 2026
Merged

[AI-1887] Emit judge-fact guidelines at session start for all harnesses#538
realtonyyoung merged 1 commit into
mainfrom
ai-1887-guideline-injection-all-vendors

Conversation

@realtonyyoung

Copy link
Copy Markdown
Collaborator

What

SessionStart guideline injection (## Known patterns / ## Guidance from past sessions, evaluation-derived fact clusters) reached only Claude Code, which reads top_clusters off its hook POST response. The other eight harnesses got the team-memory index but no guidelines.

This adds a parallel guidelines lane to the existing per-vendor memory orchestration and composes one combined fragment (marker → ## Team memory → guidelines) that rides each harness's existing delivery seam unchanged — no plugin/extension/envelope changes.

How

  • ISessionStartContextProvider seam on the orchestrator; SessionStartContextFetch shares the authenticated-GET / 401-refresh / 256 KiB bounded-read transport between the two lanes (byte-for-byte what the memory lane did before).
  • SessionStartGuidelinesLane fetches GET /api/repositories/{hash}/guidelines and maps 404 → retryable — the visibility race: a 404 means "not visible yet" (the endpoint gates on a projected caller-visible session), not "no facts". The memory lane keeps 404 → empty; this deliberate divergence is why it's a separate lane.
  • SessionStartCompositeContextProvider resolves the repo/machine scope once (matters under Cursor's 2s budget) and runs both lanes in parallel, combining over enabled lanes only: any content ⇒ commit; all empty ⇒ complete-without-context; no content + ≥1 retryable ⇒ retry with the max of the lanes' Retry-After hints. A disabled lane contributes nothing and never blocks commit. Marker-first compose: the guidelines-only case prepends the shared kcap-memory-index marker so Pi/OpenCode capture (stdout must OPEN with it) is unchanged.
  • The orchestrator's early-return becomes both-disabled; every non-Claude adapter routes through SessionStartMemoryHookSupport.CompositeProvider and passes the guidelines opt-out.
  • Claude is untouched except reading disable_session_guidelines from the effective profile (the pre-existing KCAP_URL/--server-url defect where ResolvedProfile.Profile is null) — it keeps its memory-only provider and issues no guidelines GET (pinned by a test).
  • Cursor gains effective-profile threading placed inside its HandleCore deadline race (so a slow config read can't blow the 2s dispatcher contract — HandleCore abandons the computation on the deadline), plus the same DisableMemoryIndex effective-profile fix.

No CLI-side size cap (matches Claude; the server clamps row count + text length).

Server side

The endpoint GET /api/repositories/{hash}/guidelines already exists. Its response contract + a GuidelineInjectionConfig size-bound validator are hardened in the companion kcap-server PR (independent, non-blocking in either direction — this CLI change is well-defined against any server version/config: an over-256 KiB or missing response is a fail-open retryable lane failure, never a crash, and never blocks a delivered memory lane).

Tests

New GuidelinesLaneAndCompositeTests (17): guidelines-lane status mapping incl. 404 → retryable, composite disposition matrix + lease/compose rules, Retry-After max aggregation, marker-first compose, disabled-lane isolation, emitter row overload. Plus a ClaudeHookCommandTests isolation test proving Claude issues no guidelines GET. All existing per-vendor memory suites (138) + foundation (31) + Cursor (42) + Codex source (16) unchanged and green.

🤖 Generated with Claude Code

@linear-code

linear-code Bot commented Aug 12, 2026

Copy link
Copy Markdown

AI-1887

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Emit session-start guidelines for all harnesses via composite context provider

✨ Enhancement 🐞 Bug fix 🧪 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Add a SessionStart guidelines fetch lane and combine it with the team-memory index.
• Route all non-Claude harnesses through a composite provider without changing delivery seams.
• Fix effective-profile opt-out reads for guidelines/memory under KCAP_URL/--server-url.
Diagram

graph TD
  A["Harness adapters"] --> B(["SessionStart orchestrator"])
  B --> C(["ISessionStartContextProvider"])
  C --> D(["Memory lane"])
  C --> E(["Guidelines lane"])
  D --> F["SessionStartContextFetch"] --> G{{"kcap server API"}}
  E --> F
  G --> H{{"GET /api/memories/index"}}
  G --> I{{"GET /api/repositories/{hash}/guidelines"}}

  subgraph Legend
    direction LR
    _mod["CLI module"] ~~~ _svc(["Provider/lane"]) ~~~ _ext{{"HTTP endpoint"}}
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Extend /api/memories/index to also return guidelines
  • ➕ Single round-trip and single status-mapping policy surface
  • ➕ Fewer moving parts in CLI (no composite provider needed)
  • ➖ Couples two independently-scoped data products (repo guidelines vs repo+machine memory index)
  • ➖ Makes divergent 404 semantics harder to express cleanly (guidelines 404=retry vs memory 404=empty)
  • ➖ Requires server contract change and coordinated rollout risk
2. Inline guidelines logic into SessionStartMemoryContextProvider with flags
  • ➕ Fewer new types than introducing a second lane + composite provider
  • ➕ Keeps provider construction sites unchanged
  • ➖ Mixes two different endpoint contracts and error semantics in one class
  • ➖ Harder to test independently (lane-level tests become more entangled)
  • ➖ Encourages accidental Claude routing through the same codepath

Recommendation: The PR’s composite-provider + two-lane design is the best fit: it preserves existing per-harness delivery seams, shares transport code, and cleanly encodes the intentional divergence in 404 handling (memory=empty, guidelines=retryable) while keeping Claude isolated from the new guidelines GET path.

Files changed (24) +734 / -139

Enhancement (13) +295 / -50
AntigravityHookCommand.csRoute Antigravity SessionStart through composite provider with guidelines opt-out +7/-7

Route Antigravity SessionStart through composite provider with guidelines opt-out

• Threads disable_session_guidelines into the SessionStart request and switches provider construction to SessionStartMemoryHookSupport.CompositeProvider. Updates the early short-circuit to only skip when both memory and guidelines are disabled.

src/Capacitor.Cli/Commands/AntigravityHookCommand.cs

CodexHookCommand.csEnable guidelines injection for Codex via composite provider +8/-6

Enable guidelines injection for Codex via composite provider

• Adds a guidelinesDisabled parameter throughout the SessionStart handshake path and uses CompositeProvider instead of the memory-only provider. Updates request construction so memory and guidelines can be independently enabled/disabled.

src/Capacitor.Cli/Commands/CodexHookCommand.cs

CopilotHookCommand.csEnable guidelines injection for Copilot via composite provider +7/-7

Enable guidelines injection for Copilot via composite provider

• Threads guidelinesDisabled into the memory task and orchestrator request, and switches provider wiring to CompositeProvider. Keeps existing commit-gate behavior while allowing guidelines to be injected through the same seam as the memory index.

src/Capacitor.Cli/Commands/CopilotHookCommand.cs

GeminiHookCommand.csEnable guidelines injection for Gemini via composite provider +8/-6

Enable guidelines injection for Gemini via composite provider

• Adds guidelinesDisabled threading through lifecycle/provider setup and routes the SessionStart orchestration via CompositeProvider. Ensures session-start fetching is only skipped when both lanes are disabled.

src/Capacitor.Cli/Commands/GeminiHookCommand.cs

KiroHookCommand.csEnable guidelines injection for Kiro via composite provider +7/-7

Enable guidelines injection for Kiro via composite provider

• Threads guidelinesDisabled into the agent-spawn SessionStart path and switches provider wiring to CompositeProvider. Aligns short-circuit logic to require both lanes disabled before skipping.

src/Capacitor.Cli/Commands/KiroHookCommand.cs

OpenCodeHookCommand.csEnable guidelines injection for OpenCode via composite provider +8/-7

Enable guidelines injection for OpenCode via composite provider

• Adds guidelinesDisabled to the StartMemoryIndexTask and lifecycle/provider setup, using CompositeProvider for combined fragment emission. Adds an explicit comment about both-lanes-off short-circuit behavior to preserve stdout marker capture expectations.

src/Capacitor.Cli/Commands/OpenCodeHookCommand.cs

PiHookCommand.csEnable guidelines injection for Pi via composite provider +7/-5

Enable guidelines injection for Pi via composite provider

• Threads guidelinesDisabled through SessionStart fetch orchestration and uses CompositeProvider. Preserves marker-first stdout behavior by relying on composite composition rules when guidelines are the only content.

src/Capacitor.Cli/Commands/PiHookCommand.cs

SessionGuidelinesEmitter.csAdd guidelines-row overload shared by non-Claude lane +27/-4

Add guidelines-row overload shared by non-Claude lane

• Refactors guideline formatting into a shared core that accepts either Claude hook JSON nodes or typed guideline rows. Adds a BuildFragment(GuidelineRow[]) overload to ensure byte-identical formatting across harnesses and keeps server-side sizing responsibility.

src/Capacitor.Cli/SessionGuidelinesEmitter.cs

SessionStartCompositeContextProvider.csAdd composite provider running memory and guidelines lanes in parallel +108/-0

Add composite provider running memory and guidelines lanes in parallel

• Implements parallel lane execution under a shared budget and single scope resolution, combining results into a single fragment. Encodes disposition rules (commit if any content; empty if all enabled lanes empty; retry if any enabled lane retryable) and marker-first composition for guidelines-only cases.

src/Capacitor.Cli/SessionStartMemory/SessionStartCompositeContextProvider.cs

SessionStartGuidelinesLane.csImplement guidelines lane fetching /api/repositories/{hash}/guidelines +63/-0

Implement guidelines lane fetching /api/repositories/{hash}/guidelines

• Adds a lane that fetches repo-scoped guideline rows, deserializes the response, and renders a marker-less guidelines fragment. Treats 404 as retryable (visibility race) while mapping 204/400 to empty to fail open on contract issues.

src/Capacitor.Cli/SessionStartMemory/SessionStartGuidelinesLane.cs

SessionStartMemoryContracts.csAdd GuidelinesDisabled flag to SessionStart context request +6/-1

Add GuidelinesDisabled flag to SessionStart context request

• Extends SessionStartMemoryContextRequest with an independent guidelines opt-out flag defaulting to true, keeping Claude’s memory-only call sites guidelines-off by default. Enables non-Claude adapters to explicitly opt in/out of guidelines without affecting memory index behavior.

src/Capacitor.Cli/SessionStartMemory/SessionStartMemoryContracts.cs

SessionStartMemoryHookSupport.csAdd CompositeProvider factory for non-Claude harnesses +27/-0

Add CompositeProvider factory for non-Claude harnesses

• Adds a single construction site that wires scope resolution once and shares the authenticated client factory across both lanes. Documents lane enablement via request flags and explicitly keeps Claude on the memory-only provider.

src/Capacitor.Cli/SessionStartMemory/SessionStartMemoryHookSupport.cs

SessionStartMemoryJsonContext.csAdd JSON contracts for guidelines endpoint response +12/-0

Add JSON contracts for guidelines endpoint response

• Extends the source-generated JSON context with GuidelinesResponse and GuidelineRow records. Pins snake_case property names to match the server contract used by the guidelines lane.

src/Capacitor.Cli/SessionStartMemory/SessionStartMemoryJsonContext.cs

Bug fix (2) +24 / -9
ClaudeHookCommand.csFix guideline opt-out to use effective profile under KCAP_URL/--server-url +7/-1

Fix guideline opt-out to use effective profile under KCAP_URL/--server-url

• Changes disable_session_guidelines evaluation to read from the already-resolved active profile rather than AppConfig.ResolvedProfile?.Profile. Prevents silently ignoring guideline opt-out when server URL overrides cause ProfileResolver to return a null Profile.

src/Capacitor.Cli/Commands/ClaudeHookCommand.cs

CursorHookCommand.csUse effective profile inside Cursor deadline race; add guidelines lane +17/-8

Use effective profile inside Cursor deadline race; add guidelines lane

• Moves active-profile resolution into the HandleCore deadline-raced path so slow config reads cannot violate the 2s dispatcher contract. Fixes disable_memory_index/disable_session_guidelines reads under KCAP_URL/--server-url and routes Cursor through CompositeProvider with correct disposal semantics for the shared client.

src/Capacitor.Cli/Commands/CursorHookCommand.cs

Refactor (4) +135 / -70
ISessionStartContextProvider.csIntroduce provider seam for orchestrator context fetching +13/-0

Introduce provider seam for orchestrator context fetching

• Adds an internal interface to decouple SessionStartMemoryOrchestrator from a specific provider implementation. Enables swapping between memory-only and composite (memory+guidelines) providers without changing lease/lifecycle logic.

src/Capacitor.Cli/SessionStartMemory/ISessionStartContextProvider.cs

SessionStartContextFetch.csExtract shared authenticated GET/refresh/bounded-read transport +85/-0

Extract shared authenticated GET/refresh/bounded-read transport

• Centralizes HTTP GET behavior used by SessionStart context lanes: single 401-refresh retry, 256 KiB bounded read, and Retry-After parsing. Leaves status mapping to callers so memory vs guidelines can intentionally diverge on 404 handling.

src/Capacitor.Cli/SessionStartMemory/SessionStartContextFetch.cs

SessionStartMemoryContextProvider.csMake memory provider implement seam and reuse shared transport +32/-68

Make memory provider implement seam and reuse shared transport

• Implements ISessionStartContextProvider and extracts scope-resolved fetching into FetchWithScopeAsync for reuse by the composite provider. Replaces inlined HTTP logic with SessionStartContextFetch while preserving prior behavior and error handling.

src/Capacitor.Cli/SessionStartMemory/SessionStartMemoryContextProvider.cs

SessionStartMemoryOrchestrator.csOrchestrator supports composite providers and both-disabled early return +5/-2

Orchestrator supports composite providers and both-disabled early return

• Changes the orchestrator to depend on ISessionStartContextProvider rather than the memory-only provider. Updates the early-return condition to skip only when both memory and guidelines are disabled, allowing single-lane injection and avoiding unnecessary lease spending.

src/Capacitor.Cli/SessionStartMemory/SessionStartMemoryOrchestrator.cs

Tests (4) +279 / -9
AntigravitySessionStartMemoryTests.csUpdate Antigravity session-start tests for two-lane disable semantics +7/-5

Update Antigravity session-start tests for two-lane disable semantics

• Adjusts guard-condition tests to reflect that fetching is skipped only when both lanes are disabled, while scope/budget/url guards still suppress regardless of lane enablement. Updates factory-throw behavior test to pass the new guidelinesDisabled parameter.

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

ClaudeHookCommandTests.csPin that Claude never calls guidelines GET endpoint +21/-0

Pin that Claude never calls guidelines GET endpoint

• Adds a regression test ensuring Claude continues to source guidelines exclusively from its hook POST response (top_clusters) and never issues /api/repositories/{hash}/guidelines. Protects against accidental routing through the composite provider.

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

PiSessionStartMemoryTests.csUpdate Pi session-start tests for two-lane disable semantics +6/-4

Update Pi session-start tests for two-lane disable semantics

• Updates short-circuit tests to pass guidelinesDisabled and clarifies that both lanes must be disabled to suppress fetching. Keeps validation that url/scope/budget guards still prevent execution.

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

GuidelinesLaneAndCompositeTests.csAdd unit tests for guidelines lane, composite disposition, and emitter overload +245/-0

Add unit tests for guidelines lane, composite disposition, and emitter overload

• Introduces focused tests covering guidelines fetch status mapping (including 404→retryable), marker-less rendering, composite composition/ordering rules, Retry-After max selection, and per-lane disable behavior. Also tests the new SessionGuidelinesEmitter overload that groups rows by category.

test/Capacitor.Cli.Tests.Unit/SessionStartMemory/GuidelinesLaneAndCompositeTests.cs

Documentation (1) +1 / -1
README.mdDocument cross-harness SessionStart guideline delivery +1/-1

Document cross-harness SessionStart guideline delivery

• Updates the SessionStart context injection description to reflect that guidelines are now delivered for all supported harnesses. Clarifies Claude reads from hook response while others fetch /api/repositories/{hash}/guidelines alongside the memory index, and reiterates the opt-out flag.

README.md

@qodo-code-review

qodo-code-review Bot commented Aug 12, 2026

Copy link
Copy Markdown

Code Review by Qodo

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

Grey Divider


Remediation recommended

1. Cursor budget misaccounted 🐞 Bug ☼ Reliability ⭐ New
Description
CursorHookCommand snapshots memBudget before awaiting GetActiveProfileAsync(), so profile-read time
is not deducted from the remaining session-start budget and the subsequent context fetch can run
longer than the intended leftover window.
Code

src/Capacitor.Cli/Commands/CursorHookCommand.cs[R551-554]

+        var activeProfile      = await AppConfig.GetActiveProfileAsync();
+        var disabled           = activeProfile?.DisableMemoryIndex is true;
+        var guidelinesDisabled = activeProfile?.DisableSessionGuidelines is true;
+        if (disabled && guidelinesDisabled) return null;
Evidence
The code computes the remaining budget (memBudget) before awaiting the profile load, but then uses
that pre-await budget to bound cancellation and to populate the request budget passed into the
orchestrator. This means elapsed time in the profile read is not reflected in the time window
allocated to the context fetch.

src/Capacitor.Cli/Commands/CursorHookCommand.cs[542-544]
src/Capacitor.Cli/Commands/CursorHookCommand.cs[551-554]
src/Capacitor.Cli/Commands/CursorHookCommand.cs[557-559]
src/Capacitor.Cli/Commands/CursorHookCommand.cs[589-590]
src/Capacitor.Cli/Commands/CursorHookCommand.cs[509-519]

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

### Issue description
`RunMemoryOrchestrationAsync` computes `memBudget` before awaiting `AppConfig.GetActiveProfileAsync()`. The awaited profile load can consume non-trivial time, but `memBudget` is later used to bound `memCts.CancelAfter(memBudget)` and is passed into `SessionStartMemoryContextRequest`, so the network/context work can receive a larger budget than what is actually left.

### Issue Context
This method’s design intent (per comments) is that memory/guidelines work runs strictly on “whatever budget is left over”. That intent is currently undermined by doing the async profile read after capturing `memBudget`.

### Fix Focus Areas
- src/Capacitor.Cli/Commands/CursorHookCommand.cs[542-590]

### Suggested fix
Move the active-profile read earlier (before computing `memBudget`), or recompute `memBudget` after the `await` using an updated `sw.Elapsed` (preserving `memoryBudgetOverride` semantics if it’s intended to be absolute). Then keep the existing `memBudget <= 0` guard and continue using the recomputed budget for `CancelAfter` and `SessionStartMemoryContextRequest`.

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


2. Verbose XML doc for overload 📘 Rule violation ⚙ Maintainability ⭐ New
Description
The new BuildFragment(IReadOnlyList<GuidelineRow>?) overload includes an overly long XML doc
comment describing endpoint behavior and size limits. This should be shortened to the essential
contract and any non-obvious constraints.
Code

src/Capacitor.Cli/SessionGuidelinesEmitter.cs[R36-39]

+    /// <summary>
+    /// Overload for the guidelines lane: the eight non-Claude harnesses
+    /// fetch <c>GET /api/repositories/{hash}/guidelines</c> directly and pass the
+    /// parsed rows here, rather than reading <c>top_clusters</c> from a hook
Evidence
PR Compliance ID 5 prohibits overly verbose comments; the added XML docs are long and operationally
detailed (endpoint URL, cross-harness behavior, size-cap rationale), which can be reduced without
losing clarity.

CLAUDE.md: Keep code comments minimal and prefer self-explanatory code
src/Capacitor.Cli/SessionGuidelinesEmitter.cs[36-43]

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

## Issue description
New XML documentation is overly verbose and includes operational details that can live elsewhere.

## Issue Context
PR Compliance requires minimal comments and self-explanatory code.

## Fix Focus Areas
- src/Capacitor.Cli/SessionGuidelinesEmitter.cs[36-43]

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


3. Uncancellable Cursor profile load ✓ Resolved 🐞 Bug ☼ Reliability
Description
CursorHookCommand now calls AppConfig.GetActiveProfileAsync() inside HandleCoreInner (which can be
abandoned on the 2s deadline), but that call cannot observe the dispatcher CancellationToken, so
config.json reads can continue after the deadline path returns.
Code

src/Capacitor.Cli/Commands/CursorHookCommand.cs[R551-554]

+        var activeProfile      = await AppConfig.GetActiveProfileAsync();
+        var disabled           = activeProfile?.DisableMemoryIndex is true;
+        var guidelinesDisabled = activeProfile?.DisableSessionGuidelines is true;
+        if (disabled && guidelinesDisabled) return null;
Evidence
The Cursor hook runs HandleCoreInner under a deadline race and abandons it on timeout, but the
newly-added profile load has no cancellation path: RunMemoryOrchestrationAsync awaits
AppConfig.GetActiveProfileAsync() with no token, and GetActiveProfileAsync itself has no
CancellationToken parameter and falls back to LoadProfileConfig() without passing a token, even
though LoadProfileConfig supports cancellation via its ct parameter.

src/Capacitor.Cli/Commands/CursorHookCommand.cs[153-193]
src/Capacitor.Cli/Commands/CursorHookCommand.cs[546-555]
src/Capacitor.Cli.Core/Config/AppConfig.cs[398-402]
src/Capacitor.Cli.Core/Config/AppConfig.cs[282-290]

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

### Issue description
`CursorHookCommand.RunMemoryOrchestrationAsync` loads the active profile inside `HandleCoreInner`, which is raced against a hard deadline in `HandleCore`. On the deadline branch, `HandleCoreInner` is *abandoned* (not awaited), and while the outer CTS is canceled, `AppConfig.GetActiveProfileAsync()` does not accept a `CancellationToken`, so the config read cannot observe cancellation and may continue in the background.

### Issue Context
- `HandleCore` uses a deadline race and explicitly documents that the inner task is abandoned on the deadline branch.
- `RunMemoryOrchestrationAsync` introduced a new `GetActiveProfileAsync` call (no ct) in the Cursor path.
- `AppConfig.GetActiveProfileAsync()` calls `LoadProfileConfig()` without threading any token.

### Fix Focus Areas
- src/Capacitor.Cli/Commands/CursorHookCommand.cs[546-555]
- src/Capacitor.Cli.Core/Config/AppConfig.cs[398-402]
- src/Capacitor.Cli.Core/Config/AppConfig.cs[282-290]

### Suggested fix
1. Add an overload `GetActiveProfileAsync(CancellationToken ct)` (or extend the existing method with an optional `CancellationToken ct = default`).
2. When `ResolvedProfile?.Profile` is null, call `LoadProfileConfig(ct)`.
3. In `CursorHookCommand.RunMemoryOrchestrationAsync`, pass the dispatcher/linked token (e.g., `dispatcherCt`) into `GetActiveProfileAsync(...)`.
4. Keep the existing parameterless signature for non-deadline call sites to avoid broad churn, or update call sites gradually.

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


View review recommended (1)
4. Verbose activeProfile comment ✓ Resolved 📘 Rule violation ⚙ Maintainability
Description
A lengthy multi-line comment was added explaining profile resolution and timing behavior, which is
overly verbose for inline code and reduces readability. Prefer a shorter intent-focused comment and
let naming/structure convey most of this detail.
Code

src/Capacitor.Cli/Commands/CursorHookCommand.cs[R546-549]

+        // The EFFECTIVE profile, not AppConfig.ResolvedProfile?.Profile: ProfileResolver returns a
+        // null Profile whenever --server-url or KCAP_URL wins, so the resolved read silently ignored
+        // the opt-outs for every KCAP_URL user. This read runs INSIDE HandleCoreInner — itself inside
+        // HandleCore's deadline race — so even a slow config file read cannot blow the 2s dispatcher
Evidence
PR Compliance ID 5 requires concise comments that add intent without excessive narrative. The added
comment block at the cited location is multi-line and highly detailed, making the code harder to
scan and maintain.

CLAUDE.md: Keep comments minimal; prefer self-explanatory code
src/Capacitor.Cli/Commands/CursorHookCommand.cs[546-550]

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

## Issue description
Newly added inline comments are overly verbose and include extensive background/context that can live in docs, commit message, or a short summary comment.

## Issue Context
Compliance requires keeping comments minimal and preferring self-explanatory code. The added comment explains multiple implementation details (effective profile behavior, config read timing, deadline race) in-line.

## Fix Focus Areas
- src/Capacitor.Cli/Commands/CursorHookCommand.cs[546-550]

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


Grey Divider

Tip of the day
💡 Did you know, you can enable the Remediation agent and Qodo fixes findings in a dedicated fix PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Previous review results

Review updated until commit 4045f67

Results up to commit 26c903b ⚖️ Balanced


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


Remediation recommended
1. Uncancellable Cursor profile load ✓ Resolved 🐞 Bug ☼ Reliability
Description
CursorHookCommand now calls AppConfig.GetActiveProfileAsync() inside HandleCoreInner (which can be
abandoned on the 2s deadline), but that call cannot observe the dispatcher CancellationToken, so
config.json reads can continue after the deadline path returns.
Code

src/Capacitor.Cli/Commands/CursorHookCommand.cs[R551-554]

+        var activeProfile      = await AppConfig.GetActiveProfileAsync();
+        var disabled           = activeProfile?.DisableMemoryIndex is true;
+        var guidelinesDisabled = activeProfile?.DisableSessionGuidelines is true;
+        if (disabled && guidelinesDisabled) return null;
Evidence
The Cursor hook runs HandleCoreInner under a deadline race and abandons it on timeout, but the
newly-added profile load has no cancellation path: RunMemoryOrchestrationAsync awaits
AppConfig.GetActiveProfileAsync() with no token, and GetActiveProfileAsync itself has no
CancellationToken parameter and falls back to LoadProfileConfig() without passing a token, even
though LoadProfileConfig supports cancellation via its ct parameter.

src/Capacitor.Cli/Commands/CursorHookCommand.cs[153-193]
src/Capacitor.Cli/Commands/CursorHookCommand.cs[546-555]
src/Capacitor.Cli.Core/Config/AppConfig.cs[398-402]
src/Capacitor.Cli.Core/Config/AppConfig.cs[282-290]

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

### Issue description
`CursorHookCommand.RunMemoryOrchestrationAsync` loads the active profile inside `HandleCoreInner`, which is raced against a hard deadline in `HandleCore`. On the deadline branch, `HandleCoreInner` is *abandoned* (not awaited), and while the outer CTS is canceled, `AppConfig.GetActiveProfileAsync()` does not accept a `CancellationToken`, so the config read cannot observe cancellation and may continue in the background.

### Issue Context
- `HandleCore` uses a deadline race and explicitly documents that the inner task is abandoned on the deadline branch.
- `RunMemoryOrchestrationAsync` introduced a new `GetActiveProfileAsync` call (no ct) in the Cursor path.
- `AppConfig.GetActiveProfileAsync()` calls `LoadProfileConfig()` without threading any token.

### Fix Focus Areas
- src/Capacitor.Cli/Commands/CursorHookCommand.cs[546-555]
- src/Capacitor.Cli.Core/Config/AppConfig.cs[398-402]
- src/Capacitor.Cli.Core/Config/AppConfig.cs[282-290]

### Suggested fix
1. Add an overload `GetActiveProfileAsync(CancellationToken ct)` (or extend the existing method with an optional `CancellationToken ct = default`).
2. When `ResolvedProfile?.Profile` is null, call `LoadProfileConfig(ct)`.
3. In `CursorHookCommand.RunMemoryOrchestrationAsync`, pass the dispatcher/linked token (e.g., `dispatcherCt`) into `GetActiveProfileAsync(...)`.
4. Keep the existing parameterless signature for non-deadline call sites to avoid broad churn, or update call sites gradually.

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


2. Verbose activeProfile comment ✓ Resolved 📘 Rule violation ⚙ Maintainability
Description
A lengthy multi-line comment was added explaining profile resolution and timing behavior, which is
overly verbose for inline code and reduces readability. Prefer a shorter intent-focused comment and
let naming/structure convey most of this detail.
Code

src/Capacitor.Cli/Commands/CursorHookCommand.cs[R546-549]

+        // The EFFECTIVE profile, not AppConfig.ResolvedProfile?.Profile: ProfileResolver returns a
+        // null Profile whenever --server-url or KCAP_URL wins, so the resolved read silently ignored
+        // the opt-outs for every KCAP_URL user. This read runs INSIDE HandleCoreInner — itself inside
+        // HandleCore's deadline race — so even a slow config file read cannot blow the 2s dispatcher
Evidence
PR Compliance ID 5 requires concise comments that add intent without excessive narrative. The added
comment block at the cited location is multi-line and highly detailed, making the code harder to
scan and maintain.

CLAUDE.md: Keep comments minimal; prefer self-explanatory code
src/Capacitor.Cli/Commands/CursorHookCommand.cs[546-550]

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

## Issue description
Newly added inline comments are overly verbose and include extensive background/context that can live in docs, commit message, or a short summary comment.

## Issue Context
Compliance requires keeping comments minimal and preferring self-explanatory code. The added comment explains multiple implementation details (effective profile behavior, config read timing, deadline race) in-line.

## Fix Focus Areas
- src/Capacitor.Cli/Commands/CursorHookCommand.cs[546-550]

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


Qodo Logo

Comment thread src/Capacitor.Cli/Commands/CursorHookCommand.cs Outdated
Comment thread src/Capacitor.Cli/Commands/CursorHookCommand.cs Outdated
@realtonyyoung
realtonyyoung force-pushed the ai-1887-guideline-injection-all-vendors branch from 26c903b to 9651e22 Compare August 12, 2026 00:26
@realtonyyoung

Copy link
Copy Markdown
Collaborator Author

/agentic_review

Comment on lines +36 to +39
/// <summary>
/// Overload for the guidelines lane: the eight non-Claude harnesses
/// fetch <c>GET /api/repositories/{hash}/guidelines</c> directly and pass the
/// parsed rows here, rather than reading <c>top_clusters</c> from a hook

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

1. Verbose xml doc for overload 📘 Rule violation ⚙ Maintainability

The new BuildFragment(IReadOnlyList<GuidelineRow>?) overload includes an overly long XML doc
comment describing endpoint behavior and size limits. This should be shortened to the essential
contract and any non-obvious constraints.
Agent Prompt
## Issue description
New XML documentation is overly verbose and includes operational details that can live elsewhere.

## Issue Context
PR Compliance requires minimal comments and self-explanatory code.

## Fix Focus Areas
- src/Capacitor.Cli/SessionGuidelinesEmitter.cs[36-43]

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

Comment on lines +551 to +554
var activeProfile = await AppConfig.GetActiveProfileAsync();
var disabled = activeProfile?.DisableMemoryIndex is true;
var guidelinesDisabled = activeProfile?.DisableSessionGuidelines is true;
if (disabled && guidelinesDisabled) return null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

2. Cursor budget misaccounted 🐞 Bug ☼ Reliability

CursorHookCommand snapshots memBudget before awaiting GetActiveProfileAsync(), so profile-read time
is not deducted from the remaining session-start budget and the subsequent context fetch can run
longer than the intended leftover window.
Agent Prompt
### Issue description
`RunMemoryOrchestrationAsync` computes `memBudget` before awaiting `AppConfig.GetActiveProfileAsync()`. The awaited profile load can consume non-trivial time, but `memBudget` is later used to bound `memCts.CancelAfter(memBudget)` and is passed into `SessionStartMemoryContextRequest`, so the network/context work can receive a larger budget than what is actually left.

### Issue Context
This method’s design intent (per comments) is that memory/guidelines work runs strictly on “whatever budget is left over”. That intent is currently undermined by doing the async profile read after capturing `memBudget`.

### Fix Focus Areas
- src/Capacitor.Cli/Commands/CursorHookCommand.cs[542-590]

### Suggested fix
Move the active-profile read earlier (before computing `memBudget`), or recompute `memBudget` after the `await` using an updated `sw.Elapsed` (preserving `memoryBudgetOverride` semantics if it’s intended to be absolute). Then keep the existing `memBudget <= 0` guard and continue using the recomputed budget for `CancelAfter` and `SessionStartMemoryContextRequest`.

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

@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 9651e22

SessionStart guideline injection (## Known patterns / ## Guidance from past
sessions) reached only Claude, which reads top_clusters off its hook POST
response. The eight non-Claude harnesses got the team-memory index but no
guidelines.

This adds a parallel "guidelines lane" to the existing per-vendor memory
orchestration and composes ONE combined fragment (marker → ## Team memory →
guidelines) that rides each harness's existing delivery seam unchanged — no
plugin/extension/envelope changes.

- ISessionStartContextProvider seam on the orchestrator; SessionStartContextFetch
  shares the authenticated-GET/401-refresh/bounded-read transport between lanes.
- SessionStartGuidelinesLane fetches GET /api/repositories/{hash}/guidelines and
  maps 404 -> retryable (the visibility race: 404 = "not visible yet", not "no
  facts"), unlike the memory lane's 404 -> empty.
- SessionStartCompositeContextProvider resolves scope once, runs both lanes in
  parallel, and combines over ENABLED lanes only: any content commits; all empty
  completes-without-context; no content + >=1 retryable holds for retry with the
  max of the lanes' Retry-After hints. A disabled lane contributes nothing.
- The orchestrator's early return becomes both-disabled; every non-Claude adapter
  routes through SessionStartMemoryHookSupport.CompositeProvider and passes the
  guidelines opt-out. Claude keeps its memory-only provider (no guidelines GET);
  its only change is reading disable_session_guidelines from the EFFECTIVE profile
  (the KCAP_URL defect). Cursor gains effective-profile threading inside its 2s
  dispatcher race + the same DisableMemoryIndex fix.

No CLI-side size cap (match Claude; the server clamps). Combined fragment still
opens with the kcap-memory-index marker so Pi/OpenCode capture is unchanged.

Tests: guidelines-lane status mapping incl. 404->retryable, composite disposition
matrix + lease/compose rules, Retry-After max aggregation, marker-first compose,
emitter row overload, and a Claude-issues-no-guidelines-GET isolation test.
Existing per-vendor memory suites unchanged and green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@realtonyyoung
realtonyyoung force-pushed the ai-1887-guideline-injection-all-vendors branch from 9651e22 to 4045f67 Compare August 12, 2026 00:33
@realtonyyoung

Copy link
Copy Markdown
Collaborator Author

Addressed both qodo findings. (1) Uncancellable Cursor profile load: AppConfig.GetActiveProfileAsync now takes an optional CancellationToken and threads it into LoadProfileConfig(ct); the Cursor hook passes dispatcherCt, so when HandleCore abandons the inner computation on the 2s deadline the config read observes cancellation instead of lingering (existing parameterless callers unchanged). (2) Verbose comment trimmed to two intent-focused lines.

@realtonyyoung
realtonyyoung merged commit 7759ef2 into main Aug 12, 2026
6 checks passed
@realtonyyoung
realtonyyoung deleted the ai-1887-guideline-injection-all-vendors branch August 12, 2026 01:06
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.

1 participant