@learningnodes/elen 0.1.3 → 0.1.5

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.
@@ -16,22 +16,93 @@ class SQLiteStorage {
16
16
  this.db.exec(`
17
17
  CREATE TABLE IF NOT EXISTS constraint_sets (constraint_set_id TEXT PRIMARY KEY, atoms TEXT NOT NULL, summary TEXT NOT NULL);
18
18
  CREATE TABLE IF NOT EXISTS decisions (decision_id TEXT PRIMARY KEY, decision_json TEXT NOT NULL);
19
- CREATE TABLE IF NOT EXISTS records (
20
- record_id TEXT PRIMARY KEY,
21
- decision_id TEXT,
22
- agent_id TEXT NOT NULL,
23
- domain TEXT NOT NULL,
24
- project_id TEXT NOT NULL,
25
- question_text TEXT,
26
- decision_text TEXT,
27
- confidence REAL,
28
- payload_json TEXT NOT NULL
29
- );
30
19
  `);
20
+ // Check if records table exists and what schema it has
21
+ const tableExists = this.db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='records'").get();
22
+ if (!tableExists) {
23
+ // Fresh DB: create spec-compliant table
24
+ this.db.exec(`
25
+ CREATE TABLE records (
26
+ record_id TEXT PRIMARY KEY,
27
+ decision_id TEXT NOT NULL,
28
+ q_id TEXT NOT NULL,
29
+ agent_id TEXT NOT NULL,
30
+ domain TEXT NOT NULL,
31
+ project_id TEXT NOT NULL DEFAULT 'default',
32
+ question_text TEXT,
33
+ decision_text TEXT NOT NULL,
34
+ constraint_set_id TEXT NOT NULL,
35
+ refs TEXT NOT NULL DEFAULT '[]',
36
+ status TEXT NOT NULL DEFAULT 'active',
37
+ supersedes_id TEXT,
38
+ timestamp TEXT NOT NULL,
39
+ payload_json TEXT
40
+ );
41
+ `);
42
+ return;
43
+ }
44
+ // Table exists — check if it needs migration
45
+ const cols = this.db.pragma('table_info(records)');
46
+ const colNames = new Set(cols.map(c => c.name));
47
+ const needsRebuild = colNames.has('record_json') || !colNames.has('payload_json');
48
+ if (needsRebuild) {
49
+ // Old schema detected — rebuild table to fix NOT NULL constraints
50
+ this.db.exec('BEGIN TRANSACTION');
51
+ try {
52
+ this.db.exec('ALTER TABLE records RENAME TO _records_old');
53
+ this.db.exec(`
54
+ CREATE TABLE records (
55
+ record_id TEXT PRIMARY KEY,
56
+ decision_id TEXT NOT NULL,
57
+ q_id TEXT NOT NULL,
58
+ agent_id TEXT NOT NULL,
59
+ domain TEXT NOT NULL,
60
+ project_id TEXT NOT NULL DEFAULT 'default',
61
+ question_text TEXT,
62
+ decision_text TEXT NOT NULL,
63
+ constraint_set_id TEXT NOT NULL,
64
+ refs TEXT NOT NULL DEFAULT '[]',
65
+ status TEXT NOT NULL DEFAULT 'active',
66
+ supersedes_id TEXT,
67
+ timestamp TEXT NOT NULL,
68
+ payload_json TEXT
69
+ );
70
+ `);
71
+ // Copy data, mapping old columns to new
72
+ const hasRecordJson = colNames.has('record_json');
73
+ const hasQuestionText = colNames.has('question_text');
74
+ const payloadCol = hasRecordJson ? 'record_json' : (colNames.has('payload_json') ? 'payload_json' : 'NULL');
75
+ const questionCol = hasQuestionText ? 'question_text' : 'NULL';
76
+ this.db.exec(`
77
+ INSERT INTO records (
78
+ record_id, decision_id, q_id, agent_id, domain, project_id,
79
+ question_text, decision_text, constraint_set_id,
80
+ refs, status, supersedes_id, timestamp, payload_json
81
+ )
82
+ SELECT
83
+ record_id, decision_id, q_id, agent_id, domain, project_id,
84
+ ${questionCol}, decision_text, constraint_set_id,
85
+ refs, status, supersedes_id, timestamp, ${payloadCol}
86
+ FROM _records_old
87
+ `);
88
+ this.db.exec('DROP TABLE _records_old');
89
+ this.db.exec('COMMIT');
90
+ }
91
+ catch (err) {
92
+ this.db.exec('ROLLBACK');
93
+ throw err;
94
+ }
95
+ }
96
+ else if (!colNames.has('question_text')) {
97
+ // Partial migration: just add missing columns
98
+ this.db.exec('ALTER TABLE records ADD COLUMN question_text TEXT');
99
+ }
31
100
  }
101
+ /* ── Decisions (context objects) ──────────────────── */
32
102
  async saveDecision(decision) {
33
103
  this.db.prepare('INSERT OR REPLACE INTO decisions(decision_id, decision_json) VALUES (?,?)').run([decision.decision_id, JSON.stringify(decision)]);
34
104
  }
105
+ /* ── Constraint Sets ─────────────────────────────── */
35
106
  async saveConstraintSet(constraintSet) {
36
107
  this.db.prepare('INSERT OR IGNORE INTO constraint_sets(constraint_set_id, atoms, summary) VALUES (?,?,?)').run([constraintSet.constraint_set_id, JSON.stringify(constraintSet.atoms), constraintSet.summary]);
37
108
  }
@@ -39,33 +110,82 @@ class SQLiteStorage {
39
110
  const row = this.db.prepare('SELECT * FROM constraint_sets WHERE constraint_set_id=?').get(id);
40
111
  return row ? { constraint_set_id: row.constraint_set_id, atoms: JSON.parse(row.atoms), summary: row.summary } : null;
41
112
  }
113
+ /* ── Records ─────────────────────────────────────── */
42
114
  async saveRecord(record) {
43
115
  if ("record_id" in record) {
44
116
  await this.saveLegacyRecord(record);
45
117
  return;
46
118
  }
47
- this.db.prepare('INSERT OR REPLACE INTO records(record_id, decision_id, agent_id, domain, project_id, question_text, decision_text, payload_json) VALUES (?,?,?,?,?,?,?,?)').run([record.decision_id, record.decision_id, record.agent_id, record.domain, this.projectId, record.question_text ?? null, record.decision_text, JSON.stringify(record)]);
119
+ // Spec-compliant MinimalDecisionRecord all columns populated
120
+ this.db.prepare(`
121
+ INSERT OR REPLACE INTO records(
122
+ record_id, decision_id, q_id, agent_id, domain, project_id,
123
+ question_text, decision_text, constraint_set_id,
124
+ refs, status, supersedes_id, timestamp, payload_json
125
+ ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)
126
+ `).run([
127
+ record.decision_id, // record_id = decision_id for minimal records
128
+ record.decision_id,
129
+ record.q_id,
130
+ record.agent_id,
131
+ record.domain,
132
+ this.projectId,
133
+ record.question_text ?? null,
134
+ record.decision_text,
135
+ record.constraint_set_id,
136
+ JSON.stringify(record.refs),
137
+ record.status,
138
+ record.supersedes_id ?? null,
139
+ record.timestamp,
140
+ JSON.stringify(record)
141
+ ]);
48
142
  }
49
143
  async saveLegacyRecord(record) {
50
- this.db.prepare('INSERT OR REPLACE INTO records(record_id, decision_id, agent_id, domain, project_id, question_text, decision_text, confidence, payload_json) VALUES (?,?,?,?,?,?,?,?,?)').run([record.record_id, record.decision_id, record.agent_id, record.domain, this.projectId, record.question, record.answer, record.confidence, JSON.stringify(record)]);
144
+ // Legacy DecisionRecord (v0) map old fields to spec columns
145
+ this.db.prepare(`
146
+ INSERT OR REPLACE INTO records(
147
+ record_id, decision_id, q_id, agent_id, domain, project_id,
148
+ question_text, decision_text, constraint_set_id,
149
+ refs, status, supersedes_id, timestamp, payload_json
150
+ ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)
151
+ `).run([
152
+ record.record_id,
153
+ record.decision_id,
154
+ '', // q_id not available in legacy format
155
+ record.agent_id,
156
+ record.domain,
157
+ this.projectId,
158
+ record.question, // question → question_text
159
+ record.answer, // answer → decision_text
160
+ '', // no constraint_set_id in legacy
161
+ JSON.stringify([]), // no refs in legacy
162
+ 'active', // default status
163
+ null, // no supersedes_id in legacy
164
+ record.published_at, // published_at → timestamp
165
+ JSON.stringify(record)
166
+ ]);
51
167
  }
52
168
  async getRecord(recordId) {
53
169
  const row = this.db.prepare('SELECT payload_json FROM records WHERE record_id=? OR decision_id=?').get([recordId, recordId]);
54
- return row ? JSON.parse(row.payload_json) : null;
170
+ return row?.payload_json ? JSON.parse(row.payload_json) : null;
55
171
  }
172
+ /* ── Search ──────────────────────────────────────── */
56
173
  async searchRecords(opts) {
57
- let rows = this.db.prepare('SELECT payload_json, decision_id, project_id, confidence, question_text, decision_text, domain FROM records').all();
58
- if (this.defaultIsolation === 'strict' || opts.includeShared === false)
174
+ let rows = this.db.prepare('SELECT payload_json, decision_id, project_id, question_text, decision_text, domain, status FROM records WHERE status != ?').all(['withdrawn']);
175
+ // Project isolation
176
+ if (this.defaultIsolation === 'strict' || opts.includeShared === false) {
59
177
  rows = rows.filter(r => r.project_id === this.projectId);
178
+ }
179
+ // Domain filter
60
180
  if (opts.domain)
61
181
  rows = rows.filter(r => r.domain === opts.domain);
62
- if (typeof opts.minConfidence === 'number')
63
- rows = rows.filter(r => r.confidence == null || r.confidence >= opts.minConfidence);
182
+ // Text search across question_text + decision_text + domain
64
183
  if (opts.query) {
65
184
  const q = opts.query.toLowerCase();
66
- rows = rows.filter(r => { const p = JSON.parse(r.payload_json); const extra = p.constraints_snapshot ? `${p.constraints_snapshot.map((c) => c.description).join(' ')} ${p.evidence_snapshot.map((e) => `${e.claim} ${e.proof}`).join(' ')}` : ''; return `${r.question_text ?? ''} ${r.decision_text ?? ''} ${r.domain ?? ''} ${extra}`.toLowerCase().includes(q); });
185
+ rows = rows.filter(r => `${r.question_text ?? ''} ${r.decision_text ?? ''} ${r.domain ?? ''}`.toLowerCase().includes(q));
67
186
  }
68
- let parsed = rows.map(r => JSON.parse(r.payload_json));
187
+ let parsed = rows.map(r => r.payload_json ? JSON.parse(r.payload_json) : null).filter(Boolean);
188
+ // Parent prompt filter (searches decision context)
69
189
  if (opts.parentPrompt) {
70
190
  const needle = opts.parentPrompt.toLowerCase();
71
191
  parsed = parsed.filter((r) => {
@@ -78,9 +198,10 @@ class SQLiteStorage {
78
198
  }
79
199
  return opts.limit ? parsed.slice(0, opts.limit) : parsed;
80
200
  }
201
+ /* ── Agent queries ───────────────────────────────── */
81
202
  async getAgentDecisions(agentId, domain) {
82
203
  const rows = this.db.prepare('SELECT payload_json FROM records WHERE agent_id=?').all([agentId]);
83
- const parsed = rows.map(r => JSON.parse(r.payload_json));
204
+ const parsed = rows.map(r => r.payload_json ? JSON.parse(r.payload_json) : null).filter(Boolean);
84
205
  return domain ? parsed.filter(r => r.domain === domain) : parsed;
85
206
  }
86
207
  async getCompetencyProfile(agentId) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@learningnodes/elen",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "license": "AGPL-3.0",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -10,7 +10,7 @@
10
10
  "lint": "tsc -p tsconfig.json --noEmit"
11
11
  },
12
12
  "dependencies": {
13
- "@learningnodes/elen-core": "^0.1.3",
13
+ "@learningnodes/elen-core": "^0.1.5",
14
14
  "better-sqlite3": "^11.7.0",
15
15
  "nanoid": "^5.1.4"
16
16
  },
@@ -1,17 +0,0 @@
1
- 
2
-  RUN  v2.1.9 C:/Users/ln_ni/OneDrive/Desktop/Desktop/ventures/learningnodes/git/marketplace-repos/Elen/packages/sdk-ts
3
-
4
- Γ£ô tests/client.test.ts > ElenClient > logDecision creates a valid DecisionRecord
5
- Γ£ô tests/client.test.ts > ElenClient > logDecision throws if no constraints provided
6
- Γ£ô tests/client.test.ts > ElenClient > logDecision throws if no evidence provided
7
- Γ£ô tests/client.test.ts > ElenClient > logDecision auto-classifies epistemic types
8
- Γ£ô tests/client.test.ts > ElenClient > searchRecords filters by domain, minConfidence and parentPrompt
9
- Γ£ô tests/client.test.ts > ElenClient > competency profile is computed from records
10
- Γ£ô tests/client.test.ts > ElenClient > stores linked precedents in evidence
11
- Γ£ô tests/client.test.ts > ElenClient > ID generation produces unique IDs
12
-
13
-  Test Files  1 passed (1)
14
-  Tests  8 passed (8)
15
-  Start at  12:18:15
16
-  Duration  2.30s (transform 471ms, setup 0ms, collect 666ms, tests 51ms, environment 1ms, prepare 546ms)
17
-
package/src/client.ts DELETED
@@ -1,172 +0,0 @@
1
- import {
2
- minimalDecisionRecordSchema,
3
- constraintSetSchema,
4
- classifyEpistemicType,
5
- type MinimalDecisionRecord,
6
- type DecisionRecord,
7
- type ConstraintSet,
8
- type Constraint,
9
- type Evidence,
10
- type Check
11
- } from '@learningnodes/elen-core';
12
- import { createId, createDecisionId, createConstraintSetId } from './id';
13
- import type { StorageAdapter } from './storage';
14
- import type { CompetencyProfileResult, CommitDecisionInput, LogDecisionInput, SearchOptions } from './types';
15
-
16
- export class ElenClient {
17
- constructor(private readonly agentId: string, private readonly storage: StorageAdapter) { }
18
-
19
- async logDecision(input: LogDecisionInput): Promise<DecisionRecord> {
20
- if (input.constraints.length === 0) throw new Error('Decision must include at least one constraint');
21
- if (input.evidence.length === 0) throw new Error('Decision must include at least one evidence');
22
-
23
- const decisionId = createDecisionId(input.domain);
24
- const now = new Date().toISOString();
25
-
26
- const constraintsSnapshot: Constraint[] = input.constraints.map((description, i) => ({
27
- constraint_id: createId(`con${i}`),
28
- decision_id: decisionId,
29
- type: 'requirement',
30
- description,
31
- locked: false
32
- }));
33
-
34
- const evidenceSnapshot: Evidence[] = input.evidence.map((e, i) => ({
35
- evidence_id: createId(`evd${i}`),
36
- decision_id: decisionId,
37
- type: input.linkedPrecedents?.[i] ? 'precedent' : 'observation',
38
- claim: e,
39
- proof: e,
40
- confidence: input.confidence?.[i] ?? 0.8,
41
- linked_precedent: input.linkedPrecedents?.[i]
42
- }));
43
-
44
- const checksSnapshot: Check[] = evidenceSnapshot.map((e, i) => ({
45
- check_id: createId(`chk${i}`),
46
- decision_id: decisionId,
47
- claim: e.claim,
48
- result: 'pass',
49
- evidence_ids: [e.evidence_id],
50
- epistemic_type: classifyEpistemicType(e),
51
- confidence: e.confidence
52
- }));
53
-
54
- const record: DecisionRecord = {
55
- record_id: createId('rec'),
56
- decision_id: decisionId,
57
- agent_id: this.agentId,
58
- question: input.question,
59
- answer: input.answer,
60
- constraints_snapshot: constraintsSnapshot,
61
- evidence_snapshot: evidenceSnapshot,
62
- checks_snapshot: checksSnapshot,
63
- confidence: evidenceSnapshot.reduce((a, e) => a + e.confidence, 0) / evidenceSnapshot.length,
64
- validation_type: 'self',
65
- domain: input.domain,
66
- tags: [],
67
- published_at: now,
68
- expires_at: null
69
- };
70
-
71
- await this.storage.saveDecision?.({
72
- decision_id: decisionId,
73
- agent_id: this.agentId,
74
- question: input.question,
75
- domain: input.domain,
76
- status: 'validated',
77
- constraints: constraintsSnapshot,
78
- evidence: evidenceSnapshot,
79
- checks: checksSnapshot,
80
- created_at: now,
81
- parent_prompt: input.parentPrompt
82
- });
83
-
84
- await this.storage.saveLegacyRecord?.(record);
85
- return record;
86
- }
87
-
88
- async commitDecision(input: CommitDecisionInput): Promise<MinimalDecisionRecord> {
89
- const now = new Date().toISOString();
90
-
91
- const constraintSetId = createConstraintSetId(input.constraints);
92
- const existingConstraints = await this.storage.getConstraintSet(constraintSetId);
93
-
94
- if (!existingConstraints) {
95
- const newSet: ConstraintSet = {
96
- constraint_set_id: constraintSetId,
97
- atoms: input.constraints,
98
- summary: `Auto-generated summary for ${input.constraints.length} constraints`
99
- };
100
- constraintSetSchema.parse(newSet);
101
- await this.storage.saveConstraintSet(newSet);
102
- }
103
-
104
- const record: MinimalDecisionRecord = {
105
- decision_id: createDecisionId(input.domain),
106
- q_id: createId('q'),
107
- question_text: input.question,
108
- decision_text: input.decisionText,
109
- constraint_set_id: constraintSetId,
110
- refs: input.refs ?? [],
111
- status: input.status ?? 'active',
112
- supersedes_id: input.supersedesId,
113
- timestamp: now,
114
- agent_id: this.agentId,
115
- domain: input.domain
116
- };
117
-
118
- minimalDecisionRecordSchema.parse(record);
119
- await this.storage.saveRecord(record);
120
-
121
- return record;
122
- }
123
-
124
- async supersedeDecision(oldDecisionId: string, input: CommitDecisionInput): Promise<MinimalDecisionRecord> {
125
- const oldRecord = await this.storage.getRecord(oldDecisionId);
126
- if (oldRecord && 'status' in oldRecord) {
127
- oldRecord.status = 'superseded';
128
- await this.storage.saveRecord(oldRecord);
129
- }
130
-
131
- return this.commitDecision({ ...input, supersedesId: oldDecisionId });
132
- }
133
-
134
- async searchRecords(opts: SearchOptions) {
135
- return this.storage.searchRecords(opts);
136
- }
137
-
138
- async searchPrecedents(query: string, opts: SearchOptions = {}) {
139
- const direct = await this.storage.searchRecords({ ...opts, query });
140
- if (direct.length > 0) return direct;
141
- return this.storage.searchRecords({ ...opts, limit: opts.limit ?? 5 });
142
- }
143
-
144
- async suggest(opts: SearchOptions): Promise<Array<Partial<MinimalDecisionRecord>>> {
145
- const fullRecords = await this.storage.searchRecords(opts);
146
- return fullRecords
147
- .filter((r): r is MinimalDecisionRecord => 'decision_text' in r)
148
- .map((r) => ({
149
- decision_id: r.decision_id,
150
- status: r.status,
151
- decision_text: r.decision_text,
152
- question_text: r.question_text,
153
- constraint_set_id: r.constraint_set_id,
154
- refs: r.refs,
155
- supersedes_id: r.supersedes_id
156
- }));
157
- }
158
-
159
- async expand(decisionId: string): Promise<{ record: MinimalDecisionRecord, constraints: ConstraintSet } | null> {
160
- const record = await this.storage.getRecord(decisionId);
161
- if (!record || !('constraint_set_id' in record)) return null;
162
-
163
- const constraints = await this.storage.getConstraintSet(record.constraint_set_id);
164
- if (!constraints) return null;
165
-
166
- return { record, constraints };
167
- }
168
-
169
- async getCompetencyProfile(): Promise<CompetencyProfileResult> {
170
- return this.storage.getCompetencyProfile(this.agentId);
171
- }
172
- }
package/src/id.ts DELETED
@@ -1,18 +0,0 @@
1
- import { randomBytes, createHash } from 'node:crypto';
2
-
3
- export function createId(prefix: string): string {
4
- return `${prefix}-${randomBytes(8).toString('base64url').slice(0, 10)}`;
5
- }
6
-
7
- export function createDecisionId(domain: string): string {
8
- // Analytical Prefix: 3 chars domain + 'A' (Agent) + 'T3' (Routine Tier)
9
- const d = domain.toUpperCase().replace(/[^A-Z]/g, '').padEnd(3, 'X').substring(0, 3);
10
- const prefix = `${d}AT3`;
11
- return `dec:${prefix}-${randomBytes(4).toString('base64url').slice(0, 6)}`;
12
- }
13
-
14
- export function createConstraintSetId(atoms: string[]): string {
15
- const sorted = [...atoms].sort().join('|');
16
- const hash = createHash('sha256').update(sorted).digest('hex').substring(0, 8);
17
- return `cs:${hash}`;
18
- }
package/src/index.ts DELETED
@@ -1,57 +0,0 @@
1
- import { ElenClient } from './client';
2
- import { InMemoryStorage, SQLiteStorage, type StorageAdapter } from './storage';
3
- import type { ElenConfig, CommitDecisionInput, LogDecisionInput, SearchOptions } from './types';
4
-
5
- export class Elen {
6
- private readonly client: ElenClient;
7
-
8
- constructor(config: ElenConfig) {
9
- const storage = this.createStorage(config);
10
- this.client = new ElenClient(config.agentId, storage);
11
- }
12
-
13
- private createStorage(config: ElenConfig): StorageAdapter {
14
- if (config.storage === 'sqlite') {
15
- return new SQLiteStorage(config.sqlitePath ?? 'elen.db', config.projectId, config.defaultProjectIsolation ?? 'strict');
16
- }
17
-
18
- return new InMemoryStorage();
19
- }
20
-
21
- async logDecision(input: LogDecisionInput) {
22
- return this.client.logDecision(input);
23
- }
24
-
25
- async commitDecision(input: CommitDecisionInput) {
26
- return this.client.commitDecision(input);
27
- }
28
-
29
- async supersedeDecision(oldDecisionId: string, input: CommitDecisionInput) {
30
- return this.client.supersedeDecision(oldDecisionId, input);
31
- }
32
-
33
- async searchRecords(opts: SearchOptions) {
34
- return this.client.searchRecords(opts);
35
- }
36
-
37
- async searchPrecedents(query: string, opts: SearchOptions = {}) {
38
- return this.client.searchPrecedents(query, opts);
39
- }
40
-
41
- async suggest(opts: SearchOptions) {
42
- return this.client.suggest(opts);
43
- }
44
-
45
- async expand(decisionId: string) {
46
- return this.client.expand(decisionId);
47
- }
48
-
49
- async getCompetencyProfile() {
50
- return this.client.getCompetencyProfile();
51
- }
52
- }
53
-
54
- export * from './client';
55
- export * from './id';
56
- export * from './storage';
57
- export * from './types';
package/src/shims.d.ts DELETED
@@ -1,27 +0,0 @@
1
- declare module 'nanoid' {
2
- export function nanoid(size?: number): string;
3
- }
4
-
5
- declare module 'better-sqlite3' {
6
- interface Statement {
7
- run(params?: unknown): unknown;
8
- get(...params: unknown[]): unknown;
9
- all(params?: unknown): unknown[];
10
- }
11
-
12
- interface DatabaseInstance {
13
- exec(sql: string): void;
14
- prepare(sql: string): Statement;
15
- }
16
-
17
- interface DatabaseConstructor {
18
- new (path: string): DatabaseInstance;
19
- }
20
-
21
- const Database: DatabaseConstructor;
22
- namespace Database {
23
- export type Database = DatabaseInstance;
24
- }
25
-
26
- export default Database;
27
- }
@@ -1,3 +0,0 @@
1
- export * from './interface';
2
- export * from './memory';
3
- export * from './sqlite';
@@ -1,20 +0,0 @@
1
- import type {
2
- CompetencyProfile,
3
- ConstraintSet,
4
- DecisionContext,
5
- DecisionRecord,
6
- MinimalDecisionRecord
7
- } from '@learningnodes/elen-core';
8
- import type { SearchOptions } from '../types';
9
-
10
- export interface StorageAdapter {
11
- saveDecision?(decision: DecisionContext): Promise<void>;
12
- saveConstraintSet(constraintSet: ConstraintSet): Promise<void>;
13
- getConstraintSet(id: string): Promise<ConstraintSet | null>;
14
- saveRecord(record: MinimalDecisionRecord | DecisionRecord): Promise<void>;
15
- saveLegacyRecord?(record: DecisionRecord): Promise<void>;
16
- getRecord(recordId: string): Promise<MinimalDecisionRecord | DecisionRecord | null>;
17
- searchRecords(opts: SearchOptions): Promise<Array<MinimalDecisionRecord | DecisionRecord>>;
18
- getAgentDecisions(agentId: string, domain?: string): Promise<Array<MinimalDecisionRecord | DecisionRecord>>;
19
- getCompetencyProfile(agentId: string): Promise<CompetencyProfile>;
20
- }
@@ -1,63 +0,0 @@
1
- import type {
2
- CompetencyProfile,
3
- ConstraintSet,
4
- DecisionContext,
5
- DecisionRecord,
6
- MinimalDecisionRecord
7
- } from '@learningnodes/elen-core';
8
- import type { SearchOptions } from '../types';
9
- import type { StorageAdapter } from './interface';
10
-
11
- export class InMemoryStorage implements StorageAdapter {
12
- private readonly constraintSets = new Map<string, ConstraintSet>();
13
- private readonly records = new Map<string, MinimalDecisionRecord | DecisionRecord>();
14
- private readonly decisions = new Map<string, DecisionContext>();
15
-
16
- async saveDecision(decision: DecisionContext): Promise<void> { this.decisions.set(decision.decision_id, decision); }
17
- async saveConstraintSet(constraintSet: ConstraintSet): Promise<void> { if (!this.constraintSets.has(constraintSet.constraint_set_id)) this.constraintSets.set(constraintSet.constraint_set_id, constraintSet); }
18
- async getConstraintSet(id: string): Promise<ConstraintSet | null> { return this.constraintSets.get(id) ?? null; }
19
- async saveRecord(record: MinimalDecisionRecord | DecisionRecord): Promise<void> { this.records.set(("record_id" in record ? record.record_id : record.decision_id), record); }
20
- async saveLegacyRecord(record: DecisionRecord): Promise<void> { this.records.set(record.record_id, record); }
21
- async getRecord(recordId: string): Promise<MinimalDecisionRecord | DecisionRecord | null> { return this.records.get(recordId) ?? null; }
22
-
23
- async searchRecords(opts: SearchOptions): Promise<Array<MinimalDecisionRecord | DecisionRecord>> {
24
- let results = Array.from(this.records.values());
25
- if (opts.domain) results = results.filter((r) => r.domain === opts.domain);
26
- if (typeof opts.minConfidence === 'number') results = results.filter((r) => ('confidence' in r ? r.confidence >= opts.minConfidence! : true));
27
- if (opts.parentPrompt) {
28
- const needle = opts.parentPrompt.toLowerCase();
29
- results = results.filter((r) => {
30
- const ctx = this.decisions.get(r.decision_id);
31
- return ctx?.parent_prompt?.toLowerCase().includes(needle) ?? false;
32
- });
33
- }
34
- if (opts.query) {
35
- const needle = opts.query.toLowerCase();
36
- results = results.filter((r) => {
37
- const haystack = 'decision_text' in r ? `${r.question_text ?? ''} ${r.decision_text} ${r.domain}` : `${r.question} ${r.answer} ${r.domain} ${r.constraints_snapshot.map(c=>c.description).join(' ')} ${r.evidence_snapshot.map(e=>`${e.claim} ${e.proof}`).join(' ')}`;
38
- return haystack.toLowerCase().includes(needle);
39
- });
40
- }
41
- const limit = opts.limit ?? results.length;
42
- return results.slice(0, limit);
43
- }
44
-
45
- async getAgentDecisions(agentId: string, domain?: string): Promise<Array<MinimalDecisionRecord | DecisionRecord>> {
46
- return Array.from(this.records.values()).filter((r) => r.agent_id === agentId && (domain ? r.domain === domain : true));
47
- }
48
-
49
- async getCompetencyProfile(agentId: string): Promise<CompetencyProfile> {
50
- const records = await this.getAgentDecisions(agentId);
51
- const stats = new Map<string, {count:number; conf:number}>();
52
- for (const r of records) {
53
- const cur = stats.get(r.domain) ?? {count:0, conf:0};
54
- cur.count += 1;
55
- cur.conf += ("confidence" in r ? r.confidence : 0.8);
56
- stats.set(r.domain, cur);
57
- }
58
- const domains = [...stats.keys()];
59
- const strengths = domains.filter((d)=>{ const s=stats.get(d)!; return s.count >= 1 && (s.conf/s.count) >= 0.7;});
60
- const weaknesses = domains.filter((d)=>{ const s=stats.get(d)!; return (s.conf/s.count) < 0.7;});
61
- return { agent_id: agentId, domains, strengths, weaknesses, updated_at: new Date().toISOString() };
62
- }
63
- }
@@ -1,95 +0,0 @@
1
- import Database from 'better-sqlite3';
2
- import type { CompetencyProfile, ConstraintSet, DecisionContext, DecisionRecord, MinimalDecisionRecord } from '@learningnodes/elen-core';
3
- import type { SearchOptions } from '../types';
4
- import type { StorageAdapter } from './interface';
5
-
6
- export class SQLiteStorage implements StorageAdapter {
7
- private readonly db: Database.Database;
8
- private readonly projectId: string;
9
- private readonly defaultIsolation: 'strict' | 'open';
10
-
11
- constructor(path: string, projectId: string = 'default', defaultIsolation: 'strict' | 'open' = 'strict') {
12
- this.db = new Database(path);
13
- this.projectId = projectId;
14
- this.defaultIsolation = defaultIsolation;
15
- this.init();
16
- }
17
-
18
- private init() {
19
- this.db.exec(`
20
- CREATE TABLE IF NOT EXISTS constraint_sets (constraint_set_id TEXT PRIMARY KEY, atoms TEXT NOT NULL, summary TEXT NOT NULL);
21
- CREATE TABLE IF NOT EXISTS decisions (decision_id TEXT PRIMARY KEY, decision_json TEXT NOT NULL);
22
- CREATE TABLE IF NOT EXISTS records (
23
- record_id TEXT PRIMARY KEY,
24
- decision_id TEXT,
25
- agent_id TEXT NOT NULL,
26
- domain TEXT NOT NULL,
27
- project_id TEXT NOT NULL,
28
- question_text TEXT,
29
- decision_text TEXT,
30
- confidence REAL,
31
- payload_json TEXT NOT NULL
32
- );
33
- `);
34
- }
35
-
36
- async saveDecision(decision: DecisionContext): Promise<void> {
37
- this.db.prepare('INSERT OR REPLACE INTO decisions(decision_id, decision_json) VALUES (?,?)').run([decision.decision_id, JSON.stringify(decision)]);
38
- }
39
- async saveConstraintSet(constraintSet: ConstraintSet): Promise<void> {
40
- this.db.prepare('INSERT OR IGNORE INTO constraint_sets(constraint_set_id, atoms, summary) VALUES (?,?,?)').run([constraintSet.constraint_set_id, JSON.stringify(constraintSet.atoms), constraintSet.summary]);
41
- }
42
- async getConstraintSet(id: string): Promise<ConstraintSet | null> {
43
- const row = this.db.prepare('SELECT * FROM constraint_sets WHERE constraint_set_id=?').get(id) as any;
44
- return row ? { constraint_set_id: row.constraint_set_id, atoms: JSON.parse(row.atoms), summary: row.summary } : null;
45
- }
46
- async saveRecord(record: MinimalDecisionRecord | DecisionRecord): Promise<void> {
47
- if ("record_id" in record) {
48
- await this.saveLegacyRecord(record);
49
- return;
50
- }
51
- this.db.prepare('INSERT OR REPLACE INTO records(record_id, decision_id, agent_id, domain, project_id, question_text, decision_text, payload_json) VALUES (?,?,?,?,?,?,?,?)').run([record.decision_id, record.decision_id, record.agent_id, record.domain, this.projectId, record.question_text ?? null, record.decision_text, JSON.stringify(record)]);
52
- }
53
- async saveLegacyRecord(record: DecisionRecord): Promise<void> {
54
- this.db.prepare('INSERT OR REPLACE INTO records(record_id, decision_id, agent_id, domain, project_id, question_text, decision_text, confidence, payload_json) VALUES (?,?,?,?,?,?,?,?,?)').run([record.record_id, record.decision_id, record.agent_id, record.domain, this.projectId, record.question, record.answer, record.confidence, JSON.stringify(record)]);
55
- }
56
- async getRecord(recordId: string): Promise<MinimalDecisionRecord | DecisionRecord | null> {
57
- const row = this.db.prepare('SELECT payload_json FROM records WHERE record_id=? OR decision_id=?').get([recordId, recordId]) as any;
58
- return row ? JSON.parse(row.payload_json) : null;
59
- }
60
- async searchRecords(opts: SearchOptions): Promise<Array<MinimalDecisionRecord | DecisionRecord>> {
61
- let rows = this.db.prepare('SELECT payload_json, decision_id, project_id, confidence, question_text, decision_text, domain FROM records').all() as any[];
62
- if (this.defaultIsolation === 'strict' || opts.includeShared === false) rows = rows.filter(r => r.project_id === this.projectId);
63
- if (opts.domain) rows = rows.filter(r => r.domain === opts.domain);
64
- if (typeof opts.minConfidence === 'number') rows = rows.filter(r => r.confidence == null || r.confidence >= opts.minConfidence!);
65
- if (opts.query) {
66
- const q=opts.query.toLowerCase();
67
- rows=rows.filter(r => { const p = JSON.parse(r.payload_json); const extra = p.constraints_snapshot ? `${p.constraints_snapshot.map((c:any)=>c.description).join(' ')} ${p.evidence_snapshot.map((e:any)=>`${e.claim} ${e.proof}`).join(' ')}` : ''; return `${r.question_text ?? ''} ${r.decision_text ?? ''} ${r.domain ?? ''} ${extra}`.toLowerCase().includes(q); });
68
- }
69
- let parsed = rows.map(r => JSON.parse(r.payload_json));
70
- if (opts.parentPrompt) {
71
- const needle = opts.parentPrompt.toLowerCase();
72
- parsed = parsed.filter((r: any) => {
73
- const d = this.db.prepare('SELECT decision_json FROM decisions WHERE decision_id=?').get(r.decision_id) as any;
74
- if (!d) return false;
75
- const ctx = JSON.parse(d.decision_json) as DecisionContext;
76
- return ctx.parent_prompt?.toLowerCase().includes(needle) ?? false;
77
- });
78
- }
79
- return opts.limit ? parsed.slice(0, opts.limit) : parsed;
80
- }
81
- async getAgentDecisions(agentId: string, domain?: string): Promise<Array<MinimalDecisionRecord | DecisionRecord>> {
82
- const rows = this.db.prepare('SELECT payload_json FROM records WHERE agent_id=?').all([agentId]) as any[];
83
- const parsed = rows.map(r => JSON.parse(r.payload_json));
84
- return domain ? parsed.filter(r => r.domain === domain) : parsed;
85
- }
86
- async getCompetencyProfile(agentId: string): Promise<CompetencyProfile> {
87
- const records = await this.getAgentDecisions(agentId);
88
- const stats = new Map<string, {count:number; conf:number}>();
89
- for (const r of records) { const c=stats.get(r.domain) ?? {count:0, conf:0}; c.count +=1; c.conf += ("confidence" in r ? r.confidence : 0.8); stats.set(r.domain,c); }
90
- const domains=[...stats.keys()];
91
- const strengths=domains.filter(d=>{const s=stats.get(d)!; return (s.conf/s.count)>=0.7;});
92
- const weaknesses=domains.filter(d=>{const s=stats.get(d)!; return (s.conf/s.count)<0.7;});
93
- return { agent_id: agentId, domains, strengths, weaknesses, updated_at: new Date().toISOString() };
94
- }
95
- }
package/src/types.ts DELETED
@@ -1,50 +0,0 @@
1
- import type { CompetencyProfile, DecisionRecord, DecisionStatus, ConstraintSet } from '@learningnodes/elen-core';
2
-
3
- export interface ElenConfig {
4
- agentId: string;
5
- projectId?: string;
6
- storage?: 'memory' | 'sqlite';
7
- sqlitePath?: string;
8
- apiUrl?: string;
9
- apiKey?: string;
10
- defaultProjectIsolation?: 'strict' | 'open';
11
- }
12
-
13
- export interface CommitDecisionInput {
14
- question: string;
15
- domain: string;
16
- decisionText: string;
17
- constraints: string[];
18
- refs?: string[];
19
- status?: DecisionStatus;
20
- supersedesId?: string;
21
- }
22
-
23
- export interface LogDecisionInput {
24
- question: string;
25
- domain: string;
26
- constraints: string[];
27
- evidence: string[];
28
- confidence?: number[];
29
- answer: string;
30
- parentPrompt?: string;
31
- linkedPrecedents?: string[];
32
- }
33
-
34
- export interface SearchOptions {
35
- domain?: string;
36
- projectId?: string;
37
- includeShared?: boolean;
38
- query?: string;
39
- limit?: number;
40
- minConfidence?: number;
41
- parentPrompt?: string;
42
- }
43
-
44
- export interface SearchPrecedentsOptions {
45
- limit?: number;
46
- }
47
-
48
- export type DecisionRecordResult = DecisionRecord;
49
- export type CompetencyProfileResult = CompetencyProfile;
50
- export type ExpandedDecision = { record: DecisionRecord; constraints: ConstraintSet };
package/test_output.txt DELETED
@@ -1,78 +0,0 @@
1
- 
2
-  RUN  v2.1.9 C:/Users/ln_ni/OneDrive/Desktop/Desktop/ventures/learningnodes/git/marketplace-repos/Elen/packages/sdk-ts
3
-
4
- Γ¥» tests/integration.test.ts (2 tests | 2 failed) 52ms
5
-  × Elen integration > supports batch decision logging and precedent search in memory mode 11ms
6
-  → expected 0 to be greater than 0
7
-  × Elen integration > supports sqlite-backed usage 39ms
8
-  → EBUSY: resource busy or locked, unlink 'C:\Users\ln_ni\AppData\Local\Temp\elen-int-1771463766853.db'
9
-
10
- node.exe : ⎯⎯⎯⎯⎯⎯⎯
11
- Failed Tests 2
12
- ⎯⎯⎯⎯⎯⎯⎯
13
- At C:\Users\ln_ni\AppData\Roaming\npm\npx.ps1:24
14
- char:5
15
- + & "node$exe"
16
- "$basedir/node_modules/npm/bin/npx-cli.js" $args
17
- + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
18
- ~~~~~~~~~~~~~~~~~~~
19
- + CategoryInfo : NotSpecified: ([3
20
- 1mΓÄ»ΓÄ»ΓÄ»Γ...»ΓÄ»ΓÄ»ΓÄ»:String) [], R
21
- emoteException
22
- + FullyQualifiedErrorId : NativeCommandError
23
-
24
-
25
-  FAIL 
26
- tests/integration.test.ts > Elen
27
- integration > supports batch decision
28
- logging and precedent search in memory mode
29
- AssertionError: expected 0 to be
30
- greater than 0
31
-  Γ¥»
32
- tests/integration.test.ts:29:31
33
-  27| 
34
-  28|  const
35
- precedents = await elen.
36
- searchPrecedents('high
37
- concurrency d…
38
-  29|  expect(precedent
39
- s.length).toBeGreaterTha
40
- n(0);
41
-  | 
42
- ^
43
-  30|  });
44
-  31| 
45
-
46
- ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»Γ
47
- Ä»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»[1/2]ΓÄ»[39
48
- m
49
-
50
-  FAIL 
51
- tests/integration.test.ts > Elen
52
- integration > supports sqlite-backed
53
- usage
54
- Error: EBUSY: resource busy or
55
- locked, unlink 'C:\Users\ln_ni\AppData\Local\Temp
56
- \elen-int-1771463766853.db'
57
-  Γ¥»
58
- tests/integration.test.ts:47:5
59
-  45|  expect(records)
60
- [33m.toHaveLength(1)[33
61
- m;
62
-  46| 
63
-  47| 
64
- rmSync(dbPath, {
65
- force: true });
66
-  |  ^
67
-  48|  });
68
-  49| });
69
-
70
- ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»Γ
71
- Ä»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»[2/2]ΓÄ»[39
72
- m
73
-
74
-  Test Files  1 failed (1)
75
-  Tests  2 failed (2)
76
-  Start at  12:16:06
77
-  Duration  619ms (transform 125ms, setup 0ms, collect 193ms, tests 52ms, environment 0ms, prepare 118ms)
78
-
@@ -1,147 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { ElenClient } from '../src/client';
3
- import { createId } from '../src/id';
4
- import { InMemoryStorage } from '../src/storage';
5
-
6
- describe('ElenClient', () => {
7
- it('logDecision creates a valid DecisionRecord', async () => {
8
- const client = new ElenClient('agent-a', new InMemoryStorage());
9
-
10
- const record = await client.logDecision({
11
- question: 'Which DB?',
12
- domain: 'infrastructure',
13
- constraints: ['Must be open-source'],
14
- evidence: ['benchmark: PostgreSQL 3200 TPS'],
15
- answer: 'PostgreSQL'
16
- });
17
-
18
- expect(record.record_id.startsWith('rec-')).toBe(true);
19
- expect(record.validation_type).toBe('self');
20
- expect(record.constraints_snapshot).toHaveLength(1);
21
- expect(record.evidence_snapshot).toHaveLength(1);
22
- expect(record.checks_snapshot).toHaveLength(1);
23
- });
24
-
25
- it('logDecision throws if no constraints provided', async () => {
26
- const client = new ElenClient('agent-a', new InMemoryStorage());
27
-
28
- await expect(
29
- client.logDecision({
30
- question: 'Which DB?',
31
- domain: 'infrastructure',
32
- constraints: [],
33
- evidence: ['benchmark evidence'],
34
- answer: 'PostgreSQL'
35
- })
36
- ).rejects.toThrow('at least one constraint');
37
- });
38
-
39
- it('logDecision throws if no evidence provided', async () => {
40
- const client = new ElenClient('agent-a', new InMemoryStorage());
41
-
42
- await expect(
43
- client.logDecision({
44
- question: 'Which DB?',
45
- domain: 'infrastructure',
46
- constraints: ['Must scale'],
47
- evidence: [],
48
- answer: 'PostgreSQL'
49
- })
50
- ).rejects.toThrow('at least one evidence');
51
- });
52
-
53
- it('logDecision auto-classifies epistemic types', async () => {
54
- const client = new ElenClient('agent-a', new InMemoryStorage());
55
-
56
- const record = await client.logDecision({
57
- question: 'Which DB?',
58
- domain: 'infrastructure',
59
- constraints: ['Scale'],
60
- evidence: ['benchmark measured 3200 TPS'],
61
- answer: 'PostgreSQL'
62
- });
63
-
64
- expect(record.checks_snapshot[0].epistemic_type).toBe('empirical');
65
- });
66
-
67
- it('searchRecords filters by domain, minConfidence and parentPrompt', async () => {
68
- const client = new ElenClient('agent-a', new InMemoryStorage());
69
-
70
- await client.logDecision({
71
- question: 'Which DB?',
72
- domain: 'infrastructure',
73
- parentPrompt: 'Build auth system',
74
- constraints: ['Scale'],
75
- evidence: ['benchmark 3200 TPS'],
76
- answer: 'PostgreSQL'
77
- });
78
-
79
- await client.logDecision({
80
- question: 'Which color?',
81
- domain: 'design',
82
- parentPrompt: 'Design system refresh',
83
- constraints: ['Accessible'],
84
- evidence: ['usually blue works'],
85
- confidence: [0.5],
86
- answer: 'Blue'
87
- });
88
-
89
- const byDomain = await client.searchRecords({ domain: 'infrastructure' });
90
- const byConfidence = await client.searchRecords({ minConfidence: 0.8 });
91
- const byParent = await client.searchRecords({ parentPrompt: 'auth system' });
92
-
93
- expect(byDomain).toHaveLength(1);
94
- expect(byConfidence).toHaveLength(1);
95
- expect(byParent).toHaveLength(1);
96
- expect(byParent[0].domain).toBe('infrastructure');
97
- });
98
-
99
- it('competency profile is computed from records', async () => {
100
- const client = new ElenClient('agent-a', new InMemoryStorage());
101
-
102
- await client.logDecision({
103
- question: 'DB?',
104
- domain: 'infrastructure',
105
- constraints: ['Scale'],
106
- evidence: ['benchmark 3200 TPS'],
107
- confidence: [0.9],
108
- answer: 'PostgreSQL'
109
- });
110
-
111
- await client.logDecision({
112
- question: 'UI color?',
113
- domain: 'design',
114
- constraints: ['Accessible'],
115
- evidence: ['rule of thumb says blue'],
116
- confidence: [0.5],
117
- answer: 'Blue'
118
- });
119
-
120
- const profile = await client.getCompetencyProfile();
121
-
122
- expect(profile.domains).toEqual(expect.arrayContaining(['infrastructure', 'design']));
123
- expect(profile.strengths).toContain('infrastructure');
124
- expect(profile.weaknesses).toContain('design');
125
- });
126
-
127
- it('stores linked precedents in evidence', async () => {
128
- const client = new ElenClient('agent-a', new InMemoryStorage());
129
-
130
- const record = await client.logDecision({
131
- question: 'Which pooler?',
132
- domain: 'infrastructure',
133
- constraints: ['Must work with PostgreSQL'],
134
- evidence: ['PostgreSQL selected per precedent'],
135
- linkedPrecedents: ['rec-abc123'],
136
- answer: 'PgBouncer'
137
- });
138
-
139
- expect(record.evidence_snapshot[0].linked_precedent).toBe('rec-abc123');
140
- expect(record.evidence_snapshot[0].type).toBe('precedent');
141
- });
142
-
143
- it('ID generation produces unique IDs', () => {
144
- const ids = new Set(Array.from({ length: 300 }, () => createId('rec')));
145
- expect(ids.size).toBe(300);
146
- });
147
- });
@@ -1,49 +0,0 @@
1
- import { rmSync } from 'node:fs';
2
- import { join } from 'node:path';
3
- import { tmpdir } from 'node:os';
4
- import { describe, expect, it } from 'vitest';
5
- import { Elen } from '../src';
6
-
7
- describe('Elen integration', () => {
8
- it('supports batch decision logging and precedent search in memory mode', async () => {
9
- const elen = new Elen({ agentId: 'agent-int', storage: 'memory' });
10
-
11
- const first = await elen.logDecision({
12
- question: 'Which database for sessions?',
13
- domain: 'infrastructure',
14
- constraints: ['Must support >1000 writes'],
15
- evidence: ['pgbench: PostgreSQL 3200 TPS vs SQLite 280 TPS'],
16
- answer: 'PostgreSQL 16'
17
- });
18
-
19
- await elen.logDecision({
20
- question: 'Which pooler?',
21
- domain: 'infrastructure',
22
- constraints: ['Must work with PostgreSQL'],
23
- evidence: ['PostgreSQL selected per precedent'],
24
- linkedPrecedents: [first.record_id],
25
- answer: 'PgBouncer'
26
- });
27
-
28
- const precedents = await elen.searchPrecedents('high concurrency database selection');
29
- expect(precedents.length).toBeGreaterThan(0);
30
- });
31
-
32
- it('supports sqlite-backed usage', async () => {
33
- const dbPath = join(tmpdir(), `elen-int-${Date.now()}.db`);
34
- const elen = new Elen({ agentId: 'agent-sql', storage: 'sqlite', sqlitePath: dbPath });
35
-
36
- await elen.logDecision({
37
- question: 'Which cache?',
38
- domain: 'infrastructure',
39
- constraints: ['Must be open-source'],
40
- evidence: ['documentation recommends Redis for this workload'],
41
- answer: 'Redis'
42
- });
43
-
44
- const records = await elen.searchRecords({ query: 'redis' });
45
- expect(records).toHaveLength(1);
46
-
47
- rmSync(dbPath, { force: true });
48
- });
49
- });
@@ -1,100 +0,0 @@
1
- import { rmSync } from 'node:fs';
2
- import { join } from 'node:path';
3
- import { tmpdir } from 'node:os';
4
- import { describe, expect, it } from 'vitest';
5
- import type { DecisionContext, DecisionRecord } from '@learningnodes/elen-core';
6
- import { InMemoryStorage, SQLiteStorage } from '../src/storage';
7
-
8
- function fixtures(): { context: DecisionContext; record: DecisionRecord } {
9
- const decisionId = 'dec-1';
10
- const context: DecisionContext = {
11
- decision_id: decisionId,
12
- agent_id: 'agent-a',
13
- question: 'Which DB?',
14
- domain: 'infrastructure',
15
- status: 'validated',
16
- constraints: [
17
- {
18
- constraint_id: 'con-1',
19
- decision_id: decisionId,
20
- type: 'requirement',
21
- description: 'Must scale',
22
- locked: false
23
- }
24
- ],
25
- evidence: [
26
- {
27
- evidence_id: 'evd-1',
28
- decision_id: decisionId,
29
- type: 'benchmark',
30
- claim: 'Postgres faster',
31
- proof: 'benchmark 3200 TPS',
32
- confidence: 0.9
33
- }
34
- ],
35
- checks: [
36
- {
37
- check_id: 'chk-1',
38
- decision_id: decisionId,
39
- claim: 'Postgres faster',
40
- result: 'pass',
41
- evidence_ids: ['evd-1'],
42
- epistemic_type: 'empirical',
43
- confidence: 0.9
44
- }
45
- ],
46
- created_at: new Date().toISOString(),
47
- parent_prompt: 'Build auth system'
48
- };
49
-
50
- const record: DecisionRecord = {
51
- record_id: 'rec-1',
52
- decision_id: decisionId,
53
- agent_id: 'agent-a',
54
- question: 'Which DB?',
55
- answer: 'PostgreSQL',
56
- constraints_snapshot: context.constraints,
57
- evidence_snapshot: context.evidence,
58
- checks_snapshot: context.checks,
59
- confidence: 0.9,
60
- validation_type: 'self',
61
- domain: 'infrastructure',
62
- tags: ['database'],
63
- published_at: new Date().toISOString(),
64
- expires_at: null
65
- };
66
-
67
- return { context, record };
68
- }
69
-
70
- describe('Storage adapters', () => {
71
- it('InMemoryStorage supports CRUD/search/profile', async () => {
72
- const storage = new InMemoryStorage();
73
- const { context, record } = fixtures();
74
-
75
- await storage.saveDecision(context);
76
- await storage.saveRecord(record);
77
-
78
- expect(await storage.getRecord('rec-1')).not.toBeNull();
79
- expect(await storage.searchRecords({ domain: 'infrastructure' })).toHaveLength(1);
80
- expect(await storage.searchRecords({ parentPrompt: 'auth' })).toHaveLength(1);
81
- expect((await storage.getCompetencyProfile('agent-a')).strengths).toContain('infrastructure');
82
- });
83
-
84
- it('SQLiteStorage supports CRUD/search/profile', async () => {
85
- const dbPath = join(tmpdir(), `elen-sdk-${Date.now()}.db`);
86
- const storage = new SQLiteStorage(dbPath);
87
- const { context, record } = fixtures();
88
-
89
- await storage.saveDecision(context);
90
- await storage.saveRecord(record);
91
-
92
- expect(await storage.getRecord('rec-1')).not.toBeNull();
93
- expect(await storage.searchRecords({ domain: 'infrastructure' })).toHaveLength(1);
94
- expect(await storage.searchRecords({ minConfidence: 0.8 })).toHaveLength(1);
95
- expect(await storage.searchRecords({ parentPrompt: 'auth system' })).toHaveLength(1);
96
- expect((await storage.getCompetencyProfile('agent-a')).domains).toContain('infrastructure');
97
-
98
- rmSync(dbPath, { force: true });
99
- });
100
- });
package/tsconfig.json DELETED
@@ -1,16 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2020",
4
- "module": "CommonJS",
5
- "moduleResolution": "Node",
6
- "declaration": true,
7
- "outDir": "dist",
8
- "rootDir": "src",
9
- "strict": true,
10
- "esModuleInterop": true,
11
- "skipLibCheck": true,
12
- "forceConsistentCasingInFileNames": true,
13
- "baseUrl": "."
14
- },
15
- "include": ["src"]
16
- }