@soleri/core 7.0.0 → 8.0.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/agency/agency-manager.d.ts +27 -1
- package/dist/agency/agency-manager.d.ts.map +1 -1
- package/dist/agency/agency-manager.js +180 -9
- package/dist/agency/agency-manager.js.map +1 -1
- package/dist/agency/default-rules.d.ts +7 -0
- package/dist/agency/default-rules.d.ts.map +1 -0
- package/dist/agency/default-rules.js +79 -0
- package/dist/agency/default-rules.js.map +1 -0
- package/dist/agency/types.d.ts +48 -0
- package/dist/agency/types.d.ts.map +1 -1
- package/dist/brain/brain.d.ts +17 -2
- package/dist/brain/brain.d.ts.map +1 -1
- package/dist/brain/brain.js +118 -8
- package/dist/brain/brain.js.map +1 -1
- package/dist/brain/knowledge-synthesizer.d.ts +37 -0
- package/dist/brain/knowledge-synthesizer.d.ts.map +1 -0
- package/dist/brain/knowledge-synthesizer.js +161 -0
- package/dist/brain/knowledge-synthesizer.js.map +1 -0
- package/dist/brain/learning-radar.d.ts +96 -0
- package/dist/brain/learning-radar.d.ts.map +1 -0
- package/dist/brain/learning-radar.js +202 -0
- package/dist/brain/learning-radar.js.map +1 -0
- package/dist/brain/types.d.ts +15 -0
- package/dist/brain/types.d.ts.map +1 -1
- package/dist/context/context-engine.d.ts.map +1 -1
- package/dist/context/context-engine.js +82 -17
- package/dist/context/context-engine.js.map +1 -1
- package/dist/context/types.d.ts +5 -0
- package/dist/context/types.d.ts.map +1 -1
- package/dist/control/intent-router.d.ts +12 -1
- package/dist/control/intent-router.d.ts.map +1 -1
- package/dist/control/intent-router.js +68 -0
- package/dist/control/intent-router.js.map +1 -1
- package/dist/control/types.d.ts +17 -0
- package/dist/control/types.d.ts.map +1 -1
- package/dist/curator/classifier.d.ts +18 -0
- package/dist/curator/classifier.d.ts.map +1 -0
- package/dist/curator/classifier.js +61 -0
- package/dist/curator/classifier.js.map +1 -0
- package/dist/curator/quality-gate.d.ts +29 -0
- package/dist/curator/quality-gate.d.ts.map +1 -0
- package/dist/curator/quality-gate.js +88 -0
- package/dist/curator/quality-gate.js.map +1 -0
- package/dist/engine/bin/soleri-engine.js +1 -0
- package/dist/engine/bin/soleri-engine.js.map +1 -1
- package/dist/events/event-bus.d.ts +30 -0
- package/dist/events/event-bus.d.ts.map +1 -0
- package/dist/events/event-bus.js +51 -0
- package/dist/events/event-bus.js.map +1 -0
- package/dist/flows/chain-runner.d.ts +46 -0
- package/dist/flows/chain-runner.d.ts.map +1 -0
- package/dist/flows/chain-runner.js +271 -0
- package/dist/flows/chain-runner.js.map +1 -0
- package/dist/flows/chain-types.d.ts +103 -0
- package/dist/flows/chain-types.d.ts.map +1 -0
- package/dist/flows/chain-types.js +23 -0
- package/dist/flows/chain-types.js.map +1 -0
- package/dist/health/doctor-checks.d.ts +15 -0
- package/dist/health/doctor-checks.d.ts.map +1 -0
- package/dist/health/doctor-checks.js +98 -0
- package/dist/health/doctor-checks.js.map +1 -0
- package/dist/intake/text-ingester.d.ts +52 -0
- package/dist/intake/text-ingester.d.ts.map +1 -0
- package/dist/intake/text-ingester.js +181 -0
- package/dist/intake/text-ingester.js.map +1 -0
- package/dist/llm/llm-client.d.ts.map +1 -1
- package/dist/llm/llm-client.js +37 -1
- package/dist/llm/llm-client.js.map +1 -1
- package/dist/llm/oauth-discovery.d.ts +26 -0
- package/dist/llm/oauth-discovery.d.ts.map +1 -0
- package/dist/llm/oauth-discovery.js +149 -0
- package/dist/llm/oauth-discovery.js.map +1 -0
- package/dist/planning/evidence-collector.d.ts +41 -0
- package/dist/planning/evidence-collector.d.ts.map +1 -0
- package/dist/planning/evidence-collector.js +194 -0
- package/dist/planning/evidence-collector.js.map +1 -0
- package/dist/planning/planner.d.ts +4 -0
- package/dist/planning/planner.d.ts.map +1 -1
- package/dist/planning/planner.js +11 -0
- package/dist/planning/planner.js.map +1 -1
- package/dist/queue/job-queue.d.ts +92 -0
- package/dist/queue/job-queue.d.ts.map +1 -0
- package/dist/queue/job-queue.js +180 -0
- package/dist/queue/job-queue.js.map +1 -0
- package/dist/queue/pipeline-runner.d.ts +62 -0
- package/dist/queue/pipeline-runner.d.ts.map +1 -0
- package/dist/queue/pipeline-runner.js +126 -0
- package/dist/queue/pipeline-runner.js.map +1 -0
- package/dist/runtime/admin-setup-ops.d.ts +20 -0
- package/dist/runtime/admin-setup-ops.d.ts.map +1 -0
- package/dist/runtime/admin-setup-ops.js +583 -0
- package/dist/runtime/admin-setup-ops.js.map +1 -0
- package/dist/runtime/chain-ops.d.ts +9 -0
- package/dist/runtime/chain-ops.d.ts.map +1 -0
- package/dist/runtime/chain-ops.js +107 -0
- package/dist/runtime/chain-ops.js.map +1 -0
- package/dist/runtime/claude-md-helpers.d.ts +65 -0
- package/dist/runtime/claude-md-helpers.d.ts.map +1 -0
- package/dist/runtime/claude-md-helpers.js +173 -0
- package/dist/runtime/claude-md-helpers.js.map +1 -0
- package/dist/runtime/curator-extra-ops.d.ts +3 -2
- package/dist/runtime/curator-extra-ops.d.ts.map +1 -1
- package/dist/runtime/curator-extra-ops.js +81 -3
- package/dist/runtime/curator-extra-ops.js.map +1 -1
- package/dist/runtime/facades/admin-facade.d.ts.map +1 -1
- package/dist/runtime/facades/admin-facade.js +4 -0
- package/dist/runtime/facades/admin-facade.js.map +1 -1
- package/dist/runtime/facades/agency-facade.d.ts.map +1 -1
- package/dist/runtime/facades/agency-facade.js +64 -0
- package/dist/runtime/facades/agency-facade.js.map +1 -1
- package/dist/runtime/facades/brain-facade.d.ts.map +1 -1
- package/dist/runtime/facades/brain-facade.js +122 -1
- package/dist/runtime/facades/brain-facade.js.map +1 -1
- package/dist/runtime/facades/control-facade.d.ts.map +1 -1
- package/dist/runtime/facades/control-facade.js +42 -0
- package/dist/runtime/facades/control-facade.js.map +1 -1
- package/dist/runtime/facades/memory-facade.d.ts.map +1 -1
- package/dist/runtime/facades/memory-facade.js +20 -2
- package/dist/runtime/facades/memory-facade.js.map +1 -1
- package/dist/runtime/facades/plan-facade.d.ts.map +1 -1
- package/dist/runtime/facades/plan-facade.js +2 -0
- package/dist/runtime/facades/plan-facade.js.map +1 -1
- package/dist/runtime/facades/vault-facade.d.ts.map +1 -1
- package/dist/runtime/facades/vault-facade.js +25 -5
- package/dist/runtime/facades/vault-facade.js.map +1 -1
- package/dist/runtime/intake-ops.d.ts +7 -5
- package/dist/runtime/intake-ops.d.ts.map +1 -1
- package/dist/runtime/intake-ops.js +98 -5
- package/dist/runtime/intake-ops.js.map +1 -1
- package/dist/runtime/memory-extra-ops.d.ts +6 -3
- package/dist/runtime/memory-extra-ops.d.ts.map +1 -1
- package/dist/runtime/memory-extra-ops.js +292 -4
- package/dist/runtime/memory-extra-ops.js.map +1 -1
- package/dist/runtime/planning-extra-ops.d.ts.map +1 -1
- package/dist/runtime/planning-extra-ops.js +85 -0
- package/dist/runtime/planning-extra-ops.js.map +1 -1
- package/dist/runtime/playbook-ops.js +1 -1
- package/dist/runtime/playbook-ops.js.map +1 -1
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +143 -2
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/session-briefing.d.ts +23 -0
- package/dist/runtime/session-briefing.d.ts.map +1 -0
- package/dist/runtime/session-briefing.js +140 -0
- package/dist/runtime/session-briefing.js.map +1 -0
- package/dist/runtime/types.d.ts +23 -0
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/runtime/vault-linking-ops.d.ts.map +1 -1
- package/dist/runtime/vault-linking-ops.js +1 -3
- package/dist/runtime/vault-linking-ops.js.map +1 -1
- package/dist/vault/vault.d.ts +25 -0
- package/dist/vault/vault.d.ts.map +1 -1
- package/dist/vault/vault.js +67 -3
- package/dist/vault/vault.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/admin-setup-ops.test.ts +355 -0
- package/src/__tests__/async-infrastructure.test.ts +307 -0
- package/src/__tests__/cognee-client-gaps.test.ts +6 -2
- package/src/__tests__/cognee-hybrid-search.test.ts +49 -35
- package/src/__tests__/cognee-sync-manager-deep.test.ts +89 -65
- package/src/__tests__/curator-extra-ops.test.ts +6 -2
- package/src/__tests__/curator-pipeline-e2e.test.ts +358 -0
- package/src/__tests__/memory-extra-ops.test.ts +2 -2
- package/src/__tests__/planning-extra-ops.test.ts +2 -2
- package/src/__tests__/second-brain-features.test.ts +583 -0
- package/src/agency/agency-manager.ts +217 -9
- package/src/agency/default-rules.ts +83 -0
- package/src/agency/types.ts +61 -0
- package/src/brain/brain.ts +110 -8
- package/src/brain/knowledge-synthesizer.ts +218 -0
- package/src/brain/learning-radar.ts +340 -0
- package/src/brain/types.ts +16 -0
- package/src/context/context-engine.ts +114 -15
- package/src/context/types.ts +5 -0
- package/src/control/intent-router.ts +107 -0
- package/src/control/types.ts +10 -0
- package/src/curator/classifier.ts +88 -0
- package/src/curator/quality-gate.ts +129 -0
- package/src/engine/bin/soleri-engine.ts +1 -0
- package/src/events/event-bus.ts +58 -0
- package/src/flows/chain-runner.ts +369 -0
- package/src/flows/chain-types.ts +57 -0
- package/src/health/doctor-checks.ts +115 -0
- package/src/intake/text-ingester.ts +234 -0
- package/src/llm/llm-client.ts +38 -1
- package/src/llm/oauth-discovery.ts +169 -0
- package/src/planning/evidence-collector.ts +247 -0
- package/src/planning/planner.ts +11 -0
- package/src/queue/job-queue.ts +281 -0
- package/src/queue/pipeline-runner.ts +149 -0
- package/src/runtime/admin-setup-ops.ts +664 -0
- package/src/runtime/chain-ops.ts +121 -0
- package/src/runtime/claude-md-helpers.ts +236 -0
- package/src/runtime/curator-extra-ops.ts +86 -3
- package/src/runtime/facades/admin-facade.ts +4 -0
- package/src/runtime/facades/agency-facade.ts +68 -0
- package/src/runtime/facades/brain-facade.ts +142 -1
- package/src/runtime/facades/control-facade.ts +45 -0
- package/src/runtime/facades/memory-facade.ts +20 -2
- package/src/runtime/facades/plan-facade.ts +2 -0
- package/src/runtime/facades/vault-facade.ts +28 -5
- package/src/runtime/intake-ops.ts +107 -5
- package/src/runtime/memory-extra-ops.ts +312 -4
- package/src/runtime/planning-extra-ops.ts +94 -0
- package/src/runtime/playbook-ops.ts +1 -1
- package/src/runtime/runtime.ts +138 -2
- package/src/runtime/session-briefing.ts +161 -0
- package/src/runtime/types.ts +23 -0
- package/src/runtime/vault-linking-ops.ts +1 -3
- package/src/vault/vault.ts +79 -4
|
@@ -71,11 +71,34 @@ const STOP_PATTERNS = new Set([
|
|
|
71
71
|
'out-of-the-box',
|
|
72
72
|
]);
|
|
73
73
|
|
|
74
|
-
// ───
|
|
74
|
+
// ─── Scoring Constants (tunable) ────────────────────────────────────
|
|
75
75
|
|
|
76
|
+
/** Confidence thresholds for discrete levels. */
|
|
76
77
|
const HIGH_CONFIDENCE = 0.75;
|
|
77
78
|
const MEDIUM_CONFIDENCE = 0.45;
|
|
78
79
|
|
|
80
|
+
/** Weights for knowledge item multi-signal scoring. */
|
|
81
|
+
const KNOWLEDGE_WEIGHTS = {
|
|
82
|
+
/** Base FTS/vector score weight. */
|
|
83
|
+
baseScore: 0.4,
|
|
84
|
+
/** Title keyword overlap weight. */
|
|
85
|
+
titleMatch: 0.25,
|
|
86
|
+
/** Tag overlap weight. */
|
|
87
|
+
tagOverlap: 0.2,
|
|
88
|
+
/** Intent/domain alignment weight. */
|
|
89
|
+
intentBoost: 0.15,
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/** Weights for confidence computation. */
|
|
93
|
+
const CONFIDENCE_WEIGHTS = {
|
|
94
|
+
entitySignalPerEntity: 0.08,
|
|
95
|
+
entitySignalMax: 0.4,
|
|
96
|
+
actionSignal: 0.2,
|
|
97
|
+
knowledgeSignalMultiplier: 0.3,
|
|
98
|
+
sourceDiversityPerExtra: 0.05,
|
|
99
|
+
sourceDiversityMax: 0.1,
|
|
100
|
+
};
|
|
101
|
+
|
|
79
102
|
// ─── Class ──────────────────────────────────────────────────────────
|
|
80
103
|
|
|
81
104
|
export class ContextEngine {
|
|
@@ -120,7 +143,13 @@ export class ContextEngine {
|
|
|
120
143
|
if (seen.has(key)) continue;
|
|
121
144
|
if (type === 'pattern' && STOP_PATTERNS.has(value)) continue;
|
|
122
145
|
seen.add(key);
|
|
123
|
-
entities.push({
|
|
146
|
+
entities.push({
|
|
147
|
+
type,
|
|
148
|
+
value,
|
|
149
|
+
confidence,
|
|
150
|
+
start: match.index,
|
|
151
|
+
end: match.index + match[0].length,
|
|
152
|
+
});
|
|
124
153
|
}
|
|
125
154
|
}
|
|
126
155
|
|
|
@@ -147,15 +176,31 @@ export class ContextEngine {
|
|
|
147
176
|
domain,
|
|
148
177
|
limit: this.config.vaultSearchLimit,
|
|
149
178
|
});
|
|
150
|
-
// Normalize FTS5 -rank scores to 0-1 range
|
|
179
|
+
// Normalize FTS5 -rank scores to 0-1 range, then apply multi-signal scoring
|
|
151
180
|
const maxScore = vaultResults.length > 0 ? Math.max(...vaultResults.map((r) => r.score)) : 1;
|
|
181
|
+
const promptTokens = new Set(
|
|
182
|
+
prompt
|
|
183
|
+
.toLowerCase()
|
|
184
|
+
.split(/\s+/)
|
|
185
|
+
.filter((t) => t.length >= 3),
|
|
186
|
+
);
|
|
152
187
|
for (const r of vaultResults) {
|
|
188
|
+
const baseScore = maxScore > 0 ? r.score / maxScore : 0.5;
|
|
189
|
+
const enrichedScore = scoreKnowledgeItem(
|
|
190
|
+
baseScore,
|
|
191
|
+
r.entry.title,
|
|
192
|
+
r.entry.tags,
|
|
193
|
+
promptTokens,
|
|
194
|
+
domain,
|
|
195
|
+
r.entry.domain,
|
|
196
|
+
);
|
|
153
197
|
items.push({
|
|
154
198
|
id: r.entry.id,
|
|
155
199
|
title: r.entry.title,
|
|
156
|
-
score:
|
|
200
|
+
score: enrichedScore,
|
|
157
201
|
source: 'vault',
|
|
158
202
|
domain: r.entry.domain,
|
|
203
|
+
tags: r.entry.tags,
|
|
159
204
|
});
|
|
160
205
|
vaultHits++;
|
|
161
206
|
}
|
|
@@ -252,25 +297,29 @@ export class ContextEngine {
|
|
|
252
297
|
): number {
|
|
253
298
|
let score = 0;
|
|
254
299
|
|
|
255
|
-
// Entity signal
|
|
300
|
+
// Entity signal: more entities = clearer prompt
|
|
256
301
|
const entityCount = entities.entities.length;
|
|
257
|
-
|
|
258
|
-
|
|
302
|
+
score += Math.min(
|
|
303
|
+
CONFIDENCE_WEIGHTS.entitySignalMax,
|
|
304
|
+
entityCount * CONFIDENCE_WEIGHTS.entitySignalPerEntity,
|
|
305
|
+
);
|
|
259
306
|
|
|
260
|
-
// Action signal
|
|
307
|
+
// Action signal: explicit actions boost confidence
|
|
261
308
|
const actions = entities.byType.action ?? [];
|
|
262
|
-
if (actions.length > 0) score +=
|
|
309
|
+
if (actions.length > 0) score += CONFIDENCE_WEIGHTS.actionSignal;
|
|
263
310
|
|
|
264
|
-
// Knowledge signal
|
|
311
|
+
// Knowledge signal: relevant knowledge found
|
|
265
312
|
if (knowledge.items.length > 0) {
|
|
266
|
-
|
|
267
|
-
score += topScore * 0.3;
|
|
313
|
+
score += knowledge.items[0].score * CONFIDENCE_WEIGHTS.knowledgeSignalMultiplier;
|
|
268
314
|
}
|
|
269
315
|
|
|
270
|
-
// Source diversity bonus
|
|
316
|
+
// Source diversity bonus: multiple sources = more confident
|
|
271
317
|
const sources = new Set(knowledge.items.map((i) => i.source));
|
|
272
|
-
|
|
273
|
-
|
|
318
|
+
const diversityBonus = Math.min(
|
|
319
|
+
CONFIDENCE_WEIGHTS.sourceDiversityMax,
|
|
320
|
+
Math.max(0, sources.size - 1) * CONFIDENCE_WEIGHTS.sourceDiversityPerExtra,
|
|
321
|
+
);
|
|
322
|
+
score += diversityBonus;
|
|
274
323
|
|
|
275
324
|
return Math.min(1, score);
|
|
276
325
|
}
|
|
@@ -300,3 +349,53 @@ export class ContextEngine {
|
|
|
300
349
|
return [...domains];
|
|
301
350
|
}
|
|
302
351
|
}
|
|
352
|
+
|
|
353
|
+
// ─── Multi-Signal Knowledge Scoring ─────────────────────────────────
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Score a knowledge item using multiple signals:
|
|
357
|
+
* - baseScore: FTS5 rank or vector similarity (0-1)
|
|
358
|
+
* - titleMatch: keyword overlap between prompt and entry title
|
|
359
|
+
* - tagOverlap: how many prompt tokens appear in entry tags
|
|
360
|
+
* - intentBoost: domain alignment between query domain and entry domain
|
|
361
|
+
*/
|
|
362
|
+
function scoreKnowledgeItem(
|
|
363
|
+
baseScore: number,
|
|
364
|
+
title: string,
|
|
365
|
+
tags: string[],
|
|
366
|
+
promptTokens: Set<string>,
|
|
367
|
+
queryDomain: string | undefined,
|
|
368
|
+
entryDomain: string | undefined,
|
|
369
|
+
): number {
|
|
370
|
+
// Title match: fraction of prompt tokens found in title
|
|
371
|
+
const titleTokens = new Set(
|
|
372
|
+
title
|
|
373
|
+
.toLowerCase()
|
|
374
|
+
.split(/\s+/)
|
|
375
|
+
.filter((t) => t.length >= 3),
|
|
376
|
+
);
|
|
377
|
+
let titleOverlap = 0;
|
|
378
|
+
for (const t of promptTokens) {
|
|
379
|
+
if (titleTokens.has(t)) titleOverlap++;
|
|
380
|
+
}
|
|
381
|
+
const titleMatch = promptTokens.size > 0 ? titleOverlap / promptTokens.size : 0;
|
|
382
|
+
|
|
383
|
+
// Tag overlap: fraction of tags that match prompt tokens
|
|
384
|
+
const lowerTags = tags.map((t) => t.toLowerCase());
|
|
385
|
+
let tagHits = 0;
|
|
386
|
+
for (const tag of lowerTags) {
|
|
387
|
+
if (promptTokens.has(tag)) tagHits++;
|
|
388
|
+
}
|
|
389
|
+
const tagOverlap = lowerTags.length > 0 ? tagHits / lowerTags.length : 0;
|
|
390
|
+
|
|
391
|
+
// Intent boost: 1.0 if domains match, 0.0 otherwise
|
|
392
|
+
const intentBoost = queryDomain && entryDomain && queryDomain === entryDomain ? 1.0 : 0.0;
|
|
393
|
+
|
|
394
|
+
return Math.min(
|
|
395
|
+
1.0,
|
|
396
|
+
baseScore * KNOWLEDGE_WEIGHTS.baseScore +
|
|
397
|
+
titleMatch * KNOWLEDGE_WEIGHTS.titleMatch +
|
|
398
|
+
tagOverlap * KNOWLEDGE_WEIGHTS.tagOverlap +
|
|
399
|
+
intentBoost * KNOWLEDGE_WEIGHTS.intentBoost,
|
|
400
|
+
);
|
|
401
|
+
}
|
package/src/context/types.ts
CHANGED
|
@@ -10,6 +10,10 @@ export interface ExtractedEntity {
|
|
|
10
10
|
type: EntityType;
|
|
11
11
|
value: string;
|
|
12
12
|
confidence: number;
|
|
13
|
+
/** Start offset in the original prompt. */
|
|
14
|
+
start?: number;
|
|
15
|
+
/** End offset in the original prompt. */
|
|
16
|
+
end?: number;
|
|
13
17
|
}
|
|
14
18
|
|
|
15
19
|
export interface EntityExtractionResult {
|
|
@@ -25,6 +29,7 @@ export interface KnowledgeItem {
|
|
|
25
29
|
score: number;
|
|
26
30
|
source: 'vault' | 'cognee' | 'brain';
|
|
27
31
|
domain?: string;
|
|
32
|
+
tags?: string[];
|
|
28
33
|
}
|
|
29
34
|
|
|
30
35
|
export interface KnowledgeRetrievalResult {
|
|
@@ -16,6 +16,7 @@ import type {
|
|
|
16
16
|
IntentClassification,
|
|
17
17
|
ModeConfig,
|
|
18
18
|
MorphResult,
|
|
19
|
+
RoutingAccuracyReport,
|
|
19
20
|
} from './types.js';
|
|
20
21
|
|
|
21
22
|
// ─── Token Stemming ─────────────────────────────────────────────────
|
|
@@ -168,6 +169,16 @@ export class IntentRouter {
|
|
|
168
169
|
matched_keywords TEXT NOT NULL DEFAULT '[]',
|
|
169
170
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
170
171
|
);
|
|
172
|
+
|
|
173
|
+
CREATE TABLE IF NOT EXISTS routing_feedback (
|
|
174
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
175
|
+
routing_log_id INTEGER,
|
|
176
|
+
initial_intent TEXT NOT NULL,
|
|
177
|
+
actual_intent TEXT NOT NULL,
|
|
178
|
+
correction INTEGER NOT NULL DEFAULT 0,
|
|
179
|
+
confidence REAL NOT NULL,
|
|
180
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
181
|
+
);
|
|
171
182
|
`);
|
|
172
183
|
}
|
|
173
184
|
|
|
@@ -314,6 +325,102 @@ export class IntentRouter {
|
|
|
314
325
|
}
|
|
315
326
|
}
|
|
316
327
|
|
|
328
|
+
// ─── Routing Feedback ─────────────────────────────────────────────
|
|
329
|
+
|
|
330
|
+
recordRoutingFeedback(input: {
|
|
331
|
+
routingLogId?: number;
|
|
332
|
+
initialIntent: string;
|
|
333
|
+
actualIntent: string;
|
|
334
|
+
confidence: number;
|
|
335
|
+
correction: boolean;
|
|
336
|
+
}): { recorded: boolean; id: number } {
|
|
337
|
+
const result = this.provider.run(
|
|
338
|
+
`INSERT INTO routing_feedback (routing_log_id, initial_intent, actual_intent, correction, confidence)
|
|
339
|
+
VALUES (?, ?, ?, ?, ?)`,
|
|
340
|
+
[
|
|
341
|
+
input.routingLogId ?? null,
|
|
342
|
+
input.initialIntent,
|
|
343
|
+
input.actualIntent,
|
|
344
|
+
input.correction ? 1 : 0,
|
|
345
|
+
input.confidence,
|
|
346
|
+
],
|
|
347
|
+
);
|
|
348
|
+
return { recorded: true, id: Number(result.lastInsertRowid) };
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
getRoutingAccuracy(periodDays: number = 30): RoutingAccuracyReport {
|
|
352
|
+
const cutoff = new Date(Date.now() - periodDays * 86400000).toISOString();
|
|
353
|
+
|
|
354
|
+
const total = this.provider.get<{ count: number }>(
|
|
355
|
+
'SELECT COUNT(*) as count FROM routing_feedback WHERE created_at >= ?',
|
|
356
|
+
[cutoff],
|
|
357
|
+
)!.count;
|
|
358
|
+
|
|
359
|
+
const correct = this.provider.get<{ count: number }>(
|
|
360
|
+
'SELECT COUNT(*) as count FROM routing_feedback WHERE initial_intent = actual_intent AND created_at >= ?',
|
|
361
|
+
[cutoff],
|
|
362
|
+
)!.count;
|
|
363
|
+
|
|
364
|
+
const corrections = this.provider.get<{ count: number }>(
|
|
365
|
+
'SELECT COUNT(*) as count FROM routing_feedback WHERE correction = 1 AND created_at >= ?',
|
|
366
|
+
[cutoff],
|
|
367
|
+
)!.count;
|
|
368
|
+
|
|
369
|
+
// Common misroutes
|
|
370
|
+
const misrouteRows = this.provider.all<{
|
|
371
|
+
from_intent: string;
|
|
372
|
+
to_intent: string;
|
|
373
|
+
count: number;
|
|
374
|
+
}>(
|
|
375
|
+
`SELECT initial_intent as from_intent, actual_intent as to_intent, COUNT(*) as count
|
|
376
|
+
FROM routing_feedback
|
|
377
|
+
WHERE initial_intent != actual_intent AND created_at >= ?
|
|
378
|
+
GROUP BY initial_intent, actual_intent
|
|
379
|
+
ORDER BY count DESC
|
|
380
|
+
LIMIT 10`,
|
|
381
|
+
[cutoff],
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
// Confidence calibration: group by confidence bucket and check accuracy per bucket
|
|
385
|
+
const calibrationRows = this.provider.all<{ bucket: string; total: number; correct: number }>(
|
|
386
|
+
`SELECT
|
|
387
|
+
CASE
|
|
388
|
+
WHEN confidence >= 0.8 THEN 'high'
|
|
389
|
+
WHEN confidence >= 0.4 THEN 'medium'
|
|
390
|
+
ELSE 'low'
|
|
391
|
+
END as bucket,
|
|
392
|
+
COUNT(*) as total,
|
|
393
|
+
SUM(CASE WHEN initial_intent = actual_intent THEN 1 ELSE 0 END) as correct
|
|
394
|
+
FROM routing_feedback
|
|
395
|
+
WHERE created_at >= ?
|
|
396
|
+
GROUP BY bucket`,
|
|
397
|
+
[cutoff],
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
const calibration: Record<string, { total: number; correct: number; accuracy: number }> = {};
|
|
401
|
+
for (const row of calibrationRows) {
|
|
402
|
+
calibration[row.bucket] = {
|
|
403
|
+
total: row.total,
|
|
404
|
+
correct: row.correct,
|
|
405
|
+
accuracy: row.total > 0 ? Math.round((row.correct / row.total) * 100) : 0,
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return {
|
|
410
|
+
periodDays,
|
|
411
|
+
total,
|
|
412
|
+
correct,
|
|
413
|
+
accuracy: total > 0 ? Math.round((correct / total) * 100) : 100,
|
|
414
|
+
corrections,
|
|
415
|
+
commonMisroutes: misrouteRows.map((r) => ({
|
|
416
|
+
from: r.from_intent,
|
|
417
|
+
to: r.to_intent,
|
|
418
|
+
count: r.count,
|
|
419
|
+
})),
|
|
420
|
+
confidenceCalibration: calibration,
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
|
|
317
424
|
// ─── Analytics ──────────────────────────────────────────────────────
|
|
318
425
|
|
|
319
426
|
getRoutingStats(): {
|
package/src/control/types.ts
CHANGED
|
@@ -100,3 +100,13 @@ export interface MorphResult {
|
|
|
100
100
|
currentMode: OperationalMode;
|
|
101
101
|
behaviorRules: string[];
|
|
102
102
|
}
|
|
103
|
+
|
|
104
|
+
export interface RoutingAccuracyReport {
|
|
105
|
+
periodDays: number;
|
|
106
|
+
total: number;
|
|
107
|
+
correct: number;
|
|
108
|
+
accuracy: number;
|
|
109
|
+
corrections: number;
|
|
110
|
+
commonMisroutes: Array<{ from: string; to: string; count: number }>;
|
|
111
|
+
confidenceCalibration: Record<string, { total: number; correct: number; accuracy: number }>;
|
|
112
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Classifier — LLM-based auto-categorization for vault entries.
|
|
3
|
+
*
|
|
4
|
+
* Suggests category, severity, and additional tags based on entry content.
|
|
5
|
+
* Graceful degradation: returns empty suggestions when LLM is unavailable.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { LLMClient } from '../llm/llm-client.js';
|
|
9
|
+
import type { IntelligenceEntry } from '../intelligence/types.js';
|
|
10
|
+
|
|
11
|
+
// ─── Types ───────────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
export interface ClassificationResult {
|
|
14
|
+
classified: boolean;
|
|
15
|
+
suggestedDomain: string | null;
|
|
16
|
+
suggestedSeverity: string | null;
|
|
17
|
+
suggestedTags: string[];
|
|
18
|
+
confidence: number;
|
|
19
|
+
error?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ─── Classify ────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
const CLASSIFY_PROMPT = `Classify this knowledge entry. Suggest the best domain, severity, and 3-5 tags.
|
|
25
|
+
|
|
26
|
+
**Title:** {title}
|
|
27
|
+
**Type:** {type}
|
|
28
|
+
**Current tags:** {tags}
|
|
29
|
+
**Description:** {description}
|
|
30
|
+
|
|
31
|
+
Respond with ONLY this JSON (no markdown fences):
|
|
32
|
+
{"domain":"string","severity":"critical|warning|suggestion","tags":["tag1","tag2","tag3"],"confidence":0.0-1.0}
|
|
33
|
+
|
|
34
|
+
Rules:
|
|
35
|
+
- domain: a single word (e.g., architecture, testing, security, design, performance, accessibility)
|
|
36
|
+
- severity: critical = must-know, warning = important, suggestion = nice-to-know
|
|
37
|
+
- tags: lowercase, hyphenated, specific and useful for search
|
|
38
|
+
- confidence: how sure you are about this classification (0.0-1.0)`;
|
|
39
|
+
|
|
40
|
+
export async function classifyEntry(
|
|
41
|
+
entry: IntelligenceEntry,
|
|
42
|
+
llm: LLMClient | null,
|
|
43
|
+
): Promise<ClassificationResult> {
|
|
44
|
+
const fallback: ClassificationResult = {
|
|
45
|
+
classified: false,
|
|
46
|
+
suggestedDomain: null,
|
|
47
|
+
suggestedSeverity: null,
|
|
48
|
+
suggestedTags: [],
|
|
49
|
+
confidence: 0,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
if (!llm) return fallback;
|
|
53
|
+
|
|
54
|
+
const prompt = CLASSIFY_PROMPT.replace('{title}', entry.title)
|
|
55
|
+
.replace('{type}', entry.type)
|
|
56
|
+
.replace('{tags}', entry.tags.join(', ') || 'none')
|
|
57
|
+
.replace('{description}', entry.description);
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
const result = await llm.complete({
|
|
61
|
+
provider: 'openai',
|
|
62
|
+
model: 'gpt-4o-mini',
|
|
63
|
+
systemPrompt: 'You are a knowledge classifier. Respond only with JSON.',
|
|
64
|
+
userPrompt: prompt,
|
|
65
|
+
temperature: 0.1,
|
|
66
|
+
maxTokens: 300,
|
|
67
|
+
caller: 'classifier',
|
|
68
|
+
task: 'classify',
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const parsed = JSON.parse(result.text) as {
|
|
72
|
+
domain: string;
|
|
73
|
+
severity: string;
|
|
74
|
+
tags: string[];
|
|
75
|
+
confidence: number;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
classified: true,
|
|
80
|
+
suggestedDomain: parsed.domain ?? null,
|
|
81
|
+
suggestedSeverity: parsed.severity ?? null,
|
|
82
|
+
suggestedTags: parsed.tags ?? [],
|
|
83
|
+
confidence: parsed.confidence ?? 0.5,
|
|
84
|
+
};
|
|
85
|
+
} catch (err) {
|
|
86
|
+
return { ...fallback, error: (err as Error).message };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quality Gate — LLM-based entry quality evaluation.
|
|
3
|
+
*
|
|
4
|
+
* Evaluates vault entries on 5 criteria (novelty, actionability, specificity,
|
|
5
|
+
* relevance, informationDensity). Entries scoring below threshold are rejected.
|
|
6
|
+
*
|
|
7
|
+
* Graceful degradation: returns ACCEPT when LLM is unavailable.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { LLMClient } from '../llm/llm-client.js';
|
|
11
|
+
import type { IntelligenceEntry } from '../intelligence/types.js';
|
|
12
|
+
|
|
13
|
+
// ─── Types ───────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
export type QualityVerdict = 'ACCEPT' | 'REJECT';
|
|
16
|
+
|
|
17
|
+
export interface QualityScores {
|
|
18
|
+
novelty: number;
|
|
19
|
+
actionability: number;
|
|
20
|
+
specificity: number;
|
|
21
|
+
relevance: number;
|
|
22
|
+
informationDensity: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface QualityResult {
|
|
26
|
+
evaluated: boolean;
|
|
27
|
+
verdict: QualityVerdict;
|
|
28
|
+
overallScore: number;
|
|
29
|
+
scores: QualityScores;
|
|
30
|
+
reasoning: string;
|
|
31
|
+
rejectReasons?: string[];
|
|
32
|
+
error?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ─── Constants ───────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
const REJECT_THRESHOLD = 50;
|
|
38
|
+
const CRITICAL_LOW = 15;
|
|
39
|
+
|
|
40
|
+
const QUALITY_PROMPT = `You are a strict knowledge base curator deciding whether an entry deserves to be in a high-quality vault.
|
|
41
|
+
|
|
42
|
+
Your job is to REJECT junk and keep the vault clean. Be ruthless but fair.
|
|
43
|
+
|
|
44
|
+
## Entry Under Review
|
|
45
|
+
- **Type:** {type}
|
|
46
|
+
- **Title:** {title}
|
|
47
|
+
- **Tags:** {tags}
|
|
48
|
+
- **Description:** {description}
|
|
49
|
+
{why}{example}{context}
|
|
50
|
+
|
|
51
|
+
## Scoring Criteria (each 0-100)
|
|
52
|
+
|
|
53
|
+
1. **novelty** — Is this genuinely new knowledge or a truism? ("Write clean code" = 5, "Use driver adapter pattern to avoid Prisma lock-in" = 90)
|
|
54
|
+
2. **actionability** — Can someone act on this? Vague advice = low, specific do/don't = high
|
|
55
|
+
3. **specificity** — Is this specific to a real context or generic fluff?
|
|
56
|
+
4. **relevance** — Does this belong in a technical knowledge vault?
|
|
57
|
+
5. **informationDensity** — Is there real substance or mostly filler?
|
|
58
|
+
|
|
59
|
+
## Verdict Rules
|
|
60
|
+
- Overall score = average of all 5 criteria
|
|
61
|
+
- **REJECT** if overall < ${REJECT_THRESHOLD}, or if ANY criterion scores <= ${CRITICAL_LOW}
|
|
62
|
+
- **ACCEPT** otherwise
|
|
63
|
+
- When in doubt, REJECT.
|
|
64
|
+
|
|
65
|
+
Respond with ONLY this JSON (no markdown fences):
|
|
66
|
+
{"verdict":"ACCEPT or REJECT","overallScore":0-100,"scores":{"novelty":N,"actionability":N,"specificity":N,"relevance":N,"informationDensity":N},"reasoning":"2-3 sentences","rejectReasons":["reason1"]}`;
|
|
67
|
+
|
|
68
|
+
// ─── Evaluate ────────────────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
export async function evaluateQuality(
|
|
71
|
+
entry: IntelligenceEntry,
|
|
72
|
+
llm: LLMClient | null,
|
|
73
|
+
): Promise<QualityResult> {
|
|
74
|
+
const fallback: QualityResult = {
|
|
75
|
+
evaluated: false,
|
|
76
|
+
verdict: 'ACCEPT',
|
|
77
|
+
overallScore: 50,
|
|
78
|
+
scores: {
|
|
79
|
+
novelty: 50,
|
|
80
|
+
actionability: 50,
|
|
81
|
+
specificity: 50,
|
|
82
|
+
relevance: 50,
|
|
83
|
+
informationDensity: 50,
|
|
84
|
+
},
|
|
85
|
+
reasoning: 'LLM unavailable — defaulting to accept',
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
if (!llm) return fallback;
|
|
89
|
+
|
|
90
|
+
const prompt = QUALITY_PROMPT.replace('{type}', entry.type)
|
|
91
|
+
.replace('{title}', entry.title)
|
|
92
|
+
.replace('{tags}', entry.tags.join(', ') || 'none')
|
|
93
|
+
.replace('{description}', entry.description)
|
|
94
|
+
.replace('{why}', entry.why ? `- **Why:** ${entry.why}\n` : '')
|
|
95
|
+
.replace('{example}', entry.example ? `- **Example:** ${entry.example}\n` : '')
|
|
96
|
+
.replace('{context}', entry.context ? `- **Context:** ${entry.context}\n` : '');
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
const result = await llm.complete({
|
|
100
|
+
provider: 'openai',
|
|
101
|
+
model: 'gpt-4o-mini',
|
|
102
|
+
systemPrompt: 'You are a knowledge quality evaluator. Respond only with JSON.',
|
|
103
|
+
userPrompt: prompt,
|
|
104
|
+
temperature: 0.1,
|
|
105
|
+
maxTokens: 500,
|
|
106
|
+
caller: 'quality-gate',
|
|
107
|
+
task: 'evaluate',
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const parsed = JSON.parse(result.text) as {
|
|
111
|
+
verdict: string;
|
|
112
|
+
overallScore: number;
|
|
113
|
+
scores: QualityScores;
|
|
114
|
+
reasoning: string;
|
|
115
|
+
rejectReasons?: string[];
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
evaluated: true,
|
|
120
|
+
verdict: (parsed.verdict === 'REJECT' ? 'REJECT' : 'ACCEPT') as QualityVerdict,
|
|
121
|
+
overallScore: parsed.overallScore ?? 50,
|
|
122
|
+
scores: parsed.scores ?? fallback.scores,
|
|
123
|
+
reasoning: parsed.reasoning ?? '',
|
|
124
|
+
rejectReasons: parsed.rejectReasons,
|
|
125
|
+
};
|
|
126
|
+
} catch (err) {
|
|
127
|
+
return { ...fallback, error: (err as Error).message };
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed Event Bus — generic event emitter for decoupled subsystem communication.
|
|
3
|
+
*
|
|
4
|
+
* Wraps Node EventEmitter with type-safe emit/subscribe.
|
|
5
|
+
* Any module can define its own event map and create a bus.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { EventEmitter } from 'node:events';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Create a typed event bus.
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* ```ts
|
|
15
|
+
* type MyEvents = {
|
|
16
|
+
* 'entry:created': { id: string; title: string };
|
|
17
|
+
* 'entry:deleted': { id: string };
|
|
18
|
+
* };
|
|
19
|
+
* const bus = new TypedEventBus<MyEvents>();
|
|
20
|
+
* bus.on('entry:created', (payload) => console.log(payload.title));
|
|
21
|
+
* bus.emit('entry:created', { id: '1', title: 'Hello' });
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export class TypedEventBus<TEvents extends Record<string, unknown>> {
|
|
25
|
+
private emitter = new EventEmitter();
|
|
26
|
+
|
|
27
|
+
on<E extends keyof TEvents & string>(event: E, listener: (payload: TEvents[E]) => void): this {
|
|
28
|
+
this.emitter.on(event, listener as (...args: unknown[]) => void);
|
|
29
|
+
return this;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
once<E extends keyof TEvents & string>(event: E, listener: (payload: TEvents[E]) => void): this {
|
|
33
|
+
this.emitter.once(event, listener as (...args: unknown[]) => void);
|
|
34
|
+
return this;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
off<E extends keyof TEvents & string>(event: E, listener: (payload: TEvents[E]) => void): this {
|
|
38
|
+
this.emitter.off(event, listener as (...args: unknown[]) => void);
|
|
39
|
+
return this;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
emit<E extends keyof TEvents & string>(event: E, payload: TEvents[E]): boolean {
|
|
43
|
+
return this.emitter.emit(event, payload);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
listenerCount(): number {
|
|
47
|
+
let total = 0;
|
|
48
|
+
for (const name of this.emitter.eventNames()) {
|
|
49
|
+
total += this.emitter.listenerCount(name);
|
|
50
|
+
}
|
|
51
|
+
return total;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
removeAllListeners(): this {
|
|
55
|
+
this.emitter.removeAllListeners();
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
58
|
+
}
|