@cccarv82/freya 2.5.8 → 2.6.1

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/init.js CHANGED
@@ -64,7 +64,9 @@ function copyDirRecursive(srcDir, destDir, force, summary, options = {}) {
64
64
  continue;
65
65
  }
66
66
 
67
- copyDirRecursive(src, dest, force, summary, options);
67
+ // Always force-update scripts (app code, not user data)
68
+ const isScriptsDir = ent.name === 'scripts';
69
+ copyDirRecursive(src, dest, force || isScriptsDir, summary, options);
68
70
  continue;
69
71
  }
70
72
 
@@ -90,6 +92,10 @@ function ensurePackageJson(targetDir, force, summary) {
90
92
  blockers: 'node scripts/generate-blockers-report.js'
91
93
  };
92
94
 
95
+ const depsToEnsure = {
96
+ 'sql.js': '^1.12.0'
97
+ };
98
+
93
99
  if (!existing) {
94
100
  const name = path.basename(targetDir);
95
101
  const pkg = {
@@ -97,7 +103,8 @@ function ensurePackageJson(targetDir, force, summary) {
97
103
  private: true,
98
104
  version: '0.0.0',
99
105
  description: 'F.R.E.Y.A workspace',
100
- scripts: scriptsToEnsure
106
+ scripts: scriptsToEnsure,
107
+ dependencies: depsToEnsure
101
108
  };
102
109
  writeJson(pkgPath, pkg);
103
110
  summary.created.push('package.json');
@@ -114,6 +121,15 @@ function ensurePackageJson(targetDir, force, summary) {
114
121
  existing.scripts[k] = v;
115
122
  }
116
123
 
124
+ // Ensure dependencies
125
+ existing.dependencies = existing.dependencies || {};
126
+ for (const [k, v] of Object.entries(depsToEnsure)) {
127
+ existing.dependencies[k] = v;
128
+ }
129
+ // Remove deprecated native dependencies
130
+ delete existing.dependencies['better-sqlite3'];
131
+ delete existing.dependencies['sqlite3'];
132
+
117
133
  writeJson(pkgPath, existing);
118
134
  summary.updated.push('package.json');
119
135
  }
@@ -129,6 +145,7 @@ function formatInitSummary(summary, targetDir) {
129
145
  lines.push(`Skipped: ${summary.skipped.length}`);
130
146
  lines.push('');
131
147
  lines.push('Next steps:');
148
+ lines.push('- Run: npm install');
132
149
  lines.push('- Run: npm run health');
133
150
  lines.push('- (If upgrading old data) Run: npm run migrate');
134
151
  return lines.join('\n');
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.5.8",
3
+ "version": "2.6.1",
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
- "sqlite3": "^5.1.7"
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); });
@@ -1,7 +1,175 @@
1
- const Database = require('better-sqlite3');
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.db = new Database(dbPath);
15
- this.initSchema();
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
- initSchema() {
19
- // Enable Write-Ahead Logging for better concurrent performance
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
- // Export a singleton instance by default, or the class for testing
280
+ // ---------------------------------------------------------------------------
281
+ // Singleton + ready promise
282
+ // ---------------------------------------------------------------------------
283
+
91
284
  const defaultInstance = new DataLayer();
92
- module.exports = { defaultInstance, DataLayer };
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,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
- dl.db.transaction(() => {
177
- migrateProjects();
178
- migrateTasks();
179
- migrateBlockers();
180
- migrateLogs();
181
- })();
182
-
183
- console.log('--- Migração concluída com sucesso! ---');
184
- dl.close();
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,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); });
@@ -3,7 +3,8 @@ const path = require('path');
3
3
 
4
4
  const { toIsoDate, safeParseToMs, isWithinRange } = require('./lib/date-utils');
5
5
 
6
- const BLOCKERS_FILE = path.join(__dirname, '../data/blockers/blocker-log.json');
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,26 +96,15 @@ function ensureReportDir() {
95
96
  }
96
97
  }
97
98
 
98
- function loadBlockers() {
99
- if (!fs.existsSync(BLOCKERS_FILE)) {
100
- return [];
101
- }
102
- try {
103
- const raw = fs.readFileSync(BLOCKERS_FILE, 'utf8');
104
- const data = JSON.parse(raw);
105
- return Array.isArray(data.blockers) ? data.blockers : [];
106
- } catch (err) {
107
- console.error(`Error reading blockers file: ${err.message}`);
108
- return [];
109
- }
110
- }
111
-
112
- function generateReport() {
99
+ async function generateReport() {
100
+ await ready;
113
101
  const now = new Date();
114
102
  const nowMs = now.getTime();
115
103
  const reportDate = toIsoDate(now);
116
- 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}`; })();
117
- const blockers = loadBlockers();
104
+ 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}`; })();
105
+
106
+ const dm = new DataManager();
107
+ const blockers = dm.getBlockersRaw();
118
108
 
119
109
  const statusCounts = new Map();
120
110
  blockers.forEach(blocker => {
@@ -213,4 +203,4 @@ const reportTime = (() => { const d = new Date(); const hh = String(d.getHours()
213
203
  console.log(report);
214
204
  }
215
205
 
216
- 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');
@@ -79,9 +80,39 @@ function getBlockerTitle(blocker) {
79
80
  );
80
81
  }
81
82
 
83
+ function normalizeSeverity(blocker) {
84
+ const raw = blocker.severity || blocker.priority || blocker.level;
85
+ if (!raw) return 'UNSPECIFIED';
86
+ const value = String(raw).trim().toUpperCase();
87
+ if (value.includes('CRIT')) return 'CRITICAL';
88
+ if (value.includes('HIGH')) return 'HIGH';
89
+ if (value.includes('MED')) return 'MEDIUM';
90
+ if (value.includes('LOW')) return 'LOW';
91
+ return value;
92
+ }
93
+
94
+ function getCreatedAt(blocker) {
95
+ const candidates = [
96
+ blocker.createdAt,
97
+ blocker.created_at,
98
+ blocker.openedAt,
99
+ blocker.opened_at,
100
+ blocker.reportedAt,
101
+ blocker.reported_at,
102
+ blocker.date,
103
+ blocker.loggedAt,
104
+ ];
105
+ for (const value of candidates) {
106
+ const ms = safeParseToMs(value);
107
+ if (Number.isFinite(ms)) return ms;
108
+ }
109
+ return NaN;
110
+ }
111
+
82
112
  // --- Report Generation ---
83
113
 
84
- function generateReport(period) {
114
+ async function generateReport(period) {
115
+ await ready;
85
116
  const { start, end } = getDateRange(period);
86
117
  const dateStr = formatDate(new Date());
87
118
 
@@ -156,8 +187,8 @@ function generateReport(period) {
156
187
  md += `**Em aberto:**\n`;
157
188
  blockers.open.forEach(blocker => {
158
189
  const title = getBlockerTitle(blocker);
159
- const severity = dm.normalizeSeverity(blocker);
160
- const createdAt = dm.getCreatedAt(blocker);
190
+ const severity = normalizeSeverity(blocker);
191
+ const createdAt = getCreatedAt(blocker);
161
192
  const createdDate = Number.isFinite(createdAt) ? toIsoDate(createdAt) : 'Unknown';
162
193
  const project = blocker.project || blocker.projectName || blocker.projectSlug;
163
194
  const client = blocker.client || blocker.clientName || blocker.clientSlug;
@@ -216,4 +247,4 @@ if (!['daily', 'weekly'].includes(period)) {
216
247
  process.exit(1);
217
248
  }
218
249
 
219
- 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');
@@ -19,7 +20,25 @@ function daysBetweenMs(aMs, bMs) {
19
20
  return Math.floor(diff / (24 * 60 * 60 * 1000));
20
21
  }
21
22
 
22
- function generate() {
23
+ function normalizeStatus(blocker) {
24
+ const raw = blocker.status || blocker.state || blocker.currentStatus;
25
+ if (!raw) return 'UNKNOWN';
26
+ return String(raw).trim().toUpperCase();
27
+ }
28
+
29
+ function normalizeSeverity(blocker) {
30
+ const raw = blocker.severity || blocker.priority || blocker.level;
31
+ if (!raw) return 'UNSPECIFIED';
32
+ const value = String(raw).trim().toUpperCase();
33
+ if (value.includes('CRIT')) return 'CRITICAL';
34
+ if (value.includes('HIGH')) return 'HIGH';
35
+ if (value.includes('MED')) return 'MEDIUM';
36
+ if (value.includes('LOW')) return 'LOW';
37
+ return value;
38
+ }
39
+
40
+ async function generate() {
41
+ await ready;
23
42
  ensureDir(REPORT_DIR);
24
43
 
25
44
  const now = new Date();
@@ -34,7 +53,7 @@ function generate() {
34
53
  const { open: activeBlockers, blockers } = dm.getBlockers(start, end);
35
54
 
36
55
  const upcomingDueBlockers = blockers.filter(b => {
37
- const st = dm.normalizeStatus(b);
56
+ const st = normalizeStatus(b);
38
57
  if (st !== 'OPEN') return false;
39
58
  if (!b.dueDate) return false;
40
59
  const dueMs = safeParseToMs(String(b.dueDate));
@@ -71,13 +90,13 @@ function generate() {
71
90
  md += `None.\n\n`;
72
91
  } else {
73
92
  activeBlockers.forEach(b => {
74
- const createdMs = safeParseToMs(b.createdAt) || Date.now();
93
+ const createdMs = safeParseToMs(b.created_at || b.createdAt) || Date.now();
75
94
  const agingDays = daysBetweenMs(createdMs, Date.now());
76
- const sev = String(b.severity || '').toUpperCase();
77
- const st = String(b.status || '').toUpperCase();
95
+ const sev = normalizeSeverity(b);
96
+ const st = normalizeStatus(b);
78
97
  const owner = b.owner ? `; owner: ${b.owner}` : '';
79
98
  const proj = b.projectSlug ? `; project: ${b.projectSlug}` : '';
80
- const next = b.nextAction ? `; next: ${b.nextAction}` : '';
99
+ const next = b.next_action || b.nextAction ? `; next: ${b.next_action || b.nextAction}` : '';
81
100
  md += `- [${sev}/${st}] ${b.title} (aging: ${agingDays}d${owner}${proj}${next})\n`;
82
101
  });
83
102
  md += `\n`;
@@ -123,4 +142,4 @@ function generate() {
123
142
  console.log(`\nSaved: ${outPath}`);
124
143
  }
125
144
 
126
- generate();
145
+ generate().catch(err => { console.error(err); process.exit(1); });
@@ -1,7 +1,175 @@
1
- const Database = require('better-sqlite3');
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.db = new Database(dbPath);
15
- this.initSchema();
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
- initSchema() {
19
- // Enable Write-Ahead Logging for better concurrent performance
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
- // Export a singleton instance by default, or the class for testing
280
+ // ---------------------------------------------------------------------------
281
+ // Singleton + ready promise
282
+ // ---------------------------------------------------------------------------
283
+
91
284
  const defaultInstance = new DataLayer();
92
- module.exports = { defaultInstance, DataLayer };
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,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
- dl.db.transaction(() => {
177
- migrateProjects();
178
- migrateTasks();
179
- migrateBlockers();
180
- migrateLogs();
181
- })();
182
-
183
- console.log('--- Migração concluída com sucesso! ---');
184
- dl.close();
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) {
@@ -45,31 +45,7 @@ function parseFrontmatter(text) {
45
45
  return fm;
46
46
  }
47
47
 
48
- function validateDailyLogs() {
49
- if (!exists(LOGS_DIR)) return;
50
- const files = fs.readdirSync(LOGS_DIR)
51
- .filter((f) => /^\d{4}-\d{2}-\d{2}\.md$/.test(f));
52
-
53
- for (const name of files) {
54
- const full = path.join(LOGS_DIR, name);
55
- const body = readFileSafe(full);
56
- const fm = parseFrontmatter(body);
57
- const date = name.replace(/\.md$/, '');
58
- if (!fm) {
59
- errors.push(`Daily log missing frontmatter: ${path.relative(ROOT, full)}`);
60
- continue;
61
- }
62
- const type = String(fm.Type || '').toLowerCase();
63
- const fmDate = String(fm.Date || '').trim();
64
- if (type !== 'daily') {
65
- errors.push(`Daily log frontmatter Type must be 'daily': ${path.relative(ROOT, full)}`);
66
- }
67
- if (fmDate !== date) {
68
- errors.push(`Daily log frontmatter Date must match filename (${date}): ${path.relative(ROOT, full)}`);
69
- }
70
- }
71
- }
72
-
48
+ // Daily logs are now managed by SQLite in V2.
73
49
  function collectProjectSlugs() {
74
50
  return dl.db.prepare('SELECT slug FROM projects WHERE is_active = 1').all().map(p => p.slug.toLowerCase());
75
51
  }
@@ -112,8 +88,9 @@ function validateDocsHubs() {
112
88
  }
113
89
  }
114
90
 
115
- function main() {
116
- validateDailyLogs();
91
+ async function main() {
92
+ await ready;
93
+ // validateDailyLogs(); removed context since migrated to SQLite
117
94
  validateProjectStatusHistory();
118
95
  validateTaskProjectSlugs();
119
96
  validateDocsHubs();
@@ -126,4 +103,4 @@ function main() {
126
103
  console.log('✅ Structure validation passed');
127
104
  }
128
105
 
129
- main();
106
+ main().catch(err => { console.error(err); process.exit(1); });