Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 77 additions & 10 deletions cline/plugins/slow-powers.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,30 @@
* prompt. This replaces the SessionStart-hook injection used on Claude/Codex
* and the system-prompt transform used on OpenCode.
*
* 2. PLAN GATE — the FIRST switch_to_act_mode call of a conversation is skipped
* with an instruction to run the hardening-plans skill on the plan first.
* switch_to_act_mode is how Cline presents a finished plan and leaves plan
* mode; skipping it keeps the session in plan mode, so the agent can load
* the skill, fix findings inline, and re-submit a hardened plan. The
* re-submission finds the per-conversation marker and is allowed through.
* 2. PLAN GATE — in Cline, presenting a plan is a free-form assistant message:
* the agent shows the plan, ends its turn, the user approves in a follow-up
* message, and ONLY THEN does the agent call switch_to_act_mode (the CLI's
* own plan-mode prompt and the tool description mandate that order). So,
* unlike Claude Code — where the plan text rides inside the ExitPlanMode
* call and a PreToolUse deny lands before the user ever sees the plan —
* there is NO hook moment in Cline that precedes plan presentation. The
* gate therefore works in two layers:
*
* a. PRE-PRESENTATION (rule): the plan-presentation rule registered below
* tells plan-mode agents to run hardening-plans on a draft BEFORE
* presenting it. A rule is the only mechanism that reaches the agent
* before a plan is shown.
* b. PRE-EXECUTION (hook): the first switch_to_act_mode call of a
* conversation whose transcript shows no hardening-plans invocation is
* skipped with an instruction to harden, re-present the hardened plan,
* and retry. This is the deterministic backstop: an un-hardened plan can
* never be executed even if the agent skipped the rule.
*
* ALREADY-HARDENED SHORT-CIRCUIT: when the rule was followed, the transcript
* already holds a skills tool call for hardening-plans, and the hook lets the
* switch through with no beat (parity with hooks/exit-plan-mode, issue #153).
* Detection matches the tool-input shape only, never prose, so this hook's own
* skip reason in the transcript cannot false-positive.
*
* WHY DENY-ONCE (and not deny-until-proven-hardened): keying the marker per
* conversation and allowing the second attempt guarantees we can never
Expand Down Expand Up @@ -70,9 +88,49 @@ function markerPath(context) {
}

const SKIP_REASON =
"A plan is about to be presented. Before it leaves your hands, use the " +
"hardening-plans skill to review the plan file as a skeptical executor, " +
"then call switch_to_act_mode again to present the hardened plan.";
"Plan execution is gated. This conversation has not run the hardening-plans " +
"skill on the plan yet, so the plan must not be executed as-is. Use the " +
"hardening-plans skill to review the plan as a skeptical executor and fix " +
"its findings, present the hardened plan to the user, and call " +
"switch_to_act_mode again once they approve it.";

// Pre-presentation half of the plan gate. Cline offers no hook moment before a
// plan is shown (presentation is a free-form assistant message), so this rule
// is what puts the hardening beat ahead of presentation; the switch_to_act_mode
// hook below is the deterministic backstop.
const PLAN_PRESENTATION_RULE =
"Plan-mode discipline: when you are working in plan mode, never present a " +
"drafted plan to the user until you have invoked the hardening-plans skill " +
"on it and applied its findings — a plan reaches the user hardened or not " +
"at all. The switch_to_act_mode tool is gated the same way: if it is " +
"skipped with a hardening instruction, run hardening-plans on the plan, " +
"present the hardened plan, and wait for approval before calling " +
"switch_to_act_mode again.";

// Already-hardened short-circuit (parity with hooks/exit-plan-mode, issue
// #153): if the agent ran hardening-plans this conversation, the transcript
// holds a skills tool call whose input names the skill. Match that tool-input
// shape ONLY — never prose — so this hook's own skip reason (which mentions
// "hardening-plans" and lands in the transcript as tool output) can never
// false-positive. Any missing/odd shape falls through to deny-once below.
function planAlreadyHardened(context) {
const messages = context?.snapshot?.messages;
if (!Array.isArray(messages)) return false;
for (const message of messages) {
const content = message?.content;
if (!Array.isArray(content)) continue;
for (const part of content) {
if (part?.type !== "tool-call" || part?.toolName !== "skills") continue;
const input = part.input;
const skill =
input && typeof input === "object" ? input.skill : undefined;
if (typeof skill === "string" && skill.includes("hardening-plans")) {
return true;
}
}
}
return false;
}

/** @type {import("@cline/sdk").AgentPlugin} */
const SlowPowersPlugin = {
Expand All @@ -89,6 +147,11 @@ const SlowPowersPlugin = {
source: "slow-powers",
content: bootstrap,
});
api.registerRule({
id: "slow-powers/plan-presentation",
source: "slow-powers",
content: PLAN_PRESENTATION_RULE,
});
},

hooks: {
Expand All @@ -100,9 +163,13 @@ const SlowPowersPlugin = {
const toolName = context?.tool?.name ?? context?.toolCall?.name;
if (toolName !== "switch_to_act_mode") return undefined;

// The agent already hardened the plan this conversation — let the
// approved plan be executed with no redundant beat.
if (planAlreadyHardened(context)) return undefined;

const marker = markerPath(context);
if (fs.existsSync(marker)) {
// Re-submission after hardening — let the plan be presented.
// Re-submission after the skip-once beat — let it through.
return undefined;
}

Expand Down
101 changes: 65 additions & 36 deletions memory-bank/activeContext.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,57 +2,72 @@

## Current focus

Cline support was just added (August 2026). Two halves:
Cline plan-gate timing fix (August 2026). The first live test of the Cline
plugin showed the gate rejecting an un-hardened plan only AFTER the plan was
presented and approved — because `switch_to_act_mode` is called post-approval
in Cline, unlike Claude's `ExitPlanMode` which carries the plan text. The gate
is now two-layer:

1. **Cline plugin** (`cline/plugins/slow-powers.js`, declared via the `cline`
field in `package.json`): registers `bootstrap.md` as a session rule and
gates the first `switch_to_act_mode` of each conversation on
hardening-plans (skip-once + tmp marker, mirroring `hooks/exit-plan-mode`).
field in `package.json`): registers `bootstrap.md` AND a
`slow-powers/plan-presentation` rule (harden before presenting — the only
mechanism that reaches the agent pre-presentation), and gates
`switch_to_act_mode` as a pre-EXECUTION backstop with an
already-hardened transcript short-circuit (skip-once marker as fail-open
floor, mirroring `hooks/exit-plan-mode`).
Skills are auto-discovered from the package root — no wiring needed.
2. **Repo-local Cline setup**: `.clinerules/memory-bank.md` (canonical Memory
Bank instructions) and this `memory-bank/` directory, both committed.

## Recent changes

- `cline/plugins/slow-powers.js` (new), `package.json` `cline` field + `files`
- `tests/harness/spec.ts` Cline entry; Cline assertions in `manifests.test.ts`;
new `tests/harness/cline-plugin.test.ts`
- README Cline install section; AGENTS.md four-harness update;
`.gitignore` covers `.cline/plugins/` install artifacts
- `fix/cline-plan-gate-timing` branch: plugin header docs rewritten (real
Cline plan flow), `PLAN_PRESENTATION_RULE` added, `planAlreadyHardened()`
transcript scan added (matches the `skills` tool-input shape only, never
prose, so the hook's own skip reason can't false-positive), `SKIP_REASON`
reworded for execution-gate semantics; 5 new tests in
`tests/harness/cline-plugin.test.ts` (rule registration, short-circuit,
false-positive guards, full flow).
- Earlier (merged via PR #266/#267/#268): `cline/plugins/slow-powers.js` (new),
`package.json` `cline` field + `files`; `tests/harness/spec.ts` Cline entry;
Cline assertions in `manifests.test.ts`; README Cline install section;
AGENTS.md four-harness update; `.gitignore` covers `.cline/plugins/`.

## Verification results (Cline CLI 3.0.51, headless)
## Verification results

- `cline plugin install <repo> --cwd <scratch>` works; installer copies the
repo and registers the plugin entry.
- Live session: all 8 skills discovered; `<EXTREMELY-IMPORTANT>` bootstrap
block present in instructions; bootstrap behavior observed (agent invoked a
skill on a ~1% match, per the bootstrap rule).
- `bun test`: 167 pass / 0 fail; typecheck and biome clean on changed files.
(Baseline note: `bun run check` fails on three pre-existing
`.eval-magic/hardening-plans/iteration-2` eval-fixture files — unrelated.)
- Live (Cline CLI 3.0.51, headless): install, skills discovery, bootstrap rule
injection confirmed. First interactive test exposed the gate-timing issue
this branch fixes.
- Plan gate: unit-tested against the documented `AgentBeforeToolResult`
contract. The runtime's `skip` handling (tool doesn't run, `reason` goes to
the model) and the hook context shape were confirmed in the shipped CLI
source — the first-party `core.plan-mode-command-guard` extension uses the
same pattern. `switch_to_act_mode` is NOT exposed in headless one-shot
sessions, so an interactive (TUI) confirmation of the gate firing is the one
remaining manual check.
contract; runtime `skip` handling and hook context shape confirmed in the
shipped CLI source. `switch_to_act_mode` is NOT exposed in headless one-shot
sessions, so an interactive (TUI) confirmation of the new two-layer behavior
is the one remaining manual check.

## Next steps

- PR opened: https://github.com/slowdini/slow-powers/pull/266 (base `dev`).
- Manually confirm the plan gate in an interactive `cline -i` plan-mode
session (present plan → approve → first `switch_to_act_mode` gets skipped
with the hardening instruction) — easiest via a test release, per the
maintainer.
- After merge to `dev`: trigger the Release PR workflow with the next version
to ship the Cline plugin (that release doubles as the test release).
- Manually confirm the new behavior in an interactive `cline -i` plan-mode
session: with the rule active the agent should harden BEFORE presenting;
if it skips hardening, the first `switch_to_act_mode` after approval is
skipped with the hardening instruction and the retry (transcript now holds
the skills call) passes.
- Then open the PR for `fix/cline-plan-gate-timing` (base `dev`).

## Active decisions

- Distribution reuses the root `package.json` (git install); no separate npm
package or release-workflow change.
- The Cline gate is skip-once only. The already-hardened short-circuit
(upstream #153 refinement) is deferred — it needs reliable detection that
hardening-plans already ran (the skill-invocation tool is `skills` in the
Cline runtime).
- Pre-presentation enforcement is a RULE, not a hook: Cline has no hook moment
before a plan is shown (verified against the installed binary and
`@cline/shared` `AgentRuntimeHooks`). The hook stays as the pre-execution
backstop. Trust guarantee moves from "user only ever sees a hardened plan"
(Claude, achievable) to "an un-hardened plan is never executed, and hook
firing routes the agent to harden + re-present" (Cline).
- The already-hardened short-circuit (upstream #153 refinement) is now
implemented for Cline via the `snapshot.messages` transcript scan.
- No `.cline/skills/` dogfooding symlinks: Cline's skill registry is
last-wins with plugin dirs scanned *after* workspace dirs, so an installed
slow-powers plugin would silently shadow the repo's skills. The
Expand All @@ -64,11 +79,25 @@ Cline support was just added (August 2026). Two halves:
- Cline plugins load only in CLI/SDK/Kanban — not VSCode/JetBrains. IDE users
get skills via manual copy into `.cline/skills/` or `~/.cline/skills/`.
- Cline reads `AGENTS.md` natively; no memory-file symlink needed for it.
- Cline's plan-exit tool is `switch_to_act_mode`; `AgentBeforeToolResult.skip`
+ `reason` is the deny mechanism; `registerRule` puts content in the system
prompt every session.
- **Cline plan-mode flow (verified in CLI 3.0.51 source):** the plan is
presented as a free-form assistant message; the CLI's plan-mode system
prompt and the `switch_to_act_mode` tool description both mandate: present
plan → end turn → user approves in a follow-up message → ONLY THEN call
`switch_to_act_mode` (`lifecycle.completesRun`, then a continuation turn
with "The user approved switching to act mode..."). So
`switch_to_act_mode` is an execution boundary, never a presentation moment.
- **Complete plugin hook surface** (`AgentRuntimeHooks`, binary + SDK agree):
`beforeRun`, `afterRun` (observe), `beforeModel` (rewrite request / stop),
`afterModel` (stop only — and `stop:true` aborts the whole run),
`beforeTool` (skip/input/policy/stop), `afterTool` (result/stop),
`onEvent` (observe only). Nothing fires before streamed assistant text,
so no hook can gate plan presentation.
- Hook contexts pass the tool name on BOTH `tool.name` (first-party shape) and
`toolCall.name` (docs shape) — read `tool?.name ?? toolCall?.name`.
`toolCall.name` (docs shape) — read `tool?.name ?? toolCall?.name`. The
`beforeTool` context also carries `snapshot.messages` — the full
conversation transcript, usable for detection logic.
- Skill invocation in Cline goes through a `skills` tool with input
`{skill, args}` — match that shape for skill-use detection.
- Headless one-shot sessions (`cline -p "..."`) don't expose
`switch_to_act_mode` and can't drive TTY-only commands (`cline config`); use
interactive sessions for plan-gate verification.
Expand Down
30 changes: 21 additions & 9 deletions memory-bank/progress.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,22 @@
- Eight skills with eval coverage; bootstrap injection and plan gates on
Claude Code, Codex CLI, and OpenCode.
- Full test suite green (`bun test`), typecheck and biome clean.
- **Cline support (new)**: plugin entry, manifest field, unit + manifest
tests, README/AGENTS.md docs, memory bank initialized. Verified live on
Cline CLI 3.0.51: install, skills discovery, and bootstrap rule injection
all confirmed in headless sessions.
- **Cline support**: plugin entry, manifest field, unit + manifest tests,
README/AGENTS.md docs, memory bank initialized. Verified live on Cline CLI
3.0.51: install, skills discovery, and bootstrap rule injection confirmed
in headless sessions.
- **Cline plan-gate timing fixed** (`fix/cline-plan-gate-timing`): first live
test showed the old skip-once hook firing after plan presentation and
approval (Cline's `switch_to_act_mode` is post-approval by design). Now
two-layer: a plan-presentation rule enforces hardening BEFORE presentation
(no Cline hook fires earlier than that), and the hook is the pre-execution
backstop with an already-hardened transcript short-circuit.

## What's left

- Manual interactive check of the Cline plan gate (`switch_to_act_mode` is
only exposed in interactive sessions), then PR.
- Release: next version bump will carry the Cline plugin via the normal flow.
- Commit/PR for `fix/cline-plan-gate-timing`; manual interactive check of the
new two-layer gate (`switch_to_act_mode` is only exposed in interactive
sessions), then release: next version bump carries the Cline plugin.

## Known issues / deferred

Expand All @@ -24,8 +30,10 @@
workspace ones, so an installed slow-powers plugin shadows same-named
workspace skills (the reverse of what this repo wants for development).
Deferred: cross-harness installed-vs-repo precedence exploration.
- Cline plan gate has no already-hardened short-circuit yet (deferred; needs
skill-invocation detection).
- Cline pre-presentation enforcement is prompt-level (rule) — Cline exposes no
hook moment before streamed assistant text. The hook backstop guarantees an
un-hardened plan is never executed; if it fires, the user briefly saw an
un-hardened draft before the agent hardens and re-presents.

## Decision log

Expand All @@ -35,3 +43,7 @@
`memory-bank/`).
- 2026-08: No `.cline/skills/` symlinks (option (c)) pending the precedence
exploration.
- 2026-08: Cline plan gate re-anchored to a two-layer design (rule
pre-presentation + hook pre-execution backstop) after the first live test
showed `switch_to_act_mode` fires post-approval in Cline; the deferred
already-hardened short-circuit implemented via `snapshot.messages` scan.
2 changes: 1 addition & 1 deletion memory-bank/systemPatterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ mechanism:
| Claude | `hooks/session-start` (SessionStart) | `hooks/exit-plan-mode` (PreToolUse, deny-once) |
| Codex | shared `hooks/hooks.json` SessionStart | `hooks/codex-stop-plan-mode` (Stop hook) |
| OpenCode | `opencode/plugins/slow-powers.js` system-prompt transform | same plugin, `file.edited` event on plan files |
| Cline | `cline/plugins/slow-powers.js` `registerRule` | same plugin, `beforeTool` skip-once on `switch_to_act_mode` |
| Cline | `cline/plugins/slow-powers.js` `registerRule` (bootstrap + plan-presentation rules) | same plugin, `beforeTool` on `switch_to_act_mode` — pre-execution backstop: transcript short-circuit when hardening-plans already ran, else skip-once |

Claude/Codex hooks are extensionless bash scripts dispatched by the
`hooks/run-hook.cmd` polyglot (Windows-safe). OpenCode/Cline integrations are
Expand Down
Loading
Loading