tau-coding-agent 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +1 -1
  2. package/extensions/answer.ts +49 -43
  3. package/extensions/branch-term.ts +11 -16
  4. package/extensions/btw.ts +8 -8
  5. package/extensions/ghostty.ts +65 -12
  6. package/extensions/git-diff-stats.ts +1 -1
  7. package/extensions/git-pr-status.ts +1 -1
  8. package/extensions/insights.ts +6 -5
  9. package/extensions/loop.ts +39 -11
  10. package/extensions/memory.ts +6 -5
  11. package/extensions/notify.ts +25 -6
  12. package/extensions/openai-fast.ts +6 -4
  13. package/extensions/openai-verbosity.ts +6 -4
  14. package/extensions/review/fix.ts +239 -0
  15. package/extensions/review/git.ts +517 -0
  16. package/extensions/review/index.ts +232 -0
  17. package/extensions/review/message-queue.ts +249 -0
  18. package/extensions/review/models.ts +589 -0
  19. package/extensions/review/prompts.ts +337 -0
  20. package/extensions/review/renderers/inline.ts +126 -0
  21. package/extensions/review/request.ts +504 -0
  22. package/extensions/review/review.ts +1305 -0
  23. package/extensions/review/runner.ts +522 -0
  24. package/extensions/review/runtime.ts +275 -0
  25. package/extensions/review/schema.ts +160 -0
  26. package/extensions/review/submit-review-tool.ts +66 -0
  27. package/extensions/review/submit-triage-tool.ts +59 -0
  28. package/extensions/review/triage.ts +746 -0
  29. package/extensions/sandbox/index.ts +428 -103
  30. package/extensions/{interlude.ts → stash.ts} +13 -11
  31. package/extensions/tool-display-mode.ts +173 -17
  32. package/extensions/usage/anthropic.ts +1 -1
  33. package/extensions/usage/github-copilot.ts +1 -1
  34. package/extensions/usage/index.ts +9 -5
  35. package/extensions/usage/minimax.ts +1 -1
  36. package/extensions/usage/openai-codex.ts +1 -1
  37. package/extensions/usage/openrouter.ts +1 -1
  38. package/extensions/usage/providers.ts +0 -2
  39. package/extensions/usage/types.ts +1 -2
  40. package/extensions/usage/zai.ts +1 -1
  41. package/extensions/websearch/README.md +1 -1
  42. package/extensions/websearch/browser/chromium.ts +42 -31
  43. package/extensions/websearch/browser/discovery.ts +11 -9
  44. package/extensions/websearch/browser/firefox.ts +10 -7
  45. package/extensions/websearch/browser/fs.ts +10 -0
  46. package/extensions/websearch/browser/sqlite.ts +22 -15
  47. package/extensions/websearch/config.ts +2 -2
  48. package/extensions/websearch/index.ts +9 -9
  49. package/extensions/websearch/providers/anthropic.pi.ts +1 -1
  50. package/extensions/websearch/providers/gemini.pi.ts +1 -1
  51. package/extensions/websearch/providers/openai-codex.pi.ts +1 -1
  52. package/extensions/websearch/providers/pi-model.shared.ts +2 -2
  53. package/extensions/worktree.ts +36 -36
  54. package/package.json +7 -7
  55. package/skills/git-clean-history/SKILL.md +1 -0
  56. package/skills/git-commit/SKILL.md +2 -2
  57. package/skills/oracle/scripts/oracle +28 -12
  58. package/themes/tau-dark.json +2 -1
  59. package/extensions/review.ts +0 -4347
  60. package/extensions/usage/google-gemini-cli.ts +0 -232
@@ -0,0 +1,337 @@
1
+ import { REVIEW_FOCUS_NAMES, type ReviewFocus } from "./schema.js";
2
+
3
+ type FocusDefinition = { suffix: string; qualifier: string; context: string };
4
+
5
+ export const REVIEW_RUBRIC_PROMPT = `# Review Guidelines
6
+
7
+ You are acting as a code reviewer for a proposed code change made by another engineer.
8
+
9
+ Below are default guidelines for determining what to flag. These are not the final word — if you encounter more specific guidelines elsewhere (in a developer message, user message, file, or project review guidelines appended below), those override these general instructions.
10
+
11
+ ## Determining what to flag
12
+
13
+ Flag issues that:
14
+ 1. Meaningfully impact the accuracy, performance, security, or maintainability of the code.
15
+ 2. Are discrete and actionable (not general issues or multiple combined issues).
16
+ 3. Don't demand rigor inconsistent with the rest of the codebase.
17
+ 4. Were introduced in the changes being reviewed and related to the original intent, not adjacent cleanup or opportunistic refactoring.
18
+ 5. The author would likely fix if aware of them.
19
+ 6. Have provable impact. It is not enough to speculate that a change may disrupt another part, you must identify the parts that are provably affected.
20
+ 7. Are clearly not intentional changes by the author.
21
+ 8. Call out newly added dependencies explicitly and explain why they're needed.
22
+ 9. Apply system-level thinking; flag changes that increase operational risk or on-call burden.
23
+
24
+ If an issue is valid and worth tracking but out of scope for the reviewed change, pre-existing, or merely adjacent, report it only as P3 and clearly frame it as follow-up work. Omit unrelated issues that are speculative, vague, or not worth tracking.
25
+
26
+ ## Finding field guidelines
27
+
28
+ 1. Explain why the issue matters and the concrete scenario/environment where it fails.
29
+ 2. Keep each finding brief, matter-of-fact, and easy to understand.
30
+ 3. Keep suggestions specific and actionable.
31
+ 4. Avoid flattery or filler phrases like "Great job...".
32
+
33
+ ## Priority levels
34
+
35
+ - P0: critical/blocking.
36
+ - P1: urgent.
37
+ - P2: normal.
38
+ - P3: low/nice-to-have/out-of-scope.
39
+
40
+ If an issue is valid but out of scope for the reviewed change, pre-existing, or merely adjacent, report it as P3 and frame it as follow-up work.`;
41
+
42
+ export const REVIEW_FOCUSES: Record<ReviewFocus, FocusDefinition> = {
43
+ general: {
44
+ suffix: "",
45
+ qualifier: "",
46
+ context: REVIEW_RUBRIC_PROMPT,
47
+ },
48
+ security: {
49
+ suffix: " specializing in security analysis",
50
+ qualifier: " security",
51
+ context: `Review the changes for potential security issues, such as:
52
+ 1. Auth and permissions: changed routes, commands, jobs, or data access must preserve required authentication, authorization, tenant isolation, and ownership checks.
53
+ 2. Untrusted input: SQL or command construction must be parameterized; path, URL, shell, and HTML output must be escaped or encoded for the target context.
54
+ 3. Filesystem and process boundaries: user-controlled paths and process arguments must not allow traversal, arbitrary file access, command injection, or unsafe environment changes.
55
+ 4. Server-side fetches: server requests to user-controlled URLs must block localhost, private/link-local IP ranges, cloud metadata endpoints, and internal hostnames, including after DNS resolution and redirects.
56
+ 5. Redirects and navigation: user-controlled destinations must be same-origin relative paths or explicitly allowlisted origins.
57
+ 6. Secrets: new logging, errors, telemetry, files, or API responses must not expose tokens, keys, credentials, cookies, or sensitive identifiers.
58
+ 7. Serialization and parsing: avoid unsafe deserialization, dynamic code execution, prototype pollution, XML external entities, YAML custom object construction, and parser modes that load external resources.
59
+ 8. Dependencies: newly added dependencies that touch input parsing, networking, auth, crypto, secrets, or code execution need an explicit security reason.
60
+ Only flag issues with a concrete exploit path or trust-boundary failure introduced by the reviewed changes.`,
61
+ },
62
+ reuse: {
63
+ suffix: " specializing in reuse analysis",
64
+ qualifier: " reuse",
65
+ context: `Review the changes for potential reuse issues, such as:
66
+ 1. Search for existing capabilities that could replace newly written code: standard library APIs, native platform features, already-installed dependencies, and existing utilities/helpers. Start with ripgrep-style searches (use the grep tool first), then inspect utility directories, shared modules, and adjacent files.
67
+ 2. Flag any new function that duplicates existing functionality. Suggest the existing function, API, or feature to use instead.
68
+ 3. Flag any inline logic that could use an existing capability — hand-rolled standard-library behavior, string manipulation, manual path handling, custom environment checks, ad-hoc type guards, native platform features, and similar patterns are common candidates.
69
+ 4. Flag new dependencies when the standard library, runtime/platform, or an already-installed dependency provides the same capability or behavior.
70
+ 5. Flag duplicate modules, thin pass-through wrappers, and manual registries when they duplicate an existing source of truth or local pattern. Prefer deleting, consolidating, or reusing the existing path.`,
71
+ },
72
+ quality: {
73
+ suffix: " specializing in quality analysis",
74
+ qualifier: " quality",
75
+ context: `Review the changes for potential quality issues, such as:
76
+ 1. Redundant state: state that duplicates existing state, cached values that could be derived, observers/effects that could be direct calls.
77
+ 2. Parameter sprawl: adding new parameters to a function instead of generalizing or restructuring existing ones.
78
+ 3. Copy-paste with slight variation: near-duplicate code blocks that should be unified with a shared abstraction.
79
+ 4. Leaky abstractions: exposing internal details that should be encapsulated, or breaking existing abstraction boundaries.
80
+ 5. Stringly-typed code: using raw strings where constants, enums (string unions), or branded types already exist in the codebase.
81
+ 6. Simplicity/YAGNI: prefer simple, direct solutions over wrappers, abstractions, configuration, options, extensibility, or scaffolding without clear reuse value or explicit need. Prefer deletion or direct code until the second use appears.
82
+ 7. Shrinkage: flag code that preserves behavior with fewer branches, lines, moving parts, or custom helpers. Do not shrink away input validation at trust boundaries, data-loss error handling, security measures, or accessibility basics.
83
+ 8. Nested conditionals: ternary chains, deeply nested if/else blocks, or nested switches should be simplified when they obscure distinct cases, duplicate branches, or make error/edge paths easy to miss.
84
+ 9. Over-defensive code: broad try/catch blocks, fallback/null guard/logging paths, or safe wrappers that are not tied to a real trust boundary or documented failure mode.
85
+ 10. Fail-fast: favor explicit failures over logging-and-continue patterns that hide errors. Prefer predictable failure modes over silent degradation.
86
+ 11. Error classification: ensure errors are checked against codes or stable identifiers, never error message strings.
87
+ 12. Band-aid code: broad any/type-ignore casts, sleeps/timeouts, fake success returns, removed checks, or path mutation that hides a real failure.`,
88
+ },
89
+ testing: {
90
+ suffix: " specializing in test analysis",
91
+ qualifier: " testing",
92
+ context: `Review the changes for potential testing issues, such as:
93
+ 1. High-signal suite: favor a smaller test suite over exhaustive coverage. Treat tests as carrying maintenance cost. Each test should protect important behavior, a realistic failure mode, or a stable shared contract.
94
+ 2. Low-value coverage: flag tests added only to cover implementation trivia. Examples include trivial getters/wrappers/constants, exact internal formatting, incidental telemetry/log details or events, timer internals, framework wiring with no behavior of its own, synthetic edge cases with no realistic breakage story, or behavior already covered by a higher-value test.
95
+ 3. Test bloat: redundant cases, copy-paste matrices, excessive or repeated setup that should use or extract a fixture/helper, gratuitous snapshots, or unparameterized variations that increase maintenance cost without clear regression signal. Suggest consolidation or deletion in these cases.
96
+ 4. Missing coverage: important behavior that can break without a test failing. Only ask for new tests when you can name the public/user-visible contract, security/privacy boundary, data-loss risk, serialization/wire contract, state transition, permission check, concurrency issue, or prior regression being protected.
97
+ 5. Weak assertions: tests that do not check observable behavior or invariants.
98
+ 6. Implementation-coupled tests: tests that assert private details, internal calls, or branch structure instead of behavior. Logs are worth testing only when logging itself is required behavior.
99
+ 7. Over-mocking: mocks that erase the behavior under test, hide integration behavior, or only prove mocks were called/configured. Prefer real fixtures or recorded external-service interactions when local practice supports them.
100
+ 8. Flaky patterns: time, random data, network calls, ordering, concurrency, or shared state that is not controlled by fixtures, clocks, cleanup, or deterministic assertions.
101
+ Do not ask for tests just because code changed. Only flag a missing test when you can name the important behavior or failure mode that could break. Only flag test removal/simplification when the remaining suite still protects the important intended behavior.`,
102
+ },
103
+ efficiency: {
104
+ suffix: " specializing in efficiency analysis",
105
+ qualifier: " efficiency",
106
+ context: `Review the changes for potential efficiency issues, such as:
107
+ 1. Unnecessary work: redundant computations, repeated file reads, duplicate network/API calls, N+1 patterns.
108
+ 2. Missed concurrency: independent operations run sequentially when they could run in parallel.
109
+ 3. Hot-path bloat: new blocking work added to startup or per-request/per-render hot paths.
110
+ 4. Unnecessary existence checks: pre-checking file/resource existence before operating (TOCTOU anti-pattern) — operate directly and handle the error.
111
+ 5. Memory: unbounded data structures, missing cleanup, event listener leaks.
112
+ 6. Overly broad operations: reading entire files when only a portion is needed, loading all items when filtering for one.
113
+ 7. Accidental indirection: wrapper chains, adapters, or registries that add repeated runtime work without hiding real complexity. Prefer deletion or consolidation when the local code shows the extra work.
114
+ 8. Backpressure: treat backpressure handling as critical to system stability; flag unbounded queues, missing flow control, or producer-consumer imbalances.`,
115
+ },
116
+ };
117
+
118
+ export const ADDITIONAL_CONTEXT_SECTION_PROMPT = `Additional context from user:
119
+ {ADDITIONAL_CONTEXT}
120
+ `;
121
+
122
+ export const REVIEW_PROJECT_GUIDELINES_SECTION_PROMPT = `Project-specific review guidelines:
123
+ {PROJECT_GUIDELINES}
124
+ `;
125
+
126
+ export function buildAdditionalContextSection(additionalContext: string | undefined): string {
127
+ const trimmed = additionalContext?.trim();
128
+ if (!trimmed) return "";
129
+ return ADDITIONAL_CONTEXT_SECTION_PROMPT.replace("{ADDITIONAL_CONTEXT}", () => trimmed);
130
+ }
131
+
132
+ export function buildProjectReviewGuidelinesSection(projectGuidelines: string | null): string {
133
+ return projectGuidelines
134
+ ? REVIEW_PROJECT_GUIDELINES_SECTION_PROMPT.replace(
135
+ "{PROJECT_GUIDELINES}",
136
+ () => projectGuidelines,
137
+ )
138
+ : "";
139
+ }
140
+
141
+ export const SUBMIT_TOOL_RETRY_PROMPT = `You did not call {SUBMIT_TOOL} as instructed. You must call that tool exactly once with the final payload. Do not output any text, only call the {SUBMIT_TOOL} when you're done.`;
142
+
143
+ export const REVIEW_OUTPUT_CONTRACT_PROMPT = `Requirements:
144
+ - Never output findings or notes as text or write them to files.
145
+ - Always call submit_review exactly once as your final action.
146
+ - If no issues are found, pass an empty array of findings to submit_review.
147
+ - If uncertain, pass a note to submit_review.`;
148
+
149
+ export const REVIEW_FOCUS_PROMPT = `You are an expert code reviewer{FOCUS_SUFFIX}.
150
+
151
+ Objective:
152
+ - Find concrete, high-confidence{FOCUS_QUALIFIER} issues introduced by the scoped changes.
153
+ - Submit every finding the author would fix if they were made aware of it. Do not stop at the first qualifying finding — continue until you have listed every qualifying finding.
154
+ - Do not flag issues the author would not fix. If there is no finding that a person would definitely want to see and fix, prefer outputting no findings.
155
+
156
+ {SCOPE_INSTRUCTIONS}
157
+
158
+ {FOCUS_CONTEXT}
159
+
160
+ Important:
161
+ - Submit only issues introduced by the scoped changes, locally provable from the repository or diff, discrete, actionable, and likely worth fixing. Do not report speculative, stylistic, or pre-existing issues.
162
+ - This is a read-only review focus. Do not modify files or repository state; do not run mutating commands.
163
+
164
+ {ADDITIONAL_CONTEXT_SECTION}{PROJECT_GUIDELINES_SECTION}
165
+ {OUTPUT_CONTRACT}`;
166
+
167
+ export const FIX_PROMPT = `You are an expert software engineer applying fixes and improvements from a completed code review.
168
+
169
+ Use ONLY the findings in the review payload below as your worklist.
170
+
171
+ You are the decision-maker:
172
+
173
+ - For each finding that is valid, worthwhile, and within the reviewed change's intent, fix it.
174
+ - For each finding that is valid but fixing it would broaden the changeset beyond the reviewed change's goal and scope, defer with a brief explainer.
175
+ - For each finding that is invalid, duplicate, too risky, speculative, vague, or not worth tracking, skip it with a brief reason.
176
+
177
+ Process:
178
+
179
+ 1) Work findings one by one in priority order: P0, P1, P2, P3.
180
+ 2) For each finding:
181
+ - Validate against current code.
182
+ - If valid, worthwhile, and within scope, implement the minimal correct fix.
183
+ - If valid but outside scope, defer with a short explainer.
184
+ - If invalid, skip with a short reason.
185
+ 3) Run relevant verification for touched code (targeted tests/checks preferred; avoid unnecessary full-suite runs).
186
+ 4) Keep changes focused; avoid unrelated refactors, adjacent cleanup, pre-existing issues, and low-value tests.
187
+ 5) Do not stop at first fix; continue through the whole list.
188
+
189
+ Output formatting requirements:
190
+
191
+ - In Verification, prefer plain text. If you cite executed commands, append them after a semicolon and wrap only the command snippet in inline backticks.
192
+ - In Notes, use plain prose. Use inline backticks sparingly when they improve clarity, such as for exact identifiers, paths, or command snippets.
193
+ - Do not use code fences.
194
+ - Do not include the pipe character in any cell text (including inside backticks). Avoid regex alternation patterns like (a|b); rewrite checks without pipes and separate multiple checks with semicolons.
195
+ - Decision values must be exactly fixed, deferred, or skipped.
196
+
197
+ {FIX_ADDITIONAL_CONTEXT_SECTION}Review findings:
198
+
199
+ {REVIEW_FINDINGS_JSON}
200
+
201
+ At the end, output only this table (no section headings, no summary):
202
+
203
+ | # | Location | Finding | Decision | Verification | Notes |
204
+ |---|---|---|---|---|---|`;
205
+
206
+ export const TRIAGE_PROMPT = `You are an expert code reviewer triaging pull request feedback.
207
+
208
+ Your job is to classify each feedback item into exactly one of these decisions:
209
+ - address: the feedback is correct or worthwhile enough to handle in this PR.
210
+ - push_back: the feedback seems incorrect, inapplicable, or not worth changing.
211
+ - research: you cannot decide yet without external docs, repo conventions, or further verification.
212
+ - ignore: the item is non-actionable noise, already-resolved chatter, or has no remaining ask.
213
+
214
+ Process:
215
+ 1) Review the scoped PR diff and relevant files before deciding.
216
+ 2) Use the diff command in the scope instructions as mandatory context.
217
+ 3) Triage every feedback item exactly once. Do not omit any id.
218
+ 4) If a review thread contains back-and-forth, focus on the latest remaining ask.
219
+ 5) Resolved or outdated threads often become ignore, but verify before deciding.
220
+ 6) This is a read-only triage. Do not modify files or repository state; do not run mutating commands.
221
+
222
+ {SCOPE_INSTRUCTIONS}
223
+
224
+ {PROJECT_GUIDELINES_SECTION}
225
+
226
+ PR feedback payload (authoritative JSON):
227
+ {TRIAGE_INPUT_JSON}
228
+
229
+ Requirements:
230
+ - Never output triage items as text or write them to files.
231
+ - Always call submit_triage exactly once as your final action.
232
+ - Pass exactly one item per input feedback id to submit_triage.
233
+ - Keep summary, rationale, and action concise and specific.`;
234
+
235
+ export const REVIEW_DEDUP_PROMPT = `You are identifying duplicate findings from multiple independent code review passes.
236
+
237
+ This is a pure deduplication step. Do not inspect the repository, do not use tools, and do not rewrite findings.
238
+
239
+ Input findings JSON (authoritative):
240
+ {REVIEW_FINDINGS_JSON}
241
+
242
+ Output JSON only, with this exact shape:
243
+ {
244
+ "groups": [
245
+ {
246
+ "ids": [1, 2],
247
+ "reason": "same underlying issue"
248
+ }
249
+ ]
250
+ }
251
+
252
+ Requirements:
253
+ - Only group findings that are truly duplicates: same underlying issue and materially the same fix.
254
+ - Treat wording differences, overlapping line ranges in the same file, and different reviewer terminology as duplicates when the root cause is the same.
255
+ - Do not group related but distinct issues with different root causes, impacts, or fixes.
256
+ - ids must reference input finding ids.
257
+ - Do not include singleton groups. Every group must contain at least two ids.
258
+ - Each finding id may appear in at most one group total.
259
+ - Input findings are already ordered by review priority. The host will keep the lowest id in each group.
260
+ - Keep reason very short.
261
+ - If there are no duplicates, return { "groups": [] }.
262
+ - Before sending, self-check that JSON.parse(output) would succeed.`;
263
+
264
+ export const TRIAGE_METADATA_QUERY = `query($owner: String!, $name: String!, $number: Int!) {
265
+ repository(owner: $owner, name: $name) {
266
+ pullRequest(number: $number) {
267
+ number
268
+ url
269
+ title
270
+ body
271
+ baseRefName
272
+ headRefName
273
+ author {
274
+ login
275
+ }
276
+ comments(first: 100) {
277
+ nodes {
278
+ id
279
+ body
280
+ url
281
+ createdAt
282
+ author {
283
+ login
284
+ }
285
+ }
286
+ }
287
+ reviews(first: 100) {
288
+ nodes {
289
+ id
290
+ body
291
+ state
292
+ url
293
+ submittedAt
294
+ author {
295
+ login
296
+ }
297
+ }
298
+ }
299
+ }
300
+ }
301
+ }`;
302
+
303
+ export const TRIAGE_THREADS_QUERY = `query($owner: String!, $name: String!, $number: Int!, $endCursor: String) {
304
+ repository(owner: $owner, name: $name) {
305
+ pullRequest(number: $number) {
306
+ reviewThreads(first: 100, after: $endCursor) {
307
+ nodes {
308
+ id
309
+ isResolved
310
+ isOutdated
311
+ path
312
+ line
313
+ originalLine
314
+ startLine
315
+ originalStartLine
316
+ comments(first: 100) {
317
+ nodes {
318
+ id
319
+ body
320
+ url
321
+ createdAt
322
+ author {
323
+ login
324
+ }
325
+ }
326
+ }
327
+ }
328
+ pageInfo {
329
+ hasNextPage
330
+ endCursor
331
+ }
332
+ }
333
+ }
334
+ }
335
+ }`;
336
+
337
+ export { REVIEW_FOCUS_NAMES };
@@ -0,0 +1,126 @@
1
+ import type { ResolvedScope } from "../git.js";
2
+ import type { ReviewReportFinding } from "../schema.js";
3
+
4
+ const REVIEW_STALE_SECTION_TITLE = "Repository changed";
5
+
6
+ type ReviewFailure = {
7
+ focus: string;
8
+ model: string;
9
+ error?: string;
10
+ };
11
+
12
+ type TriagedPr = {
13
+ prNumber: number;
14
+ title: string;
15
+ };
16
+
17
+ type TriageMarkdownItem = {
18
+ feedbackKind: string;
19
+ location: string;
20
+ author: string;
21
+ summary: string;
22
+ decision: string;
23
+ rationale: string;
24
+ action: string;
25
+ };
26
+
27
+ export function escapeMarkdownTableCell(value: string): string {
28
+ return value.replace(/\|/g, "\\|").replace(/\n+/g, " ").trim();
29
+ }
30
+
31
+ export function buildReviewFindingsMarkdown(
32
+ reviewedScopeLine: string,
33
+ findings: ReviewReportFinding[],
34
+ completedReviews: number,
35
+ totalReviews: number,
36
+ footerNotes: string[] = [],
37
+ ): string {
38
+ const reviewWord = totalReviews === 1 ? "review" : "reviews";
39
+ const completionLine =
40
+ completedReviews === totalReviews
41
+ ? `All ${totalReviews} ${reviewWord} completed`
42
+ : `${completedReviews} of ${totalReviews} ${reviewWord} completed`;
43
+
44
+ if (findings.length === 0) {
45
+ return appendMarkdownListSection(
46
+ `${reviewedScopeLine}\n\n${completionLine}.\n\nNo findings.\n`,
47
+ REVIEW_STALE_SECTION_TITLE,
48
+ footerNotes,
49
+ );
50
+ }
51
+
52
+ let table = "| # | Focus | Model | Priority | Location | Finding | Suggestion |\n";
53
+ table += "|---|---|---|---|---|---|---|\n";
54
+ findings.forEach((finding, index) => {
55
+ table += `| ${index + 1} | ${escapeMarkdownTableCell(finding.focus)} | ${escapeMarkdownTableCell(finding.model)} | ${escapeMarkdownTableCell(finding.priority)} | ${escapeMarkdownTableCell(finding.location)} | ${escapeMarkdownTableCell(finding.finding)} | ${escapeMarkdownTableCell(finding.suggestion)} |\n`;
56
+ });
57
+ return appendMarkdownListSection(
58
+ `${reviewedScopeLine}\n\n${completionLine}:\n\n${table}\n`,
59
+ REVIEW_STALE_SECTION_TITLE,
60
+ footerNotes,
61
+ );
62
+ }
63
+
64
+ export function buildReviewFailuresMarkdown(failedFocuses: ReviewFailure[]): string {
65
+ const reviewWord = failedFocuses.length === 1 ? "review" : "reviews";
66
+ let table = "| Focus | Model | Error |\n";
67
+ table += "|---|---|---|\n";
68
+ for (const focus of failedFocuses) {
69
+ table += `| ${escapeMarkdownTableCell(focus.focus)} | ${escapeMarkdownTableCell(focus.model)} | ${escapeMarkdownTableCell(focus.error ?? "Unknown failure")} |\n`;
70
+ }
71
+ return `${failedFocuses.length} ${reviewWord} failed:\n\n${table}\n`;
72
+ }
73
+
74
+ export function formatDuration(ms: number): string {
75
+ const totalSeconds = Math.max(0, Math.floor(ms / 1000));
76
+ const seconds = totalSeconds % 60;
77
+ const totalMinutes = Math.floor(totalSeconds / 60);
78
+ const minutes = totalMinutes % 60;
79
+ const hours = Math.floor(totalMinutes / 60);
80
+
81
+ if (hours > 0) return `${hours}h${minutes}m${seconds}s`;
82
+ if (totalMinutes > 0) return `${totalMinutes}m${seconds}s`;
83
+ return `${seconds}s`;
84
+ }
85
+
86
+ export function buildReviewedScopeLine(scope: ResolvedScope, durationMs: number): string {
87
+ const scopeText =
88
+ scope.kind === "working-tree"
89
+ ? `working tree (${scope.trackedFiles.length} tracked, ${scope.untrackedFiles.length} untracked)`
90
+ : scope.kind === "branch-diff"
91
+ ? `branch diff vs ${scope.baseBranch} (${scope.diffFiles.length} files)`
92
+ : scope.kind === "commit"
93
+ ? `commit ${scope.sha}`
94
+ : scope.kind === "folder"
95
+ ? `snapshot for ${scope.paths.join(", ")}`
96
+ : "custom scope";
97
+ return `Reviewed ${scopeText} in ${formatDuration(durationMs)}.`;
98
+ }
99
+
100
+ export function buildTriagedPrLine(context: TriagedPr, durationMs: number): string {
101
+ return `Triaged PR #${context.prNumber} (${context.title}) in ${formatDuration(durationMs)}.`;
102
+ }
103
+
104
+ export function buildTriageMarkdown(
105
+ context: TriagedPr,
106
+ items: TriageMarkdownItem[],
107
+ durationMs: number,
108
+ ): string {
109
+ const header = buildTriagedPrLine(context, durationMs);
110
+ if (items.length === 0) {
111
+ return `${header}\n\nNo PR feedback items found.`;
112
+ }
113
+
114
+ let table = "| # | Kind | Location | Author | Summary | Decision | Rationale | Action |\n";
115
+ table += "|---|---|---|---|---|---|---|---|\n";
116
+ items.forEach((item, index) => {
117
+ table += `| ${index + 1} | ${escapeMarkdownTableCell(item.feedbackKind)} | ${escapeMarkdownTableCell(item.location)} | ${escapeMarkdownTableCell(item.author)} | ${escapeMarkdownTableCell(item.summary)} | ${escapeMarkdownTableCell(item.decision)} | ${escapeMarkdownTableCell(item.rationale)} | ${escapeMarkdownTableCell(item.action)} |\n`;
118
+ });
119
+
120
+ return `${header}\n\n${table}`;
121
+ }
122
+
123
+ function appendMarkdownListSection(markdown: string, title: string, items: string[]): string {
124
+ if (items.length === 0) return markdown;
125
+ return `${markdown.trimEnd()}\n\n${title}:\n${items.map((item) => `- ${item}`).join("\n")}\n`;
126
+ }