@cccarv82/freya 2.3.13 → 2.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/.agent/rules/freya/agents/coach.mdc +7 -16
  2. package/.agent/rules/freya/agents/ingestor.mdc +1 -89
  3. package/.agent/rules/freya/agents/master.mdc +3 -0
  4. package/.agent/rules/freya/agents/oracle.mdc +7 -23
  5. package/cli/web-ui.css +860 -182
  6. package/cli/web-ui.js +547 -175
  7. package/cli/web.js +690 -536
  8. package/package.json +6 -3
  9. package/scripts/build-vector-index.js +85 -0
  10. package/scripts/export-obsidian.js +6 -16
  11. package/scripts/generate-blockers-report.js +5 -17
  12. package/scripts/generate-daily-summary.js +25 -58
  13. package/scripts/generate-executive-report.js +22 -204
  14. package/scripts/generate-sm-weekly-report.js +27 -92
  15. package/scripts/lib/DataLayer.js +92 -0
  16. package/scripts/lib/DataManager.js +198 -0
  17. package/scripts/lib/Embedder.js +59 -0
  18. package/scripts/lib/schema.js +23 -0
  19. package/scripts/migrate-v1-v2.js +184 -0
  20. package/scripts/validate-data.js +48 -51
  21. package/scripts/validate-structure.js +12 -58
  22. package/templates/base/scripts/build-vector-index.js +85 -0
  23. package/templates/base/scripts/export-obsidian.js +143 -0
  24. package/templates/base/scripts/generate-daily-summary.js +25 -58
  25. package/templates/base/scripts/generate-executive-report.js +14 -225
  26. package/templates/base/scripts/generate-sm-weekly-report.js +9 -91
  27. package/templates/base/scripts/index/build-index.js +13 -0
  28. package/templates/base/scripts/index/update-index.js +15 -0
  29. package/templates/base/scripts/lib/DataLayer.js +92 -0
  30. package/templates/base/scripts/lib/DataManager.js +198 -0
  31. package/templates/base/scripts/lib/Embedder.js +59 -0
  32. package/templates/base/scripts/lib/index-utils.js +407 -0
  33. package/templates/base/scripts/lib/schema.js +23 -0
  34. package/templates/base/scripts/lib/search-utils.js +183 -0
  35. package/templates/base/scripts/migrate-v1-v2.js +184 -0
  36. package/templates/base/scripts/validate-data.js +48 -51
  37. package/templates/base/scripts/validate-structure.js +10 -32
package/cli/web.js CHANGED
@@ -8,6 +8,8 @@ const { spawn } = require('child_process');
8
8
  const { searchWorkspace } = require('../scripts/lib/search-utils');
9
9
  const { searchIndex } = require('../scripts/lib/index-utils');
10
10
  const { initWorkspace } = require('./init');
11
+ const { defaultInstance: dl } = require('../scripts/lib/DataLayer');
12
+ const DataManager = require('../scripts/lib/DataManager');
11
13
 
12
14
  function readAppVersion() {
13
15
  const pkgPath = path.join(__dirname, '..', 'package.json');
@@ -943,6 +945,11 @@ function timelineHtml(defaultDir) {
943
945
  return buildTimelineHtml(safeDefault, APP_VERSION);
944
946
  }
945
947
 
948
+ function settingsHtml(defaultDir) {
949
+ const safeDefault = String(defaultDir || './freya').replace(/\\/g, '\\\\').replace(/"/g, '\\"');
950
+ return buildSettingsHtml(safeDefault, APP_VERSION);
951
+ }
952
+
946
953
  function buildHtml(safeDefault, appVersion) {
947
954
  const safeVersion = escapeHtml(appVersion || 'unknown');
948
955
  return `<!doctype html>
@@ -963,15 +970,27 @@ function buildHtml(safeDefault, appVersion) {
963
970
  <div class="railLogo">F</div>
964
971
  </div>
965
972
  <div class="railNav">
966
- <button class="railBtn active" id="railDashboard" type="button" title="Dashboard">D</button>
967
- <button class="railBtn" id="railReports" type="button" title="Relatórios">R</button>
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>
971
- <button class="railBtn" id="railGraph" type="button" title="Grafo">G</button>
973
+ <button class="railBtn active" id="railDashboard" type="button" title="Dashboard">
974
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="9"></rect><rect x="14" y="3" width="7" height="5"></rect><rect x="14" y="12" width="7" height="9"></rect><rect x="3" y="16" width="7" height="5"></rect></svg>
975
+ </button>
976
+ <button class="railBtn" id="railReports" type="button" title="Relatórios">
977
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
978
+ </button>
979
+ <button class="railBtn" id="railCompanion" type="button" title="Companion">
980
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"></polygon></svg>
981
+ </button>
982
+ <button class="railBtn" id="railProjects" type="button" title="Projects">
983
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
984
+ </button>
985
+ <button class="railBtn" id="railTimeline" type="button" title="Timeline">
986
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>
987
+ </button>
988
+ <button class="railBtn" id="railGraph" type="button" title="Grafo">
989
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3"></circle><circle cx="6" cy="12" r="3"></circle><circle cx="18" cy="19" r="3"></circle><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line></svg>
990
+ </button>
972
991
  </div>
973
- <div class="railBottom">
974
- <div class="railStatus" id="railStatus" title="status"></div>
992
+ <div class="railBottom">\n <button id="railSettings" class="railBtn" title="Configurações" onclick="if (document.body.dataset.page !== \'settings\') window.location.href=\'/settings\';"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg></button>
993
+
975
994
  </div>
976
995
  </aside>
977
996
 
@@ -991,60 +1010,73 @@ function buildHtml(safeDefault, appVersion) {
991
1010
  </div>
992
1011
 
993
1012
  <div class="centerBody">
994
- <section class="promptShell">
995
- <div class="promptBar">
996
- <div class="promptMeta">
997
- <div class="promptTitle">Prompt</div>
998
- <div id="status" class="small">pronto</div>
999
- </div>
1000
- <textarea id="inboxText" rows="3" placeholder="Cole updates do dia (status, blockers, decisões, ideias) ou faça uma pergunta..."></textarea>
1001
- <div class="promptActions">
1002
- <button class="btn primary" type="button" onclick="saveAndPlan()">Salvar + Processar (Agents)</button>
1003
- <button class="btn" type="button" onclick="runSuggestedReports()">Rodar relatórios sugeridos</button>
1004
- <button class="btn" type="button" onclick="askFreya()">Perguntar à Freya</button>
1013
+ <section style="margin-bottom: 24px; display: grid; grid-template-columns: 1fr 1fr; gap: 16px; height: 380px; max-height: 450px;">
1014
+ <div class="promptBar" style="width: 100%; border-radius: 20px; height: 100%; display: flex; flex-direction: column; justify-content: space-between; overflow: hidden;">
1015
+ <div style="flex: 1; display: flex; flex-direction: column;">
1016
+ <div class="promptMeta">
1017
+ <div class="promptTitle" style="display: flex; align-items: center; gap: 8px;">
1018
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="color: var(--primary)"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="17 8 12 3 7 8"></polyline><line x1="12" y1="3" x2="12" y2="15"></line></svg>
1019
+ <span>Inbox & Prompt</span>
1020
+ </div>
1021
+ <div id="status" class="small">pronto</div>
1022
+ </div>
1023
+ <textarea id="inboxText" placeholder="Cole updates do dia (status, blockers, decisões, ideias) ou faça uma pergunta para a Freya..." style="resize:none; flex: 1; min-height: 0;"></textarea>
1005
1024
  </div>
1006
- <div class="promptToggles">
1007
- <label class="toggleRow">
1008
- <input id="autoApply" type="checkbox" checked style="width:auto" onchange="toggleAutoApply()" />
1009
- Auto-apply plan
1010
- </label>
1011
- <label class="toggleRow">
1012
- <input id="autoRunReports" type="checkbox" style="width:auto" onchange="toggleAutoRunReports()" />
1013
- Auto-run suggested reports
1014
- </label>
1025
+ <div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 12px; margin-top: 12px;">
1026
+ <div class="promptActions" style="margin: 0;">
1027
+ <button class="btn primary small" type="button" onclick="saveAndPlan()">Salvar + Processar</button>
1028
+ <button class="btn small" type="button" onclick="runSuggestedReports()">Rodar relatórios sugeridos</button>
1029
+ </div>
1030
+ <div class="promptToggles" style="margin: 0;">
1031
+ <label class="toggleRow">
1032
+ <input id="autoApply" type="checkbox" checked style="width:auto; margin: 0;" onchange="toggleAutoApply()" />
1033
+ Auto-apply
1034
+ </label>
1035
+ <label class="toggleRow">
1036
+ <input id="autoRunReports" type="checkbox" style="width:auto; margin: 0;" onchange="toggleAutoRunReports()" />
1037
+ Auto-run reports
1038
+ </label>
1039
+ </div>
1015
1040
  </div>
1016
1041
  </div>
1017
- </section>
1018
1042
 
1019
- <section class="utilityGrid" id="reportsSection">
1020
- <div class="utilityCard">
1021
- <div class="utilityHead">Área de trabalho</div>
1022
- <div class="sidePath" id="sidePath">./freya</div>
1023
- <div class="row" style="grid-template-columns: 1fr auto">
1024
- <input id="dir" placeholder="./freya" />
1025
- <button class="btn small" type="button" onclick="pickDir()">Browse</button>
1043
+ <div class="panel" style="height: 100%; display: flex; flex-direction: column;">
1044
+ <div class="panelHead">
1045
+ <b style="display: flex; align-items: center; gap: 8px;">
1046
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path></svg>
1047
+ Consultar Oracle
1048
+ </b>
1049
+ <div class="stack">
1050
+ <button class="btn small" type="button" onclick="exportChatObsidian()">Exportar Log</button>
1051
+ </div>
1026
1052
  </div>
1027
- <div class="stack" style="margin-top:10px">
1028
- <button class="btn" type="button" onclick="doUpdate()">Sincronizar workspace</button>
1029
- <button class="btn" type="button" onclick="doMigrate()">Migrar dados</button>
1053
+ <div class="panelBody" style="flex:1; display:flex; flex-direction:column; padding:0; background:var(--glass-bg); min-height:0;">
1054
+ <div id="chatThread" style="flex:1; overflow-y:auto; overflow-x:hidden; padding:12px; display:flex; flex-direction:column; gap:8px;"></div>
1055
+ <div style="padding: 12px; border-top: 1px solid var(--glass-border); display:flex; gap: 8px; background: var(--paper);">
1056
+ <input type="text" id="oracleInput" autocomplete="off" style="flex:1;" placeholder="Pergunte algo à Freya..." onkeydown="if(event.key==='Enter') window.askFreyaInline()" />
1057
+ <button class="btn primary small" type="button" onclick="window.askFreyaInline()">Enviar</button>
1058
+ </div>
1030
1059
  </div>
1031
- <div style="height:10px"></div>
1032
- <div class="help"><b>Sync workspace</b>: atualiza scripts/templates/agents na pasta <code>freya</code> sem sobrescrever <code>data/</code> e <code>logs/</code>.</div>
1033
- <div class="help"><b>Migrate data</b>: ajusta formatos/schemaVersion quando uma versão nova exige.</div>
1034
1060
  </div>
1061
+ </section>
1035
1062
 
1036
- <div class="utilityCard">
1037
- <div class="utilityHead">Relatórios rápidos</div>
1038
- <div class="cardsMini">
1039
- <button class="miniCard" type="button" onclick="runReport('status')"><span class="miniIcon">E</span><span>Executivo</span></button>
1040
- <button class="miniCard" type="button" onclick="runReport('sm-weekly')"><span class="miniIcon">S</span><span>SM semanal</span></button>
1041
- <button class="miniCard" type="button" onclick="runReport('blockers')"><span class="miniIcon warn">B</span><span>Bloqueios</span></button>
1042
- <button class="miniCard" type="button" onclick="runReport('daily')"><span class="miniIcon">D</span><span>Daily</span></button>
1063
+ <section class="utilityGrid" id="dashboardControls" style="display:grid; grid-template-columns: 1fr; gap: 16px; margin-bottom: 24px;">
1064
+ <div class="utilityCard" style="display:flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: 16px;">
1065
+ <div>
1066
+ <div class="utilityHead" style="margin-bottom: 4px; color: var(--text);">Relatórios Rápidos</div>
1067
+ <div class="help" style="margin: 0;">Gere relatórios sintéticos instantaneamente</div>
1068
+ </div>
1069
+ <div style="display:flex; gap: 8px; flex-wrap: wrap;">
1070
+ <button class="btn" style="min-width: 100px; padding: 6px 12px; font-weight: 500;" type="button" onclick="runReport('status')">Executivo</button>
1071
+ <button class="btn" style="min-width: 100px; padding: 6px 12px; font-weight: 500;" type="button" onclick="runReport('sm-weekly')">SM Semanal</button>
1072
+ <button class="btn" style="min-width: 100px; padding: 6px 12px; font-weight: 500; border-color: rgba(239, 68, 68, 0.4); color: #f87171;" type="button" onclick="runReport('blockers')">Bloqueios</button>
1073
+ <button class="btn" style="min-width: 100px; padding: 6px 12px; font-weight: 500;" type="button" onclick="runReport('daily')">Daily</button>
1043
1074
  </div>
1044
- <div class="help" style="margin-top:8px">Clique para gerar e atualizar o preview/publicação.</div>
1045
1075
  </div>
1046
1076
  </section>
1047
1077
 
1078
+
1079
+
1048
1080
  <div class="centerHead">
1049
1081
  <div>
1050
1082
  <h1 style="margin:0">Seu dia em um painel</h1>
@@ -1056,63 +1088,40 @@ function buildHtml(safeDefault, appVersion) {
1056
1088
  </div>
1057
1089
 
1058
1090
  <div class="midGrid">
1059
- <section class="panel">
1060
- <div class="panelHead">
1061
- <b>Hoje</b>
1091
+ <!-- Hoje Panel gets more prominent focus -->
1092
+ <section class="panel midSpan">
1093
+ <div class="panelHead" style="background: linear-gradient(90deg, var(--paper2), var(--paper)); border-left: 4px solid var(--accent)">
1094
+ <b style="color: var(--text); font-size: 14px;">Foco de Hoje</b>
1062
1095
  <div class="stack">
1063
1096
  <button class="btn small" type="button" onclick="refreshToday()">Atualizar</button>
1064
1097
  </div>
1065
1098
  </div>
1066
- <div class="panelBody panelScroll">
1067
- <div class="small" style="margin-bottom:8px; opacity:.8">Fazer agora</div>
1068
- <div id="tasksList" style="display:grid; gap:8px"></div>
1069
- <div style="height:12px"></div>
1070
- <div class="small" style="margin-bottom:8px; opacity:.8">Bloqueios abertos</div>
1071
- <div id="blockersList" style="display:grid; gap:8px"></div>
1072
- <div style="height:12px"></div>
1073
- <div class="small" style="margin-bottom:8px; opacity:.8">Insights de blockers</div>
1074
- <div id="blockersInsights" class="help"></div>
1075
- <div style="margin-top:8px"><button class="btn small" type="button" onclick="refreshBlockersInsights()">Atualizar insights</button></div>
1099
+ <div class="panelBody" style="display: flex; flex-direction: column; gap: 16px;">
1100
+ <div id="swimlaneContainer" style="display:flex; flex-direction: column; gap: 12px;"></div>
1101
+
1102
+ <div style="border-top: 1px solid var(--border); padding-top: 16px;">
1103
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
1104
+ <div class="small" style="opacity:.8; font-weight: 600; text-transform: uppercase;">Insights Globais de Bloqueios</div>
1105
+ <button class="btn small" type="button" onclick="refreshBlockersInsights()">Atualizar insights</button>
1106
+ </div>
1107
+ <div id="blockersInsights" class="help"></div>
1108
+ </div>
1076
1109
  </div>
1077
1110
  </section>
1078
1111
 
1079
1112
  <section class="panel">
1080
1113
  <div class="panelHead">
1081
- <b>Relatórios</b>
1114
+ <b>Relatórios Recentes</b>
1082
1115
  <div class="stack">
1083
1116
  <button class="btn small" type="button" onclick="refreshReports()">Atualizar</button>
1084
1117
  </div>
1085
1118
  </div>
1086
- <div class="panelBody panelScroll">
1087
- <input id="reportsFilter" placeholder="filter (ex: daily, executive, 2026-01-29)" style="width:100%; margin-bottom:10px" oninput="renderReportsList()" />
1119
+ <div class="panelBody panelScroll" style="max-height: 300px;">
1120
+ <input id="reportsFilter" placeholder="Filtrar relatórios..." style="width:100%; margin-bottom:10px" oninput="renderReportsList()" />
1088
1121
  <div id="reportsList" style="display:grid; gap:8px"></div>
1089
- <div class="help">Últimos relatórios em <code>docs/reports</code>. Clique para abrir o preview.</div>
1090
- </div>
1091
- </section>
1092
-
1093
- <section class="panel midSpan">
1094
- <div class="panelHead">
1095
- <b>Preview</b>
1096
- <div class="stack">
1097
- <button class="btn small" type="button" onclick="copyOut()">Copy</button>
1098
- <button class="btn small" type="button" onclick="applyPlan()">Apply plan</button>
1099
- <button class="btn small" type="button" onclick="copyPath()">Copy path</button>
1100
- <button class="btn small" type="button" onclick="openSelected()">Open file</button>
1101
- <button class="btn small" type="button" onclick="downloadSelected()">Download .md</button>
1102
- <button class="btn small" type="button" onclick="clearOut()">Clear</button>
1103
- </div>
1104
- </div>
1105
- <div class="panelBody">
1106
- <div id="reportPreview" class="log md" style="font-family: var(--sans);"></div>
1107
- <div class="help">O preview renderiza Markdown básico (headers, listas, code). O botão Copy copia o conteúdo completo.</div>
1108
1122
  </div>
1109
1123
  </section>
1110
1124
  </div>
1111
-
1112
- <details class="devDrawer" id="devDrawer">
1113
- <summary>Developer (modo avançado)</summary>
1114
- <div class="devBody">
1115
- <div class="devGrid">
1116
1125
  <div class="panel">
1117
1126
  <div class="panelHead"><b>Configurações de publicação</b></div>
1118
1127
  <div class="panelBody">
@@ -1128,61 +1137,6 @@ function buildHtml(safeDefault, appVersion) {
1128
1137
  <label style="display:flex; align-items:center; gap:10px; user-select:none; margin: 6px 0 12px 0">
1129
1138
  <input id="prettyPublish" type="checkbox" checked style="width:auto" onchange="togglePrettyPublish()" />
1130
1139
  Publicação bonita (cards/embeds)
1131
- </label>
1132
-
1133
- <div class="stack">
1134
- <button class="btn" type="button" onclick="saveSettings()">Salvar configurações</button>
1135
- <button class="btn" type="button" onclick="publish('discord')">Publicar selecionado → Discord</button>
1136
- <button class="btn" type="button" onclick="publish('teams')">Publicar selecionado → Teams</button>
1137
- </div>
1138
- </div>
1139
- </div>
1140
-
1141
- <div class="panel">
1142
- <div class="panelHead"><b>Slugs & Export</b></div>
1143
- <div class="panelBody">
1144
- <label>Regras de slug do projeto</label>
1145
- <textarea id="slugRules" rows="8" placeholder="{ \"rules\": [ { \"contains\": \"fideliza\", \"slug\": \"vivo/fidelizacao\" } ] }"></textarea>
1146
- <div class="help">Regras usadas pra inferir <code>projectSlug</code>. Formato JSON (objeto com <code>rules</code>).</div>
1147
- <div class="stack" style="margin-top:10px">
1148
- <button class="btn" type="button" onclick="reloadSlugRules()">Recarregar regras</button>
1149
- <button class="btn" type="button" onclick="saveSlugRules()">Salvar regras</button>
1150
- <button class="btn" type="button" onclick="exportObsidian()">Exportar notas (Obsidian)</button>
1151
- </div>
1152
- </div>
1153
- </div>
1154
-
1155
- <div class="panel">
1156
- <div class="panelHead"><b>Debug</b></div>
1157
- <div class="panelBody">
1158
- <div class="help">Logs ficam em <code>logs/</code> e debug traces em <code>.debuglogs/</code> dentro da workspace.</div>
1159
- <div class="help">Use <b>Open file</b> / <b>Copy path</b> no Preview para abrir/compartilhar o relatório selecionado.</div>
1160
- <div class="stack" style="margin-top:10px">
1161
- <button class="btn" type="button" onclick="rebuildIndex()">Rebuild search index</button>
1162
- </div>
1163
- </div>
1164
- </div>
1165
- </div>
1166
- </div>
1167
- </details>
1168
- </div>
1169
- </main>
1170
-
1171
- <aside class="chatPane">
1172
- <div class="chatHead">
1173
- <div>
1174
- <div class="chatTitle">Conversa</div>
1175
- <div class="chatSub">Cole seus updates e deixe os Agents planejar/aplicar.</div>
1176
- </div>
1177
- </div>
1178
-
1179
- <div class="chatThread" id="chatThread">
1180
- <div class="bubble assistant">
1181
- <div class="bubbleMeta">FREYA</div>
1182
- <div class="bubbleBody">Cole seus updates (status, blockers, decisões, ideias) e clique em <b>Save + Process</b>.</div>
1183
- </div>
1184
- </div>
1185
- </aside>
1186
1140
 
1187
1141
  </div>
1188
1142
  </div>
@@ -1216,15 +1170,27 @@ function buildReportsHtml(safeDefault, appVersion) {
1216
1170
  <div class="railLogo">F</div>
1217
1171
  </div>
1218
1172
  <div class="railNav">
1219
- <button class="railBtn" id="railDashboard" type="button" title="Dashboard">D</button>
1220
- <button class="railBtn active" id="railReports" type="button" title="Relatórios">R</button>
1221
- <button class="railBtn" id="railCompanion" type="button" title="Companion">C</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>
1173
+ <button class="railBtn" id="railDashboard" type="button" title="Dashboard">
1174
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="9"></rect><rect x="14" y="3" width="7" height="5"></rect><rect x="14" y="12" width="7" height="9"></rect><rect x="3" y="16" width="7" height="5"></rect></svg>
1175
+ </button>
1176
+ <button class="railBtn active" id="railReports" type="button" title="Relatórios">
1177
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
1178
+ </button>
1179
+ <button class="railBtn" id="railCompanion" type="button" title="Companion">
1180
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"></polygon></svg>
1181
+ </button>
1182
+ <button class="railBtn" id="railProjects" type="button" title="Projects">
1183
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
1184
+ </button>
1185
+ <button class="railBtn" id="railTimeline" type="button" title="Timeline">
1186
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>
1187
+ </button>
1188
+ <button class="railBtn" id="railGraph" type="button" title="Grafo">
1189
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3"></circle><circle cx="6" cy="12" r="3"></circle><circle cx="18" cy="19" r="3"></circle><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line></svg>
1190
+ </button>
1225
1191
  </div>
1226
- <div class="railBottom">
1227
- <div class="railStatus" id="railStatus" title="status"></div>
1192
+ <div class="railBottom">\n <button id="railSettings" class="railBtn" title="Configura��es">\n <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>\n </button>
1193
+
1228
1194
  </div>
1229
1195
  </aside>
1230
1196
 
@@ -1256,8 +1222,12 @@ function buildReportsHtml(safeDefault, appVersion) {
1256
1222
  </div>
1257
1223
  </section>
1258
1224
 
1259
- <section class="reportsTools">
1260
- <input id="reportsFilter" placeholder="filtrar (ex: daily, executive, 2026-01-29)" oninput="renderReportsPage()" />
1225
+ <section class="reportsTools" style="display:flex; gap:12px; align-items:center;">
1226
+ <div class="tabs" style="display:flex; gap:8px;" id="reportsTabs">
1227
+ <button class="pill ok" id="tabChrono" onclick="setReportsTab('chrono')">Cronológico</button>
1228
+ <button class="pill" id="tabType" onclick="setReportsTab('type')">Por Tipo</button>
1229
+ </div>
1230
+ <input id="reportsFilter" style="flex:1" placeholder="filtrar (ex: daily, executive, 2026-01-29)" oninput="renderReportsPage()" />
1261
1231
  </section>
1262
1232
 
1263
1233
  <section class="reportsGrid" id="reportsGrid"></section>
@@ -1296,15 +1266,27 @@ function buildProjectsHtml(safeDefault, appVersion) {
1296
1266
  <div class="railLogo">F</div>
1297
1267
  </div>
1298
1268
  <div class="railNav">
1299
- <button class="railBtn" id="railDashboard" type="button" title="Dashboard">D</button>
1300
- <button class="railBtn" id="railReports" type="button" title="Relatorios">R</button>
1301
- <button class="railBtn" id="railCompanion" type="button" title="Companion">C</button>
1302
- <button class="railBtn active" id="railProjects" type="button" title="Projects">P</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>
1269
+ <button class="railBtn" id="railDashboard" type="button" title="Dashboard">
1270
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="9"></rect><rect x="14" y="3" width="7" height="5"></rect><rect x="14" y="12" width="7" height="9"></rect><rect x="3" y="16" width="7" height="5"></rect></svg>
1271
+ </button>
1272
+ <button class="railBtn" id="railReports" type="button" title="Relatorios">
1273
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
1274
+ </button>
1275
+ <button class="railBtn" id="railCompanion" type="button" title="Companion">
1276
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"></polygon></svg>
1277
+ </button>
1278
+ <button class="railBtn active" id="railProjects" type="button" title="Projects">
1279
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
1280
+ </button>
1281
+ <button class="railBtn" id="railTimeline" type="button" title="Timeline">
1282
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>
1283
+ </button>
1284
+ <button class="railBtn" id="railGraph" type="button" title="Grafo">
1285
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3"></circle><circle cx="6" cy="12" r="3"></circle><circle cx="18" cy="19" r="3"></circle><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line></svg>
1286
+ </button>
1305
1287
  </div>
1306
- <div class="railBottom">
1307
- <div class="railStatus" id="railStatus" title="status"></div>
1288
+ <div class="railBottom">\n <button id="railSettings" class="railBtn" title="Configura��es">\n <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>\n </button>
1289
+
1308
1290
  </div>
1309
1291
  </aside>
1310
1292
 
@@ -1376,12 +1358,24 @@ function buildTimelineHtml(safeDefault, appVersion) {
1376
1358
  <div class=\"railLogo\">F</div>
1377
1359
  </div>
1378
1360
  <div class=\"railNav\">
1379
- <button class=\"railBtn\" id=\"railDashboard\" type=\"button\" title=\"Dashboard\">D</button>
1380
- <button class=\"railBtn\" id=\"railReports\" type=\"button\" title=\"Relatorios\">R</button>
1381
- <button class=\"railBtn\" id=\"railCompanion\" type=\"button\" title=\"Companion\">C</button>
1382
- <button class=\"railBtn\" id=\"railProjects\" type=\"button\" title=\"Projects\">P</button>
1383
- <button class=\"railBtn active\" id=\"railTimeline\" type=\"button\" title=\"Timeline\">T</button>
1384
- <button class=\"railBtn\" id=\"railGraph\" type=\"button\" title=\"Grafo\">G</button>
1361
+ <button class=\"railBtn\" id=\"railDashboard\" type=\"button\" title=\"Dashboard\">
1362
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="9"></rect><rect x="14" y="3" width="7" height="5"></rect><rect x="14" y="12" width="7" height="9"></rect><rect x="3" y="16" width="7" height="5"></rect></svg>
1363
+ </button>
1364
+ <button class=\"railBtn\" id=\"railReports\" type=\"button\" title=\"Relatorios\">
1365
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
1366
+ </button>
1367
+ <button class=\"railBtn\" id=\"railCompanion\" type=\"button\" title=\"Companion\">
1368
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"></polygon></svg>
1369
+ </button>
1370
+ <button class=\"railBtn\" id=\"railProjects\" type=\"button\" title=\"Projects\">
1371
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
1372
+ </button>
1373
+ <button class=\"railBtn active\" id=\"railTimeline\" type=\"button\" title=\"Timeline\">
1374
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>
1375
+ </button>
1376
+ <button class=\"railBtn\" id=\"railGraph\" type=\"button\" title=\"Grafo\">
1377
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3"></circle><circle cx="6" cy="12" r="3"></circle><circle cx="18" cy="19" r="3"></circle><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line></svg>
1378
+ </button>
1385
1379
  </div>
1386
1380
  <div class=\"railBottom\">
1387
1381
  <div class=\"railStatus\" id=\"railStatus\" title=\"status\"></div>
@@ -1471,15 +1465,27 @@ function buildGraphHtml(safeDefault, appVersion) {
1471
1465
  <div class="railLogo">F</div>
1472
1466
  </div>
1473
1467
  <div class="railNav">
1474
- <button class="railBtn" id="railDashboard" type="button" title="Dashboard">D</button>
1475
- <button class="railBtn" id="railReports" type="button" title="Relatorios">R</button>
1476
- <button class="railBtn" id="railCompanion" type="button" title="Companion">C</button>
1477
- <button class="railBtn" id="railProjects" type="button" title="Projects">P</button>
1478
- <button class="railBtn" id="railTimeline" type="button" title="Timeline">T</button>
1479
- <button class="railBtn active" id="railGraph" type="button" title="Grafo">G</button>
1468
+ <button class="railBtn" id="railDashboard" type="button" title="Dashboard">
1469
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="9"></rect><rect x="14" y="3" width="7" height="5"></rect><rect x="14" y="12" width="7" height="9"></rect><rect x="3" y="16" width="7" height="5"></rect></svg>
1470
+ </button>
1471
+ <button class="railBtn" id="railReports" type="button" title="Relatorios">
1472
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
1473
+ </button>
1474
+ <button class="railBtn" id="railCompanion" type="button" title="Companion">
1475
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"></polygon></svg>
1476
+ </button>
1477
+ <button class="railBtn" id="railProjects" type="button" title="Projects">
1478
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
1479
+ </button>
1480
+ <button class="railBtn" id="railTimeline" type="button" title="Timeline">
1481
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>
1482
+ </button>
1483
+ <button class="railBtn active" id="railGraph" type="button" title="Grafo">
1484
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3"></circle><circle cx="6" cy="12" r="3"></circle><circle cx="18" cy="19" r="3"></circle><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line></svg>
1485
+ </button>
1480
1486
  </div>
1481
- <div class="railBottom">
1482
- <div class="railStatus" id="railStatus" title="status"></div>
1487
+ <div class="railBottom">\n <button id="railSettings" class="railBtn" title="Configura��es">\n <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>\n </button>
1488
+
1483
1489
  </div>
1484
1490
  </aside>
1485
1491
 
@@ -1549,15 +1555,27 @@ function buildCompanionHtml(safeDefault, appVersion) {
1549
1555
  <div class="railLogo">F</div>
1550
1556
  </div>
1551
1557
  <div class="railNav">
1552
- <button class="railBtn" id="railDashboard" type="button" title="Dashboard">D</button>
1553
- <button class="railBtn" id="railReports" type="button" title="Relatórios">R</button>
1554
- <button class="railBtn active" id="railCompanion" type="button" title="Companion">C</button>
1555
- <button class="railBtn" id="railProjects" type="button" title="Projects">P</button>
1556
- <button class="railBtn" id="railTimeline" type="button" title="Timeline">T</button>
1557
- <button class="railBtn" id="railGraph" type="button" title="Grafo">G</button>
1558
+ <button class="railBtn" id="railDashboard" type="button" title="Dashboard">
1559
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="9"></rect><rect x="14" y="3" width="7" height="5"></rect><rect x="14" y="12" width="7" height="9"></rect><rect x="3" y="16" width="7" height="5"></rect></svg>
1560
+ </button>
1561
+ <button class="railBtn" id="railReports" type="button" title="Relatórios">
1562
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
1563
+ </button>
1564
+ <button class="railBtn active" id="railCompanion" type="button" title="Companion">
1565
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"></polygon></svg>
1566
+ </button>
1567
+ <button class="railBtn" id="railProjects" type="button" title="Projects">
1568
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
1569
+ </button>
1570
+ <button class="railBtn" id="railTimeline" type="button" title="Timeline">
1571
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>
1572
+ </button>
1573
+ <button class="railBtn" id="railGraph" type="button" title="Grafo">
1574
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3"></circle><circle cx="6" cy="12" r="3"></circle><circle cx="18" cy="19" r="3"></circle><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line></svg>
1575
+ </button>
1558
1576
  </div>
1559
- <div class="railBottom">
1560
- <div class="railStatus" id="railStatus" title="status"></div>
1577
+ <div class="railBottom">\n <button id="railSettings" class="railBtn" title="Configura��es">\n <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>\n </button>
1578
+
1561
1579
  </div>
1562
1580
  </aside>
1563
1581
 
@@ -1582,81 +1600,107 @@ function buildCompanionHtml(safeDefault, appVersion) {
1582
1600
  <section class="reportsHeader">
1583
1601
  <div>
1584
1602
  <div class="reportsTitle">Scrum Master Companion</div>
1585
- <div class="reportsSubtitle">Painel rapido para gerar relatorios e checar pendencias.</div>
1603
+ <div class="reportsSubtitle">Painel rápido para gerar relatórios e checar pendências.</div>
1586
1604
  </div>
1587
1605
  <div class="reportsActions">
1588
- <button class="btn small" type="button" onclick="refreshHealthChecklist()">Atualizar</button> </div>
1606
+ <button class="btn small" type="button" onclick="refreshHealthChecklist()">Atualizar Painel</button>
1607
+ </div>
1589
1608
  </section>
1590
1609
 
1591
- <section class="reportsTools" style="grid-template-columns: repeat(auto-fit,minmax(180px,1fr));">
1610
+ <section class="reportsTools" style="display: grid; grid-template-columns: repeat(auto-fit,minmax(180px,1fr)); gap: 12px; margin-bottom: 24px;">
1592
1611
  <button class="btn" type="button" onclick="runReport('daily')">Gerar Daily</button>
1593
1612
  <button class="btn" type="button" onclick="runReport('blockers')">Gerar Blockers</button>
1594
1613
  <button class="btn" type="button" onclick="runReport('status')">Gerar Status</button>
1595
1614
  <button class="btn" type="button" onclick="runReport('sm-weekly')">Gerar SM Weekly</button>
1596
1615
  </section>
1597
1616
 
1598
- <section class="reportsGrid" id="healthChecklist"></section>
1599
-
1600
- <section class="panel" style="margin-top:16px">
1601
- <div class="panelHead" style="display:flex; align-items:center; justify-content:space-between; gap:10px">
1602
- <b>Qualidade de Log</b>
1603
- <button class="btn small" type="button" onclick="refreshQualityScore()">Atualizar</button>
1604
- </div>
1605
- <div class="panelBody">
1606
- <div id="qualityScoreCard"></div>
1607
- </div>
1608
- </section>
1617
+ <!-- BENTO GRID LAYOUT -->
1618
+ <div style="display: grid; grid-template-columns: 1fr 1.5fr; gap: 24px; align-items: start;">
1619
+
1620
+ <!-- Left Column / Block 1 -->
1621
+ <div style="display: flex; flex-direction: column; gap: 16px;">
1622
+ <section class="panel">
1623
+ <div class="panelHead" style="display:flex; align-items:center; justify-content:space-between; gap:10px">
1624
+ <b>Resumo Executivo</b>
1625
+ <button class="btn small" type="button" onclick="refreshExecutiveSummary()">↻</button>
1626
+ </div>
1627
+ <div class="panelBody">
1628
+ <div id="executiveSummary" class="help"></div>
1629
+ </div>
1630
+ </section>
1631
+
1632
+ <section class="panel">
1633
+ <div class="panelHead" style="display:flex; align-items:center; justify-content:space-between; gap:10px">
1634
+ <b>Radar de Risco</b>
1635
+ <button class="btn small" type="button" onclick="refreshRiskRadar()">↻</button>
1636
+ </div>
1637
+ <div class="panelBody">
1638
+ <div id="riskRadarBox"></div>
1639
+ </div>
1640
+ </section>
1609
1641
 
1610
- <section class="panel" style="margin-top:16px">
1611
- <div class="panelHead" style="display:flex; align-items:center; justify-content:space-between; gap:10px">
1612
- <b>Resumo Executivo</b>
1613
- <button class="btn small" type="button" onclick="refreshExecutiveSummary()">Atualizar</button>
1614
- </div>
1615
- <div class="panelBody">
1616
- <div id="executiveSummary" class="help"></div>
1642
+ <section class="panel">
1643
+ <div class="panelHead" style="display:flex; align-items:center; justify-content:space-between; gap:10px">
1644
+ <b>Task Heatmap</b>
1645
+ </div>
1646
+ <div class="panelBody">
1647
+ <div id="heatmapGrid"></div>
1648
+ </div>
1649
+ </section>
1617
1650
  </div>
1618
- </section>
1619
1651
 
1620
- <section class="panel" style="margin-top:16px">
1621
- <div class="panelHead" style="display:flex; align-items:center; justify-content:space-between; gap:10px">
1622
- <b>Anomalias</b>
1623
- <button class="btn small" type="button" onclick="refreshAnomalies()">Atualizar</button>
1624
- </div>
1625
- <div class="panelBody">
1626
- <div id="anomaliesBox"></div>
1627
- </div>
1628
- </section>
1652
+ <!-- Right Column / Block 2 -->
1653
+ <div style="display: flex; flex-direction: column; gap: 16px;">
1654
+ <section class="panel">
1655
+ <div class="panelHead" style="display:flex; align-items:center; justify-content:space-between; gap:10px">
1656
+ <b style="color: var(--accent);">Saúde & Checklists</b>
1657
+ </div>
1658
+ <div class="panelBody" style="padding: 0;">
1659
+ <section class="reportsGrid" id="healthChecklist" style="gap: 0; border: none; box-shadow: none;"></section>
1660
+ </div>
1661
+ </section>
1629
1662
 
1630
- <section class="panel" style="margin-top:16px">
1631
- <div class="panelHead" style="display:flex; align-items:center; justify-content:space-between; gap:10px">
1632
- <b>Radar de Risco</b>
1633
- <button class="btn small" type="button" onclick="refreshRiskRadar()">Atualizar</button>
1634
- </div>
1635
- <div class="panelBody">
1636
- <div id="riskRadarBox"></div>
1637
- </div>
1638
- </section>
1663
+ <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px;">
1664
+ <section class="panel">
1665
+ <div class="panelHead" style="display:flex; align-items:center; justify-content:space-between; gap:10px">
1666
+ <b>Qualidade de Log</b>
1667
+ <button class="btn small" type="button" onclick="refreshQualityScore()">↻</button>
1668
+ </div>
1669
+ <div class="panelBody">
1670
+ <div id="qualityScoreCard"></div>
1671
+ </div>
1672
+ </section>
1639
1673
 
1640
- <section class="panel" style="margin-top:16px">
1641
- <div class="panelHead"><b>Incident Radar</b></div>
1642
- <div class="panelBody">
1643
- <div id="incidentsBox" class="log md" style="font-family: var(--sans);"></div>
1674
+ <section class="panel">
1675
+ <div class="panelHead" style="display:flex; align-items:center; justify-content:space-between; gap:10px">
1676
+ <b>Anomalias</b>
1677
+ <button class="btn small" type="button" onclick="refreshAnomalies()">↻</button>
1678
+ </div>
1679
+ <div class="panelBody">
1680
+ <div id="anomaliesBox"></div>
1681
+ </div>
1682
+ </section>
1683
+ </div>
1644
1684
  </div>
1645
- </section>
1685
+
1686
+ <!-- Full Width Span -->
1687
+ <div style="grid-column: 1 / -1; display: flex; flex-direction: column; gap: 16px;">
1688
+ <section class="panel">
1689
+ <div class="panelHead"><b>Incident Radar</b></div>
1690
+ <div class="panelBody">
1691
+ <div id="incidentsBox" class="log md" style="font-family: var(--sans);"></div>
1692
+ </div>
1693
+ </section>
1646
1694
 
1647
- <section class="panel" style="margin-top:16px">
1648
- <div class="panelHead"><b>Task Heatmap</b></div>
1649
- <div class="panelBody">
1650
- <div id="heatmapGrid"></div>
1695
+ <section class="panel">
1696
+ <div class="panelHead"><b>Saída de Relatório</b></div>
1697
+ <div class="panelBody">
1698
+ <div id="reportPreview" class="log md" style="font-family: var(--sans);"></div>
1699
+ </div>
1700
+ </section>
1651
1701
  </div>
1652
- </section>
1653
1702
 
1654
- <section class="panel" style="margin-top:16px">
1655
- <div class="panelHead"><b>Saida</b></div>
1656
- <div class="panelBody">
1657
- <div id="reportPreview" class="log md" style="font-family: var(--sans);"></div>
1658
- </div>
1659
- </section>
1703
+ </div>
1660
1704
  </div>
1661
1705
  </main>
1662
1706
 
@@ -2073,6 +2117,14 @@ async function cmdWeb({ port, dir, open, dev }) {
2073
2117
  return;
2074
2118
  }
2075
2119
 
2120
+ if (req.method === 'GET' && req.url === '/settings') {
2121
+ try { res.__freyaDebug.workspaceDir = normalizeWorkspaceDir(dir || './freya'); } catch { }
2122
+ const body = settingsHtml(dir || './freya');
2123
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store' });
2124
+ res.end(body);
2125
+ return;
2126
+ }
2127
+
2076
2128
  if (req.method === 'GET' && req.url === '/app.css') {
2077
2129
  const css = fs.readFileSync(path.join(__dirname, 'web-ui.css'), 'utf8');
2078
2130
  res.writeHead(200, { 'Content-Type': 'text/css; charset=utf-8', 'Cache-Control': 'no-store' });
@@ -2166,34 +2218,23 @@ async function cmdWeb({ port, dir, open, dev }) {
2166
2218
  }
2167
2219
 
2168
2220
  if (req.url === '/api/projects/list') {
2169
- const base = path.join(workspaceDir, 'data', 'Clients');
2221
+ const rawProjects = dl.db.prepare('SELECT * FROM projects').all();
2170
2222
  const items = [];
2171
- if (exists(base)) {
2172
- const stack = [base];
2173
- while (stack.length) {
2174
- const dirp = stack.pop();
2175
- const entries = fs.readdirSync(dirp, { withFileTypes: true });
2176
- for (const ent of entries) {
2177
- const full = path.join(dirp, ent.name);
2178
- if (ent.isDirectory()) stack.push(full);
2179
- else if (ent.isFile() && ent.name === 'status.json') {
2180
- const doc = readJsonOrNull(full) || {};
2181
- const rel = path.relative(base, path.dirname(full)).replace(/\\/g, '/');
2182
- items.push({
2183
- slug: rel,
2184
- client: doc.client || null,
2185
- program: doc.program || null,
2186
- stream: doc.stream || null,
2187
- project: doc.project || null,
2188
- active: doc.active !== false,
2189
- currentStatus: doc.currentStatus || '',
2190
- lastUpdated: doc.lastUpdated || '',
2191
- tags: Array.isArray(doc.tags) ? doc.tags : [],
2192
- historyCount: Array.isArray(doc.history) ? doc.history.length : 0
2193
- });
2194
- }
2195
- }
2196
- }
2223
+ for (const p of rawProjects) {
2224
+ const h = dl.db.prepare('SELECT count(*) as count FROM project_status_history WHERE project_id = ?').get(p.id);
2225
+ const historyCount = h ? h.count : 0;
2226
+ const latestStatus = dl.db.prepare('SELECT status_text FROM project_status_history WHERE project_id = ? ORDER BY date DESC LIMIT 1').get(p.id);
2227
+
2228
+ items.push({
2229
+ slug: p.slug,
2230
+ client: p.client,
2231
+ project: p.name,
2232
+ active: p.is_active === 1,
2233
+ currentStatus: latestStatus ? latestStatus.status_text : '',
2234
+ lastUpdated: p.updated_at,
2235
+ historyCount: historyCount,
2236
+ tags: []
2237
+ });
2197
2238
  }
2198
2239
  items.sort((a, b) => String(b.lastUpdated || '').localeCompare(String(a.lastUpdated || '')));
2199
2240
  return safeJson(res, 200, { ok: true, projects: items });
@@ -2213,68 +2254,33 @@ async function cmdWeb({ port, dir, open, dev }) {
2213
2254
  };
2214
2255
 
2215
2256
  // 1. Load Projects
2216
- const base = path.join(workspaceDir, 'data', 'Clients');
2217
- if (exists(base)) {
2218
- const stack = [base];
2219
- while (stack.length) {
2220
- const dirp = stack.pop();
2221
- const entries = fs.readdirSync(dirp, { withFileTypes: true });
2222
- for (const ent of entries) {
2223
- const full = path.join(dirp, ent.name);
2224
- if (ent.isDirectory()) stack.push(full);
2225
- else if (ent.isFile() && ent.name === 'status.json') {
2226
- const doc = readJsonOrNull(full) || {};
2227
- const slug = path.relative(base, path.dirname(full)).replace(/\\/g, '/');
2228
- addNode(slug, slug, 'project');
2229
-
2230
- // Link tags
2231
- const tags = Array.isArray(doc.tags) ? doc.tags : [];
2232
- for (const t of tags) {
2233
- const tid = 'tag:' + String(t).trim().toLowerCase();
2234
- addNode(tid, String(t).trim(), 'tag');
2235
- edges.push({ from: slug, to: tid });
2236
- }
2237
- }
2238
- }
2239
- }
2257
+ const rawProjects = dl.db.prepare('SELECT slug FROM projects').all();
2258
+ for (const p of rawProjects) {
2259
+ if (p && p.slug) addNode(p.slug, p.slug, 'project');
2240
2260
  }
2241
2261
 
2242
2262
  // 2. Load Tasks
2243
- const taskFile = path.join(workspaceDir, 'data', 'tasks', 'task-log.json');
2244
- const taskDoc = readJsonOrNull(taskFile) || { tasks: [] };
2245
- const tasks = Array.isArray(taskDoc.tasks) ? taskDoc.tasks : [];
2246
-
2247
- for (const t of tasks) {
2248
- // Only show pending high/medium tasks for clarity, or unassigned ones.
2249
- if (t.status === 'COMPLETED') continue;
2250
-
2263
+ const rawTasks = dl.db.prepare('SELECT * FROM tasks WHERE status != "COMPLETED"').all();
2264
+ for (const t of rawTasks) {
2251
2265
  const tid = 'task:' + t.id;
2252
- addNode(tid, t.title || 'Tarefa', 'task');
2266
+ addNode(tid, t.description || 'Tarefa', 'task');
2253
2267
 
2254
- const slug = t.projectSlug || null;
2255
- if (slug && nodeIds.has(slug)) {
2256
- edges.push({ from: tid, to: slug });
2268
+ if (t.project_slug && nodeIds.has(t.project_slug)) {
2269
+ edges.push({ from: tid, to: t.project_slug });
2257
2270
  } else {
2258
- // Connect to an "Unassigned" node
2259
2271
  addNode('unassigned', 'Sin Asignar', 'unassigned');
2260
2272
  edges.push({ from: tid, to: 'unassigned' });
2261
2273
  }
2262
2274
  }
2263
2275
 
2264
2276
  // 3. Load Blockers
2265
- const blockerFile = path.join(workspaceDir, 'data', 'blockers', 'blocker-log.json');
2266
- const blockerDoc = readJsonOrNull(blockerFile) || { blockers: [] };
2267
- const blockers = Array.isArray(blockerDoc.blockers) ? blockerDoc.blockers : [];
2268
-
2269
- for (const b of blockers) {
2270
- if (String(b.status || '').toUpperCase() !== 'OPEN') continue;
2271
-
2277
+ const openBlockers = dl.db.prepare('SELECT * FROM blockers WHERE status = "OPEN"').all();
2278
+ for (const b of openBlockers) {
2272
2279
  const bid = 'blocker:' + b.id;
2273
2280
  addNode(bid, 'BLQ: ' + (b.title || 'Blocker'), 'blocker');
2274
2281
 
2275
- const slug = b.projectSlug || null;
2276
- if (slug && nodeIds.has(slug)) {
2277
- edges.push({ from: bid, to: slug });
2282
+ if (b.project_slug && nodeIds.has(b.project_slug)) {
2283
+ edges.push({ from: bid, to: b.project_slug });
2278
2284
  } else {
2279
2285
  addNode('unassigned_blockers', 'Bloqueios Soltos', 'unassigned');
2280
2286
  edges.push({ from: bid, to: 'unassigned_blockers' });
@@ -2832,8 +2838,8 @@ async function cmdWeb({ port, dir, open, dev }) {
2832
2838
  const schema = {
2833
2839
  actions: [
2834
2840
  { type: 'append_daily_log', text: '<string>' },
2835
- { type: 'create_task', description: '<string>', priority: 'HIGH|MEDIUM|LOW', category: 'DO_NOW|SCHEDULE|DELEGATE|IGNORE', projectSlug: '<string optional>' },
2836
- { type: 'create_blocker', title: '<string>', severity: 'CRITICAL|HIGH|MEDIUM|LOW', notes: '<string>', projectSlug: '<string optional>' },
2841
+ { type: 'create_task', description: '<string>', priority: 'HIGH|MEDIUM|LOW', category: 'DO_NOW|SCHEDULE|DELEGATE|IGNORE', projectSlug: '<string optional>', streamSlug: '<string optional, ex: nome da frente ou stream>' },
2842
+ { type: 'create_blocker', title: '<string>', severity: 'CRITICAL|HIGH|MEDIUM|LOW', notes: '<string>', projectSlug: '<string optional>', streamSlug: '<string optional, ex: nome da frente ou stream>' },
2837
2843
  { type: 'suggest_report', name: 'daily|status|sm-weekly|blockers' },
2838
2844
  { type: 'oracle_query', query: '<string>' }
2839
2845
  ]
@@ -2991,18 +2997,6 @@ async function cmdWeb({ port, dir, open, dev }) {
2991
2997
  if (!Array.isArray(actions) || actions.length === 0) {
2992
2998
  return safeJson(res, 400, { error: 'Plan has no actions[]' });
2993
2999
  }
2994
-
2995
- const taskFile = path.join(workspaceDir, 'data', 'tasks', 'task-log.json');
2996
- const blockerFile = path.join(workspaceDir, 'data', 'blockers', 'blocker-log.json');
2997
-
2998
- const taskLog = readJsonOrNull(taskFile) || { schemaVersion: 1, tasks: [] };
2999
- if (!Array.isArray(taskLog.tasks)) taskLog.tasks = [];
3000
- if (typeof taskLog.schemaVersion !== 'number') taskLog.schemaVersion = 1;
3001
-
3002
- const blockerLog = readJsonOrNull(blockerFile) || { schemaVersion: 1, blockers: [] };
3003
- if (!Array.isArray(blockerLog.blockers)) blockerLog.blockers = [];
3004
- if (typeof blockerLog.schemaVersion !== 'number') blockerLog.schemaVersion = 1;
3005
-
3006
3000
  function normalizeWhitespace(t) {
3007
3001
  return String(t || '').replace(/\s+/g, ' ').trim();
3008
3002
  }
@@ -3015,27 +3009,11 @@ async function cmdWeb({ port, dir, open, dev }) {
3015
3009
  return crypto.createHash('sha1').update(String(text || ''), 'utf8').digest('hex');
3016
3010
  }
3017
3011
 
3018
- function within24h(iso) {
3019
- try {
3020
- const ms = Date.parse(iso);
3021
- if (!Number.isFinite(ms)) return false;
3022
- return (Date.now() - ms) <= 24 * 60 * 60 * 1000;
3023
- } catch {
3024
- return false;
3025
- }
3026
- }
3027
-
3028
- const existingTaskKeys24h = new Set(
3029
- taskLog.tasks
3030
- .filter((t) => t && within24h(t.createdAt))
3031
- .map((t) => sha1(normalizeTextForKey(t.description)))
3032
- );
3012
+ const recentTasks = dl.db.prepare("SELECT description FROM tasks WHERE created_at >= datetime('now', '-1 day')").all();
3013
+ const existingTaskKeys24h = new Set(recentTasks.map(t => sha1(normalizeTextForKey(t.description))));
3033
3014
 
3034
- const existingBlockerKeys24h = new Set(
3035
- blockerLog.blockers
3036
- .filter((b) => b && within24h(b.createdAt))
3037
- .map((b) => sha1(normalizeTextForKey(b.title)))
3038
- );
3015
+ const recentBlockers = dl.db.prepare("SELECT title FROM blockers WHERE created_at >= datetime('now', '-1 day')").all();
3016
+ const existingBlockerKeys24h = new Set(recentBlockers.map(b => sha1(normalizeTextForKey(b.title))));
3039
3017
 
3040
3018
  const now = new Date().toISOString();
3041
3019
  const applyMode = String(payload.mode || 'all').trim();
@@ -3067,71 +3045,72 @@ async function cmdWeb({ port, dir, open, dev }) {
3067
3045
 
3068
3046
  const validTaskCats = new Set(['DO_NOW', 'SCHEDULE', 'DELEGATE', 'IGNORE']);
3069
3047
 
3070
- for (const a of actions) {
3071
- if (!a || typeof a !== 'object') continue;
3072
- const type = String(a.type || '').trim();
3048
+ const insertTask = dl.db.prepare(`INSERT INTO tasks (id, project_slug, description, category, status, metadata) VALUES (?, ?, ?, ?, ?, ?)`);
3049
+ const insertBlocker = dl.db.prepare(`INSERT INTO blockers (id, project_slug, title, severity, status, owner, next_action, metadata) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`);
3050
+
3051
+ // Execute everything inside a transaction for the apply process
3052
+ const applyTx = dl.db.transaction((actionsToApply) => {
3053
+ for (const a of actionsToApply) {
3054
+ if (!a || typeof a !== 'object') continue;
3055
+ const type = String(a.type || '').trim();
3056
+
3057
+ if (type === 'create_task') {
3058
+ if (applyMode !== 'all' && applyMode !== 'tasks') continue;
3059
+ const description = normalizeWhitespace(a.description);
3060
+ if (!description) continue;
3061
+ const projectSlug = String(a.projectSlug || '').trim() || inferProjectSlug(description, slugMap);
3062
+ const streamSlug = String(a.streamSlug || '').trim();
3063
+ const key = sha1(normalizeTextForKey((projectSlug ? projectSlug + ' ' : '') + description));
3064
+ if (existingTaskKeys24h.has(key)) { applied.tasksSkipped++; continue; }
3065
+ const category = validTaskCats.has(String(a.category || '').trim()) ? String(a.category).trim() : 'DO_NOW';
3066
+ const priority = normPriority(a.priority);
3067
+
3068
+ const id = makeId('t');
3069
+ const metadata = JSON.stringify({ priority, streamSlug });
3070
+ insertTask.run(id, projectSlug || null, description, category, 'PENDING', metadata);
3071
+
3072
+ applied.tasks++;
3073
+ existingTaskKeys24h.add(key); // prevent duplicates within same batch
3074
+ continue;
3075
+ }
3073
3076
 
3074
- if (type === 'create_task') {
3075
- if (applyMode !== 'all' && applyMode !== 'tasks') continue;
3076
- const description = normalizeWhitespace(a.description);
3077
- if (!description) continue;
3078
- const projectSlug = String(a.projectSlug || '').trim() || inferProjectSlug(description, slugMap);
3079
- const key = sha1(normalizeTextForKey((projectSlug ? projectSlug + ' ' : '') + description));
3080
- if (existingTaskKeys24h.has(key)) { applied.tasksSkipped++; continue; }
3081
- const category = validTaskCats.has(String(a.category || '').trim()) ? String(a.category).trim() : 'DO_NOW';
3082
- const priority = normPriority(a.priority);
3083
- const task = {
3084
- id: makeId('t'),
3085
- description,
3086
- category,
3087
- status: 'PENDING',
3088
- createdAt: now,
3089
- };
3090
- if (projectSlug) task.projectSlug = projectSlug;
3091
- if (priority) task.priority = priority;
3092
- taskLog.tasks.push(task);
3093
- applied.tasks++;
3094
- continue;
3095
- }
3077
+ if (type === 'create_blocker') {
3078
+ if (applyMode !== 'all' && applyMode !== 'blockers') continue;
3079
+ const title = normalizeWhitespace(a.title);
3080
+ const notes = normalizeWhitespace(a.notes);
3081
+ if (!title) continue;
3082
+ const projectSlug = String(a.projectSlug || '').trim() || inferProjectSlug(title + ' ' + notes, slugMap);
3083
+ const streamSlug = String(a.streamSlug || '').trim();
3084
+ const key = sha1(normalizeTextForKey((projectSlug ? projectSlug + ' ' : '') + title));
3085
+ if (existingBlockerKeys24h.has(key)) { applied.blockersSkipped++; continue; }
3086
+ const severity = normSeverity(a.severity);
3087
+
3088
+ const id = makeId('b');
3089
+ const metadata = JSON.stringify({ streamSlug, description: notes || title });
3090
+ insertBlocker.run(id, projectSlug || null, title, severity, 'OPEN', null, null, metadata);
3091
+
3092
+ applied.blockers++;
3093
+ existingBlockerKeys24h.add(key); // prevent duplicates within same batch
3094
+ continue;
3095
+ }
3096
3096
 
3097
- if (type === 'create_blocker') {
3098
- if (applyMode !== 'all' && applyMode !== 'blockers') continue;
3099
- const title = normalizeWhitespace(a.title);
3100
- const projectSlug = String(a.projectSlug || '').trim() || inferProjectSlug(title + ' ' + normalizeWhitespace(a.notes), slugMap);
3101
- const key = sha1(normalizeTextForKey((projectSlug ? projectSlug + ' ' : '') + title));
3102
- if (existingBlockerKeys24h.has(key)) { applied.blockersSkipped++; continue; }
3103
- const notes = normalizeWhitespace(a.notes);
3104
- if (!title) continue;
3105
- const severity = normSeverity(a.severity);
3106
- const blocker = {
3107
- id: makeId('b'),
3108
- title,
3109
- description: notes || title,
3110
- createdAt: now,
3111
- status: 'OPEN',
3112
- severity,
3113
- };
3114
- if (projectSlug) blocker.projectSlug = projectSlug;
3115
- blockerLog.blockers.push(blocker);
3116
- applied.blockers++;
3117
- continue;
3118
- }
3097
+ if (type === 'suggest_report') {
3098
+ const name = String(a.name || '').trim();
3099
+ if (name) applied.reportsSuggested.push(name);
3100
+ continue;
3101
+ }
3119
3102
 
3120
- if (type === 'suggest_report') {
3121
- const name = String(a.name || '').trim();
3122
- if (name) applied.reportsSuggested.push(name);
3123
- continue;
3103
+ if (type === 'oracle_query') {
3104
+ const query = String(a.query || '').trim();
3105
+ if (query) applied.oracleQueries.push(query);
3106
+ continue;
3107
+ }
3124
3108
  }
3109
+ });
3125
3110
 
3126
- if (type === 'oracle_query') {
3127
- const query = String(a.query || '').trim();
3128
- if (query) applied.oracleQueries.push(query);
3129
- continue;
3130
- }
3131
- }
3111
+ applyTx(actions);
3132
3112
 
3133
3113
  // Auto-suggest reports when planner didn't include any
3134
- // (keeps UX consistent: if you created a blocker, at least suggest blockers)
3135
3114
  if (!applied.reportsSuggested.length) {
3136
3115
  const sug = [];
3137
3116
  sug.push('daily');
@@ -3143,10 +3122,6 @@ async function cmdWeb({ port, dir, open, dev }) {
3143
3122
  applied.reportsSuggested = Array.from(new Set(applied.reportsSuggested.map((s) => String(s).trim()).filter(Boolean)));
3144
3123
  }
3145
3124
 
3146
- // Persist
3147
- writeJson(taskFile, taskLog);
3148
- writeJson(blockerFile, blockerLog);
3149
-
3150
3125
  return safeJson(res, 200, { ok: true, applied });
3151
3126
  }
3152
3127
 
@@ -3237,28 +3212,51 @@ async function cmdWeb({ port, dir, open, dev }) {
3237
3212
  const query = String(payload.query || '').trim();
3238
3213
  if (!query) return safeJson(res, 400, { error: 'Missing query' });
3239
3214
 
3240
- const copilotResult = await copilotSearch(workspaceDir, query, { limit: 8 });
3241
- const indexMatches = searchIndex(workspaceDir, query, { limit: 12 });
3242
- const baseMatches = indexMatches.length
3243
- ? indexMatches
3244
- : searchWorkspace(workspaceDir, query, { limit: 12 });
3245
-
3246
- if (copilotResult.ok) {
3247
- const merged = mergeMatches(copilotResult.matches || [], baseMatches, 8);
3248
- const answer = buildChatAnswer(
3249
- query,
3250
- merged.matches,
3251
- copilotResult.summary,
3252
- copilotResult.evidence,
3253
- copilotResult.answer,
3254
- merged.total
3255
- );
3256
- return safeJson(res, 200, { ok: true, sessionId, answer, matches: merged.matches });
3215
+ const workspaceRulesBase = path.join(workspaceDir, '.agent', 'rules', 'freya');
3216
+ const packagedRulesBase = path.join(__dirname, '..', '.agent', 'rules', 'freya');
3217
+ const rulesBase = exists(workspaceRulesBase) ? workspaceRulesBase : packagedRulesBase;
3218
+
3219
+ const files = [
3220
+ path.join(rulesBase, 'freya.mdc'),
3221
+ path.join(rulesBase, 'agents', 'master.mdc'),
3222
+ path.join(rulesBase, 'agents', 'oracle.mdc')
3223
+ ].filter(exists);
3224
+
3225
+ const rulesText = files.map((p) => {
3226
+ const rel = path.relative(workspaceDir, p).replace(/\\/g, '/');
3227
+ return `\n\n---\nFILE: ${rel}\n---\n` + fs.readFileSync(p, 'utf8');
3228
+ }).join('');
3229
+
3230
+ // V2 RAG Context
3231
+ const dm = new DataManager(workspaceDir, path.join(workspaceDir, 'logs'));
3232
+ const ragResults = await dm.semanticSearch(query, 12);
3233
+ let ragContext = '';
3234
+ if (ragResults.length > 0) {
3235
+ ragContext = '\n\n[MEMÓRIA DE LONGO PRAZO RECUPERADA (RAG VIA SQLITE)]\n';
3236
+ for (const r of ragResults) {
3237
+ ragContext += `\n---\nFONTE: ${r.reference_type} -> ID: ${r.reference_id} (Score: ${r.score.toFixed(3)})\nCONTEÚDO:\n${r.text_chunk}\n`;
3238
+ }
3257
3239
  }
3258
3240
 
3259
- const merged = mergeMatches(baseMatches, [], 8);
3260
- const answer = buildChatAnswer(query, merged.matches, '', [], '', merged.total);
3261
- return safeJson(res, 200, { ok: true, sessionId, answer, matches: merged.matches });
3241
+ const prompt = `Você é o agente Oracle do sistema F.R.E.Y.A.\n\nSiga estritamente os arquivos de regras abaixo.\nResponda de forma analítica e consultiva.\n${ragContext}\n\nREGRAS:${rulesText}\n\nCONSULTA DO USUÁRIO:\n${query}\n`;
3242
+
3243
+ const cmd = process.env.COPILOT_CMD || 'copilot';
3244
+
3245
+ try {
3246
+ // Removed --allow-all-tools and --add-dir to force reliance on RAG context
3247
+ const r = await run(cmd, ['-s', '--no-color', '--stream', 'off', '-p', prompt], workspaceDir);
3248
+ const out = (r.stdout + r.stderr).trim();
3249
+ if (r.code !== 0) {
3250
+ return safeJson(res, 200, { ok: false, answer: 'Falha na busca do agente Oracle:\n' + (out || 'Exit code != 0'), sessionId });
3251
+ }
3252
+ return safeJson(res, 200, { ok: true, answer: out, sessionId });
3253
+ } catch (e) {
3254
+ return safeJson(res, 200, {
3255
+ ok: false,
3256
+ answer: `Agente indisponível (cmd: ${cmd}).\nDetalhes:\n` + (e.message || String(e)),
3257
+ sessionId
3258
+ });
3259
+ }
3262
3260
  }
3263
3261
 
3264
3262
  // Chat persistence (per session)
@@ -3361,52 +3359,48 @@ async function cmdWeb({ port, dir, open, dev }) {
3361
3359
  const cat = payload.category ? String(payload.category).trim() : null;
3362
3360
  const status = payload.status ? String(payload.status).trim() : null;
3363
3361
 
3364
- const file = path.join(workspaceDir, 'data', 'tasks', 'task-log.json');
3365
- const doc = readJsonOrNull(file) || { schemaVersion: 1, tasks: [] };
3366
- const tasks = Array.isArray(doc.tasks) ? doc.tasks.slice() : [];
3367
-
3368
- const filtered = tasks
3369
- .filter((t) => {
3370
- if (!t || typeof t !== 'object') return false;
3371
- if (cat && String(t.category || '').trim() !== cat) return false;
3372
- if (status && String(t.status || '').trim() !== status) return false;
3373
- return true;
3374
- })
3375
- .sort((a, b) => {
3376
- const pa = String(a.priority || '').toLowerCase();
3377
- const pb = String(b.priority || '').toLowerCase();
3378
- const rank = (p) => (p === 'high' ? 0 : p === 'medium' ? 1 : p === 'low' ? 2 : 3);
3379
- const ra = rank(pa), rb = rank(pb);
3380
- if (ra !== rb) return ra - rb;
3381
- return String(b.createdAt || '').localeCompare(String(a.createdAt || ''));
3382
- })
3383
- .slice(0, limit);
3384
-
3385
- return safeJson(res, 200, { ok: true, tasks: filtered });
3362
+ let query = 'SELECT * FROM tasks WHERE 1=1';
3363
+ const params = [];
3364
+
3365
+ if (cat) { query += ' AND category = ?'; params.push(cat); }
3366
+ if (status) { query += ' AND status = ?'; params.push(status); }
3367
+
3368
+ query += ` ORDER BY
3369
+ CASE JSON_EXTRACT(metadata, '$.priority')
3370
+ WHEN 'high' THEN 0 WHEN 'medium' THEN 1 WHEN 'low' THEN 2 ELSE 3
3371
+ END ASC,
3372
+ created_at DESC
3373
+ LIMIT ?`;
3374
+ params.push(limit);
3375
+
3376
+ const rawTasks = dl.db.prepare(query).all(...params);
3377
+ const tasks = rawTasks.map(t => {
3378
+ const meta = t.metadata ? JSON.parse(t.metadata) : {};
3379
+ return {
3380
+ id: t.id,
3381
+ description: t.description,
3382
+ category: t.category,
3383
+ status: t.status,
3384
+ createdAt: t.created_at,
3385
+ completedAt: t.completed_at,
3386
+ projectSlug: t.project_slug,
3387
+ priority: meta.priority,
3388
+ streamSlug: meta.streamSlug
3389
+ };
3390
+ });
3391
+
3392
+ return safeJson(res, 200, { ok: true, tasks });
3386
3393
  }
3387
3394
 
3388
3395
  if (req.url === '/api/tasks/complete') {
3389
3396
  const id = String(payload.id || '').trim();
3390
3397
  if (!id) return safeJson(res, 400, { error: 'Missing id' });
3391
3398
 
3392
- const file = path.join(workspaceDir, 'data', 'tasks', 'task-log.json');
3393
- const doc = readJsonOrNull(file) || { schemaVersion: 1, tasks: [] };
3394
- const tasks = Array.isArray(doc.tasks) ? doc.tasks : [];
3395
-
3396
- const now = isoNow();
3397
- let updated = null;
3398
- for (const t of tasks) {
3399
- if (t && t.id === id) {
3400
- t.status = 'COMPLETED';
3401
- t.completedAt = now;
3402
- updated = t;
3403
- break;
3404
- }
3405
- }
3399
+ const now = new Date().toISOString();
3400
+ const info = dl.db.prepare(`UPDATE tasks SET status = 'COMPLETED', completed_at = ? WHERE id = ?`).run(now, id);
3406
3401
 
3407
- if (!updated) return safeJson(res, 404, { error: 'Task not found' });
3408
- writeJson(file, doc);
3409
- return safeJson(res, 200, { ok: true, task: updated });
3402
+ if (info.changes === 0) return safeJson(res, 404, { error: 'Task not found' });
3403
+ return safeJson(res, 200, { ok: true, task: { id, status: 'COMPLETED', completedAt: now } });
3410
3404
  }
3411
3405
 
3412
3406
 
@@ -3415,22 +3409,26 @@ async function cmdWeb({ port, dir, open, dev }) {
3415
3409
  if (!id) return safeJson(res, 400, { error: 'Missing id' });
3416
3410
  const patch = payload.patch && typeof payload.patch === 'object' ? payload.patch : {};
3417
3411
 
3418
- const file = path.join(workspaceDir, 'data', 'tasks', 'task-log.json');
3419
- const doc = readJsonOrNull(file) || { schemaVersion: 1, tasks: [] };
3420
- const tasks = Array.isArray(doc.tasks) ? doc.tasks : [];
3412
+ let queryUpdates = [];
3413
+ let params = [];
3421
3414
 
3422
- let updated = null;
3423
- for (const t of tasks) {
3424
- if (t && t.id === id) {
3425
- if (typeof patch.projectSlug === 'string') t.projectSlug = patch.projectSlug.trim() || undefined;
3426
- if (typeof patch.category === 'string') t.category = patch.category.trim();
3427
- updated = t;
3428
- break;
3429
- }
3415
+ if (typeof patch.projectSlug === 'string') {
3416
+ queryUpdates.push('project_slug = ?');
3417
+ params.push(patch.projectSlug.trim() || null);
3430
3418
  }
3431
- if (!updated) return safeJson(res, 404, { error: 'Task not found' });
3432
- writeJson(file, doc);
3433
- return safeJson(res, 200, { ok: true, task: updated });
3419
+ if (typeof patch.category === 'string') {
3420
+ queryUpdates.push('category = ?');
3421
+ params.push(patch.category.trim());
3422
+ }
3423
+
3424
+ if (queryUpdates.length === 0) return safeJson(res, 200, { ok: true, task: { id } });
3425
+
3426
+ params.push(id);
3427
+ const updateQuery = `UPDATE tasks SET ${queryUpdates.join(', ')} WHERE id = ?`;
3428
+ const info = dl.db.prepare(updateQuery).run(...params);
3429
+
3430
+ if (info.changes === 0) return safeJson(res, 404, { error: 'Task not found' });
3431
+ return safeJson(res, 200, { ok: true, task: { id, ...patch } });
3434
3432
  }
3435
3433
 
3436
3434
  if (req.url === '/api/health/checklist') {
@@ -3442,13 +3440,11 @@ async function cmdWeb({ port, dir, open, dev }) {
3442
3440
  return safeJson(res, 200, { ok: false, needsInit: true, error: 'Workspace not initialized', items: [] });
3443
3441
  }
3444
3442
 
3445
- const tasksFile = path.join(workspaceDir, 'data', 'tasks', 'task-log.json');
3446
- const blockersFile = path.join(workspaceDir, 'data', 'blockers', 'blocker-log.json');
3447
- const tasksDoc = readJsonOrNull(tasksFile) || { schemaVersion: 1, tasks: [] };
3448
- const blockersDoc = readJsonOrNull(blockersFile) || { schemaVersion: 1, blockers: [] };
3443
+ const pendingTasksRes = dl.db.prepare('SELECT count(*) as count FROM tasks WHERE status = "PENDING" AND category = "DO_NOW"').get();
3444
+ const openBlockersRes = dl.db.prepare('SELECT count(*) as count FROM blockers WHERE status = "OPEN"').get();
3449
3445
 
3450
- const pendingTasks = (tasksDoc.tasks || []).filter((t) => t && t.status === 'PENDING' && t.category === 'DO_NOW').length;
3451
- const openBlockers = (blockersDoc.blockers || []).filter((b) => b && String(b.status || '').trim() === 'OPEN').length;
3446
+ const pendingTasks = pendingTasksRes ? pendingTasksRes.count : 0;
3447
+ const openBlockers = openBlockersRes ? openBlockersRes.count : 0;
3452
3448
 
3453
3449
  const today = isoDate();
3454
3450
  const reports = listReports(workspaceDir);
@@ -3457,7 +3453,7 @@ async function cmdWeb({ port, dir, open, dev }) {
3457
3453
  const items = [
3458
3454
  { label: 'Blockers abertos', status: openBlockers > 0 ? 'warn' : 'ok', detail: `${openBlockers} aberto(s)` },
3459
3455
  { label: 'Tarefas DO_NOW pendentes', status: pendingTasks > 0 ? 'warn' : 'ok', detail: `${pendingTasks} pendente(s)` },
3460
- { label: 'Relatorios de hoje', status: reportsToday > 0 ? 'ok' : 'warn', detail: `${reportsToday} gerado(s)` }
3456
+ { label: 'Relatórios de hoje', status: reportsToday > 0 ? 'ok' : 'warn', detail: `${reportsToday} gerado(s)` }
3461
3457
  ];
3462
3458
 
3463
3459
  return safeJson(res, 200, { ok: true, items, stats: { pendingTasks, openBlockers, reportsToday, reportsTotal: reports.length } });
@@ -3507,11 +3503,18 @@ async function cmdWeb({ port, dir, open, dev }) {
3507
3503
  }
3508
3504
 
3509
3505
  if (req.url === '/api/blockers/summary') {
3510
- const file = path.join(workspaceDir, 'data', 'blockers', 'blocker-log.json');
3511
- const doc = readJsonOrNull(file) || { schemaVersion: 1, blockers: [] };
3512
- const blockers = Array.isArray(doc.blockers) ? doc.blockers : [];
3506
+ const open = dl.db.prepare(`
3507
+ SELECT * FROM blockers
3508
+ WHERE status IN ('OPEN', 'MITIGATING')
3509
+ `).all().map(b => ({
3510
+ id: b.id,
3511
+ title: b.title,
3512
+ severity: b.severity,
3513
+ status: b.status,
3514
+ projectSlug: b.project_slug,
3515
+ createdAt: b.created_at
3516
+ }));
3513
3517
 
3514
- const open = blockers.filter((b) => b && String(b.status || '').trim() === 'OPEN');
3515
3518
  const sevCount = { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0, OTHER: 0 };
3516
3519
  const now = Date.now();
3517
3520
  let older7 = 0;
@@ -3530,7 +3533,7 @@ async function cmdWeb({ port, dir, open, dev }) {
3530
3533
  .slice(0, 5)
3531
3534
  .map((b) => ({
3532
3535
  id: b.id,
3533
- title: b.title || b.description || 'Sem titulo',
3536
+ title: b.title || 'Sem titulo',
3534
3537
  severity: b.severity || 'UNKNOWN',
3535
3538
  projectSlug: b.projectSlug || null
3536
3539
  }));
@@ -3549,34 +3552,40 @@ async function cmdWeb({ port, dir, open, dev }) {
3549
3552
  const limit = Math.max(1, Math.min(50, Number(payload.limit || 10)));
3550
3553
  const status = payload.status ? String(payload.status).trim() : 'OPEN';
3551
3554
 
3552
- const file = path.join(workspaceDir, 'data', 'blockers', 'blocker-log.json');
3553
- const doc = readJsonOrNull(file) || { schemaVersion: 1, blockers: [] };
3554
- const blockers = Array.isArray(doc.blockers) ? doc.blockers.slice() : [];
3555
-
3556
- const sevRank = (s) => {
3557
- const v = String(s || '').toUpperCase();
3558
- if (v === 'CRITICAL') return 0;
3559
- if (v === 'HIGH') return 1;
3560
- if (v === 'MEDIUM') return 2;
3561
- if (v === 'LOW') return 3;
3562
- return 9;
3563
- };
3555
+ let query = 'SELECT * FROM blockers WHERE 1=1';
3556
+ const params = [];
3557
+
3558
+ if (status) {
3559
+ query += ' AND status = ?';
3560
+ params.push(status);
3561
+ }
3564
3562
 
3565
- const filtered = blockers
3566
- .filter((b) => {
3567
- if (!b || typeof b !== 'object') return false;
3568
- if (status && String(b.status || '').trim() !== status) return false;
3569
- return true;
3570
- })
3571
- .sort((a, b) => {
3572
- const ra = sevRank(a.severity);
3573
- const rb = sevRank(b.severity);
3574
- if (ra !== rb) return ra - rb;
3575
- return String(a.createdAt || '').localeCompare(String(b.createdAt || ''));
3576
- })
3577
- .slice(0, limit);
3578
-
3579
- return safeJson(res, 200, { ok: true, blockers: filtered });
3563
+ query += ` ORDER BY
3564
+ CASE severity
3565
+ WHEN 'CRITICAL' THEN 0 WHEN 'HIGH' THEN 1 WHEN 'MEDIUM' THEN 2 WHEN 'LOW' THEN 3 ELSE 9
3566
+ END ASC,
3567
+ created_at ASC
3568
+ LIMIT ?`;
3569
+ params.push(limit);
3570
+
3571
+ const rawBlockers = dl.db.prepare(query).all(...params);
3572
+ const blockers = rawBlockers.map(b => {
3573
+ const meta = b.metadata ? JSON.parse(b.metadata) : {};
3574
+ return {
3575
+ id: b.id,
3576
+ title: b.title,
3577
+ severity: b.severity,
3578
+ status: b.status,
3579
+ createdAt: b.created_at,
3580
+ resolvedAt: b.resolved_at,
3581
+ projectSlug: b.project_slug,
3582
+ owner: b.owner,
3583
+ nextAction: b.next_action,
3584
+ streamSlug: meta.streamSlug
3585
+ };
3586
+ });
3587
+
3588
+ return safeJson(res, 200, { ok: true, blockers });
3580
3589
  }
3581
3590
 
3582
3591
  if (req.url === '/api/blockers/update') {
@@ -3584,21 +3593,22 @@ async function cmdWeb({ port, dir, open, dev }) {
3584
3593
  if (!id) return safeJson(res, 400, { error: 'Missing id' });
3585
3594
  const patch = payload.patch && typeof payload.patch === 'object' ? payload.patch : {};
3586
3595
 
3587
- const file = path.join(workspaceDir, 'data', 'blockers', 'blocker-log.json');
3588
- const doc = readJsonOrNull(file) || { schemaVersion: 1, blockers: [] };
3589
- const blockers = Array.isArray(doc.blockers) ? doc.blockers : [];
3596
+ let queryUpdates = [];
3597
+ let params = [];
3590
3598
 
3591
- let updated = null;
3592
- for (const b of blockers) {
3593
- if (b && b.id === id) {
3594
- if (typeof patch.projectSlug === 'string') b.projectSlug = patch.projectSlug.trim() || undefined;
3595
- updated = b;
3596
- break;
3597
- }
3599
+ if (typeof patch.projectSlug === 'string') {
3600
+ queryUpdates.push('project_slug = ?');
3601
+ params.push(patch.projectSlug.trim() || null);
3598
3602
  }
3599
- if (!updated) return safeJson(res, 404, { error: 'Blocker not found' });
3600
- writeJson(file, doc);
3601
- return safeJson(res, 200, { ok: true, blocker: updated });
3603
+
3604
+ if (queryUpdates.length === 0) return safeJson(res, 200, { ok: true, blocker: { id } });
3605
+
3606
+ params.push(id);
3607
+ const updateQuery = `UPDATE blockers SET ${queryUpdates.join(', ')} WHERE id = ?`;
3608
+ const info = dl.db.prepare(updateQuery).run(...params);
3609
+
3610
+ if (info.changes === 0) return safeJson(res, 404, { error: 'Blocker not found' });
3611
+ return safeJson(res, 200, { ok: true, blocker: { id, ...patch } });
3602
3612
  }
3603
3613
 
3604
3614
  if (req.url === '/api/report') {
@@ -3689,4 +3699,148 @@ async function cmdWeb({ port, dir, open, dev }) {
3689
3699
  if (open) openBrowser(url);
3690
3700
  }
3691
3701
 
3702
+ function buildSettingsHtml(safeDefault, appVersion) {
3703
+ const safeVersion = escapeHtml(appVersion || 'unknown');
3704
+ return `<!doctype html>
3705
+ <html>
3706
+ <head>
3707
+ <meta charset="utf-8" />
3708
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
3709
+ <title>FREYA Settings</title>
3710
+ <link rel="stylesheet" href="/app.css" />
3711
+ </head>
3712
+ <body data-page="settings">
3713
+ <div class="app">
3714
+ <div class="frame">
3715
+ <div class="shell">
3716
+
3717
+ <aside class="rail">
3718
+ <div class="railTop">
3719
+ <div class="railLogo">F</div>
3720
+ </div>
3721
+ <div class="railNav">
3722
+ <button class="railBtn" id="railDashboard" type="button" title="Dashboard">
3723
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="9"></rect><rect x="14" y="3" width="7" height="5"></rect><rect x="14" y="12" width="7" height="9"></rect><rect x="3" y="16" width="7" height="5"></rect></svg>
3724
+ </button>
3725
+ <button class="railBtn" id="railReports" type="button" title="Relatórios">
3726
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
3727
+ </button>
3728
+ <button class="railBtn" id="railCompanion" type="button" title="Companion">
3729
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"></polygon></svg>
3730
+ </button>
3731
+ <button class="railBtn" id="railProjects" type="button" title="Projects">
3732
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
3733
+ </button>
3734
+ <button class="railBtn" id="railTimeline" type="button" title="Timeline">
3735
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>
3736
+ </button>
3737
+ <button class="railBtn" id="railGraph" type="button" title="Grafo">
3738
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3"></circle><circle cx="6" cy="12" r="3"></circle><circle cx="18" cy="19" r="3"></circle><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line></svg>
3739
+ </button>
3740
+ </div>
3741
+ <div class="railBottom">\n <button id="railSettings" class="railBtn" title="Configura��es">\n <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>\n </button>
3742
+ <button id="railSettings" class="railBtn active" title="Configurações">
3743
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
3744
+ </button>
3745
+
3746
+ </div>
3747
+ </aside>
3748
+
3749
+ <main class="center" id="settingsPage">
3750
+ <div class="topbar">
3751
+ <div class="brandLine">
3752
+ <span class="spark"></span>
3753
+ <div class="brandStack">
3754
+ <div class="brand">FREYA</div>
3755
+ <div class="brandSub">Configurações Avançadas</div>
3756
+ </div>
3757
+ </div>
3758
+ <div class="topActions">
3759
+ <span class="chip" id="chipVersion">v${safeVersion}</span>
3760
+ <button class="btn icon" type="button" onclick="window.toggleTheme()" title="Alternar tema claro/escuro">
3761
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg>
3762
+ </button>
3763
+ </div>
3764
+ </div>
3765
+
3766
+ <div class="centerBody">
3767
+ <section class="utilityGrid" style="margin-bottom: 24px;">
3768
+ <div class="utilityCard">
3769
+ <div class="utilityHead">Área de trabalho local</div>
3770
+ <div class="sidePath" id="sidePath">${safeDefault}</div>
3771
+ <div class="row" style="grid-template-columns: 1fr auto">
3772
+ <input id="dir" placeholder="./freya" value="${safeDefault}" />
3773
+ <button class="btn small" type="button" onclick="window.pickDir()">Procurar</button>
3774
+ </div>
3775
+ <div class="stack" style="margin-top:10px">
3776
+ <button class="btn" type="button" onclick="window.doUpdate()">Sincronizar workspace</button>
3777
+ <button class="btn" type="button" onclick="window.doMigrate()">Migrar dados base</button>
3778
+ </div>
3779
+ <div style="height:10px"></div>
3780
+ <div class="help"><b>Sincronizar workspace</b>: Atualiza scripts/templates da suite FREYA sem afetar <code>data</code>.</div>
3781
+ <div class="help"><b>Migrar dados</b>: Se alguma versão quebrar compatibilidade, ele recria o log.</div>
3782
+ </div>
3783
+
3784
+ <div class="utilityCard">
3785
+ <div class="utilityHead">Interface & Sistema</div>
3786
+ <div style="display:flex; flex-direction:column; gap:8px;">
3787
+ <label style="display:flex; align-items:center; gap:10px; user-select:none; margin: 6px 0 12px 0">
3788
+ <input id="prettyPublish" type="checkbox" checked style="width:auto" onchange="window.togglePrettyPublish()" />
3789
+ Publicação bonita (cards/embeds em RTF)
3790
+ </label>
3791
+ <div class="stack" style="margin-top:10px">
3792
+ <button class="btn" type="button" onclick="window.rebuildIndex()">Reconstruir Índice de Busca (Copilot)</button>
3793
+ <button class="btn" type="button" onclick="window.exportObsidian()">Exportar Notas (Obsidian Markdown)</button>
3794
+ </div>
3795
+ <div class="help">Usa Copilot/RagIndex para buscas conversacionais.</div>
3796
+ </div>
3797
+ </div>
3798
+ </section>
3799
+
3800
+ <div class="devGrid">
3801
+ <div class="panel">
3802
+ <div class="panelHead"><b>Configuração de Notificações</b></div>
3803
+ <div class="panelBody">
3804
+ <label>Discord webhook URL</label>
3805
+ <input id="discord" placeholder="https://discord.com/api/webhooks/..." />
3806
+ <div style="height:10px"></div>
3807
+
3808
+ <label>Teams webhook URL</label>
3809
+ <input id="teams" placeholder="https://..." />
3810
+
3811
+ <div class="stack" style="margin-top:20px;">
3812
+ <button class="btn primary" type="button" onclick="window.saveSettings()">Salvar Configurações</button>
3813
+ </div>
3814
+ </div>
3815
+ </div>
3816
+
3817
+ <div class="panel">
3818
+ <div class="panelHead"><b>Mapeamento Smart de Projetos</b></div>
3819
+ <div class="panelBody">
3820
+ <label>Regras de inferência (JSON)</label>
3821
+ <textarea id="slugRules" rows="8" placeholder="{ \"rules\": [ { \"contains\": \"fideliza\", \"slug\": \"vivo/fidelizacao\" } ] }"></textarea>
3822
+ <div class="help">Usado pra inferir <code>projectSlug</code>.</div>
3823
+ <div class="stack" style="margin-top:10px">
3824
+ <button class="btn" type="button" onclick="window.reloadSlugRules()">Recarregar regras</button>
3825
+ <button class="btn primary" type="button" onclick="window.saveSlugRules()">Salvar regras</button>
3826
+ </div>
3827
+ </div>
3828
+ </div>
3829
+ </div>
3830
+
3831
+ </div>
3832
+ </main>
3833
+ </div>
3834
+ </div>
3835
+ </div>
3836
+
3837
+ <script>
3838
+ window.__FREYA_DEFAULT_DIR = "${safeDefault}";
3839
+ </script>
3840
+ <script src="/app.js"></script>
3841
+ </body>
3842
+ </html>`;
3843
+ }
3844
+
3692
3845
  module.exports = { cmdWeb };
3846
+