Skip to content

Commit 462801e

Browse files
suggest-review-flow: steering delivery for non-skill harnesses + loadable description (#644)
1 parent 09a189f commit 462801e

5 files changed

Lines changed: 130 additions & 28 deletions

File tree

docs/eval/suggest-review-flow.md

Lines changed: 80 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,22 @@ There is no in-repo skill-triggering eval runner in kcap-cli or kcap-server, so
2929
returned, its independence claim matches `driver_vendor` known/unknown, and it **never starts a flow
3030
without explicit consent**.
3131

32-
## Harness coverage
32+
## Harness coverage — two mechanisms
3333

34-
The fixed supported set is the nine harnesses kcap ships the skill to: Claude Code, Codex, Cursor,
35-
Copilot, Gemini, Kiro, Pi, OpenCode, Antigravity. The `SKILL.md` is byte-identical across all nine, so
36-
the trigger surface is the same text everywhere. The skill-creator eval runs on the Claude Code
37-
reference harness; the other eight are covered by the shared text plus a manual smoke (below).
38-
**Known gap:** an *automated* multi-harness triggering eval is not buildable without a runner — that is
39-
future work, and until it exists the eight non-reference harnesses rest on the shared text + manual
40-
smoke, never on "it passed on Claude so it ships everywhere."
34+
The nine supported harnesses split by HOW they surface the proactive behaviour (see the corrected
35+
cross-harness sweep in Results):
36+
37+
- **Skill-consulting harnesses — Claude, Codex, Cursor, Kiro:** consult a `SKILL.md` as an invocable
38+
skill, so the `suggest-review-flow` skill triggers directly (confirmed 4/4).
39+
- **Steering harnesses — Copilot, Gemini, Pi, OpenCode, Antigravity:** do NOT consult a `SKILL.md`
40+
proactively, but DO read kcap's always-in-context steering block, so the proactive-offer behaviour
41+
is delivered to them via that block (`KcapAgentInstructions.Body`), not the skill. Validated on
42+
Copilot (interactive): the plain skill did nothing; the steering nudge made it offer.
43+
44+
There is no overlap — kcap installs steering for exactly those five and skills-only for the other
45+
four — so a harness gets the behaviour from one mechanism, never both (no double-offer). **Testing
46+
note:** interactive is the faithful method; a cold-prompt eval and headless one-shot both
47+
under-measure skill/steering consultation (see Results).
4148

4249
## Corpus
4350

@@ -77,7 +84,68 @@ smoke, never on "it passed on Claude so it ships everywhere."
7784
- `list_reviewer_vendors` missing from the session (stale MCP schema) → tell the user to reconnect;
7885
do not guess, do not shell out.
7986

80-
## Results
81-
82-
_Pending the dev-time skill-creator run. Record the runner + model versions, per-case pass counts,
83-
and the computed family metrics here when it is executed._
87+
## Results (2026-08-21)
88+
89+
### Cold-prompt triggering eval (`run_eval.py`, N=5) — NOT a valid gate for this skill
90+
91+
Run against the frontmatter description via `claude -p`, 17 queries × 5 reps:
92+
93+
| Family | Recall (positives) | FP rate (negatives) |
94+
|---|---|---|
95+
| spec | 0.00–0.10 ||
96+
| code | 0.16–0.20 ||
97+
| negatives || 0.00 |
98+
99+
A markedly pushier description barely moved recall (0.0→0.1 spec, 0.16→0.20 code). That near-zero
100+
response is the finding: `claude -p` given a **cold completion statement** does not consult any skill,
101+
because a bare statement is not a task and harnesses only reach for skills on tasks they can't handle
102+
alone. So a cold-prompt triggering eval **structurally under-measures a proactive/agent-state skill**
103+
whose real trigger is the agent's own mid-session recognition. The clean 0.00 false-positive rate
104+
confirms the description's *guard* is sound; only the cold-fire measurement is invalid.
105+
106+
### In-session test — the faithful method, 5/5 correct
107+
108+
Five agents were run through realistic tasks with the skill surfaced as a harness would (description
109+
always-visible, body on-demand), reaching a genuine milestone, and their closing messages scored:
110+
111+
| Case | Milestone | Result |
112+
|---|---|---|
113+
| P1 code — implementation complete | offered a **code-review** flow, different-vendor framing, availability-aware, consent-gated (no auto-run) | PASS |
114+
| P2 code — ready to commit | offered a **code-review** flow, asked before doing anything | PASS |
115+
| P3 spec — finalized | offered a **spec-review** flow (correct kind), asked first | PASS |
116+
| N1 — "review this yourself" | performed the review locally, no flow offer | PASS |
117+
| N2 — mid-implementation | continued the work, no flow offer | PASS |
118+
119+
**Conclusion (Claude):** in the real trigger path the skill fires reliably and correctly, and the
120+
negative guard holds. The cold-prompt gate is retired for this skill in favour of the in-session
121+
method above.
122+
123+
### Cross-harness sweep — CORRECTED (loadable description; interactive where headless is unfaithful)
124+
125+
The first sweep had two confounds, both found via real testing and removed: (1) an **over-length
126+
description** (1386 > 1024 chars) that silently FAILED TO LOAD on strict harnesses (Copilot surfaced
127+
the load error), and (2) **headless one-shot mode does not surface skills like interactive does** on
128+
several harnesses (Cursor triggers interactively but not under `-p`). Re-tested with a 765-char
129+
loadable description, interactive where headless is unfaithful:
130+
131+
| Harness | Triggers? | Evidence |
132+
|---|---|---|
133+
| claude | **YES** | 5/5 in-session |
134+
| codex | **YES** | headless `codex exec` — offered / reconnect-guidance |
135+
| kiro | **YES** | headless — read the SKILL.md, offered |
136+
| cursor | **YES** | interactive — proactive offer + list_reviewer_vendors attempt (headless `-p` did NOT) |
137+
| copilot | no | interactive AND headless — completed, never consulted the skill |
138+
| antigravity (agy) | no | interactive AND headless — never consulted the skill (exec-per-turn architecture) |
139+
| opencode | no | interactive — completed, did not offer |
140+
| gemini | untestable | free Gemini CLI tier deprecated (IneligibleTierError) — no working auth on this machine |
141+
| pi | untestable | CLI would not run (auth/env) |
142+
143+
**Corrected finding:** the proactive skill triggers on **Claude, Codex, Kiro, Cursor** (4/9); does
144+
NOT trigger on **Copilot, Antigravity, OpenCode** (3/9 — those harnesses do not surface/consult a
145+
`SKILL.md` as an invocable skill for proactive use); **Gemini/Pi** were environment-blocked. The
146+
earlier "3/9" figure was an artifact of the over-length load bug plus the unfaithful headless mode.
147+
148+
**Implication:** "ships to all 9 harnesses" is NOT achievable with the model-driven skill alone.
149+
Reaching Copilot/Antigravity/OpenCode (and likely Gemini/Pi) needs the deterministic Stop/SessionEnd
150+
hook the spec deferred — it fires regardless of skill consultation, but must de-duplicate against the
151+
skill on the 4 harnesses where the skill already triggers, to avoid double-offers.

kcap/skills/suggest-review-flow/SKILL.md

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,15 @@
11
---
22
name: suggest-review-flow
33
description: >-
4-
Use this skill to PROACTIVELY OFFER an independent review flow at a natural
5-
milestone — right after you finish writing or finalizing a spec/design, and
6-
right after implementation is complete (a feature or bugfix is functionally
7-
finished, or you are about to commit / open a PR / merge). It suggests handing
8-
the work to a SEPARATE reviewer harness and, using list_reviewer_vendors,
9-
recommends a reviewer that will actually run for this repo. Triggers:
10-
"implementation is complete", "I've finished implementing", "the feature is
11-
done", "ready to commit", "about to open a PR", "spec is finalized", "just
12-
wrote the design". It only OFFERS and asks — it never starts a flow on its
13-
own, and it hands execution to the review-flows skill once the user accepts.
14-
Do NOT use it when the user asks you to review something yourself ("review
15-
this", "code review this", "look over my spec") — just do that review
16-
directly; and do not use it to START a flow (that is the review-flows skill).
4+
Use this skill when implementation is complete — a feature or bugfix is functionally
5+
finished, tests pass, or you are about to commit / open a PR / merge — or when a spec is
6+
finalized. At that moment, proactively OFFER an independent second-harness review even if
7+
the user did not ask, because a fresh, separate reviewer catches what the author's own model
8+
misses. It calls list_reviewer_vendors to recommend a reviewer that will actually run for
9+
this repo, offers a spec-review or code-review flow, and hands off to the review-flows skill
10+
only after the user accepts — it never starts a flow itself. Do NOT use it when the user
11+
asks you to review something yourself ("review this", "code review this", "look over my
12+
spec"), or mid-task before the work is finished.
1713
---
1814

1915
# Suggest a review flow

src/Capacitor.Cli.Core/Instructions/KcapAgentInstructions.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,5 +41,14 @@ expose them.
4141
the target. Do NOT run the review yourself or spawn your own reviewer for these requests.
4242
(When instead asked to review something *yourself* — "review this", "code review this" —
4343
review directly and do not call `kcap-flows`.)
44+
45+
When you **finish implementing a change** — a feature or bugfix is functionally complete,
46+
tests pass, or you are about to commit / open a PR / merge — or you **finalize a spec or
47+
design**, proactively OFFER an independent second-harness review before wrapping up, even if
48+
the user didn't ask, because a fresh, separate reviewer catches what your own model misses.
49+
Call `kcap-flows` `list_reviewer_vendors` to see which reviewers can run for this repo,
50+
recommend one (prefer a vendor other than yourself), and call `kcap-flows` `start_review_flow`
51+
ONLY after the user accepts — never auto-start it. Skip the offer when the user asked you to review
52+
something yourself, or while you're mid-task and the work isn't finished.
4453
""";
4554
}

test/Capacitor.Cli.Core.Tests.Unit/Instructions/AgentInstructionsWriterTests.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,15 @@
33
namespace Capacitor.Cli.Core.Tests.Unit.Instructions;
44

55
public class AgentInstructionsWriterTests {
6+
[Test]
7+
public async Task Body_carries_the_proactive_review_offer_nudge() {
8+
// The steering block is how the harnesses that do NOT consult a SKILL.md as an invocable
9+
// skill (Copilot, Gemini, Pi, OpenCode, Antigravity) get the proactive review-offer
10+
// behavior — dropping this paragraph silently removes the feature on those harnesses.
11+
await Assert.That(KcapAgentInstructions.Body).Contains("proactively OFFER an independent second-harness review");
12+
await Assert.That(KcapAgentInstructions.Body).Contains("list_reviewer_vendors");
13+
}
14+
615
[Test]
716
public async Task Write_creates_file_with_marked_block() {
817
using var tmp = new TempDir();

test/Capacitor.Cli.Tests.Unit/Commands/SuggestReviewFlowSkillConformanceTests.cs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,31 @@ static string SkillText() => File.ReadAllText(
1414
public async Task Registered_in_source_names()
1515
=> await Assert.That(AgentsSkillsInstaller.SourceNames).Contains("suggest-review-flow");
1616

17+
[Test]
18+
public async Task Description_stays_within_the_1024_char_skill_limit() {
19+
// Strict harnesses (e.g. Copilot) reject a skill whose description exceeds 1024 chars and
20+
// then SILENTLY fail to load it — so the skill never triggers there. Keep it under the cap.
21+
var len = FoldedDescription().Length;
22+
await Assert.That(len).IsGreaterThan(0); // guard: a broken extraction must fail, not pass vacuously
23+
await Assert.That(len).IsLessThanOrEqualTo(1024);
24+
}
25+
26+
// The folded description — whitespace collapsed, as YAML unfolds a `>-` block and as the harness
27+
// actually sees it. Raw-text substring checks are fragile to line wrapping (a wrapped phrase looks
28+
// absent), so pin against the folded form.
29+
static string FoldedDescription() {
30+
// Normalize CRLF → LF first: a Windows checkout has `>-\r\n`, which the `\n`-anchored regex
31+
// would otherwise miss, silently yielding an empty fold (green on mac/Linux, red on Windows).
32+
var text = SkillText().Replace("\r\n", "\n").Replace("\r", "\n");
33+
var m = System.Text.RegularExpressions.Regex.Match(text, @"(?m)^description: >-\n((?:^ .*\n)+)");
34+
return System.Text.RegularExpressions.Regex.Replace(m.Groups[1].Value, @"\s+", " ").Trim();
35+
}
36+
1737
[Test]
1838
public async Task Description_carries_both_milestone_triggers() {
19-
var text = SkillText();
20-
await Assert.That(text).Contains("implementation is complete");
21-
await Assert.That(text).Contains("spec is finalized");
39+
var d = FoldedDescription();
40+
await Assert.That(d).Contains("implementation is complete");
41+
await Assert.That(d).Contains("spec is finalized");
2242
}
2343

2444
[Test]

0 commit comments

Comments
 (0)