@cccarv82/freya 2.3.12 → 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 -535
  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,11 +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>
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>
1384
1379
  </div>
1385
1380
  <div class=\"railBottom\">
1386
1381
  <div class=\"railStatus\" id=\"railStatus\" title=\"status\"></div>
@@ -1470,15 +1465,27 @@ function buildGraphHtml(safeDefault, appVersion) {
1470
1465
  <div class="railLogo">F</div>
1471
1466
  </div>
1472
1467
  <div class="railNav">
1473
- <button class="railBtn" id="railDashboard" type="button" title="Dashboard">D</button>
1474
- <button class="railBtn" id="railReports" type="button" title="Relatorios">R</button>
1475
- <button class="railBtn" id="railCompanion" type="button" title="Companion">C</button>
1476
- <button class="railBtn" id="railProjects" type="button" title="Projects">P</button>
1477
- <button class="railBtn" id="railTimeline" type="button" title="Timeline">T</button>
1478
- <button class="railBtn active" id="railGraph" type="button" title="Grafo">G</button>
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>
1479
1486
  </div>
1480
- <div class="railBottom">
1481
- <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
+
1482
1489
  </div>
1483
1490
  </aside>
1484
1491
 
@@ -1548,15 +1555,27 @@ function buildCompanionHtml(safeDefault, appVersion) {
1548
1555
  <div class="railLogo">F</div>
1549
1556
  </div>
1550
1557
  <div class="railNav">
1551
- <button class="railBtn" id="railDashboard" type="button" title="Dashboard">D</button>
1552
- <button class="railBtn" id="railReports" type="button" title="Relatórios">R</button>
1553
- <button class="railBtn active" id="railCompanion" type="button" title="Companion">C</button>
1554
- <button class="railBtn" id="railProjects" type="button" title="Projects">P</button>
1555
- <button class="railBtn" id="railTimeline" type="button" title="Timeline">T</button>
1556
- <button class="railBtn" id="railGraph" type="button" title="Grafo">G</button>
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>
1557
1576
  </div>
1558
- <div class="railBottom">
1559
- <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
+
1560
1579
  </div>
1561
1580
  </aside>
1562
1581
 
@@ -1581,81 +1600,107 @@ function buildCompanionHtml(safeDefault, appVersion) {
1581
1600
  <section class="reportsHeader">
1582
1601
  <div>
1583
1602
  <div class="reportsTitle">Scrum Master Companion</div>
1584
- <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>
1585
1604
  </div>
1586
1605
  <div class="reportsActions">
1587
- <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>
1588
1608
  </section>
1589
1609
 
1590
- <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;">
1591
1611
  <button class="btn" type="button" onclick="runReport('daily')">Gerar Daily</button>
1592
1612
  <button class="btn" type="button" onclick="runReport('blockers')">Gerar Blockers</button>
1593
1613
  <button class="btn" type="button" onclick="runReport('status')">Gerar Status</button>
1594
1614
  <button class="btn" type="button" onclick="runReport('sm-weekly')">Gerar SM Weekly</button>
1595
1615
  </section>
1596
1616
 
1597
- <section class="reportsGrid" id="healthChecklist"></section>
1598
-
1599
- <section class="panel" style="margin-top:16px">
1600
- <div class="panelHead" style="display:flex; align-items:center; justify-content:space-between; gap:10px">
1601
- <b>Qualidade de Log</b>
1602
- <button class="btn small" type="button" onclick="refreshQualityScore()">Atualizar</button>
1603
- </div>
1604
- <div class="panelBody">
1605
- <div id="qualityScoreCard"></div>
1606
- </div>
1607
- </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>
1608
1641
 
1609
- <section class="panel" style="margin-top:16px">
1610
- <div class="panelHead" style="display:flex; align-items:center; justify-content:space-between; gap:10px">
1611
- <b>Resumo Executivo</b>
1612
- <button class="btn small" type="button" onclick="refreshExecutiveSummary()">Atualizar</button>
1613
- </div>
1614
- <div class="panelBody">
1615
- <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>
1616
1650
  </div>
1617
- </section>
1618
1651
 
1619
- <section class="panel" style="margin-top:16px">
1620
- <div class="panelHead" style="display:flex; align-items:center; justify-content:space-between; gap:10px">
1621
- <b>Anomalias</b>
1622
- <button class="btn small" type="button" onclick="refreshAnomalies()">Atualizar</button>
1623
- </div>
1624
- <div class="panelBody">
1625
- <div id="anomaliesBox"></div>
1626
- </div>
1627
- </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>
1628
1662
 
1629
- <section class="panel" style="margin-top:16px">
1630
- <div class="panelHead" style="display:flex; align-items:center; justify-content:space-between; gap:10px">
1631
- <b>Radar de Risco</b>
1632
- <button class="btn small" type="button" onclick="refreshRiskRadar()">Atualizar</button>
1633
- </div>
1634
- <div class="panelBody">
1635
- <div id="riskRadarBox"></div>
1636
- </div>
1637
- </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>
1638
1673
 
1639
- <section class="panel" style="margin-top:16px">
1640
- <div class="panelHead"><b>Incident Radar</b></div>
1641
- <div class="panelBody">
1642
- <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>
1643
1684
  </div>
1644
- </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>
1645
1694
 
1646
- <section class="panel" style="margin-top:16px">
1647
- <div class="panelHead"><b>Task Heatmap</b></div>
1648
- <div class="panelBody">
1649
- <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>
1650
1701
  </div>
1651
- </section>
1652
1702
 
1653
- <section class="panel" style="margin-top:16px">
1654
- <div class="panelHead"><b>Saida</b></div>
1655
- <div class="panelBody">
1656
- <div id="reportPreview" class="log md" style="font-family: var(--sans);"></div>
1657
- </div>
1658
- </section>
1703
+ </div>
1659
1704
  </div>
1660
1705
  </main>
1661
1706
 
@@ -2072,6 +2117,14 @@ async function cmdWeb({ port, dir, open, dev }) {
2072
2117
  return;
2073
2118
  }
2074
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
+
2075
2128
  if (req.method === 'GET' && req.url === '/app.css') {
2076
2129
  const css = fs.readFileSync(path.join(__dirname, 'web-ui.css'), 'utf8');
2077
2130
  res.writeHead(200, { 'Content-Type': 'text/css; charset=utf-8', 'Cache-Control': 'no-store' });
@@ -2165,34 +2218,23 @@ async function cmdWeb({ port, dir, open, dev }) {
2165
2218
  }
2166
2219
 
2167
2220
  if (req.url === '/api/projects/list') {
2168
- const base = path.join(workspaceDir, 'data', 'Clients');
2221
+ const rawProjects = dl.db.prepare('SELECT * FROM projects').all();
2169
2222
  const items = [];
2170
- if (exists(base)) {
2171
- const stack = [base];
2172
- while (stack.length) {
2173
- const dirp = stack.pop();
2174
- const entries = fs.readdirSync(dirp, { withFileTypes: true });
2175
- for (const ent of entries) {
2176
- const full = path.join(dirp, ent.name);
2177
- if (ent.isDirectory()) stack.push(full);
2178
- else if (ent.isFile() && ent.name === 'status.json') {
2179
- const doc = readJsonOrNull(full) || {};
2180
- const rel = path.relative(base, path.dirname(full)).replace(/\\/g, '/');
2181
- items.push({
2182
- slug: rel,
2183
- client: doc.client || null,
2184
- program: doc.program || null,
2185
- stream: doc.stream || null,
2186
- project: doc.project || null,
2187
- active: doc.active !== false,
2188
- currentStatus: doc.currentStatus || '',
2189
- lastUpdated: doc.lastUpdated || '',
2190
- tags: Array.isArray(doc.tags) ? doc.tags : [],
2191
- historyCount: Array.isArray(doc.history) ? doc.history.length : 0
2192
- });
2193
- }
2194
- }
2195
- }
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
+ });
2196
2238
  }
2197
2239
  items.sort((a, b) => String(b.lastUpdated || '').localeCompare(String(a.lastUpdated || '')));
2198
2240
  return safeJson(res, 200, { ok: true, projects: items });
@@ -2212,68 +2254,33 @@ async function cmdWeb({ port, dir, open, dev }) {
2212
2254
  };
2213
2255
 
2214
2256
  // 1. Load Projects
2215
- const base = path.join(workspaceDir, 'data', 'Clients');
2216
- if (exists(base)) {
2217
- const stack = [base];
2218
- while (stack.length) {
2219
- const dirp = stack.pop();
2220
- const entries = fs.readdirSync(dirp, { withFileTypes: true });
2221
- for (const ent of entries) {
2222
- const full = path.join(dirp, ent.name);
2223
- if (ent.isDirectory()) stack.push(full);
2224
- else if (ent.isFile() && ent.name === 'status.json') {
2225
- const doc = readJsonOrNull(full) || {};
2226
- const slug = path.relative(base, path.dirname(full)).replace(/\\/g, '/');
2227
- addNode(slug, slug, 'project');
2228
-
2229
- // Link tags
2230
- const tags = Array.isArray(doc.tags) ? doc.tags : [];
2231
- for (const t of tags) {
2232
- const tid = 'tag:' + String(t).trim().toLowerCase();
2233
- addNode(tid, String(t).trim(), 'tag');
2234
- edges.push({ from: slug, to: tid });
2235
- }
2236
- }
2237
- }
2238
- }
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');
2239
2260
  }
2240
2261
 
2241
2262
  // 2. Load Tasks
2242
- const taskFile = path.join(workspaceDir, 'data', 'tasks', 'task-log.json');
2243
- const taskDoc = readJsonOrNull(taskFile) || { tasks: [] };
2244
- const tasks = Array.isArray(taskDoc.tasks) ? taskDoc.tasks : [];
2245
-
2246
- for (const t of tasks) {
2247
- // Only show pending high/medium tasks for clarity, or unassigned ones.
2248
- if (t.status === 'COMPLETED') continue;
2249
-
2263
+ const rawTasks = dl.db.prepare('SELECT * FROM tasks WHERE status != "COMPLETED"').all();
2264
+ for (const t of rawTasks) {
2250
2265
  const tid = 'task:' + t.id;
2251
- addNode(tid, t.title || 'Tarefa', 'task');
2266
+ addNode(tid, t.description || 'Tarefa', 'task');
2252
2267
 
2253
- const slug = t.projectSlug || null;
2254
- if (slug && nodeIds.has(slug)) {
2255
- 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 });
2256
2270
  } else {
2257
- // Connect to an "Unassigned" node
2258
2271
  addNode('unassigned', 'Sin Asignar', 'unassigned');
2259
2272
  edges.push({ from: tid, to: 'unassigned' });
2260
2273
  }
2261
2274
  }
2262
2275
 
2263
2276
  // 3. Load Blockers
2264
- const blockerFile = path.join(workspaceDir, 'data', 'blockers', 'blocker-log.json');
2265
- const blockerDoc = readJsonOrNull(blockerFile) || { blockers: [] };
2266
- const blockers = Array.isArray(blockerDoc.blockers) ? blockerDoc.blockers : [];
2267
-
2268
- for (const b of blockers) {
2269
- if (String(b.status || '').toUpperCase() !== 'OPEN') continue;
2270
-
2277
+ const openBlockers = dl.db.prepare('SELECT * FROM blockers WHERE status = "OPEN"').all();
2278
+ for (const b of openBlockers) {
2271
2279
  const bid = 'blocker:' + b.id;
2272
2280
  addNode(bid, 'BLQ: ' + (b.title || 'Blocker'), 'blocker');
2273
2281
 
2274
- const slug = b.projectSlug || null;
2275
- if (slug && nodeIds.has(slug)) {
2276
- 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 });
2277
2284
  } else {
2278
2285
  addNode('unassigned_blockers', 'Bloqueios Soltos', 'unassigned');
2279
2286
  edges.push({ from: bid, to: 'unassigned_blockers' });
@@ -2831,8 +2838,8 @@ async function cmdWeb({ port, dir, open, dev }) {
2831
2838
  const schema = {
2832
2839
  actions: [
2833
2840
  { type: 'append_daily_log', text: '<string>' },
2834
- { type: 'create_task', description: '<string>', priority: 'HIGH|MEDIUM|LOW', category: 'DO_NOW|SCHEDULE|DELEGATE|IGNORE', projectSlug: '<string optional>' },
2835
- { 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>' },
2836
2843
  { type: 'suggest_report', name: 'daily|status|sm-weekly|blockers' },
2837
2844
  { type: 'oracle_query', query: '<string>' }
2838
2845
  ]
@@ -2990,18 +2997,6 @@ async function cmdWeb({ port, dir, open, dev }) {
2990
2997
  if (!Array.isArray(actions) || actions.length === 0) {
2991
2998
  return safeJson(res, 400, { error: 'Plan has no actions[]' });
2992
2999
  }
2993
-
2994
- const taskFile = path.join(workspaceDir, 'data', 'tasks', 'task-log.json');
2995
- const blockerFile = path.join(workspaceDir, 'data', 'blockers', 'blocker-log.json');
2996
-
2997
- const taskLog = readJsonOrNull(taskFile) || { schemaVersion: 1, tasks: [] };
2998
- if (!Array.isArray(taskLog.tasks)) taskLog.tasks = [];
2999
- if (typeof taskLog.schemaVersion !== 'number') taskLog.schemaVersion = 1;
3000
-
3001
- const blockerLog = readJsonOrNull(blockerFile) || { schemaVersion: 1, blockers: [] };
3002
- if (!Array.isArray(blockerLog.blockers)) blockerLog.blockers = [];
3003
- if (typeof blockerLog.schemaVersion !== 'number') blockerLog.schemaVersion = 1;
3004
-
3005
3000
  function normalizeWhitespace(t) {
3006
3001
  return String(t || '').replace(/\s+/g, ' ').trim();
3007
3002
  }
@@ -3014,27 +3009,11 @@ async function cmdWeb({ port, dir, open, dev }) {
3014
3009
  return crypto.createHash('sha1').update(String(text || ''), 'utf8').digest('hex');
3015
3010
  }
3016
3011
 
3017
- function within24h(iso) {
3018
- try {
3019
- const ms = Date.parse(iso);
3020
- if (!Number.isFinite(ms)) return false;
3021
- return (Date.now() - ms) <= 24 * 60 * 60 * 1000;
3022
- } catch {
3023
- return false;
3024
- }
3025
- }
3026
-
3027
- const existingTaskKeys24h = new Set(
3028
- taskLog.tasks
3029
- .filter((t) => t && within24h(t.createdAt))
3030
- .map((t) => sha1(normalizeTextForKey(t.description)))
3031
- );
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))));
3032
3014
 
3033
- const existingBlockerKeys24h = new Set(
3034
- blockerLog.blockers
3035
- .filter((b) => b && within24h(b.createdAt))
3036
- .map((b) => sha1(normalizeTextForKey(b.title)))
3037
- );
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))));
3038
3017
 
3039
3018
  const now = new Date().toISOString();
3040
3019
  const applyMode = String(payload.mode || 'all').trim();
@@ -3066,71 +3045,72 @@ async function cmdWeb({ port, dir, open, dev }) {
3066
3045
 
3067
3046
  const validTaskCats = new Set(['DO_NOW', 'SCHEDULE', 'DELEGATE', 'IGNORE']);
3068
3047
 
3069
- for (const a of actions) {
3070
- if (!a || typeof a !== 'object') continue;
3071
- 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
+ }
3072
3076
 
3073
- if (type === 'create_task') {
3074
- if (applyMode !== 'all' && applyMode !== 'tasks') continue;
3075
- const description = normalizeWhitespace(a.description);
3076
- if (!description) continue;
3077
- const projectSlug = String(a.projectSlug || '').trim() || inferProjectSlug(description, slugMap);
3078
- const key = sha1(normalizeTextForKey((projectSlug ? projectSlug + ' ' : '') + description));
3079
- if (existingTaskKeys24h.has(key)) { applied.tasksSkipped++; continue; }
3080
- const category = validTaskCats.has(String(a.category || '').trim()) ? String(a.category).trim() : 'DO_NOW';
3081
- const priority = normPriority(a.priority);
3082
- const task = {
3083
- id: makeId('t'),
3084
- description,
3085
- category,
3086
- status: 'PENDING',
3087
- createdAt: now,
3088
- };
3089
- if (projectSlug) task.projectSlug = projectSlug;
3090
- if (priority) task.priority = priority;
3091
- taskLog.tasks.push(task);
3092
- applied.tasks++;
3093
- continue;
3094
- }
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
+ }
3095
3096
 
3096
- if (type === 'create_blocker') {
3097
- if (applyMode !== 'all' && applyMode !== 'blockers') continue;
3098
- const title = normalizeWhitespace(a.title);
3099
- const projectSlug = String(a.projectSlug || '').trim() || inferProjectSlug(title + ' ' + normalizeWhitespace(a.notes), slugMap);
3100
- const key = sha1(normalizeTextForKey((projectSlug ? projectSlug + ' ' : '') + title));
3101
- if (existingBlockerKeys24h.has(key)) { applied.blockersSkipped++; continue; }
3102
- const notes = normalizeWhitespace(a.notes);
3103
- if (!title) continue;
3104
- const severity = normSeverity(a.severity);
3105
- const blocker = {
3106
- id: makeId('b'),
3107
- title,
3108
- description: notes || title,
3109
- createdAt: now,
3110
- status: 'OPEN',
3111
- severity,
3112
- };
3113
- if (projectSlug) blocker.projectSlug = projectSlug;
3114
- blockerLog.blockers.push(blocker);
3115
- applied.blockers++;
3116
- continue;
3117
- }
3097
+ if (type === 'suggest_report') {
3098
+ const name = String(a.name || '').trim();
3099
+ if (name) applied.reportsSuggested.push(name);
3100
+ continue;
3101
+ }
3118
3102
 
3119
- if (type === 'suggest_report') {
3120
- const name = String(a.name || '').trim();
3121
- if (name) applied.reportsSuggested.push(name);
3122
- continue;
3103
+ if (type === 'oracle_query') {
3104
+ const query = String(a.query || '').trim();
3105
+ if (query) applied.oracleQueries.push(query);
3106
+ continue;
3107
+ }
3123
3108
  }
3109
+ });
3124
3110
 
3125
- if (type === 'oracle_query') {
3126
- const query = String(a.query || '').trim();
3127
- if (query) applied.oracleQueries.push(query);
3128
- continue;
3129
- }
3130
- }
3111
+ applyTx(actions);
3131
3112
 
3132
3113
  // Auto-suggest reports when planner didn't include any
3133
- // (keeps UX consistent: if you created a blocker, at least suggest blockers)
3134
3114
  if (!applied.reportsSuggested.length) {
3135
3115
  const sug = [];
3136
3116
  sug.push('daily');
@@ -3142,10 +3122,6 @@ async function cmdWeb({ port, dir, open, dev }) {
3142
3122
  applied.reportsSuggested = Array.from(new Set(applied.reportsSuggested.map((s) => String(s).trim()).filter(Boolean)));
3143
3123
  }
3144
3124
 
3145
- // Persist
3146
- writeJson(taskFile, taskLog);
3147
- writeJson(blockerFile, blockerLog);
3148
-
3149
3125
  return safeJson(res, 200, { ok: true, applied });
3150
3126
  }
3151
3127
 
@@ -3236,28 +3212,51 @@ async function cmdWeb({ port, dir, open, dev }) {
3236
3212
  const query = String(payload.query || '').trim();
3237
3213
  if (!query) return safeJson(res, 400, { error: 'Missing query' });
3238
3214
 
3239
- const copilotResult = await copilotSearch(workspaceDir, query, { limit: 8 });
3240
- const indexMatches = searchIndex(workspaceDir, query, { limit: 12 });
3241
- const baseMatches = indexMatches.length
3242
- ? indexMatches
3243
- : searchWorkspace(workspaceDir, query, { limit: 12 });
3244
-
3245
- if (copilotResult.ok) {
3246
- const merged = mergeMatches(copilotResult.matches || [], baseMatches, 8);
3247
- const answer = buildChatAnswer(
3248
- query,
3249
- merged.matches,
3250
- copilotResult.summary,
3251
- copilotResult.evidence,
3252
- copilotResult.answer,
3253
- merged.total
3254
- );
3255
- 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
+ }
3256
3239
  }
3257
3240
 
3258
- const merged = mergeMatches(baseMatches, [], 8);
3259
- const answer = buildChatAnswer(query, merged.matches, '', [], '', merged.total);
3260
- 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
+ }
3261
3260
  }
3262
3261
 
3263
3262
  // Chat persistence (per session)
@@ -3360,52 +3359,48 @@ async function cmdWeb({ port, dir, open, dev }) {
3360
3359
  const cat = payload.category ? String(payload.category).trim() : null;
3361
3360
  const status = payload.status ? String(payload.status).trim() : null;
3362
3361
 
3363
- const file = path.join(workspaceDir, 'data', 'tasks', 'task-log.json');
3364
- const doc = readJsonOrNull(file) || { schemaVersion: 1, tasks: [] };
3365
- const tasks = Array.isArray(doc.tasks) ? doc.tasks.slice() : [];
3366
-
3367
- const filtered = tasks
3368
- .filter((t) => {
3369
- if (!t || typeof t !== 'object') return false;
3370
- if (cat && String(t.category || '').trim() !== cat) return false;
3371
- if (status && String(t.status || '').trim() !== status) return false;
3372
- return true;
3373
- })
3374
- .sort((a, b) => {
3375
- const pa = String(a.priority || '').toLowerCase();
3376
- const pb = String(b.priority || '').toLowerCase();
3377
- const rank = (p) => (p === 'high' ? 0 : p === 'medium' ? 1 : p === 'low' ? 2 : 3);
3378
- const ra = rank(pa), rb = rank(pb);
3379
- if (ra !== rb) return ra - rb;
3380
- return String(b.createdAt || '').localeCompare(String(a.createdAt || ''));
3381
- })
3382
- .slice(0, limit);
3383
-
3384
- 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 });
3385
3393
  }
3386
3394
 
3387
3395
  if (req.url === '/api/tasks/complete') {
3388
3396
  const id = String(payload.id || '').trim();
3389
3397
  if (!id) return safeJson(res, 400, { error: 'Missing id' });
3390
3398
 
3391
- const file = path.join(workspaceDir, 'data', 'tasks', 'task-log.json');
3392
- const doc = readJsonOrNull(file) || { schemaVersion: 1, tasks: [] };
3393
- const tasks = Array.isArray(doc.tasks) ? doc.tasks : [];
3394
-
3395
- const now = isoNow();
3396
- let updated = null;
3397
- for (const t of tasks) {
3398
- if (t && t.id === id) {
3399
- t.status = 'COMPLETED';
3400
- t.completedAt = now;
3401
- updated = t;
3402
- break;
3403
- }
3404
- }
3399
+ const now = new Date().toISOString();
3400
+ const info = dl.db.prepare(`UPDATE tasks SET status = 'COMPLETED', completed_at = ? WHERE id = ?`).run(now, id);
3405
3401
 
3406
- if (!updated) return safeJson(res, 404, { error: 'Task not found' });
3407
- writeJson(file, doc);
3408
- 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 } });
3409
3404
  }
3410
3405
 
3411
3406
 
@@ -3414,22 +3409,26 @@ async function cmdWeb({ port, dir, open, dev }) {
3414
3409
  if (!id) return safeJson(res, 400, { error: 'Missing id' });
3415
3410
  const patch = payload.patch && typeof payload.patch === 'object' ? payload.patch : {};
3416
3411
 
3417
- const file = path.join(workspaceDir, 'data', 'tasks', 'task-log.json');
3418
- const doc = readJsonOrNull(file) || { schemaVersion: 1, tasks: [] };
3419
- const tasks = Array.isArray(doc.tasks) ? doc.tasks : [];
3412
+ let queryUpdates = [];
3413
+ let params = [];
3420
3414
 
3421
- let updated = null;
3422
- for (const t of tasks) {
3423
- if (t && t.id === id) {
3424
- if (typeof patch.projectSlug === 'string') t.projectSlug = patch.projectSlug.trim() || undefined;
3425
- if (typeof patch.category === 'string') t.category = patch.category.trim();
3426
- updated = t;
3427
- break;
3428
- }
3415
+ if (typeof patch.projectSlug === 'string') {
3416
+ queryUpdates.push('project_slug = ?');
3417
+ params.push(patch.projectSlug.trim() || null);
3429
3418
  }
3430
- if (!updated) return safeJson(res, 404, { error: 'Task not found' });
3431
- writeJson(file, doc);
3432
- 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 } });
3433
3432
  }
3434
3433
 
3435
3434
  if (req.url === '/api/health/checklist') {
@@ -3441,13 +3440,11 @@ async function cmdWeb({ port, dir, open, dev }) {
3441
3440
  return safeJson(res, 200, { ok: false, needsInit: true, error: 'Workspace not initialized', items: [] });
3442
3441
  }
3443
3442
 
3444
- const tasksFile = path.join(workspaceDir, 'data', 'tasks', 'task-log.json');
3445
- const blockersFile = path.join(workspaceDir, 'data', 'blockers', 'blocker-log.json');
3446
- const tasksDoc = readJsonOrNull(tasksFile) || { schemaVersion: 1, tasks: [] };
3447
- 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();
3448
3445
 
3449
- const pendingTasks = (tasksDoc.tasks || []).filter((t) => t && t.status === 'PENDING' && t.category === 'DO_NOW').length;
3450
- 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;
3451
3448
 
3452
3449
  const today = isoDate();
3453
3450
  const reports = listReports(workspaceDir);
@@ -3456,7 +3453,7 @@ async function cmdWeb({ port, dir, open, dev }) {
3456
3453
  const items = [
3457
3454
  { label: 'Blockers abertos', status: openBlockers > 0 ? 'warn' : 'ok', detail: `${openBlockers} aberto(s)` },
3458
3455
  { label: 'Tarefas DO_NOW pendentes', status: pendingTasks > 0 ? 'warn' : 'ok', detail: `${pendingTasks} pendente(s)` },
3459
- { 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)` }
3460
3457
  ];
3461
3458
 
3462
3459
  return safeJson(res, 200, { ok: true, items, stats: { pendingTasks, openBlockers, reportsToday, reportsTotal: reports.length } });
@@ -3506,11 +3503,18 @@ async function cmdWeb({ port, dir, open, dev }) {
3506
3503
  }
3507
3504
 
3508
3505
  if (req.url === '/api/blockers/summary') {
3509
- const file = path.join(workspaceDir, 'data', 'blockers', 'blocker-log.json');
3510
- const doc = readJsonOrNull(file) || { schemaVersion: 1, blockers: [] };
3511
- 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
+ }));
3512
3517
 
3513
- const open = blockers.filter((b) => b && String(b.status || '').trim() === 'OPEN');
3514
3518
  const sevCount = { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0, OTHER: 0 };
3515
3519
  const now = Date.now();
3516
3520
  let older7 = 0;
@@ -3529,7 +3533,7 @@ async function cmdWeb({ port, dir, open, dev }) {
3529
3533
  .slice(0, 5)
3530
3534
  .map((b) => ({
3531
3535
  id: b.id,
3532
- title: b.title || b.description || 'Sem titulo',
3536
+ title: b.title || 'Sem titulo',
3533
3537
  severity: b.severity || 'UNKNOWN',
3534
3538
  projectSlug: b.projectSlug || null
3535
3539
  }));
@@ -3548,34 +3552,40 @@ async function cmdWeb({ port, dir, open, dev }) {
3548
3552
  const limit = Math.max(1, Math.min(50, Number(payload.limit || 10)));
3549
3553
  const status = payload.status ? String(payload.status).trim() : 'OPEN';
3550
3554
 
3551
- const file = path.join(workspaceDir, 'data', 'blockers', 'blocker-log.json');
3552
- const doc = readJsonOrNull(file) || { schemaVersion: 1, blockers: [] };
3553
- const blockers = Array.isArray(doc.blockers) ? doc.blockers.slice() : [];
3554
-
3555
- const sevRank = (s) => {
3556
- const v = String(s || '').toUpperCase();
3557
- if (v === 'CRITICAL') return 0;
3558
- if (v === 'HIGH') return 1;
3559
- if (v === 'MEDIUM') return 2;
3560
- if (v === 'LOW') return 3;
3561
- return 9;
3562
- };
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
+ }
3563
3562
 
3564
- const filtered = blockers
3565
- .filter((b) => {
3566
- if (!b || typeof b !== 'object') return false;
3567
- if (status && String(b.status || '').trim() !== status) return false;
3568
- return true;
3569
- })
3570
- .sort((a, b) => {
3571
- const ra = sevRank(a.severity);
3572
- const rb = sevRank(b.severity);
3573
- if (ra !== rb) return ra - rb;
3574
- return String(a.createdAt || '').localeCompare(String(b.createdAt || ''));
3575
- })
3576
- .slice(0, limit);
3577
-
3578
- 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 });
3579
3589
  }
3580
3590
 
3581
3591
  if (req.url === '/api/blockers/update') {
@@ -3583,21 +3593,22 @@ async function cmdWeb({ port, dir, open, dev }) {
3583
3593
  if (!id) return safeJson(res, 400, { error: 'Missing id' });
3584
3594
  const patch = payload.patch && typeof payload.patch === 'object' ? payload.patch : {};
3585
3595
 
3586
- const file = path.join(workspaceDir, 'data', 'blockers', 'blocker-log.json');
3587
- const doc = readJsonOrNull(file) || { schemaVersion: 1, blockers: [] };
3588
- const blockers = Array.isArray(doc.blockers) ? doc.blockers : [];
3596
+ let queryUpdates = [];
3597
+ let params = [];
3589
3598
 
3590
- let updated = null;
3591
- for (const b of blockers) {
3592
- if (b && b.id === id) {
3593
- if (typeof patch.projectSlug === 'string') b.projectSlug = patch.projectSlug.trim() || undefined;
3594
- updated = b;
3595
- break;
3596
- }
3599
+ if (typeof patch.projectSlug === 'string') {
3600
+ queryUpdates.push('project_slug = ?');
3601
+ params.push(patch.projectSlug.trim() || null);
3597
3602
  }
3598
- if (!updated) return safeJson(res, 404, { error: 'Blocker not found' });
3599
- writeJson(file, doc);
3600
- 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 } });
3601
3612
  }
3602
3613
 
3603
3614
  if (req.url === '/api/report') {
@@ -3688,4 +3699,148 @@ async function cmdWeb({ port, dir, open, dev }) {
3688
3699
  if (open) openBrowser(url);
3689
3700
  }
3690
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
+
3691
3845
  module.exports = { cmdWeb };
3846
+