Skip to content
Open
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
34 changes: 34 additions & 0 deletions apps/hook/server/annotate-outcome.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { describe, expect, test } from "bun:test";
import { APPROVED_PLAINTEXT_MARKER, formatAnnotateOutcome } from "./annotate-outcome";

describe("formatAnnotateOutcome", () => {
test("preserves plaintext gate output", () => {
expect(formatAnnotateOutcome({ feedback: "", approved: true }, "plaintext")).toBe(APPROVED_PLAINTEXT_MARKER);
expect(formatAnnotateOutcome({ feedback: "", exit: true }, "plaintext")).toBeNull();
expect(formatAnnotateOutcome({ feedback: "Revise this." }, "plaintext")).toBe("Revise this.");
});

test("preserves structured JSON gate output", () => {
expect(formatAnnotateOutcome({ feedback: "", approved: true }, "json")).toBe(JSON.stringify({ decision: "approved" }));
expect(formatAnnotateOutcome({ feedback: "", exit: true }, "json")).toBe(JSON.stringify({ decision: "dismissed" }));
expect(formatAnnotateOutcome({
feedback: "Revise this.",
selectedMessageId: "message-1",
feedbackScope: "message",
}, "json")).toBe(JSON.stringify({
decision: "annotated",
feedback: "Revise this.",
selectedMessageId: "message-1",
feedbackScope: "message",
}));
});

test("preserves hook-native blocking output", () => {
expect(formatAnnotateOutcome({ feedback: "", approved: true }, "hook")).toBeNull();
expect(formatAnnotateOutcome({ feedback: "", exit: true }, "hook")).toBeNull();
expect(formatAnnotateOutcome({ feedback: "Revise this." }, "hook")).toBe(JSON.stringify({
decision: "block",
reason: "Revise this.",
}));
});
});
44 changes: 44 additions & 0 deletions apps/hook/server/annotate-outcome.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Slash-command templates match this approval marker literally.
export const APPROVED_PLAINTEXT_MARKER = "The user approved.";

export type AnnotateOutcomeFormat = "hook" | "json" | "plaintext";

export type AnnotateOutcome = {
feedback: string;
exit?: boolean;
approved?: boolean;
selectedMessageId?: string;
feedbackScope?: "message" | "messages";
};

export function formatAnnotateOutcome(
result: AnnotateOutcome,
format: AnnotateOutcomeFormat,
): string | null {
if (format === "hook") {
if (result.approved || result.exit) return null;
if (result.feedback) {
return JSON.stringify({ decision: "block", reason: result.feedback });
}
return null;
}

if (format === "json") {
if (result.approved) {
return JSON.stringify({ decision: "approved" });
}
if (result.exit) {
return JSON.stringify({ decision: "dismissed" });
}
return JSON.stringify({
decision: "annotated",
feedback: result.feedback || "",
...(result.selectedMessageId && { selectedMessageId: result.selectedMessageId }),
...(result.feedbackScope && { feedbackScope: result.feedbackScope }),
});
}

if (result.exit) return null;
if (result.approved) return APPROVED_PLAINTEXT_MARKER;
return result.feedback || null;
}
37 changes: 9 additions & 28 deletions apps/hook/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ import {
isTopLevelHelpInvocation,
isVersionInvocation,
} from "./cli";
import { formatAnnotateOutcome } from "./annotate-outcome";
import path from "path";
import { tmpdir } from "os";
import { buildLocalWorkspaceReview, type WorkspaceDiffType } from "@plannotator/server/review-workspace";
Expand Down Expand Up @@ -200,41 +201,21 @@ if (renderMarkdownFlag) args.splice(renderMarkdownIdx, 1);
// Plaintext (default):
// Close → empty. Approve → "The user approved." Annotate → feedback.
//
// TODO: The plaintext --gate approval sentinel must stay as the exact string
// "The user approved." because slash command templates (plannotator-annotate.md,
// plannotator-last.md) instruct the agent to match it literally. Making this
// configurable requires updating those templates to accept dynamic values or
// switching gate mode to structured output only.
const APPROVED_PLAINTEXT_MARKER = "The user approved.";

function emitAnnotateOutcome(result: {
feedback: string;
exit?: boolean;
approved?: boolean;
selectedMessageId?: string;
feedbackScope?: "message" | "messages";
}): void {
let format: "hook" | "json" | "plaintext" = "plaintext";
if (hookFlag) {
if (result.approved || result.exit) return;
if (result.feedback) {
console.log(JSON.stringify({ decision: "block", reason: result.feedback }));
}
return;
}
if (jsonFlag) {
if (result.approved) {
console.log(JSON.stringify({ decision: "approved" }));
} else if (result.exit) {
console.log(JSON.stringify({ decision: "dismissed" }));
} else {
console.log(JSON.stringify({ decision: "annotated", feedback: result.feedback || "" }));
}
return;
}
if (result.exit) return;
if (result.approved) {
console.log(APPROVED_PLAINTEXT_MARKER);
return;
format = "hook";
} else if (jsonFlag) {
format = "json";
}
if (result.feedback) console.log(result.feedback);
const output = formatAnnotateOutcome(result, format);
if (output) console.log(output);
}

async function loadGoalSetupBundle(
Expand Down
6 changes: 3 additions & 3 deletions apps/kiro-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ one-liner as everyone else.
convention used for Codex and Gemini) and installs:

- the 2 Kiro-specific skills above → `~/.kiro/skills`
- the 2 shared skills `plannotator-setup-goal` and `plannotator-visual-explainer` (pulled from
- the shared skills `plannotator-setup-goal`, `plannotator-visual-plan`, and `plannotator-visual-explainer` (pulled from
`apps/skills/extra/`, not duplicated here) → `~/.kiro/skills`
- the example agent `agents/plannotator.json` → `~/.kiro/agents/plannotator.json` (an existing file
is never overwritten)
Expand All @@ -32,8 +32,8 @@ curl -fsSL https://plannotator.ai/install.sh | bash

## Use the Plannotator agent

The installed agent wires all four skills via `skill://` resources and, in its prompt, documents
which skill to use for which task (review, annotate, setup-goal, visual-explainer). Launch
The installed agent wires the Plannotator skills via `skill://` resources and, in its prompt, documents
which skill to use for which task (review, annotate, setup-goal, visual-plan, visual-explainer). Launch
it:

```bash
Expand Down
2 changes: 1 addition & 1 deletion apps/kiro-cli/agents/plannotator.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "plannotator",
"description": "Kiro custom agent wiring for Plannotator review and annotation workflows.",
"prompt": "You run Plannotator, which opens a browser UI for human review and annotation. Choose the skill that matches the task:\n- plannotator-review: review the current code changes (git/jj diff) or a pull request before continuing; optionally pass a PR URL.\n- plannotator-annotate: annotate a markdown or HTML file, a folder of docs, or a URL, then act on the returned annotations.\n- plannotator-setup-goal: turn an idea into a structured goal package by interviewing the user, building a fact sheet, then a plan.\n- plannotator-visual-explainer: generate a polished, self-contained HTML visual (implementation plan, PR walkthrough, or diagram) and open it for review.\nEach skill runs a `plannotator` shell command. plannotator-review and plannotator-annotate set PLANNOTATOR_ORIGIN=kiro-cli.",
"prompt": "You run Plannotator, which opens a browser UI for human review and annotation. Choose the skill that matches the task:\n- plannotator-review: review the current code changes (git/jj diff) or a pull request before continuing; optionally pass a PR URL.\n- plannotator-annotate: annotate a markdown or HTML file, a folder of docs, or a URL, then act on the returned annotations.\n- plannotator-setup-goal: turn an idea into a structured goal package by interviewing the user, building a fact sheet, then a plan.\n- plannotator-visual-plan: author a PFM visual plan packet and open it with annotate gate for approval.\n- plannotator-visual-explainer: generate a polished, self-contained HTML visual (implementation plan, PR walkthrough, or diagram) and open it for review.\nEach skill runs a `plannotator` shell command. plannotator-review and plannotator-annotate set PLANNOTATOR_ORIGIN=kiro-cli.",
"tools": [
"shell"
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ irm https://plannotator.ai/install.ps1 | iex

When run in a terminal for the first time, the installer asks two questions:

1. **Install the extra skills?** (compound planning, setup-goal, visual explainer) — answering yes launches `npx skills add` so you pick which agents get them in its UI. Skipped automatically if the extras are already installed.
1. **Install the extra skills?** (compound planning, setup-goal, visual plan, visual explainer) — answering yes launches `npx skills add` so you pick which agents get them in its UI. Skipped automatically if the extras are already installed.
2. **Make any skills callable by the model?** — answering yes opens a picker (space toggles on macOS/Linux/PowerShell; numbered toggles in the cmd installer). Chosen skills have `disable-model-invocation` removed from their *installed* copies (and the Codex sidecar flipped to match); everything else stays user-invoked only.

Answers are saved to `<data dir>/install-prefs` and reused silently on re-runs — pass `--reconfigure` to change them. **Automated installs are unaffected**: runs without a terminal (CI, scripts) never prompt and keep the defaults (no extras, nothing model-invocable). Automation can opt in explicitly with `--extras` / `--no-extras` / `--model-invocable <list>` / `--non-interactive`.
Expand Down Expand Up @@ -114,7 +114,7 @@ Plannotator's slash commands (`/plannotator-review`, `/plannotator-annotate`, `/

Upgrading from an older version? The installer removes the legacy `~/.claude/commands/plannotator-*.md` files automatically, but the marketplace plugin's old namespaced `plannotator:*` command entries are managed by Claude Code — run `/plugin marketplace update` once so they disappear from the `/` menu.

Optional extra skills (compound planning, setup-goal, visual explainer) are not installed by default. Add them with:
Optional extra skills (compound planning, setup-goal, visual plan, visual explainer) are not installed by default. Add them with:

```bash
npx skills add backnotprop/plannotator/apps/skills/extra
Expand Down Expand Up @@ -219,7 +219,7 @@ Notes:
- Codex hooks are currently experimental.
- The current official Codex hooks docs say hooks are disabled on Windows, so this flow is currently macOS/Linux/WSL only.

The installer also copies Plannotator's core skills (`plannotator-review`, `plannotator-annotate`, `plannotator-last`) into `~/.agents/skills` — the official OpenAI agent skills path. Optional extra skills (compound planning, setup-goal, visual explainer) are not installed by default; add them with:
The installer also copies Plannotator's core skills (`plannotator-review`, `plannotator-annotate`, `plannotator-last`) into `~/.agents/skills` — the official OpenAI agent skills path. Optional extra skills (compound planning, setup-goal, visual plan, visual explainer) are not installed by default; add them with:

```bash
npx skills add backnotprop/plannotator/apps/skills/extra
Expand Down
2 changes: 1 addition & 1 deletion apps/marketing/src/content/docs/guides/claude-code.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ Opens any markdown file in the annotation UI. See the [annotate docs](/docs/comm

Annotates the agent's most recent message. See the [annotate last docs](/docs/commands/annotate-last/) for details.

Optional extra skills (compound planning, setup-goal, visual explainer) are not installed by default. Add them with:
Optional extra skills (compound planning, setup-goal, visual plan, visual explainer) are not installed by default. Add them with:

```bash
npx skills add backnotprop/plannotator/apps/skills/extra
Expand Down
2 changes: 2 additions & 0 deletions apps/marketing/src/content/docs/guides/kiro-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ Kiro-specific skills (run with `PLANNOTATOR_ORIGIN=kiro-cli`):
Shared extra skills (installed from Plannotator's canonical `apps/skills/extra/` set, not duplicated):

- `plannotator-setup-goal`
- `plannotator-visual-plan`
- `plannotator-visual-explainer`

The shared skills show the default agent badge rather than "Kiro CLI" — origin is cosmetic for
Expand All @@ -64,6 +65,7 @@ commands, and its prompt spells out which skill to use for which task:
| `plannotator-review` | Review the current code changes or a pull request |
| `plannotator-annotate` | Annotate a markdown/HTML file, folder, or URL |
| `plannotator-setup-goal` | Turn an idea into a structured goal package |
| `plannotator-visual-plan` | Author a PFM visual plan packet and open it with annotate gate |
| `plannotator-visual-explainer` | Generate a polished visual HTML explainer |

Launch it:
Expand Down
40 changes: 40 additions & 0 deletions apps/pi-extension/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
runVcsDiff,
stageFile,
startPlanReviewServer,
startAnnotateServer,
startReviewServer,
unstageFile,
} from "./server";
Expand Down Expand Up @@ -1007,6 +1008,45 @@ describe("pi review server", () => {
}, 20_000);
});

describe("pi annotate server", () => {
test("marks visual packet annotate gates without changing annotate mode", async () => {
process.env.PLANNOTATOR_PORT = String(await reservePort());

const server = await startAnnotateServer({
markdown: "---\npfm: visual-plan\n---\n\n# Plan\n",
filePath: join(tmpdir(), "pi-visual-plan.md"),
htmlContent: "<!doctype html><html><body>annotate</body></html>",
gate: true,
});

try {
const response = await fetch(`${server.url}/api/plan`);
const plan = await response.json() as {
mode?: string;
gate?: boolean;
pfmPacket?: { kind?: string; visual?: boolean; detectedBy?: string };
};

expect(plan.mode).toBe("annotate");
expect(plan.gate).toBe(true);
expect(plan.pfmPacket).toEqual({
kind: "visual-plan",
visual: true,
detectedBy: "frontmatter",
});

await fetch(`${server.url}/api/approve`, { method: "POST" });
await expect(server.waitForDecision()).resolves.toEqual({
feedback: "",
annotations: [],
approved: true,
});
} finally {
server.stop();
}
});
});

describe("pi plan server file browser", () => {
test("filters excluded folders from tree and workspace status", async () => {
const repo = makeTempDir("plannotator-pi-files-");
Expand Down
3 changes: 3 additions & 0 deletions apps/pi-extension/server/serverAnnotate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import {
supportsAnnotateAgentTerminalMode,
type AgentTerminalCapability,
} from "../generated/agent-terminal.js";
import { detectPfmPacket } from "../generated/pfm-packet.js";

export interface AnnotateServerResult {
port: number;
Expand Down Expand Up @@ -342,6 +343,7 @@ export async function startAnnotateServer(options: {
? htmlAssets.rewriteHtml(options.rawHtml, options.filePath)
: undefined;
const primarySource = getPrimarySource();
const pfmPacket = displayRawHtml ? null : detectPfmPacket(primarySource.plan);
json(res, {
plan: primarySource.plan,
origin: options.origin ?? "pi",
Expand All @@ -361,6 +363,7 @@ export async function startAnnotateServer(options: {
projectRoot: options.folderPath || process.cwd(),
serverConfig: getServerConfig(gitUser),
agentTerminal: agentTerminalCapability,
...(pfmPacket ? { pfmPacket } : {}),
...(options.recentMessages ? { recentMessages: options.recentMessages } : {}),
});
} else if (url.pathname === "/api/share-html" && req.method === "GET") {
Expand Down
2 changes: 1 addition & 1 deletion apps/pi-extension/vendor.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ cd "$(dirname "$0")"
rm -rf generated
mkdir -p generated generated/ai/providers

for f in feedback-templates prompts review-core diff-paths cli-pagination jj-core vcs-core review-args storage draft project pr-types pr-context-live pr-provider pr-stack pr-github pr-gitlab checklist integrations-common repo reference-common favicon code-file resolve-file annotate-reference-roots-node config external-annotation agent-jobs agent-terminal worktree worktree-pool html-to-markdown html-assets html-assets-node url-to-markdown tour guide annotate-args at-reference review-workspace-node review-workspace pfm-reminder improvement-hooks code-nav data-dir semantic-diff-types semantic-diff source-save source-save-node workspace-status open-in-apps review-profiles commit-avatars commit-history; do
for f in feedback-templates prompts review-core diff-paths cli-pagination jj-core vcs-core review-args storage draft project pr-types pr-context-live pr-provider pr-stack pr-github pr-gitlab checklist integrations-common repo reference-common favicon code-file resolve-file annotate-reference-roots-node config external-annotation agent-jobs agent-terminal worktree worktree-pool html-to-markdown html-assets html-assets-node url-to-markdown tour guide annotate-args at-reference review-workspace-node review-workspace pfm-reminder pfm-packet improvement-hooks code-nav data-dir semantic-diff-types semantic-diff source-save source-save-node workspace-status open-in-apps review-profiles commit-avatars commit-history; do
src="../../packages/shared/$f.ts"
printf '// @generated — DO NOT EDIT. Source: packages/shared/%s.ts\n' "$f" | cat - "$src" > "generated/$f.ts"
done
Expand Down
48 changes: 48 additions & 0 deletions apps/skills/extra/plannotator-visual-plan/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
name: plannotator-visual-plan
disable-model-invocation: true
description: Author a Plannotator Flavored Markdown visual plan packet and open it in annotate gate.
---

# Plannotator Visual Plan

Create a visual plan packet in Plannotator Flavored Markdown (PFM), then open it through annotate gate for approval or requested changes.

## Build The Packet

1. Inspect the codebase or source material enough to name real files, commands, risks, and decisions.
2. Write a readable markdown packet with `plannotator-visual-plan` frontmatter and Plannotator visual directives.
3. Keep the packet standalone: a reviewer who did not read the chat should understand the scope, planned changes, risks, and verification.
4. Include the visual blocks that help the plan scan faster. Read `references/visual-blocks.md` before authoring the first packet in a run.
5. Save the packet as `plan.md` inside a task-specific folder when there are supporting fragments, or as a single `.md` file for small plans.

Use this frontmatter:

```markdown
---
plannotator: visual-plan
title: Human-readable plan title
---
```

## Open The Gate

Run Plannotator yourself and wait for the browser session to finish:

```bash
plannotator annotate --gate <file-or-folder>
```

If approved, continue with the approved plan. If feedback or annotations return, revise the source packet and rerun the same command. If the session is closed without feedback, report that the gate was closed and do not treat it as approval.

## Constraints

- Use PFM source, not MDX.
- Do not claim Agent-Native compatibility.
- Do not use React imports, MDX components, or runtime component execution.
- Use only Plannotator's documented visual directives for custom blocks.
- Keep arbitrary HTML out of the packet unless a supported directive explicitly calls for visual markup.

## Example

See `examples/visual-plan-packet.md` for a compact packet that exercises the visual plan path.
Loading