Skip to content

Commit dd99129

Browse files
ymansurozerclaude
andauthored
refactor!: reframe guide file fields as orientation + flag (#18)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent be23483 commit dd99129

12 files changed

Lines changed: 67 additions & 62 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ I'm not saying this is *the* review surface. I built it in a week and I'm still
5656
- **Per-line comment threads.** Comment on any line. Ask a question and your agent answers live in the thread; leave a change request and it rides to the handoff.
5757
- **Per-change accept/reject.** Accept or reject individual changes, or sign off a whole file.
5858
- **A tight handoff loop.** Hit **Send to Agent** and your agent gets a structured review. It makes the edits, re-diffs into the same tab, and replies in place.
59-
- **Guided review.** Your agent can attach a guide: an overview, the files in a sensible order, a per-file summary and category, and the risky ones flagged.
59+
- **Guided review.** Your agent can attach a guide: an overview, the files in a sensible order, a per-file orientation (the lens to read it with) and category, and the risky ones flagged.
6060
- **Four review modes.** The working tree, the staged diff, a single file (tracked or not, like a plan, PRD, or issue), or a branch against its merge-base.
6161
- **Keyboard-first.** Intuitive navigation: move by file, line, or change, and accept, reject, comment, or approve without touching the mouse.
6262
- **Open in editor.** Configure a repo-scoped editor command and jump from the review desk to the current file and line.

src/cli.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ function loadGuideArg(value: string | boolean | undefined) {
268268
return null;
269269
}
270270
const SCHEMA =
271-
"Expected JSON: { overview, prDescription?, files: [{ path, order, category, summary, critical?, why? }] }.";
271+
"Expected JSON: { overview, prDescription?, files: [{ path, order, category, orientation, flag? }] }.";
272272
let parsed: unknown;
273273
try {
274274
parsed = JSON.parse(readFileSync(value, "utf8"));

src/guide.test.ts

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,10 @@ const validInput = () => ({
1313
path: "src/middleware/rateLimit.ts",
1414
order: 2,
1515
category: "Core",
16-
summary: "The limiter.",
17-
critical: true,
18-
why: "reject path",
16+
orientation: "The limiter.",
17+
flag: "reject path",
1918
},
20-
{ path: "src/config/limits.ts", order: 1, category: "Config", summary: "Constants." },
19+
{ path: "src/config/limits.ts", order: 1, category: "Config", orientation: "Constants." },
2120
],
2221
});
2322

@@ -32,30 +31,29 @@ test("validateGuide accepts a well-formed guide and sorts files by order", () =>
3231
r.guide.files.map((f) => f.path),
3332
["src/config/limits.ts", "src/middleware/rateLimit.ts"],
3433
);
35-
assert.equal(r.guide.files[1]!.critical, true);
36-
assert.equal(r.guide.files[1]!.why, "reject path");
34+
assert.equal(r.guide.files[1]!.flag, "reject path");
3735
});
3836

3937
test("validateGuide defaults missing order (by position) and category", () => {
40-
const r = validateGuide({ overview: "x", files: [{ path: "a.ts", summary: "s" }] });
38+
const r = validateGuide({ overview: "x", files: [{ path: "a.ts", orientation: "s" }] });
4139
assert.ok(r.ok);
4240
if (!r.ok) return;
4341
assert.equal(r.guide.files[0]!.order, 0);
4442
assert.equal(r.guide.files[0]!.category, "Changes");
45-
assert.equal(r.guide.files[0]!.critical, undefined);
43+
assert.equal(r.guide.files[0]!.flag, undefined);
4644
});
4745

4846
test("validateGuide rejects malformed input", () => {
4947
const cases: unknown[] = [
5048
null,
5149
"nope",
52-
{ files: [{ path: "a", summary: "s" }] }, // missing overview
53-
{ overview: " ", files: [{ path: "a", summary: "s" }] }, // blank overview
50+
{ files: [{ path: "a", orientation: "s" }] }, // missing overview
51+
{ overview: " ", files: [{ path: "a", orientation: "s" }] }, // blank overview
5452
{ overview: "x" }, // missing files
5553
{ overview: "x", files: "no" }, // files not array
5654
{ overview: "x", files: [] }, // empty files
57-
{ overview: "x", files: [{ summary: "s" }] }, // entry missing path
58-
{ overview: "x", files: [{ path: "a" }] }, // entry missing summary
55+
{ overview: "x", files: [{ orientation: "s" }] }, // entry missing path
56+
{ overview: "x", files: [{ path: "a" }] }, // entry missing orientation
5957
];
6058
for (const input of cases) assert.equal(validateGuide(input).ok, false, JSON.stringify(input));
6159
});
@@ -85,7 +83,7 @@ function state(over: Partial<ReviewState>): ReviewState {
8583
test("mergeReviewState carries an attached guide across a reload", () => {
8684
const guide: Guide = {
8785
overview: "o",
88-
files: [{ path: "a.ts", order: 0, category: "Config", summary: "s" }],
86+
files: [{ path: "a.ts", order: 0, category: "Config", orientation: "s" }],
8987
};
9088
const base = state({ baseDiffHash: "new" }); // freshly rebuilt diff — no guide
9189
const saved = state({ guide }); // live state with the attached guide

src/guide.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ import type { Guide, GuideFile } from "./types.js";
33
export type GuideValidation = { ok: true; guide: Guide } | { ok: false; reason: string };
44

55
// Validate + normalize an agent-supplied guide. Required: a non-empty `overview` and a
6-
// non-empty `files` array whose every entry has a `path` and a `summary`. `order` and
7-
// `category` are optional (default to the entry's position / "Changes"); `critical`/`why`
8-
// are optional flags. Returns the normalized guide with files sorted by `order`, or a
9-
// reason the input was rejected. Pure — no IO — so it's the same check on the server and CLI.
6+
// non-empty `files` array whose every entry has a `path` and an `orientation`. `order` and
7+
// `category` are optional (default to the entry's position / "Changes"); `flag` is an
8+
// optional note whose presence raises the file's flag. Returns the normalized guide with
9+
// files sorted by `order`, or a reason the input was rejected. Pure — no IO — so it's the
10+
// same check on the server and CLI.
1011
export function validateGuide(input: unknown): GuideValidation {
1112
if (!input || typeof input !== "object") return { ok: false, reason: "guide must be an object" };
1213
const g = input as Record<string, unknown>;
@@ -22,16 +23,15 @@ export function validateGuide(input: unknown): GuideValidation {
2223
const f = raw as Record<string, unknown>;
2324
if (typeof f.path !== "string" || !f.path.trim())
2425
return { ok: false, reason: `guide.files[${i}].path must be a non-empty string` };
25-
if (typeof f.summary !== "string" || !f.summary.trim())
26-
return { ok: false, reason: `guide.files[${i}].summary must be a non-empty string` };
26+
if (typeof f.orientation !== "string" || !f.orientation.trim())
27+
return { ok: false, reason: `guide.files[${i}].orientation must be a non-empty string` };
2728
const file: GuideFile = {
2829
path: f.path,
2930
order: typeof f.order === "number" && Number.isFinite(f.order) ? f.order : i,
3031
category: typeof f.category === "string" && f.category.trim() ? f.category : "Changes",
31-
summary: f.summary,
32+
orientation: f.orientation,
3233
};
33-
if (f.critical === true) file.critical = true;
34-
if (typeof f.why === "string" && f.why.trim()) file.why = f.why;
34+
if (typeof f.flag === "string" && f.flag.trim()) file.flag = f.flag;
3535
files.push(file);
3636
}
3737
files.sort((a, b) => a.order - b.order);

src/spec.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const ANCHORS = [
2828
"one-paragraph changeset overview",
2929
"files (required, non-empty array)",
3030
"repo-relative; must be a file in the diff",
31-
"shown in the file's diff header",
31+
"Orientation, not a changelog",
3232
// the rest of the operational contract
3333
"reload vs restart",
3434
"desk.lock",

src/spec.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ Then \`galley reload\` to surface your edits, and \`galley await\` for the next
9999
100100
## Guided review (optional)
101101
Attach with \`galley <mode> --guide <file>\`: an overview page + files in your order, each with
102-
summary/category, critical ones flagged. Galley validates+renders it (markdown in prose fields,
102+
orientation/category, files worth scrutinizing flagged. Galley validates+renders it (markdown in prose fields,
103103
raw HTML stripped) and runs no model — content and order are yours. Write the guide OUTSIDE the
104104
working tree (a temp or gitignored path): working mode surfaces untracked files, so an in-repo
105105
guide shows as a stray addition. The guide is stamped to its diff and survives reload/restart;
@@ -114,14 +114,17 @@ One JSON object:
114114
- prDescription? — author/PR intent, shown on the overview page.
115115
- files (required, non-empty array) — one entry per reviewed file:
116116
- path (required, non-empty) — repo-relative; must be a file in the diff.
117-
- summary (required, non-empty) — shown in the file's diff header.
117+
- orientation (required, non-empty) — the lens to read this file with: its role, the
118+
problem it solves, what to expect before opening it, what's non-obvious or worth
119+
scrutinizing. Orientation, not a changelog — the reviewer already sees the diff. Shown
120+
in the file's diff header.
118121
- order? — ascending review order; defaults to array position.
119122
- category? — group label (default "Changes").
120-
- critical? — flags closer review; surfaces \`why\` when true.
121-
- why? — shown when critical.
123+
- flag? — raises a flag on the file for closer scrutiny; the text is the note (what to
124+
double-check, what's risky). Omit unless the file genuinely warrants it.
122125
Validation: overview a non-empty string, files a non-empty array, every file a non-empty
123-
path+summary; an unreadable file / invalid JSON / schema violation aborts the launch naming the
124-
offending field.
126+
path+orientation; an unreadable file / invalid JSON / schema violation aborts the launch naming
127+
the offending field.
125128
126129
## Between rounds — reload vs restart, and the desk lock
127130
- Don't edit tracked files mid-round: the reviewer wouldn't see the edits and their in-flight

src/types.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -105,14 +105,15 @@ export type ReviewMode = "repo" | "file" | "pr";
105105

106106
// A per-file entry in an agent-generated guided review. `order` drives Next/Prev
107107
// (general → specific); `category` is the stepper grouping (e.g. Config/Core/Wiring,
108-
// semantic, distinct from the folder); `critical` + `why` drive the flag + "why flagged".
108+
// semantic, distinct from the folder); `orientation` is the lens to read the file with
109+
// (role, problem, what to expect — not a changelog); `flag`, when present, raises the
110+
// flag for closer scrutiny and is its note. (Its presence is the flag — no separate bool.)
109111
export type GuideFile = {
110112
path: string;
111113
order: number;
112114
category: string;
113-
summary: string;
114-
critical?: boolean;
115-
why?: string;
115+
orientation: string;
116+
flag?: string;
116117
};
117118

118119
// The guided review the coding agent attaches (the desk renders it, runs no model).
@@ -162,7 +163,7 @@ export type ReviewState = {
162163
decisionFiles?: string[];
163164
// Explicit accept/reject records — the source of truth for decisions.
164165
decisions?: Decision[];
165-
// Agent-generated guided review (overview + per-file summaries/order/category).
166+
// Agent-generated guided review (overview + per-file orientation/order/category).
166167
// Optional: absent → no guide surfaces render.
167168
guide?: Guide;
168169
persistFile?: string;

src/ui/guide.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,8 @@ export function guideStale(): boolean {
127127
);
128128
}
129129

130-
// One file row in the Overview list: path (dimmed dir, bright basename) + critical flag +
131-
// ±counts + a read-only review-state badge on top, the guide's one-line summary beneath.
130+
// One file row in the Overview list: path (dimmed dir, bright basename) + flag icon +
131+
// ±counts + a read-only review-state badge on top, the guide's orientation beneath.
132132
// Read-only by design — decisions stay where the diff is visible; clicking opens the file.
133133
function overviewFileRow(f: WalkFile): string {
134134
const badge =
@@ -140,10 +140,10 @@ function overviewFileRow(f: WalkFile): string {
140140
return `<button class="go-file" data-i="${f.fileIndex}">
141141
<span class="go-file-top">
142142
<span class="go-file-path"><span class="fdir">${esc(f.dir)}</span><span class="fname">${esc(f.name)}</span></span>
143-
${f.critical ? `<svg class="ic crit" title="Critical"><use href="#gly-flag"></use></svg>` : ""}
143+
${f.flag ? `<svg class="ic crit" title="${esc(f.flag)}"><use href="#gly-flag"></use></svg>` : ""}
144144
<span class="go-file-stats">${f.added ? `<i class="add">+${f.added}</i>` : ""}${f.removed ? `<i class="del">−${f.removed}</i>` : ""}${badge}</span>
145145
</span>
146-
${f.summary ? `<span class="go-file-sum">${renderMarkdownInline(f.summary)}</span>` : ""}
146+
${f.orientation ? `<span class="go-file-sum">${renderMarkdownInline(f.orientation)}</span>` : ""}
147147
</button>`;
148148
}
149149

src/ui/index.html

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -665,22 +665,22 @@
665665
line-height: 1.55
666666
}
667667

668-
.ghdr .ghdr-why {
668+
.ghdr .ghdr-flag {
669669
display: flex;
670670
align-items: flex-start;
671671
gap: 6px;
672672
max-width: 74ch;
673673
color: var(--amber)
674674
}
675675

676-
.ghdr .ghdr-why>.ic {
676+
.ghdr .ghdr-flag>.ic {
677677
width: 12px;
678678
height: 12px;
679679
margin-top: 3px;
680680
fill: currentColor
681681
}
682682

683-
.ghdr .ghdr-why .md {
683+
.ghdr .ghdr-flag .md {
684684
margin-top: 0;
685685
color: var(--amber);
686686
font-size: var(--text-xs);
@@ -2237,7 +2237,7 @@
22372237
margin-top: 0
22382238
}
22392239

2240-
/* Overview file list: the guide's files grouped by category, each row path + summary +
2240+
/* Overview file list: the guide's files grouped by category, each row path + orientation +
22412241
±counts + a read-only state badge (decisions stay in the diff; clicking opens it). */
22422242
.guide-overview .go-files-h {
22432243
margin: 0 0 7px
@@ -2560,7 +2560,7 @@
25602560
<svg class="ic st approved" x-show="r.kind==='file' && r.state==='approved'" title="Approved"><use href="#gly-check"></use></svg>
25612561
<svg class="ic st changes" x-show="r.kind==='file' && r.state==='changes-requested'" title="Changes requested"><use href="#gly-circle-alert"></use></svg>
25622562
<span class="nm" x-show="r.kind==='file'" x-text="r.name" :title="r.path"></span>
2563-
<svg class="ic crit" x-show="r.kind==='file' && r.critical" title="Critical"><use href="#gly-flag"></use></svg>
2563+
<svg class="ic crit" x-show="r.kind==='file' && r.flag" :title="r.flag"><use href="#gly-flag"></use></svg>
25642564
<span class="status-pack">
25652565
<svg class="ic badge approved" x-show="r.kind==='cat' && r.complete" title="All files reviewed"><use href="#gly-check"></use></svg>
25662566
<i class="add" x-show="r.kind==='file' && r.added" x-text="'+'+r.added"></i>

src/ui/render.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -387,19 +387,19 @@ async function renderCenter() {
387387
const guide = document.createElement("div");
388388
guide.className = "ghdr-guide";
389389
const chip = document.createElement("span");
390-
chip.className = "ghdr-cat" + (entry.critical ? " crit" : "");
390+
chip.className = "ghdr-cat" + (entry.flag ? " crit" : "");
391391
chip.textContent = entry.category;
392392
guide.appendChild(chip);
393393
const expl = document.createElement("div");
394394
expl.className = "ghdr-expl md";
395-
expl.innerHTML = renderMarkdown(entry.summary);
395+
expl.innerHTML = renderMarkdown(entry.orientation);
396396
guide.appendChild(expl);
397-
// Critical "why" gets its own readable callout within the card.
398-
if (entry.critical && entry.why) {
399-
const why = document.createElement("div");
400-
why.className = "ghdr-why";
401-
why.innerHTML = `<svg class="ic"><use href="#gly-flag"></use></svg><div class="md">${renderMarkdown(entry.why)}</div>`;
402-
guide.appendChild(why);
397+
// A flagged file gets its own readable callout within the card.
398+
if (entry.flag) {
399+
const flag = document.createElement("div");
400+
flag.className = "ghdr-flag";
401+
flag.innerHTML = `<svg class="ic"><use href="#gly-flag"></use></svg><div class="md">${renderMarkdown(entry.flag)}</div>`;
402+
guide.appendChild(flag);
403403
}
404404
wrap.appendChild(guide);
405405
}

0 commit comments

Comments
 (0)