dual-brain 3.7.0 → 3.7.1
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/hooks/cost-logger.mjs
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* Output contract: must print "{}" to stdout and exit 0 within ~100 ms.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import { createHash } from "crypto";
|
|
11
12
|
import { appendFileSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
12
13
|
import { dirname, join } from "path";
|
|
13
14
|
import { fileURLToPath } from "url";
|
|
@@ -25,8 +26,8 @@ mkdirSync(__dirname, { recursive: true });
|
|
|
25
26
|
function loadActiveProfile() {
|
|
26
27
|
try {
|
|
27
28
|
const data = JSON.parse(readFileSync(PROFILE_FILE, 'utf8'));
|
|
28
|
-
return data.active || '
|
|
29
|
-
} catch { return '
|
|
29
|
+
return data.active || 'auto';
|
|
30
|
+
} catch { return 'auto'; }
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
const SESSION_ID = process.env.CLAUDE_SESSION_ID || process.ppid?.toString() || null;
|
|
@@ -261,6 +262,15 @@ async function main() {
|
|
|
261
262
|
updateSummary(entryObj);
|
|
262
263
|
} catch {}
|
|
263
264
|
|
|
265
|
+
// Record failures for adaptive routing (failure-loop detection)
|
|
266
|
+
if (status === 'error' && toolName === 'Agent') {
|
|
267
|
+
try {
|
|
268
|
+
const { recordFailure } = await import('./failure-detector.mjs');
|
|
269
|
+
const promptHash = createHash('md5').update(JSON.stringify(toolInput)).digest('hex').slice(0, 12);
|
|
270
|
+
recordFailure(promptHash, tier, payload?.error || 'agent_error');
|
|
271
|
+
} catch {}
|
|
272
|
+
}
|
|
273
|
+
|
|
264
274
|
const budgetMsg = await checkBudget();
|
|
265
275
|
|
|
266
276
|
// PostToolUse hooks must emit a JSON object to stdout
|
|
@@ -135,7 +135,7 @@ function hasIssues(text) {
|
|
|
135
135
|
if (hasIssueIndicators) return true;
|
|
136
136
|
|
|
137
137
|
// No concrete issues — check if review explicitly says it's clean
|
|
138
|
-
const good = ['lgtm', 'looks good', 'no issues', 'no problems', 'no concerns', 'all good', 'clean'];
|
|
138
|
+
const good = ['lgtm', 'looks good', 'no issues', 'no problems', 'no concerns', 'all good', 'clean', 'approved', 'ship it', 'ready to merge', 'good to go', 'looks fine', 'no blockers'];
|
|
139
139
|
if (good.some(g => lower.includes(g))) return false;
|
|
140
140
|
|
|
141
141
|
// Ambiguous — default to flagging for human review
|
package/hooks/enforce-tier.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import { createHash } from 'crypto';
|
|
|
4
4
|
import { dirname, resolve, join } from 'path';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import { classifyRisk, extractPaths } from './risk-classifier.mjs';
|
|
7
|
-
import { checkFailureLoop } from './failure-detector.mjs';
|
|
7
|
+
import { checkFailureLoop, recordFailure } from './failure-detector.mjs';
|
|
8
8
|
|
|
9
9
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
10
|
const CONFIG_FILE = resolve(__dirname, '..', 'orchestrator.json');
|
|
@@ -28,7 +28,7 @@ function checkFailureLoop(promptHash) {
|
|
|
28
28
|
const entry = JSON.parse(line);
|
|
29
29
|
if (entry.prompt_hash !== promptHash) continue;
|
|
30
30
|
if (Date.parse(entry.timestamp) < twoHoursAgo) continue;
|
|
31
|
-
if (entry.success === false
|
|
31
|
+
if (entry.success === false) {
|
|
32
32
|
failures++;
|
|
33
33
|
lastTier = entry.tier;
|
|
34
34
|
}
|
package/hooks/quality-gate.mjs
CHANGED
|
@@ -21,9 +21,10 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
|
21
21
|
import { dirname, extname, join, resolve } from 'path';
|
|
22
22
|
import { fileURLToPath } from 'url';
|
|
23
23
|
|
|
24
|
+
import { getProfileOverrides as _getProfileOverrides } from './profiles.mjs';
|
|
25
|
+
|
|
24
26
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
25
27
|
const ORCHESTRATOR_CONFIG = resolve(__dirname, '..', 'orchestrator.json');
|
|
26
|
-
const PROFILE_FILE = resolve(__dirname, '..', 'dual-brain.profile.json');
|
|
27
28
|
const REVIEWS_DIR = resolve(__dirname, '..', 'reviews');
|
|
28
29
|
const DUAL_BRAIN = resolve(__dirname, 'dual-brain-review.mjs');
|
|
29
30
|
|
|
@@ -31,14 +32,7 @@ const RISK_LEVELS = ['low', 'medium', 'high', 'critical'];
|
|
|
31
32
|
|
|
32
33
|
function loadProfileGateSettings() {
|
|
33
34
|
try {
|
|
34
|
-
|
|
35
|
-
const name = data.active || 'balanced';
|
|
36
|
-
const defaults = {
|
|
37
|
-
balanced: { sensitivity_floor: 'medium', dual_brain_minimum: 'high' },
|
|
38
|
-
'cost-saver': { sensitivity_floor: 'high', dual_brain_minimum: 'critical' },
|
|
39
|
-
'quality-first': { sensitivity_floor: 'low', dual_brain_minimum: 'medium' },
|
|
40
|
-
};
|
|
41
|
-
return defaults[name] || defaults.balanced;
|
|
35
|
+
return _getProfileOverrides('quality-gate');
|
|
42
36
|
} catch {
|
|
43
37
|
return { sensitivity_floor: 'medium', dual_brain_minimum: 'high' };
|
|
44
38
|
}
|
|
@@ -310,6 +310,33 @@ test('orchestrator.json: dual_thinking configured', () => {
|
|
|
310
310
|
return true;
|
|
311
311
|
});
|
|
312
312
|
|
|
313
|
+
// ─── Test 15: profile consistency across modules ────────────────────────────
|
|
314
|
+
test('profiles: consistent across modules', () => {
|
|
315
|
+
const profilesSrc = readFileSync(resolve(__dirname, 'profiles.mjs'), 'utf8');
|
|
316
|
+
const profileNames = ['auto', 'balanced', 'cost-saver', 'quality-first'];
|
|
317
|
+
for (const name of profileNames) {
|
|
318
|
+
if (!profilesSrc.includes(`${name}:`) && !profilesSrc.includes(`'${name}':`)) return `profiles.mjs missing: ${name}`;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const installSrc = readFileSync(resolve(__dirname, '..', 'install.mjs'), 'utf8');
|
|
322
|
+
for (const name of profileNames) {
|
|
323
|
+
if (!installSrc.includes(`${name}:`) && !installSrc.includes(`'${name}':`)) return `install.mjs missing profile: ${name}`;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const enforceSrc = readFileSync(resolve(__dirname, 'enforce-tier.mjs'), 'utf8');
|
|
327
|
+
if (!enforceSrc.includes('auto:')) return 'enforce-tier.mjs missing auto in PROFILE_SETTINGS';
|
|
328
|
+
|
|
329
|
+
return true;
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// ─── Test 16: failure-detector only counts real failures ─────────────────────
|
|
333
|
+
test('failure-detector: ignores followed=false', () => {
|
|
334
|
+
const src = readFileSync(resolve(__dirname, 'failure-detector.mjs'), 'utf8');
|
|
335
|
+
if (src.includes('followed === false')) return 'still conflates followed=false with failure';
|
|
336
|
+
if (!src.includes('success === false')) return 'missing success===false check';
|
|
337
|
+
return true;
|
|
338
|
+
});
|
|
339
|
+
|
|
313
340
|
// ─── Summary ─────────────────────────────────────────────────────────────────
|
|
314
341
|
const total = passed + failed;
|
|
315
342
|
console.log(`\n${passed}/${total} tests passed`);
|
package/install.mjs
CHANGED
|
@@ -424,6 +424,12 @@ function profilePath(workspace) {
|
|
|
424
424
|
}
|
|
425
425
|
|
|
426
426
|
const PROFILES = {
|
|
427
|
+
auto: {
|
|
428
|
+
description: 'Adapts routing based on task risk, provider health, and outcomes',
|
|
429
|
+
routing: { prefer_provider: 'auto', think_threshold: 'adaptive', gpt_dispatch_bias: 0 },
|
|
430
|
+
budgets: { session_warn_usd: 5, session_limit_usd: 10, daily_warn_usd: 20, daily_limit_usd: 50 },
|
|
431
|
+
quality_gate: { sensitivity_floor: 'medium', dual_brain_minimum: 'high' },
|
|
432
|
+
},
|
|
427
433
|
balanced: {
|
|
428
434
|
description: 'Auto-routes by complexity, uses both providers evenly',
|
|
429
435
|
routing: { prefer_provider: 'auto', think_threshold: 'normal', gpt_dispatch_bias: 0 },
|
package/package.json
CHANGED