openvibers/openvibe.codes Commits
Anchor incremental re-reviews to the reviewed head (#387)
Changes
@@@ -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 } : {}),@@@ -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');});
@@ -96,7 +96,7 @@ function pickReviewer({ models = [], author = null } = {}) {
@@ -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
* 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.
* 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);
const before = priorBrief(prior);
return [
return [
'You are reviewing a change to a repository. Answer only with JSON:',
'You are reviewing a change to a repository. Answer only with JSON:',
@@ -108,6 +108,7 @@ function reviewPrompt({ patch = '', summary = '', tests = null, prior = [] } = {
@@ -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.',
'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 : '',
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) : '',
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) : '',
tests ? 'The tests the author says pass:\n' + String(tests).slice(0, 2000) : '',
'',
'',
@@ -154,6 +155,8 @@ function readVerdict(text) {
@@ -154,6 +155,8 @@ function readVerdict(text) {
* @param {string} what.model the reviewer's model
* @param {string} what.model the reviewer's model
* @param {string} what.family the reviewer's family
* @param {string} what.family the reviewer's family
* @param {string} [what.patch] the diff
* @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 {string} [what.summary] what the change was for
* @param {function} [what.fetchImpl] fetch, for a test
* @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
* @returns {Promise<{ family: string, model: string, verdict: string, summary: string, resolved_model?: string, disclosure?: object }>} the verdict
@@ -167,12 +170,14 @@ async function reviewWithAi({
@@ -167,12 +170,14 @@ async function reviewWithAi({
summary = '',
summary = '',
tests = null,
tests = null,
prior = [],
prior = [],
+ reviewedHead = null,
+ head = null,
fetchImpl = globalThis.fetch,
fetchImpl = globalThis.fetch,
} = {}) {
} = {}) {
if (!token) throw new Error('the review needs the task\'s token');
if (!token) throw new Error('the review needs the task\'s token');
if (!model) throw new Error('the review needs a model');
if (!model) throw new Error('the review needs a model');
const root = String(baseUrl).replace(/\/+$/, '');
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}` };
const headers = { 'content-type': 'application/json', authorization: `Bearer ${token}` };
let res = await fetchImpl(`${root}/chat/completions`, {
let res = await fetchImpl(`${root}/chat/completions`, {
method: 'POST', headers, body: JSON.stringify({ model, messages: [{ role: 'user', content: prompt }] }),
method: 'POST', headers, body: JSON.stringify({ model, messages: [{ role: 'user', content: prompt }] }),
@@ -207,6 +212,7 @@ async function reviewWithAi({
@@ -207,6 +212,7 @@ async function reviewWithAi({
family: familyOf(resolved) || family || familyOf(model) || null,
family: familyOf(resolved) || family || familyOf(model) || null,
model: resolved,
model: resolved,
requested_model: model,
requested_model: model,
+ ...(reviewedHead && head ? { reviewed_head: reviewedHead, head } : {}),
...(x && x.resolved_model ? { resolved_model: String(x.resolved_model) } : {}),
...(x && x.resolved_model ? { resolved_model: String(x.resolved_model) } : {}),
...(x ? { disclosure: x } : {}),
...(x ? { disclosure: x } : {}),
...(x && Number.isFinite(+x.cost_usd) ? { cost_usd: +x.cost_usd } : {}),
...(x && Number.isFinite(+x.cost_usd) ? { cost_usd: +x.cost_usd } : {}),
@@ -49,6 +49,12 @@ const OK = async () => ({ status: 200, ok: true, json: async () => ({ choices: [
@@ -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');
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', () => {
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' });
const prompt = reviewPrompt({ patch: '+x', summary: 'add x' });
assert.match(prompt, /only issues this change introduces/i);
assert.match(prompt, /only issues this change introduces/i);
@@ -89,7 +95,7 @@ const OK = async () => ({ status: 200, ok: true, json: async () => ({ choices: [
@@ -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 fetchImpl = async (url, options) => { calls.push({ url, options }); return OK(); };
const verdict = await reviewWithAi({
const verdict = await reviewWithAi({
baseUrl: 'https://ai.openvibe.services/v1', token: 'test-token', model: 'deepseek-chat',
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.length, 1);
assert.strictEqual(calls[0].url, 'https://ai.openvibe.services/v1/chat/completions');
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: [
@@ -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(JSON.parse(calls[0].options.body).model, 'deepseek-chat');
assert.strictEqual(verdict.verdict, 'approved');
assert.strictEqual(verdict.verdict, 'approved');
assert.strictEqual(verdict.family, 'deepseek');
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');
assert.deepEqual(verdict.findings, [], 'an approval is an empty findings list, not a "no issues" note');
});
});