@soleri/core 2.1.0 → 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.
Files changed (207) hide show
  1. package/dist/brain/brain.d.ts +3 -1
  2. package/dist/brain/brain.d.ts.map +1 -1
  3. package/dist/brain/brain.js +60 -4
  4. package/dist/brain/brain.js.map +1 -1
  5. package/dist/brain/intelligence.d.ts +36 -1
  6. package/dist/brain/intelligence.d.ts.map +1 -1
  7. package/dist/brain/intelligence.js +119 -14
  8. package/dist/brain/intelligence.js.map +1 -1
  9. package/dist/brain/types.d.ts +32 -0
  10. package/dist/brain/types.d.ts.map +1 -1
  11. package/dist/control/identity-manager.d.ts +22 -0
  12. package/dist/control/identity-manager.d.ts.map +1 -0
  13. package/dist/control/identity-manager.js +233 -0
  14. package/dist/control/identity-manager.js.map +1 -0
  15. package/dist/control/intent-router.d.ts +32 -0
  16. package/dist/control/intent-router.d.ts.map +1 -0
  17. package/dist/control/intent-router.js +242 -0
  18. package/dist/control/intent-router.js.map +1 -0
  19. package/dist/control/types.d.ts +68 -0
  20. package/dist/control/types.d.ts.map +1 -0
  21. package/dist/control/types.js +9 -0
  22. package/dist/control/types.js.map +1 -0
  23. package/dist/curator/curator.d.ts +29 -0
  24. package/dist/curator/curator.d.ts.map +1 -1
  25. package/dist/curator/curator.js +135 -0
  26. package/dist/curator/curator.js.map +1 -1
  27. package/dist/facades/types.d.ts +1 -1
  28. package/dist/governance/governance.d.ts +42 -0
  29. package/dist/governance/governance.d.ts.map +1 -0
  30. package/dist/governance/governance.js +488 -0
  31. package/dist/governance/governance.js.map +1 -0
  32. package/dist/governance/index.d.ts +3 -0
  33. package/dist/governance/index.d.ts.map +1 -0
  34. package/dist/governance/index.js +2 -0
  35. package/dist/governance/index.js.map +1 -0
  36. package/dist/governance/types.d.ts +102 -0
  37. package/dist/governance/types.d.ts.map +1 -0
  38. package/dist/governance/types.js +3 -0
  39. package/dist/governance/types.js.map +1 -0
  40. package/dist/index.d.ts +32 -3
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +29 -1
  43. package/dist/index.js.map +1 -1
  44. package/dist/logging/logger.d.ts +37 -0
  45. package/dist/logging/logger.d.ts.map +1 -0
  46. package/dist/logging/logger.js +145 -0
  47. package/dist/logging/logger.js.map +1 -0
  48. package/dist/logging/types.d.ts +19 -0
  49. package/dist/logging/types.d.ts.map +1 -0
  50. package/dist/logging/types.js +2 -0
  51. package/dist/logging/types.js.map +1 -0
  52. package/dist/loop/loop-manager.d.ts +49 -0
  53. package/dist/loop/loop-manager.d.ts.map +1 -0
  54. package/dist/loop/loop-manager.js +105 -0
  55. package/dist/loop/loop-manager.js.map +1 -0
  56. package/dist/loop/types.d.ts +35 -0
  57. package/dist/loop/types.d.ts.map +1 -0
  58. package/dist/loop/types.js +8 -0
  59. package/dist/loop/types.js.map +1 -0
  60. package/dist/planning/gap-analysis.d.ts +29 -0
  61. package/dist/planning/gap-analysis.d.ts.map +1 -0
  62. package/dist/planning/gap-analysis.js +265 -0
  63. package/dist/planning/gap-analysis.js.map +1 -0
  64. package/dist/planning/gap-types.d.ts +29 -0
  65. package/dist/planning/gap-types.d.ts.map +1 -0
  66. package/dist/planning/gap-types.js +28 -0
  67. package/dist/planning/gap-types.js.map +1 -0
  68. package/dist/planning/planner.d.ts +150 -1
  69. package/dist/planning/planner.d.ts.map +1 -1
  70. package/dist/planning/planner.js +365 -2
  71. package/dist/planning/planner.js.map +1 -1
  72. package/dist/project/project-registry.d.ts +79 -0
  73. package/dist/project/project-registry.d.ts.map +1 -0
  74. package/dist/project/project-registry.js +276 -0
  75. package/dist/project/project-registry.js.map +1 -0
  76. package/dist/project/types.d.ts +28 -0
  77. package/dist/project/types.d.ts.map +1 -0
  78. package/dist/project/types.js +5 -0
  79. package/dist/project/types.js.map +1 -0
  80. package/dist/runtime/admin-extra-ops.d.ts +13 -0
  81. package/dist/runtime/admin-extra-ops.d.ts.map +1 -0
  82. package/dist/runtime/admin-extra-ops.js +284 -0
  83. package/dist/runtime/admin-extra-ops.js.map +1 -0
  84. package/dist/runtime/admin-ops.d.ts +15 -0
  85. package/dist/runtime/admin-ops.d.ts.map +1 -0
  86. package/dist/runtime/admin-ops.js +322 -0
  87. package/dist/runtime/admin-ops.js.map +1 -0
  88. package/dist/runtime/capture-ops.d.ts +15 -0
  89. package/dist/runtime/capture-ops.d.ts.map +1 -0
  90. package/dist/runtime/capture-ops.js +345 -0
  91. package/dist/runtime/capture-ops.js.map +1 -0
  92. package/dist/runtime/core-ops.d.ts +7 -3
  93. package/dist/runtime/core-ops.d.ts.map +1 -1
  94. package/dist/runtime/core-ops.js +474 -8
  95. package/dist/runtime/core-ops.js.map +1 -1
  96. package/dist/runtime/curator-extra-ops.d.ts +9 -0
  97. package/dist/runtime/curator-extra-ops.d.ts.map +1 -0
  98. package/dist/runtime/curator-extra-ops.js +59 -0
  99. package/dist/runtime/curator-extra-ops.js.map +1 -0
  100. package/dist/runtime/domain-ops.d.ts.map +1 -1
  101. package/dist/runtime/domain-ops.js +59 -13
  102. package/dist/runtime/domain-ops.js.map +1 -1
  103. package/dist/runtime/grading-ops.d.ts +14 -0
  104. package/dist/runtime/grading-ops.d.ts.map +1 -0
  105. package/dist/runtime/grading-ops.js +105 -0
  106. package/dist/runtime/grading-ops.js.map +1 -0
  107. package/dist/runtime/loop-ops.d.ts +13 -0
  108. package/dist/runtime/loop-ops.d.ts.map +1 -0
  109. package/dist/runtime/loop-ops.js +179 -0
  110. package/dist/runtime/loop-ops.js.map +1 -0
  111. package/dist/runtime/memory-cross-project-ops.d.ts +12 -0
  112. package/dist/runtime/memory-cross-project-ops.d.ts.map +1 -0
  113. package/dist/runtime/memory-cross-project-ops.js +165 -0
  114. package/dist/runtime/memory-cross-project-ops.js.map +1 -0
  115. package/dist/runtime/memory-extra-ops.d.ts +13 -0
  116. package/dist/runtime/memory-extra-ops.d.ts.map +1 -0
  117. package/dist/runtime/memory-extra-ops.js +173 -0
  118. package/dist/runtime/memory-extra-ops.js.map +1 -0
  119. package/dist/runtime/orchestrate-ops.d.ts +17 -0
  120. package/dist/runtime/orchestrate-ops.d.ts.map +1 -0
  121. package/dist/runtime/orchestrate-ops.js +240 -0
  122. package/dist/runtime/orchestrate-ops.js.map +1 -0
  123. package/dist/runtime/planning-extra-ops.d.ts +17 -0
  124. package/dist/runtime/planning-extra-ops.d.ts.map +1 -0
  125. package/dist/runtime/planning-extra-ops.js +300 -0
  126. package/dist/runtime/planning-extra-ops.js.map +1 -0
  127. package/dist/runtime/project-ops.d.ts +15 -0
  128. package/dist/runtime/project-ops.d.ts.map +1 -0
  129. package/dist/runtime/project-ops.js +181 -0
  130. package/dist/runtime/project-ops.js.map +1 -0
  131. package/dist/runtime/runtime.d.ts.map +1 -1
  132. package/dist/runtime/runtime.js +44 -1
  133. package/dist/runtime/runtime.js.map +1 -1
  134. package/dist/runtime/types.d.ts +21 -0
  135. package/dist/runtime/types.d.ts.map +1 -1
  136. package/dist/runtime/vault-extra-ops.d.ts +9 -0
  137. package/dist/runtime/vault-extra-ops.d.ts.map +1 -0
  138. package/dist/runtime/vault-extra-ops.js +195 -0
  139. package/dist/runtime/vault-extra-ops.js.map +1 -0
  140. package/dist/telemetry/telemetry.d.ts +48 -0
  141. package/dist/telemetry/telemetry.d.ts.map +1 -0
  142. package/dist/telemetry/telemetry.js +87 -0
  143. package/dist/telemetry/telemetry.js.map +1 -0
  144. package/dist/vault/vault.d.ts +94 -0
  145. package/dist/vault/vault.d.ts.map +1 -1
  146. package/dist/vault/vault.js +340 -1
  147. package/dist/vault/vault.js.map +1 -1
  148. package/package.json +1 -1
  149. package/src/__tests__/admin-extra-ops.test.ts +420 -0
  150. package/src/__tests__/admin-ops.test.ts +271 -0
  151. package/src/__tests__/brain-intelligence.test.ts +205 -0
  152. package/src/__tests__/brain.test.ts +131 -0
  153. package/src/__tests__/capture-ops.test.ts +509 -0
  154. package/src/__tests__/core-ops.test.ts +266 -2
  155. package/src/__tests__/curator-extra-ops.test.ts +359 -0
  156. package/src/__tests__/domain-ops.test.ts +66 -0
  157. package/src/__tests__/governance.test.ts +522 -0
  158. package/src/__tests__/grading-ops.test.ts +340 -0
  159. package/src/__tests__/identity-manager.test.ts +243 -0
  160. package/src/__tests__/intent-router.test.ts +222 -0
  161. package/src/__tests__/logger.test.ts +200 -0
  162. package/src/__tests__/loop-ops.test.ts +398 -0
  163. package/src/__tests__/memory-cross-project-ops.test.ts +246 -0
  164. package/src/__tests__/memory-extra-ops.test.ts +352 -0
  165. package/src/__tests__/orchestrate-ops.test.ts +284 -0
  166. package/src/__tests__/planner.test.ts +331 -0
  167. package/src/__tests__/planning-extra-ops.test.ts +548 -0
  168. package/src/__tests__/project-ops.test.ts +367 -0
  169. package/src/__tests__/vault-extra-ops.test.ts +407 -0
  170. package/src/brain/brain.ts +114 -7
  171. package/src/brain/intelligence.ts +179 -10
  172. package/src/brain/types.ts +38 -0
  173. package/src/control/identity-manager.ts +354 -0
  174. package/src/control/intent-router.ts +326 -0
  175. package/src/control/types.ts +102 -0
  176. package/src/curator/curator.ts +213 -0
  177. package/src/governance/governance.ts +698 -0
  178. package/src/governance/index.ts +18 -0
  179. package/src/governance/types.ts +111 -0
  180. package/src/index.ts +102 -2
  181. package/src/logging/logger.ts +154 -0
  182. package/src/logging/types.ts +21 -0
  183. package/src/loop/loop-manager.ts +130 -0
  184. package/src/loop/types.ts +44 -0
  185. package/src/planning/gap-analysis.ts +506 -0
  186. package/src/planning/gap-types.ts +58 -0
  187. package/src/planning/planner.ts +478 -2
  188. package/src/project/project-registry.ts +358 -0
  189. package/src/project/types.ts +31 -0
  190. package/src/runtime/admin-extra-ops.ts +307 -0
  191. package/src/runtime/admin-ops.ts +329 -0
  192. package/src/runtime/capture-ops.ts +385 -0
  193. package/src/runtime/core-ops.ts +535 -7
  194. package/src/runtime/curator-extra-ops.ts +71 -0
  195. package/src/runtime/domain-ops.ts +65 -13
  196. package/src/runtime/grading-ops.ts +121 -0
  197. package/src/runtime/loop-ops.ts +194 -0
  198. package/src/runtime/memory-cross-project-ops.ts +192 -0
  199. package/src/runtime/memory-extra-ops.ts +186 -0
  200. package/src/runtime/orchestrate-ops.ts +272 -0
  201. package/src/runtime/planning-extra-ops.ts +327 -0
  202. package/src/runtime/project-ops.ts +196 -0
  203. package/src/runtime/runtime.ts +49 -1
  204. package/src/runtime/types.ts +21 -0
  205. package/src/runtime/vault-extra-ops.ts +225 -0
  206. package/src/telemetry/telemetry.ts +118 -0
  207. 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
+ }
@@ -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,
@@ -638,6 +647,210 @@ export class Curator {
638
647
  };
639
648
  }
640
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
+
641
854
  // ─── Private Helpers ────────────────────────────────────────────
642
855
 
643
856
  private buildVocabulary(entries: IntelligenceEntry[]): Map<string, number> {