feat(annotate): add --gate, --json, and --silent-approve flags (#570)

Adds an opt-in review gate flow to annotation mode with three composable flags:

- `--gate`: 3-way UX (Approve / Send Annotations / Close)
- `--json`: structured decision output (`{"decision":"approved|annotated|dismissed"}`)
- `--silent-approve`: suppresses plaintext approve marker for naive hooks

Includes shared arg parser, @-reference handling, updated templates across all
harnesses (Claude Code, Copilot, Gemini, OpenCode, Pi), and full documentation.

Closes #570

For provenance purposes, this commit was AI assisted.
This commit is contained in:
Michael Ramos
2026-04-23 17:40:40 -07:00
committed by GitHub
parent 68972aa6a6
commit d102c5f709
27 changed files with 1176 additions and 117 deletions
+9 -1
View File
@@ -28,7 +28,7 @@ export interface AnnotateServerResult {
port: number;
portSource: "env" | "remote-default" | "random";
url: string;
waitForDecision: () => Promise<{ feedback: string; annotations: unknown[]; exit?: boolean }>;
waitForDecision: () => Promise<{ feedback: string; annotations: unknown[]; exit?: boolean; approved?: boolean }>;
stop: () => void;
}
@@ -43,6 +43,7 @@ export async function startAnnotateServer(options: {
shareBaseUrl?: string;
pasteApiUrl?: string;
sourceInfo?: string;
gate?: boolean;
}): Promise<AnnotateServerResult> {
const gitUser = detectGitUser();
const sharingEnabled =
@@ -56,11 +57,13 @@ export async function startAnnotateServer(options: {
feedback: string;
annotations: unknown[];
exit?: boolean;
approved?: boolean;
}) => void;
const decisionPromise = new Promise<{
feedback: string;
annotations: unknown[];
exit?: boolean;
approved?: boolean;
}>((r) => {
resolveDecision = r;
});
@@ -89,6 +92,7 @@ export async function startAnnotateServer(options: {
mode: options.mode || "annotate",
filePath: options.filePath,
sourceInfo: options.sourceInfo,
gate: options.gate ?? false,
sharingEnabled,
shareBaseUrl,
pasteApiUrl,
@@ -135,6 +139,10 @@ export async function startAnnotateServer(options: {
deleteDraft(draftKey);
resolveDecision({ feedback: "", annotations: [], exit: true });
json(res, { ok: true });
} else if (url.pathname === "/api/approve" && req.method === "POST") {
deleteDraft(draftKey);
resolveDecision({ feedback: "", annotations: [], approved: true });
json(res, { ok: true });
} else if (url.pathname === "/api/feedback" && req.method === "POST") {
try {
const body = await parseBody(req);