openvibers/openvibe.codes Commits

Anchor incremental re-reviews to the reviewed head (#387)

OP OpenVibers committed 2 days ago · 7374d69e78 · 2 files, +16 −3

Changes

ddiff --git a/engine/review.js b/engine/review.js
iindex ee19451..504a8d2 100644
---- a/engine/review.js
++++ b/engine/review.js
@@@ -96,7 +96,7 @@ function pickReviewer({ models = [], author = null } = {}) {
* Quiet by default — the rules are the point: only what this diff introduces, at most three findings, no style, no
* speculation, and an approval is an empty list rather than a "no issues" note.
*/
--function reviewPrompt({ patch = '', summary = '', tests = null, prior = [] } = {}) {
++function reviewPrompt({ patch = '', summary = '', tests = null, prior = [], reviewedHead = null, head = null } = {}) {
const before = priorBrief(prior);
return [
'You are reviewing a change to a repository. Answer only with JSON:',
@@@ -108,6 +108,7 @@ function reviewPrompt({ patch = '', summary = '', tests = null, prior = [] } = {
'is wrong, "approved" with an empty findings list; never write a "no issues" finding or a comment.',
'',
before ? 'A previous review of an earlier revision raised the findings below. Treat them as the ledger: say in your\nsummary which are addressed, and report one again only when it still stands in this revision; then add only new\nissues this revision introduced. The ids stay stable across re-wrapping.\n' + before : '',
++ reviewedHead && head ? `This is an incremental re-review of ${reviewedHead}..${head}. The supplied diff is anchored on the last reviewed head; review only the changes in that range and reconcile the earlier findings above.` : '',
summary ? 'What the change was asked to do:\n' + String(summary).slice(0, 4000) : '',
tests ? 'The tests the author says pass:\n' + String(tests).slice(0, 2000) : '',
'',
@@@ -154,6 +155,8 @@ function readVerdict(text) {
* @param {string} what.model the reviewer's model
* @param {string} what.family the reviewer's family
* @param {string} [what.patch] the diff
++ * @param {string} [what.reviewedHead] last commit already reviewed; when paired with head, patch is that incremental range
++ * @param {string} [what.head] current commit being reviewed
* @param {string} [what.summary] what the change was for
* @param {function} [what.fetchImpl] fetch, for a test
* @returns {Promise<{ family: string, model: string, verdict: string, summary: string, resolved_model?: string, disclosure?: object }>} the verdict
@@@ -167,12 +170,14 @@ async function reviewWithAi({
summary = '',
tests = null,
prior = [],
++ reviewedHead = null,
++ head = null,
fetchImpl = globalThis.fetch,
} = {}) {
if (!token) throw new Error('the review needs the task\'s token');
if (!model) throw new Error('the review needs a model');
const root = String(baseUrl).replace(/\/+$/, '');
-- const prompt = reviewPrompt({ patch, summary, tests, prior });
++ const prompt = reviewPrompt({ patch, summary, tests, prior, reviewedHead, head });
const headers = { 'content-type': 'application/json', authorization: `Bearer ${token}` };
let res = await fetchImpl(`${root}/chat/completions`, {
method: 'POST', headers, body: JSON.stringify({ model, messages: [{ role: 'user', content: prompt }] }),
@@@ -207,6 +212,7 @@ async function reviewWithAi({
family: familyOf(resolved) || family || familyOf(model) || null,
model: resolved,
requested_model: model,
++ ...(reviewedHead && head ? { reviewed_head: reviewedHead, head } : {}),
...(x && x.resolved_model ? { resolved_model: String(x.resolved_model) } : {}),
...(x ? { disclosure: x } : {}),
...(x && Number.isFinite(+x.cost_usd) ? { cost_usd: +x.cost_usd } : {}),
ddiff --git a/test/engine-review.test.js b/test/engine-review.test.js
iindex 1a6fde0..ba57661 100644
---- a/test/engine-review.test.js
++++ b/test/engine-review.test.js
@@@ -49,6 +49,12 @@ const OK = async () => ({ status: 200, ok: true, json: async () => ({ choices: [
assert.ok(/approved/.test(prompt) && /changes_requested/.test(prompt), 'the verdict is asked for');
});
++ await check('an incremental re-review names the reviewed and current heads', () => {
++ const prompt = reviewPrompt({ patch: '+fix', reviewedHead: 'abc123', head: 'def456' });
++ assert.ok(prompt.includes('incremental re-review of abc123..def456'));
++ assert.ok(!reviewPrompt({ patch: '+fix', reviewedHead: 'abc123' }).includes('incremental re-review'));
++ });
++
await check('quiet by default: only this diff, at most three, no style, and a prior ledger on a re-review', () => {
const prompt = reviewPrompt({ patch: '+x', summary: 'add x' });
assert.match(prompt, /only issues this change introduces/i);
@@@ -89,7 +95,7 @@ const OK = async () => ({ status: 200, ok: true, json: async () => ({ choices: [
const fetchImpl = async (url, options) => { calls.push({ url, options }); return OK(); };
const verdict = await reviewWithAi({
baseUrl: 'https://ai.openvibe.services/v1', token: 'test-token', model: 'deepseek-chat',
-- patch: '+x', summary: 'a change', fetchImpl,
++ patch: '+x', summary: 'a change', reviewedHead: 'abc123', head: 'def456', fetchImpl,
});
assert.strictEqual(calls.length, 1);
assert.strictEqual(calls[0].url, 'https://ai.openvibe.services/v1/chat/completions');
@@@ -97,6 +103,7 @@ const OK = async () => ({ status: 200, ok: true, json: async () => ({ choices: [
assert.strictEqual(JSON.parse(calls[0].options.body).model, 'deepseek-chat');
assert.strictEqual(verdict.verdict, 'approved');
assert.strictEqual(verdict.family, 'deepseek');
++ assert.deepStrictEqual([verdict.reviewed_head, verdict.head], ['abc123', 'def456']);
assert.deepEqual(verdict.findings, [], 'an approval is an empty findings list, not a "no issues" note');
});