@cccarv82/freya 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cli/web-ui.css CHANGED
@@ -240,6 +240,11 @@ body {
240
240
  .topActions { display: flex; align-items: center; gap: 10px; }
241
241
  .chip { font-family: var(--mono); font-size: 12px; padding: 7px 10px; border-radius: 999px; border: 1px solid var(--line); background: rgba(0,0,0,.22); color: var(--faint); }
242
242
 
243
+ .pill { font-family: var(--mono); font-size: 11px; padding: 4px 8px; border-radius: 999px; border: 1px solid var(--line); background: var(--paper2); color: var(--muted); text-transform: uppercase; letter-spacing: .08em; }
244
+ .pill.ok { background: rgba(16, 185, 129, .12); color: #065f46; border-color: rgba(16, 185, 129, .28); }
245
+ .pill.warn { background: rgba(245, 158, 11, .18); color: #92400e; border-color: rgba(245, 158, 11, .35); }
246
+ .pill.info { background: rgba(59, 130, 246, .14); color: #1e3a8a; border-color: rgba(59, 130, 246, .35); }
247
+
243
248
  .centerBody {
244
249
  padding: 16px 20px 20px;
245
250
  overflow: auto;
package/cli/web-ui.js CHANGED
@@ -680,6 +680,7 @@
680
680
  function wireRailNav() {
681
681
  const dash = $('railDashboard');
682
682
  const rep = $('railReports');
683
+ const health = $('railHealth');
683
684
  if (dash) {
684
685
  dash.onclick = () => {
685
686
  const isReports = document.body && document.body.dataset && document.body.dataset.page === 'reports';
@@ -697,6 +698,12 @@
697
698
  if (!isReports) window.location.href = '/reports';
698
699
  };
699
700
  }
701
+ if (health) {
702
+ health.onclick = () => {
703
+ const isHealth = document.body && document.body.dataset && document.body.dataset.page === 'health';
704
+ if (!isHealth) window.location.href = '/health';
705
+ };
706
+ }
700
707
  }
701
708
 
702
709
  async function editTask(t) {
@@ -819,11 +826,42 @@
819
826
  ]);
820
827
  renderTasks((t && t.tasks) || []);
821
828
  renderBlockers((b && b.blockers) || []);
829
+ refreshBlockersInsights();
822
830
  } catch (e) {
823
831
  // keep silent in background refresh
824
832
  }
825
833
  }
826
834
 
835
+ function renderBlockersInsights(payload) {
836
+ const el = $('blockersInsights');
837
+ if (!el) return;
838
+ if (!payload || !payload.summary) {
839
+ el.textContent = 'Sem insights no momento.';
840
+ return;
841
+ }
842
+ const lines = [];
843
+ lines.push('<div class="help" style="margin-bottom:6px"><b>Resumo:</b> ' + escapeHtml(payload.summary) + '</div>');
844
+ if (payload.suggestions && payload.suggestions.length) {
845
+ lines.push('<div class="help"><b>Proximos passos:</b></div>');
846
+ lines.push('<ul style="margin:6px 0 0 18px; padding:0;">' + payload.suggestions.map((s) => '<li class="help">' + escapeHtml(s) + '</li>').join('') + '</ul>');
847
+ }
848
+ if (payload.top && payload.top.length) {
849
+ lines.push('<div class="help" style="margin-top:8px"><b>Top blockers:</b></div>');
850
+ lines.push('<ul style="margin:6px 0 0 18px; padding:0;">' + payload.top.map((b) => '<li class="help">' + escapeHtml(String(b.severity || '')) + ' - ' + escapeHtml(String(b.title || '')) + '</li>').join('') + '</ul>');
851
+ }
852
+ el.innerHTML = lines.join('');
853
+ }
854
+
855
+ async function refreshBlockersInsights() {
856
+ try {
857
+ const r = await api('/api/blockers/summary', { dir: dirOrDefault() });
858
+ renderBlockersInsights(r);
859
+ } catch (e) {
860
+ const el = $('blockersInsights');
861
+ if (el) el.textContent = 'Falha ao carregar insights.';
862
+ }
863
+ }
864
+
827
865
  async function pickDir() {
828
866
  try {
829
867
  setPill('run', 'picker…');
@@ -879,6 +917,50 @@
879
917
  }
880
918
  }
881
919
 
920
+ function renderHealthChecklist(items) {
921
+ const el = $('healthChecklist');
922
+ if (!el) return;
923
+ el.innerHTML = '';
924
+ const list = Array.isArray(items) ? items : [];
925
+ if (!list.length) {
926
+ const empty = document.createElement('div');
927
+ empty.className = 'help';
928
+ empty.textContent = 'Sem checks disponiveis.';
929
+ el.appendChild(empty);
930
+ return;
931
+ }
932
+ for (const it of list) {
933
+ const row = document.createElement('div');
934
+ row.className = 'rep';
935
+ const status = String(it.status || 'info');
936
+ row.innerHTML = '<div style="display:flex; justify-content:space-between; gap:10px; align-items:center">'
937
+ + '<div style="min-width:0"><div style="font-weight:800">' + escapeHtml(it.label || '') + '</div>'
938
+ + '<div class="help" style="margin-top:4px">' + escapeHtml(it.detail || '') + '</div></div>'
939
+ + '<div class="pill ' + escapeHtml(status) + '">' + escapeHtml(status) + '</div>'
940
+ + '</div>';
941
+ el.appendChild(row);
942
+ }
943
+ }
944
+
945
+ async function refreshHealthChecklist() {
946
+ try {
947
+ setPill('run', 'checklist…');
948
+ const r = await api('/api/health/checklist', { dir: dirOrDefault() });
949
+ if (r && r.needsInit) {
950
+ setOut(r.error || 'Workspace not initialized');
951
+ setPill('plan', 'needs init');
952
+ renderHealthChecklist([]);
953
+ return;
954
+ }
955
+ renderHealthChecklist(r.items || []);
956
+ setPill('ok', 'pronto');
957
+ } catch (e) {
958
+ setPill('err', 'checklist failed');
959
+ const el = $('healthChecklist');
960
+ if (el) el.textContent = 'Falha ao carregar checklist.';
961
+ }
962
+ }
963
+
882
964
  async function doHealth() {
883
965
  try {
884
966
  saveLocal();
@@ -886,6 +968,12 @@
886
968
  setPill('run', 'health…');
887
969
  setOut('');
888
970
  const r = await api('/api/health', { dir: dirOrDefault() });
971
+ if (r && r.needsInit) {
972
+ setOut(r.error || 'Workspace not initialized');
973
+ setLast(null);
974
+ setPill('plan', 'needs init');
975
+ return;
976
+ }
889
977
  setOut(r.output);
890
978
  setLast(null);
891
979
  setPill('ok', 'health ok');
@@ -1214,6 +1302,7 @@
1214
1302
  } catch {}
1215
1303
 
1216
1304
  const isReportsPage = document.body && document.body.dataset && document.body.dataset.page === 'reports';
1305
+ const isHealthPage = document.body && document.body.dataset && document.body.dataset.page === 'health';
1217
1306
 
1218
1307
  // Load persisted settings from the workspace + bootstrap (auto-init + auto-health)
1219
1308
  (async () => {
@@ -1241,6 +1330,11 @@
1241
1330
  return;
1242
1331
  }
1243
1332
 
1333
+ if (isHealthPage) {
1334
+ await refreshHealthChecklist();
1335
+ return;
1336
+ }
1337
+
1244
1338
  // If workspace isn't initialized yet, auto-init (reduces clicks)
1245
1339
  try {
1246
1340
  if (defaults && defaults.workspaceOk === false) {
@@ -1279,6 +1373,8 @@
1279
1373
  window.renderReportsList = renderReportsList;
1280
1374
  window.renderReportsPage = renderReportsPage;
1281
1375
  window.refreshReportsPage = refreshReportsPage;
1376
+ window.refreshBlockersInsights = refreshBlockersInsights;
1377
+ window.refreshHealthChecklist = refreshHealthChecklist;
1282
1378
  window.copyOut = copyOut;
1283
1379
  window.copyPath = copyPath;
1284
1380
  window.openSelected = openSelected;
package/cli/web.js CHANGED
@@ -877,6 +877,11 @@ function reportsHtml(defaultDir) {
877
877
  return buildReportsHtml(safeDefault, APP_VERSION);
878
878
  }
879
879
 
880
+ function healthHtml(defaultDir) {
881
+ const safeDefault = String(defaultDir || './freya').replace(/\\/g, '\\\\').replace(/"/g, '\\"');
882
+ return buildHealthHtml(safeDefault, APP_VERSION);
883
+ }
884
+
880
885
  function buildHtml(safeDefault, appVersion) {
881
886
  const safeVersion = escapeHtml(appVersion || 'unknown');
882
887
  return `<!doctype html>
@@ -899,6 +904,7 @@ function buildHtml(safeDefault, appVersion) {
899
904
  <div class="railNav">
900
905
  <button class="railBtn active" id="railDashboard" type="button" title="Dashboard">D</button>
901
906
  <button class="railBtn" id="railReports" type="button" title="Relatórios">R</button>
907
+ <button class="railBtn" id="railHealth" type="button" title="Saude">H</button>
902
908
  </div>
903
909
  <div class="railBottom">
904
910
  <div class="railStatus" id="railStatus" title="status"></div>
@@ -999,6 +1005,10 @@ function buildHtml(safeDefault, appVersion) {
999
1005
  <div style="height:12px"></div>
1000
1006
  <div class="small" style="margin-bottom:8px; opacity:.8">Bloqueios abertos</div>
1001
1007
  <div id="blockersList" style="display:grid; gap:8px"></div>
1008
+ <div style="height:12px"></div>
1009
+ <div class="small" style="margin-bottom:8px; opacity:.8">Insights de blockers</div>
1010
+ <div id="blockersInsights" class="help"></div>
1011
+ <div style="margin-top:8px"><button class="btn small" type="button" onclick="refreshBlockersInsights()">Atualizar insights</button></div>
1002
1012
  </div>
1003
1013
  </section>
1004
1014
 
@@ -1144,6 +1154,7 @@ function buildReportsHtml(safeDefault, appVersion) {
1144
1154
  <div class="railNav">
1145
1155
  <button class="railBtn" id="railDashboard" type="button" title="Dashboard">D</button>
1146
1156
  <button class="railBtn active" id="railReports" type="button" title="Relatórios">R</button>
1157
+ <button class="railBtn" id="railHealth" type="button" title="Saude">H</button>
1147
1158
  </div>
1148
1159
  <div class="railBottom">
1149
1160
  <div class="railStatus" id="railStatus" title="status"></div>
@@ -1195,6 +1206,87 @@ function buildReportsHtml(safeDefault, appVersion) {
1195
1206
  </script>
1196
1207
  <script src="/app.js"></script>
1197
1208
  </body>
1209
+ </html>`
1210
+ }
1211
+
1212
+ function buildHealthHtml(safeDefault, appVersion) {
1213
+ const safeVersion = escapeHtml(appVersion || 'unknown');
1214
+ return `<!doctype html>
1215
+ <html>
1216
+ <head>
1217
+ <meta charset="utf-8" />
1218
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
1219
+ <title>FREYA Health</title>
1220
+ <link rel="stylesheet" href="/app.css" />
1221
+ </head>
1222
+ <body data-page="health">
1223
+ <div class="app">
1224
+ <div class="frame">
1225
+ <div class="shell">
1226
+
1227
+ <aside class="rail">
1228
+ <div class="railTop">
1229
+ <div class="railLogo">F</div>
1230
+ </div>
1231
+ <div class="railNav">
1232
+ <button class="railBtn" id="railDashboard" type="button" title="Dashboard">D</button>
1233
+ <button class="railBtn" id="railReports" type="button" title="Relatórios">R</button>
1234
+ <button class="railBtn active" id="railHealth" type="button" title="Saude">H</button>
1235
+ </div>
1236
+ <div class="railBottom">
1237
+ <div class="railStatus" id="railStatus" title="status"></div>
1238
+ </div>
1239
+ </aside>
1240
+
1241
+ <main class="center reportsPage" id="healthPage">
1242
+ <div class="topbar">
1243
+ <div class="brandLine">
1244
+ <span class="spark"></span>
1245
+ <div class="brandStack">
1246
+ <div class="brand">FREYA</div>
1247
+ <div class="brandSub">Checklist de Saude</div>
1248
+ </div>
1249
+ </div>
1250
+ <div class="topActions">
1251
+ <span class="chip" id="chipVersion">v${safeVersion}</span>
1252
+ <span class="chip" id="chipPort">127.0.0.1:3872</span>
1253
+ </div>
1254
+ </div>
1255
+
1256
+ <div class="centerBody">
1257
+ <input id="dir" type="hidden" />
1258
+
1259
+ <section class="reportsHeader">
1260
+ <div>
1261
+ <div class="reportsTitle">Checklist de Saude</div>
1262
+ <div class="reportsSubtitle">Verificacoes rapidas antes de reunioes e rituais.</div>
1263
+ </div>
1264
+ <div class="reportsActions">
1265
+ <button class="btn small" type="button" onclick="refreshHealthChecklist()">Atualizar</button>
1266
+ <button class="btn small" type="button" onclick="doHealth()">Rodar health</button>
1267
+ </div>
1268
+ </section>
1269
+
1270
+ <section class="reportsTools" style="grid-template-columns: repeat(auto-fit,minmax(180px,1fr));">
1271
+ <button class="btn" type="button" onclick="runReport('daily')">Gerar Daily</button>
1272
+ <button class="btn" type="button" onclick="runReport('blockers')">Gerar Blockers</button>
1273
+ <button class="btn" type="button" onclick="runReport('status')">Gerar Status</button>
1274
+ <button class="btn" type="button" onclick="runReport('sm-weekly')">Gerar SM Weekly</button>
1275
+ </section>
1276
+
1277
+ <section class="reportsGrid" id="healthChecklist"></section>
1278
+ </div>
1279
+ </main>
1280
+
1281
+ </div>
1282
+ </div>
1283
+ </div>
1284
+
1285
+ <script>
1286
+ window.__FREYA_DEFAULT_DIR = "${safeDefault}";
1287
+ </script>
1288
+ <script src="/app.js"></script>
1289
+ </body>
1198
1290
  </html>`;
1199
1291
  }
1200
1292
 
@@ -1511,6 +1603,14 @@ async function cmdWeb({ port, dir, open, dev }) {
1511
1603
  return;
1512
1604
  }
1513
1605
 
1606
+ if (req.method === 'GET' && req.url === '/health') {
1607
+ try { res.__freyaDebug.workspaceDir = normalizeWorkspaceDir(dir || './freya'); } catch {}
1608
+ const body = healthHtml(dir || './freya');
1609
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store' });
1610
+ res.end(body);
1611
+ return;
1612
+ }
1613
+
1514
1614
  if (req.method === 'GET' && req.url === '/app.css') {
1515
1615
  const css = fs.readFileSync(path.join(__dirname, 'web-ui.css'), 'utf8');
1516
1616
  res.writeHead(200, { 'Content-Type': 'text/css; charset=utf-8', 'Cache-Control': 'no-store' });
@@ -1525,12 +1625,23 @@ async function cmdWeb({ port, dir, open, dev }) {
1525
1625
  return;
1526
1626
  }
1527
1627
 
1628
+ if (req.method === 'GET' && req.url === '/favicon.ico') {
1629
+ res.writeHead(204, { 'Cache-Control': 'no-store' });
1630
+ res.end();
1631
+ return;
1632
+ }
1633
+
1528
1634
  if (req.url.startsWith('/api/')) {
1529
1635
  const raw = await readBody(req);
1530
1636
  const payload = raw ? JSON.parse(raw) : {};
1531
1637
 
1532
1638
  const requestedDir = payload.dir || dir || './freya';
1533
- const workspaceDir = normalizeWorkspaceDir(requestedDir);
1639
+ let workspaceDir;
1640
+ try {
1641
+ workspaceDir = normalizeWorkspaceDir(requestedDir);
1642
+ } catch {
1643
+ workspaceDir = path.resolve(process.cwd(), requestedDir);
1644
+ }
1534
1645
 
1535
1646
  // debug logging (always on)
1536
1647
  try {
@@ -2035,7 +2146,15 @@ async function cmdWeb({ port, dir, open, dev }) {
2035
2146
 
2036
2147
  if (req.url === '/api/init') {
2037
2148
  try {
2038
- const { output } = await initWorkspace({ targetDir: workspaceDir, force: false, forceData: false, forceLogs: false });
2149
+ const requestedInitDir = String(payload.dir || '').trim() || './freya';
2150
+ let initDir;
2151
+ try {
2152
+ initDir = normalizeWorkspaceDir(requestedInitDir);
2153
+ } catch {
2154
+ initDir = path.resolve(process.cwd(), requestedInitDir);
2155
+ }
2156
+ res.__freyaDebug.workspaceDir = initDir;
2157
+ const { output } = await initWorkspace({ targetDir: initDir, force: false, forceData: false, forceLogs: false });
2039
2158
  return safeJson(res, 200, { output: String(output || '').trim() });
2040
2159
  } catch (e) {
2041
2160
  const message = e && e.message ? e.message : String(e);
@@ -2056,6 +2175,9 @@ async function cmdWeb({ port, dir, open, dev }) {
2056
2175
  const npmCmd = guessNpmCmd();
2057
2176
 
2058
2177
  if (req.url === '/api/health') {
2178
+ if (!looksLikeFreyaWorkspace(workspaceDir)) {
2179
+ return safeJson(res, 200, { ok: false, needsInit: true, error: 'Workspace not initialized' });
2180
+ }
2059
2181
  const r = await run(npmCmd, ['run', 'health'], workspaceDir);
2060
2182
  const output = (r.stdout + r.stderr).trim();
2061
2183
  return safeJson(res, r.code === 0 ? 200 : 400, r.code === 0 ? { output } : { error: output || 'health failed', output });
@@ -2282,6 +2404,72 @@ async function cmdWeb({ port, dir, open, dev }) {
2282
2404
  return safeJson(res, 200, { ok: true, task: updated });
2283
2405
  }
2284
2406
 
2407
+ if (req.url === '/api/health/checklist') {
2408
+ if (!looksLikeFreyaWorkspace(workspaceDir)) {
2409
+ return safeJson(res, 200, { ok: false, needsInit: true, error: 'Workspace not initialized', items: [] });
2410
+ }
2411
+
2412
+ const tasksFile = path.join(workspaceDir, 'data', 'tasks', 'task-log.json');
2413
+ const blockersFile = path.join(workspaceDir, 'data', 'blockers', 'blocker-log.json');
2414
+ const tasksDoc = readJsonOrNull(tasksFile) || { schemaVersion: 1, tasks: [] };
2415
+ const blockersDoc = readJsonOrNull(blockersFile) || { schemaVersion: 1, blockers: [] };
2416
+
2417
+ const pendingTasks = (tasksDoc.tasks || []).filter((t) => t && t.status === 'PENDING' && t.category === 'DO_NOW').length;
2418
+ const openBlockers = (blockersDoc.blockers || []).filter((b) => b && String(b.status || '').trim() === 'OPEN').length;
2419
+
2420
+ const today = isoDate();
2421
+ const reports = listReports(workspaceDir);
2422
+ const reportsToday = reports.filter((r) => r && String(r.name || '').includes(today)).length;
2423
+
2424
+ const items = [
2425
+ { label: 'Workspace inicializada', status: 'ok', detail: 'ok' },
2426
+ { label: 'Blockers abertos', status: openBlockers > 0 ? 'warn' : 'ok', detail: `${openBlockers} aberto(s)` },
2427
+ { label: 'Tarefas DO_NOW pendentes', status: pendingTasks > 0 ? 'warn' : 'ok', detail: `${pendingTasks} pendente(s)` },
2428
+ { label: 'Relatorios de hoje', status: reportsToday > 0 ? 'ok' : 'warn', detail: `${reportsToday} gerado(s)` }
2429
+ ];
2430
+
2431
+ return safeJson(res, 200, { ok: true, items, stats: { pendingTasks, openBlockers, reportsToday, reportsTotal: reports.length } });
2432
+ }
2433
+
2434
+ if (req.url === '/api/blockers/summary') {
2435
+ const file = path.join(workspaceDir, 'data', 'blockers', 'blocker-log.json');
2436
+ const doc = readJsonOrNull(file) || { schemaVersion: 1, blockers: [] };
2437
+ const blockers = Array.isArray(doc.blockers) ? doc.blockers : [];
2438
+
2439
+ const open = blockers.filter((b) => b && String(b.status || '').trim() === 'OPEN');
2440
+ const sevCount = { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0, OTHER: 0 };
2441
+ const now = Date.now();
2442
+ let older7 = 0;
2443
+ let missingSlug = 0;
2444
+
2445
+ for (const b of open) {
2446
+ const sev = String(b.severity || '').toUpperCase();
2447
+ if (sevCount[sev] !== undefined) sevCount[sev]++; else sevCount.OTHER++;
2448
+ if (!b.projectSlug) missingSlug++;
2449
+ const created = b.createdAt ? Date.parse(b.createdAt) : null;
2450
+ if (created && now - created > 7 * 24 * 60 * 60 * 1000) older7++;
2451
+ }
2452
+
2453
+ const top = open
2454
+ .sort((a, b) => String(a.createdAt || '').localeCompare(String(b.createdAt || '')))
2455
+ .slice(0, 5)
2456
+ .map((b) => ({
2457
+ id: b.id,
2458
+ title: b.title || b.description || 'Sem titulo',
2459
+ severity: b.severity || 'UNKNOWN',
2460
+ projectSlug: b.projectSlug || null
2461
+ }));
2462
+
2463
+ const suggestions = [];
2464
+ if (sevCount.CRITICAL > 0) suggestions.push('Escalar blockers CRITICAL agora e definir dono/ETA.');
2465
+ if (sevCount.HIGH > 0) suggestions.push('Agendar alinhamento rapido para blockers HIGH.');
2466
+ if (missingSlug > 0) suggestions.push('Adicionar projectSlug nos blockers sem contexto.');
2467
+ if (older7 > 0) suggestions.push('Revisar blockers com mais de 7 dias e decidir: resolver ou descartar.');
2468
+ if (!suggestions.length) suggestions.push('Nenhuma acao urgente. Monitorar diariamente.');
2469
+
2470
+ const summary = `CRITICAL: ${sevCount.CRITICAL}, HIGH: ${sevCount.HIGH}, MEDIUM: ${sevCount.MEDIUM}, LOW: ${sevCount.LOW}, TOTAL: ${open.length}`;
2471
+ return safeJson(res, 200, { ok: true, summary, counts: sevCount, suggestions, top });
2472
+ }
2285
2473
  if (req.url === '/api/blockers/list') {
2286
2474
  const limit = Math.max(1, Math.min(50, Number(payload.limit || 10)));
2287
2475
  const status = payload.status ? String(payload.status).trim() : 'OPEN';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cccarv82/freya",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "Personal AI Assistant with local-first persistence",
5
5
  "scripts": {
6
6
  "health": "node scripts/validate-data.js",
@@ -113,6 +113,7 @@ function generateReport() {
113
113
  const now = new Date();
114
114
  const nowMs = now.getTime();
115
115
  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}`; })();
116
117
  const blockers = loadBlockers();
117
118
 
118
119
  const statusCounts = new Map();
@@ -207,7 +208,7 @@ function generateReport() {
207
208
  }
208
209
 
209
210
  ensureReportDir();
210
- const outputPath = path.join(REPORT_DIR, `blockers-${reportDate}.md`);
211
+ const outputPath = path.join(REPORT_DIR, `blockers-${reportDate}-${reportTime}.md`);
211
212
  fs.writeFileSync(outputPath, report);
212
213
  console.log(report);
213
214
  }
@@ -93,7 +93,8 @@ function generateDailySummary() {
93
93
  try {
94
94
  fs.mkdirSync(REPORT_DIR, { recursive: true });
95
95
  const date = new Date().toISOString().slice(0, 10);
96
- const outPath = path.join(REPORT_DIR, `daily-${date}.md`);
96
+ const time = new Date().toTimeString().slice(0, 8).replace(/:/g, '');
97
+ const outPath = path.join(REPORT_DIR, `daily-${date}-${time}.md`);
97
98
  fs.writeFileSync(outPath, `# Daily Summary — ${date}\n\n${summary}\n`, 'utf8');
98
99
  } catch (e) {
99
100
  // non-fatal
@@ -407,7 +407,8 @@ function generateReport(period) {
407
407
 
408
408
  // Save
409
409
  ensureDir(OUTPUT_DIR);
410
- const filename = `executive-${period}-${dateStr}.md`;
410
+ const timeStr = new Date().toTimeString().slice(0, 8).replace(/:/g, '');
411
+ const filename = `executive-${period}-${dateStr}-${timeStr}.md`;
411
412
  const outputPath = path.join(OUTPUT_DIR, filename);
412
413
  fs.writeFileSync(outputPath, md, 'utf8');
413
414
 
@@ -67,6 +67,7 @@ function generate() {
67
67
  start.setDate(now.getDate() - 7);
68
68
 
69
69
  const reportDate = toIsoDate(now);
70
+ 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}`; })();
70
71
 
71
72
  const taskLog = readJsonOrQuarantine(TASKS_FILE, { schemaVersion: 1, tasks: [] });
72
73
  const blockersLog = readJsonOrQuarantine(BLOCKERS_FILE, { schemaVersion: 1, blockers: [] });
@@ -198,7 +199,7 @@ function generate() {
198
199
  }
199
200
  }
200
201
 
201
- const outPath = path.join(REPORT_DIR, `sm-weekly-${reportDate}.md`);
202
+ const outPath = path.join(REPORT_DIR, `sm-weekly-${reportDate}-${reportTime}.md`);
202
203
  fs.writeFileSync(outPath, md, 'utf8');
203
204
  console.log(md);
204
205
  console.log(`\nSaved: ${outPath}`);
@@ -24,6 +24,13 @@ function getFormattedDate() {
24
24
  return toIsoDate(now);
25
25
  }
26
26
 
27
+ function getFormattedTime() {
28
+ const hh = String(now.getHours()).padStart(2, '0');
29
+ const mm = String(now.getMinutes()).padStart(2, '0');
30
+ const ss = String(now.getSeconds()).padStart(2, '0');
31
+ return `${hh}${mm}${ss}`;
32
+ }
33
+
27
34
  // --- File Walking ---
28
35
  function walk(dir, fileList = []) {
29
36
  const files = fs.readdirSync(dir);
@@ -69,6 +76,7 @@ function generateWeeklyReport() {
69
76
 
70
77
  // 2. Generate Content
71
78
  const reportDate = getFormattedDate();
79
+ const reportTime = getFormattedTime();
72
80
  let report = `# Weekly Report - ${reportDate}\n\n`;
73
81
 
74
82
  // Projects
@@ -122,7 +130,7 @@ function generateWeeklyReport() {
122
130
  }
123
131
 
124
132
  // 3. Save and Output
125
- const outputPath = path.join(REPORT_DIR, `weekly-${reportDate}.md`);
133
+ const outputPath = path.join(REPORT_DIR, `weekly-${reportDate}-${reportTime}.md`);
126
134
  fs.writeFileSync(outputPath, report);
127
135
 
128
136
  console.log(`✅ Report generated at: ${outputPath}`);
@@ -113,6 +113,7 @@ function generateReport() {
113
113
  const now = new Date();
114
114
  const nowMs = now.getTime();
115
115
  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}`; })();
116
117
  const blockers = loadBlockers();
117
118
 
118
119
  const statusCounts = new Map();
@@ -207,7 +208,7 @@ function generateReport() {
207
208
  }
208
209
 
209
210
  ensureReportDir();
210
- const outputPath = path.join(REPORT_DIR, `blockers-${reportDate}.md`);
211
+ const outputPath = path.join(REPORT_DIR, `blockers-${reportDate}-${reportTime}.md`);
211
212
  fs.writeFileSync(outputPath, report);
212
213
  console.log(report);
213
214
  }
@@ -93,7 +93,8 @@ function generateDailySummary() {
93
93
  try {
94
94
  fs.mkdirSync(REPORT_DIR, { recursive: true });
95
95
  const date = new Date().toISOString().slice(0, 10);
96
- const outPath = path.join(REPORT_DIR, `daily-${date}.md`);
96
+ const time = new Date().toTimeString().slice(0, 8).replace(/:/g, '');
97
+ const outPath = path.join(REPORT_DIR, `daily-${date}-${time}.md`);
97
98
  fs.writeFileSync(outPath, `# Daily Summary — ${date}\n\n${summary}\n`, 'utf8');
98
99
  } catch (e) {
99
100
  // non-fatal
@@ -407,7 +407,8 @@ function generateReport(period) {
407
407
 
408
408
  // Save
409
409
  ensureDir(OUTPUT_DIR);
410
- const filename = `executive-${period}-${dateStr}.md`;
410
+ const timeStr = new Date().toTimeString().slice(0, 8).replace(/:/g, '');
411
+ const filename = `executive-${period}-${dateStr}-${timeStr}.md`;
411
412
  const outputPath = path.join(OUTPUT_DIR, filename);
412
413
  fs.writeFileSync(outputPath, md, 'utf8');
413
414
 
@@ -67,6 +67,7 @@ function generate() {
67
67
  start.setDate(now.getDate() - 7);
68
68
 
69
69
  const reportDate = toIsoDate(now);
70
+ 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}`; })();
70
71
 
71
72
  const taskLog = readJsonOrQuarantine(TASKS_FILE, { schemaVersion: 1, tasks: [] });
72
73
  const blockersLog = readJsonOrQuarantine(BLOCKERS_FILE, { schemaVersion: 1, blockers: [] });
@@ -198,7 +199,7 @@ function generate() {
198
199
  }
199
200
  }
200
201
 
201
- const outPath = path.join(REPORT_DIR, `sm-weekly-${reportDate}.md`);
202
+ const outPath = path.join(REPORT_DIR, `sm-weekly-${reportDate}-${reportTime}.md`);
202
203
  fs.writeFileSync(outPath, md, 'utf8');
203
204
  console.log(md);
204
205
  console.log(`\nSaved: ${outPath}`);
@@ -24,6 +24,13 @@ function getFormattedDate() {
24
24
  return toIsoDate(now);
25
25
  }
26
26
 
27
+ function getFormattedTime() {
28
+ const hh = String(now.getHours()).padStart(2, '0');
29
+ const mm = String(now.getMinutes()).padStart(2, '0');
30
+ const ss = String(now.getSeconds()).padStart(2, '0');
31
+ return `${hh}${mm}${ss}`;
32
+ }
33
+
27
34
  // --- File Walking ---
28
35
  function walk(dir, fileList = []) {
29
36
  const files = fs.readdirSync(dir);
@@ -69,6 +76,7 @@ function generateWeeklyReport() {
69
76
 
70
77
  // 2. Generate Content
71
78
  const reportDate = getFormattedDate();
79
+ const reportTime = getFormattedTime();
72
80
  let report = `# Weekly Report - ${reportDate}\n\n`;
73
81
 
74
82
  // Projects
@@ -122,7 +130,7 @@ function generateWeeklyReport() {
122
130
  }
123
131
 
124
132
  // 3. Save and Output
125
- const outputPath = path.join(REPORT_DIR, `weekly-${reportDate}.md`);
133
+ const outputPath = path.join(REPORT_DIR, `weekly-${reportDate}-${reportTime}.md`);
126
134
  fs.writeFileSync(outputPath, report);
127
135
 
128
136
  console.log(`✅ Report generated at: ${outputPath}`);