@cccarv82/freya 2.6.0 → 2.7.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/auto-update.js +134 -0
- package/cli/init.js +19 -2
- package/cli/web.js +8 -0
- package/package.json +1 -1
- package/templates/base/scripts/build-vector-index.js +2 -1
- package/templates/base/scripts/export-obsidian.js +4 -3
- package/templates/base/scripts/generate-blockers-report.js +9 -19
- package/templates/base/scripts/generate-daily-summary.js +3 -1
- package/templates/base/scripts/generate-executive-report.js +35 -4
- package/templates/base/scripts/generate-sm-weekly-report.js +26 -7
- package/templates/base/scripts/migrate-v1-v2.js +15 -10
- package/templates/base/scripts/validate-structure.js +6 -29
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* auto-update.js
|
|
3
|
+
* Transparently keeps workspace scripts and dependencies in sync
|
|
4
|
+
* with the installed Freya version.
|
|
5
|
+
*
|
|
6
|
+
* Called automatically on every `freya` CLI invocation.
|
|
7
|
+
* - Compares installed Freya version vs. workspace's last-synced version
|
|
8
|
+
* - If different, runs initWorkspace to copy updated scripts
|
|
9
|
+
* - Runs `npm install` if dependencies changed
|
|
10
|
+
* - Writes a .freya-version marker so it only runs once per upgrade
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
const { execSync } = require('child_process');
|
|
18
|
+
|
|
19
|
+
function getInstalledVersion() {
|
|
20
|
+
try {
|
|
21
|
+
const pkg = require('../package.json');
|
|
22
|
+
return pkg.version || '0.0.0';
|
|
23
|
+
} catch {
|
|
24
|
+
return '0.0.0';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getWorkspaceVersion(workspaceDir) {
|
|
29
|
+
const markerPath = path.join(workspaceDir, '.freya-version');
|
|
30
|
+
try {
|
|
31
|
+
return fs.readFileSync(markerPath, 'utf8').trim();
|
|
32
|
+
} catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function setWorkspaceVersion(workspaceDir, version) {
|
|
38
|
+
const markerPath = path.join(workspaceDir, '.freya-version');
|
|
39
|
+
try {
|
|
40
|
+
fs.writeFileSync(markerPath, version + '\n', 'utf8');
|
|
41
|
+
} catch {
|
|
42
|
+
// non-fatal
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function needsNpmInstall(workspaceDir) {
|
|
47
|
+
// Check if sql.js is installed in the workspace's node_modules
|
|
48
|
+
const sqlJsPath = path.join(workspaceDir, 'node_modules', 'sql.js');
|
|
49
|
+
try {
|
|
50
|
+
fs.accessSync(sqlJsPath);
|
|
51
|
+
return false;
|
|
52
|
+
} catch {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function runNpmInstall(workspaceDir) {
|
|
58
|
+
try {
|
|
59
|
+
console.log('[FREYA] Installing workspace dependencies...');
|
|
60
|
+
const opts = {
|
|
61
|
+
cwd: workspaceDir,
|
|
62
|
+
stdio: 'pipe',
|
|
63
|
+
timeout: 120000, // 2 min max
|
|
64
|
+
env: { ...process.env }
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
if (process.platform === 'win32') {
|
|
68
|
+
execSync('npm install --no-audit --no-fund', opts);
|
|
69
|
+
} else {
|
|
70
|
+
execSync('npm install --no-audit --no-fund', opts);
|
|
71
|
+
}
|
|
72
|
+
console.log('[FREYA] Dependencies installed successfully.');
|
|
73
|
+
return true;
|
|
74
|
+
} catch (err) {
|
|
75
|
+
console.error('[FREYA] Warning: npm install failed:', err.message || String(err));
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Auto-update the workspace if the installed Freya version is newer.
|
|
82
|
+
* This is designed to be silent and non-disruptive.
|
|
83
|
+
*
|
|
84
|
+
* @param {string} workspaceDir - Resolved absolute path to the workspace
|
|
85
|
+
* @returns {Promise<{updated: boolean, version: string}>}
|
|
86
|
+
*/
|
|
87
|
+
async function autoUpdate(workspaceDir) {
|
|
88
|
+
const installedVersion = getInstalledVersion();
|
|
89
|
+
const workspaceVersion = getWorkspaceVersion(workspaceDir);
|
|
90
|
+
|
|
91
|
+
// Already in sync
|
|
92
|
+
if (workspaceVersion === installedVersion) {
|
|
93
|
+
return { updated: false, version: installedVersion };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Workspace doesn't look like a Freya workspace (no scripts/ dir)
|
|
97
|
+
const scriptsDir = path.join(workspaceDir, 'scripts');
|
|
98
|
+
try {
|
|
99
|
+
fs.accessSync(scriptsDir);
|
|
100
|
+
} catch {
|
|
101
|
+
// Not a workspace, skip
|
|
102
|
+
return { updated: false, version: installedVersion };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
console.log(`[FREYA] Updating workspace from ${workspaceVersion || 'unknown'} → ${installedVersion}...`);
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
// Use initWorkspace to sync templates (scripts, etc.)
|
|
109
|
+
const { initWorkspace } = require('./init');
|
|
110
|
+
await initWorkspace({
|
|
111
|
+
targetDir: workspaceDir,
|
|
112
|
+
force: false, // Don't force non-script files
|
|
113
|
+
forceData: false,
|
|
114
|
+
forceLogs: false
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Check if npm install is needed
|
|
118
|
+
if (needsNpmInstall(workspaceDir)) {
|
|
119
|
+
runNpmInstall(workspaceDir);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Mark workspace as up-to-date
|
|
123
|
+
setWorkspaceVersion(workspaceDir, installedVersion);
|
|
124
|
+
console.log(`[FREYA] Workspace updated to v${installedVersion} ✓`);
|
|
125
|
+
|
|
126
|
+
return { updated: true, version: installedVersion };
|
|
127
|
+
} catch (err) {
|
|
128
|
+
console.error('[FREYA] Warning: auto-update failed:', err.message || String(err));
|
|
129
|
+
// Non-fatal — don't block the user
|
|
130
|
+
return { updated: false, version: installedVersion, error: err.message };
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
module.exports = { autoUpdate, getInstalledVersion };
|
package/cli/init.js
CHANGED
|
@@ -64,7 +64,9 @@ function copyDirRecursive(srcDir, destDir, force, summary, options = {}) {
|
|
|
64
64
|
continue;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
|
|
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
|
@@ -2238,6 +2238,14 @@ function seedDevWorkspace(workspaceDir) {
|
|
|
2238
2238
|
|
|
2239
2239
|
async function cmdWeb({ port, dir, open, dev }) {
|
|
2240
2240
|
await ready;
|
|
2241
|
+
|
|
2242
|
+
// Auto-update workspace scripts/deps if Freya version changed
|
|
2243
|
+
try {
|
|
2244
|
+
const { autoUpdate } = require('./auto-update');
|
|
2245
|
+
const wsDir = normalizeWorkspaceDir(dir || './freya');
|
|
2246
|
+
await autoUpdate(wsDir);
|
|
2247
|
+
} catch { /* non-fatal */ }
|
|
2248
|
+
|
|
2241
2249
|
const host = '127.0.0.1';
|
|
2242
2250
|
|
|
2243
2251
|
const server = http.createServer(async (req, res) => {
|
package/package.json
CHANGED
|
@@ -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
|
|
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
|
|
99
|
-
|
|
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
|
-
|
|
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 =
|
|
160
|
-
const createdAt =
|
|
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
|
|
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 =
|
|
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 =
|
|
77
|
-
const st =
|
|
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,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) {
|
|
@@ -45,31 +45,7 @@ function parseFrontmatter(text) {
|
|
|
45
45
|
return fm;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
|
|
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
|
-
|
|
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); });
|