@gethmy/agent 1.7.1 → 1.7.2

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 (77) hide show
  1. package/dist/cli.js +6376 -141
  2. package/dist/index.js +6206 -333
  3. package/package.json +2 -2
  4. package/dist/board-helpers.d.ts +0 -31
  5. package/dist/board-helpers.js +0 -150
  6. package/dist/budget.d.ts +0 -39
  7. package/dist/budget.js +0 -73
  8. package/dist/cli.d.ts +0 -14
  9. package/dist/completion.d.ts +0 -36
  10. package/dist/completion.js +0 -322
  11. package/dist/config-validation.d.ts +0 -23
  12. package/dist/config-validation.js +0 -77
  13. package/dist/config.d.ts +0 -23
  14. package/dist/config.js +0 -103
  15. package/dist/episode-writer.d.ts +0 -116
  16. package/dist/episode-writer.js +0 -349
  17. package/dist/git-diff-stat.d.ts +0 -24
  18. package/dist/git-diff-stat.js +0 -56
  19. package/dist/git-pr.d.ts +0 -38
  20. package/dist/git-pr.js +0 -399
  21. package/dist/http-server.d.ts +0 -66
  22. package/dist/http-server.js +0 -96
  23. package/dist/index.d.ts +0 -5
  24. package/dist/log.d.ts +0 -34
  25. package/dist/log.js +0 -100
  26. package/dist/merge-monitor.d.ts +0 -23
  27. package/dist/merge-monitor.js +0 -169
  28. package/dist/pm.d.ts +0 -14
  29. package/dist/pm.js +0 -63
  30. package/dist/pool.d.ts +0 -71
  31. package/dist/pool.js +0 -259
  32. package/dist/process-group.d.ts +0 -26
  33. package/dist/process-group.js +0 -72
  34. package/dist/progress-tracker.d.ts +0 -82
  35. package/dist/progress-tracker.js +0 -457
  36. package/dist/prompt.d.ts +0 -23
  37. package/dist/prompt.js +0 -160
  38. package/dist/queue.d.ts +0 -39
  39. package/dist/queue.js +0 -100
  40. package/dist/reconcile.d.ts +0 -35
  41. package/dist/reconcile.js +0 -174
  42. package/dist/recovery.d.ts +0 -30
  43. package/dist/recovery.js +0 -141
  44. package/dist/review-completion.d.ts +0 -35
  45. package/dist/review-completion.js +0 -475
  46. package/dist/review-knowledge.d.ts +0 -14
  47. package/dist/review-knowledge.js +0 -89
  48. package/dist/review-prompt.d.ts +0 -12
  49. package/dist/review-prompt.js +0 -103
  50. package/dist/review-worker.d.ts +0 -56
  51. package/dist/review-worker.js +0 -638
  52. package/dist/review-worktree.d.ts +0 -12
  53. package/dist/review-worktree.js +0 -95
  54. package/dist/run-log.d.ts +0 -6
  55. package/dist/run-log.js +0 -19
  56. package/dist/startup-banner.d.ts +0 -29
  57. package/dist/startup-banner.js +0 -143
  58. package/dist/state-store.d.ts +0 -89
  59. package/dist/state-store.js +0 -230
  60. package/dist/stream-parser-selftest.d.ts +0 -9
  61. package/dist/stream-parser-selftest.js +0 -97
  62. package/dist/stream-parser.d.ts +0 -43
  63. package/dist/stream-parser.js +0 -174
  64. package/dist/transitions.d.ts +0 -57
  65. package/dist/transitions.js +0 -131
  66. package/dist/types.d.ts +0 -167
  67. package/dist/types.js +0 -76
  68. package/dist/verification.d.ts +0 -39
  69. package/dist/verification.js +0 -317
  70. package/dist/watcher.d.ts +0 -53
  71. package/dist/watcher.js +0 -153
  72. package/dist/worker.d.ts +0 -54
  73. package/dist/worker.js +0 -507
  74. package/dist/worktree-gc.d.ts +0 -67
  75. package/dist/worktree-gc.js +0 -245
  76. package/dist/worktree.d.ts +0 -18
  77. package/dist/worktree.js +0 -177
@@ -1,475 +0,0 @@
1
- import { readFileSync, statSync } from "node:fs";
2
- import { addLabelByName, moveCardToColumn } from "./board-helpers.js";
3
- import { buildTokenPayload } from "./completion.js";
4
- import { backfillReviewVerdict, findLatestImplementEpisode, writeEpisode, } from "./episode-writer.js";
5
- import { createPullRequest, detectGitProvider, getBranchWebUrl, pushBranch, renameRemoteBranch, } from "./git-pr.js";
6
- import { log } from "./log.js";
7
- import { NEED_REVIEW_LABEL, NEED_REVIEW_LABEL_COLOR, } from "./types.js";
8
- import { cleanupWorktree } from "./worktree.js";
9
- const TAG = "review-completion";
10
- const MAX_FINDINGS = 10;
11
- const REVIEW_MARKER = "---\n**Review:";
12
- const RUN_LOG_TAIL_BYTES = 2048;
13
- /**
14
- * Read the last N bytes of a file as UTF-8. Returns null on any IO failure —
15
- * the parse-error surfacing is best-effort diagnostic, it must not throw.
16
- */
17
- function tailRunLog(path, bytes = RUN_LOG_TAIL_BYTES) {
18
- try {
19
- const size = statSync(path).size;
20
- if (size === 0)
21
- return null;
22
- const start = Math.max(0, size - bytes);
23
- const buf = readFileSync(path);
24
- return buf.subarray(start).toString("utf-8");
25
- }
26
- catch {
27
- return null;
28
- }
29
- }
30
- /**
31
- * Extract structured fields from a parsed JSON object into a ReviewResult.
32
- */
33
- function extractResult(parsed) {
34
- const verdict = parsed.verdict === "approved" || parsed.verdict === "rejected"
35
- ? parsed.verdict
36
- : "rejected";
37
- const findings = Array.isArray(parsed.findings)
38
- ? parsed.findings
39
- .filter((f) => typeof f === "object" && f !== null && "title" in f)
40
- .map((f) => ({
41
- severity: f.severity === "critical"
42
- ? "critical"
43
- : f.severity === "minor"
44
- ? "minor"
45
- : "major",
46
- title: String(f.title ?? "Untitled finding"),
47
- description: String(f.description ?? ""),
48
- category: f.category ? String(f.category) : undefined,
49
- location: f.location ? String(f.location) : undefined,
50
- }))
51
- : [];
52
- const scopeCheck = parsed.scopeCheck &&
53
- typeof parsed.scopeCheck === "object" &&
54
- "status" in parsed.scopeCheck
55
- ? {
56
- status: ["clean", "drift", "missing"].includes(parsed.scopeCheck.status)
57
- ? parsed.scopeCheck.status
58
- : "clean",
59
- notes: parsed.scopeCheck.notes
60
- ? String(parsed.scopeCheck.notes)
61
- : undefined,
62
- }
63
- : undefined;
64
- return {
65
- verdict,
66
- summary: String(parsed.summary ?? "").slice(0, 2000),
67
- scopeCheck,
68
- findings,
69
- };
70
- }
71
- /**
72
- * Parse Claude's review output into a structured ReviewResult.
73
- *
74
- * Tries multiple extraction strategies in order:
75
- * 1. ```json ... ``` fenced block (what the prompt asks for)
76
- * 2. Any top-level JSON object containing a "verdict" key (last-wins)
77
- * 3. Regex for a bare `"verdict": "approved|rejected"` anywhere — lossy
78
- * but keeps the pipeline moving
79
- * 4. Falls back to verdict: "error" — keeps card in Review instead of
80
- * bouncing it to To Do for a parse failure that isn't a code quality signal.
81
- */
82
- export function parseReviewOutput(stdout) {
83
- // Strategy 1: fenced ```json block (greedy-last to handle multiple blocks)
84
- const fencedBlocks = [...stdout.matchAll(/```json\s*([\s\S]*?)```/g)];
85
- for (let i = fencedBlocks.length - 1; i >= 0; i--) {
86
- const raw = fencedBlocks[i][1].trim();
87
- try {
88
- const parsed = JSON.parse(raw);
89
- if (parsed && typeof parsed === "object" && "verdict" in parsed) {
90
- log.debug(TAG, "Parsed review output from fenced JSON block");
91
- return extractResult(parsed);
92
- }
93
- }
94
- catch {
95
- // try next block
96
- }
97
- }
98
- // Strategy 2: scan every top-level { ... } block and take the last one
99
- // that parses AND contains "verdict". This handles cases where the output
100
- // has multiple stray braces before the real JSON object.
101
- const candidates = [];
102
- let depth = 0;
103
- let start = -1;
104
- for (let i = 0; i < stdout.length; i++) {
105
- const ch = stdout[i];
106
- if (ch === "{") {
107
- if (depth === 0)
108
- start = i;
109
- depth++;
110
- }
111
- else if (ch === "}") {
112
- depth--;
113
- if (depth === 0 && start !== -1) {
114
- candidates.push(stdout.slice(start, i + 1));
115
- start = -1;
116
- }
117
- }
118
- }
119
- for (let i = candidates.length - 1; i >= 0; i--) {
120
- try {
121
- const parsed = JSON.parse(candidates[i]);
122
- if (parsed && typeof parsed === "object" && "verdict" in parsed) {
123
- log.debug(TAG, "Parsed review output from raw JSON object");
124
- return extractResult(parsed);
125
- }
126
- }
127
- catch {
128
- // try next
129
- }
130
- }
131
- // Strategy 3: regex for a bare verdict declaration anywhere in the output.
132
- // Loses findings/summary but preserves approve/reject signal so the pipeline
133
- // can make progress instead of looping on "error".
134
- const verdictMatch = stdout.match(/"verdict"\s*:\s*"(approved|rejected)"/i);
135
- if (verdictMatch) {
136
- log.warn(TAG, `Parsed verdict via regex fallback — findings lost (${verdictMatch[1]})`);
137
- return {
138
- verdict: verdictMatch[1].toLowerCase(),
139
- summary: "Parsed via regex fallback — original JSON was malformed. Check run log.",
140
- findings: [],
141
- };
142
- }
143
- // Strategy 4: nothing parseable — return error verdict so the card stays in Review
144
- log.warn(TAG, "Failed to parse review JSON output — returning error verdict (card stays in Review)");
145
- return {
146
- verdict: "error",
147
- summary: stdout.slice(0, 500),
148
- findings: [],
149
- };
150
- }
151
- /**
152
- * Get the current review cycle count from card description.
153
- * Looks for: Review cycle: N/M
154
- */
155
- function getReviewCycle(description) {
156
- if (!description)
157
- return 0;
158
- const match = description.match(/Review cycle:\s*(\d+)/);
159
- return match ? parseInt(match[1], 10) : 0;
160
- }
161
- /**
162
- * Update the review cycle marker in the card description.
163
- */
164
- function updateReviewCycleMarker(description, cycle, maxCycles) {
165
- const marker = `Review cycle: ${cycle}/${maxCycles}`;
166
- const existing = description.match(/Review cycle:\s*\d+\/\d+/);
167
- if (existing) {
168
- return description.replace(/Review cycle:\s*\d+\/\d+/, marker);
169
- }
170
- return `${description}\n\n${marker}`;
171
- }
172
- /**
173
- * Strip any previous review summary block from the description.
174
- */
175
- function stripReviewSummary(description) {
176
- const idx = description.indexOf(REVIEW_MARKER);
177
- if (idx === -1)
178
- return description;
179
- return description.slice(0, idx).trimEnd();
180
- }
181
- /**
182
- * Post-review completion pipeline.
183
- * Handles approved/rejected verdicts, creates subtasks for findings,
184
- * and moves the card to the appropriate column.
185
- */
186
- /**
187
- * Post a review verdict as a typed comment instead of mutating the card
188
- * description (card-comments plan, Phase 3). Best-effort — never throws.
189
- */
190
- async function postReviewComment(client, card, commentType, body) {
191
- try {
192
- await client.addComment(card.id, body, { commentType });
193
- }
194
- catch (err) {
195
- log.error(TAG, `Failed to post review comment to #${card.short_id}: ${err instanceof Error ? err.message : err}`);
196
- }
197
- }
198
- export async function runReviewCompletion(client, card, result, config, worktreePath, branchName, sessionStats, runLogPath, workspaceId, agentSessionId, stateStore) {
199
- // Re-fetch card for fresh description (avoids stale data from enqueue time)
200
- let freshDesc;
201
- try {
202
- const { card: fresh } = (await client.getCard(card.id));
203
- freshDesc = fresh.description || "";
204
- }
205
- catch {
206
- freshDesc = card.description || "";
207
- }
208
- const currentCycle = getReviewCycle(freshDesc) + 1;
209
- const maxCycles = config.review.maxReviewCycles;
210
- if (result.verdict === "error") {
211
- // Parse failure — not a code quality signal. Keep card in Review and
212
- // add the "Need Review" label so reconcile stops re-enqueueing it.
213
- // Without the label, the reconcile loop would respawn the review every
214
- // cycle and burn budget on the same unparseable output (see #122).
215
- log.warn(TAG, `#${card.short_id} review output unparseable — labelling "${NEED_REVIEW_LABEL}" for manual inspection`);
216
- try {
217
- await addLabelByName(client, card, NEED_REVIEW_LABEL, NEED_REVIEW_LABEL_COLOR);
218
- }
219
- catch (err) {
220
- log.warn(TAG, `Failed to add "${NEED_REVIEW_LABEL}" label: ${err instanceof Error ? err.message : err}`);
221
- }
222
- if (config.review.postFindings) {
223
- const rawTail = runLogPath ? tailRunLog(runLogPath) : null;
224
- // Log content routinely contains ```json fences from Claude's own
225
- // output; embedding it inside a 3-backtick fence would break markdown.
226
- // Use a 4-backtick fence and downgrade any 4+-backtick runs.
227
- const runLogTail = rawTail
228
- ? rawTail.replace(/`{4,}/g, () => "`".repeat(3))
229
- : null;
230
- const runLogHint = runLogPath
231
- ? `Run log: \`${runLogPath}\``
232
- : "Run log: (not captured)";
233
- const body = [
234
- "**Review — parse error.**",
235
- 'The review agent\'s output could not be parsed. Card stays in Review with the "Need Review" label — inspect the run log below to diagnose.',
236
- runLogHint,
237
- result.summary ? `Raw output (truncated):\n${result.summary}` : "",
238
- runLogTail
239
- ? `Run log tail (last ${RUN_LOG_TAIL_BYTES}B):\n\`\`\`\`\n${runLogTail}\n\`\`\`\``
240
- : "",
241
- ]
242
- .filter(Boolean)
243
- .join("\n\n");
244
- await postReviewComment(client, card, "blocker", body);
245
- }
246
- await client.endAgentSession(card.id, {
247
- status: "paused",
248
- ...buildTokenPayload(sessionStats),
249
- });
250
- // Cleanup worktree but do NOT move the card
251
- if (branchName) {
252
- cleanupWorktree(worktreePath, branchName);
253
- }
254
- return;
255
- }
256
- if (result.verdict === "approved") {
257
- // Ensure branch is pushed (skip in local mode — no branch to push)
258
- let prUrl = null;
259
- let approvedBranch = branchName;
260
- if (branchName) {
261
- pushBranch(branchName, worktreePath);
262
- // Graduate the branch from `agent-attempts/*` to `agent/*` so the
263
- // approved PR opens on a clean ref. Renaming on origin is force-with-
264
- // lease + delete-old; the old ref is the same SHA, so no work is lost.
265
- const failedPrefix = config.worktree.failedBranchPrefix;
266
- const approvedPrefix = config.worktree.approvedBranchPrefix;
267
- if (failedPrefix &&
268
- approvedPrefix &&
269
- branchName.startsWith(failedPrefix)) {
270
- const newRef = `${approvedPrefix}${branchName.slice(failedPrefix.length)}`;
271
- try {
272
- renameRemoteBranch(branchName, newRef, worktreePath);
273
- approvedBranch = newRef;
274
- }
275
- catch (err) {
276
- log.warn(TAG, `Branch rename failed (continuing on ${branchName}): ${err instanceof Error ? err.message : err}`);
277
- }
278
- }
279
- // Create PR if configured
280
- if (config.review.createPR && approvedBranch) {
281
- const provider = detectGitProvider(worktreePath);
282
- prUrl = createPullRequest(card, approvedBranch, worktreePath, config, provider);
283
- }
284
- }
285
- // Add "Ready to Merge" label
286
- await addLabelByName(client, card, config.review.approvedLabel, config.review.approvedLabelColor);
287
- // Post the approval verdict as a decision comment (card stays in Review).
288
- if (config.review.postFindings) {
289
- const scopeLine = result.scopeCheck
290
- ? `Scope: ${result.scopeCheck.status}${result.scopeCheck.notes ? ` — ${result.scopeCheck.notes}` : ""}`
291
- : "";
292
- const body = [
293
- "**Review — approved.**",
294
- result.summary || "",
295
- scopeLine,
296
- result.findings.length > 0
297
- ? `${result.findings.length} minor finding(s) noted.`
298
- : "",
299
- prUrl ? `PR: ${prUrl}` : "",
300
- ]
301
- .filter(Boolean)
302
- .join("\n\n");
303
- await postReviewComment(client, card, "decision", body);
304
- }
305
- await client.endAgentSession(card.id, {
306
- status: "completed",
307
- progressPercent: 100,
308
- ...buildTokenPayload(sessionStats),
309
- });
310
- log.info(TAG, `#${card.short_id} approved${prUrl ? ` — PR: ${prUrl}` : ""} — labeled "${config.review.approvedLabel}"`);
311
- }
312
- else {
313
- // Rejected
314
- const criticalFindings = result.findings
315
- .filter((f) => f.severity === "critical")
316
- .slice(0, MAX_FINDINGS);
317
- const majorFindings = result.findings
318
- .filter((f) => f.severity === "major")
319
- .slice(0, MAX_FINDINGS);
320
- const linkedFindings = [...criticalFindings, ...majorFindings];
321
- const minorFindings = result.findings
322
- .filter((f) => f.severity === "minor")
323
- .slice(0, MAX_FINDINGS);
324
- // Check if we've exceeded max review cycles
325
- if (currentCycle >= maxCycles) {
326
- log.warn(TAG, `#${card.short_id} reached max review cycles (${maxCycles}), moving to Done with note`);
327
- await moveCardToColumn(client, card, config.review.moveToColumn);
328
- const body = [
329
- "**Review — needs human review.**",
330
- `Reached max review cycles (${maxCycles}). Please review manually.`,
331
- result.summary || "",
332
- ]
333
- .filter(Boolean)
334
- .join("\n\n");
335
- await postReviewComment(client, card, "blocker", body);
336
- await client.endAgentSession(card.id, {
337
- status: "completed",
338
- ...buildTokenPayload(sessionStats),
339
- });
340
- // Max-cycles rejection: the verdict still teaches "this approach kept
341
- // failing review" — write the episode + back-fill before exiting.
342
- if (workspaceId) {
343
- const origId = await findLatestImplementEpisode(client, workspaceId, card.project_id, card.short_id);
344
- const reviewId = await writeEpisode(client, {
345
- kind: "review",
346
- card,
347
- workspaceId,
348
- verdict: "rejected",
349
- summary: `Reached max review cycles (${maxCycles}). ${result.summary}`,
350
- cost: sessionStats?.cost ?? null,
351
- agentSessionId: agentSessionId ?? null,
352
- originalEpisodeId: origId,
353
- });
354
- if (origId) {
355
- await backfillReviewVerdict(client, origId, "rejected", reviewId);
356
- }
357
- }
358
- if (branchName) {
359
- cleanupWorktree(worktreePath, branchName);
360
- }
361
- return;
362
- }
363
- // Post findings
364
- if (config.review.postFindings) {
365
- // Add critical + major findings as new linked cards (parallel)
366
- await Promise.all(linkedFindings.map(async (finding) => {
367
- try {
368
- const locationLine = finding.location
369
- ? `\n**Location:** ${finding.location}`
370
- : "";
371
- const newCard = await client.createCard(card.project_id, {
372
- title: `[Review: ${finding.severity}] ${finding.title}`,
373
- description: `Found during review of #${card.short_id} (${finding.severity}):\n\n${finding.description}${locationLine}`,
374
- });
375
- const newCardId = newCard?.card?.id;
376
- if (newCardId) {
377
- await client.addLinkToCard(card.id, newCardId, "relates_to");
378
- }
379
- }
380
- catch (err) {
381
- log.error(TAG, `Failed to create finding card: ${err instanceof Error ? err.message : err}`);
382
- }
383
- }));
384
- // Add minor findings as subtasks (parallel)
385
- await Promise.all(minorFindings.map(async (finding) => {
386
- try {
387
- const title = finding.title.length > 120
388
- ? `${finding.title.slice(0, 117)}...`
389
- : finding.title;
390
- await client.createSubtask(card.id, title);
391
- }
392
- catch (err) {
393
- log.error(TAG, `Failed to create subtask: ${err instanceof Error ? err.message : err}`);
394
- }
395
- }));
396
- // The review cycle counter stays in the description — it is functional
397
- // state used to enforce maxReviewCycles, not narrative. (This also strips
398
- // any legacy summary block from the description.) The review narrative
399
- // goes to a summary comment instead.
400
- const baseDesc = stripReviewSummary(freshDesc);
401
- const updatedDesc = updateReviewCycleMarker(baseDesc, currentCycle, maxCycles);
402
- try {
403
- await client.updateCard(card.id, { description: updatedDesc });
404
- }
405
- catch (err) {
406
- log.error(TAG, `Failed to update review cycle marker: ${err instanceof Error ? err.message : err}`);
407
- }
408
- const scopeLine = result.scopeCheck
409
- ? `Scope: ${result.scopeCheck.status}${result.scopeCheck.notes ? ` — ${result.scopeCheck.notes}` : ""}`
410
- : "";
411
- const body = [
412
- "**Review — rejected.**",
413
- result.summary || "",
414
- scopeLine,
415
- `${criticalFindings.length} critical, ${majorFindings.length} major, ${minorFindings.length} minor finding(s).`,
416
- ]
417
- .filter(Boolean)
418
- .join("\n\n");
419
- await postReviewComment(client, card, "summary", body);
420
- }
421
- // Move back to failColumn (To Do) for re-implementation
422
- await moveCardToColumn(client, card, config.review.failColumn);
423
- const failureSummary = `Review rejected (cycle ${currentCycle}/${maxCycles}): ${criticalFindings.length} critical, ${majorFindings.length} major, ${minorFindings.length} minor`;
424
- const recoveryBranch = branchName ?? undefined;
425
- const recoveryUrl = branchName
426
- ? getBranchWebUrl(branchName, worktreePath)
427
- : null;
428
- try {
429
- await stateStore.recordFailureSummary(card.id, {
430
- summary: failureSummary,
431
- reason: "review",
432
- recoveryBranch,
433
- });
434
- }
435
- catch (err) {
436
- log.debug(TAG, `recordFailureSummary failed: ${err instanceof Error ? err.message : err}`);
437
- }
438
- if (recoveryBranch) {
439
- log.info(TAG, `#${card.short_id} recovery branch ${recoveryBranch}${recoveryUrl ? ` (${recoveryUrl})` : ""}`);
440
- }
441
- await client.endAgentSession(card.id, {
442
- status: "failed",
443
- failureReason: "review",
444
- failureSummary,
445
- recoveryBranch,
446
- ...buildTokenPayload(sessionStats),
447
- });
448
- log.info(TAG, `#${card.short_id} rejected (cycle ${currentCycle}/${maxCycles}) — moved to "${config.review.failColumn}"`);
449
- }
450
- // Episode write + verdict back-fill (Phase 1.5). Runs for approved or
451
- // rejected verdicts only — "error" verdicts return early above. Best-effort:
452
- // failures are logged by writeEpisode/backfillReviewVerdict and never block
453
- // worktree cleanup.
454
- if (workspaceId &&
455
- (result.verdict === "approved" || result.verdict === "rejected")) {
456
- const originalEpisodeId = await findLatestImplementEpisode(client, workspaceId, card.project_id, card.short_id);
457
- const reviewEpisodeId = await writeEpisode(client, {
458
- kind: "review",
459
- card,
460
- workspaceId,
461
- verdict: result.verdict,
462
- summary: result.summary,
463
- cost: sessionStats?.cost ?? null,
464
- agentSessionId: agentSessionId ?? null,
465
- originalEpisodeId,
466
- });
467
- if (originalEpisodeId) {
468
- await backfillReviewVerdict(client, originalEpisodeId, result.verdict, reviewEpisodeId);
469
- }
470
- }
471
- // Cleanup worktree (skip in local mode — no worktree to clean)
472
- if (branchName) {
473
- cleanupWorktree(worktreePath, branchName);
474
- }
475
- }
@@ -1,14 +0,0 @@
1
- /**
2
- * Embedded review and QA knowledge for the review worker.
3
- * Condensed from the /review checklist and /qa skill.
4
- */
5
- /**
6
- * Static system prompt with review methodology.
7
- * Covers two-pass review categories, suppressions, and severity classification.
8
- */
9
- export declare const REVIEW_SYSTEM_PROMPT = "You are a senior code reviewer. Follow this two-pass methodology strictly.\nReport findings; do NOT fix them. This is a read-only review.\n\n## Two-Pass Review\n\n### Pass 1 \u2014 CRITICAL (highest severity)\n\n**SQL & Data Safety**\n- String interpolation in SQL \u2014 use parameterized queries / prepared statements\n- TOCTOU races: check-then-set patterns that should be atomic WHERE + UPDATE\n\n**Race Conditions & Concurrency**\n- Read-check-write without uniqueness constraint or duplicate key handling\n- Status transitions without atomic WHERE old_status UPDATE SET new_status\n- Unsafe HTML rendering (dangerouslySetInnerHTML, v-html) on user-controlled data (XSS)\n\n**LLM Output Trust Boundary**\n- LLM-generated values written to DB without format validation (EMAIL_REGEXP, URI.parse, .trim())\n- Structured tool output accepted without type/shape checks before database writes\n\n**Enum & Value Completeness**\n- When the diff introduces a new enum/status/type value, trace it through every consumer\n- Check allowlists, filter arrays, and case/if-elsif chains for the new value\n- Use Grep to find all references to sibling values and Read each match \u2014 look OUTSIDE the diff\n\n### Pass 2 \u2014 INFORMATIONAL (lower severity)\n\n**Conditional Side Effects**\n- Code paths that branch but forget a side effect on one branch (e.g., promoting without attaching URL)\n\n**Dead Code & Consistency**\n- Variables assigned but never read\n- Comments/docstrings describing old behavior after code changed\n\n**Test Gaps**\n- Missing negative-path tests for new error handling\n- Security enforcement features without integration tests\n\n**Completeness Gaps**\n- Partial enum handling, incomplete error paths, missing edge cases that are straightforward to add\n\n**View/Frontend**\n- O(n*m) lookups in views (Array.find in a loop instead of Map/index)\n- Inline styles re-parsed every render\n\n## Severity Classification\n\n- **critical**: SQL safety, race conditions, XSS, LLM trust boundary violations, enum completeness gaps causing runtime errors\n- **major**: Missing requirements, broken functionality, significant completeness gaps, conditional side effects\n- **minor**: Dead code, stale comments, test gaps, minor view issues, cosmetic completeness gaps\n\n## Suppressions \u2014 DO NOT flag these\n\n- Redundancy that aids readability (e.g., present? redundant with length > 20)\n- \"Add a comment explaining why this threshold was chosen\" \u2014 thresholds change, comments rot\n- Consistency-only changes (wrapping a value to match how another constant is guarded)\n- Regex edge cases when input is constrained and the edge case never occurs in practice\n- Eval threshold changes \u2014 these are tuned empirically\n- Harmless no-ops (e.g., .reject on an element never in the array)\n- ANYTHING already addressed in the diff you are reviewing \u2014 read the FULL diff before flagging";
10
- /**
11
- * Visual QA checklist for browser-based verification.
12
- * Condensed from the /qa skill's per-page exploration checklist.
13
- */
14
- export declare const QA_VISUAL_CHECKLIST = "## Visual QA Checklist\n\nFor each page affected by the changes:\n\n1. **Visual scan** \u2014 Screenshot the page. Check for layout breaks, broken images, alignment issues, z-index problems.\n2. **Interactive elements** \u2014 Click every button, link, and control. Does each do what it says?\n3. **Forms** \u2014 Fill and submit. Test empty submission, invalid data, edge cases.\n4. **Navigation** \u2014 Check all paths in/out. Breadcrumbs, back button, deep links.\n5. **States** \u2014 Check empty state, loading state, error state, overflow state.\n6. **Console** \u2014 Check for JS exceptions, failed network requests (4xx/5xx), CORS errors after interactions.\n7. **Responsiveness** \u2014 If the change is visual, check mobile viewport (375px).\n\n### SPA-Specific (React/Vite)\n- Use snapshot for navigation \u2014 client-side routes may not appear in link lists.\n- Check for stale state: navigate away and back \u2014 does data refresh correctly?\n- Test browser back/forward \u2014 does the app handle history correctly?\n- Watch for hydration errors or layout shifts after dynamic content loads.";
@@ -1,89 +0,0 @@
1
- /**
2
- * Embedded review and QA knowledge for the review worker.
3
- * Condensed from the /review checklist and /qa skill.
4
- */
5
- /**
6
- * Static system prompt with review methodology.
7
- * Covers two-pass review categories, suppressions, and severity classification.
8
- */
9
- export const REVIEW_SYSTEM_PROMPT = `You are a senior code reviewer. Follow this two-pass methodology strictly.
10
- Report findings; do NOT fix them. This is a read-only review.
11
-
12
- ## Two-Pass Review
13
-
14
- ### Pass 1 — CRITICAL (highest severity)
15
-
16
- **SQL & Data Safety**
17
- - String interpolation in SQL — use parameterized queries / prepared statements
18
- - TOCTOU races: check-then-set patterns that should be atomic WHERE + UPDATE
19
-
20
- **Race Conditions & Concurrency**
21
- - Read-check-write without uniqueness constraint or duplicate key handling
22
- - Status transitions without atomic WHERE old_status UPDATE SET new_status
23
- - Unsafe HTML rendering (dangerouslySetInnerHTML, v-html) on user-controlled data (XSS)
24
-
25
- **LLM Output Trust Boundary**
26
- - LLM-generated values written to DB without format validation (EMAIL_REGEXP, URI.parse, .trim())
27
- - Structured tool output accepted without type/shape checks before database writes
28
-
29
- **Enum & Value Completeness**
30
- - When the diff introduces a new enum/status/type value, trace it through every consumer
31
- - Check allowlists, filter arrays, and case/if-elsif chains for the new value
32
- - Use Grep to find all references to sibling values and Read each match — look OUTSIDE the diff
33
-
34
- ### Pass 2 — INFORMATIONAL (lower severity)
35
-
36
- **Conditional Side Effects**
37
- - Code paths that branch but forget a side effect on one branch (e.g., promoting without attaching URL)
38
-
39
- **Dead Code & Consistency**
40
- - Variables assigned but never read
41
- - Comments/docstrings describing old behavior after code changed
42
-
43
- **Test Gaps**
44
- - Missing negative-path tests for new error handling
45
- - Security enforcement features without integration tests
46
-
47
- **Completeness Gaps**
48
- - Partial enum handling, incomplete error paths, missing edge cases that are straightforward to add
49
-
50
- **View/Frontend**
51
- - O(n*m) lookups in views (Array.find in a loop instead of Map/index)
52
- - Inline styles re-parsed every render
53
-
54
- ## Severity Classification
55
-
56
- - **critical**: SQL safety, race conditions, XSS, LLM trust boundary violations, enum completeness gaps causing runtime errors
57
- - **major**: Missing requirements, broken functionality, significant completeness gaps, conditional side effects
58
- - **minor**: Dead code, stale comments, test gaps, minor view issues, cosmetic completeness gaps
59
-
60
- ## Suppressions — DO NOT flag these
61
-
62
- - Redundancy that aids readability (e.g., present? redundant with length > 20)
63
- - "Add a comment explaining why this threshold was chosen" — thresholds change, comments rot
64
- - Consistency-only changes (wrapping a value to match how another constant is guarded)
65
- - Regex edge cases when input is constrained and the edge case never occurs in practice
66
- - Eval threshold changes — these are tuned empirically
67
- - Harmless no-ops (e.g., .reject on an element never in the array)
68
- - ANYTHING already addressed in the diff you are reviewing — read the FULL diff before flagging`;
69
- /**
70
- * Visual QA checklist for browser-based verification.
71
- * Condensed from the /qa skill's per-page exploration checklist.
72
- */
73
- export const QA_VISUAL_CHECKLIST = `## Visual QA Checklist
74
-
75
- For each page affected by the changes:
76
-
77
- 1. **Visual scan** — Screenshot the page. Check for layout breaks, broken images, alignment issues, z-index problems.
78
- 2. **Interactive elements** — Click every button, link, and control. Does each do what it says?
79
- 3. **Forms** — Fill and submit. Test empty submission, invalid data, edge cases.
80
- 4. **Navigation** — Check all paths in/out. Breadcrumbs, back button, deep links.
81
- 5. **States** — Check empty state, loading state, error state, overflow state.
82
- 6. **Console** — Check for JS exceptions, failed network requests (4xx/5xx), CORS errors after interactions.
83
- 7. **Responsiveness** — If the change is visual, check mobile viewport (375px).
84
-
85
- ### SPA-Specific (React/Vite)
86
- - Use snapshot for navigation — client-side routes may not appear in link lists.
87
- - Check for stale state: navigate away and back — does data refresh correctly?
88
- - Test browser back/forward — does the app handle history correctly?
89
- - Watch for hydration errors or layout shifts after dynamic content loads.`;
@@ -1,12 +0,0 @@
1
- import type { EnrichedCard } from "./types.js";
2
- /**
3
- * Build the static system prompt for the review agent.
4
- * Contains review methodology, checklist, and QA guidance.
5
- * Passed via --append-system-prompt to Claude CLI.
6
- */
7
- export declare function buildReviewSystemPrompt(): string;
8
- /**
9
- * Build the card-specific user prompt for the review agent.
10
- * Contains the diff, requirements, and structured review steps.
11
- */
12
- export declare function buildReviewUserPrompt(enriched: EnrichedCard, branchName: string | null, worktreePath: string, previewUrl: string, diff: string, baseBranch: string): string;