@cccarv82/freya 2.5.7 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/web.js +2 -1
- package/package.json +2 -3
- package/scripts/build-vector-index.js +2 -1
- package/scripts/export-obsidian.js +4 -3
- package/scripts/generate-blockers-report.js +4 -2
- package/scripts/generate-daily-summary.js +3 -1
- package/scripts/generate-executive-report.js +4 -2
- package/scripts/generate-sm-weekly-report.js +4 -2
- package/scripts/lib/DataLayer.js +209 -8
- package/scripts/lib/Embedder.js +7 -10
- package/scripts/migrate-v1-v2.js +15 -10
- package/scripts/validate-structure.js +4 -3
- package/templates/base/scripts/lib/DataLayer.js +209 -8
- package/templates/base/scripts/lib/Embedder.js +7 -10
package/cli/web.js
CHANGED
|
@@ -8,7 +8,7 @@ const { spawn } = require('child_process');
|
|
|
8
8
|
const { searchWorkspace } = require('../scripts/lib/search-utils');
|
|
9
9
|
const { searchIndex } = require('../scripts/lib/index-utils');
|
|
10
10
|
const { initWorkspace } = require('./init');
|
|
11
|
-
const { defaultInstance: dl } = require('../scripts/lib/DataLayer');
|
|
11
|
+
const { defaultInstance: dl, ready } = require('../scripts/lib/DataLayer');
|
|
12
12
|
const DataManager = require('../scripts/lib/DataManager');
|
|
13
13
|
|
|
14
14
|
function readAppVersion() {
|
|
@@ -2237,6 +2237,7 @@ function seedDevWorkspace(workspaceDir) {
|
|
|
2237
2237
|
}
|
|
2238
2238
|
|
|
2239
2239
|
async function cmdWeb({ port, dir, open, dev }) {
|
|
2240
|
+
await ready;
|
|
2240
2241
|
const host = '127.0.0.1';
|
|
2241
2242
|
|
|
2242
2243
|
const server = http.createServer(async (req, res) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cccarv82/freya",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0",
|
|
4
4
|
"description": "Personal AI Assistant with local-first persistence",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"health": "node scripts/validate-data.js && node scripts/validate-structure.js",
|
|
@@ -32,8 +32,7 @@
|
|
|
32
32
|
"preferGlobal": true,
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@xenova/transformers": "^2.17.2",
|
|
35
|
-
"better-sqlite3": "^12.6.2",
|
|
36
35
|
"pdf-lib": "^1.17.1",
|
|
37
|
-
"
|
|
36
|
+
"sql.js": "^1.12.0"
|
|
38
37
|
}
|
|
39
38
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { defaultInstance: dl } = require('./lib/DataLayer');
|
|
1
|
+
const { defaultInstance: dl, ready } = require('./lib/DataLayer');
|
|
2
2
|
const { defaultEmbedder } = require('./lib/Embedder');
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -27,6 +27,7 @@ function chunkText(text, maxChars = 800, overlap = 150) {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
async function buildVectorIndex() {
|
|
30
|
+
await ready;
|
|
30
31
|
console.log('[RAG] Booting Embedding Engine...');
|
|
31
32
|
await defaultEmbedder.init();
|
|
32
33
|
console.log('[RAG] Model ready.');
|
|
@@ -11,7 +11,7 @@ function ensureDir(p) {
|
|
|
11
11
|
fs.mkdirSync(p, { recursive: true });
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
const { defaultInstance: dl } = require('./lib/DataLayer');
|
|
14
|
+
const { defaultInstance: dl, ready } = require('./lib/DataLayer');
|
|
15
15
|
const DataManager = require('./lib/DataManager');
|
|
16
16
|
|
|
17
17
|
function slugifyFileName(s) {
|
|
@@ -43,7 +43,8 @@ function writeNote(baseDir, relPathNoExt, md) {
|
|
|
43
43
|
return outPath;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
function main() {
|
|
46
|
+
async function main() {
|
|
47
|
+
await ready;
|
|
47
48
|
const workspaceDir = path.resolve(process.cwd());
|
|
48
49
|
|
|
49
50
|
const dm = new DataManager(path.join(workspaceDir, 'data'), path.join(workspaceDir, 'logs'));
|
|
@@ -140,4 +141,4 @@ function main() {
|
|
|
140
141
|
process.stdout.write(JSON.stringify({ ok: true, created: created.map((p) => path.relative(workspaceDir, p).replace(/\\/g, '/')) }, null, 2) + '\n');
|
|
141
142
|
}
|
|
142
143
|
|
|
143
|
-
main();
|
|
144
|
+
main().catch(err => { console.error(err); process.exit(1); });
|
|
@@ -4,6 +4,7 @@ const path = require('path');
|
|
|
4
4
|
const { toIsoDate, safeParseToMs, isWithinRange } = require('./lib/date-utils');
|
|
5
5
|
|
|
6
6
|
const DataManager = require('./lib/DataManager');
|
|
7
|
+
const { ready } = require('./lib/DataLayer');
|
|
7
8
|
const REPORT_DIR = path.join(__dirname, '../docs/reports');
|
|
8
9
|
|
|
9
10
|
const SEVERITY_ORDER = {
|
|
@@ -95,7 +96,8 @@ function ensureReportDir() {
|
|
|
95
96
|
}
|
|
96
97
|
}
|
|
97
98
|
|
|
98
|
-
function generateReport() {
|
|
99
|
+
async function generateReport() {
|
|
100
|
+
await ready;
|
|
99
101
|
const now = new Date();
|
|
100
102
|
const nowMs = now.getTime();
|
|
101
103
|
const reportDate = toIsoDate(now);
|
|
@@ -201,4 +203,4 @@ function generateReport() {
|
|
|
201
203
|
console.log(report);
|
|
202
204
|
}
|
|
203
205
|
|
|
204
|
-
generateReport();
|
|
206
|
+
generateReport().catch(err => { console.error(err); process.exit(1); });
|
|
@@ -2,6 +2,7 @@ const fs = require('fs');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
|
|
4
4
|
const DataManager = require('./lib/DataManager');
|
|
5
|
+
const { ready } = require('./lib/DataLayer');
|
|
5
6
|
|
|
6
7
|
const DATA_DIR = path.join(__dirname, '../data');
|
|
7
8
|
const LOGS_DIR = path.join(__dirname, '../logs/daily');
|
|
@@ -9,7 +10,8 @@ const REPORT_DIR = path.join(__dirname, '../docs/reports');
|
|
|
9
10
|
|
|
10
11
|
const dm = new DataManager(DATA_DIR, LOGS_DIR);
|
|
11
12
|
|
|
12
|
-
function generateDailySummary() {
|
|
13
|
+
async function generateDailySummary() {
|
|
14
|
+
await ready;
|
|
13
15
|
try {
|
|
14
16
|
const now = new Date();
|
|
15
17
|
const start = new Date(now);
|
|
@@ -11,6 +11,7 @@ const path = require('path');
|
|
|
11
11
|
|
|
12
12
|
const { toIsoDate, safeParseToMs } = require('./lib/date-utils');
|
|
13
13
|
const DataManager = require('./lib/DataManager');
|
|
14
|
+
const { ready } = require('./lib/DataLayer');
|
|
14
15
|
|
|
15
16
|
// --- Configuration ---
|
|
16
17
|
const DATA_DIR = path.join(__dirname, '../data');
|
|
@@ -110,7 +111,8 @@ function getCreatedAt(blocker) {
|
|
|
110
111
|
|
|
111
112
|
// --- Report Generation ---
|
|
112
113
|
|
|
113
|
-
function generateReport(period) {
|
|
114
|
+
async function generateReport(period) {
|
|
115
|
+
await ready;
|
|
114
116
|
const { start, end } = getDateRange(period);
|
|
115
117
|
const dateStr = formatDate(new Date());
|
|
116
118
|
|
|
@@ -245,4 +247,4 @@ if (!['daily', 'weekly'].includes(period)) {
|
|
|
245
247
|
process.exit(1);
|
|
246
248
|
}
|
|
247
249
|
|
|
248
|
-
generateReport(period);
|
|
250
|
+
generateReport(period).catch(err => { console.error(err); process.exit(1); });
|
|
@@ -3,6 +3,7 @@ const path = require('path');
|
|
|
3
3
|
|
|
4
4
|
const { toIsoDate, safeParseToMs } = require('./lib/date-utils');
|
|
5
5
|
const DataManager = require('./lib/DataManager');
|
|
6
|
+
const { ready } = require('./lib/DataLayer');
|
|
6
7
|
|
|
7
8
|
const DATA_DIR = path.join(__dirname, '../data');
|
|
8
9
|
const LOGS_DIR = path.join(__dirname, '../logs/daily');
|
|
@@ -36,7 +37,8 @@ function normalizeSeverity(blocker) {
|
|
|
36
37
|
return value;
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
function generate() {
|
|
40
|
+
async function generate() {
|
|
41
|
+
await ready;
|
|
40
42
|
ensureDir(REPORT_DIR);
|
|
41
43
|
|
|
42
44
|
const now = new Date();
|
|
@@ -140,4 +142,4 @@ function generate() {
|
|
|
140
142
|
console.log(`\nSaved: ${outPath}`);
|
|
141
143
|
}
|
|
142
144
|
|
|
143
|
-
generate();
|
|
145
|
+
generate().catch(err => { console.error(err); process.exit(1); });
|
package/scripts/lib/DataLayer.js
CHANGED
|
@@ -1,7 +1,175 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* DataLayer.js (V2.1 - sql.js powered, no native compilation needed)
|
|
3
|
+
*
|
|
4
|
+
* Drop-in replacement for better-sqlite3 using sql.js (WebAssembly SQLite).
|
|
5
|
+
* Exposes the same API surface: .prepare().all(), .prepare().get(), .prepare().run(),
|
|
6
|
+
* .exec(), .pragma(), .transaction(), .close()
|
|
7
|
+
*
|
|
8
|
+
* Auto-persists to disk after every write operation.
|
|
9
|
+
*/
|
|
10
|
+
|
|
2
11
|
const path = require('path');
|
|
3
12
|
const fs = require('fs');
|
|
4
13
|
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Compatibility wrapper: makes sql.js look like better-sqlite3
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
class PreparedStatement {
|
|
19
|
+
constructor(wrapper, sql) {
|
|
20
|
+
this._wrapper = wrapper;
|
|
21
|
+
this._sql = sql;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Execute and return all matching rows as an array of plain objects.
|
|
26
|
+
* Mirrors better-sqlite3's stmt.all(...params)
|
|
27
|
+
*/
|
|
28
|
+
all(...params) {
|
|
29
|
+
const db = this._wrapper._db;
|
|
30
|
+
let stmt;
|
|
31
|
+
try {
|
|
32
|
+
stmt = db.prepare(this._sql);
|
|
33
|
+
if (params.length > 0) {
|
|
34
|
+
stmt.bind(this._flattenParams(params));
|
|
35
|
+
}
|
|
36
|
+
const results = [];
|
|
37
|
+
while (stmt.step()) {
|
|
38
|
+
results.push(stmt.getAsObject());
|
|
39
|
+
}
|
|
40
|
+
return results;
|
|
41
|
+
} finally {
|
|
42
|
+
if (stmt) stmt.free();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Execute and return the first matching row, or undefined.
|
|
48
|
+
* Mirrors better-sqlite3's stmt.get(...params)
|
|
49
|
+
*/
|
|
50
|
+
get(...params) {
|
|
51
|
+
const db = this._wrapper._db;
|
|
52
|
+
let stmt;
|
|
53
|
+
try {
|
|
54
|
+
stmt = db.prepare(this._sql);
|
|
55
|
+
if (params.length > 0) {
|
|
56
|
+
stmt.bind(this._flattenParams(params));
|
|
57
|
+
}
|
|
58
|
+
if (stmt.step()) {
|
|
59
|
+
return stmt.getAsObject();
|
|
60
|
+
}
|
|
61
|
+
return undefined;
|
|
62
|
+
} finally {
|
|
63
|
+
if (stmt) stmt.free();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Execute a statement (INSERT/UPDATE/DELETE) and return { changes }.
|
|
69
|
+
* Mirrors better-sqlite3's stmt.run(...params)
|
|
70
|
+
*/
|
|
71
|
+
run(...params) {
|
|
72
|
+
const db = this._wrapper._db;
|
|
73
|
+
db.run(this._sql, this._flattenParams(params));
|
|
74
|
+
const changes = db.getRowsModified();
|
|
75
|
+
this._wrapper._save();
|
|
76
|
+
return { changes };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Flatten params: better-sqlite3 accepts (a, b, c) while sql.js wants [a, b, c].
|
|
81
|
+
* Also handles the case where a single array is passed (already flat).
|
|
82
|
+
*/
|
|
83
|
+
_flattenParams(params) {
|
|
84
|
+
if (params.length === 1 && Array.isArray(params[0])) {
|
|
85
|
+
return params[0];
|
|
86
|
+
}
|
|
87
|
+
return params;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
class SqlJsDatabase {
|
|
92
|
+
constructor(sqlJsDb, filePath) {
|
|
93
|
+
this._db = sqlJsDb;
|
|
94
|
+
this._filePath = filePath;
|
|
95
|
+
this._saveScheduled = false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Persist the in-memory database to disk.
|
|
100
|
+
*/
|
|
101
|
+
_save() {
|
|
102
|
+
if (!this._filePath) return;
|
|
103
|
+
try {
|
|
104
|
+
const data = this._db.export();
|
|
105
|
+
const buffer = Buffer.from(data);
|
|
106
|
+
fs.writeFileSync(this._filePath, buffer);
|
|
107
|
+
} catch (err) {
|
|
108
|
+
console.error('[DataLayer] Failed to save database:', err.message);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* PRAGMA support. sql.js supports PRAGMA through exec.
|
|
114
|
+
*/
|
|
115
|
+
pragma(str) {
|
|
116
|
+
try {
|
|
117
|
+
this._db.run(`PRAGMA ${str}`);
|
|
118
|
+
} catch {
|
|
119
|
+
// Some pragmas may not be supported in WASM mode (e.g., WAL), silently ignore.
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Execute raw SQL (typically DDL).
|
|
125
|
+
* Mirrors better-sqlite3's db.exec(sql)
|
|
126
|
+
*/
|
|
127
|
+
exec(sql) {
|
|
128
|
+
this._db.run(sql);
|
|
129
|
+
this._save();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Create a prepared statement wrapper.
|
|
134
|
+
* Mirrors better-sqlite3's db.prepare(sql)
|
|
135
|
+
*/
|
|
136
|
+
prepare(sql) {
|
|
137
|
+
return new PreparedStatement(this, sql);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Transaction wrapper. Returns a function that, when called, wraps fn() in BEGIN/COMMIT.
|
|
142
|
+
* Mirrors better-sqlite3's db.transaction(fn)
|
|
143
|
+
*/
|
|
144
|
+
transaction(fn) {
|
|
145
|
+
const self = this;
|
|
146
|
+
return function (...args) {
|
|
147
|
+
self._db.run('BEGIN TRANSACTION');
|
|
148
|
+
try {
|
|
149
|
+
const result = fn(...args);
|
|
150
|
+
self._db.run('COMMIT');
|
|
151
|
+
self._save();
|
|
152
|
+
return result;
|
|
153
|
+
} catch (e) {
|
|
154
|
+
try { self._db.run('ROLLBACK'); } catch { /* already rolled back */ }
|
|
155
|
+
throw e;
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Close the database.
|
|
162
|
+
*/
|
|
163
|
+
close() {
|
|
164
|
+
this._save();
|
|
165
|
+
this._db.close();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
// DataLayer class
|
|
171
|
+
// ---------------------------------------------------------------------------
|
|
172
|
+
|
|
5
173
|
class DataLayer {
|
|
6
174
|
constructor(dbPath = null) {
|
|
7
175
|
if (!dbPath) {
|
|
@@ -11,12 +179,34 @@ class DataLayer {
|
|
|
11
179
|
}
|
|
12
180
|
dbPath = path.join(dataDir, 'freya.sqlite');
|
|
13
181
|
}
|
|
14
|
-
this.
|
|
15
|
-
this.
|
|
182
|
+
this._dbPath = dbPath;
|
|
183
|
+
this.db = null; // Will be set after async init
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Async initialization (required by sql.js WASM loading).
|
|
188
|
+
*/
|
|
189
|
+
async init() {
|
|
190
|
+
if (this.db) return; // Already initialized
|
|
191
|
+
|
|
192
|
+
const initSqlJs = require('sql.js');
|
|
193
|
+
const SQL = await initSqlJs();
|
|
194
|
+
|
|
195
|
+
// Load existing database from disk, or create new one
|
|
196
|
+
let sqlJsDb;
|
|
197
|
+
if (fs.existsSync(this._dbPath)) {
|
|
198
|
+
const fileBuffer = fs.readFileSync(this._dbPath);
|
|
199
|
+
sqlJsDb = new SQL.Database(fileBuffer);
|
|
200
|
+
} else {
|
|
201
|
+
sqlJsDb = new SQL.Database();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
this.db = new SqlJsDatabase(sqlJsDb, this._dbPath);
|
|
205
|
+
this._initSchema();
|
|
16
206
|
}
|
|
17
207
|
|
|
18
|
-
|
|
19
|
-
//
|
|
208
|
+
_initSchema() {
|
|
209
|
+
// WAL mode may not work in WASM — we try, but it's silently ignored if unsupported
|
|
20
210
|
this.db.pragma('journal_mode = WAL');
|
|
21
211
|
|
|
22
212
|
this.db.exec(`
|
|
@@ -83,10 +273,21 @@ class DataLayer {
|
|
|
83
273
|
|
|
84
274
|
// Helper close method
|
|
85
275
|
close() {
|
|
86
|
-
this.db.close();
|
|
276
|
+
if (this.db) this.db.close();
|
|
87
277
|
}
|
|
88
278
|
}
|
|
89
279
|
|
|
90
|
-
//
|
|
280
|
+
// ---------------------------------------------------------------------------
|
|
281
|
+
// Singleton + ready promise
|
|
282
|
+
// ---------------------------------------------------------------------------
|
|
283
|
+
|
|
91
284
|
const defaultInstance = new DataLayer();
|
|
92
|
-
|
|
285
|
+
|
|
286
|
+
// `ready` is a promise that resolves once sql.js is loaded and the DB is ready.
|
|
287
|
+
// All consumers must `await ready` before using `defaultInstance.db`.
|
|
288
|
+
const ready = defaultInstance.init().catch(err => {
|
|
289
|
+
console.error('[DataLayer] Fatal: Failed to initialize database:', err.message);
|
|
290
|
+
process.exit(1);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
module.exports = { defaultInstance, DataLayer, ready };
|
package/scripts/lib/Embedder.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
const { pipeline } = require('@xenova/transformers');
|
|
2
|
-
|
|
3
1
|
class Embedder {
|
|
4
2
|
constructor() {
|
|
5
3
|
this.modelName = 'Xenova/all-MiniLM-L6-v2';
|
|
@@ -10,14 +8,13 @@ class Embedder {
|
|
|
10
8
|
async init() {
|
|
11
9
|
if (this.extractorInfo) return;
|
|
12
10
|
if (!this.initPromise) {
|
|
13
|
-
this.initPromise =
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
});
|
|
11
|
+
this.initPromise = (async () => {
|
|
12
|
+
const { pipeline } = await import('@xenova/transformers');
|
|
13
|
+
this.extractorInfo = await pipeline('feature-extraction', this.modelName, { quantized: true });
|
|
14
|
+
})().catch((err) => {
|
|
15
|
+
this.initPromise = null;
|
|
16
|
+
throw err;
|
|
17
|
+
});
|
|
21
18
|
}
|
|
22
19
|
await this.initPromise;
|
|
23
20
|
}
|
package/scripts/migrate-v1-v2.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const { defaultInstance: dl } = require('./lib/DataLayer');
|
|
3
|
+
const { defaultInstance: dl, ready } = require('./lib/DataLayer');
|
|
4
4
|
const DataManager = require('./lib/DataManager');
|
|
5
5
|
|
|
6
6
|
const dataDir = path.join(__dirname, '..', 'data');
|
|
@@ -173,12 +173,17 @@ function migrateLogs() {
|
|
|
173
173
|
}
|
|
174
174
|
|
|
175
175
|
// Transaction wrapper
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
176
|
+
async function main() {
|
|
177
|
+
await ready;
|
|
178
|
+
dl.db.transaction(() => {
|
|
179
|
+
migrateProjects();
|
|
180
|
+
migrateTasks();
|
|
181
|
+
migrateBlockers();
|
|
182
|
+
migrateLogs();
|
|
183
|
+
})();
|
|
184
|
+
|
|
185
|
+
console.log('--- Migração concluída com sucesso! ---');
|
|
186
|
+
dl.close();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
main().catch(err => { console.error(err); process.exit(1); });
|
|
@@ -7,7 +7,7 @@ const DATA_DIR = path.join(ROOT, 'data');
|
|
|
7
7
|
const DOCS_DIR = path.join(ROOT, 'docs');
|
|
8
8
|
const CLIENTS_DIR = path.join(DATA_DIR, 'Clients');
|
|
9
9
|
|
|
10
|
-
const { defaultInstance: dl } = require('./lib/DataLayer');
|
|
10
|
+
const { defaultInstance: dl, ready } = require('./lib/DataLayer');
|
|
11
11
|
const errors = [];
|
|
12
12
|
|
|
13
13
|
function exists(p) {
|
|
@@ -88,7 +88,8 @@ function validateDocsHubs() {
|
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
function main() {
|
|
91
|
+
async function main() {
|
|
92
|
+
await ready;
|
|
92
93
|
// validateDailyLogs(); removed context since migrated to SQLite
|
|
93
94
|
validateProjectStatusHistory();
|
|
94
95
|
validateTaskProjectSlugs();
|
|
@@ -102,4 +103,4 @@ function main() {
|
|
|
102
103
|
console.log('✅ Structure validation passed');
|
|
103
104
|
}
|
|
104
105
|
|
|
105
|
-
main();
|
|
106
|
+
main().catch(err => { console.error(err); process.exit(1); });
|
|
@@ -1,7 +1,175 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* DataLayer.js (V2.1 - sql.js powered, no native compilation needed)
|
|
3
|
+
*
|
|
4
|
+
* Drop-in replacement for better-sqlite3 using sql.js (WebAssembly SQLite).
|
|
5
|
+
* Exposes the same API surface: .prepare().all(), .prepare().get(), .prepare().run(),
|
|
6
|
+
* .exec(), .pragma(), .transaction(), .close()
|
|
7
|
+
*
|
|
8
|
+
* Auto-persists to disk after every write operation.
|
|
9
|
+
*/
|
|
10
|
+
|
|
2
11
|
const path = require('path');
|
|
3
12
|
const fs = require('fs');
|
|
4
13
|
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Compatibility wrapper: makes sql.js look like better-sqlite3
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
class PreparedStatement {
|
|
19
|
+
constructor(wrapper, sql) {
|
|
20
|
+
this._wrapper = wrapper;
|
|
21
|
+
this._sql = sql;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Execute and return all matching rows as an array of plain objects.
|
|
26
|
+
* Mirrors better-sqlite3's stmt.all(...params)
|
|
27
|
+
*/
|
|
28
|
+
all(...params) {
|
|
29
|
+
const db = this._wrapper._db;
|
|
30
|
+
let stmt;
|
|
31
|
+
try {
|
|
32
|
+
stmt = db.prepare(this._sql);
|
|
33
|
+
if (params.length > 0) {
|
|
34
|
+
stmt.bind(this._flattenParams(params));
|
|
35
|
+
}
|
|
36
|
+
const results = [];
|
|
37
|
+
while (stmt.step()) {
|
|
38
|
+
results.push(stmt.getAsObject());
|
|
39
|
+
}
|
|
40
|
+
return results;
|
|
41
|
+
} finally {
|
|
42
|
+
if (stmt) stmt.free();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Execute and return the first matching row, or undefined.
|
|
48
|
+
* Mirrors better-sqlite3's stmt.get(...params)
|
|
49
|
+
*/
|
|
50
|
+
get(...params) {
|
|
51
|
+
const db = this._wrapper._db;
|
|
52
|
+
let stmt;
|
|
53
|
+
try {
|
|
54
|
+
stmt = db.prepare(this._sql);
|
|
55
|
+
if (params.length > 0) {
|
|
56
|
+
stmt.bind(this._flattenParams(params));
|
|
57
|
+
}
|
|
58
|
+
if (stmt.step()) {
|
|
59
|
+
return stmt.getAsObject();
|
|
60
|
+
}
|
|
61
|
+
return undefined;
|
|
62
|
+
} finally {
|
|
63
|
+
if (stmt) stmt.free();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Execute a statement (INSERT/UPDATE/DELETE) and return { changes }.
|
|
69
|
+
* Mirrors better-sqlite3's stmt.run(...params)
|
|
70
|
+
*/
|
|
71
|
+
run(...params) {
|
|
72
|
+
const db = this._wrapper._db;
|
|
73
|
+
db.run(this._sql, this._flattenParams(params));
|
|
74
|
+
const changes = db.getRowsModified();
|
|
75
|
+
this._wrapper._save();
|
|
76
|
+
return { changes };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Flatten params: better-sqlite3 accepts (a, b, c) while sql.js wants [a, b, c].
|
|
81
|
+
* Also handles the case where a single array is passed (already flat).
|
|
82
|
+
*/
|
|
83
|
+
_flattenParams(params) {
|
|
84
|
+
if (params.length === 1 && Array.isArray(params[0])) {
|
|
85
|
+
return params[0];
|
|
86
|
+
}
|
|
87
|
+
return params;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
class SqlJsDatabase {
|
|
92
|
+
constructor(sqlJsDb, filePath) {
|
|
93
|
+
this._db = sqlJsDb;
|
|
94
|
+
this._filePath = filePath;
|
|
95
|
+
this._saveScheduled = false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Persist the in-memory database to disk.
|
|
100
|
+
*/
|
|
101
|
+
_save() {
|
|
102
|
+
if (!this._filePath) return;
|
|
103
|
+
try {
|
|
104
|
+
const data = this._db.export();
|
|
105
|
+
const buffer = Buffer.from(data);
|
|
106
|
+
fs.writeFileSync(this._filePath, buffer);
|
|
107
|
+
} catch (err) {
|
|
108
|
+
console.error('[DataLayer] Failed to save database:', err.message);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* PRAGMA support. sql.js supports PRAGMA through exec.
|
|
114
|
+
*/
|
|
115
|
+
pragma(str) {
|
|
116
|
+
try {
|
|
117
|
+
this._db.run(`PRAGMA ${str}`);
|
|
118
|
+
} catch {
|
|
119
|
+
// Some pragmas may not be supported in WASM mode (e.g., WAL), silently ignore.
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Execute raw SQL (typically DDL).
|
|
125
|
+
* Mirrors better-sqlite3's db.exec(sql)
|
|
126
|
+
*/
|
|
127
|
+
exec(sql) {
|
|
128
|
+
this._db.run(sql);
|
|
129
|
+
this._save();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Create a prepared statement wrapper.
|
|
134
|
+
* Mirrors better-sqlite3's db.prepare(sql)
|
|
135
|
+
*/
|
|
136
|
+
prepare(sql) {
|
|
137
|
+
return new PreparedStatement(this, sql);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Transaction wrapper. Returns a function that, when called, wraps fn() in BEGIN/COMMIT.
|
|
142
|
+
* Mirrors better-sqlite3's db.transaction(fn)
|
|
143
|
+
*/
|
|
144
|
+
transaction(fn) {
|
|
145
|
+
const self = this;
|
|
146
|
+
return function (...args) {
|
|
147
|
+
self._db.run('BEGIN TRANSACTION');
|
|
148
|
+
try {
|
|
149
|
+
const result = fn(...args);
|
|
150
|
+
self._db.run('COMMIT');
|
|
151
|
+
self._save();
|
|
152
|
+
return result;
|
|
153
|
+
} catch (e) {
|
|
154
|
+
try { self._db.run('ROLLBACK'); } catch { /* already rolled back */ }
|
|
155
|
+
throw e;
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Close the database.
|
|
162
|
+
*/
|
|
163
|
+
close() {
|
|
164
|
+
this._save();
|
|
165
|
+
this._db.close();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
// DataLayer class
|
|
171
|
+
// ---------------------------------------------------------------------------
|
|
172
|
+
|
|
5
173
|
class DataLayer {
|
|
6
174
|
constructor(dbPath = null) {
|
|
7
175
|
if (!dbPath) {
|
|
@@ -11,12 +179,34 @@ class DataLayer {
|
|
|
11
179
|
}
|
|
12
180
|
dbPath = path.join(dataDir, 'freya.sqlite');
|
|
13
181
|
}
|
|
14
|
-
this.
|
|
15
|
-
this.
|
|
182
|
+
this._dbPath = dbPath;
|
|
183
|
+
this.db = null; // Will be set after async init
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Async initialization (required by sql.js WASM loading).
|
|
188
|
+
*/
|
|
189
|
+
async init() {
|
|
190
|
+
if (this.db) return; // Already initialized
|
|
191
|
+
|
|
192
|
+
const initSqlJs = require('sql.js');
|
|
193
|
+
const SQL = await initSqlJs();
|
|
194
|
+
|
|
195
|
+
// Load existing database from disk, or create new one
|
|
196
|
+
let sqlJsDb;
|
|
197
|
+
if (fs.existsSync(this._dbPath)) {
|
|
198
|
+
const fileBuffer = fs.readFileSync(this._dbPath);
|
|
199
|
+
sqlJsDb = new SQL.Database(fileBuffer);
|
|
200
|
+
} else {
|
|
201
|
+
sqlJsDb = new SQL.Database();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
this.db = new SqlJsDatabase(sqlJsDb, this._dbPath);
|
|
205
|
+
this._initSchema();
|
|
16
206
|
}
|
|
17
207
|
|
|
18
|
-
|
|
19
|
-
//
|
|
208
|
+
_initSchema() {
|
|
209
|
+
// WAL mode may not work in WASM — we try, but it's silently ignored if unsupported
|
|
20
210
|
this.db.pragma('journal_mode = WAL');
|
|
21
211
|
|
|
22
212
|
this.db.exec(`
|
|
@@ -83,10 +273,21 @@ class DataLayer {
|
|
|
83
273
|
|
|
84
274
|
// Helper close method
|
|
85
275
|
close() {
|
|
86
|
-
this.db.close();
|
|
276
|
+
if (this.db) this.db.close();
|
|
87
277
|
}
|
|
88
278
|
}
|
|
89
279
|
|
|
90
|
-
//
|
|
280
|
+
// ---------------------------------------------------------------------------
|
|
281
|
+
// Singleton + ready promise
|
|
282
|
+
// ---------------------------------------------------------------------------
|
|
283
|
+
|
|
91
284
|
const defaultInstance = new DataLayer();
|
|
92
|
-
|
|
285
|
+
|
|
286
|
+
// `ready` is a promise that resolves once sql.js is loaded and the DB is ready.
|
|
287
|
+
// All consumers must `await ready` before using `defaultInstance.db`.
|
|
288
|
+
const ready = defaultInstance.init().catch(err => {
|
|
289
|
+
console.error('[DataLayer] Fatal: Failed to initialize database:', err.message);
|
|
290
|
+
process.exit(1);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
module.exports = { defaultInstance, DataLayer, ready };
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
const { pipeline } = require('@xenova/transformers');
|
|
2
|
-
|
|
3
1
|
class Embedder {
|
|
4
2
|
constructor() {
|
|
5
3
|
this.modelName = 'Xenova/all-MiniLM-L6-v2';
|
|
@@ -10,14 +8,13 @@ class Embedder {
|
|
|
10
8
|
async init() {
|
|
11
9
|
if (this.extractorInfo) return;
|
|
12
10
|
if (!this.initPromise) {
|
|
13
|
-
this.initPromise =
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
});
|
|
11
|
+
this.initPromise = (async () => {
|
|
12
|
+
const { pipeline } = await import('@xenova/transformers');
|
|
13
|
+
this.extractorInfo = await pipeline('feature-extraction', this.modelName, { quantized: true });
|
|
14
|
+
})().catch((err) => {
|
|
15
|
+
this.initPromise = null;
|
|
16
|
+
throw err;
|
|
17
|
+
});
|
|
21
18
|
}
|
|
22
19
|
await this.initPromise;
|
|
23
20
|
}
|