@cccarv82/freya 2.3.13 → 2.5.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 (37) hide show
  1. package/.agent/rules/freya/agents/coach.mdc +7 -16
  2. package/.agent/rules/freya/agents/ingestor.mdc +1 -89
  3. package/.agent/rules/freya/agents/master.mdc +3 -0
  4. package/.agent/rules/freya/agents/oracle.mdc +7 -23
  5. package/cli/web-ui.css +965 -182
  6. package/cli/web-ui.js +551 -173
  7. package/cli/web.js +863 -536
  8. package/package.json +7 -4
  9. package/scripts/build-vector-index.js +85 -0
  10. package/scripts/export-obsidian.js +6 -16
  11. package/scripts/generate-blockers-report.js +5 -17
  12. package/scripts/generate-daily-summary.js +25 -58
  13. package/scripts/generate-executive-report.js +22 -204
  14. package/scripts/generate-sm-weekly-report.js +27 -92
  15. package/scripts/lib/DataLayer.js +92 -0
  16. package/scripts/lib/DataManager.js +198 -0
  17. package/scripts/lib/Embedder.js +59 -0
  18. package/scripts/lib/schema.js +23 -0
  19. package/scripts/migrate-v1-v2.js +184 -0
  20. package/scripts/validate-data.js +48 -51
  21. package/scripts/validate-structure.js +12 -58
  22. package/templates/base/scripts/build-vector-index.js +85 -0
  23. package/templates/base/scripts/export-obsidian.js +143 -0
  24. package/templates/base/scripts/generate-daily-summary.js +25 -58
  25. package/templates/base/scripts/generate-executive-report.js +14 -225
  26. package/templates/base/scripts/generate-sm-weekly-report.js +9 -91
  27. package/templates/base/scripts/index/build-index.js +13 -0
  28. package/templates/base/scripts/index/update-index.js +15 -0
  29. package/templates/base/scripts/lib/DataLayer.js +92 -0
  30. package/templates/base/scripts/lib/DataManager.js +198 -0
  31. package/templates/base/scripts/lib/Embedder.js +59 -0
  32. package/templates/base/scripts/lib/index-utils.js +407 -0
  33. package/templates/base/scripts/lib/schema.js +23 -0
  34. package/templates/base/scripts/lib/search-utils.js +183 -0
  35. package/templates/base/scripts/migrate-v1-v2.js +184 -0
  36. package/templates/base/scripts/validate-data.js +48 -51
  37. package/templates/base/scripts/validate-structure.js +10 -32
@@ -1,61 +1,39 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
3
 
4
- const { toIsoDate, safeParseToMs, isWithinRange } = require('./lib/date-utils');
5
- const { safeReadJson, quarantineCorruptedFile } = require('./lib/fs-utils');
4
+ const { toIsoDate, safeParseToMs } = require('./lib/date-utils');
5
+ const DataManager = require('./lib/DataManager');
6
6
 
7
7
  const DATA_DIR = path.join(__dirname, '../data');
8
+ const LOGS_DIR = path.join(__dirname, '../logs/daily');
8
9
  const REPORT_DIR = path.join(__dirname, '../docs/reports');
9
10
 
10
- const TASKS_FILE = path.join(DATA_DIR, 'tasks/task-log.json');
11
- const BLOCKERS_FILE = path.join(DATA_DIR, 'blockers/blocker-log.json');
12
- const CLIENTS_DIR = path.join(DATA_DIR, 'Clients');
13
-
14
- const SEV_ORDER = { CRITICAL: 0, HIGH: 1, MEDIUM: 2, LOW: 3 };
11
+ const dm = new DataManager(DATA_DIR, LOGS_DIR);
15
12
 
16
13
  function ensureDir(dir) {
17
14
  if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
18
15
  }
19
16
 
20
- function readJsonOrQuarantine(filePath, fallback) {
21
- if (!fs.existsSync(filePath)) return fallback;
22
- const res = safeReadJson(filePath);
23
- if (!res.ok) {
24
- if (res.error.type === 'parse') {
25
- quarantineCorruptedFile(filePath, res.error.message);
26
- console.warn(`⚠️ JSON parse failed; quarantined: ${filePath}`);
27
- } else {
28
- console.warn(`⚠️ JSON read failed: ${filePath}: ${res.error.message}`);
29
- }
30
- return fallback;
31
- }
32
- return res.json;
33
- }
34
-
35
17
  function daysBetweenMs(aMs, bMs) {
36
18
  const diff = Math.max(0, bMs - aMs);
37
19
  return Math.floor(diff / (24 * 60 * 60 * 1000));
38
20
  }
39
21
 
40
- function scanProjectStatusFiles() {
41
- const results = [];
42
- if (!fs.existsSync(CLIENTS_DIR)) return results;
43
-
44
- const clients = fs.readdirSync(CLIENTS_DIR);
45
- for (const clientSlug of clients) {
46
- const clientPath = path.join(CLIENTS_DIR, clientSlug);
47
- if (!fs.statSync(clientPath).isDirectory()) continue;
48
-
49
- const projects = fs.readdirSync(clientPath);
50
- for (const projectSlug of projects) {
51
- const projectPath = path.join(clientPath, projectSlug);
52
- if (!fs.statSync(projectPath).isDirectory()) continue;
22
+ function normalizeStatus(blocker) {
23
+ const raw = blocker.status || blocker.state || blocker.currentStatus;
24
+ if (!raw) return 'UNKNOWN';
25
+ return String(raw).trim().toUpperCase();
26
+ }
53
27
 
54
- const statusPath = path.join(projectPath, 'status.json');
55
- if (fs.existsSync(statusPath)) results.push(statusPath);
56
- }
57
- }
58
- return results;
28
+ function normalizeSeverity(blocker) {
29
+ const raw = blocker.severity || blocker.priority || blocker.level;
30
+ if (!raw) return 'UNSPECIFIED';
31
+ const value = String(raw).trim().toUpperCase();
32
+ if (value.includes('CRIT')) return 'CRITICAL';
33
+ if (value.includes('HIGH')) return 'HIGH';
34
+ if (value.includes('MED')) return 'MEDIUM';
35
+ if (value.includes('LOW')) return 'LOW';
36
+ return value;
59
37
  }
60
38
 
61
39
  function generate() {
@@ -67,40 +45,15 @@ function generate() {
67
45
  start.setDate(now.getDate() - 7);
68
46
 
69
47
  const reportDate = toIsoDate(now);
70
- const reportTime = (() => { const d = new Date(); const hh = String(d.getHours()).padStart(2,'0'); const mm = String(d.getMinutes()).padStart(2,'0'); const ss = String(d.getSeconds()).padStart(2,'0'); return `${hh}${mm}${ss}`; })();
71
-
72
- const taskLog = readJsonOrQuarantine(TASKS_FILE, { schemaVersion: 1, tasks: [] });
73
- const blockersLog = readJsonOrQuarantine(BLOCKERS_FILE, { schemaVersion: 1, blockers: [] });
48
+ const reportTime = (() => { const d = new Date(); const hh = String(d.getHours()).padStart(2, '0'); const mm = String(d.getMinutes()).padStart(2, '0'); const ss = String(d.getSeconds()).padStart(2, '0'); return `${hh}${mm}${ss}`; })();
74
49
 
75
- const tasks = taskLog.tasks || [];
76
- const blockers = blockersLog.blockers || [];
77
-
78
- const completedTasks = tasks.filter(t => {
79
- if (t.status !== 'COMPLETED') return false;
80
- return isWithinRange(t.completedAt || t.completed_at, start, end);
81
- });
82
-
83
- const pendingDoNow = tasks.filter(t => t.status === 'PENDING' && t.category === 'DO_NOW');
84
-
85
- const activeBlockers = blockers.filter(b => {
86
- const st = String(b.status || '').toUpperCase();
87
- return st === 'OPEN' || st === 'MITIGATING';
88
- });
89
-
90
- activeBlockers.sort((a, b) => {
91
- const sa = SEV_ORDER[String(a.severity || '').toUpperCase()] ?? 99;
92
- const sb = SEV_ORDER[String(b.severity || '').toUpperCase()] ?? 99;
93
- if (sa !== sb) return sa - sb;
94
- const ta = safeParseToMs(a.createdAt) || 0;
95
- const tb = safeParseToMs(b.createdAt) || 0;
96
- return ta - tb; // older first
97
- });
50
+ const { completed: completedTasks, pending: pendingDoNow } = dm.getTasks(start, end);
51
+ const { open: activeBlockers, blockers } = dm.getBlockers(start, end);
98
52
 
99
53
  const upcomingDueBlockers = blockers.filter(b => {
100
- const st = String(b.status || '').toUpperCase();
54
+ const st = normalizeStatus(b);
101
55
  if (st !== 'OPEN') return false;
102
56
  if (!b.dueDate) return false;
103
- // dueDate is an ISO date; treat as UTC midnight
104
57
  const dueMs = safeParseToMs(String(b.dueDate));
105
58
  if (!Number.isFinite(dueMs)) return false;
106
59
  const due = new Date(dueMs);
@@ -109,25 +62,7 @@ const reportTime = (() => { const d = new Date(); const hh = String(d.getHours()
109
62
  return due >= now && due <= soonEnd;
110
63
  });
111
64
 
112
- // Projects
113
- const statusFiles = scanProjectStatusFiles();
114
- const projectsWithUpdates = [];
115
-
116
- for (const statusPath of statusFiles) {
117
- const proj = readJsonOrQuarantine(statusPath, null);
118
- if (!proj) continue;
119
-
120
- const history = Array.isArray(proj.history) ? proj.history : [];
121
- const recent = history.filter(h => isWithinRange(h.date || h.timestamp, start, end));
122
- if (recent.length === 0) continue;
123
-
124
- projectsWithUpdates.push({
125
- client: proj.client || path.basename(path.dirname(path.dirname(statusPath))),
126
- project: proj.project || path.basename(path.dirname(statusPath)),
127
- currentStatus: proj.currentStatus,
128
- recent
129
- });
130
- }
65
+ const projectsWithUpdates = dm.getProjectUpdates(start, end);
131
66
 
132
67
  // Markdown
133
68
  let md = `# Scrum Master Weekly Report — ${reportDate}\n`;
@@ -153,13 +88,13 @@ const reportTime = (() => { const d = new Date(); const hh = String(d.getHours()
153
88
  md += `None.\n\n`;
154
89
  } else {
155
90
  activeBlockers.forEach(b => {
156
- const createdMs = safeParseToMs(b.createdAt) || Date.now();
91
+ const createdMs = safeParseToMs(b.created_at || b.createdAt) || Date.now();
157
92
  const agingDays = daysBetweenMs(createdMs, Date.now());
158
- const sev = String(b.severity || '').toUpperCase();
159
- const st = String(b.status || '').toUpperCase();
93
+ const sev = normalizeSeverity(b);
94
+ const st = normalizeStatus(b);
160
95
  const owner = b.owner ? `; owner: ${b.owner}` : '';
161
96
  const proj = b.projectSlug ? `; project: ${b.projectSlug}` : '';
162
- const next = b.nextAction ? `; next: ${b.nextAction}` : '';
97
+ const next = b.next_action || b.nextAction ? `; next: ${b.next_action || b.nextAction}` : '';
163
98
  md += `- [${sev}/${st}] ${b.title} (aging: ${agingDays}d${owner}${proj}${next})\n`;
164
99
  });
165
100
  md += `\n`;
@@ -0,0 +1,92 @@
1
+ const Database = require('better-sqlite3');
2
+ const path = require('path');
3
+ const fs = require('fs');
4
+
5
+ class DataLayer {
6
+ constructor(dbPath = null) {
7
+ if (!dbPath) {
8
+ const dataDir = path.join(__dirname, '..', '..', 'data');
9
+ if (!fs.existsSync(dataDir)) {
10
+ fs.mkdirSync(dataDir, { recursive: true });
11
+ }
12
+ dbPath = path.join(dataDir, 'freya.sqlite');
13
+ }
14
+ this.db = new Database(dbPath, { verbose: console.log });
15
+ this.initSchema();
16
+ }
17
+
18
+ initSchema() {
19
+ // Enable Write-Ahead Logging for better concurrent performance
20
+ this.db.pragma('journal_mode = WAL');
21
+
22
+ this.db.exec(`
23
+ CREATE TABLE IF NOT EXISTS projects (
24
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
25
+ slug TEXT UNIQUE NOT NULL,
26
+ client TEXT,
27
+ name TEXT,
28
+ is_active BOOLEAN DEFAULT 1,
29
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
30
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
31
+ );
32
+
33
+ CREATE TABLE IF NOT EXISTS project_status_history (
34
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
35
+ project_id INTEGER NOT NULL,
36
+ status_text TEXT NOT NULL,
37
+ date DATETIME DEFAULT CURRENT_TIMESTAMP,
38
+ FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE
39
+ );
40
+
41
+ CREATE TABLE IF NOT EXISTS tasks (
42
+ id TEXT PRIMARY KEY, /* UUID */
43
+ project_slug TEXT, /* Can be null, or match projects.slug */
44
+ description TEXT NOT NULL,
45
+ category TEXT NOT NULL, /* DO_NOW, SCHEDULE, DELEGATE, IGNORE */
46
+ status TEXT NOT NULL, /* PENDING, COMPLETED, ARCHIVED */
47
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
48
+ completed_at DATETIME,
49
+ metadata TEXT /* JSON string for extra fields like priority, streamSlug */
50
+ );
51
+
52
+ CREATE TABLE IF NOT EXISTS blockers (
53
+ id TEXT PRIMARY KEY, /* UUID */
54
+ project_slug TEXT,
55
+ title TEXT NOT NULL,
56
+ severity TEXT NOT NULL, /* CRITICAL, HIGH, MEDIUM, LOW */
57
+ status TEXT NOT NULL, /* OPEN, MITIGATING, RESOLVED, ARCHIVED */
58
+ owner TEXT,
59
+ next_action TEXT,
60
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
61
+ resolved_at DATETIME,
62
+ metadata TEXT /* JSON string */
63
+ );
64
+
65
+ CREATE TABLE IF NOT EXISTS daily_logs (
66
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
67
+ date TEXT UNIQUE NOT NULL, /* YYYY-MM-DD */
68
+ raw_markdown TEXT NOT NULL,
69
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
70
+ );
71
+
72
+ CREATE TABLE IF NOT EXISTS document_embeddings (
73
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
74
+ reference_type TEXT NOT NULL, /* 'daily_log', 'task', 'blocker' */
75
+ reference_id TEXT NOT NULL,
76
+ chunk_index INTEGER DEFAULT 0,
77
+ text_chunk TEXT NOT NULL,
78
+ embedding BLOB NOT NULL, /* Stored as Buffer of Float32Array */
79
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
80
+ );
81
+ `);
82
+ }
83
+
84
+ // Helper close method
85
+ close() {
86
+ this.db.close();
87
+ }
88
+ }
89
+
90
+ // Export a singleton instance by default, or the class for testing
91
+ const defaultInstance = new DataLayer();
92
+ module.exports = { defaultInstance, DataLayer };
@@ -0,0 +1,198 @@
1
+ /**
2
+ * DataManager.js (V2 - SQLite Powered)
3
+ * Centralized data access layer for F.R.E.Y.A. reports and scripts.
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const { isWithinRange, formatDate } = require('./date-utils');
9
+ const SCHEMA = require('./schema');
10
+ const { defaultInstance: dl } = require('./DataLayer');
11
+ const { defaultEmbedder } = require('./Embedder');
12
+
13
+ class DataManager {
14
+ constructor(dataDir, logsDir) {
15
+ // Keeping args for backwards compatibility, though SQLite handles the storage now
16
+ this.dataDir = dataDir;
17
+ this.logsDir = logsDir;
18
+ }
19
+
20
+ // --- Helpers (Deprecated but kept for backwards compat if needed elsewhere) ---
21
+ readJsonOrQuarantine(filePath, fallback) {
22
+ return fallback;
23
+ }
24
+
25
+ // --- Tasks ---
26
+ getTasksRaw() {
27
+ return dl.db.prepare('SELECT * FROM tasks').all().map(t => {
28
+ const meta = t.metadata ? JSON.parse(t.metadata) : {};
29
+ return {
30
+ id: t.id,
31
+ projectSlug: t.project_slug,
32
+ description: t.description,
33
+ category: t.category,
34
+ status: t.status,
35
+ createdAt: t.created_at,
36
+ completedAt: t.completed_at,
37
+ priority: meta.priority,
38
+ streamSlug: meta.streamSlug
39
+ };
40
+ });
41
+ }
42
+
43
+ getTasks(start, end) {
44
+ // SQLite date func: use ISO format
45
+ const toIso = (d) => d ? (d instanceof Date ? d.toISOString() : new Date(d).toISOString()) : null;
46
+ const startIso = toIso(start);
47
+ const endIso = toIso(end);
48
+
49
+ const tasks = this.getTasksRaw();
50
+ const completed = dl.db.prepare(`
51
+ SELECT * FROM tasks
52
+ WHERE status = 'COMPLETED'
53
+ AND completed_at >= ? AND completed_at <= ?
54
+ `).all(startIso, endIso).map(t => ({ ...t, completedAt: t.completed_at, createdAt: t.created_at }));
55
+
56
+ const pending = dl.db.prepare(`
57
+ SELECT * FROM tasks
58
+ WHERE status = 'PENDING' AND category = 'DO_NOW'
59
+ `).all().map(t => ({ ...t, createdAt: t.created_at }));
60
+
61
+ return { tasks, completed, pending };
62
+ }
63
+
64
+ // --- Blockers ---
65
+ getBlockersRaw() {
66
+ return dl.db.prepare('SELECT * FROM blockers').all().map(b => {
67
+ const meta = b.metadata ? JSON.parse(b.metadata) : {};
68
+ return {
69
+ id: b.id,
70
+ projectSlug: b.project_slug,
71
+ title: b.title,
72
+ severity: b.severity,
73
+ status: b.status,
74
+ owner: b.owner,
75
+ nextAction: b.next_action,
76
+ createdAt: b.created_at,
77
+ resolvedAt: b.resolved_at,
78
+ streamSlug: meta.streamSlug
79
+ };
80
+ });
81
+ }
82
+
83
+ getBlockers(start, end) {
84
+ const toIso = (d) => d ? (d instanceof Date ? d.toISOString() : new Date(d).toISOString()) : null;
85
+ const startIso = toIso(start);
86
+ const endIso = toIso(end);
87
+
88
+ const blockers = this.getBlockersRaw();
89
+
90
+ const open = dl.db.prepare(`
91
+ SELECT * FROM blockers
92
+ WHERE status NOT IN ('RESOLVED', 'CLOSED', 'DONE', 'FIXED')
93
+ AND resolved_at IS NULL
94
+ ORDER BY
95
+ CASE severity
96
+ WHEN 'CRITICAL' THEN 0
97
+ WHEN 'HIGH' THEN 1
98
+ WHEN 'MEDIUM' THEN 2
99
+ WHEN 'LOW' THEN 3
100
+ ELSE 99
101
+ END ASC,
102
+ created_at ASC
103
+ `).all().map(b => ({ ...b, projectSlug: b.project_slug, createdAt: b.created_at }));
104
+
105
+ const openedRecent = dl.db.prepare(`
106
+ SELECT * FROM blockers
107
+ WHERE created_at >= ? AND created_at <= ?
108
+ `).all(startIso, endIso).map(b => ({ ...b, projectSlug: b.project_slug, createdAt: b.created_at }));
109
+
110
+ const resolvedRecent = dl.db.prepare(`
111
+ SELECT * FROM blockers
112
+ WHERE resolved_at >= ? AND resolved_at <= ?
113
+ `).all(startIso, endIso).map(b => ({ ...b, projectSlug: b.project_slug, resolvedAt: b.resolved_at }));
114
+
115
+ return { blockers, open, openedRecent, resolvedRecent };
116
+ }
117
+
118
+ // --- Project Updates ---
119
+ getProjectUpdates(start, end) {
120
+ const toIso = (d) => d ? (d instanceof Date ? d.toISOString() : new Date(d).toISOString()) : null;
121
+ const startIso = toIso(start);
122
+ const endIso = toIso(end);
123
+
124
+ const updates = [];
125
+
126
+ // Find active projects with recent history
127
+ const activeProjects = dl.db.prepare(`SELECT * FROM projects WHERE is_active = 1`).all();
128
+
129
+ for (const proj of activeProjects) {
130
+ const recentHistory = dl.db.prepare(`
131
+ SELECT status_text, date FROM project_status_history
132
+ WHERE project_id = ? AND date >= ? AND date <= ?
133
+ ORDER BY date DESC
134
+ `).all(proj.id, startIso, endIso).map(h => ({
135
+ content: h.status_text,
136
+ date: h.date
137
+ }));
138
+
139
+ // We determine currentStatus as the latest from history, or a placeholder if none
140
+ const latest = dl.db.prepare(`
141
+ SELECT status_text FROM project_status_history
142
+ WHERE project_id = ? ORDER BY date DESC LIMIT 1
143
+ `).get(proj.id);
144
+
145
+ const currentStatus = latest ? latest.status_text : "Initialized";
146
+
147
+ if (recentHistory.length > 0) {
148
+ updates.push({
149
+ client: proj.client,
150
+ project: proj.name,
151
+ slug: proj.slug,
152
+ currentStatus: currentStatus,
153
+ events: recentHistory
154
+ });
155
+ }
156
+ }
157
+
158
+ return updates;
159
+ }
160
+
161
+ // --- Daily Logs ---
162
+ getDailyLogs(start, end) {
163
+ function toIso(d) { return (d instanceof Date ? d : new Date(d)).toISOString().slice(0, 10); }
164
+ const startIso = formatDate ? formatDate(start) : toIso(start);
165
+ const endIso = formatDate ? formatDate(end) : toIso(end);
166
+
167
+ return dl.db.prepare(`
168
+ SELECT date, raw_markdown as content FROM daily_logs
169
+ WHERE date >= ? AND date <= ?
170
+ ORDER BY date ASC
171
+ `).all(startIso, endIso);
172
+ }
173
+
174
+ // --- RAG (Vector Search) ---
175
+ async semanticSearch(query, topK = 10) {
176
+ const queryVector = await defaultEmbedder.embedText(query);
177
+
178
+ // Fetch all stored embeddings. For a local personal tool with < 100k chunks, in-memory cosine sim is perfectly fast.
179
+ const rows = dl.db.prepare(`
180
+ SELECT reference_type, reference_id, chunk_index, text_chunk, embedding
181
+ FROM document_embeddings
182
+ `).all();
183
+
184
+ const scored = [];
185
+ for (const row of rows) {
186
+ const chunkVector = defaultEmbedder.bufferToVector(row.embedding);
187
+ const score = defaultEmbedder.cosineSimilarity(queryVector, chunkVector);
188
+ scored.push({ ...row, score });
189
+ }
190
+
191
+ // Sort pure descending by similarity score
192
+ scored.sort((a, b) => b.score - a.score);
193
+
194
+ return scored.slice(0, topK);
195
+ }
196
+ }
197
+
198
+ module.exports = DataManager;
@@ -0,0 +1,59 @@
1
+ const { pipeline } = require('@xenova/transformers');
2
+
3
+ class Embedder {
4
+ constructor() {
5
+ this.modelName = 'Xenova/all-MiniLM-L6-v2';
6
+ this.extractorInfo = null;
7
+ this.initPromise = null;
8
+ }
9
+
10
+ async init() {
11
+ if (this.extractorInfo) return;
12
+ if (!this.initPromise) {
13
+ this.initPromise = pipeline('feature-extraction', this.modelName, { quantized: true })
14
+ .then((ex) => {
15
+ this.extractorInfo = ex;
16
+ })
17
+ .catch((err) => {
18
+ this.initPromise = null;
19
+ throw err;
20
+ });
21
+ }
22
+ await this.initPromise;
23
+ }
24
+
25
+ async embedText(text) {
26
+ await this.init();
27
+ const cleanText = String(text || '').trim();
28
+ if (!cleanText) return new Float32Array(384); // Empty zero vector for model
29
+
30
+ const output = await this.extractorInfo(cleanText, { pooling: 'mean', normalize: true });
31
+ return new Float32Array(output.data);
32
+ }
33
+
34
+ // Prepares a Float32Array into a Node.js Buffer for SQLite BLOB storage
35
+ vectorToBuffer(float32Array) {
36
+ return Buffer.from(float32Array.buffer);
37
+ }
38
+
39
+ // Parses a SQLite BLOB Buffer back into a Float32Array
40
+ bufferToVector(buffer) {
41
+ return new Float32Array(buffer.buffer, buffer.byteOffset, buffer.length / Float32Array.BYTES_PER_ELEMENT);
42
+ }
43
+
44
+ cosineSimilarity(vecA, vecB) {
45
+ let dotProduct = 0;
46
+ let normA = 0;
47
+ let normB = 0;
48
+ for (let i = 0; i < vecA.length; i++) {
49
+ dotProduct += vecA[i] * vecB[i];
50
+ normA += vecA[i] * vecA[i];
51
+ normB += vecB[i] * vecB[i];
52
+ }
53
+ if (normA === 0 || normB === 0) return 0;
54
+ return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
55
+ }
56
+ }
57
+
58
+ const defaultEmbedder = new Embedder();
59
+ module.exports = { Embedder, defaultEmbedder };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * schema.js
3
+ * Centralized schema constants for F.R.E.Y.A. data structures.
4
+ * This is the single source of truth for validation and ingestion.
5
+ */
6
+
7
+ module.exports = {
8
+ TASK: {
9
+ CATEGORIES: ['DO_NOW', 'SCHEDULE', 'DELEGATE', 'IGNORE'],
10
+ STATUSES: ['PENDING', 'COMPLETED', 'ARCHIVED'],
11
+ PRIORITIES: ['LOW', 'MEDIUM', 'HIGH', 'CRITICAL']
12
+ },
13
+ BLOCKER: {
14
+ STATUSES: ['OPEN', 'MITIGATING', 'RESOLVED'],
15
+ SEVERITIES: ['LOW', 'MEDIUM', 'HIGH', 'CRITICAL']
16
+ },
17
+ CAREER: {
18
+ TYPES: ['Achievement', 'Feedback', 'Certification', 'Goal']
19
+ },
20
+ PROJECT: {
21
+ HISTORY_TYPES: ['Status', 'Decision', 'Risk', 'Achievement', 'Feedback', 'Goal', 'Blocker']
22
+ }
23
+ };