@cccarv82/freya 2.3.10 → 2.3.11

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 CHANGED
@@ -467,7 +467,7 @@ function redactSecrets(text) {
467
467
 
468
468
  function debugLogPath(workspaceDir) {
469
469
  const dir = path.join(workspaceDir, '.debuglogs');
470
- try { fs.mkdirSync(dir, { recursive: true }); } catch {}
470
+ try { fs.mkdirSync(dir, { recursive: true }); } catch { }
471
471
  return path.join(dir, 'debug.jsonl');
472
472
  }
473
473
 
@@ -511,7 +511,7 @@ function rotateDebugLog(filePath, maxBytes = 5 * 1024 * 1024) {
511
511
  const stamp = new Date().toISOString().replace(/[:.]/g, '-');
512
512
  const rotated = path.join(dir, 'debug-' + stamp + '.jsonl');
513
513
  fs.renameSync(filePath, rotated);
514
- } catch {}
514
+ } catch { }
515
515
  }
516
516
 
517
517
  function writeDebugEvent(workspaceDir, event) {
@@ -521,7 +521,7 @@ function writeDebugEvent(workspaceDir, event) {
521
521
  rotateDebugLog(filePath);
522
522
  const line = JSON.stringify({ ts: new Date().toISOString(), ...event });
523
523
  fs.appendFileSync(filePath, line + '\n', 'utf8');
524
- } catch {}
524
+ } catch { }
525
525
  }
526
526
 
527
527
  async function publishRobust(webhookUrl, text, opts = {}) {
@@ -593,7 +593,7 @@ function safeJson(res, code, obj) {
593
593
  error
594
594
  });
595
595
  }
596
- } catch {}
596
+ } catch { }
597
597
 
598
598
  res.writeHead(code, {
599
599
  'Content-Type': 'application/json; charset=utf-8',
@@ -966,8 +966,9 @@ function buildHtml(safeDefault, appVersion) {
966
966
  <button class="railBtn active" id="railDashboard" type="button" title="Dashboard">D</button>
967
967
  <button class="railBtn" id="railReports" type="button" title="Relatórios">R</button>
968
968
  <button class="railBtn" id="railCompanion" type="button" title="Companion">C</button>
969
- <button class="railBtn" id="railProjects" type="button" title="Projects">P</button>
970
- <button class="railBtn" id="railTimeline" type="button" title="Timeline">T</button>
969
+ <button class="railBtn" id="railProjects" type="button" title="Projects">P</button>
970
+ <button class="railBtn" id="railTimeline" type="button" title="Timeline">T</button>
971
+ <button class="railBtn" id="railGraph" type="button" title="Grafo">G</button>
971
972
  </div>
972
973
  <div class="railBottom">
973
974
  <div class="railStatus" id="railStatus" title="status"></div>
@@ -1218,8 +1219,9 @@ function buildReportsHtml(safeDefault, appVersion) {
1218
1219
  <button class="railBtn" id="railDashboard" type="button" title="Dashboard">D</button>
1219
1220
  <button class="railBtn active" id="railReports" type="button" title="Relatórios">R</button>
1220
1221
  <button class="railBtn" id="railCompanion" type="button" title="Companion">C</button>
1221
- <button class="railBtn" id="railProjects" type="button" title="Projects">P</button>
1222
- <button class="railBtn" id="railTimeline" type="button" title="Timeline">T</button>
1222
+ <button class="railBtn" id="railProjects" type="button" title="Projects">P</button>
1223
+ <button class="railBtn" id="railTimeline" type="button" title="Timeline">T</button>
1224
+ <button class="railBtn" id="railGraph" type="button" title="Grafo">G</button>
1223
1225
  </div>
1224
1226
  <div class="railBottom">
1225
1227
  <div class="railStatus" id="railStatus" title="status"></div>
@@ -1298,7 +1300,8 @@ function buildProjectsHtml(safeDefault, appVersion) {
1298
1300
  <button class="railBtn" id="railReports" type="button" title="Relatorios">R</button>
1299
1301
  <button class="railBtn" id="railCompanion" type="button" title="Companion">C</button>
1300
1302
  <button class="railBtn active" id="railProjects" type="button" title="Projects">P</button>
1301
- <button class="railBtn" id="railTimeline" type="button" title="Timeline">T</button>
1303
+ <button class="railBtn" id="railTimeline" type="button" title="Timeline">T</button>
1304
+ <button class="railBtn" id="railGraph" type="button" title="Grafo">G</button>
1302
1305
  </div>
1303
1306
  <div class="railBottom">
1304
1307
  <div class="railStatus" id="railStatus" title="status"></div>
@@ -1437,6 +1440,94 @@ function buildTimelineHtml(safeDefault, appVersion) {
1437
1440
  </html>`;
1438
1441
  }
1439
1442
 
1443
+ function buildGraphHtml(safeDefault, appVersion) {
1444
+ const safeVersion = escapeHtml(appVersion || 'unknown');
1445
+ return `<!doctype html>
1446
+ <html>
1447
+ <head>
1448
+ <meta charset="utf-8" />
1449
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
1450
+ <title>Connections Graph</title>
1451
+ <link rel="stylesheet" href="/app.css" />
1452
+ <script src="/vis-network.min.js"></script>
1453
+ <style>
1454
+ #networkGraph {
1455
+ width: 100%;
1456
+ height: 600px;
1457
+ border: 1px solid var(--border);
1458
+ border-radius: 6px;
1459
+ background: var(--bg2);
1460
+ }
1461
+ </style>
1462
+ </head>
1463
+ <body data-page="graph">
1464
+ <div class="app">
1465
+ <div class="frame">
1466
+ <div class="shell">
1467
+
1468
+ <aside class="rail">
1469
+ <div class="railTop">
1470
+ <div class="railLogo">F</div>
1471
+ </div>
1472
+ <div class="railNav">
1473
+ <button class="railBtn" id="railDashboard" type="button" title="Dashboard">D</button>
1474
+ <button class="railBtn" id="railReports" type="button" title="Relatorios">R</button>
1475
+ <button class="railBtn" id="railCompanion" type="button" title="Companion">C</button>
1476
+ <button class="railBtn" id="railProjects" type="button" title="Projects">P</button>
1477
+ <button class="railBtn" id="railTimeline" type="button" title="Timeline">T</button>
1478
+ <button class="railBtn active" id="railGraph" type="button" title="Grafo">G</button>
1479
+ </div>
1480
+ <div class="railBottom">
1481
+ <div class="railStatus" id="railStatus" title="status"></div>
1482
+ </div>
1483
+ </aside>
1484
+
1485
+ <main class="center reportsPage" id="graphPage">
1486
+ <div class="topbar">
1487
+ <div class="brandLine">
1488
+ <span class="spark"></span>
1489
+ <div class="brandStack">
1490
+ <div class="brand">FREYA</div>
1491
+ <div class="brandSub">Connections Graph</div>
1492
+ </div>
1493
+ </div>
1494
+ <div class="topActions">
1495
+ <span class="chip" id="chipVersion">v${safeVersion}</span>
1496
+ <span class="chip" id="chipPort">127.0.0.1:3872</span>
1497
+ </div>
1498
+ </div>
1499
+
1500
+ <div class="centerBody">
1501
+ <input id="dir" type="hidden" />
1502
+
1503
+ <section class="reportsHeader">
1504
+ <div>
1505
+ <div class="reportsTitle">Grafo de Conexões</div>
1506
+ <div class="reportsSubtitle">Visualização de dependências entre projetos e tags.</div>
1507
+ </div>
1508
+ <div class="reportsActions">
1509
+ <button class="btn small" type="button" onclick="refreshGraph()">Atualizar</button>
1510
+ </div>
1511
+ </section>
1512
+
1513
+ <section class="reportsGrid">
1514
+ <div id="networkGraph"></div>
1515
+ </section>
1516
+ </div>
1517
+ </main>
1518
+
1519
+ </div>
1520
+ </div>
1521
+ </div>
1522
+
1523
+ <script>
1524
+ window.__FREYA_DEFAULT_DIR = "${safeDefault}";
1525
+ </script>
1526
+ <script src="/app.js"></script>
1527
+ </body>
1528
+ </html>`;
1529
+ }
1530
+
1440
1531
  function buildCompanionHtml(safeDefault, appVersion) {
1441
1532
  const safeVersion = escapeHtml(appVersion || 'unknown');
1442
1533
  return `<!doctype html>
@@ -1460,8 +1551,9 @@ function buildCompanionHtml(safeDefault, appVersion) {
1460
1551
  <button class="railBtn" id="railDashboard" type="button" title="Dashboard">D</button>
1461
1552
  <button class="railBtn" id="railReports" type="button" title="Relatórios">R</button>
1462
1553
  <button class="railBtn active" id="railCompanion" type="button" title="Companion">C</button>
1463
- <button class="railBtn" id="railProjects" type="button" title="Projects">P</button>
1464
- <button class="railBtn" id="railTimeline" type="button" title="Timeline">T</button>
1554
+ <button class="railBtn" id="railProjects" type="button" title="Projects">P</button>
1555
+ <button class="railBtn" id="railTimeline" type="button" title="Timeline">T</button>
1556
+ <button class="railBtn" id="railGraph" type="button" title="Grafo">G</button>
1465
1557
  </div>
1466
1558
  <div class="railBottom">
1467
1559
  <div class="railStatus" id="railStatus" title="status"></div>
@@ -1536,11 +1628,11 @@ function buildCompanionHtml(safeDefault, appVersion) {
1536
1628
 
1537
1629
  <section class="panel" style="margin-top:16px">
1538
1630
  <div class="panelHead" style="display:flex; align-items:center; justify-content:space-between; gap:10px">
1539
- <b>Resumo de Risco</b>
1540
- <button class="btn small" type="button" onclick="refreshRiskSummary()">Atualizar</button>
1631
+ <b>Radar de Risco</b>
1632
+ <button class="btn small" type="button" onclick="refreshRiskRadar()">Atualizar</button>
1541
1633
  </div>
1542
1634
  <div class="panelBody">
1543
- <div id="riskSummary"></div>
1635
+ <div id="riskRadarBox"></div>
1544
1636
  </div>
1545
1637
  </section>
1546
1638
 
@@ -1933,7 +2025,7 @@ async function cmdWeb({ port, dir, open, dev }) {
1933
2025
  if (!req.url) return safeJson(res, 404, { error: 'Not found' });
1934
2026
 
1935
2027
  if (req.method === 'GET' && req.url === '/') {
1936
- try { res.__freyaDebug.workspaceDir = normalizeWorkspaceDir(dir || './freya'); } catch {}
2028
+ try { res.__freyaDebug.workspaceDir = normalizeWorkspaceDir(dir || './freya'); } catch { }
1937
2029
  const body = html(dir || './freya');
1938
2030
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store' });
1939
2031
  res.end(body);
@@ -1941,7 +2033,7 @@ async function cmdWeb({ port, dir, open, dev }) {
1941
2033
  }
1942
2034
 
1943
2035
  if (req.method === 'GET' && req.url === '/reports') {
1944
- try { res.__freyaDebug.workspaceDir = normalizeWorkspaceDir(dir || './freya'); } catch {}
2036
+ try { res.__freyaDebug.workspaceDir = normalizeWorkspaceDir(dir || './freya'); } catch { }
1945
2037
  const body = reportsHtml(dir || './freya');
1946
2038
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store' });
1947
2039
  res.end(body);
@@ -1949,7 +2041,7 @@ async function cmdWeb({ port, dir, open, dev }) {
1949
2041
  }
1950
2042
 
1951
2043
  if (req.method === 'GET' && req.url === '/companion') {
1952
- try { res.__freyaDebug.workspaceDir = normalizeWorkspaceDir(dir || './freya'); } catch {}
2044
+ try { res.__freyaDebug.workspaceDir = normalizeWorkspaceDir(dir || './freya'); } catch { }
1953
2045
  const body = companionHtml(dir || './freya');
1954
2046
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store' });
1955
2047
  res.end(body);
@@ -1957,15 +2049,23 @@ async function cmdWeb({ port, dir, open, dev }) {
1957
2049
  }
1958
2050
 
1959
2051
  if (req.method === 'GET' && req.url === '/projects') {
1960
- try { res.__freyaDebug.workspaceDir = normalizeWorkspaceDir(dir || './freya'); } catch {}
2052
+ try { res.__freyaDebug.workspaceDir = normalizeWorkspaceDir(dir || './freya'); } catch { }
1961
2053
  const body = projectsHtml(dir || './freya');
1962
2054
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store' });
1963
2055
  res.end(body);
1964
2056
  return;
1965
2057
  }
1966
2058
 
2059
+ if (req.method === 'GET' && req.url === '/graph') {
2060
+ try { res.__freyaDebug.workspaceDir = normalizeWorkspaceDir(dir || './freya'); } catch { }
2061
+ const body = buildGraphHtml(dir || './freya', APP_VERSION);
2062
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store' });
2063
+ res.end(body);
2064
+ return;
2065
+ }
2066
+
1967
2067
  if (req.method === 'GET' && req.url === '/timeline') {
1968
- try { res.__freyaDebug.workspaceDir = normalizeWorkspaceDir(dir || './freya'); } catch {}
2068
+ try { res.__freyaDebug.workspaceDir = normalizeWorkspaceDir(dir || './freya'); } catch { }
1969
2069
  const body = timelineHtml(dir || './freya');
1970
2070
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store' });
1971
2071
  res.end(body);
@@ -1986,6 +2086,13 @@ async function cmdWeb({ port, dir, open, dev }) {
1986
2086
  return;
1987
2087
  }
1988
2088
 
2089
+ if (req.method === 'GET' && req.url === '/vis-network.min.js') {
2090
+ const js = fs.readFileSync(path.join(__dirname, 'vis-network.min.js'), 'utf8');
2091
+ res.writeHead(200, { 'Content-Type': 'application/javascript; charset=utf-8', 'Cache-Control': 'max-age=31536000' });
2092
+ res.end(js);
2093
+ return;
2094
+ }
2095
+
1989
2096
  if (req.method === 'GET' && req.url === '/favicon.ico') {
1990
2097
  res.writeHead(204, { 'Cache-Control': 'no-store' });
1991
2098
  res.end();
@@ -2014,7 +2121,7 @@ async function cmdWeb({ port, dir, open, dev }) {
2014
2121
  url: req.url,
2015
2122
  payload: summarizePayload(payload)
2016
2123
  });
2017
- } catch {}
2124
+ } catch { }
2018
2125
 
2019
2126
  if (req.url === '/api/pick-dir') {
2020
2127
  const picked = await pickDirectoryNative();
@@ -2087,11 +2194,96 @@ async function cmdWeb({ port, dir, open, dev }) {
2087
2194
  }
2088
2195
  }
2089
2196
  }
2090
- items.sort((a,b)=> String(b.lastUpdated||'').localeCompare(String(a.lastUpdated||'')));
2197
+ items.sort((a, b) => String(b.lastUpdated || '').localeCompare(String(a.lastUpdated || '')));
2091
2198
  return safeJson(res, 200, { ok: true, projects: items });
2092
2199
  }
2093
2200
 
2094
- if (req.url === '/api/timeline') {
2201
+ if (req.url === '/api/graph/data') {
2202
+ const nodes = [];
2203
+ const edges = [];
2204
+ const nodeIds = new Set();
2205
+ let idCounter = 1;
2206
+
2207
+ const addNode = (id, label, group) => {
2208
+ if (!nodeIds.has(id)) {
2209
+ nodes.push({ id, label: truncateText(label, 30), group });
2210
+ nodeIds.add(id);
2211
+ }
2212
+ };
2213
+
2214
+ // 1. Load Projects
2215
+ const base = path.join(workspaceDir, 'data', 'Clients');
2216
+ if (exists(base)) {
2217
+ const stack = [base];
2218
+ while (stack.length) {
2219
+ const dirp = stack.pop();
2220
+ const entries = fs.readdirSync(dirp, { withFileTypes: true });
2221
+ for (const ent of entries) {
2222
+ const full = path.join(dirp, ent.name);
2223
+ if (ent.isDirectory()) stack.push(full);
2224
+ else if (ent.isFile() && ent.name === 'status.json') {
2225
+ const doc = readJsonOrNull(full) || {};
2226
+ const slug = path.relative(base, path.dirname(full)).replace(/\\/g, '/');
2227
+ addNode(slug, slug, 'project');
2228
+
2229
+ // Link tags
2230
+ const tags = Array.isArray(doc.tags) ? doc.tags : [];
2231
+ for (const t of tags) {
2232
+ const tid = 'tag:' + String(t).trim().toLowerCase();
2233
+ addNode(tid, String(t).trim(), 'tag');
2234
+ edges.push({ from: slug, to: tid });
2235
+ }
2236
+ }
2237
+ }
2238
+ }
2239
+ }
2240
+
2241
+ // 2. Load Tasks
2242
+ const taskFile = path.join(workspaceDir, 'data', 'tasks', 'task-log.json');
2243
+ const taskDoc = readJsonOrNull(taskFile) || { tasks: [] };
2244
+ const tasks = Array.isArray(taskDoc.tasks) ? taskDoc.tasks : [];
2245
+
2246
+ for (const t of tasks) {
2247
+ // Only show pending high/medium tasks for clarity, or unassigned ones.
2248
+ if (t.status === 'COMPLETED') continue;
2249
+
2250
+ const tid = 'task:' + t.id;
2251
+ addNode(tid, t.title || 'Tarefa', 'task');
2252
+
2253
+ const slug = t.projectSlug || null;
2254
+ if (slug && nodeIds.has(slug)) {
2255
+ edges.push({ from: tid, to: slug });
2256
+ } else {
2257
+ // Connect to an "Unassigned" node
2258
+ addNode('unassigned', 'Sin Asignar', 'unassigned');
2259
+ edges.push({ from: tid, to: 'unassigned' });
2260
+ }
2261
+ }
2262
+
2263
+ // 3. Load Blockers
2264
+ const blockerFile = path.join(workspaceDir, 'data', 'blockers', 'blocker-log.json');
2265
+ const blockerDoc = readJsonOrNull(blockerFile) || { blockers: [] };
2266
+ const blockers = Array.isArray(blockerDoc.blockers) ? blockerDoc.blockers : [];
2267
+
2268
+ for (const b of blockers) {
2269
+ if (String(b.status || '').toUpperCase() !== 'OPEN') continue;
2270
+
2271
+ const bid = 'blocker:' + b.id;
2272
+ addNode(bid, 'BLQ: ' + (b.title || 'Blocker'), 'blocker');
2273
+
2274
+ const slug = b.projectSlug || null;
2275
+ if (slug && nodeIds.has(slug)) {
2276
+ edges.push({ from: bid, to: slug });
2277
+ } else {
2278
+ addNode('unassigned_blockers', 'Bloqueios Soltos', 'unassigned');
2279
+ edges.push({ from: bid, to: 'unassigned_blockers' });
2280
+ }
2281
+ }
2282
+
2283
+ return safeJson(res, 200, { ok: true, nodes, edges });
2284
+ }
2285
+
2286
+ if (req.url === '/api/timeline') {
2095
2287
  const items = getTimelineItems(workspaceDir);
2096
2288
  return safeJson(res, 200, { ok: true, items });
2097
2289
  }
@@ -2313,6 +2505,107 @@ if (req.url === '/api/timeline') {
2313
2505
  return safeJson(res, 200, { ok: true, anomalies });
2314
2506
  }
2315
2507
 
2508
+ if (req.url === '/api/companion/risk-radar') {
2509
+ if (!looksLikeFreyaWorkspace(workspaceDir)) {
2510
+ return safeJson(res, 200, { ok: false, needsInit: true, error: 'Workspace not initialized' });
2511
+ }
2512
+
2513
+ const now = Date.now();
2514
+ const items = [];
2515
+
2516
+ // 1. Inactive Projects (Stale)
2517
+ const base = path.join(workspaceDir, 'data', 'Clients');
2518
+ if (exists(base)) {
2519
+ const stack = [base];
2520
+ while (stack.length) {
2521
+ const dirp = stack.pop();
2522
+ const entries = fs.readdirSync(dirp, { withFileTypes: true });
2523
+ for (const ent of entries) {
2524
+ const full = path.join(dirp, ent.name);
2525
+ if (ent.isDirectory()) stack.push(full);
2526
+ else if (ent.isFile() && ent.name === 'status.json') {
2527
+ const doc = readJsonOrNull(full) || {};
2528
+ const slug = path.relative(base, path.dirname(full)).replace(/\\/g, '/');
2529
+ if (doc.active !== false) {
2530
+ const lastUpdated = doc.lastUpdated ? Date.parse(doc.lastUpdated) : null;
2531
+ if (lastUpdated) {
2532
+ const ageDays = Math.floor((now - lastUpdated) / (24 * 60 * 60 * 1000));
2533
+ if (ageDays > 14) { // 14 days without an update is a risk
2534
+ items.push({
2535
+ type: 'stale_project',
2536
+ severity: ageDays > 30 ? 'high' : 'medium',
2537
+ slug: slug,
2538
+ message: `Projeto Inativo (${ageDays} dias sem update)`
2539
+ });
2540
+ }
2541
+ }
2542
+ }
2543
+ }
2544
+ }
2545
+ }
2546
+ }
2547
+
2548
+ // 2. Blockers concentration
2549
+ const blockerFile = path.join(workspaceDir, 'data', 'blockers', 'blocker-log.json');
2550
+ if (exists(blockerFile)) {
2551
+ const blockerDoc = readJsonOrNull(blockerFile) || { blockers: [] };
2552
+ const blockers = Array.isArray(blockerDoc.blockers) ? blockerDoc.blockers : [];
2553
+ const blockersByProject = {};
2554
+ for (const b of blockers) {
2555
+ if (String(b.status || '').toUpperCase() === 'OPEN') {
2556
+ const slug = String(b.projectSlug || '').trim();
2557
+ if (slug) {
2558
+ blockersByProject[slug] = (blockersByProject[slug] || 0) + 1;
2559
+ }
2560
+ }
2561
+ }
2562
+ for (const [slug, count] of Object.entries(blockersByProject)) {
2563
+ if (count >= 3) {
2564
+ items.push({
2565
+ type: 'blocker_concentration',
2566
+ severity: count > 5 ? 'high' : 'medium',
2567
+ slug: slug,
2568
+ message: `Concentração de Bloqueios (${count} abertos)`
2569
+ });
2570
+ }
2571
+ }
2572
+ }
2573
+
2574
+ // 3. Task Overload
2575
+ const taskFile = path.join(workspaceDir, 'data', 'tasks', 'task-log.json');
2576
+ if (exists(taskFile)) {
2577
+ const taskDoc = readJsonOrNull(taskFile) || { tasks: [] };
2578
+ const tasks = Array.isArray(taskDoc.tasks) ? taskDoc.tasks : [];
2579
+ const tasksByProject = {};
2580
+ for (const t of tasks) {
2581
+ if (t.status !== 'COMPLETED' && String(t.priority || '').toUpperCase() === 'HIGH') {
2582
+ const slug = String(t.projectSlug || '').trim();
2583
+ if (slug) {
2584
+ tasksByProject[slug] = (tasksByProject[slug] || 0) + 1;
2585
+ }
2586
+ }
2587
+ }
2588
+ for (const [slug, count] of Object.entries(tasksByProject)) {
2589
+ if (count >= 5) {
2590
+ items.push({
2591
+ type: 'task_overload',
2592
+ severity: 'high',
2593
+ slug: slug,
2594
+ message: `Sobrecarga Crítica (${count} tarefas High-priority pendentes)`
2595
+ });
2596
+ }
2597
+ }
2598
+ }
2599
+
2600
+ items.sort((a, b) => {
2601
+ const sA = a.severity === 'high' ? 2 : 1;
2602
+ const sB = b.severity === 'high' ? 2 : 1;
2603
+ return sB - sA;
2604
+ });
2605
+
2606
+ return safeJson(res, 200, { ok: true, items });
2607
+ }
2608
+
2316
2609
  if (req.url === '/api/incidents/resolve') {
2317
2610
  const title = payload.title;
2318
2611
  const index = Number.isInteger(payload.index) ? payload.index : null;
@@ -2356,7 +2649,7 @@ if (req.url === '/api/timeline') {
2356
2649
  return safeJson(res, 200, { ok: true, items });
2357
2650
  }
2358
2651
 
2359
- if (req.url === '/api/reports/list') {
2652
+ if (req.url === '/api/reports/list') {
2360
2653
  const reports = listReports(workspaceDir);
2361
2654
  return safeJson(res, 200, { reports });
2362
2655
  }
@@ -3024,7 +3317,7 @@ if (req.url === '/api/reports/list') {
3024
3317
  const obj = JSON.parse(line);
3025
3318
  if (!obj || typeof obj !== 'object') continue;
3026
3319
  items.push(obj);
3027
- } catch {}
3320
+ } catch { }
3028
3321
  }
3029
3322
 
3030
3323
  const outDir = path.join(workspaceDir, 'docs', 'chat');
@@ -3161,7 +3454,7 @@ if (req.url === '/api/reports/list') {
3161
3454
  const reportsToday = reports.filter((r) => r && String(r.name || '').includes(today)).length;
3162
3455
 
3163
3456
  const items = [
3164
- { label: 'Blockers abertos', status: openBlockers > 0 ? 'warn' : 'ok', detail: `${openBlockers} aberto(s)` },
3457
+ { label: 'Blockers abertos', status: openBlockers > 0 ? 'warn' : 'ok', detail: `${openBlockers} aberto(s)` },
3165
3458
  { label: 'Tarefas DO_NOW pendentes', status: pendingTasks > 0 ? 'warn' : 'ok', detail: `${pendingTasks} pendente(s)` },
3166
3459
  { label: 'Relatorios de hoje', status: reportsToday > 0 ? 'ok' : 'warn', detail: `${reportsToday} gerado(s)` }
3167
3460
  ];
@@ -3212,7 +3505,7 @@ if (req.url === '/api/reports/list') {
3212
3505
  return safeJson(res, 200, { ok: true, items });
3213
3506
  }
3214
3507
 
3215
- if (req.url === '/api/blockers/summary') {
3508
+ if (req.url === '/api/blockers/summary') {
3216
3509
  const file = path.join(workspaceDir, 'data', 'blockers', 'blocker-log.json');
3217
3510
  const doc = readJsonOrNull(file) || { schemaVersion: 1, blockers: [] };
3218
3511
  const blockers = Array.isArray(doc.blockers) ? doc.blockers : [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cccarv82/freya",
3
- "version": "2.3.10",
3
+ "version": "2.3.11",
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",