mirror of
https://github.com/backnotprop/plannotator.git
synced 2026-09-14 14:17:26 +08:00
e138c824aa
Add Cursor CLI and OpenCode as background code-review engines alongside Claude/Codex/Tour, unified behind a shared marker-review module (prose marker-block parsing, since neither CLI exposes a schema flag). Includes multi-provider dynamic model discovery, review-profile support, fail-closed parse/ingest, Bun+Pi parity, and the review UI (Provider picker, brand icons). Hardening from review: per-job nonce on marker tags (prevents echoed/quoted tags from corrupting extraction), truncation fail-closed, async model discovery (never blocks the event loop), nested-slash model ids, shared finding types.
75 lines
2.3 KiB
TypeScript
75 lines
2.3 KiB
TypeScript
import { classifyFindingPlacement } from "@plannotator/shared/external-annotation";
|
|
import { toRelativePath } from "./path-utils";
|
|
|
|
/**
|
|
* Shared review-finding model + transform.
|
|
*
|
|
* Claude and the marker engines (Cursor/OpenCode) all emit findings with the
|
|
* same shape — `{ severity, file?, line?, end_line?, description, reasoning }`,
|
|
* nullable file/line — and turn them into annotations the same way (relative
|
|
* path → classifyFindingPlacement → line/whole-file/general). This is the one
|
|
* canonical place for that, so a placement/annotation fix lands once.
|
|
*
|
|
* (Codex is NOT here: its findings carry a different shape — priority +
|
|
* code_location — and it maps separately.)
|
|
*/
|
|
|
|
export type ReviewSeverity = "important" | "nit" | "pre_existing";
|
|
|
|
export interface ReviewFinding {
|
|
severity: ReviewSeverity;
|
|
file?: string | null; // null for a general (review-level) comment
|
|
line?: number | null; // null for a whole-file or general comment
|
|
end_line?: number | null;
|
|
description: string;
|
|
reasoning: string;
|
|
}
|
|
|
|
export interface ReviewAnnotationInput {
|
|
source: string;
|
|
filePath: string;
|
|
lineStart: number;
|
|
lineEnd: number;
|
|
type: string;
|
|
side: string;
|
|
scope: string;
|
|
text: string;
|
|
severity: ReviewSeverity;
|
|
reasoning: string;
|
|
author: string;
|
|
}
|
|
|
|
/**
|
|
* Transform severity-style findings into annotation inputs. Routes each finding
|
|
* by what it carries — nothing is dropped: file+line → line comment; file only
|
|
* → whole-file comment; neither → general (review-level) comment.
|
|
*/
|
|
export function transformSeverityFindings(
|
|
findings: ReviewFinding[],
|
|
source: string,
|
|
author: string,
|
|
cwd?: string,
|
|
pathTransform?: (path: string) => string,
|
|
): ReviewAnnotationInput[] {
|
|
return findings.map((f) => {
|
|
const rawFile = typeof f.file === "string" ? f.file : "";
|
|
const filePath = rawFile
|
|
? (pathTransform ? pathTransform(toRelativePath(rawFile, cwd)) : toRelativePath(rawFile, cwd))
|
|
: "";
|
|
const placement = classifyFindingPlacement(filePath, f.line, f.end_line);
|
|
return {
|
|
source,
|
|
filePath: placement.filePath,
|
|
lineStart: placement.lineStart,
|
|
lineEnd: placement.lineEnd,
|
|
type: "comment",
|
|
side: "new",
|
|
scope: placement.scope,
|
|
text: `[${f.severity}] ${f.description}`,
|
|
severity: f.severity,
|
|
reasoning: f.reasoning,
|
|
author,
|
|
};
|
|
});
|
|
}
|