@cccarv82/freya 2.3.9 → 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',
@@ -938,6 +938,11 @@ function companionHtml(defaultDir) {
938
938
  return buildCompanionHtml(safeDefault, APP_VERSION);
939
939
  }
940
940
 
941
+ function timelineHtml(defaultDir) {
942
+ const safeDefault = String(defaultDir || './freya').replace(/\\/g, '\\\\').replace(/"/g, '\\"');
943
+ return buildTimelineHtml(safeDefault, APP_VERSION);
944
+ }
945
+
941
946
  function buildHtml(safeDefault, appVersion) {
942
947
  const safeVersion = escapeHtml(appVersion || 'unknown');
943
948
  return `<!doctype html>
@@ -961,8 +966,9 @@ function buildHtml(safeDefault, appVersion) {
961
966
  <button class="railBtn active" id="railDashboard" type="button" title="Dashboard">D</button>
962
967
  <button class="railBtn" id="railReports" type="button" title="Relatórios">R</button>
963
968
  <button class="railBtn" id="railCompanion" type="button" title="Companion">C</button>
964
- <button class="railBtn" id="railProjects" type="button" title="Projects">P</button>
965
- <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>
966
972
  </div>
967
973
  <div class="railBottom">
968
974
  <div class="railStatus" id="railStatus" title="status"></div>
@@ -1213,8 +1219,9 @@ function buildReportsHtml(safeDefault, appVersion) {
1213
1219
  <button class="railBtn" id="railDashboard" type="button" title="Dashboard">D</button>
1214
1220
  <button class="railBtn active" id="railReports" type="button" title="Relatórios">R</button>
1215
1221
  <button class="railBtn" id="railCompanion" type="button" title="Companion">C</button>
1216
- <button class="railBtn" id="railProjects" type="button" title="Projects">P</button>
1217
- <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>
1218
1225
  </div>
1219
1226
  <div class="railBottom">
1220
1227
  <div class="railStatus" id="railStatus" title="status"></div>
@@ -1293,7 +1300,8 @@ function buildProjectsHtml(safeDefault, appVersion) {
1293
1300
  <button class="railBtn" id="railReports" type="button" title="Relatorios">R</button>
1294
1301
  <button class="railBtn" id="railCompanion" type="button" title="Companion">C</button>
1295
1302
  <button class="railBtn active" id="railProjects" type="button" title="Projects">P</button>
1296
- <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>
1297
1305
  </div>
1298
1306
  <div class="railBottom">
1299
1307
  <div class="railStatus" id="railStatus" title="status"></div>
@@ -1432,6 +1440,94 @@ function buildTimelineHtml(safeDefault, appVersion) {
1432
1440
  </html>`;
1433
1441
  }
1434
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
+
1435
1531
  function buildCompanionHtml(safeDefault, appVersion) {
1436
1532
  const safeVersion = escapeHtml(appVersion || 'unknown');
1437
1533
  return `<!doctype html>
@@ -1455,8 +1551,9 @@ function buildCompanionHtml(safeDefault, appVersion) {
1455
1551
  <button class="railBtn" id="railDashboard" type="button" title="Dashboard">D</button>
1456
1552
  <button class="railBtn" id="railReports" type="button" title="Relatórios">R</button>
1457
1553
  <button class="railBtn active" id="railCompanion" type="button" title="Companion">C</button>
1458
- <button class="railBtn" id="railProjects" type="button" title="Projects">P</button>
1459
- <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>
1460
1557
  </div>
1461
1558
  <div class="railBottom">
1462
1559
  <div class="railStatus" id="railStatus" title="status"></div>
@@ -1531,11 +1628,11 @@ function buildCompanionHtml(safeDefault, appVersion) {
1531
1628
 
1532
1629
  <section class="panel" style="margin-top:16px">
1533
1630
  <div class="panelHead" style="display:flex; align-items:center; justify-content:space-between; gap:10px">
1534
- <b>Resumo de Risco</b>
1535
- <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>
1536
1633
  </div>
1537
1634
  <div class="panelBody">
1538
- <div id="riskSummary"></div>
1635
+ <div id="riskRadarBox"></div>
1539
1636
  </div>
1540
1637
  </section>
1541
1638
 
@@ -1928,7 +2025,7 @@ async function cmdWeb({ port, dir, open, dev }) {
1928
2025
  if (!req.url) return safeJson(res, 404, { error: 'Not found' });
1929
2026
 
1930
2027
  if (req.method === 'GET' && req.url === '/') {
1931
- try { res.__freyaDebug.workspaceDir = normalizeWorkspaceDir(dir || './freya'); } catch {}
2028
+ try { res.__freyaDebug.workspaceDir = normalizeWorkspaceDir(dir || './freya'); } catch { }
1932
2029
  const body = html(dir || './freya');
1933
2030
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store' });
1934
2031
  res.end(body);
@@ -1936,7 +2033,7 @@ async function cmdWeb({ port, dir, open, dev }) {
1936
2033
  }
1937
2034
 
1938
2035
  if (req.method === 'GET' && req.url === '/reports') {
1939
- try { res.__freyaDebug.workspaceDir = normalizeWorkspaceDir(dir || './freya'); } catch {}
2036
+ try { res.__freyaDebug.workspaceDir = normalizeWorkspaceDir(dir || './freya'); } catch { }
1940
2037
  const body = reportsHtml(dir || './freya');
1941
2038
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store' });
1942
2039
  res.end(body);
@@ -1944,7 +2041,7 @@ async function cmdWeb({ port, dir, open, dev }) {
1944
2041
  }
1945
2042
 
1946
2043
  if (req.method === 'GET' && req.url === '/companion') {
1947
- try { res.__freyaDebug.workspaceDir = normalizeWorkspaceDir(dir || './freya'); } catch {}
2044
+ try { res.__freyaDebug.workspaceDir = normalizeWorkspaceDir(dir || './freya'); } catch { }
1948
2045
  const body = companionHtml(dir || './freya');
1949
2046
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store' });
1950
2047
  res.end(body);
@@ -1952,16 +2049,24 @@ async function cmdWeb({ port, dir, open, dev }) {
1952
2049
  }
1953
2050
 
1954
2051
  if (req.method === 'GET' && req.url === '/projects') {
1955
- try { res.__freyaDebug.workspaceDir = normalizeWorkspaceDir(dir || './freya'); } catch {}
2052
+ try { res.__freyaDebug.workspaceDir = normalizeWorkspaceDir(dir || './freya'); } catch { }
1956
2053
  const body = projectsHtml(dir || './freya');
1957
2054
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store' });
1958
2055
  res.end(body);
1959
2056
  return;
1960
2057
  }
1961
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
+
1962
2067
  if (req.method === 'GET' && req.url === '/timeline') {
1963
- try { res.__freyaDebug.workspaceDir = normalizeWorkspaceDir(dir || './freya'); } catch {}
1964
- const body = buildTimelineHtml(dir || './freya', version);
2068
+ try { res.__freyaDebug.workspaceDir = normalizeWorkspaceDir(dir || './freya'); } catch { }
2069
+ const body = timelineHtml(dir || './freya');
1965
2070
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store' });
1966
2071
  res.end(body);
1967
2072
  return;
@@ -1981,6 +2086,13 @@ async function cmdWeb({ port, dir, open, dev }) {
1981
2086
  return;
1982
2087
  }
1983
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
+
1984
2096
  if (req.method === 'GET' && req.url === '/favicon.ico') {
1985
2097
  res.writeHead(204, { 'Cache-Control': 'no-store' });
1986
2098
  res.end();
@@ -2009,7 +2121,7 @@ async function cmdWeb({ port, dir, open, dev }) {
2009
2121
  url: req.url,
2010
2122
  payload: summarizePayload(payload)
2011
2123
  });
2012
- } catch {}
2124
+ } catch { }
2013
2125
 
2014
2126
  if (req.url === '/api/pick-dir') {
2015
2127
  const picked = await pickDirectoryNative();
@@ -2082,11 +2194,96 @@ async function cmdWeb({ port, dir, open, dev }) {
2082
2194
  }
2083
2195
  }
2084
2196
  }
2085
- items.sort((a,b)=> String(b.lastUpdated||'').localeCompare(String(a.lastUpdated||'')));
2197
+ items.sort((a, b) => String(b.lastUpdated || '').localeCompare(String(a.lastUpdated || '')));
2086
2198
  return safeJson(res, 200, { ok: true, projects: items });
2087
2199
  }
2088
2200
 
2089
- 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') {
2090
2287
  const items = getTimelineItems(workspaceDir);
2091
2288
  return safeJson(res, 200, { ok: true, items });
2092
2289
  }
@@ -2308,6 +2505,107 @@ if (req.url === '/api/timeline') {
2308
2505
  return safeJson(res, 200, { ok: true, anomalies });
2309
2506
  }
2310
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
+
2311
2609
  if (req.url === '/api/incidents/resolve') {
2312
2610
  const title = payload.title;
2313
2611
  const index = Number.isInteger(payload.index) ? payload.index : null;
@@ -2351,7 +2649,7 @@ if (req.url === '/api/timeline') {
2351
2649
  return safeJson(res, 200, { ok: true, items });
2352
2650
  }
2353
2651
 
2354
- if (req.url === '/api/reports/list') {
2652
+ if (req.url === '/api/reports/list') {
2355
2653
  const reports = listReports(workspaceDir);
2356
2654
  return safeJson(res, 200, { reports });
2357
2655
  }
@@ -3019,7 +3317,7 @@ if (req.url === '/api/reports/list') {
3019
3317
  const obj = JSON.parse(line);
3020
3318
  if (!obj || typeof obj !== 'object') continue;
3021
3319
  items.push(obj);
3022
- } catch {}
3320
+ } catch { }
3023
3321
  }
3024
3322
 
3025
3323
  const outDir = path.join(workspaceDir, 'docs', 'chat');
@@ -3156,7 +3454,7 @@ if (req.url === '/api/reports/list') {
3156
3454
  const reportsToday = reports.filter((r) => r && String(r.name || '').includes(today)).length;
3157
3455
 
3158
3456
  const items = [
3159
- { 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)` },
3160
3458
  { label: 'Tarefas DO_NOW pendentes', status: pendingTasks > 0 ? 'warn' : 'ok', detail: `${pendingTasks} pendente(s)` },
3161
3459
  { label: 'Relatorios de hoje', status: reportsToday > 0 ? 'ok' : 'warn', detail: `${reportsToday} gerado(s)` }
3162
3460
  ];
@@ -3207,7 +3505,7 @@ if (req.url === '/api/reports/list') {
3207
3505
  return safeJson(res, 200, { ok: true, items });
3208
3506
  }
3209
3507
 
3210
- if (req.url === '/api/blockers/summary') {
3508
+ if (req.url === '/api/blockers/summary') {
3211
3509
  const file = path.join(workspaceDir, 'data', 'blockers', 'blocker-log.json');
3212
3510
  const doc = readJsonOrNull(file) || { schemaVersion: 1, blockers: [] };
3213
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.9",
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",