Files
backnotprop__plannotator/packages/shared/guide.ts
Michael Ramos 98113182b5 feat(guide): reviewer-supplied extra instructions for Guided Review (#1267)
* feat(guide): reviewer-supplied extra instructions for Guided Review (#1265)

Adds a quiet, collapsed-by-default Custom instructions affordance to the
guide launch page. The text is APPENDED to the built-in organizer
methodology as a clearly delimited section (composeGuideMethodology) and
never replaces it; absent or blank instructions produce byte-identical
prompts to before. Persisted in a dedicated cookie
(plannotator-guide-instructions) so a standing team preference survives
sessions without bloating the plannotator.agents blob past the browser's
per-cookie limit.

Server side, the launch body gains an optional guide-only instructions
field (both the Bun and Pi node:http agent-jobs handlers accept and
thread it); prompt composition lives in the shared guide-review.ts that
vendor.sh already vendors to Pi, so both runtimes compose identically.
Text is capped at GUIDE_EXTRA_INSTRUCTIONS_MAX_CHARS (2000) server-side
and mirrored by the textarea maxLength. Repair launches deliberately
ignore instructions: a repair is a mechanical JSON fix, not a rewrite.

Tests pin the regression contract (empty input keeps prior prompt bytes),
appended-not-replacing composition, the length cap, repair isolation, and
the cookie round-trip via the storage backend seam.

* refactor(guide): store standing instructions server-side, not in a cookie

Review findings on the cookie approach (silent write failure past the
encoded 4KB per-cookie limit for multi-byte text) pointed at the real
design problem: the instructions are consumed by the SERVER at launch
time, so they belong in the data dir like review-skills.json, where no
size ceiling or encoding inflation exists and the preference follows
the machine instead of one browser profile.

New GET/PUT /api/agents/guide-instructions in both runtimes backed by
shared guide-instructions-store (vendored to Pi). Guide launches apply
the stored text when the body carries none; the launch page still sends
its live textarea value (explicit wins), so a just-typed preference can
never race the debounced save. The sidebar surface sends nothing and
inherits the stored text server-side. All cookie machinery removed.

Also folds in the review fixes: marker-tag-shaped strings in
instructions are defanged so first-match nonce recovery cannot be
hijacked by pasted examples.
2026-08-11 10:24:39 -07:00

62 lines
2.9 KiB
TypeScript

/** Hard cap on reviewer-supplied extra guide instructions (#1265): the
* freeform text appended to the organizer methodology at launch. A hygiene
* bound on prompt size, not a security boundary (the instructions run on the
* user's own machine against their own agent). Enforced server-side in
* composeGuideMethodology and mirrored by the launch textarea's maxLength. */
export const GUIDE_EXTRA_INSTRUCTIONS_MAX_CHARS = 2000;
export interface GuideDiffRef {
/** Repo-relative path; must match a DiffFile.path in the current review patch. */
file: string;
/** 1-2 sentence semantic description of what changed in THIS file, written
* from the diff hunks alone (no investigation). Required by the JSON schema
* for schema-enforced engines; optional here so a marker engine that omits
* it still yields a valid guide — the UI simply renders nothing. */
summary?: string;
}
export interface GuideSection {
/** Concept-level title, e.g. "Payment localization module" — never a filename paraphrase. */
title: string;
/** Markdown prose: what changed, why it exists, and its key implications.
* Semantic order (core first, consequences next, glue grouped last) is
* carried by the array position, not by any label field. */
overview: string;
/** File references into the provided changeset. Usually 1..n, but a
* deliberate prose-only context section (no diffs, real overview text) is
* a valid model output and is preserved as-is rather than dropped. */
diffs: GuideDiffRef[];
}
export interface CodeGuideOutput {
/** From the PR title when a PR is given, otherwise derived from the changes. */
title: string;
/** 1-2 sentence framing shown under the title: why this changeset exists. */
intent: string;
/** Ordered sections: core first, consequence next, support last. */
sections: GuideSection[];
/** Changed files the model didn't place — rendered in a trailing "Everything else" section. */
unplacedFiles?: string[];
}
/** One row of GET /api/guides — a persisted guide for the current repo
* (#1112). Loaded through the guide endpoints as the `saved:{id}` pseudo
* job id. Browser-safe (types only); the store lives in guide-store.ts. */
export interface SavedGuideListEntry {
id: string;
/** Review-target label — "PR #1082" or the branch name. */
label: string;
title: string;
/** Epoch ms when the guide was first persisted. */
savedAt: number;
progress: { reviewed: number; total: number };
/** True when the stored head sha differs from the current head. */
moved: boolean;
}
/** UI-side guide shape: server output extended with persisted per-section reviewed state.
* `saved` is set when the guide is persisted on disk (autosaved live job, or a
* `saved:{id}` load); `moved` is set only on `saved:` loads whose stored head
* sha differs from the head currently under review. */
export type CodeGuideData = CodeGuideOutput & { reviewed: boolean[]; saved?: boolean; moved?: boolean };