@soleri/core 2.0.2 → 2.4.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/brain/brain.d.ts +14 -50
- package/dist/brain/brain.d.ts.map +1 -1
- package/dist/brain/brain.js +207 -16
- package/dist/brain/brain.js.map +1 -1
- package/dist/brain/intelligence.d.ts +86 -0
- package/dist/brain/intelligence.d.ts.map +1 -0
- package/dist/brain/intelligence.js +771 -0
- package/dist/brain/intelligence.js.map +1 -0
- package/dist/brain/types.d.ts +197 -0
- package/dist/brain/types.d.ts.map +1 -0
- package/dist/brain/types.js +2 -0
- package/dist/brain/types.js.map +1 -0
- package/dist/cognee/client.d.ts +35 -0
- package/dist/cognee/client.d.ts.map +1 -0
- package/dist/cognee/client.js +291 -0
- package/dist/cognee/client.js.map +1 -0
- package/dist/cognee/types.d.ts +46 -0
- package/dist/cognee/types.d.ts.map +1 -0
- package/dist/cognee/types.js +3 -0
- package/dist/cognee/types.js.map +1 -0
- package/dist/control/identity-manager.d.ts +22 -0
- package/dist/control/identity-manager.d.ts.map +1 -0
- package/dist/control/identity-manager.js +233 -0
- package/dist/control/identity-manager.js.map +1 -0
- package/dist/control/intent-router.d.ts +32 -0
- package/dist/control/intent-router.d.ts.map +1 -0
- package/dist/control/intent-router.js +242 -0
- package/dist/control/intent-router.js.map +1 -0
- package/dist/control/types.d.ts +68 -0
- package/dist/control/types.d.ts.map +1 -0
- package/dist/control/types.js +9 -0
- package/dist/control/types.js.map +1 -0
- package/dist/curator/curator.d.ts +29 -0
- package/dist/curator/curator.d.ts.map +1 -1
- package/dist/curator/curator.js +142 -5
- package/dist/curator/curator.js.map +1 -1
- package/dist/facades/types.d.ts +1 -1
- package/dist/governance/governance.d.ts +42 -0
- package/dist/governance/governance.d.ts.map +1 -0
- package/dist/governance/governance.js +488 -0
- package/dist/governance/governance.js.map +1 -0
- package/dist/governance/index.d.ts +3 -0
- package/dist/governance/index.d.ts.map +1 -0
- package/dist/governance/index.js +2 -0
- package/dist/governance/index.js.map +1 -0
- package/dist/governance/types.d.ts +102 -0
- package/dist/governance/types.d.ts.map +1 -0
- package/dist/governance/types.js +3 -0
- package/dist/governance/types.js.map +1 -0
- package/dist/index.d.ts +35 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +32 -1
- package/dist/index.js.map +1 -1
- package/dist/llm/llm-client.d.ts.map +1 -1
- package/dist/llm/llm-client.js +9 -2
- package/dist/llm/llm-client.js.map +1 -1
- package/dist/logging/logger.d.ts +37 -0
- package/dist/logging/logger.d.ts.map +1 -0
- package/dist/logging/logger.js +145 -0
- package/dist/logging/logger.js.map +1 -0
- package/dist/logging/types.d.ts +19 -0
- package/dist/logging/types.d.ts.map +1 -0
- package/dist/logging/types.js +2 -0
- package/dist/logging/types.js.map +1 -0
- package/dist/loop/loop-manager.d.ts +49 -0
- package/dist/loop/loop-manager.d.ts.map +1 -0
- package/dist/loop/loop-manager.js +105 -0
- package/dist/loop/loop-manager.js.map +1 -0
- package/dist/loop/types.d.ts +35 -0
- package/dist/loop/types.d.ts.map +1 -0
- package/dist/loop/types.js +8 -0
- package/dist/loop/types.js.map +1 -0
- package/dist/planning/gap-analysis.d.ts +29 -0
- package/dist/planning/gap-analysis.d.ts.map +1 -0
- package/dist/planning/gap-analysis.js +265 -0
- package/dist/planning/gap-analysis.js.map +1 -0
- package/dist/planning/gap-types.d.ts +29 -0
- package/dist/planning/gap-types.d.ts.map +1 -0
- package/dist/planning/gap-types.js +28 -0
- package/dist/planning/gap-types.js.map +1 -0
- package/dist/planning/planner.d.ts +150 -1
- package/dist/planning/planner.d.ts.map +1 -1
- package/dist/planning/planner.js +365 -2
- package/dist/planning/planner.js.map +1 -1
- package/dist/project/project-registry.d.ts +79 -0
- package/dist/project/project-registry.d.ts.map +1 -0
- package/dist/project/project-registry.js +276 -0
- package/dist/project/project-registry.js.map +1 -0
- package/dist/project/types.d.ts +28 -0
- package/dist/project/types.d.ts.map +1 -0
- package/dist/project/types.js +5 -0
- package/dist/project/types.js.map +1 -0
- package/dist/runtime/admin-extra-ops.d.ts +13 -0
- package/dist/runtime/admin-extra-ops.d.ts.map +1 -0
- package/dist/runtime/admin-extra-ops.js +284 -0
- package/dist/runtime/admin-extra-ops.js.map +1 -0
- package/dist/runtime/admin-ops.d.ts +15 -0
- package/dist/runtime/admin-ops.d.ts.map +1 -0
- package/dist/runtime/admin-ops.js +322 -0
- package/dist/runtime/admin-ops.js.map +1 -0
- package/dist/runtime/capture-ops.d.ts +15 -0
- package/dist/runtime/capture-ops.d.ts.map +1 -0
- package/dist/runtime/capture-ops.js +345 -0
- package/dist/runtime/capture-ops.js.map +1 -0
- package/dist/runtime/core-ops.d.ts +7 -3
- package/dist/runtime/core-ops.d.ts.map +1 -1
- package/dist/runtime/core-ops.js +646 -15
- package/dist/runtime/core-ops.js.map +1 -1
- package/dist/runtime/curator-extra-ops.d.ts +9 -0
- package/dist/runtime/curator-extra-ops.d.ts.map +1 -0
- package/dist/runtime/curator-extra-ops.js +59 -0
- package/dist/runtime/curator-extra-ops.js.map +1 -0
- package/dist/runtime/domain-ops.d.ts.map +1 -1
- package/dist/runtime/domain-ops.js +59 -13
- package/dist/runtime/domain-ops.js.map +1 -1
- package/dist/runtime/grading-ops.d.ts +14 -0
- package/dist/runtime/grading-ops.d.ts.map +1 -0
- package/dist/runtime/grading-ops.js +105 -0
- package/dist/runtime/grading-ops.js.map +1 -0
- package/dist/runtime/loop-ops.d.ts +13 -0
- package/dist/runtime/loop-ops.d.ts.map +1 -0
- package/dist/runtime/loop-ops.js +179 -0
- package/dist/runtime/loop-ops.js.map +1 -0
- package/dist/runtime/memory-cross-project-ops.d.ts +12 -0
- package/dist/runtime/memory-cross-project-ops.d.ts.map +1 -0
- package/dist/runtime/memory-cross-project-ops.js +165 -0
- package/dist/runtime/memory-cross-project-ops.js.map +1 -0
- package/dist/runtime/memory-extra-ops.d.ts +13 -0
- package/dist/runtime/memory-extra-ops.d.ts.map +1 -0
- package/dist/runtime/memory-extra-ops.js +173 -0
- package/dist/runtime/memory-extra-ops.js.map +1 -0
- package/dist/runtime/orchestrate-ops.d.ts +17 -0
- package/dist/runtime/orchestrate-ops.d.ts.map +1 -0
- package/dist/runtime/orchestrate-ops.js +240 -0
- package/dist/runtime/orchestrate-ops.js.map +1 -0
- package/dist/runtime/planning-extra-ops.d.ts +17 -0
- package/dist/runtime/planning-extra-ops.d.ts.map +1 -0
- package/dist/runtime/planning-extra-ops.js +300 -0
- package/dist/runtime/planning-extra-ops.js.map +1 -0
- package/dist/runtime/project-ops.d.ts +15 -0
- package/dist/runtime/project-ops.d.ts.map +1 -0
- package/dist/runtime/project-ops.js +181 -0
- package/dist/runtime/project-ops.js.map +1 -0
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +48 -1
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/types.d.ts +23 -0
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/runtime/vault-extra-ops.d.ts +9 -0
- package/dist/runtime/vault-extra-ops.d.ts.map +1 -0
- package/dist/runtime/vault-extra-ops.js +195 -0
- package/dist/runtime/vault-extra-ops.js.map +1 -0
- package/dist/telemetry/telemetry.d.ts +48 -0
- package/dist/telemetry/telemetry.d.ts.map +1 -0
- package/dist/telemetry/telemetry.js +87 -0
- package/dist/telemetry/telemetry.js.map +1 -0
- package/dist/vault/vault.d.ts +94 -0
- package/dist/vault/vault.d.ts.map +1 -1
- package/dist/vault/vault.js +340 -1
- package/dist/vault/vault.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/admin-extra-ops.test.ts +420 -0
- package/src/__tests__/admin-ops.test.ts +271 -0
- package/src/__tests__/brain-intelligence.test.ts +828 -0
- package/src/__tests__/brain.test.ts +396 -27
- package/src/__tests__/capture-ops.test.ts +509 -0
- package/src/__tests__/cognee-client.test.ts +524 -0
- package/src/__tests__/core-ops.test.ts +341 -49
- package/src/__tests__/curator-extra-ops.test.ts +359 -0
- package/src/__tests__/curator.test.ts +126 -31
- package/src/__tests__/domain-ops.test.ts +111 -9
- package/src/__tests__/governance.test.ts +522 -0
- package/src/__tests__/grading-ops.test.ts +340 -0
- package/src/__tests__/identity-manager.test.ts +243 -0
- package/src/__tests__/intent-router.test.ts +222 -0
- package/src/__tests__/logger.test.ts +200 -0
- package/src/__tests__/loop-ops.test.ts +398 -0
- package/src/__tests__/memory-cross-project-ops.test.ts +246 -0
- package/src/__tests__/memory-extra-ops.test.ts +352 -0
- package/src/__tests__/orchestrate-ops.test.ts +284 -0
- package/src/__tests__/planner.test.ts +331 -0
- package/src/__tests__/planning-extra-ops.test.ts +548 -0
- package/src/__tests__/project-ops.test.ts +367 -0
- package/src/__tests__/runtime.test.ts +13 -11
- package/src/__tests__/vault-extra-ops.test.ts +407 -0
- package/src/brain/brain.ts +308 -72
- package/src/brain/intelligence.ts +1230 -0
- package/src/brain/types.ts +214 -0
- package/src/cognee/client.ts +352 -0
- package/src/cognee/types.ts +62 -0
- package/src/control/identity-manager.ts +354 -0
- package/src/control/intent-router.ts +326 -0
- package/src/control/types.ts +102 -0
- package/src/curator/curator.ts +265 -15
- package/src/governance/governance.ts +698 -0
- package/src/governance/index.ts +18 -0
- package/src/governance/types.ts +111 -0
- package/src/index.ts +128 -3
- package/src/llm/llm-client.ts +18 -24
- package/src/logging/logger.ts +154 -0
- package/src/logging/types.ts +21 -0
- package/src/loop/loop-manager.ts +130 -0
- package/src/loop/types.ts +44 -0
- package/src/planning/gap-analysis.ts +506 -0
- package/src/planning/gap-types.ts +58 -0
- package/src/planning/planner.ts +478 -2
- package/src/project/project-registry.ts +358 -0
- package/src/project/types.ts +31 -0
- package/src/runtime/admin-extra-ops.ts +307 -0
- package/src/runtime/admin-ops.ts +329 -0
- package/src/runtime/capture-ops.ts +385 -0
- package/src/runtime/core-ops.ts +747 -26
- package/src/runtime/curator-extra-ops.ts +71 -0
- package/src/runtime/domain-ops.ts +65 -13
- package/src/runtime/grading-ops.ts +121 -0
- package/src/runtime/loop-ops.ts +194 -0
- package/src/runtime/memory-cross-project-ops.ts +192 -0
- package/src/runtime/memory-extra-ops.ts +186 -0
- package/src/runtime/orchestrate-ops.ts +272 -0
- package/src/runtime/planning-extra-ops.ts +327 -0
- package/src/runtime/project-ops.ts +196 -0
- package/src/runtime/runtime.ts +54 -1
- package/src/runtime/types.ts +23 -0
- package/src/runtime/vault-extra-ops.ts +225 -0
- package/src/telemetry/telemetry.ts +118 -0
- package/src/vault/vault.ts +412 -1
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for Identity Management and Intent Routing.
|
|
3
|
+
*
|
|
4
|
+
* Two separate concerns:
|
|
5
|
+
* - Identity: agent persona CRUD with versioning and guidelines
|
|
6
|
+
* - Intent Routing: keyword-based intent classification and operational modes
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// ─── Identity Types ──────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
export type GuidelineCategory = 'behavior' | 'preference' | 'restriction' | 'style';
|
|
12
|
+
|
|
13
|
+
export interface Guideline {
|
|
14
|
+
id: string;
|
|
15
|
+
category: GuidelineCategory;
|
|
16
|
+
text: string;
|
|
17
|
+
priority: number;
|
|
18
|
+
createdAt: string;
|
|
19
|
+
updatedAt: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface AgentIdentity {
|
|
23
|
+
agentId: string;
|
|
24
|
+
name: string;
|
|
25
|
+
role: string;
|
|
26
|
+
description: string;
|
|
27
|
+
personality: string[];
|
|
28
|
+
guidelines: Guideline[];
|
|
29
|
+
version: number;
|
|
30
|
+
updatedAt: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface IdentityVersion {
|
|
34
|
+
version: number;
|
|
35
|
+
snapshot: string;
|
|
36
|
+
changedBy: string;
|
|
37
|
+
changeReason: string;
|
|
38
|
+
createdAt: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface IdentityUpdateInput {
|
|
42
|
+
name?: string;
|
|
43
|
+
role?: string;
|
|
44
|
+
description?: string;
|
|
45
|
+
personality?: string[];
|
|
46
|
+
changedBy?: string;
|
|
47
|
+
changeReason?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface GuidelineInput {
|
|
51
|
+
category: GuidelineCategory;
|
|
52
|
+
text: string;
|
|
53
|
+
priority?: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ─── Intent Routing Types ────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
export type IntentType =
|
|
59
|
+
| 'build'
|
|
60
|
+
| 'fix'
|
|
61
|
+
| 'validate'
|
|
62
|
+
| 'design'
|
|
63
|
+
| 'improve'
|
|
64
|
+
| 'deliver'
|
|
65
|
+
| 'explore'
|
|
66
|
+
| 'plan'
|
|
67
|
+
| 'review'
|
|
68
|
+
| 'general';
|
|
69
|
+
|
|
70
|
+
export type OperationalMode =
|
|
71
|
+
| 'BUILD-MODE'
|
|
72
|
+
| 'FIX-MODE'
|
|
73
|
+
| 'VALIDATE-MODE'
|
|
74
|
+
| 'DESIGN-MODE'
|
|
75
|
+
| 'IMPROVE-MODE'
|
|
76
|
+
| 'DELIVER-MODE'
|
|
77
|
+
| 'EXPLORE-MODE'
|
|
78
|
+
| 'PLAN-MODE'
|
|
79
|
+
| 'REVIEW-MODE'
|
|
80
|
+
| 'GENERAL-MODE';
|
|
81
|
+
|
|
82
|
+
export interface IntentClassification {
|
|
83
|
+
intent: IntentType;
|
|
84
|
+
mode: OperationalMode;
|
|
85
|
+
confidence: number;
|
|
86
|
+
method: 'keyword';
|
|
87
|
+
matchedKeywords: string[];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface ModeConfig {
|
|
91
|
+
mode: OperationalMode;
|
|
92
|
+
intent: IntentType;
|
|
93
|
+
description: string;
|
|
94
|
+
behaviorRules: string[];
|
|
95
|
+
keywords: string[];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface MorphResult {
|
|
99
|
+
previousMode: OperationalMode;
|
|
100
|
+
currentMode: OperationalMode;
|
|
101
|
+
behaviorRules: string[];
|
|
102
|
+
}
|
package/src/curator/curator.ts
CHANGED
|
@@ -93,6 +93,15 @@ export class Curator {
|
|
|
93
93
|
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
94
94
|
);
|
|
95
95
|
|
|
96
|
+
CREATE TABLE IF NOT EXISTS curator_entry_history (
|
|
97
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
98
|
+
entry_id TEXT NOT NULL,
|
|
99
|
+
snapshot TEXT NOT NULL,
|
|
100
|
+
changed_by TEXT DEFAULT 'system',
|
|
101
|
+
change_reason TEXT,
|
|
102
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
103
|
+
);
|
|
104
|
+
|
|
96
105
|
CREATE TABLE IF NOT EXISTS curator_contradictions (
|
|
97
106
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
98
107
|
pattern_id TEXT NOT NULL,
|
|
@@ -188,7 +197,13 @@ export class Curator {
|
|
|
188
197
|
JSON.stringify(dedupedTags),
|
|
189
198
|
entryId,
|
|
190
199
|
);
|
|
191
|
-
this.logChange(
|
|
200
|
+
this.logChange(
|
|
201
|
+
'normalize_tags',
|
|
202
|
+
entryId,
|
|
203
|
+
JSON.stringify(entry.tags),
|
|
204
|
+
JSON.stringify(dedupedTags),
|
|
205
|
+
'Tag normalization',
|
|
206
|
+
);
|
|
192
207
|
}
|
|
193
208
|
|
|
194
209
|
return results;
|
|
@@ -245,13 +260,13 @@ export class Curator {
|
|
|
245
260
|
// Build vectors for all entries
|
|
246
261
|
const vectors = new Map<string, SparseVector>();
|
|
247
262
|
for (const entry of entries) {
|
|
248
|
-
const text = [entry.title, entry.description, entry.context ?? '', entry.tags.join(' ')].join(
|
|
263
|
+
const text = [entry.title, entry.description, entry.context ?? '', entry.tags.join(' ')].join(
|
|
264
|
+
' ',
|
|
265
|
+
);
|
|
249
266
|
vectors.set(entry.id, calculateTfIdf(tokenize(text), vocabulary));
|
|
250
267
|
}
|
|
251
268
|
|
|
252
|
-
const targetEntries = entryId
|
|
253
|
-
? entries.filter((e) => e.id === entryId)
|
|
254
|
-
: entries;
|
|
269
|
+
const targetEntries = entryId ? entries.filter((e) => e.id === entryId) : entries;
|
|
255
270
|
|
|
256
271
|
const results: DuplicateDetectionResult[] = [];
|
|
257
272
|
|
|
@@ -428,7 +443,8 @@ export class Curator {
|
|
|
428
443
|
const dryRun = options?.dryRun ?? true;
|
|
429
444
|
const staleDaysThreshold = options?.staleDaysThreshold ?? DEFAULT_STALE_DAYS;
|
|
430
445
|
const duplicateThreshold = options?.duplicateThreshold ?? DEFAULT_DUPLICATE_THRESHOLD;
|
|
431
|
-
const contradictionThreshold =
|
|
446
|
+
const contradictionThreshold =
|
|
447
|
+
options?.contradictionThreshold ?? DEFAULT_CONTRADICTION_THRESHOLD;
|
|
432
448
|
|
|
433
449
|
// Detect duplicates
|
|
434
450
|
const duplicates = this.detectDuplicates(undefined, duplicateThreshold);
|
|
@@ -455,7 +471,13 @@ export class Curator {
|
|
|
455
471
|
VALUES (?, 'archived', unixepoch())
|
|
456
472
|
ON CONFLICT(entry_id) DO UPDATE SET status = 'archived', last_groomed_at = unixepoch()`,
|
|
457
473
|
).run(entryId);
|
|
458
|
-
this.logChange(
|
|
474
|
+
this.logChange(
|
|
475
|
+
'archive',
|
|
476
|
+
entryId,
|
|
477
|
+
'active',
|
|
478
|
+
'archived',
|
|
479
|
+
'Stale entry archived during consolidation',
|
|
480
|
+
);
|
|
459
481
|
mutations++;
|
|
460
482
|
}
|
|
461
483
|
|
|
@@ -526,9 +548,21 @@ export class Curator {
|
|
|
526
548
|
const hasAntiPatterns = typeCount['anti-pattern'] > 0;
|
|
527
549
|
const hasRules = typeCount.rule > 0;
|
|
528
550
|
let coverageScore = 1;
|
|
529
|
-
if (!hasPatterns) {
|
|
530
|
-
|
|
531
|
-
|
|
551
|
+
if (!hasPatterns) {
|
|
552
|
+
score -= 10;
|
|
553
|
+
coverageScore -= 0.33;
|
|
554
|
+
recommendations.push('No patterns found — add patterns to improve coverage.');
|
|
555
|
+
}
|
|
556
|
+
if (!hasAntiPatterns) {
|
|
557
|
+
score -= 5;
|
|
558
|
+
coverageScore -= 0.17;
|
|
559
|
+
recommendations.push('No anti-patterns found — add anti-patterns to detect contradictions.');
|
|
560
|
+
}
|
|
561
|
+
if (!hasRules) {
|
|
562
|
+
score -= 5;
|
|
563
|
+
coverageScore -= 0.17;
|
|
564
|
+
recommendations.push('No rules found — add rules for completeness.');
|
|
565
|
+
}
|
|
532
566
|
coverageScore = Math.max(0, coverageScore);
|
|
533
567
|
|
|
534
568
|
// Freshness: penalize stale entries
|
|
@@ -536,14 +570,18 @@ export class Curator {
|
|
|
536
570
|
const now = Math.floor(Date.now() / 1000);
|
|
537
571
|
const staleThreshold = now - DEFAULT_STALE_DAYS * 86400;
|
|
538
572
|
const staleCount = (
|
|
539
|
-
db
|
|
573
|
+
db
|
|
574
|
+
.prepare('SELECT COUNT(*) as count FROM entries WHERE updated_at < ?')
|
|
575
|
+
.get(staleThreshold) as { count: number }
|
|
540
576
|
).count;
|
|
541
577
|
const staleRatio = staleCount / entries.length;
|
|
542
578
|
const freshnessScore = 1 - staleRatio;
|
|
543
579
|
if (staleRatio > 0.3) {
|
|
544
580
|
const penalty = Math.min(20, Math.round(staleRatio * 30));
|
|
545
581
|
score -= penalty;
|
|
546
|
-
recommendations.push(
|
|
582
|
+
recommendations.push(
|
|
583
|
+
`${staleCount} stale entries (${Math.round(staleRatio * 100)}%) — run grooming to update.`,
|
|
584
|
+
);
|
|
547
585
|
}
|
|
548
586
|
|
|
549
587
|
// Quality: penalize duplicates and contradictions
|
|
@@ -571,12 +609,18 @@ export class Curator {
|
|
|
571
609
|
if (lowTagRatio > 0.3) {
|
|
572
610
|
const penalty = Math.min(10, Math.round(lowTagRatio * 15));
|
|
573
611
|
score -= penalty;
|
|
574
|
-
recommendations.push(
|
|
612
|
+
recommendations.push(
|
|
613
|
+
`${lowTagEntries.length} entries have fewer than 2 tags — improve tagging.`,
|
|
614
|
+
);
|
|
575
615
|
}
|
|
576
616
|
|
|
577
617
|
// Penalize ungroomed entries
|
|
578
618
|
const groomedCount = (
|
|
579
|
-
db
|
|
619
|
+
db
|
|
620
|
+
.prepare(
|
|
621
|
+
'SELECT COUNT(*) as count FROM curator_entry_state WHERE last_groomed_at IS NOT NULL',
|
|
622
|
+
)
|
|
623
|
+
.get() as { count: number }
|
|
580
624
|
).count;
|
|
581
625
|
if (groomedCount < entries.length) {
|
|
582
626
|
const ungroomed = entries.length - groomedCount;
|
|
@@ -603,13 +647,219 @@ export class Curator {
|
|
|
603
647
|
};
|
|
604
648
|
}
|
|
605
649
|
|
|
650
|
+
// ─── Entry History (Version Snapshots) ─────────────────────────
|
|
651
|
+
|
|
652
|
+
recordSnapshot(
|
|
653
|
+
entryId: string,
|
|
654
|
+
changedBy?: string,
|
|
655
|
+
changeReason?: string,
|
|
656
|
+
): { recorded: boolean; historyId: number } {
|
|
657
|
+
const entry = this.vault.get(entryId);
|
|
658
|
+
if (!entry) return { recorded: false, historyId: -1 };
|
|
659
|
+
|
|
660
|
+
const db = this.vault.getDb();
|
|
661
|
+
const result = db
|
|
662
|
+
.prepare(
|
|
663
|
+
'INSERT INTO curator_entry_history (entry_id, snapshot, changed_by, change_reason, created_at) VALUES (?, ?, ?, ?, unixepoch())',
|
|
664
|
+
)
|
|
665
|
+
.run(entryId, JSON.stringify(entry), changedBy ?? 'system', changeReason ?? null);
|
|
666
|
+
|
|
667
|
+
return { recorded: true, historyId: Number(result.lastInsertRowid) };
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
getVersionHistory(
|
|
671
|
+
entryId: string,
|
|
672
|
+
): Array<{
|
|
673
|
+
historyId: number;
|
|
674
|
+
entryId: string;
|
|
675
|
+
snapshot: IntelligenceEntry;
|
|
676
|
+
changedBy: string;
|
|
677
|
+
changeReason: string | null;
|
|
678
|
+
createdAt: number;
|
|
679
|
+
}> {
|
|
680
|
+
const db = this.vault.getDb();
|
|
681
|
+
const rows = db
|
|
682
|
+
.prepare(
|
|
683
|
+
'SELECT * FROM curator_entry_history WHERE entry_id = ? ORDER BY created_at ASC, id ASC',
|
|
684
|
+
)
|
|
685
|
+
.all(entryId) as Array<Record<string, unknown>>;
|
|
686
|
+
|
|
687
|
+
return rows.map((row) => ({
|
|
688
|
+
historyId: row.id as number,
|
|
689
|
+
entryId: row.entry_id as string,
|
|
690
|
+
snapshot: JSON.parse(row.snapshot as string) as IntelligenceEntry,
|
|
691
|
+
changedBy: row.changed_by as string,
|
|
692
|
+
changeReason: (row.change_reason as string) ?? null,
|
|
693
|
+
createdAt: row.created_at as number,
|
|
694
|
+
}));
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// ─── Queue Stats ──────────────────────────────────────────────
|
|
698
|
+
|
|
699
|
+
getQueueStats(): {
|
|
700
|
+
totalEntries: number;
|
|
701
|
+
groomedEntries: number;
|
|
702
|
+
ungroomedEntries: number;
|
|
703
|
+
staleEntries: number;
|
|
704
|
+
freshEntries: number;
|
|
705
|
+
avgDaysSinceGroom: number;
|
|
706
|
+
} {
|
|
707
|
+
const db = this.vault.getDb();
|
|
708
|
+
const totalEntries = (
|
|
709
|
+
db.prepare('SELECT COUNT(*) as count FROM entries').get() as { count: number }
|
|
710
|
+
).count;
|
|
711
|
+
|
|
712
|
+
const groomedEntries = (
|
|
713
|
+
db
|
|
714
|
+
.prepare(
|
|
715
|
+
'SELECT COUNT(*) as count FROM curator_entry_state WHERE last_groomed_at IS NOT NULL',
|
|
716
|
+
)
|
|
717
|
+
.get() as { count: number }
|
|
718
|
+
).count;
|
|
719
|
+
|
|
720
|
+
const ungroomedEntries = totalEntries - groomedEntries;
|
|
721
|
+
|
|
722
|
+
const now = Math.floor(Date.now() / 1000);
|
|
723
|
+
const staleThreshold = now - 30 * 86400;
|
|
724
|
+
const freshThreshold = now - 7 * 86400;
|
|
725
|
+
|
|
726
|
+
const staleEntries = (
|
|
727
|
+
db
|
|
728
|
+
.prepare(
|
|
729
|
+
'SELECT COUNT(*) as count FROM curator_entry_state WHERE last_groomed_at IS NOT NULL AND last_groomed_at < ?',
|
|
730
|
+
)
|
|
731
|
+
.get(staleThreshold) as { count: number }
|
|
732
|
+
).count;
|
|
733
|
+
|
|
734
|
+
const freshEntries = (
|
|
735
|
+
db
|
|
736
|
+
.prepare(
|
|
737
|
+
'SELECT COUNT(*) as count FROM curator_entry_state WHERE last_groomed_at IS NOT NULL AND last_groomed_at >= ?',
|
|
738
|
+
)
|
|
739
|
+
.get(freshThreshold) as { count: number }
|
|
740
|
+
).count;
|
|
741
|
+
|
|
742
|
+
let avgDaysSinceGroom = 0;
|
|
743
|
+
if (groomedEntries > 0) {
|
|
744
|
+
const sumRow = db
|
|
745
|
+
.prepare(
|
|
746
|
+
'SELECT SUM(? - last_groomed_at) as total FROM curator_entry_state WHERE last_groomed_at IS NOT NULL',
|
|
747
|
+
)
|
|
748
|
+
.get(now) as { total: number | null };
|
|
749
|
+
const totalSeconds = sumRow.total ?? 0;
|
|
750
|
+
avgDaysSinceGroom = Math.round((totalSeconds / groomedEntries / 86400) * 100) / 100;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
return {
|
|
754
|
+
totalEntries,
|
|
755
|
+
groomedEntries,
|
|
756
|
+
ungroomedEntries,
|
|
757
|
+
staleEntries,
|
|
758
|
+
freshEntries,
|
|
759
|
+
avgDaysSinceGroom,
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// ─── Metadata Enrichment ──────────────────────────────────────
|
|
764
|
+
|
|
765
|
+
enrichMetadata(
|
|
766
|
+
entryId: string,
|
|
767
|
+
): { enriched: boolean; changes: Array<{ field: string; before: string; after: string }> } {
|
|
768
|
+
const entry = this.vault.get(entryId);
|
|
769
|
+
if (!entry) return { enriched: false, changes: [] };
|
|
770
|
+
|
|
771
|
+
const changes: Array<{ field: string; before: string; after: string }> = [];
|
|
772
|
+
const updates: Partial<
|
|
773
|
+
Pick<IntelligenceEntry, 'title' | 'description' | 'tags' | 'severity' | 'type'>
|
|
774
|
+
> = {};
|
|
775
|
+
|
|
776
|
+
// Auto-capitalize title
|
|
777
|
+
if (entry.title.length > 0 && entry.title[0] !== entry.title[0].toUpperCase()) {
|
|
778
|
+
const capitalized = entry.title[0].toUpperCase() + entry.title.slice(1);
|
|
779
|
+
changes.push({ field: 'title', before: entry.title, after: capitalized });
|
|
780
|
+
updates.title = capitalized;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// Normalize tags: lowercase, trim, dedup
|
|
784
|
+
const normalizedTags = [...new Set(entry.tags.map((t) => t.toLowerCase().trim()))];
|
|
785
|
+
const tagsChanged =
|
|
786
|
+
normalizedTags.length !== entry.tags.length ||
|
|
787
|
+
normalizedTags.some((t, i) => t !== entry.tags[i]);
|
|
788
|
+
if (tagsChanged) {
|
|
789
|
+
changes.push({
|
|
790
|
+
field: 'tags',
|
|
791
|
+
before: JSON.stringify(entry.tags),
|
|
792
|
+
after: JSON.stringify(normalizedTags),
|
|
793
|
+
});
|
|
794
|
+
updates.tags = normalizedTags;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
// Infer severity from keywords if currently 'suggestion'
|
|
798
|
+
if (entry.severity === 'suggestion') {
|
|
799
|
+
const text = (entry.title + ' ' + entry.description).toLowerCase();
|
|
800
|
+
const criticalKeywords = ['never', 'must not', 'critical', 'security', 'vulnerability'];
|
|
801
|
+
const warningKeywords = ['avoid', 'should not', 'deprecated', 'careful', 'warning'];
|
|
802
|
+
if (criticalKeywords.some((k) => text.includes(k))) {
|
|
803
|
+
changes.push({ field: 'severity', before: entry.severity, after: 'critical' });
|
|
804
|
+
updates.severity = 'critical';
|
|
805
|
+
} else if (warningKeywords.some((k) => text.includes(k))) {
|
|
806
|
+
changes.push({ field: 'severity', before: entry.severity, after: 'warning' });
|
|
807
|
+
updates.severity = 'warning';
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
// Infer type from title patterns
|
|
812
|
+
if (entry.type === 'pattern') {
|
|
813
|
+
const titleLower = entry.title.toLowerCase();
|
|
814
|
+
if (
|
|
815
|
+
titleLower.startsWith('avoid') ||
|
|
816
|
+
titleLower.startsWith('never') ||
|
|
817
|
+
titleLower.startsWith("don't") ||
|
|
818
|
+
titleLower.startsWith('do not')
|
|
819
|
+
) {
|
|
820
|
+
changes.push({ field: 'type', before: entry.type, after: 'anti-pattern' });
|
|
821
|
+
updates.type = 'anti-pattern';
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// Trim whitespace from description
|
|
826
|
+
const trimmed = entry.description.trim();
|
|
827
|
+
if (trimmed !== entry.description) {
|
|
828
|
+
changes.push({ field: 'description', before: entry.description, after: trimmed });
|
|
829
|
+
updates.description = trimmed;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
if (changes.length === 0) {
|
|
833
|
+
return { enriched: false, changes: [] };
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
// Apply updates
|
|
837
|
+
this.vault.update(entryId, updates);
|
|
838
|
+
|
|
839
|
+
// Record snapshot
|
|
840
|
+
this.recordSnapshot(entryId, 'curator', 'Metadata enrichment');
|
|
841
|
+
|
|
842
|
+
// Log change
|
|
843
|
+
this.logChange(
|
|
844
|
+
'enrich_metadata',
|
|
845
|
+
entryId,
|
|
846
|
+
JSON.stringify(changes.map((c) => c.field)),
|
|
847
|
+
JSON.stringify(changes.map((c) => c.after)),
|
|
848
|
+
'Rule-based metadata enrichment',
|
|
849
|
+
);
|
|
850
|
+
|
|
851
|
+
return { enriched: true, changes };
|
|
852
|
+
}
|
|
853
|
+
|
|
606
854
|
// ─── Private Helpers ────────────────────────────────────────────
|
|
607
855
|
|
|
608
856
|
private buildVocabulary(entries: IntelligenceEntry[]): Map<string, number> {
|
|
609
857
|
const docCount = entries.length;
|
|
610
858
|
const termDocFreq = new Map<string, number>();
|
|
611
859
|
for (const entry of entries) {
|
|
612
|
-
const text = [entry.title, entry.description, entry.context ?? '', entry.tags.join(' ')].join(
|
|
860
|
+
const text = [entry.title, entry.description, entry.context ?? '', entry.tags.join(' ')].join(
|
|
861
|
+
' ',
|
|
862
|
+
);
|
|
613
863
|
const tokens = new Set(tokenize(text));
|
|
614
864
|
for (const token of tokens) {
|
|
615
865
|
termDocFreq.set(token, (termDocFreq.get(token) ?? 0) + 1);
|