openvibers/openvibe.codes Commits
Proof follow-up: the reviewer spends only the job's token, and no test match is a skip (#171)
Changes
@@@ -17,8 +17,8 @@* The four steps, in order:** tests the repo's own runner, narrowed to the tests the change reaches. The whole suite runs only when-- * the change touches what every test shares and the owner's guard allows it (CODES_PROOF_FULL_SUITE=1):-- * a machine may not spend an hour of its owner's battery on every task.++ * the owner's guard allows it (CODES_PROOF_FULL_SUITE=1): a machine may not spend an hour of its++ * owner's battery on every task. A filter that matches no test is a skip, not a failure.* screenshots phone (390) and desktop (1440) of the pages the diff touches, because a page that overflows is a* bug the diff alone never shows.* diff the stats: what changed, how many lines, which files.@@@ -37,6 +37,9 @@ const PAGE_FILE = /^(?:public\/|server\/render\/|server\/http\/)|\.(?:html?|css)/** The files that make every test matter: the shared ones. A change here runs the whole suite (when allowed). */const SHARED_FILE = /^(?:package\.json|package-lock\.json|engine\/index\.js|server\/app\.js|server\/db\.js|server\/config\.js)$/;++/** The runners' own words for "the filter matched no test": not a failure, nothing ran. */++const NO_TEST_MATCH = /no test files? (?:matched|found)|no tests? (?:found|ran|to run)|running 0 tests/i;++/** The kinds of proof item, in the order they are collected. */const KINDS = ['tests', 'screenshots', 'diff', 'review'];@@@ -94,7 +97,8 @@ function nodeTestScript(script) {** The runner is the repo's: 'npm test' when package.json has it (or the file it runs, when that is `node <file>`),* then pytest, go and cargo. The suite is narrowed to the tests the change reaches; the whole suite runs only when-- * the change touches what every test shares and the owner's guard allows it (CODES_PROOF_FULL_SUITE=1).++ * the owner's guard allows it (CODES_PROOF_FULL_SUITE=1) — a change that touches what every test shares, or one no++ * test name reaches.** @param {object} what* @param {object} what.pkg the repo's package.json (or null)@@@ -117,10 +121,13 @@ function testPlan({ pkg = null, files = [], env = {} } = {}) {const base = direct ? { command: 'node', args: [direct] } : { command: 'npm', args: ['test'] };const narrowed = direct ? [direct, ...words] : ['test', '--', ...words];if (shared && allowFull) return { ...base, mode: 'full', reason: 'the change touches what every test shares, and the full suite is allowed' };-- if (!shared) {-- if (!words.length) return { ...base, mode: 'targeted', reason: 'no test name matches the changed files, so the suite runs as it is' };-- return { command: base.command, args: narrowed, mode: 'targeted', reason: 'only the tests the change reaches (' + words.join(', ') + ')' };++ if (!words.length) {++ // Nothing in the diff names a test, so there is nothing to narrow to. The whole suite is not run without++ // the owner's guard: a machine may not spend an hour of its owner's battery on every task.++ if (allowFull) return { ...base, mode: 'full', reason: 'no test name matches the changed files, and the full suite is allowed' };++ return { command: '', args: [], mode: 'none', reason: 'no test name matches the changed files, and the full suite is not allowed (CODES_PROOF_FULL_SUITE=1)' };}++ if (!shared) return { command: base.command, args: narrowed, mode: 'targeted', reason: 'only the tests the change reaches (' + words.join(', ') + ')' };return { command: base.command, args: narrowed, mode: 'targeted', reason: 'the change touches what every test shares, but the full suite is not allowed (CODES_PROOF_FULL_SUITE=1)' };}if (pkg && (pkg.go || (pkg.dependencies && pkg.dependencies.go))) return { command: 'go', args: ['test', './...'], mode: 'full', reason: "the repo's own Go suite" };@@@ -210,10 +217,14 @@ async function collectProof({ cwd, files = [], adapters = {}, author = null, emiconst result = await run([plan.command, ...plan.args], { cwd, timeoutMs: adapters.testTimeoutMs || 600000 });const output = String((result && result.output) || '');const ok = !!result && result.code === 0;++ // The runner's filter matched nothing: no test reaches the change by name, so nothing is proved and nothing++ // failed. Saying "the tests failed" would be a false alarm on a docs or asset change.++ const noMatch = !ok && NO_TEST_MATCH.test(output);await say({-- kind: 'tests', label: 'tests', ok, skipped: false, mode: plan.mode, reason: plan.reason,++ kind: 'tests', label: 'tests', ok: noMatch ? true : ok, skipped: noMatch, mode: plan.mode, reason: plan.reason,command: [plan.command, ...plan.args].join(' '),-- summary: ok ? plan.reason : (output.trim().split('\n').slice(-6).join(' ') || 'the tests failed (exit ' + result.code + ')'),++ summary: noMatch ? 'no test matches the changed files, so none ran'++ : (ok ? plan.reason : (output.trim().split('\n').slice(-6).join(' ') || 'the tests failed (exit ' + result.code + ')')),});}@@@ -253,10 +253,17 @@ function aiRoot(env = {}) {if (env.OPENVIBE_AI_URL) return env.OPENVIBE_AI_URL;if (env.OPENAI_BASE_URL) return env.OPENAI_BASE_URL;if (env.DEEPSEEK_BASE_URL) return env.DEEPSEEK_BASE_URL;++ if (env.OPENVIBE_CODES_BASE_URL) return env.OPENVIBE_CODES_BASE_URL;if (env.ANTHROPIC_BASE_URL) return String(env.ANTHROPIC_BASE_URL).replace(/\/+$/, '') + '/v1';return '';}++/** The token C3 put in the job's input, under the name the attempt's harness was given. */++function runTokenOf(jobEnv = {}) {++ return jobEnv.OPENVIBE_AI_TOKEN || jobEnv.OPENAI_API_KEY || jobEnv.ANTHROPIC_AUTH_TOKEN++ || jobEnv.DEEPSEEK_API_KEY || jobEnv.OPENVIBE_CODES_API_KEY || '';++}++/*** The second opinion: OpenVibe.AI, with the task's run token.*@@@ -267,9 +274,9 @@ function aiRoot(env = {}) {*/function reviewAdapter({ env = {}, jobEnv = {}, fetchImpl = globalThis.fetch, baseUrl, models = [] } = {}) {return async function review({ patch = '', summary = '', tests = null, model = null, family = null } = {}) {-- // The token the attempt's own harness carries: any of the names C3 gives, that harness's provider first.-- const runToken = jobEnv.OPENVIBE_AI_TOKEN || jobEnv.OPENAI_API_KEY || jobEnv.ANTHROPIC_AUTH_TOKEN || jobEnv.DEEPSEEK_API_KEY || '';-- const token = runToken || env.OPENVIBE_CODES_TASK_TOKEN || env.OPENVIBE_AI_TOKEN || '';++ // The token the attempt's own harness carries: only the job's own input, under the name C3 gave that harness.++ // The machine's own OPENVIBE_AI_TOKEN names its owner, not this task's person, so it is never spent here.++ const token = runTokenOf(jobEnv);if (!token) {const err = new Error('no task token is connected yet, so no model could review this change');err.code = 'review.no_token';@@@ -13,13 +13,14 @@ const { check, done } = require('./helpers/boot');(async () => {/** A fake machine: the repo's package.json, a passing test run, a clean page and a second model's opinion. */-- const machine = ({ pkg = { scripts: { test: 'node test/run.js' } }, files = [], testExit = 0, review = null, shots = [] } = {}) => {++ const machine = ({ pkg = { scripts: { test: 'node test/run.js' } }, files = [], testExit = 0, testOutput = null, review = null, shots = [] } = {}) => {const calls = [];++ const output = testOutput == null ? (testExit === 0 ? '12/12 test files passed' : 'FAIL test/foo.test.js\nAssertionError') : testOutput;const run = async (argv, options) => {calls.push(argv);if (argv[0] === 'node' && argv[1] === '-e') return { code: 0, output: JSON.stringify(pkg) };-- if (argv[0] === 'node' && argv[1] === 'test/run.js') return { code: testExit, output: testExit === 0 ? '12/12 test files passed' : 'FAIL test/foo.test.js\nAssertionError' };-- if (argv[0] === 'npm') return { code: testExit, output: testExit === 0 ? '12/12 test files passed' : 'FAIL test/foo.test.js\nAssertionError' };++ if (argv[0] === 'node' && argv[1] === 'test/run.js') return { code: testExit, output };++ if (argv[0] === 'npm') return { code: testExit, output };return { code: 0, output: '' };};return {@@@ -69,6 +70,25 @@ const { check, done } = require('./helpers/boot');assert.deepStrictEqual(allowed.args, ['test/run.js']);});++ await check('a change no test name reaches does not run the whole suite unguarded', async () => {++ const plan = testPlan({ pkg: { scripts: { test: 'node test/run.js' } }, files: ['engine/index.js'], env: {} });++ assert.strictEqual(plan.mode, 'none');++ assert.ok(/full suite is not allowed/.test(plan.reason), plan.reason);++ const allowed = testPlan({ pkg: { scripts: { test: 'node test/run.js' } }, files: ['engine/index.js'], env: { CODES_PROOF_FULL_SUITE: '1' } });++ assert.strictEqual(allowed.mode, 'full');++ });++++ await check('a narrowed run that matches no test is a skip, not a failed proof', async () => {++ const m = machine({ files: ['README.md'], testExit: 1, testOutput: 'no test files matched' });++ const proof = await collectProof({ cwd: '/repo', adapters: m.adapters });++ const test = proof.items.find((i) => i.kind === 'tests');++ assert.strictEqual(test.ok, true);++ assert.strictEqual(test.skipped, true);++ assert.ok(/no test matches/.test(test.summary), test.summary);++ assert.strictEqual(proof.outcome.state, 'done');++ assert.ok(/tests was skipped/.test(proof.outcome.note), proof.outcome.note);++ });++await check('a repo with no test command of its own says so instead of failing', async () => {const m = machine({ pkg: { scripts: {} } });const proof = await collectProof({ cwd: '/repo', adapters: m.adapters });@@@ -171,7 +171,7 @@ const OK = async () => ({ status: 200, ok: true, json: async () => ({ choices: [await check('the review goes to OpenVibe.AI as chat completions, with the token', async () => {const calls = [];const fetchImpl = async (url, options) => { calls.push({ url, options }); return OK(); };-- const review = reviewAdapter({ env: { OPENVIBE_CODES_TASK_TOKEN: 'test-token' }, fetchImpl, models: ['deepseek-chat'] });++ const review = reviewAdapter({ jobEnv: { OPENVIBE_AI_TOKEN: 'test-token' }, fetchImpl, models: ['deepseek-chat'] });const verdict = await review({ patch: '+x', summary: 'a change', family: 'anthropic', model: 'deepseek-chat' });assert.strictEqual(verdict.verdict, 'approved');assert.strictEqual(verdict.family, 'deepseek');@@@ -194,10 +194,23 @@ const OK = async () => ({ status: 200, ok: true, json: async () => ({ choices: [});await check('a personal provider key in the environment is not spent as a run token', async () => {-- const review = reviewAdapter({ env: { OPENAI_API_KEY: 'a-personal-key' }, fetchImpl: OK, models: ['deepseek-chat'] });++ const review = reviewAdapter({ env: { OPENAI_API_KEY: 'a-personal-key', OPENVIBE_AI_TOKEN: 'the-owner' }, fetchImpl: OK, models: ['deepseek-chat'] });await assert.rejects(() => review({ patch: '+x', family: 'anthropic' }), (e) => e.code === 'review.no_token');});++ await check('an OpenAI-compatible harness\'s run token and endpoint are the ones the job carries', async () => {++ const calls = [];++ const fetchImpl = async (url, options) => { calls.push({ url, options }); return OK(); };++ const review = reviewAdapter({++ jobEnv: { OPENVIBE_CODES_BASE_URL: 'https://ai.openvibe.services/v1', OPENVIBE_CODES_API_KEY: 'run-token' },++ fetchImpl,++ });++ const verdict = await review({ patch: '+x', family: 'anthropic', model: 'deepseek-chat' });++ assert.strictEqual(verdict.verdict, 'approved');++ assert.strictEqual(calls[0].url, 'https://ai.openvibe.services/v1/chat/completions');++ assert.strictEqual(calls[0].options.headers.authorization, 'Bearer run-token');++ });++await check('a provider that speaks the messages dialect is tried when chat completions is not there', async () => {const calls = [];const fetchImpl = async (url) => {@@@ -205,7 +218,7 @@ const OK = async () => ({ status: 200, ok: true, json: async () => ({ choices: [if (url.endsWith('/chat/completions')) return { status: 404, ok: false, json: async () => ({}) };return { status: 200, ok: true, json: async () => ({ content: [{ type: 'text', text: 'changes requested: the parser still throws' }] }) };};-- const review = reviewAdapter({ env: { OPENVIBE_AI_TOKEN: 'test-token' }, fetchImpl });++ const review = reviewAdapter({ jobEnv: { OPENVIBE_AI_TOKEN: 'test-token' }, fetchImpl });const verdict = await review({ patch: '+x', family: 'anthropic', model: 'claude-sonnet-4' });assert.ok(calls[1].endsWith('/messages'), calls[1]);assert.strictEqual(verdict.verdict, 'changes_requested');
@@ -17,8 +17,8 @@
@@ -17,8 +17,8 @@
* The four steps, in order:
* The four steps, in order:
*
*
* tests the repo's own runner, narrowed to the tests the change reaches. The whole suite runs only when
* tests the repo's own runner, narrowed to the tests the change reaches. The whole suite runs only when
- * the change touches what every test shares and the owner's guard allows it (CODES_PROOF_FULL_SUITE=1):
- * a machine may not spend an hour of its owner's battery on every task.
+ * the owner's guard allows it (CODES_PROOF_FULL_SUITE=1): a machine may not spend an hour of its
+ * owner's battery on every task. A filter that matches no test is a skip, not a failure.
* screenshots phone (390) and desktop (1440) of the pages the diff touches, because a page that overflows is a
* screenshots phone (390) and desktop (1440) of the pages the diff touches, because a page that overflows is a
* bug the diff alone never shows.
* bug the diff alone never shows.
* diff the stats: what changed, how many lines, which files.
* diff the stats: what changed, how many lines, which files.
@@ -37,6 +37,9 @@ const PAGE_FILE = /^(?:public\/|server\/render\/|server\/http\/)|\.(?:html?|css)
@@ -37,6 +37,9 @@ const PAGE_FILE = /^(?:public\/|server\/render\/|server\/http\/)|\.(?:html?|css)
/** The files that make every test matter: the shared ones. A change here runs the whole suite (when allowed). */
/** The files that make every test matter: the shared ones. A change here runs the whole suite (when allowed). */
const SHARED_FILE = /^(?:package\.json|package-lock\.json|engine\/index\.js|server\/app\.js|server\/db\.js|server\/config\.js)$/;
const SHARED_FILE = /^(?:package\.json|package-lock\.json|engine\/index\.js|server\/app\.js|server\/db\.js|server\/config\.js)$/;
+/** The runners' own words for "the filter matched no test": not a failure, nothing ran. */
+const NO_TEST_MATCH = /no test files? (?:matched|found)|no tests? (?:found|ran|to run)|running 0 tests/i;
+
/** The kinds of proof item, in the order they are collected. */
/** The kinds of proof item, in the order they are collected. */
const KINDS = ['tests', 'screenshots', 'diff', 'review'];
const KINDS = ['tests', 'screenshots', 'diff', 'review'];
@@ -94,7 +97,8 @@ function nodeTestScript(script) {
@@ -94,7 +97,8 @@ function nodeTestScript(script) {
*
*
* The runner is the repo's: 'npm test' when package.json has it (or the file it runs, when that is `node <file>`),
* The runner is the repo's: 'npm test' when package.json has it (or the file it runs, when that is `node <file>`),
* then pytest, go and cargo. The suite is narrowed to the tests the change reaches; the whole suite runs only when
* then pytest, go and cargo. The suite is narrowed to the tests the change reaches; the whole suite runs only when
- * the change touches what every test shares and the owner's guard allows it (CODES_PROOF_FULL_SUITE=1).
+ * the owner's guard allows it (CODES_PROOF_FULL_SUITE=1) — a change that touches what every test shares, or one no
+ * test name reaches.
*
*
* @param {object} what
* @param {object} what
* @param {object} what.pkg the repo's package.json (or null)
* @param {object} what.pkg the repo's package.json (or null)
@@ -117,10 +121,13 @@ function testPlan({ pkg = null, files = [], env = {} } = {}) {
@@ -117,10 +121,13 @@ function testPlan({ pkg = null, files = [], env = {} } = {}) {
const base = direct ? { command: 'node', args: [direct] } : { command: 'npm', args: ['test'] };
const base = direct ? { command: 'node', args: [direct] } : { command: 'npm', args: ['test'] };
const narrowed = direct ? [direct, ...words] : ['test', '--', ...words];
const narrowed = direct ? [direct, ...words] : ['test', '--', ...words];
if (shared && allowFull) return { ...base, mode: 'full', reason: 'the change touches what every test shares, and the full suite is allowed' };
if (shared && allowFull) return { ...base, mode: 'full', reason: 'the change touches what every test shares, and the full suite is allowed' };
- if (!shared) {
- if (!words.length) return { ...base, mode: 'targeted', reason: 'no test name matches the changed files, so the suite runs as it is' };
- return { command: base.command, args: narrowed, mode: 'targeted', reason: 'only the tests the change reaches (' + words.join(', ') + ')' };
+ if (!words.length) {
+ // Nothing in the diff names a test, so there is nothing to narrow to. The whole suite is not run without
+ // the owner's guard: a machine may not spend an hour of its owner's battery on every task.
+ if (allowFull) return { ...base, mode: 'full', reason: 'no test name matches the changed files, and the full suite is allowed' };
+ return { command: '', args: [], mode: 'none', reason: 'no test name matches the changed files, and the full suite is not allowed (CODES_PROOF_FULL_SUITE=1)' };
}
}
+ if (!shared) return { command: base.command, args: narrowed, mode: 'targeted', reason: 'only the tests the change reaches (' + words.join(', ') + ')' };
return { command: base.command, args: narrowed, mode: 'targeted', reason: 'the change touches what every test shares, but the full suite is not allowed (CODES_PROOF_FULL_SUITE=1)' };
return { command: base.command, args: narrowed, mode: 'targeted', reason: 'the change touches what every test shares, but the full suite is not allowed (CODES_PROOF_FULL_SUITE=1)' };
}
}
if (pkg && (pkg.go || (pkg.dependencies && pkg.dependencies.go))) return { command: 'go', args: ['test', './...'], mode: 'full', reason: "the repo's own Go suite" };
if (pkg && (pkg.go || (pkg.dependencies && pkg.dependencies.go))) return { command: 'go', args: ['test', './...'], mode: 'full', reason: "the repo's own Go suite" };
@@ -210,10 +217,14 @@ async function collectProof({ cwd, files = [], adapters = {}, author = null, emi
@@ -210,10 +217,14 @@ async function collectProof({ cwd, files = [], adapters = {}, author = null, emi
const result = await run([plan.command, ...plan.args], { cwd, timeoutMs: adapters.testTimeoutMs || 600000 });
const result = await run([plan.command, ...plan.args], { cwd, timeoutMs: adapters.testTimeoutMs || 600000 });
const output = String((result && result.output) || '');
const output = String((result && result.output) || '');
const ok = !!result && result.code === 0;
const ok = !!result && result.code === 0;
+ // The runner's filter matched nothing: no test reaches the change by name, so nothing is proved and nothing
+ // failed. Saying "the tests failed" would be a false alarm on a docs or asset change.
+ const noMatch = !ok && NO_TEST_MATCH.test(output);
await say({
await say({
- kind: 'tests', label: 'tests', ok, skipped: false, mode: plan.mode, reason: plan.reason,
+ kind: 'tests', label: 'tests', ok: noMatch ? true : ok, skipped: noMatch, mode: plan.mode, reason: plan.reason,
command: [plan.command, ...plan.args].join(' '),
command: [plan.command, ...plan.args].join(' '),
- summary: ok ? plan.reason : (output.trim().split('\n').slice(-6).join(' ') || 'the tests failed (exit ' + result.code + ')'),
+ summary: noMatch ? 'no test matches the changed files, so none ran'
+ : (ok ? plan.reason : (output.trim().split('\n').slice(-6).join(' ') || 'the tests failed (exit ' + result.code + ')')),
});
});
}
}
@@ -253,10 +253,17 @@ function aiRoot(env = {}) {
@@ -253,10 +253,17 @@ function aiRoot(env = {}) {
if (env.OPENVIBE_AI_URL) return env.OPENVIBE_AI_URL;
if (env.OPENVIBE_AI_URL) return env.OPENVIBE_AI_URL;
if (env.OPENAI_BASE_URL) return env.OPENAI_BASE_URL;
if (env.OPENAI_BASE_URL) return env.OPENAI_BASE_URL;
if (env.DEEPSEEK_BASE_URL) return env.DEEPSEEK_BASE_URL;
if (env.DEEPSEEK_BASE_URL) return env.DEEPSEEK_BASE_URL;
+ if (env.OPENVIBE_CODES_BASE_URL) return env.OPENVIBE_CODES_BASE_URL;
if (env.ANTHROPIC_BASE_URL) return String(env.ANTHROPIC_BASE_URL).replace(/\/+$/, '') + '/v1';
if (env.ANTHROPIC_BASE_URL) return String(env.ANTHROPIC_BASE_URL).replace(/\/+$/, '') + '/v1';
return '';
return '';
}
}
+/** The token C3 put in the job's input, under the name the attempt's harness was given. */
+function runTokenOf(jobEnv = {}) {
+ return jobEnv.OPENVIBE_AI_TOKEN || jobEnv.OPENAI_API_KEY || jobEnv.ANTHROPIC_AUTH_TOKEN
+ || jobEnv.DEEPSEEK_API_KEY || jobEnv.OPENVIBE_CODES_API_KEY || '';
+}
+
/**
/**
* The second opinion: OpenVibe.AI, with the task's run token.
* The second opinion: OpenVibe.AI, with the task's run token.
*
*
@@ -267,9 +274,9 @@ function aiRoot(env = {}) {
@@ -267,9 +274,9 @@ function aiRoot(env = {}) {
*/
*/
function reviewAdapter({ env = {}, jobEnv = {}, fetchImpl = globalThis.fetch, baseUrl, models = [] } = {}) {
function reviewAdapter({ env = {}, jobEnv = {}, fetchImpl = globalThis.fetch, baseUrl, models = [] } = {}) {
return async function review({ patch = '', summary = '', tests = null, model = null, family = null } = {}) {
return async function review({ patch = '', summary = '', tests = null, model = null, family = null } = {}) {
- // The token the attempt's own harness carries: any of the names C3 gives, that harness's provider first.
- const runToken = jobEnv.OPENVIBE_AI_TOKEN || jobEnv.OPENAI_API_KEY || jobEnv.ANTHROPIC_AUTH_TOKEN || jobEnv.DEEPSEEK_API_KEY || '';
- const token = runToken || env.OPENVIBE_CODES_TASK_TOKEN || env.OPENVIBE_AI_TOKEN || '';
+ // The token the attempt's own harness carries: only the job's own input, under the name C3 gave that harness.
+ // The machine's own OPENVIBE_AI_TOKEN names its owner, not this task's person, so it is never spent here.
+ const token = runTokenOf(jobEnv);
if (!token) {
if (!token) {
const err = new Error('no task token is connected yet, so no model could review this change');
const err = new Error('no task token is connected yet, so no model could review this change');
err.code = 'review.no_token';
err.code = 'review.no_token';
@@ -13,13 +13,14 @@ const { check, done } = require('./helpers/boot');
@@ -13,13 +13,14 @@ const { check, done } = require('./helpers/boot');
(async () => {
(async () => {
/** A fake machine: the repo's package.json, a passing test run, a clean page and a second model's opinion. */
/** A fake machine: the repo's package.json, a passing test run, a clean page and a second model's opinion. */
- const machine = ({ pkg = { scripts: { test: 'node test/run.js' } }, files = [], testExit = 0, review = null, shots = [] } = {}) => {
+ const machine = ({ pkg = { scripts: { test: 'node test/run.js' } }, files = [], testExit = 0, testOutput = null, review = null, shots = [] } = {}) => {
const calls = [];
const calls = [];
+ const output = testOutput == null ? (testExit === 0 ? '12/12 test files passed' : 'FAIL test/foo.test.js\nAssertionError') : testOutput;
const run = async (argv, options) => {
const run = async (argv, options) => {
calls.push(argv);
calls.push(argv);
if (argv[0] === 'node' && argv[1] === '-e') return { code: 0, output: JSON.stringify(pkg) };
if (argv[0] === 'node' && argv[1] === '-e') return { code: 0, output: JSON.stringify(pkg) };
- if (argv[0] === 'node' && argv[1] === 'test/run.js') return { code: testExit, output: testExit === 0 ? '12/12 test files passed' : 'FAIL test/foo.test.js\nAssertionError' };
- if (argv[0] === 'npm') return { code: testExit, output: testExit === 0 ? '12/12 test files passed' : 'FAIL test/foo.test.js\nAssertionError' };
+ if (argv[0] === 'node' && argv[1] === 'test/run.js') return { code: testExit, output };
+ if (argv[0] === 'npm') return { code: testExit, output };
return { code: 0, output: '' };
return { code: 0, output: '' };
};
};
return {
return {
@@ -69,6 +70,25 @@ const { check, done } = require('./helpers/boot');
@@ -69,6 +70,25 @@ const { check, done } = require('./helpers/boot');
assert.deepStrictEqual(allowed.args, ['test/run.js']);
assert.deepStrictEqual(allowed.args, ['test/run.js']);
});
});
+ await check('a change no test name reaches does not run the whole suite unguarded', async () => {
+ const plan = testPlan({ pkg: { scripts: { test: 'node test/run.js' } }, files: ['engine/index.js'], env: {} });
+ assert.strictEqual(plan.mode, 'none');
+ assert.ok(/full suite is not allowed/.test(plan.reason), plan.reason);
+ const allowed = testPlan({ pkg: { scripts: { test: 'node test/run.js' } }, files: ['engine/index.js'], env: { CODES_PROOF_FULL_SUITE: '1' } });
+ assert.strictEqual(allowed.mode, 'full');
+ });
+
+ await check('a narrowed run that matches no test is a skip, not a failed proof', async () => {
+ const m = machine({ files: ['README.md'], testExit: 1, testOutput: 'no test files matched' });
+ const proof = await collectProof({ cwd: '/repo', adapters: m.adapters });
+ const test = proof.items.find((i) => i.kind === 'tests');
+ assert.strictEqual(test.ok, true);
+ assert.strictEqual(test.skipped, true);
+ assert.ok(/no test matches/.test(test.summary), test.summary);
+ assert.strictEqual(proof.outcome.state, 'done');
+ assert.ok(/tests was skipped/.test(proof.outcome.note), proof.outcome.note);
+ });
+
await check('a repo with no test command of its own says so instead of failing', async () => {
await check('a repo with no test command of its own says so instead of failing', async () => {
const m = machine({ pkg: { scripts: {} } });
const m = machine({ pkg: { scripts: {} } });
const proof = await collectProof({ cwd: '/repo', adapters: m.adapters });
const proof = await collectProof({ cwd: '/repo', adapters: m.adapters });
@@ -171,7 +171,7 @@ const OK = async () => ({ status: 200, ok: true, json: async () => ({ choices: [
@@ -171,7 +171,7 @@ const OK = async () => ({ status: 200, ok: true, json: async () => ({ choices: [
await check('the review goes to OpenVibe.AI as chat completions, with the token', async () => {
await check('the review goes to OpenVibe.AI as chat completions, with the token', async () => {
const calls = [];
const calls = [];
const fetchImpl = async (url, options) => { calls.push({ url, options }); return OK(); };
const fetchImpl = async (url, options) => { calls.push({ url, options }); return OK(); };
- const review = reviewAdapter({ env: { OPENVIBE_CODES_TASK_TOKEN: 'test-token' }, fetchImpl, models: ['deepseek-chat'] });
+ const review = reviewAdapter({ jobEnv: { OPENVIBE_AI_TOKEN: 'test-token' }, fetchImpl, models: ['deepseek-chat'] });
const verdict = await review({ patch: '+x', summary: 'a change', family: 'anthropic', model: 'deepseek-chat' });
const verdict = await review({ patch: '+x', summary: 'a change', family: 'anthropic', model: 'deepseek-chat' });
assert.strictEqual(verdict.verdict, 'approved');
assert.strictEqual(verdict.verdict, 'approved');
assert.strictEqual(verdict.family, 'deepseek');
assert.strictEqual(verdict.family, 'deepseek');
@@ -194,10 +194,23 @@ const OK = async () => ({ status: 200, ok: true, json: async () => ({ choices: [
@@ -194,10 +194,23 @@ const OK = async () => ({ status: 200, ok: true, json: async () => ({ choices: [
});
});
await check('a personal provider key in the environment is not spent as a run token', async () => {
await check('a personal provider key in the environment is not spent as a run token', async () => {
- const review = reviewAdapter({ env: { OPENAI_API_KEY: 'a-personal-key' }, fetchImpl: OK, models: ['deepseek-chat'] });
+ const review = reviewAdapter({ env: { OPENAI_API_KEY: 'a-personal-key', OPENVIBE_AI_TOKEN: 'the-owner' }, fetchImpl: OK, models: ['deepseek-chat'] });
await assert.rejects(() => review({ patch: '+x', family: 'anthropic' }), (e) => e.code === 'review.no_token');
await assert.rejects(() => review({ patch: '+x', family: 'anthropic' }), (e) => e.code === 'review.no_token');
});
});
+ await check('an OpenAI-compatible harness\'s run token and endpoint are the ones the job carries', async () => {
+ const calls = [];
+ const fetchImpl = async (url, options) => { calls.push({ url, options }); return OK(); };
+ const review = reviewAdapter({
+ jobEnv: { OPENVIBE_CODES_BASE_URL: 'https://ai.openvibe.services/v1', OPENVIBE_CODES_API_KEY: 'run-token' },
+ fetchImpl,
+ });
+ const verdict = await review({ patch: '+x', family: 'anthropic', model: 'deepseek-chat' });
+ assert.strictEqual(verdict.verdict, 'approved');
+ assert.strictEqual(calls[0].url, 'https://ai.openvibe.services/v1/chat/completions');
+ assert.strictEqual(calls[0].options.headers.authorization, 'Bearer run-token');
+ });
+
await check('a provider that speaks the messages dialect is tried when chat completions is not there', async () => {
await check('a provider that speaks the messages dialect is tried when chat completions is not there', async () => {
const calls = [];
const calls = [];
const fetchImpl = async (url) => {
const fetchImpl = async (url) => {
@@ -205,7 +218,7 @@ const OK = async () => ({ status: 200, ok: true, json: async () => ({ choices: [
@@ -205,7 +218,7 @@ const OK = async () => ({ status: 200, ok: true, json: async () => ({ choices: [
if (url.endsWith('/chat/completions')) return { status: 404, ok: false, json: async () => ({}) };
if (url.endsWith('/chat/completions')) return { status: 404, ok: false, json: async () => ({}) };
return { status: 200, ok: true, json: async () => ({ content: [{ type: 'text', text: 'changes requested: the parser still throws' }] }) };
return { status: 200, ok: true, json: async () => ({ content: [{ type: 'text', text: 'changes requested: the parser still throws' }] }) };
};
};
- const review = reviewAdapter({ env: { OPENVIBE_AI_TOKEN: 'test-token' }, fetchImpl });
+ const review = reviewAdapter({ jobEnv: { OPENVIBE_AI_TOKEN: 'test-token' }, fetchImpl });
const verdict = await review({ patch: '+x', family: 'anthropic', model: 'claude-sonnet-4' });
const verdict = await review({ patch: '+x', family: 'anthropic', model: 'claude-sonnet-4' });
assert.ok(calls[1].endsWith('/messages'), calls[1]);
assert.ok(calls[1].endsWith('/messages'), calls[1]);
assert.strictEqual(verdict.verdict, 'changes_requested');
assert.strictEqual(verdict.verdict, 'changes_requested');