@stackmemoryai/stackmemory 1.0.1 → 1.2.0
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/dist/src/cli/commands/audit.js +134 -0
- package/dist/src/cli/commands/bench.js +252 -0
- package/dist/src/cli/commands/dashboard.js +2 -1
- package/dist/src/cli/commands/stats.js +118 -0
- package/dist/src/cli/index.js +6 -0
- package/dist/src/core/config/feature-flags.js +7 -1
- package/dist/src/core/context/enhanced-rehydration.js +24 -5
- package/dist/src/core/extensions/cerebras-adapter.js +28 -0
- package/dist/src/core/extensions/deepinfra-adapter.js +32 -0
- package/dist/src/core/extensions/provider-adapter.js +33 -240
- package/dist/src/core/models/complexity-scorer.js +154 -0
- package/dist/src/core/models/model-router.js +230 -36
- package/dist/src/core/models/provider-pricing.js +63 -0
- package/dist/src/core/models/sensitive-guard.js +112 -0
- package/dist/src/core/monitoring/feedback-loops.js +88 -0
- package/dist/src/hooks/schemas.js +12 -1
- package/dist/src/integrations/anthropic/batch-client.js +256 -0
- package/dist/src/integrations/anthropic/client.js +87 -72
- package/dist/src/integrations/claude-code/subagent-client.js +133 -12
- package/dist/src/integrations/graphiti/client.js +16 -4
- package/dist/src/integrations/mcp/handlers/index.js +25 -1
- package/dist/src/integrations/mcp/handlers/provider-handlers.js +227 -0
- package/dist/src/integrations/mcp/server.js +207 -1
- package/dist/src/integrations/mcp/tool-definitions.js +38 -1
- package/dist/src/orchestrators/multimodal/baselines.js +128 -0
- package/dist/src/orchestrators/multimodal/constants.js +9 -1
- package/dist/src/orchestrators/multimodal/harness.js +86 -6
- package/dist/src/orchestrators/multimodal/providers.js +113 -2
- package/dist/src/skills/recursive-agent-orchestrator.js +15 -10
- package/dist/src/utils/fuzzy-edit.js +162 -0
- package/package.json +5 -1
|
@@ -27,12 +27,7 @@ class ClaudeAdapter {
|
|
|
27
27
|
const body = this.buildRequestBody(messages, options);
|
|
28
28
|
const response = await fetch(`${this.baseUrl}/v1/messages`, {
|
|
29
29
|
method: "POST",
|
|
30
|
-
headers:
|
|
31
|
-
"Content-Type": "application/json",
|
|
32
|
-
"x-api-key": this.apiKey,
|
|
33
|
-
"anthropic-version": "2023-06-01",
|
|
34
|
-
"anthropic-beta": this.getBetaFlags(options.extensions)
|
|
35
|
-
},
|
|
30
|
+
headers: this.getHeaders(options.extensions),
|
|
36
31
|
body: JSON.stringify({ ...body, stream: true })
|
|
37
32
|
});
|
|
38
33
|
if (!response.ok) {
|
|
@@ -78,12 +73,7 @@ class ClaudeAdapter {
|
|
|
78
73
|
const body = this.buildRequestBody(messages, options);
|
|
79
74
|
const response = await fetch(`${this.baseUrl}/v1/messages`, {
|
|
80
75
|
method: "POST",
|
|
81
|
-
headers:
|
|
82
|
-
"Content-Type": "application/json",
|
|
83
|
-
"x-api-key": this.apiKey,
|
|
84
|
-
"anthropic-version": "2023-06-01",
|
|
85
|
-
"anthropic-beta": this.getBetaFlags(options.extensions)
|
|
86
|
-
},
|
|
76
|
+
headers: this.getHeaders(options.extensions),
|
|
87
77
|
body: JSON.stringify(body)
|
|
88
78
|
});
|
|
89
79
|
if (!response.ok) {
|
|
@@ -166,7 +156,12 @@ class ClaudeAdapter {
|
|
|
166
156
|
}
|
|
167
157
|
return body;
|
|
168
158
|
}
|
|
169
|
-
|
|
159
|
+
getHeaders(extensions) {
|
|
160
|
+
const headers = {
|
|
161
|
+
"Content-Type": "application/json",
|
|
162
|
+
"x-api-key": this.apiKey,
|
|
163
|
+
"anthropic-version": "2023-06-01"
|
|
164
|
+
};
|
|
170
165
|
const flags = [];
|
|
171
166
|
if (extensions?.extendedThinking?.enabled) {
|
|
172
167
|
flags.push("interleaved-thinking-2025-05-14");
|
|
@@ -177,7 +172,10 @@ class ClaudeAdapter {
|
|
|
177
172
|
if (extensions?.documentSupport?.enabled) {
|
|
178
173
|
flags.push("pdfs-2024-09-25");
|
|
179
174
|
}
|
|
180
|
-
|
|
175
|
+
if (flags.length > 0) {
|
|
176
|
+
headers["anthropic-beta"] = flags.join(",");
|
|
177
|
+
}
|
|
178
|
+
return headers;
|
|
181
179
|
}
|
|
182
180
|
normalizeEvent(event) {
|
|
183
181
|
return event;
|
|
@@ -374,240 +372,35 @@ class GPTAdapter {
|
|
|
374
372
|
];
|
|
375
373
|
}
|
|
376
374
|
}
|
|
377
|
-
class GeminiAdapter {
|
|
378
|
-
id = "gemini";
|
|
379
|
-
name = "Google Gemini";
|
|
380
|
-
version = "1.0.0";
|
|
381
|
-
extensions = {
|
|
382
|
-
gemini: {
|
|
383
|
-
grounding: { enabled: true, dynamicThreshold: 0.3 },
|
|
384
|
-
multimodal: {
|
|
385
|
-
videoEnabled: true,
|
|
386
|
-
audioEnabled: true,
|
|
387
|
-
maxVideoDurationSec: 60
|
|
388
|
-
},
|
|
389
|
-
codeExecution: { enabled: true }
|
|
390
|
-
}
|
|
391
|
-
};
|
|
392
|
-
apiKey;
|
|
393
|
-
baseUrl;
|
|
394
|
-
constructor(config) {
|
|
395
|
-
this.apiKey = config.apiKey;
|
|
396
|
-
this.baseUrl = config.baseUrl || "https://generativelanguage.googleapis.com/v1beta";
|
|
397
|
-
}
|
|
398
|
-
supportsExtension(extension) {
|
|
399
|
-
return extension === "gemini";
|
|
400
|
-
}
|
|
401
|
-
async *stream(messages, options) {
|
|
402
|
-
const contents = messages.filter((m) => m.role !== "system").map((m) => ({
|
|
403
|
-
role: m.role === "assistant" ? "model" : "user",
|
|
404
|
-
parts: [
|
|
405
|
-
{
|
|
406
|
-
text: typeof m.content === "string" ? m.content : m.content.filter((c) => c.type === "text").map((c) => c.text).join("")
|
|
407
|
-
}
|
|
408
|
-
]
|
|
409
|
-
}));
|
|
410
|
-
const body = {
|
|
411
|
-
contents,
|
|
412
|
-
generationConfig: {
|
|
413
|
-
maxOutputTokens: options.maxTokens,
|
|
414
|
-
temperature: options.temperature,
|
|
415
|
-
topP: options.topP
|
|
416
|
-
}
|
|
417
|
-
};
|
|
418
|
-
const systemMsg = messages.find((m) => m.role === "system");
|
|
419
|
-
if (systemMsg || options.system) {
|
|
420
|
-
body.systemInstruction = {
|
|
421
|
-
parts: [
|
|
422
|
-
{
|
|
423
|
-
text: options.system || (typeof systemMsg?.content === "string" ? systemMsg.content : "")
|
|
424
|
-
}
|
|
425
|
-
]
|
|
426
|
-
};
|
|
427
|
-
}
|
|
428
|
-
if (options.extensions?.grounding?.enabled) {
|
|
429
|
-
body.tools = [
|
|
430
|
-
{
|
|
431
|
-
googleSearchRetrieval: {
|
|
432
|
-
dynamicRetrievalConfig: {
|
|
433
|
-
mode: "MODE_DYNAMIC",
|
|
434
|
-
dynamicThreshold: options.extensions.grounding.dynamicThreshold
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
];
|
|
439
|
-
}
|
|
440
|
-
if (options.extensions?.codeExecution?.enabled) {
|
|
441
|
-
body.tools = [
|
|
442
|
-
...body.tools || [],
|
|
443
|
-
{ codeExecution: {} }
|
|
444
|
-
];
|
|
445
|
-
}
|
|
446
|
-
if (options.extensions?.safetySettings) {
|
|
447
|
-
body.safetySettings = options.extensions.safetySettings;
|
|
448
|
-
}
|
|
449
|
-
const url = `${this.baseUrl}/models/${options.model}:streamGenerateContent?key=${this.apiKey}&alt=sse`;
|
|
450
|
-
const response = await fetch(url, {
|
|
451
|
-
method: "POST",
|
|
452
|
-
headers: { "Content-Type": "application/json" },
|
|
453
|
-
body: JSON.stringify(body)
|
|
454
|
-
});
|
|
455
|
-
if (!response.ok) {
|
|
456
|
-
yield {
|
|
457
|
-
type: "error",
|
|
458
|
-
error: {
|
|
459
|
-
type: "api_error",
|
|
460
|
-
message: `Gemini API error: ${response.status} ${response.statusText}`
|
|
461
|
-
}
|
|
462
|
-
};
|
|
463
|
-
return;
|
|
464
|
-
}
|
|
465
|
-
yield {
|
|
466
|
-
type: "message_start",
|
|
467
|
-
message: {
|
|
468
|
-
id: `msg_${Date.now()}`,
|
|
469
|
-
model: options.model,
|
|
470
|
-
role: "assistant"
|
|
471
|
-
}
|
|
472
|
-
};
|
|
473
|
-
const reader = response.body?.getReader();
|
|
474
|
-
if (!reader) return;
|
|
475
|
-
const decoder = new TextDecoder();
|
|
476
|
-
let buffer = "";
|
|
477
|
-
const blockIndex = 0;
|
|
478
|
-
let blockStarted = false;
|
|
479
|
-
while (true) {
|
|
480
|
-
const { done, value } = await reader.read();
|
|
481
|
-
if (done) break;
|
|
482
|
-
buffer += decoder.decode(value, { stream: true });
|
|
483
|
-
const lines = buffer.split("\n");
|
|
484
|
-
buffer = lines.pop() || "";
|
|
485
|
-
for (const line of lines) {
|
|
486
|
-
if (line.startsWith("data: ")) {
|
|
487
|
-
try {
|
|
488
|
-
const parsed = JSON.parse(line.slice(6));
|
|
489
|
-
const text = parsed.candidates?.[0]?.content?.parts?.[0]?.text;
|
|
490
|
-
if (text && !blockStarted) {
|
|
491
|
-
blockStarted = true;
|
|
492
|
-
yield {
|
|
493
|
-
type: "content_block_start",
|
|
494
|
-
index: blockIndex,
|
|
495
|
-
contentBlock: { type: "text", text: "" }
|
|
496
|
-
};
|
|
497
|
-
}
|
|
498
|
-
if (text) {
|
|
499
|
-
yield {
|
|
500
|
-
type: "content_block_delta",
|
|
501
|
-
index: blockIndex,
|
|
502
|
-
delta: { type: "text_delta", text }
|
|
503
|
-
};
|
|
504
|
-
}
|
|
505
|
-
} catch {
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
if (blockStarted) {
|
|
511
|
-
yield { type: "content_block_stop", index: blockIndex };
|
|
512
|
-
}
|
|
513
|
-
yield { type: "message_stop" };
|
|
514
|
-
}
|
|
515
|
-
async complete(messages, options) {
|
|
516
|
-
const contents = messages.filter((m) => m.role !== "system").map((m) => ({
|
|
517
|
-
role: m.role === "assistant" ? "model" : "user",
|
|
518
|
-
parts: [
|
|
519
|
-
{
|
|
520
|
-
text: typeof m.content === "string" ? m.content : m.content.filter((c) => c.type === "text").map((c) => c.text).join("")
|
|
521
|
-
}
|
|
522
|
-
]
|
|
523
|
-
}));
|
|
524
|
-
const body = {
|
|
525
|
-
contents,
|
|
526
|
-
generationConfig: {
|
|
527
|
-
maxOutputTokens: options.maxTokens,
|
|
528
|
-
temperature: options.temperature
|
|
529
|
-
}
|
|
530
|
-
};
|
|
531
|
-
const url = `${this.baseUrl}/models/${options.model}:generateContent?key=${this.apiKey}`;
|
|
532
|
-
const response = await fetch(url, {
|
|
533
|
-
method: "POST",
|
|
534
|
-
headers: { "Content-Type": "application/json" },
|
|
535
|
-
body: JSON.stringify(body)
|
|
536
|
-
});
|
|
537
|
-
if (!response.ok) {
|
|
538
|
-
throw new Error(
|
|
539
|
-
`Gemini API error: ${response.status} ${response.statusText}`
|
|
540
|
-
);
|
|
541
|
-
}
|
|
542
|
-
const data = await response.json();
|
|
543
|
-
const candidate = data.candidates?.[0];
|
|
544
|
-
const text = candidate?.content?.parts?.[0]?.text ?? "";
|
|
545
|
-
return {
|
|
546
|
-
content: [{ type: "text", text }],
|
|
547
|
-
usage: {
|
|
548
|
-
inputTokens: data.usageMetadata?.promptTokenCount ?? 0,
|
|
549
|
-
outputTokens: data.usageMetadata?.candidatesTokenCount ?? 0
|
|
550
|
-
},
|
|
551
|
-
stopReason: candidate?.finishReason ?? "STOP"
|
|
552
|
-
};
|
|
553
|
-
}
|
|
554
|
-
async validateConnection() {
|
|
555
|
-
try {
|
|
556
|
-
const response = await fetch(`${this.baseUrl}/models?key=${this.apiKey}`);
|
|
557
|
-
return response.ok;
|
|
558
|
-
} catch {
|
|
559
|
-
return false;
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
async listModels() {
|
|
563
|
-
return [
|
|
564
|
-
"gemini-2.0-flash-exp",
|
|
565
|
-
"gemini-1.5-pro",
|
|
566
|
-
"gemini-1.5-flash",
|
|
567
|
-
"gemini-1.5-flash-8b",
|
|
568
|
-
"gemini-1.0-pro"
|
|
569
|
-
];
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
375
|
function createProvider(id, config) {
|
|
573
376
|
switch (id) {
|
|
574
|
-
case "
|
|
377
|
+
case "anthropic":
|
|
575
378
|
return new ClaudeAdapter(config);
|
|
576
|
-
case "
|
|
379
|
+
case "openai":
|
|
380
|
+
case "qwen":
|
|
381
|
+
case "ollama":
|
|
577
382
|
return new GPTAdapter(config);
|
|
578
|
-
case "
|
|
579
|
-
return new
|
|
383
|
+
case "cerebras":
|
|
384
|
+
return new GPTAdapter({
|
|
385
|
+
apiKey: config.apiKey,
|
|
386
|
+
baseUrl: config.baseUrl || "https://api.cerebras.ai/v1"
|
|
387
|
+
});
|
|
388
|
+
case "deepinfra":
|
|
389
|
+
return new GPTAdapter({
|
|
390
|
+
apiKey: config.apiKey,
|
|
391
|
+
baseUrl: config.baseUrl || "https://api.deepinfra.com/v1/openai"
|
|
392
|
+
});
|
|
393
|
+
case "openrouter":
|
|
394
|
+
return new GPTAdapter({
|
|
395
|
+
apiKey: config.apiKey,
|
|
396
|
+
baseUrl: config.baseUrl || "https://openrouter.ai/api"
|
|
397
|
+
});
|
|
580
398
|
default:
|
|
581
|
-
throw new Error(`
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
class ProviderRegistry {
|
|
585
|
-
providers = /* @__PURE__ */ new Map();
|
|
586
|
-
register(adapter) {
|
|
587
|
-
this.providers.set(adapter.id, adapter);
|
|
588
|
-
}
|
|
589
|
-
get(id) {
|
|
590
|
-
return this.providers.get(id);
|
|
591
|
-
}
|
|
592
|
-
list() {
|
|
593
|
-
return Array.from(this.providers.values());
|
|
594
|
-
}
|
|
595
|
-
has(id) {
|
|
596
|
-
return this.providers.has(id);
|
|
597
|
-
}
|
|
598
|
-
/**
|
|
599
|
-
* Find providers that support a specific extension
|
|
600
|
-
*/
|
|
601
|
-
findByExtension(extension) {
|
|
602
|
-
return this.list().filter((p) => p.supportsExtension(extension));
|
|
399
|
+
throw new Error(`No adapter for provider: ${id}`);
|
|
603
400
|
}
|
|
604
401
|
}
|
|
605
|
-
const providerRegistry = new ProviderRegistry();
|
|
606
402
|
export {
|
|
607
403
|
ClaudeAdapter,
|
|
608
404
|
GPTAdapter,
|
|
609
|
-
|
|
610
|
-
ProviderRegistry,
|
|
611
|
-
createProvider,
|
|
612
|
-
providerRegistry
|
|
405
|
+
createProvider
|
|
613
406
|
};
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { fileURLToPath as __fileURLToPath } from 'url';
|
|
2
|
+
import { dirname as __pathDirname } from 'path';
|
|
3
|
+
const __filename = __fileURLToPath(import.meta.url);
|
|
4
|
+
const __dirname = __pathDirname(__filename);
|
|
5
|
+
const HIGH_COMPLEXITY_KEYWORDS = [
|
|
6
|
+
/\barchitect/i,
|
|
7
|
+
/\brefactor\b/i,
|
|
8
|
+
/\bredesign/i,
|
|
9
|
+
/\bmigrat/i,
|
|
10
|
+
/\bsecurity\b/i,
|
|
11
|
+
/\bvulnerabilit/i,
|
|
12
|
+
/\bconcurrency/i,
|
|
13
|
+
/\brace\s+condition/i,
|
|
14
|
+
/\bdistributed/i,
|
|
15
|
+
/\bconsensus/i,
|
|
16
|
+
/\btrade-?offs?\b/i,
|
|
17
|
+
/\bbackward.?compat/i,
|
|
18
|
+
/\bbreaking\s+change/i,
|
|
19
|
+
/\bperformance\s+(?:optimi|critical|bottleneck)/i,
|
|
20
|
+
/\bscalability/i,
|
|
21
|
+
/\bcrypto/i,
|
|
22
|
+
/\bencrypt/i,
|
|
23
|
+
/\bauth(?:entication|orization)\b/i,
|
|
24
|
+
/\bOWASP/i
|
|
25
|
+
];
|
|
26
|
+
const LOW_COMPLEXITY_KEYWORDS = [
|
|
27
|
+
/\bfix\s+typo/i,
|
|
28
|
+
/\brename\b/i,
|
|
29
|
+
/\bformat(?:ting)?\s+(?:the\s+)?(?:code|file|project|source)/i,
|
|
30
|
+
/\bprettier/i,
|
|
31
|
+
/\blint\s*(?:fix|error|warning)/i,
|
|
32
|
+
/\bupdate\s+(?:version|dep)/i,
|
|
33
|
+
/\badd\s+comment/i,
|
|
34
|
+
/\bremove\s+unused/i,
|
|
35
|
+
/\bimport\s+(?:sort|order)/i,
|
|
36
|
+
/\bconsole\.log/i,
|
|
37
|
+
/\btodo\b/i,
|
|
38
|
+
/\bcleanup\b/i
|
|
39
|
+
];
|
|
40
|
+
const MEDIUM_COMPLEXITY_KEYWORDS = [
|
|
41
|
+
/\bwrite\b/i,
|
|
42
|
+
/\bimplement/i,
|
|
43
|
+
/\bcreate\b/i,
|
|
44
|
+
/\bbuild\b/i,
|
|
45
|
+
/\bparse/i,
|
|
46
|
+
/\bhandle\b/i,
|
|
47
|
+
/\bvalidat/i,
|
|
48
|
+
/\btransform/i,
|
|
49
|
+
/\bconvert/i,
|
|
50
|
+
/\bgenerat/i,
|
|
51
|
+
/\bintegrat/i,
|
|
52
|
+
/\boptimiz/i
|
|
53
|
+
];
|
|
54
|
+
const REASONING_INDICATORS = [
|
|
55
|
+
/\bstep\s*(?:by|1|2|3)/i,
|
|
56
|
+
/\bfirst.*then.*finally/i,
|
|
57
|
+
/\bcompare\s+(?:and|options|approaches)/i,
|
|
58
|
+
/\banalyze/i,
|
|
59
|
+
/\bevaluate/i,
|
|
60
|
+
/\bdiagnos/i,
|
|
61
|
+
/\bdebug.*(?:complex|intermittent|flaky)/i,
|
|
62
|
+
/\broot\s+cause/i,
|
|
63
|
+
/\bsystem\s*design/i
|
|
64
|
+
];
|
|
65
|
+
const LOW_THRESHOLD = 0.25;
|
|
66
|
+
const HIGH_THRESHOLD = 0.6;
|
|
67
|
+
const SHORT_PROMPT = 200;
|
|
68
|
+
const MEDIUM_PROMPT = 800;
|
|
69
|
+
const LONG_PROMPT = 2e3;
|
|
70
|
+
function scoreComplexity(task, context) {
|
|
71
|
+
const signals = [];
|
|
72
|
+
let score = 0;
|
|
73
|
+
const highHits = HIGH_COMPLEXITY_KEYWORDS.filter((re) => re.test(task));
|
|
74
|
+
if (highHits.length >= 5) {
|
|
75
|
+
score += 0.6;
|
|
76
|
+
signals.push(`${highHits.length} high-complexity keywords`);
|
|
77
|
+
} else if (highHits.length >= 3) {
|
|
78
|
+
score += 0.5;
|
|
79
|
+
signals.push(`${highHits.length} high-complexity keywords`);
|
|
80
|
+
} else if (highHits.length >= 1) {
|
|
81
|
+
score += 0.2 * Math.min(highHits.length, 2);
|
|
82
|
+
signals.push(`${highHits.length} high-complexity keyword(s)`);
|
|
83
|
+
}
|
|
84
|
+
const lowHits = LOW_COMPLEXITY_KEYWORDS.filter((re) => re.test(task));
|
|
85
|
+
if (lowHits.length >= 2) {
|
|
86
|
+
score -= 0.25;
|
|
87
|
+
signals.push(`${lowHits.length} low-complexity keywords`);
|
|
88
|
+
} else if (lowHits.length === 1) {
|
|
89
|
+
score -= 0.15;
|
|
90
|
+
signals.push("1 low-complexity keyword");
|
|
91
|
+
}
|
|
92
|
+
const medHits = MEDIUM_COMPLEXITY_KEYWORDS.filter((re) => re.test(task));
|
|
93
|
+
if (medHits.length >= 2) {
|
|
94
|
+
score += 0.3;
|
|
95
|
+
signals.push(`${medHits.length} medium-complexity keywords`);
|
|
96
|
+
} else if (medHits.length === 1) {
|
|
97
|
+
score += 0.15;
|
|
98
|
+
signals.push("1 medium-complexity keyword");
|
|
99
|
+
}
|
|
100
|
+
const hasSubstance = highHits.length > 0 || medHits.length > 0;
|
|
101
|
+
const len = task.length;
|
|
102
|
+
if (len > LONG_PROMPT && hasSubstance) {
|
|
103
|
+
score += 0.2;
|
|
104
|
+
signals.push(`long prompt (${len} chars)`);
|
|
105
|
+
} else if (len > MEDIUM_PROMPT && hasSubstance) {
|
|
106
|
+
score += 0.1;
|
|
107
|
+
signals.push(`medium prompt (${len} chars)`);
|
|
108
|
+
} else if (len < SHORT_PROMPT) {
|
|
109
|
+
score -= 0.03;
|
|
110
|
+
}
|
|
111
|
+
const reasoningHits = REASONING_INDICATORS.filter((re) => re.test(task));
|
|
112
|
+
if (reasoningHits.length >= 2) {
|
|
113
|
+
score += 0.25;
|
|
114
|
+
signals.push(`${reasoningHits.length} reasoning indicators`);
|
|
115
|
+
} else if (reasoningHits.length === 1) {
|
|
116
|
+
score += 0.15;
|
|
117
|
+
signals.push("1 reasoning indicator");
|
|
118
|
+
}
|
|
119
|
+
if (context) {
|
|
120
|
+
const files = context["files"];
|
|
121
|
+
if (Array.isArray(files)) {
|
|
122
|
+
if (files.length >= 10) {
|
|
123
|
+
score += 0.15;
|
|
124
|
+
signals.push(`${files.length} files in context`);
|
|
125
|
+
} else if (files.length >= 4) {
|
|
126
|
+
score += 0.08;
|
|
127
|
+
signals.push(`${files.length} files in context`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const codeSize = context["codeSize"];
|
|
131
|
+
if (typeof codeSize === "number" && codeSize > 5e3) {
|
|
132
|
+
score += 0.1;
|
|
133
|
+
signals.push(`large code context (${codeSize} chars)`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const questionMarks = (task.match(/\?/g) || []).length;
|
|
137
|
+
if (questionMarks >= 3) {
|
|
138
|
+
score += 0.1;
|
|
139
|
+
signals.push(`${questionMarks} questions`);
|
|
140
|
+
}
|
|
141
|
+
score = Math.max(0, Math.min(1, score));
|
|
142
|
+
let tier;
|
|
143
|
+
if (score < LOW_THRESHOLD) {
|
|
144
|
+
tier = "low";
|
|
145
|
+
} else if (score >= HIGH_THRESHOLD) {
|
|
146
|
+
tier = "high";
|
|
147
|
+
} else {
|
|
148
|
+
tier = "medium";
|
|
149
|
+
}
|
|
150
|
+
return { score: Math.round(score * 100) / 100, tier, signals };
|
|
151
|
+
}
|
|
152
|
+
export {
|
|
153
|
+
scoreComplexity
|
|
154
|
+
};
|