limn-review 0.1.125 → 0.1.127
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.
- package/dist/api/plugins/rate-limit.js +10 -0
- package/dist/coding/ai-fix/auto-fix.js +6 -0
- package/dist/coding/ai-fix/manager.js +8 -1
- package/dist/coding/ai-fix/review-seed.js +17 -13
- package/dist/config.js +11 -0
- package/dist/db/ci-review-prune.js +22 -0
- package/dist/db/erase-account.js +23 -0
- package/dist/db/migrations/0082_ci_reviews.sql +73 -0
- package/dist/db/migrations/0083_workspace_auto_fix.sql +9 -0
- package/dist/db/migrations/0084_auto_fix_off_by_default.sql +7 -0
- package/dist/db/migrations/0085_auto_review_daily_cap.sql +6 -0
- package/dist/db/migrations/0086_ticket_review_pr_cards.sql +24 -0
- package/dist/db/migrations/0087_auto_post.sql +21 -0
- package/dist/db/migrations/meta/_journal.json +42 -0
- package/dist/db/migrations-pg/0069_ci_reviews.sql +65 -0
- package/dist/db/migrations-pg/0070_workspace_auto_fix.sql +3 -0
- package/dist/db/migrations-pg/0071_auto_fix_off_by_default.sql +5 -0
- package/dist/db/migrations-pg/0072_auto_review_daily_cap.sql +3 -0
- package/dist/db/migrations-pg/0073_ticket_review_pr_cards.sql +17 -0
- package/dist/db/migrations-pg/0074_auto_post.sql +14 -0
- package/dist/db/migrations-pg/meta/_journal.json +42 -0
- package/dist/db/my-turn-dismissals.js +8 -11
- package/dist/db/queries.js +56 -47
- package/dist/db/retention.js +3 -0
- package/dist/db/schema.pg.js +129 -1
- package/dist/db/schema.sqlite.js +176 -0
- package/dist/db/ticket-review-prune.js +4 -1
- package/dist/review/agentic.js +15 -2
- package/dist/review/ci-review/agent.js +143 -0
- package/dist/review/ci-review/currency.js +143 -0
- package/dist/review/ci-review/manager.js +363 -0
- package/dist/review/ci-review/persist.js +486 -0
- package/dist/review/ci-review/prepare.js +49 -0
- package/dist/review/ci-review/prompts.js +103 -0
- package/dist/review/ci-review/reconcile.js +78 -0
- package/dist/review/ci-review/routes.js +197 -0
- package/dist/review/ci-review/schema.js +44 -0
- package/dist/review/ci-review/sweep.js +172 -0
- package/dist/review/claude-review/auto-post.js +405 -0
- package/dist/review/claude-review/auto-settings.js +231 -25
- package/dist/review/claude-review/auto.js +45 -57
- package/dist/review/claude-review/ci-failures.js +0 -42
- package/dist/review/claude-review/manager.js +45 -70
- package/dist/review/claude-review/persist.js +178 -10
- package/dist/review/claude-review/prompts.js +42 -19
- package/dist/review/claude-review/routes.js +9 -0
- package/dist/review/claude-review/settled-by-reply.js +146 -0
- package/dist/review/claude-review/specialists.js +1 -1
- package/dist/review/clone-hygiene.js +1 -1
- package/dist/review/clone-manager.js +72 -1
- package/dist/review/path-guard.js +1 -1
- package/dist/review/post-review.js +25 -4
- package/dist/review/post-seam.js +22 -13
- package/dist/review/schema.js +9 -29
- package/dist/review/submit-map.js +5 -3
- package/dist/review/ticket-review/auto-post.js +291 -0
- package/dist/review/ticket-review/cards.js +271 -0
- package/dist/review/ticket-review/fingerprint.js +2 -0
- package/dist/review/ticket-review/manager.js +167 -26
- package/dist/review/ticket-review/persist.js +44 -2
- package/dist/review/ticket-review/post-item.js +187 -0
- package/dist/review/ticket-review/prepare.js +18 -7
- package/dist/review/ticket-review/prepass.js +259 -0
- package/dist/review/ticket-review/prompts.js +72 -11
- package/dist/review/ticket-review/routes.js +30 -146
- package/dist/review/ticket-review/schema.js +43 -0
- package/dist/review/ticket-review/sweep.js +56 -65
- package/dist/shared/types.js +42 -2
- package/package.json +1 -1
- package/public/assets/{AiFixTab-B1OazE4O.js → AiFixTab-D-Xph0Bw.js} +2 -2
- package/public/assets/ClaudeReviewTab-BwIpVwEa.js +16 -0
- package/public/assets/{PrBotBehaviourTab-h7Nujxom.js → PrBotBehaviourTab-DYNOt_5y.js} +1 -1
- package/public/assets/index-gwxOLty-.css +1 -0
- package/public/assets/index-rdFHnTfI.js +77 -0
- package/public/index.html +2 -2
- package/public/assets/ClaudeReviewTab-7GZdYcOv.js +0 -8
- package/public/assets/index-DTRruvsb.css +0 -1
- package/public/assets/index-q7WCDbAA.js +0 -77
|
@@ -468,6 +468,16 @@ function tierFor(method, path) {
|
|
|
468
468
|
return [TIERS.githubWrite];
|
|
469
469
|
if (path.startsWith('/api/ticket-reviews'))
|
|
470
470
|
return [TIERS.read];
|
|
471
|
+
// CI REVIEW (review/ci-review/routes.ts), matched EXPLICITLY by exact path for the same reason:
|
|
472
|
+
// POST /api/ci-reviews starts an agent run (reads the head's checks + logs) → ai + ai_hourly
|
|
473
|
+
// POST /api/ci-reviews/states batched DB read of the SYNCED head + failing names → read
|
|
474
|
+
// GET …/:id, …/:id/stream, /api/prs/:id/ci-review → read
|
|
475
|
+
if (path === '/api/ci-reviews' && mutating)
|
|
476
|
+
return [TIERS.ai, TIERS.aiHourly];
|
|
477
|
+
if (path === '/api/ci-reviews/states')
|
|
478
|
+
return [TIERS.read];
|
|
479
|
+
if (path.startsWith('/api/ci-reviews'))
|
|
480
|
+
return [TIERS.read];
|
|
471
481
|
if (/^\/api\/claude-reviews\/[^/]+\/chat$/.test(path)) {
|
|
472
482
|
return mutating ? [TIERS.ai, TIERS.aiHourly] : [TIERS.read];
|
|
473
483
|
}
|
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
// 0. the PR's author IS the account's own GitHub user (pull_requests.author_id → users.github_login
|
|
7
7
|
// vs accounts.github_login, case-insensitive). Anyone else's PR: nothing happens and nothing is
|
|
8
8
|
// recorded — fixing someone else's branch is never automatic;
|
|
9
|
+
// ½. `off` — auto AI Fix is switched off for the workspace holding the PR's repo
|
|
10
|
+
// (`workspaces.auto_fix_enabled`, migration 0083 / pg 0070, Settings →
|
|
11
|
+
// Auto review). ON by default; a repo with no membership row reads ON;
|
|
9
12
|
// 1. `nothing_to_fix` — the review's seed is empty (the same refusal as the button's NothingToFix);
|
|
10
13
|
// 2. `head_moved` — the PR's synced head is no longer the reviewed one (the next review decides);
|
|
11
14
|
// 3. `fix_in_progress` — a fix for this PR (auto or manual) is queued or running;
|
|
@@ -33,6 +36,7 @@ import { AUTO_FIX_DAILY_CAP, CLAUDE_REVIEW_MODELS, DEFAULT_AI_FIX_MODEL, } from
|
|
|
33
36
|
import { isFixRunning, loadReviewSeed, startReviewFix } from './manager.js';
|
|
34
37
|
import { parseChangeReport, parseReviewItems } from './persist.js';
|
|
35
38
|
import { getFixPrContext } from './pr-context.js';
|
|
39
|
+
import { readWorkspaceAutoFixForPr } from '../../review/claude-review/auto-settings.js';
|
|
36
40
|
// At most AUTO_FIX_DAILY_CAP (shared) AUTO fixes per PR in any rolling AUTO_FIX_WINDOW_MS.
|
|
37
41
|
export { AUTO_FIX_DAILY_CAP };
|
|
38
42
|
export const AUTO_FIX_WINDOW_MS = 24 * 60 * 60 * 1000;
|
|
@@ -119,6 +123,8 @@ export async function maybeStartAutoFix(ctx, input, deps = defaultDeps, nowMs =
|
|
|
119
123
|
try {
|
|
120
124
|
if (!(await prAuthorIsAccount(ctx, accountId, prId)))
|
|
121
125
|
return { status: 'not_own' };
|
|
126
|
+
if (!(await readWorkspaceAutoFixForPr(ctx, accountId, prId)))
|
|
127
|
+
return skip('off');
|
|
122
128
|
const loaded = await deps.loadReviewSeed(ctx, { accountId, prId, reviewId });
|
|
123
129
|
if (!loaded)
|
|
124
130
|
return skip('not_started');
|
|
@@ -4,6 +4,7 @@ import { getClaudeReviewById } from '../../review/claude-review/persist.js';
|
|
|
4
4
|
import { pickReviewNonce } from '../../review/claude-review/prompts.js';
|
|
5
5
|
import { buildReviewSeed } from './review-seed.js';
|
|
6
6
|
import { getOwnedTicketItemsForPr } from '../../review/ticket-review/persist.js';
|
|
7
|
+
import { getFixableCiItemsForPr } from '../../review/ci-review/persist.js';
|
|
7
8
|
import { buildFixSystemPrompt, buildFixUserPrompt } from './prompts.js';
|
|
8
9
|
import { insertQueuedFix, markFixCancelled, markFixFailed, markFixRunning, reconcileOrphanedFixes, saveFixSuccess, } from './persist.js';
|
|
9
10
|
// In-memory job manager for AI Fix's agentic fixer (CORE, free, local-only — it left the plugin
|
|
@@ -102,7 +103,13 @@ export async function loadReviewSeed(ctx, input) {
|
|
|
102
103
|
return [];
|
|
103
104
|
})
|
|
104
105
|
: [];
|
|
105
|
-
|
|
106
|
+
// The CI review's fixable items at the PR's CURRENT head (manual and auto fixes alike). A failed
|
|
107
|
+
// read costs the CI items only.
|
|
108
|
+
const ciItems = await getFixableCiItemsForPr(ctx, input.accountId, input.prId).catch((err) => {
|
|
109
|
+
ctx.log.warn({ err }, 'ai-fix: loading CI review items failed');
|
|
110
|
+
return [];
|
|
111
|
+
});
|
|
112
|
+
return { review, seed: buildReviewSeed(review, { nonce: pickReviewNonce, ticketItems, ciItems }) };
|
|
106
113
|
}
|
|
107
114
|
catch (err) {
|
|
108
115
|
ctx.log.warn({ err }, 'ai-fix: loading the review seed failed');
|
|
@@ -15,7 +15,9 @@ import { isThreadToFix } from '../../shared/index.js';
|
|
|
15
15
|
// addressed — unless this run re-raised it as a finding (then F covers it).
|
|
16
16
|
// T<n> another reviewer's thread the review judged still needs a fix (shared
|
|
17
17
|
// `isThreadToFix`: valid / partly valid AND not / partly addressed).
|
|
18
|
-
// C<n> a CI failure the review judged fixable in this PR
|
|
18
|
+
// C<n> a CI failure the CI REVIEW (review/ci-review/) explained and judged fixable in this PR —
|
|
19
|
+
// from its latest succeeded run AT THE PR'S CURRENT HEAD (`ciItems`, the caller's
|
|
20
|
+
// `getFixableCiItemsForPr`), never from the code review's own (legacy) `ciFailures`.
|
|
19
21
|
// S<t>-AC<n> an acceptance criterion of ticket t judged not met or partly met, and
|
|
20
22
|
// S<t>-M<n> something ticket t asked for that is missing — BOTH from the TICKET review
|
|
21
23
|
// (review/ticket-review/), never from the PR review, and ONLY the items whose owner is
|
|
@@ -72,21 +74,21 @@ function mkItem(ref, kind, title, over = {}) {
|
|
|
72
74
|
...over,
|
|
73
75
|
};
|
|
74
76
|
}
|
|
75
|
-
// ---- CI failures (the review's
|
|
76
|
-
// Only a DIAGNOSED failure
|
|
77
|
-
//
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
return [];
|
|
82
|
-
return raw.filter((f) => f != null && f.status === 'diagnosed' && f.fixableInPr === true);
|
|
77
|
+
// ---- CI failures (the CI review's items) ----
|
|
78
|
+
// Only a DIAGNOSED failure judged fixable in this PR (`fixableInPr === true`). Anything else —
|
|
79
|
+
// infrastructure, flaky, unclear, not checked — is not the fixer's. The code review's own legacy
|
|
80
|
+
// `ciFailures` are NEVER read: the CI review replaced them, and they describe an older head.
|
|
81
|
+
export function fixableCiItems(items) {
|
|
82
|
+
return items.filter((f) => f != null && f.status === 'diagnosed' && f.fixableInPr === true);
|
|
83
83
|
}
|
|
84
84
|
/**
|
|
85
85
|
* Collect every fixable item of a review, refs assigned. Pure — the review is the stored run.
|
|
86
86
|
*/
|
|
87
87
|
export function collectReviewItems(review,
|
|
88
88
|
// The ticket review's items THIS PR owns. A MANUAL fix only; absent/[] ⇒ none (an auto fix).
|
|
89
|
-
ticketItems = []
|
|
89
|
+
ticketItems = [],
|
|
90
|
+
// The CI review's items at the PR's current head (both manual and auto fixes); absent/[] ⇒ none.
|
|
91
|
+
ciItems = []) {
|
|
90
92
|
const out = [];
|
|
91
93
|
// F — the review's findings. A RE-RAISE (`priorFindingId` set) left out only because the same
|
|
92
94
|
// comment is already on this commit (follow-up.ts `isAlreadyOnThisCommit`) is NOT a reader's
|
|
@@ -161,13 +163,15 @@ ticketItems = []) {
|
|
|
161
163
|
});
|
|
162
164
|
});
|
|
163
165
|
// C — CI failures fixable in this PR.
|
|
164
|
-
|
|
166
|
+
fixableCiItems(ciItems).forEach((f, i) => {
|
|
165
167
|
const ref = `C${i + 1}`;
|
|
166
|
-
const first = f.relatedFiles?.[0] ?? null;
|
|
168
|
+
const first = f.path ? { path: f.path, line: f.line } : (f.relatedFiles?.[0] ?? null);
|
|
167
169
|
const body = [
|
|
168
170
|
`Check: ${f.checkName}${f.step ? ` (step: ${f.step})` : ''}`,
|
|
169
171
|
f.cause ? `Cause: ${f.cause}` : '',
|
|
170
172
|
f.explanation ? `Detail: ${f.explanation}` : '',
|
|
173
|
+
f.path ? `Where: ${where(f.path, f.line)}` : '',
|
|
174
|
+
f.suggestion ? `Suggested change: ${f.suggestion}` : '',
|
|
171
175
|
f.relatedFiles?.length
|
|
172
176
|
? `Related files: ${f.relatedFiles.map((r) => where(r.path, r.line)).join(', ')}`
|
|
173
177
|
: '',
|
|
@@ -226,7 +230,7 @@ function fencedTexts(items, summary) {
|
|
|
226
230
|
*/
|
|
227
231
|
export function buildReviewSeed(review, opts) {
|
|
228
232
|
const budget = opts.budgetChars ?? REVIEW_SEED_CHAR_BUDGET;
|
|
229
|
-
const all = collectReviewItems(review, opts.ticketItems ?? []);
|
|
233
|
+
const all = collectReviewItems(review, opts.ticketItems ?? [], opts.ciItems ?? []);
|
|
230
234
|
if (all.length === 0)
|
|
231
235
|
return { items: [], sentRefs: [], text: '' };
|
|
232
236
|
const summary = clip(review.userBody?.trim() || review.summary?.trim() || '', SUMMARY_MAX);
|
package/dist/config.js
CHANGED
|
@@ -536,9 +536,20 @@ export const config = {
|
|
|
536
536
|
// every member's worktree.
|
|
537
537
|
ticketReviewBudgetUsd: floatFromEnv('TICKET_REVIEW_BUDGET_USD', 4),
|
|
538
538
|
ticketReviewMaxTurns: intFromEnv('TICKET_REVIEW_MAX_TURNS', 40),
|
|
539
|
+
// The per-card spend cap of the ticket review's CARD PRE-PASS (Sonnet 5, diff-only, one tool —
|
|
540
|
+
// review/ticket-review/prepass.ts). Its spend is added to the ticket run's cost. Env only.
|
|
541
|
+
ticketCardBudgetUsd: floatFromEnv('TICKET_CARD_BUDGET_USD', 0.4),
|
|
539
542
|
// AUTOMATIC ticket reviews (trigger 'auto' / 'cascade') per workspace per UTC day. Not shared with
|
|
540
543
|
// the PR review's own auto cap. Manual runs never count. Default = shared TICKET_REVIEW_DAILY_CAP.
|
|
541
544
|
ticketReviewDailyCap: intFromEnv('TICKET_REVIEW_DAILY_CAP', 20),
|
|
545
|
+
// ---- CI review (one run per (PR, head, failing-check set) — review/ci-review/) ----
|
|
546
|
+
// Its OWN spend cap and turn cap: a CI run reads log excerpts and the files they point at, nothing
|
|
547
|
+
// more. Env only — no route sets it.
|
|
548
|
+
ciReviewBudgetUsd: floatFromEnv('CI_REVIEW_BUDGET_USD', 2),
|
|
549
|
+
ciReviewMaxTurns: intFromEnv('CI_REVIEW_MAX_TURNS', 25),
|
|
550
|
+
// AUTOMATIC CI reviews per workspace per UTC day. Not shared with the PR or ticket review caps.
|
|
551
|
+
// Manual runs never count. Default = shared CI_REVIEW_DAILY_CAP.
|
|
552
|
+
ciReviewDailyCap: intFromEnv('CI_REVIEW_DAILY_CAP', 20),
|
|
542
553
|
// ---- Diff-size cap ----
|
|
543
554
|
// A very large inlined diff is the dominant cost on a big PR (it's the cached
|
|
544
555
|
// prefix re-read every turn). The diff shown IN THE PROMPT is truncated at a
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { inArray } from 'drizzle-orm';
|
|
2
|
+
import { schema } from './client.js';
|
|
3
|
+
// THE CI-REVIEW HALF OF BOTH PR DELETE PATHS (deleteRepo in queries.ts, retention's
|
|
4
|
+
// deletePrSubtree). A CI review belongs to exactly one PR, so deleting the PR deletes its runs and
|
|
5
|
+
// their items. Children before parents, explicitly — the composite FKs cascade, but these paths run
|
|
6
|
+
// parent-last by hand on both dialects. Runs inside the caller's transaction.
|
|
7
|
+
export async function pruneCiReviewsForPrs(tx, prIds) {
|
|
8
|
+
if (prIds.length === 0)
|
|
9
|
+
return;
|
|
10
|
+
const runs = schema.ciReviews;
|
|
11
|
+
const rows = await tx
|
|
12
|
+
.select({ id: runs.id })
|
|
13
|
+
.from(runs)
|
|
14
|
+
.where(inArray(runs.prId, [...prIds]))
|
|
15
|
+
.execute();
|
|
16
|
+
const runIds = rows.map((r) => r.id);
|
|
17
|
+
if (runIds.length > 0) {
|
|
18
|
+
await tx.delete(schema.ciReviewItems).where(inArray(schema.ciReviewItems.ciReviewId, runIds)).execute();
|
|
19
|
+
}
|
|
20
|
+
await tx.delete(runs).where(inArray(runs.prId, [...prIds])).execute();
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=ci-review-prune.js.map
|
package/dist/db/erase-account.js
CHANGED
|
@@ -128,6 +128,19 @@ export async function eraseAccountData(accountId) {
|
|
|
128
128
|
.delete(schema.ticketReviews)
|
|
129
129
|
.where(eq(schema.ticketReviews.accountId, accountId))
|
|
130
130
|
.execute();
|
|
131
|
+
// Ticket review contribution cards (migration 0086 / pg 0073): model text describing the user's
|
|
132
|
+
// PRs. The repo loop has normally taken them with their PRs; explicit, like every agentic table.
|
|
133
|
+
await tx
|
|
134
|
+
.delete(schema.ticketReviewPrCards)
|
|
135
|
+
.where(eq(schema.ticketReviewPrCards.accountId, accountId))
|
|
136
|
+
.execute();
|
|
137
|
+
// CI reviews (migration 0082 / pg 0069), items first. The repo loop has normally taken them with
|
|
138
|
+
// their PRs; explicit, like every agentic table.
|
|
139
|
+
await tx
|
|
140
|
+
.delete(schema.ciReviewItems)
|
|
141
|
+
.where(eq(schema.ciReviewItems.accountId, accountId))
|
|
142
|
+
.execute();
|
|
143
|
+
await tx.delete(schema.ciReviews).where(eq(schema.ciReviews.accountId, accountId)).execute();
|
|
131
144
|
// AI Fix runs (patches of the user's code + the prompts they were built from). Core since
|
|
132
145
|
// migration 0074 / pg 0061 — the plugin's erasure hook used to own them.
|
|
133
146
|
await tx.delete(schema.aiFixes).where(eq(schema.aiFixes.accountId, accountId)).execute();
|
|
@@ -211,6 +224,16 @@ export function accountScopedTables() {
|
|
|
211
224
|
col: schema.ticketReviewItems.accountId,
|
|
212
225
|
table: schema.ticketReviewItems,
|
|
213
226
|
},
|
|
227
|
+
// Contribution cards (migration 0086 / pg 0073) — one per (PR, head); erased explicitly above.
|
|
228
|
+
{
|
|
229
|
+
name: 'ticketReviewPrCards',
|
|
230
|
+
col: schema.ticketReviewPrCards.accountId,
|
|
231
|
+
table: schema.ticketReviewPrCards,
|
|
232
|
+
},
|
|
233
|
+
// CI reviews (migration 0082 / pg 0069) — the runs and their per-check items. Both carry their
|
|
234
|
+
// own accountId; both are erased explicitly above.
|
|
235
|
+
{ name: 'ciReviews', col: schema.ciReviews.accountId, table: schema.ciReviews },
|
|
236
|
+
{ name: 'ciReviewItems', col: schema.ciReviewItems.accountId, table: schema.ciReviewItems },
|
|
214
237
|
{ name: 'aiUsage', col: aiUsage.accountId, table: aiUsage },
|
|
215
238
|
// Adopted from the plugin (migration 0074 / pg 0061) with AI Fix's fixer. No FKs, so nothing
|
|
216
239
|
// cascades: the explicit delete above is the whole guarantee. (`reviewLearnings`, review
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
-- CI REVIEW (local-only feature, like the rest of Claude Review): a separate Claude run that explains
|
|
2
|
+
-- why checks FAILED on a PR's head, split out of the PR review's own run. Two tables:
|
|
3
|
+
-- ci_reviews one row per run (history kept). Keyed by (pr, head, sorted failing check names):
|
|
4
|
+
-- `failing_key` is what the run read live, `trigger_key` the synced set that
|
|
5
|
+
-- started it.
|
|
6
|
+
-- ci_review_items one row per failing check: Claude's cause, or the server's not-checked reason.
|
|
7
|
+
-- Run ids arrive in request paths, so the item table's tenancy is STRUCTURAL: a composite FK against
|
|
8
|
+
-- ci_reviews(id, account_id), whose parent key `ci_reviews_id_account` is created here; the run's PR
|
|
9
|
+
-- against pull_requests(id, account_id). Both delete paths (deleteRepo, retention's deletePrSubtree)
|
|
10
|
+
-- and eraseAccountData delete these rows explicitly. `claude_reviews.ci_failures` becomes LEGACY
|
|
11
|
+
-- (read-only); nothing here touches it. The Postgres twin is migrations-pg/0069_ci_reviews.sql.
|
|
12
|
+
CREATE TABLE IF NOT EXISTS `ci_reviews` (
|
|
13
|
+
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
|
14
|
+
`account_id` integer NOT NULL,
|
|
15
|
+
`workspace_id` integer NOT NULL,
|
|
16
|
+
`pr_id` integer NOT NULL,
|
|
17
|
+
`repo_id` integer NOT NULL,
|
|
18
|
+
`head_sha` text NOT NULL,
|
|
19
|
+
`failing_key` text,
|
|
20
|
+
`trigger_key` text,
|
|
21
|
+
`failing_checks` text,
|
|
22
|
+
`trigger` text DEFAULT 'manual' NOT NULL,
|
|
23
|
+
`status` text NOT NULL,
|
|
24
|
+
`model` text NOT NULL,
|
|
25
|
+
`cost_usd` real,
|
|
26
|
+
`input_tokens` integer,
|
|
27
|
+
`output_tokens` integer,
|
|
28
|
+
`num_turns` integer,
|
|
29
|
+
`error` text,
|
|
30
|
+
`refused` text,
|
|
31
|
+
`ci_state` text,
|
|
32
|
+
`summary` text,
|
|
33
|
+
`started_at` integer,
|
|
34
|
+
`completed_at` integer,
|
|
35
|
+
`created_at` integer DEFAULT (unixepoch()) NOT NULL,
|
|
36
|
+
FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`) ON UPDATE no action ON DELETE cascade,
|
|
37
|
+
CONSTRAINT `cir_pr_account_fk` FOREIGN KEY (`pr_id`,`account_id`) REFERENCES `pull_requests`(`id`,`account_id`) ON UPDATE no action ON DELETE cascade
|
|
38
|
+
);
|
|
39
|
+
--> statement-breakpoint
|
|
40
|
+
CREATE INDEX IF NOT EXISTS `cir_account_pr_created_idx` ON `ci_reviews` (`account_id`,`pr_id`,`created_at`);
|
|
41
|
+
--> statement-breakpoint
|
|
42
|
+
CREATE INDEX IF NOT EXISTS `cir_account_ws_created_idx` ON `ci_reviews` (`account_id`,`workspace_id`,`created_at`);
|
|
43
|
+
--> statement-breakpoint
|
|
44
|
+
CREATE UNIQUE INDEX IF NOT EXISTS `ci_reviews_id_account` ON `ci_reviews` (`id`,`account_id`);
|
|
45
|
+
--> statement-breakpoint
|
|
46
|
+
CREATE TABLE IF NOT EXISTS `ci_review_items` (
|
|
47
|
+
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
|
48
|
+
`ci_review_id` integer NOT NULL,
|
|
49
|
+
`account_id` integer NOT NULL,
|
|
50
|
+
`ref` text,
|
|
51
|
+
`check_name` text NOT NULL,
|
|
52
|
+
`job_id` integer,
|
|
53
|
+
`step` text,
|
|
54
|
+
`url` text,
|
|
55
|
+
`sent` integer DEFAULT false NOT NULL,
|
|
56
|
+
`carried` integer DEFAULT false NOT NULL,
|
|
57
|
+
`status` text NOT NULL,
|
|
58
|
+
`not_checked_reason` text,
|
|
59
|
+
`cause` text,
|
|
60
|
+
`explanation` text,
|
|
61
|
+
`category` text,
|
|
62
|
+
`fixable_in_pr` integer,
|
|
63
|
+
`path` text,
|
|
64
|
+
`line` integer,
|
|
65
|
+
`suggestion` text,
|
|
66
|
+
`related_files` text,
|
|
67
|
+
`assessed_at_head` text NOT NULL,
|
|
68
|
+
`created_at` integer DEFAULT (unixepoch()) NOT NULL,
|
|
69
|
+
FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`) ON UPDATE no action ON DELETE cascade,
|
|
70
|
+
CONSTRAINT `ciri_review_account_fk` FOREIGN KEY (`ci_review_id`,`account_id`) REFERENCES `ci_reviews`(`id`,`account_id`) ON UPDATE no action ON DELETE cascade
|
|
71
|
+
);
|
|
72
|
+
--> statement-breakpoint
|
|
73
|
+
CREATE INDEX IF NOT EXISTS `ciri_review_idx` ON `ci_review_items` (`ci_review_id`);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
-- AUTO AI FIX, PER WORKSPACE — `workspaces.auto_fix_enabled`. After a succeeded AUTO Claude review
|
|
2
|
+
-- of the reader's OWN pull request, Limn may prepare one review-seeded AI Fix (never pushed;
|
|
3
|
+
-- coding/ai-fix/auto-fix.ts `maybeStartAutoFix`). This is that step's switch, edited in Settings
|
|
4
|
+
-- beside auto review and written by `setWorkspaceAutoReview` (review/claude-review/auto-settings.ts).
|
|
5
|
+
-- ⚠ DEFAULT TRUE (1) and NOT NULL: auto fix ran unconditionally before this column existed, so
|
|
6
|
+
-- every existing workspace keeps today's behaviour until someone switches it off. It only matters
|
|
7
|
+
-- while auto review is on, because it only runs after an auto review. The Postgres twin is
|
|
8
|
+
-- migrations-pg/0070_workspace_auto_fix.sql.
|
|
9
|
+
ALTER TABLE `workspaces` ADD `auto_fix_enabled` integer DEFAULT 1 NOT NULL;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
-- AUTO AI FIX IS OFF BY DEFAULT. 0083 added `workspaces.auto_fix_enabled` DEFAULT 1 so nothing
|
|
2
|
+
-- changed on upgrade; the product decision since is OFF until someone switches it on. Every
|
|
3
|
+
-- existing workspace is switched off once here (the switch shipped one release earlier, so no one
|
|
4
|
+
-- has had time to choose it). SQLite cannot change a column default in place, so the 0083 DDL
|
|
5
|
+
-- default stays 1; both workspace inserts in db/queries.ts write `autoFixEnabled: false`
|
|
6
|
+
-- explicitly. The Postgres twin is migrations-pg/0071_auto_fix_off_by_default.sql.
|
|
7
|
+
UPDATE `workspaces` SET `auto_fix_enabled` = 0;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
-- AUTO REVIEW DAILY CAP, PER WORKSPACE. How many auto Claude reviews a workspace may start per UTC
|
|
2
|
+
-- day. OVERRIDES ONLY: NULL = the product default (20, `AUTO_REVIEW_DAILY_CAP`), resolved through
|
|
3
|
+
-- `resolveAutoReviewDailyCap` (review/claude-review/auto-settings.ts); the PUT route bounds it
|
|
4
|
+
-- 1..500. Nullable with no default, so no workspace insert has to name it. The Postgres twin is
|
|
5
|
+
-- migrations-pg/0072_auto_review_daily_cap.sql.
|
|
6
|
+
ALTER TABLE `workspaces` ADD `auto_review_daily_cap` integer;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
-- TICKET REVIEW CONTRIBUTION CARDS. One row per (account, PR, head commit): a model-written,
|
|
2
|
+
-- verdict-free description of what that PR's head does (summary, interfaces, the story criteria it
|
|
3
|
+
-- moves forward, loose ends) plus the PR's changed files at that head (server-written). A ticket
|
|
4
|
+
-- review reads a member whose CURRENT head has a card as the card, not as its diff; the run writes a
|
|
5
|
+
-- card for every member it read as a diff (source 'story_check') and a cheap per-PR pre-pass writes
|
|
6
|
+
-- the overflow (source 'prepass', with its own cost). Re-writing the same head replaces the card.
|
|
7
|
+
-- Tenancy: a composite FK against pull_requests(id, account_id). Both delete paths (deleteRepo,
|
|
8
|
+
-- retention's deletePrSubtree, via db/ticket-review-prune.ts) and eraseAccountData delete these rows
|
|
9
|
+
-- explicitly. The Postgres twin is migrations-pg/0073_ticket_review_pr_cards.sql.
|
|
10
|
+
CREATE TABLE IF NOT EXISTS `ticket_review_pr_cards` (
|
|
11
|
+
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
|
12
|
+
`account_id` integer NOT NULL,
|
|
13
|
+
`pr_id` integer NOT NULL,
|
|
14
|
+
`head_sha` text NOT NULL,
|
|
15
|
+
`card` text NOT NULL,
|
|
16
|
+
`source` text NOT NULL,
|
|
17
|
+
`model` text NOT NULL,
|
|
18
|
+
`cost_usd` real,
|
|
19
|
+
`created_at` integer DEFAULT (unixepoch()) NOT NULL,
|
|
20
|
+
FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`) ON UPDATE no action ON DELETE cascade,
|
|
21
|
+
CONSTRAINT `trpc_pr_account_fk` FOREIGN KEY (`pr_id`,`account_id`) REFERENCES `pull_requests`(`id`,`account_id`) ON UPDATE no action ON DELETE cascade
|
|
22
|
+
);
|
|
23
|
+
--> statement-breakpoint
|
|
24
|
+
CREATE UNIQUE INDEX IF NOT EXISTS `trpc_account_pr_head_ux` ON `ticket_review_pr_cards` (`account_id`,`pr_id`,`head_sha`);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
-- AUTO-POSTING OF CLAUDE REVIEW OUTPUT (review/claude-review/auto-post.ts, review/ticket-review/
|
|
2
|
+
-- auto-post.ts). A per-workspace master switch, OFF for every workspace existing and new, plus the
|
|
3
|
+
-- per-run records of what was posted and the per-item "posted automatically" flag.
|
|
4
|
+
-- workspaces.auto_post_enabled NULL/false = off. Nullable, no default: no insert names it.
|
|
5
|
+
-- workspaces.auto_post_settings JSON, OVERRIDES ONLY ({ scope?, kinds? }); NULL = defaults.
|
|
6
|
+
-- claude_reviews.auto_post JSON ClaudeAutoPostRecord; NULL = never looked at.
|
|
7
|
+
-- claude_review_findings.posted_auto true = posted by auto-posting.
|
|
8
|
+
-- ticket_reviews.auto_post JSON TicketAutoPostRecord; NULL = never looked at.
|
|
9
|
+
-- ticket_review_items.posted_auto true = posted by auto-posting (carried with the posting).
|
|
10
|
+
-- The Postgres twin is migrations-pg/0074_auto_post.sql.
|
|
11
|
+
ALTER TABLE `workspaces` ADD `auto_post_enabled` integer;
|
|
12
|
+
--> statement-breakpoint
|
|
13
|
+
ALTER TABLE `workspaces` ADD `auto_post_settings` text;
|
|
14
|
+
--> statement-breakpoint
|
|
15
|
+
ALTER TABLE `claude_reviews` ADD `auto_post` text;
|
|
16
|
+
--> statement-breakpoint
|
|
17
|
+
ALTER TABLE `claude_review_findings` ADD `posted_auto` integer;
|
|
18
|
+
--> statement-breakpoint
|
|
19
|
+
ALTER TABLE `ticket_reviews` ADD `auto_post` text;
|
|
20
|
+
--> statement-breakpoint
|
|
21
|
+
ALTER TABLE `ticket_review_items` ADD `posted_auto` integer;
|
|
@@ -575,6 +575,48 @@
|
|
|
575
575
|
"when": 1790658000000,
|
|
576
576
|
"tag": "0081_review_request_requester",
|
|
577
577
|
"breakpoints": true
|
|
578
|
+
},
|
|
579
|
+
{
|
|
580
|
+
"idx": 82,
|
|
581
|
+
"version": "6",
|
|
582
|
+
"when": 1790661600000,
|
|
583
|
+
"tag": "0082_ci_reviews",
|
|
584
|
+
"breakpoints": true
|
|
585
|
+
},
|
|
586
|
+
{
|
|
587
|
+
"idx": 83,
|
|
588
|
+
"version": "6",
|
|
589
|
+
"when": 1790665200000,
|
|
590
|
+
"tag": "0083_workspace_auto_fix",
|
|
591
|
+
"breakpoints": true
|
|
592
|
+
},
|
|
593
|
+
{
|
|
594
|
+
"idx": 84,
|
|
595
|
+
"version": "6",
|
|
596
|
+
"when": 1790751600000,
|
|
597
|
+
"tag": "0084_auto_fix_off_by_default",
|
|
598
|
+
"breakpoints": true
|
|
599
|
+
},
|
|
600
|
+
{
|
|
601
|
+
"idx": 85,
|
|
602
|
+
"version": "6",
|
|
603
|
+
"when": 1790838000000,
|
|
604
|
+
"tag": "0085_auto_review_daily_cap",
|
|
605
|
+
"breakpoints": true
|
|
606
|
+
},
|
|
607
|
+
{
|
|
608
|
+
"idx": 86,
|
|
609
|
+
"version": "6",
|
|
610
|
+
"when": 1790841600000,
|
|
611
|
+
"tag": "0086_ticket_review_pr_cards",
|
|
612
|
+
"breakpoints": true
|
|
613
|
+
},
|
|
614
|
+
{
|
|
615
|
+
"idx": 87,
|
|
616
|
+
"version": "6",
|
|
617
|
+
"when": 1790928000000,
|
|
618
|
+
"tag": "0087_auto_post",
|
|
619
|
+
"breakpoints": true
|
|
578
620
|
}
|
|
579
621
|
]
|
|
580
622
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
-- Postgres twin of migrations/0082_ci_reviews.sql, where the contract lives. CI review is
|
|
2
|
+
-- local-only, so the tables exist in Postgres for schema parity. `job_id` is BIGINT: GitHub Actions
|
|
3
|
+
-- job ids have passed 2^31.
|
|
4
|
+
CREATE TABLE IF NOT EXISTS "ci_reviews" (
|
|
5
|
+
"id" serial PRIMARY KEY NOT NULL,
|
|
6
|
+
"account_id" integer NOT NULL,
|
|
7
|
+
"workspace_id" integer NOT NULL,
|
|
8
|
+
"pr_id" integer NOT NULL,
|
|
9
|
+
"repo_id" integer NOT NULL,
|
|
10
|
+
"head_sha" text NOT NULL,
|
|
11
|
+
"failing_key" text,
|
|
12
|
+
"trigger_key" text,
|
|
13
|
+
"failing_checks" jsonb,
|
|
14
|
+
"trigger" text DEFAULT 'manual' NOT NULL,
|
|
15
|
+
"status" text NOT NULL,
|
|
16
|
+
"model" text NOT NULL,
|
|
17
|
+
"cost_usd" double precision,
|
|
18
|
+
"input_tokens" integer,
|
|
19
|
+
"output_tokens" integer,
|
|
20
|
+
"num_turns" integer,
|
|
21
|
+
"error" text,
|
|
22
|
+
"refused" text,
|
|
23
|
+
"ci_state" jsonb,
|
|
24
|
+
"summary" text,
|
|
25
|
+
"started_at" timestamp with time zone,
|
|
26
|
+
"completed_at" timestamp with time zone,
|
|
27
|
+
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
28
|
+
CONSTRAINT "ci_reviews_account_id_accounts_id_fk" FOREIGN KEY ("account_id") REFERENCES "accounts"("id") ON DELETE cascade,
|
|
29
|
+
CONSTRAINT "cir_pr_account_fk" FOREIGN KEY ("pr_id","account_id") REFERENCES "pull_requests"("id","account_id") ON DELETE cascade
|
|
30
|
+
);
|
|
31
|
+
--> statement-breakpoint
|
|
32
|
+
CREATE INDEX IF NOT EXISTS "cir_account_pr_created_idx" ON "ci_reviews" USING btree ("account_id","pr_id","created_at");
|
|
33
|
+
--> statement-breakpoint
|
|
34
|
+
CREATE INDEX IF NOT EXISTS "cir_account_ws_created_idx" ON "ci_reviews" USING btree ("account_id","workspace_id","created_at");
|
|
35
|
+
--> statement-breakpoint
|
|
36
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "ci_reviews_id_account" ON "ci_reviews" USING btree ("id","account_id");
|
|
37
|
+
--> statement-breakpoint
|
|
38
|
+
CREATE TABLE IF NOT EXISTS "ci_review_items" (
|
|
39
|
+
"id" serial PRIMARY KEY NOT NULL,
|
|
40
|
+
"ci_review_id" integer NOT NULL,
|
|
41
|
+
"account_id" integer NOT NULL,
|
|
42
|
+
"ref" text,
|
|
43
|
+
"check_name" text NOT NULL,
|
|
44
|
+
"job_id" bigint,
|
|
45
|
+
"step" text,
|
|
46
|
+
"url" text,
|
|
47
|
+
"sent" boolean DEFAULT false NOT NULL,
|
|
48
|
+
"carried" boolean DEFAULT false NOT NULL,
|
|
49
|
+
"status" text NOT NULL,
|
|
50
|
+
"not_checked_reason" text,
|
|
51
|
+
"cause" text,
|
|
52
|
+
"explanation" text,
|
|
53
|
+
"category" text,
|
|
54
|
+
"fixable_in_pr" boolean,
|
|
55
|
+
"path" text,
|
|
56
|
+
"line" integer,
|
|
57
|
+
"suggestion" text,
|
|
58
|
+
"related_files" jsonb,
|
|
59
|
+
"assessed_at_head" text NOT NULL,
|
|
60
|
+
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
61
|
+
CONSTRAINT "ci_review_items_account_id_accounts_id_fk" FOREIGN KEY ("account_id") REFERENCES "accounts"("id") ON DELETE cascade,
|
|
62
|
+
CONSTRAINT "ciri_review_account_fk" FOREIGN KEY ("ci_review_id","account_id") REFERENCES "ci_reviews"("id","account_id") ON DELETE cascade
|
|
63
|
+
);
|
|
64
|
+
--> statement-breakpoint
|
|
65
|
+
CREATE INDEX IF NOT EXISTS "ciri_review_idx" ON "ci_review_items" USING btree ("ci_review_id");
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
-- Postgres twin of migrations/0083_workspace_auto_fix.sql — the per-workspace auto AI Fix switch.
|
|
2
|
+
-- DEFAULT true so every existing workspace keeps today's behaviour. See the sqlite original.
|
|
3
|
+
ALTER TABLE "workspaces" ADD COLUMN IF NOT EXISTS "auto_fix_enabled" boolean DEFAULT true NOT NULL;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
-- Postgres twin of migrations/0084_auto_fix_off_by_default.sql: auto AI Fix is OFF by default,
|
|
2
|
+
-- and every existing workspace is switched off once.
|
|
3
|
+
ALTER TABLE "workspaces" ALTER COLUMN "auto_fix_enabled" SET DEFAULT false;
|
|
4
|
+
--> statement-breakpoint
|
|
5
|
+
UPDATE "workspaces" SET "auto_fix_enabled" = false;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
-- Postgres twin of migrations/0086_ticket_review_pr_cards.sql: ticket review contribution cards,
|
|
2
|
+
-- one row per (account, PR, head commit). The contract lives in schema.sqlite.ts.
|
|
3
|
+
CREATE TABLE IF NOT EXISTS "ticket_review_pr_cards" (
|
|
4
|
+
"id" serial PRIMARY KEY NOT NULL,
|
|
5
|
+
"account_id" integer NOT NULL,
|
|
6
|
+
"pr_id" integer NOT NULL,
|
|
7
|
+
"head_sha" text NOT NULL,
|
|
8
|
+
"card" jsonb NOT NULL,
|
|
9
|
+
"source" text NOT NULL,
|
|
10
|
+
"model" text NOT NULL,
|
|
11
|
+
"cost_usd" double precision,
|
|
12
|
+
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
13
|
+
CONSTRAINT "ticket_review_pr_cards_account_id_accounts_id_fk" FOREIGN KEY ("account_id") REFERENCES "accounts"("id") ON DELETE cascade,
|
|
14
|
+
CONSTRAINT "trpc_pr_account_fk" FOREIGN KEY ("pr_id","account_id") REFERENCES "pull_requests"("id","account_id") ON DELETE cascade
|
|
15
|
+
);
|
|
16
|
+
--> statement-breakpoint
|
|
17
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "trpc_account_pr_head_ux" ON "ticket_review_pr_cards" USING btree ("account_id","pr_id","head_sha");
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
-- Postgres twin of migrations/0087_auto_post.sql: auto-posting of Claude review output. A
|
|
2
|
+
-- per-workspace switch (NULL/false = off) + overrides-only settings, the per-run records and the
|
|
3
|
+
-- per-item "posted automatically" flags. The contract lives in schema.sqlite.ts.
|
|
4
|
+
ALTER TABLE "workspaces" ADD COLUMN IF NOT EXISTS "auto_post_enabled" boolean;
|
|
5
|
+
--> statement-breakpoint
|
|
6
|
+
ALTER TABLE "workspaces" ADD COLUMN IF NOT EXISTS "auto_post_settings" jsonb;
|
|
7
|
+
--> statement-breakpoint
|
|
8
|
+
ALTER TABLE "claude_reviews" ADD COLUMN IF NOT EXISTS "auto_post" jsonb;
|
|
9
|
+
--> statement-breakpoint
|
|
10
|
+
ALTER TABLE "claude_review_findings" ADD COLUMN IF NOT EXISTS "posted_auto" boolean;
|
|
11
|
+
--> statement-breakpoint
|
|
12
|
+
ALTER TABLE "ticket_reviews" ADD COLUMN IF NOT EXISTS "auto_post" jsonb;
|
|
13
|
+
--> statement-breakpoint
|
|
14
|
+
ALTER TABLE "ticket_review_items" ADD COLUMN IF NOT EXISTS "posted_auto" boolean;
|
|
@@ -484,6 +484,48 @@
|
|
|
484
484
|
"when": 1790658000000,
|
|
485
485
|
"tag": "0068_review_request_requester",
|
|
486
486
|
"breakpoints": true
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
"idx": 69,
|
|
490
|
+
"version": "7",
|
|
491
|
+
"when": 1790661600000,
|
|
492
|
+
"tag": "0069_ci_reviews",
|
|
493
|
+
"breakpoints": true
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
"idx": 70,
|
|
497
|
+
"version": "7",
|
|
498
|
+
"when": 1790665200000,
|
|
499
|
+
"tag": "0070_workspace_auto_fix",
|
|
500
|
+
"breakpoints": true
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
"idx": 71,
|
|
504
|
+
"version": "7",
|
|
505
|
+
"when": 1790751600000,
|
|
506
|
+
"tag": "0071_auto_fix_off_by_default",
|
|
507
|
+
"breakpoints": true
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
"idx": 72,
|
|
511
|
+
"version": "7",
|
|
512
|
+
"when": 1790838000000,
|
|
513
|
+
"tag": "0072_auto_review_daily_cap",
|
|
514
|
+
"breakpoints": true
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
"idx": 73,
|
|
518
|
+
"version": "7",
|
|
519
|
+
"when": 1790841600000,
|
|
520
|
+
"tag": "0073_ticket_review_pr_cards",
|
|
521
|
+
"breakpoints": true
|
|
522
|
+
},
|
|
523
|
+
{
|
|
524
|
+
"idx": 74,
|
|
525
|
+
"version": "7",
|
|
526
|
+
"when": 1790928000000,
|
|
527
|
+
"tag": "0074_auto_post",
|
|
528
|
+
"breakpoints": true
|
|
487
529
|
}
|
|
488
530
|
]
|
|
489
531
|
}
|
|
@@ -30,6 +30,12 @@
|
|
|
30
30
|
// clock scores higher and wins `compareScored`'s age tie-break). Ties beyond that break on the
|
|
31
31
|
// item's own id, so two reads of unchanged data keep the same card.
|
|
32
32
|
//
|
|
33
|
+
// ⚠ A CLAUDE REVIEW IS EXEMPT: it never competes for the PR's one card and is never removed by
|
|
34
|
+
// it. A finished run, and above all an auto RE-review on a PR that already holds another job (a
|
|
35
|
+
// reply in your thread), is new work of its own; folding it under that job hid every re-review on
|
|
36
|
+
// a busy PR. Each run is its own item (its own id and `finishedAt` clock), so a newer run shows
|
|
37
|
+
// even after an older one's card was dismissed.
|
|
38
|
+
//
|
|
33
39
|
// ⚠ IT IS A BOARD RULE, NOT A NOTIFICATION RULE. `getWorkspaceInsights` asks for it; the
|
|
34
40
|
// account-wide `GET /api/my-turn` does not, because the browser-notification watcher diffs item
|
|
35
41
|
// ids — a deduplicated list would flip the winner whenever the top job cleared and announce the
|
|
@@ -285,14 +291,6 @@ export function onePerPr(mt, order) {
|
|
|
285
291
|
ref: `${i.threadId}`,
|
|
286
292
|
}),
|
|
287
293
|
};
|
|
288
|
-
const claude = {
|
|
289
|
-
of: (i) => ({
|
|
290
|
-
prId: i.prId,
|
|
291
|
-
reason: 'claude_review',
|
|
292
|
-
clockMs: clockMsOf(i.finishedAt),
|
|
293
|
-
ref: `${i.reviewId}`,
|
|
294
|
-
}),
|
|
295
|
-
};
|
|
296
294
|
for (const i of mt.awaitingReview)
|
|
297
295
|
offer(S.awaitingReview.of(i));
|
|
298
296
|
for (const i of mt.mentions)
|
|
@@ -319,8 +317,6 @@ export function onePerPr(mt, order) {
|
|
|
319
317
|
offer(threadReplies.of(i));
|
|
320
318
|
for (const i of mt.ownThreads)
|
|
321
319
|
offer(ownThreads.of(i));
|
|
322
|
-
for (const i of mt.claudeReviewsToAction)
|
|
323
|
-
offer(claude.of(i));
|
|
324
320
|
return {
|
|
325
321
|
awaitingReview: mt.awaitingReview.filter((i) => winnerIs(S.awaitingReview.of(i))),
|
|
326
322
|
mentions: mt.mentions.filter((i) => winnerIs(S.mentions.of(i))),
|
|
@@ -335,7 +331,8 @@ export function onePerPr(mt, order) {
|
|
|
335
331
|
threadsAwaiting: mt.threadsAwaiting.filter((i) => winnerIs(threadsAwaiting.of(i))),
|
|
336
332
|
threadReplies: mt.threadReplies.filter((i) => winnerIs(threadReplies.of(i))),
|
|
337
333
|
ownThreads: mt.ownThreads.filter((i) => winnerIs(ownThreads.of(i))),
|
|
338
|
-
|
|
334
|
+
// Exempt (header, rule 2): every Claude review keeps its own card.
|
|
335
|
+
claudeReviewsToAction: mt.claudeReviewsToAction,
|
|
339
336
|
// Repo-grained: a branch is not a pull request, and never shares a key with one.
|
|
340
337
|
redTrunks: mt.redTrunks,
|
|
341
338
|
};
|