ruvnet-kb-first 5.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.
Files changed (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +674 -0
  3. package/SKILL.md +740 -0
  4. package/bin/kb-first.js +123 -0
  5. package/install/init-project.sh +435 -0
  6. package/install/install-global.sh +257 -0
  7. package/install/kb-first-autodetect.sh +108 -0
  8. package/install/kb-first-command.md +80 -0
  9. package/install/kb-first-skill.md +262 -0
  10. package/package.json +87 -0
  11. package/phases/00-assessment.md +529 -0
  12. package/phases/01-storage.md +194 -0
  13. package/phases/01.5-hooks-setup.md +521 -0
  14. package/phases/02-kb-creation.md +413 -0
  15. package/phases/03-persistence.md +125 -0
  16. package/phases/04-visualization.md +170 -0
  17. package/phases/05-integration.md +114 -0
  18. package/phases/06-scaffold.md +130 -0
  19. package/phases/07-build.md +493 -0
  20. package/phases/08-verification.md +597 -0
  21. package/phases/09-security.md +512 -0
  22. package/phases/10-documentation.md +613 -0
  23. package/phases/11-deployment.md +670 -0
  24. package/phases/testing.md +713 -0
  25. package/scripts/1.5-hooks-verify.sh +252 -0
  26. package/scripts/8.1-code-scan.sh +58 -0
  27. package/scripts/8.2-import-check.sh +42 -0
  28. package/scripts/8.3-source-returns.sh +52 -0
  29. package/scripts/8.4-startup-verify.sh +65 -0
  30. package/scripts/8.5-fallback-check.sh +63 -0
  31. package/scripts/8.6-attribution.sh +56 -0
  32. package/scripts/8.7-confidence.sh +56 -0
  33. package/scripts/8.8-gap-logging.sh +70 -0
  34. package/scripts/9-security-audit.sh +202 -0
  35. package/scripts/init-project.sh +395 -0
  36. package/scripts/verify-enforcement.sh +167 -0
  37. package/src/commands/hooks.js +361 -0
  38. package/src/commands/init.js +315 -0
  39. package/src/commands/phase.js +372 -0
  40. package/src/commands/score.js +380 -0
  41. package/src/commands/status.js +193 -0
  42. package/src/commands/verify.js +286 -0
  43. package/src/index.js +56 -0
  44. package/src/mcp-server.js +412 -0
  45. package/templates/attention-router.ts +534 -0
  46. package/templates/code-analysis.ts +683 -0
  47. package/templates/federated-kb-learner.ts +649 -0
  48. package/templates/gnn-engine.ts +1091 -0
  49. package/templates/intentions.md +277 -0
  50. package/templates/kb-client.ts +905 -0
  51. package/templates/schema.sql +303 -0
  52. package/templates/sona-config.ts +312 -0
@@ -0,0 +1,303 @@
1
+ -- =============================================================================
2
+ -- KB-First v2.9 Database Schema
3
+ -- =============================================================================
4
+ --
5
+ -- Run this against your PostgreSQL database to set up the KB schema.
6
+ -- Works with both ruvector-postgres and standard pgvector.
7
+ --
8
+ -- Usage:
9
+ -- psql $DATABASE_URL -f schema.sql
10
+ --
11
+ -- =============================================================================
12
+
13
+ -- Enable required extensions
14
+ CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
15
+ CREATE EXTENSION IF NOT EXISTS pg_trgm; -- Text similarity
16
+
17
+ -- Try ruvector first (enhanced features), fall back to pgvector
18
+ DO $$
19
+ BEGIN
20
+ CREATE EXTENSION IF NOT EXISTS ruvector;
21
+ RAISE NOTICE 'Using ruvector extension (enhanced features available)';
22
+ EXCEPTION WHEN OTHERS THEN
23
+ BEGIN
24
+ CREATE EXTENSION IF NOT EXISTS vector;
25
+ RAISE NOTICE 'Using pgvector extension (standard features)';
26
+ EXCEPTION WHEN OTHERS THEN
27
+ RAISE NOTICE 'No vector extension available - text search only';
28
+ END;
29
+ END $$;
30
+
31
+ -- =============================================================================
32
+ -- CORE TABLES
33
+ -- =============================================================================
34
+
35
+ -- Knowledge Base Nodes
36
+ -- Each row is a piece of curated knowledge with expert attribution
37
+ CREATE TABLE IF NOT EXISTS kb_nodes (
38
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
39
+
40
+ -- Organization
41
+ namespace TEXT NOT NULL DEFAULT 'default', -- Separate KBs per client/project
42
+ path TEXT NOT NULL, -- Hierarchical path: "retirement/withdrawal/4-percent-rule"
43
+
44
+ -- Content
45
+ title TEXT NOT NULL,
46
+ content TEXT, -- The actual knowledge content
47
+
48
+ -- Attribution (REQUIRED - this is KB-First, not just RAG)
49
+ source_expert TEXT NOT NULL, -- "Wade Pfau, PhD" - who said this?
50
+ source_url TEXT NOT NULL, -- Link to original source
51
+
52
+ -- Quality
53
+ confidence REAL DEFAULT 1.0
54
+ CHECK (confidence >= 0 AND confidence <= 1), -- 0.0 to 1.0
55
+
56
+ -- Metadata
57
+ metadata JSONB DEFAULT '{}', -- Flexible additional data
58
+
59
+ -- Vector embedding (384 dims for all-MiniLM-L6-v2)
60
+ embedding vector(384),
61
+
62
+ -- Usage tracking
63
+ access_count INTEGER DEFAULT 0,
64
+ last_accessed TIMESTAMPTZ,
65
+
66
+ -- Timestamps
67
+ created_at TIMESTAMPTZ DEFAULT NOW(),
68
+ updated_at TIMESTAMPTZ DEFAULT NOW(),
69
+
70
+ -- Constraints
71
+ UNIQUE(namespace, path)
72
+ );
73
+
74
+ COMMENT ON TABLE kb_nodes IS 'Core knowledge base content with expert attribution';
75
+ COMMENT ON COLUMN kb_nodes.source_expert IS 'Required: Name of expert source (e.g., "Wade Pfau, PhD")';
76
+ COMMENT ON COLUMN kb_nodes.source_url IS 'Required: URL to original source material';
77
+ COMMENT ON COLUMN kb_nodes.confidence IS 'Quality score 0.0-1.0 (high=0.8+, medium=0.5-0.8, low=<0.5)';
78
+
79
+ -- =============================================================================
80
+ -- GAP DETECTION
81
+ -- =============================================================================
82
+
83
+ -- Track queries that couldn't be answered from the KB
84
+ -- Use this to identify what content to add
85
+ CREATE TABLE IF NOT EXISTS kb_gaps (
86
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
87
+
88
+ query TEXT NOT NULL, -- The unanswered query
89
+ reason TEXT, -- Why it couldn't be answered
90
+ namespace TEXT DEFAULT 'default',
91
+
92
+ -- Tracking
93
+ first_seen TIMESTAMPTZ DEFAULT NOW(),
94
+ last_seen TIMESTAMPTZ DEFAULT NOW(),
95
+ occurrence_count INTEGER DEFAULT 1, -- How many times this gap appeared
96
+
97
+ -- Resolution
98
+ resolved BOOLEAN DEFAULT false,
99
+ resolved_at TIMESTAMPTZ,
100
+ resolved_by_node UUID REFERENCES kb_nodes(id),
101
+
102
+ UNIQUE(query, namespace)
103
+ );
104
+
105
+ COMMENT ON TABLE kb_gaps IS 'Queries that could not be answered - use for KB improvement';
106
+
107
+ -- =============================================================================
108
+ -- REASONING BANK (Learning)
109
+ -- =============================================================================
110
+
111
+ -- Store successful query-response patterns for future reference
112
+ CREATE TABLE IF NOT EXISTS reasoning_bank (
113
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
114
+
115
+ -- Pattern
116
+ query_pattern TEXT UNIQUE NOT NULL, -- The query that worked
117
+ successful_response TEXT NOT NULL, -- The response that was good
118
+ kb_nodes_used UUID[] DEFAULT '{}', -- Which nodes contributed
119
+
120
+ -- Quality
121
+ feedback_score REAL DEFAULT 0.5
122
+ CHECK (feedback_score >= 0 AND feedback_score <= 1),
123
+ use_count INTEGER DEFAULT 1, -- Times this pattern was used
124
+
125
+ -- Vector for similarity matching
126
+ embedding vector(384),
127
+
128
+ -- Timestamps
129
+ created_at TIMESTAMPTZ DEFAULT NOW(),
130
+ updated_at TIMESTAMPTZ DEFAULT NOW()
131
+ );
132
+
133
+ COMMENT ON TABLE reasoning_bank IS 'Successful patterns for learning - improves over time';
134
+
135
+ -- =============================================================================
136
+ -- ANALYTICS
137
+ -- =============================================================================
138
+
139
+ -- Track all KB queries for analysis
140
+ CREATE TABLE IF NOT EXISTS kb_analytics (
141
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
142
+
143
+ event_type TEXT NOT NULL, -- 'search', 'gap', 'feedback'
144
+ query TEXT,
145
+ namespace TEXT DEFAULT 'default',
146
+
147
+ -- Results
148
+ results_count INTEGER DEFAULT 0,
149
+ top_confidence REAL,
150
+ gap_detected BOOLEAN DEFAULT false,
151
+
152
+ -- Performance
153
+ latency_ms INTEGER,
154
+
155
+ -- Session
156
+ session_id TEXT,
157
+
158
+ -- Timestamps
159
+ created_at TIMESTAMPTZ DEFAULT NOW()
160
+ );
161
+
162
+ COMMENT ON TABLE kb_analytics IS 'Query analytics for monitoring and improvement';
163
+
164
+ -- =============================================================================
165
+ -- INDEXES
166
+ -- =============================================================================
167
+
168
+ -- Primary lookups
169
+ CREATE INDEX IF NOT EXISTS kb_nodes_namespace_idx ON kb_nodes(namespace);
170
+ CREATE INDEX IF NOT EXISTS kb_nodes_path_idx ON kb_nodes(path);
171
+ CREATE INDEX IF NOT EXISTS kb_nodes_confidence_idx ON kb_nodes(confidence DESC);
172
+
173
+ -- Text search
174
+ CREATE INDEX IF NOT EXISTS kb_nodes_title_trgm_idx ON kb_nodes USING gin(title gin_trgm_ops);
175
+ CREATE INDEX IF NOT EXISTS kb_nodes_content_trgm_idx ON kb_nodes USING gin(content gin_trgm_ops);
176
+
177
+ -- Full text search
178
+ CREATE INDEX IF NOT EXISTS kb_nodes_fts_idx ON kb_nodes USING gin(
179
+ to_tsvector('english', COALESCE(title, '') || ' ' || COALESCE(content, ''))
180
+ );
181
+
182
+ -- Vector search (HNSW for fast approximate search)
183
+ -- This will fail gracefully if vector extension not available
184
+ DO $$ BEGIN
185
+ CREATE INDEX IF NOT EXISTS kb_nodes_embedding_idx ON kb_nodes
186
+ USING hnsw (embedding vector_cosine_ops)
187
+ WITH (m = 16, ef_construction = 64);
188
+ EXCEPTION WHEN OTHERS THEN
189
+ RAISE NOTICE 'Vector index not created - extension not available';
190
+ END $$;
191
+
192
+ -- Gap tracking
193
+ CREATE INDEX IF NOT EXISTS kb_gaps_namespace_idx ON kb_gaps(namespace);
194
+ CREATE INDEX IF NOT EXISTS kb_gaps_count_idx ON kb_gaps(occurrence_count DESC);
195
+ CREATE INDEX IF NOT EXISTS kb_gaps_unresolved_idx ON kb_gaps(namespace) WHERE NOT resolved;
196
+
197
+ -- Analytics
198
+ CREATE INDEX IF NOT EXISTS kb_analytics_type_idx ON kb_analytics(event_type);
199
+ CREATE INDEX IF NOT EXISTS kb_analytics_time_idx ON kb_analytics(created_at DESC);
200
+ CREATE INDEX IF NOT EXISTS kb_analytics_session_idx ON kb_analytics(session_id);
201
+
202
+ -- =============================================================================
203
+ -- FUNCTIONS
204
+ -- =============================================================================
205
+
206
+ -- Update timestamp trigger
207
+ CREATE OR REPLACE FUNCTION update_updated_at()
208
+ RETURNS TRIGGER AS $$
209
+ BEGIN
210
+ NEW.updated_at = NOW();
211
+ RETURN NEW;
212
+ END;
213
+ $$ LANGUAGE plpgsql;
214
+
215
+ -- Apply to tables
216
+ DROP TRIGGER IF EXISTS kb_nodes_updated_at ON kb_nodes;
217
+ CREATE TRIGGER kb_nodes_updated_at
218
+ BEFORE UPDATE ON kb_nodes
219
+ FOR EACH ROW EXECUTE FUNCTION update_updated_at();
220
+
221
+ DROP TRIGGER IF EXISTS reasoning_bank_updated_at ON reasoning_bank;
222
+ CREATE TRIGGER reasoning_bank_updated_at
223
+ BEFORE UPDATE ON reasoning_bank
224
+ FOR EACH ROW EXECUTE FUNCTION update_updated_at();
225
+
226
+ -- =============================================================================
227
+ -- HELPFUL VIEWS
228
+ -- =============================================================================
229
+
230
+ -- Top gaps to address
231
+ CREATE OR REPLACE VIEW kb_top_gaps AS
232
+ SELECT
233
+ query,
234
+ reason,
235
+ namespace,
236
+ occurrence_count,
237
+ last_seen,
238
+ first_seen
239
+ FROM kb_gaps
240
+ WHERE NOT resolved
241
+ ORDER BY occurrence_count DESC, last_seen DESC
242
+ LIMIT 100;
243
+
244
+ COMMENT ON VIEW kb_top_gaps IS 'Top unresolved gaps - prioritize these for KB improvement';
245
+
246
+ -- KB health summary
247
+ CREATE OR REPLACE VIEW kb_health AS
248
+ SELECT
249
+ namespace,
250
+ COUNT(*) as node_count,
251
+ COUNT(*) FILTER (WHERE content IS NOT NULL) as nodes_with_content,
252
+ AVG(confidence) as avg_confidence,
253
+ COUNT(*) FILTER (WHERE confidence >= 0.8) as high_confidence_count,
254
+ COUNT(*) FILTER (WHERE last_accessed > NOW() - INTERVAL '7 days') as recently_accessed,
255
+ MAX(updated_at) as last_updated
256
+ FROM kb_nodes
257
+ GROUP BY namespace;
258
+
259
+ COMMENT ON VIEW kb_health IS 'KB health metrics per namespace';
260
+
261
+ -- =============================================================================
262
+ -- SAMPLE DATA (Remove in production)
263
+ -- =============================================================================
264
+
265
+ -- Uncomment to add sample data for testing:
266
+ /*
267
+ INSERT INTO kb_nodes (namespace, path, title, content, source_expert, source_url, confidence)
268
+ VALUES
269
+ ('demo', 'retirement/withdrawal/4-percent-rule',
270
+ 'The 4% Rule',
271
+ 'The 4% rule suggests withdrawing 4% of your portfolio in the first year of retirement, then adjusting for inflation each subsequent year.',
272
+ 'William Bengen',
273
+ 'https://www.financialplanningassociation.org/article/journal/OCT94-determining-withdrawal-rates-using-historical-data',
274
+ 0.95),
275
+ ('demo', 'retirement/withdrawal/guardrails',
276
+ 'Guardrails Strategy',
277
+ 'The guardrails approach adjusts withdrawals based on portfolio performance, increasing spending when markets are up and decreasing when down.',
278
+ 'Jonathan Guyton',
279
+ 'https://www.financialplanningassociation.org/article/journal/MAR06-decision-rules-and-portfolio-management-retirees-when-change-becomes-necessary',
280
+ 0.90)
281
+ ON CONFLICT (namespace, path) DO NOTHING;
282
+ */
283
+
284
+ -- =============================================================================
285
+ -- VERIFICATION
286
+ -- =============================================================================
287
+
288
+ DO $$
289
+ DECLARE
290
+ ext_name TEXT;
291
+ BEGIN
292
+ -- Check which extension is active
293
+ SELECT extname INTO ext_name FROM pg_extension WHERE extname IN ('ruvector', 'vector') LIMIT 1;
294
+
295
+ IF ext_name IS NOT NULL THEN
296
+ RAISE NOTICE 'KB-First schema ready with % extension', ext_name;
297
+ ELSE
298
+ RAISE NOTICE 'KB-First schema ready (text search only - no vector extension)';
299
+ END IF;
300
+
301
+ RAISE NOTICE 'Tables created: kb_nodes, kb_gaps, reasoning_bank, kb_analytics';
302
+ RAISE NOTICE 'Views created: kb_top_gaps, kb_health';
303
+ END $$;
@@ -0,0 +1,312 @@
1
+ /**
2
+ * SONA Engine Configuration Template
3
+ * Self-Optimizing Neural Architecture for KB-First Applications
4
+ *
5
+ * Use this template for Scenario Learning and Continuous Optimization patterns.
6
+ */
7
+
8
+ import { Pool } from 'pg';
9
+
10
+ // ============================================
11
+ // CONFIGURATION INTERFACES
12
+ // ============================================
13
+
14
+ export interface SonaConfig {
15
+ // Core architecture
16
+ hidden_dim: number; // Hidden layer dimension (128, 256, 512)
17
+ pattern_clusters: number; // Number of pattern clusters (32, 64, 128)
18
+
19
+ // Learning parameters
20
+ ewc_lambda: number; // Elastic Weight Consolidation (0.2-0.6)
21
+ base_lora_rank: number; // LoRA rank for base learning (4-16)
22
+ micro_lora_rank: number; // LoRA rank for instant learning (2-4)
23
+
24
+ // Pattern storage
25
+ pattern_quality_threshold: number; // Min quality to store (0.6-0.8)
26
+ max_patterns_per_cluster: number; // Prevent unbounded growth
27
+
28
+ // Trajectory tracking
29
+ trajectory_timeout_seconds: number; // Auto-end incomplete trajectories
30
+ trajectory_consolidate_seconds: number; // Background consolidation interval
31
+
32
+ // Database
33
+ connectionString: string;
34
+ }
35
+
36
+ // ============================================
37
+ // PRESET CONFIGURATIONS BY DOMAIN
38
+ // ============================================
39
+
40
+ export const PRESETS = {
41
+ /**
42
+ * STABLE DOMAINS (Finance, Law, Medicine)
43
+ * - High anti-forgetting (ewc_lambda)
44
+ * - Deep pattern learning (high lora_rank)
45
+ * - High quality threshold
46
+ */
47
+ stable: {
48
+ hidden_dim: 256,
49
+ pattern_clusters: 64,
50
+ ewc_lambda: 0.6,
51
+ base_lora_rank: 16,
52
+ micro_lora_rank: 4,
53
+ pattern_quality_threshold: 0.8,
54
+ max_patterns_per_cluster: 1000,
55
+ trajectory_timeout_seconds: 86400, // 24 hours
56
+ trajectory_consolidate_seconds: 3600, // 1 hour
57
+ },
58
+
59
+ /**
60
+ * MODERATE DOMAINS (Business, Travel, Education)
61
+ * - Balanced anti-forgetting
62
+ * - Moderate pattern learning
63
+ * - Moderate quality threshold
64
+ */
65
+ moderate: {
66
+ hidden_dim: 256,
67
+ pattern_clusters: 64,
68
+ ewc_lambda: 0.4,
69
+ base_lora_rank: 12,
70
+ micro_lora_rank: 3,
71
+ pattern_quality_threshold: 0.7,
72
+ max_patterns_per_cluster: 500,
73
+ trajectory_timeout_seconds: 43200, // 12 hours
74
+ trajectory_consolidate_seconds: 1800, // 30 minutes
75
+ },
76
+
77
+ /**
78
+ * FAST-CHANGING DOMAINS (SEO, Markets, Social Media)
79
+ * - Low anti-forgetting (rapid adaptation)
80
+ * - Fast pattern learning (low lora_rank)
81
+ * - Lower quality threshold (learn from modest wins)
82
+ */
83
+ fastChanging: {
84
+ hidden_dim: 128,
85
+ pattern_clusters: 32,
86
+ ewc_lambda: 0.2,
87
+ base_lora_rank: 4,
88
+ micro_lora_rank: 2,
89
+ pattern_quality_threshold: 0.6,
90
+ max_patterns_per_cluster: 200,
91
+ trajectory_timeout_seconds: 7200, // 2 hours
92
+ trajectory_consolidate_seconds: 300, // 5 minutes
93
+ }
94
+ };
95
+
96
+ // ============================================
97
+ // SONA ENGINE CLASS
98
+ // ============================================
99
+
100
+ export class SonaEngine {
101
+ private config: SonaConfig;
102
+ private pool: Pool;
103
+
104
+ constructor(config: Partial<SonaConfig> & { connectionString: string }) {
105
+ this.config = {
106
+ ...PRESETS.moderate, // Default to moderate
107
+ ...config
108
+ };
109
+ this.pool = new Pool({ connectionString: this.config.connectionString });
110
+ }
111
+
112
+ // ------------------------------------------
113
+ // PATTERN STORAGE
114
+ // ------------------------------------------
115
+
116
+ /**
117
+ * Store a successful pattern for future recall
118
+ */
119
+ async storePattern(pattern: {
120
+ embedding: number[];
121
+ pattern: Record<string, unknown>;
122
+ quality: number;
123
+ namespace?: string;
124
+ }): Promise<string> {
125
+ // Only store if meets quality threshold
126
+ if (pattern.quality < this.config.pattern_quality_threshold) {
127
+ return null;
128
+ }
129
+
130
+ const result = await this.pool.query(`
131
+ INSERT INTO reasoning_bank (
132
+ namespace, embedding, pattern_data, quality_score, created_at
133
+ ) VALUES ($1, $2, $3, $4, NOW())
134
+ RETURNING id
135
+ `, [
136
+ pattern.namespace || 'default',
137
+ JSON.stringify(pattern.embedding),
138
+ JSON.stringify(pattern.pattern),
139
+ pattern.quality
140
+ ]);
141
+
142
+ return result.rows[0].id;
143
+ }
144
+
145
+ /**
146
+ * Recall patterns similar to a profile/query
147
+ */
148
+ async recallPatterns(
149
+ embedding: number[],
150
+ options: {
151
+ namespace?: string;
152
+ limit?: number;
153
+ minQuality?: number;
154
+ } = {}
155
+ ): Promise<Array<{
156
+ id: string;
157
+ pattern: Record<string, unknown>;
158
+ quality: number;
159
+ similarity: number;
160
+ }>> {
161
+ const { namespace = 'default', limit = 10, minQuality = 0.5 } = options;
162
+
163
+ const result = await this.pool.query(`
164
+ SELECT id, pattern_data, quality_score,
165
+ 1.0 / (1.0 + (embedding::vector <=> $1::vector)) as similarity
166
+ FROM reasoning_bank
167
+ WHERE namespace = $2
168
+ AND quality_score >= $3
169
+ ORDER BY embedding::vector <=> $1::vector
170
+ LIMIT $4
171
+ `, [
172
+ JSON.stringify(embedding),
173
+ namespace,
174
+ minQuality,
175
+ limit
176
+ ]);
177
+
178
+ return result.rows.map(row => ({
179
+ id: row.id,
180
+ pattern: row.pattern_data,
181
+ quality: row.quality_score,
182
+ similarity: row.similarity
183
+ }));
184
+ }
185
+
186
+ // ------------------------------------------
187
+ // TRAJECTORY TRACKING
188
+ // ------------------------------------------
189
+
190
+ private activeTrajectories = new Map<string, {
191
+ startTime: Date;
192
+ embedding: number[];
193
+ steps: Array<{ action: string; timestamp: Date }>;
194
+ }>();
195
+
196
+ /**
197
+ * Start tracking a trajectory (for learning from outcomes)
198
+ */
199
+ startTrajectory(id: string, embedding: number[]): void {
200
+ this.activeTrajectories.set(id, {
201
+ startTime: new Date(),
202
+ embedding,
203
+ steps: []
204
+ });
205
+
206
+ // Auto-timeout
207
+ setTimeout(() => {
208
+ if (this.activeTrajectories.has(id)) {
209
+ this.endTrajectory(id, 0); // End with zero score if timed out
210
+ }
211
+ }, this.config.trajectory_timeout_seconds * 1000);
212
+ }
213
+
214
+ /**
215
+ * Add a step to an active trajectory
216
+ */
217
+ addTrajectoryStep(id: string, action: string): void {
218
+ const trajectory = this.activeTrajectories.get(id);
219
+ if (trajectory) {
220
+ trajectory.steps.push({ action, timestamp: new Date() });
221
+ }
222
+ }
223
+
224
+ /**
225
+ * End a trajectory and optionally store as pattern
226
+ */
227
+ async endTrajectory(id: string, outcomeScore: number): Promise<void> {
228
+ const trajectory = this.activeTrajectories.get(id);
229
+ if (!trajectory) return;
230
+
231
+ this.activeTrajectories.delete(id);
232
+
233
+ // Store as pattern if outcome was good
234
+ if (outcomeScore >= this.config.pattern_quality_threshold) {
235
+ await this.storePattern({
236
+ embedding: trajectory.embedding,
237
+ pattern: {
238
+ steps: trajectory.steps,
239
+ duration: Date.now() - trajectory.startTime.getTime(),
240
+ outcome: outcomeScore
241
+ },
242
+ quality: outcomeScore
243
+ });
244
+ }
245
+ }
246
+
247
+ // ------------------------------------------
248
+ // HEALTH & DIAGNOSTICS
249
+ // ------------------------------------------
250
+
251
+ async getStats(namespace = 'default'): Promise<{
252
+ patternCount: number;
253
+ avgQuality: number;
254
+ clusterDistribution: Record<string, number>;
255
+ }> {
256
+ const result = await this.pool.query(`
257
+ SELECT
258
+ COUNT(*) as pattern_count,
259
+ AVG(quality_score) as avg_quality
260
+ FROM reasoning_bank
261
+ WHERE namespace = $1
262
+ `, [namespace]);
263
+
264
+ return {
265
+ patternCount: parseInt(result.rows[0].pattern_count),
266
+ avgQuality: parseFloat(result.rows[0].avg_quality) || 0,
267
+ clusterDistribution: {} // Would need clustering analysis
268
+ };
269
+ }
270
+ }
271
+
272
+ // ============================================
273
+ // FACTORY FUNCTION
274
+ // ============================================
275
+
276
+ export function createSonaEngine(
277
+ domainType: 'stable' | 'moderate' | 'fastChanging',
278
+ connectionString: string,
279
+ overrides: Partial<SonaConfig> = {}
280
+ ): SonaEngine {
281
+ return new SonaEngine({
282
+ ...PRESETS[domainType],
283
+ ...overrides,
284
+ connectionString
285
+ });
286
+ }
287
+
288
+ // ============================================
289
+ // USAGE EXAMPLE
290
+ // ============================================
291
+
292
+ /*
293
+ // For a retirement planning app (stable domain):
294
+ const sona = createSonaEngine('stable', process.env.DATABASE_URL);
295
+
296
+ // Store a successful pattern
297
+ await sona.storePattern({
298
+ embedding: await embedProfile(userProfile),
299
+ pattern: {
300
+ profile: { age: 55, risk: 'moderate', goal: 'early_retirement' },
301
+ recommendation: 'delayed_ss_roth_conversion',
302
+ outcome: 'user_followed_85%'
303
+ },
304
+ quality: 0.85
305
+ });
306
+
307
+ // Recall for new user
308
+ const similar = await sona.recallPatterns(
309
+ await embedProfile(newUserProfile),
310
+ { limit: 5, minQuality: 0.7 }
311
+ );
312
+ */