dual-brain 0.1.9 → 0.1.10

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/detect.mjs +132 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dual-brain",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "description": "AI orchestration across Claude + OpenAI subscriptions — smart routing, budget awareness, and dual-brain collaboration",
5
5
  "type": "module",
6
6
  "bin": {
package/src/detect.mjs CHANGED
@@ -157,6 +157,129 @@ function buildExplanation({ intent, risk, complexity, fileCount, priorFailures }
157
157
  return parts.join(' ') + '.';
158
158
  }
159
159
 
160
+ // ─── Reasoning depth classification ───────────────────────────────────────────
161
+
162
+ const ULTRA_UNCERTAINTY = /\b(not sure|maybe|should we|architect|design|trade-?off|approach)\b/i;
163
+ const ULTRA_DEEP_ANALYSIS = /\b(think about|analyze|analyse|evaluate|compare options)\b/i;
164
+ const HIGH_CROSS_CUTTING = /\b(refactor|rename across|update all|migration)\b/i;
165
+ const LOW_SIMPLE = /\b(grep|find|search|list|show|what is|where is)\b/i;
166
+
167
+ /**
168
+ * Classify the reasoning depth needed for a task.
169
+ * Returns { depth: 'low'|'medium'|'high'|'ultra', signals: string[] }
170
+ */
171
+ function classifyReasoningDepth(prompt, files = [], priorOutcomes = []) {
172
+ const signals = [];
173
+
174
+ // Gather prior failure count from priorOutcomes array
175
+ const failures = priorOutcomes.filter(o => o && (o.failed || o.status === 'failed' || o.outcome === 'failed' || o.success === false)).length;
176
+
177
+ // File-based risk (reuse classifyRisk)
178
+ const { level: fileRisk } = classifyRisk(files);
179
+
180
+ // Keyword risk from prompt (reuse RISK_KEYWORDS)
181
+ let keywordRisk = 'low';
182
+ for (const { level, regex } of RISK_KEYWORDS) {
183
+ if (regex.test(prompt)) { keywordRisk = level; break; }
184
+ }
185
+
186
+ const risk = higherRisk(fileRisk, keywordRisk);
187
+
188
+ // Directory spread from files
189
+ const dirs = new Set(files.map(f => {
190
+ const parts = f.replace(/^\//, '').split('/');
191
+ return parts.length > 1 ? parts[0] : '.';
192
+ }));
193
+ const dirCount = dirs.size;
194
+
195
+ // ── Ultra signals ──────────────────────────────────────────────────────────
196
+ const ultraSignals = [];
197
+
198
+ if (ULTRA_UNCERTAINTY.test(prompt)) {
199
+ const match = prompt.match(ULTRA_UNCERTAINTY);
200
+ ultraSignals.push(`prompt contains '${match[0]}'`);
201
+ }
202
+ if (ULTRA_DEEP_ANALYSIS.test(prompt)) {
203
+ const match = prompt.match(ULTRA_DEEP_ANALYSIS);
204
+ ultraSignals.push(`prompt requests deep analysis ('${match[0]}')`);
205
+ }
206
+ if (risk === 'critical') {
207
+ ultraSignals.push('risk classified as critical');
208
+ }
209
+ if (failures >= 2) {
210
+ ultraSignals.push(`${failures} prior failures on similar task`);
211
+ }
212
+ if (fileRisk === 'critical') {
213
+ ultraSignals.push('files include auth/security/billing/migration patterns');
214
+ }
215
+
216
+ if (ultraSignals.length > 0) {
217
+ return { depth: 'ultra', signals: ultraSignals };
218
+ }
219
+
220
+ // ── High signals ───────────────────────────────────────────────────────────
221
+ const highSignals = [];
222
+
223
+ if (risk === 'high') {
224
+ highSignals.push('risk classified as high');
225
+ }
226
+ if (files.length > 5) {
227
+ highSignals.push(`${files.length} files provided`);
228
+ }
229
+ if (failures === 1) {
230
+ highSignals.push('1 prior failure on similar task');
231
+ }
232
+ if (HIGH_CROSS_CUTTING.test(prompt)) {
233
+ const match = prompt.match(HIGH_CROSS_CUTTING);
234
+ highSignals.push(`prompt mentions cross-cutting concern ('${match[0]}')`);
235
+ }
236
+ if (dirCount >= 3) {
237
+ highSignals.push(`files span ${dirCount} directories`);
238
+ }
239
+
240
+ if (highSignals.length > 0) {
241
+ return { depth: 'high', signals: highSignals };
242
+ }
243
+
244
+ // ── Medium signals ─────────────────────────────────────────────────────────
245
+ const MEDIUM_IMPL = /\b(add|implement|build|create|fix|update)\b/i;
246
+ const mediumSignals = [];
247
+
248
+ if (risk === 'medium') {
249
+ mediumSignals.push('risk classified as medium');
250
+ }
251
+ if (files.length >= 2 && files.length <= 5) {
252
+ mediumSignals.push(`${files.length} files provided`);
253
+ }
254
+ if (MEDIUM_IMPL.test(prompt)) {
255
+ const match = prompt.match(MEDIUM_IMPL);
256
+ mediumSignals.push(`prompt contains implementation keyword ('${match[0]}')`);
257
+ }
258
+
259
+ if (mediumSignals.length > 0) {
260
+ return { depth: 'medium', signals: mediumSignals };
261
+ }
262
+
263
+ // ── Low signals ────────────────────────────────────────────────────────────
264
+ const lowSignals = [];
265
+
266
+ if (risk === 'low') {
267
+ lowSignals.push('risk classified as low');
268
+ }
269
+ if (files.length <= 1) {
270
+ lowSignals.push(files.length === 0 ? 'no files provided' : '1 file provided');
271
+ }
272
+ if (LOW_SIMPLE.test(prompt)) {
273
+ const match = prompt.match(LOW_SIMPLE);
274
+ lowSignals.push(`prompt is a simple lookup ('${match[0]}')`);
275
+ }
276
+ if (failures === 0) {
277
+ lowSignals.push('no prior failures');
278
+ }
279
+
280
+ return { depth: 'low', signals: lowSignals.length > 0 ? lowSignals : ['no elevated signals detected'] };
281
+ }
282
+
160
283
  /** Main detection function. Input: { prompt, files?, priorFailures? } */
161
284
  function detectTask(input) {
162
285
  const { prompt = '', files = [], priorFailures = 0 } = input;
@@ -213,6 +336,12 @@ function detectTask(input) {
213
336
  // 8. Explanation
214
337
  const explanation = buildExplanation({ intent, risk, complexity, fileCount, priorFailures });
215
338
 
339
+ // 9. Reasoning depth
340
+ const priorOutcomes = priorFailures > 0
341
+ ? Array.from({ length: priorFailures }, () => ({ failed: true }))
342
+ : [];
343
+ const { depth: reasoningDepth, signals: reasoningSignals } = classifyReasoningDepth(prompt, files, priorOutcomes);
344
+
216
345
  return {
217
346
  intent,
218
347
  risk,
@@ -225,6 +354,8 @@ function detectTask(input) {
225
354
  requiresWrite: requiresWrite(intent),
226
355
  explanation,
227
356
  specialist: specialistResult,
357
+ reasoningDepth,
358
+ reasoningSignals,
228
359
  };
229
360
  }
230
361
 
@@ -342,4 +473,4 @@ if (process.argv[1] && new URL(import.meta.url).pathname === process.argv[1]) {
342
473
  console.log(JSON.stringify(result, null, 2));
343
474
  }
344
475
 
345
- export { detectTask, classifyIntent, classifyRisk, estimateComplexity, inferTier, extractPaths, classifySpecialist };
476
+ export { detectTask, classifyIntent, classifyRisk, estimateComplexity, inferTier, extractPaths, classifySpecialist, classifyReasoningDepth };