@cccarv82/freya 2.20.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cli/web.js CHANGED
@@ -1192,14 +1192,9 @@ function buildHtml(safeDefault, appVersion) {
1192
1192
  </div>
1193
1193
 
1194
1194
  <!-- Textarea -->
1195
- <textarea id="inboxText" aria-label="Entrada de texto para processar ou perguntar" placeholder="Cole updates, decisões, blockers... ou faça uma pergunta à Freya.&#10;&#10;▸ Salvar & Processar → extrai tarefas e blockers do texto&#10;▸ Perguntar → consulta o histórico via busca semântica (RAG)&#10;▸ Ctrl+V com imagem → anexa screenshot ao contexto" style="resize:none; min-height: 200px; flex: 1; border-radius: 0; border-left: none; border-right: none; border-top: none; border-bottom: 1px solid var(--border); padding: 14px 16px; font-size: 13px; line-height: 1.6;"
1195
+ <textarea id="inboxText" aria-label="Entrada de texto para processar ou perguntar" placeholder="Cole updates, decisões, blockers... ou faça uma pergunta à Freya.&#10;&#10;▸ Salvar & Processar → extrai tarefas e blockers do texto&#10;▸ Perguntar → consulta o histórico via busca semântica (RAG)" style="resize:none; min-height: 200px; flex: 1; border-radius: 0; border-left: none; border-right: none; border-top: none; border-bottom: 1px solid var(--border); padding: 14px 16px; font-size: 13px; line-height: 1.6;"
1196
1196
  onkeydown="if((event.metaKey||event.ctrlKey)&&event.key==='Enter'){event.preventDefault();window.saveAndPlan();}"></textarea>
1197
1197
 
1198
- <!-- Image paste preview strip -->
1199
- <div id="pastePreview" style="display:none; padding: 8px 14px; border-bottom: 1px solid var(--border); background: rgba(0,0,0,0.05); flex-shrink:0;">
1200
- <div style="display:flex; align-items:center; gap:8px; flex-wrap:wrap;" id="pastePreviewInner"></div>
1201
- </div>
1202
-
1203
1198
  <!-- Actions bar -->
1204
1199
  <div style="padding: 10px 14px; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 8px; flex-shrink:0;">
1205
1200
  <div style="display:flex; gap:8px; flex-wrap:wrap;">
@@ -1351,13 +1346,6 @@ function buildHtml(safeDefault, appVersion) {
1351
1346
  </div>
1352
1347
  </div>
1353
1348
 
1354
- <!-- Image lightbox modal -->
1355
- <div id="imgLightbox" class="img-lightbox" onclick="window.closeLightbox(event)">
1356
- <button class="img-lightbox-close" onclick="window.closeLightbox(event)" title="Fechar (Esc)">&times;</button>
1357
- <img id="imgLightboxImg" src="" alt="preview" onclick="event.stopPropagation()" />
1358
- <div id="imgLightboxName" class="img-lightbox-name"></div>
1359
- </div>
1360
-
1361
1349
  <script>
1362
1350
  window.__FREYA_DEFAULT_DIR = "${safeDefault}";
1363
1351
  </script>
@@ -2408,26 +2396,25 @@ async function cmdWeb({ port, dir, open, dev }) {
2408
2396
  return;
2409
2397
  }
2410
2398
 
2411
- // Serve attachment images (pasted screenshots)
2399
+ // Serve attachment images from workspace data/attachments/
2412
2400
  if (req.method === 'GET' && req.url.startsWith('/attachments/')) {
2413
- const relPath = decodeURIComponent(req.url.replace('/attachments/', ''));
2401
+ const fname = decodeURIComponent(req.url.slice('/attachments/'.length)).replace(/[\/\\]/g, '');
2414
2402
  const requestedDir = dir || './freya';
2415
- const wsDir = path.resolve(process.cwd(), requestedDir);
2416
- const filePath = path.join(wsDir, 'data', 'attachments', relPath);
2417
- // Security: ensure path stays within data/attachments
2418
- if (!filePath.startsWith(path.join(wsDir, 'data', 'attachments'))) {
2419
- res.writeHead(403); res.end(); return;
2420
- }
2421
- if (exists(filePath)) {
2422
- const ext = path.extname(filePath).toLowerCase();
2423
- const mimeMap = { '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif', '.webp': 'image/webp', '.bmp': 'image/bmp' };
2424
- const mime = mimeMap[ext] || 'application/octet-stream';
2425
- const data = fs.readFileSync(filePath);
2426
- res.writeHead(200, { 'Content-Type': mime, 'Cache-Control': 'public, max-age=86400' });
2427
- res.end(data);
2403
+ let wsDir;
2404
+ try { wsDir = normalizeWorkspaceDir(requestedDir); } catch { wsDir = path.resolve(process.cwd(), requestedDir); }
2405
+ const filePath = path.join(wsDir, 'data', 'attachments', fname);
2406
+ if (!exists(filePath)) {
2407
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
2408
+ res.end('Not found');
2428
2409
  return;
2429
2410
  }
2430
- res.writeHead(404); res.end(); return;
2411
+ const ext = path.extname(fname).toLowerCase();
2412
+ const mimeMap = { '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif', '.webp': 'image/webp', '.bmp': 'image/bmp', '.svg': 'image/svg+xml' };
2413
+ const mime = mimeMap[ext] || 'application/octet-stream';
2414
+ const data = fs.readFileSync(filePath);
2415
+ res.writeHead(200, { 'Content-Type': mime, 'Cache-Control': 'max-age=86400' });
2416
+ res.end(data);
2417
+ return;
2431
2418
  }
2432
2419
 
2433
2420
  if (req.url.startsWith('/api/')) {
@@ -3240,30 +3227,9 @@ async function cmdWeb({ port, dir, open, dev }) {
3240
3227
  }
3241
3228
  }
3242
3229
 
3243
- // ── Upload attachment (image paste) ──────────────────────────
3244
- if (req.url === '/api/attachments/upload') {
3245
- const data = String(payload.data || '').trim();
3246
- const fname = String(payload.filename || '').trim();
3247
- if (!data || !fname) return safeJson(res, 400, { error: 'Missing data or filename' });
3248
-
3249
- const d = isoDate();
3250
- const attDir = path.join(workspaceDir, 'data', 'attachments', d);
3251
- ensureDir(attDir);
3252
-
3253
- // data is base64 (may have data:image/... prefix)
3254
- const base64 = data.replace(/^data:image\/[^;]+;base64,/, '');
3255
- const buf = Buffer.from(base64, 'base64');
3256
- const filePath = path.join(attDir, fname);
3257
- fs.writeFileSync(filePath, buf);
3258
-
3259
- const relPath = `data/attachments/${d}/${fname}`;
3260
- return safeJson(res, 200, { ok: true, path: relPath });
3261
- }
3262
-
3263
3230
  if (req.url === '/api/inbox/add') {
3264
3231
  const text = String(payload.text || '').trim();
3265
- const attachments = Array.isArray(payload.attachments) ? payload.attachments : [];
3266
- if (!text && !attachments.length) return safeJson(res, 400, { error: 'Missing text' });
3232
+ if (!text) return safeJson(res, 400, { error: 'Missing text' });
3267
3233
 
3268
3234
  const d = isoDate();
3269
3235
  const file = path.join(workspaceDir, 'logs', 'daily', `${d}.md`);
@@ -3277,13 +3243,8 @@ async function cmdWeb({ port, dir, open, dev }) {
3277
3243
  const linksText = linkInfo && linkInfo.linksText ? linkInfo.linksText : '';
3278
3244
  const slugs = linkInfo && Array.isArray(linkInfo.slugs) ? linkInfo.slugs : [];
3279
3245
 
3280
- // Build attachment markdown references
3281
- let attachBlock = '';
3282
- if (attachments.length) {
3283
- attachBlock = '\n\n**Anexos:**\n' + attachments.map(a => `![${a.name || 'screenshot'}](${a.path})`).join('\n');
3284
- }
3285
-
3286
- const block = `\n\n## [${hh}:${mm}] Raw Input\n${text}${linksText}${attachBlock}\n`;
3246
+ const imgRef = payload.imagePath ? `\n![anexo](../${payload.imagePath})` : '';
3247
+ const block = `\n\n## [${hh}:${mm}] Raw Input\n${text}${imgRef}${linksText}\n`;
3287
3248
  fs.appendFileSync(file, block, 'utf8');
3288
3249
 
3289
3250
  if (slugs.length) {
@@ -3342,16 +3303,7 @@ async function cmdWeb({ port, dir, open, dev }) {
3342
3303
  ]
3343
3304
  };
3344
3305
 
3345
- // Build image references if attachments were provided
3346
- const attachments = Array.isArray(payload.attachments) ? payload.attachments : [];
3347
- let imageContext = '';
3348
- if (attachments.length) {
3349
- imageContext = '\n\nIMAGENS ANEXADAS (use @caminho para analisar):\n' +
3350
- attachments.map(a => `- @${path.join(workspaceDir, a.path)} (${a.name})`).join('\n') + '\n' +
3351
- 'Analise as imagens anexadas e inclua as informações visuais no plano.\n';
3352
- }
3353
-
3354
- const prompt = `Você é o planner do sistema F.R.E.Y.A.\n\nContexto: vamos receber um input bruto do usuário e propor ações estruturadas.\nRegras: siga os arquivos de regras abaixo.\nSaída: retorne APENAS JSON válido no formato: ${JSON.stringify(schema)}\n\nRestrições:\n- NÃO use code fences (\`\`\`)\n- NÃO inclua texto extra antes/depois do JSON\n- NÃO use quebras de linha dentro de strings (transforme em uma frase única)\n\nREGRAS:${rulesText}${imageContext}\n\nINPUT DO USUÁRIO:\n${text}\n`;
3306
+ const prompt = `Você é o planner do sistema F.R.E.Y.A.\n\nContexto: vamos receber um input bruto do usuário e propor ações estruturadas.\nRegras: siga os arquivos de regras abaixo.\nSaída: retorne APENAS JSON válido no formato: ${JSON.stringify(schema)}\n\nRestrições:\n- NÃO use code fences (\`\`\`)\n- NÃO inclua texto extra antes/depois do JSON\n- NÃO use quebras de linha dentro de strings (transforme em uma frase única)\n\nREGRAS:${rulesText}\n\nINPUT DO USUÁRIO:\n${text}\n`;
3355
3307
 
3356
3308
  // Prefer COPILOT_CMD if provided, otherwise try 'copilot'
3357
3309
  const cmd = process.env.COPILOT_CMD || 'copilot';
@@ -3361,7 +3313,7 @@ async function cmdWeb({ port, dir, open, dev }) {
3361
3313
  // BUG-48: pass FREYA_WORKSPACE_DIR so the Copilot subprocess uses correct DB
3362
3314
  const agentEnv = { FREYA_WORKSPACE_DIR: workspaceDir };
3363
3315
  try {
3364
- const r = await run(cmd, ['-s', '--no-color', '--stream', 'off', '-p', prompt, '--allow-all-tools', '--add-dir', workspaceDir], workspaceDir, agentEnv);
3316
+ const r = await run(cmd, ['-s', '--no-color', '--stream', 'off', '-p', prompt, '--allow-all-tools'], workspaceDir, agentEnv);
3365
3317
  const out = (r.stdout + r.stderr).trim();
3366
3318
  if (r.code !== 0) {
3367
3319
  return safeJson(res, 200, {
@@ -3707,9 +3659,25 @@ async function cmdWeb({ port, dir, open, dev }) {
3707
3659
  return safeJson(res, r.code === 0 ? 200 : 400, r.code === 0 ? { ok: true, output: out } : { error: out || 'index rebuild failed', output: out });
3708
3660
  }
3709
3661
 
3662
+ // Upload attachment (base64 image from clipboard paste)
3663
+ if (req.url === '/api/attachments/upload') {
3664
+ const base64 = String(payload.data || '').trim();
3665
+ const mimeType = String(payload.mimeType || 'image/png').trim();
3666
+ if (!base64) return safeJson(res, 400, { error: 'Missing data' });
3667
+ const ext = mimeType === 'image/jpeg' ? '.jpg' : mimeType === 'image/gif' ? '.gif' : mimeType === 'image/webp' ? '.webp' : '.png';
3668
+ const fname = 'paste-' + Date.now() + ext;
3669
+ const attachDir = path.join(workspaceDir, 'data', 'attachments');
3670
+ if (!exists(attachDir)) fs.mkdirSync(attachDir, { recursive: true });
3671
+ const filePath = path.join(attachDir, fname);
3672
+ const buf = Buffer.from(base64, 'base64');
3673
+ fs.writeFileSync(filePath, buf);
3674
+ return safeJson(res, 200, { ok: true, filename: fname, url: '/attachments/' + fname });
3675
+ }
3676
+
3710
3677
  if (req.url === '/api/chat/ask') {
3711
3678
  const sessionId = String(payload.sessionId || '').trim();
3712
3679
  const query = String(payload.query || '').trim();
3680
+ const imagePath = payload.imagePath ? String(payload.imagePath).trim() : null;
3713
3681
  if (!query) return safeJson(res, 400, { error: 'Missing query' });
3714
3682
 
3715
3683
  const workspaceRulesBase = path.join(workspaceDir, '.agent', 'rules', 'freya');
@@ -3742,27 +3710,23 @@ async function cmdWeb({ port, dir, open, dev }) {
3742
3710
  console.error('[oracle] RAG search failed (embedder/sharp unavailable), continuing without context:', ragErr.message);
3743
3711
  }
3744
3712
 
3745
- // Build image references if attachments were provided
3746
- const askAttachments = Array.isArray(payload.attachments) ? payload.attachments : [];
3747
- let askImageContext = '';
3748
- const askHasImages = askAttachments.length > 0;
3749
- if (askHasImages) {
3750
- askImageContext = '\n\nIMAGENS ANEXADAS (use @caminho para analisar):\n' +
3751
- askAttachments.map(a => `- @${path.join(workspaceDir, a.path)} (${a.name})`).join('\n') + '\n' +
3752
- 'Analise as imagens anexadas e use-as como contexto para responder à consulta.\n';
3753
- }
3754
-
3755
- 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}${askImageContext}\n\nREGRAS:${rulesText}\n\nCONSULTA DO USUÁRIO:\n${query}\n`;
3713
+ 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`;
3756
3714
 
3757
3715
  const cmd = process.env.COPILOT_CMD || 'copilot';
3758
3716
 
3759
3717
  // BUG-48: pass FREYA_WORKSPACE_DIR so the Copilot subprocess uses correct DB
3760
3718
  const oracleEnv = { FREYA_WORKSPACE_DIR: workspaceDir };
3761
3719
  try {
3762
- // Add --add-dir when images are attached so Copilot can access them
3763
- const oracleArgs = ['-s', '--no-color', '--stream', 'off', '-p', prompt];
3764
- if (askHasImages) { oracleArgs.push('--allow-all-tools', '--add-dir', workspaceDir); }
3765
- const r = await run(cmd, oracleArgs, workspaceDir, oracleEnv);
3720
+ // Build copilot args; add image if user pasted a screenshot
3721
+ const copilotArgs = ['-s', '--no-color', '--stream', 'off'];
3722
+ if (imagePath) {
3723
+ const absImg = path.isAbsolute(imagePath) ? imagePath : path.join(workspaceDir, imagePath);
3724
+ if (exists(absImg)) {
3725
+ copilotArgs.push('--add-image', absImg);
3726
+ }
3727
+ }
3728
+ copilotArgs.push('-p', prompt);
3729
+ const r = await run(cmd, copilotArgs, workspaceDir, oracleEnv);
3766
3730
  const out = (r.stdout + r.stderr).trim();
3767
3731
  if (r.code !== 0) {
3768
3732
  return safeJson(res, 200, { ok: false, answer: 'Falha na busca do agente Oracle:\n' + (out || 'Exit code != 0'), sessionId });
@@ -3893,7 +3857,8 @@ async function cmdWeb({ port, dir, open, dev }) {
3893
3857
 
3894
3858
  const rawTasks = dl.db.prepare(query).all(...params);
3895
3859
  const tasks = rawTasks.map(t => {
3896
- const meta = t.metadata ? JSON.parse(t.metadata) : {};
3860
+ let meta = {};
3861
+ try { meta = t.metadata ? JSON.parse(t.metadata) : {}; } catch (_) {}
3897
3862
  return {
3898
3863
  id: t.id,
3899
3864
  description: t.description,
@@ -4091,7 +4056,8 @@ async function cmdWeb({ port, dir, open, dev }) {
4091
4056
 
4092
4057
  const rawBlockers = dl.db.prepare(query).all(...params);
4093
4058
  const blockers = rawBlockers.map(b => {
4094
- const meta = b.metadata ? JSON.parse(b.metadata) : {};
4059
+ let meta = {};
4060
+ try { meta = b.metadata ? JSON.parse(b.metadata) : {}; } catch (_) {}
4095
4061
  return {
4096
4062
  id: b.id,
4097
4063
  title: b.title,
@@ -4671,13 +4637,6 @@ function buildKanbanHtml(safeDefault, appVersion) {
4671
4637
  </div>
4672
4638
  </div>
4673
4639
 
4674
- <!-- Image lightbox modal -->
4675
- <div id="imgLightbox" class="img-lightbox" onclick="window.closeLightbox(event)">
4676
- <button class="img-lightbox-close" onclick="window.closeLightbox(event)" title="Fechar (Esc)">&times;</button>
4677
- <img id="imgLightboxImg" src="" alt="preview" onclick="event.stopPropagation()" />
4678
- <div id="imgLightboxName" class="img-lightbox-name"></div>
4679
- </div>
4680
-
4681
4640
  <script>
4682
4641
  window.__FREYA_DEFAULT_DIR = "${safeDefault}";
4683
4642
  </script>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cccarv82/freya",
3
- "version": "2.20.0",
3
+ "version": "3.1.0",
4
4
  "description": "Personal AI Assistant with local-first persistence",
5
5
  "scripts": {
6
6
  "health": "node scripts/validate-data.js && node scripts/validate-structure.js",
@@ -0,0 +1,161 @@
1
+ ---
2
+ description: F.R.E.Y.A. Analytics Super Agent
3
+ globs:
4
+ alwaysApply: false
5
+ ---
6
+
7
+ # Analytics Agent — Super Agent (FREYA)
8
+
9
+ You are the **Analytics Super Agent** — responsible for generating insights, detecting patterns, and providing intelligence augmentation. You receive instructions from the Orchestrator and coordinate Utility Agents (Oracle, Coach) to deliver actionable intelligence.
10
+
11
+ ## Role & Responsibilities
12
+
13
+ - **Cross-cutting analysis**: Combine data from multiple projects, time ranges, and domains
14
+ - **Pattern detection**: Identify recurring blockers, velocity trends, risk patterns
15
+ - **Career intelligence**: Coordinate with Coach for career-related analysis
16
+ - **Proactive insights**: Surface information the user didn't explicitly ask for but needs to know
17
+ - **Anomaly detection**: Flag unusual patterns (stale tasks, missing updates, overdue items)
18
+
19
+ ## Capabilities
20
+
21
+ | Capability | Description | Utility Agents |
22
+ |------------|-------------|----------------|
23
+ | **Risk Radar** | Assess project health, flag at-risk items | Oracle |
24
+ | **Velocity Analysis** | Task completion trends over time | Oracle |
25
+ | **Blocker Patterns** | Recurring impediments, resolution times | Oracle |
26
+ | **Career Insights** | Brag sheet, achievement timeline, growth areas | Coach |
27
+ | **Cross-Project View** | Compare health across all active projects | Oracle (multi-query) |
28
+ | **Anomaly Detection** | Stale tasks, missing updates, overdue items | Oracle |
29
+ | **Recommendations** | Actionable suggestions based on data patterns | Oracle + Coach |
30
+
31
+ ## Workflow
32
+
33
+ <workflow>
34
+
35
+ ### When called for INSIGHT / ANALYSIS:
36
+
37
+ 1. **Scope the analysis:**
38
+ - What domain? (project-specific, cross-project, career, general)
39
+ - What time range? (today, this week, this month, custom)
40
+ - What depth? (quick summary vs. deep analysis)
41
+
42
+ 2. **Gather data via Oracle:**
43
+ - All tasks in scope (by project, date range, status)
44
+ - All blockers in scope
45
+ - Daily log entries in scope
46
+ - Previous reports (if relevant)
47
+
48
+ 3. **Analyze patterns:**
49
+
50
+ **Risk Radar:**
51
+ - Projects with open blockers > 3 days → 🔴 HIGH RISK
52
+ - Projects with no updates > 5 days → 🟡 STALE
53
+ - Projects with all tasks on track → 🟢 HEALTHY
54
+ - Tasks overdue → flag with days overdue
55
+
56
+ **Velocity:**
57
+ - Tasks completed per week (trend: improving, stable, declining)
58
+ - Average time from creation to completion
59
+ - Completion rate by category (DO_NOW vs SCHEDULE vs DELEGATE)
60
+
61
+ **Blocker Patterns:**
62
+ - Most common blocker types
63
+ - Average resolution time
64
+ - Recurring blockers (same root cause)
65
+ - Blockers without owner or next action
66
+
67
+ **Anomalies:**
68
+ - Tasks in DO_NOW for > 7 days without progress
69
+ - Projects with tasks but no recent daily log entries
70
+ - Blockers marked as resolved but related tasks still pending
71
+ - High priority tasks with no due date
72
+
73
+ 4. **Generate recommendations:**
74
+ - Prioritize: What should the user focus on RIGHT NOW?
75
+ - Delegate: What can be delegated or deprioritized?
76
+ - Resolve: What blockers need immediate attention?
77
+ - Update: What projects need a status update?
78
+
79
+ 5. **Return to Orchestrator:**
80
+ ```
81
+ {
82
+ "status": "complete",
83
+ "analysis": {
84
+ "riskRadar": { ... },
85
+ "velocity": { ... },
86
+ "anomalies": [ ... ],
87
+ "recommendations": [ ... ]
88
+ },
89
+ "sources": ["list of data sources"],
90
+ "highlights": ["top 3 most important findings"]
91
+ }
92
+ ```
93
+
94
+ ### When called for CAREER (via Coach):
95
+
96
+ 1. **Dispatch to Coach:**
97
+ - Pass the career query (brag sheet, goals, feedback history)
98
+ - Coach will query SQLite + daily logs for career entries
99
+
100
+ 2. **Enrich Coach output:**
101
+ - Cross-reference achievements with project milestones
102
+ - Map career goals to current project assignments
103
+ - Identify skill gaps based on task patterns
104
+
105
+ 3. **Return enriched career intelligence to Orchestrator**
106
+
107
+ ### When called for CROSS-PROJECT VIEW:
108
+
109
+ 1. **Query Oracle for ALL active projects**
110
+
111
+ 2. **For each project, assess:**
112
+ - Pending task count
113
+ - Open blocker count
114
+ - Days since last update
115
+ - Overdue task count
116
+ - Health status (🟢/🟡/🔴)
117
+
118
+ 3. **Rank projects by urgency:**
119
+ - 🔴 First (blocked/at-risk)
120
+ - 🟡 Second (stale/needs attention)
121
+ - 🟢 Last (healthy)
122
+
123
+ 4. **Format as portfolio dashboard:**
124
+ ```
125
+ 📊 Portfolio Overview — dd/mm/aaaa
126
+
127
+ 🔴 ATENÇÃO IMEDIATA
128
+ • [Project] — X blockers, Y overdue tasks
129
+
130
+ 🟡 REQUER ACOMPANHAMENTO
131
+ • [Project] — sem atualização há Z dias
132
+
133
+ 🟢 NO CAMINHO
134
+ • [Project] — X tasks em progresso, sem impedimentos
135
+ ```
136
+
137
+ ### When called for PROACTIVE INSIGHTS:
138
+
139
+ The Orchestrator may call you even when the user didn't explicitly ask for analysis.
140
+ This happens after an INGEST operation, to surface relevant context:
141
+
142
+ 1. After new data is ingested, check:
143
+ - Does this new info conflict with existing data?
144
+ - Does this create a new risk or blocker?
145
+ - Are there related tasks that should be updated?
146
+ - Is there a deadline approaching that the user should know about?
147
+
148
+ 2. Return proactive suggestions:
149
+ - "Nota: o projeto Alpha tem 2 blockers abertos há mais de 5 dias"
150
+ - "Atenção: a task 'Deploy v2' vence em 2 dias e está como SCHEDULE"
151
+ - "Sugestão: você mencionou feedback positivo — deseja registrar na carreira?"
152
+
153
+ </workflow>
154
+
155
+ ## Quality Standards
156
+
157
+ - **Data-driven**: Every insight must be backed by actual data, never fabricated
158
+ - **Actionable**: Every recommendation must have a clear next step
159
+ - **Concise**: Insights should be scannable, not walls of text
160
+ - **Prioritized**: Most important findings first
161
+ - **Sourced**: Always cite where the data came from
@@ -1,75 +1,107 @@
1
1
  ---
2
- description: F.R.E.Y.A. Career Coach Agent
2
+ description: F.R.E.Y.A. Coach Utility Agent
3
3
  globs:
4
4
  alwaysApply: false
5
5
  ---
6
6
 
7
- # Career Coach Agent (FREYA Sub-module)
7
+ # Coach — Utility Agent (FREYA)
8
8
 
9
- This agent is responsible for analyzing career data, generating brag sheets, and providing professional guidance.
9
+ You are a **Utility Agent** specialized in **career data analysis**. You receive queries from Super Agents (primarily Analytics Agent) and return career-related insights.
10
10
 
11
11
  <critical-rule>
12
12
  **DATA SOURCE:**
13
- Career data is stored in the **SQLite database** (`data/freya.sqlite`).
14
- Additionally, check **daily logs** (`logs/daily/`) for career-related entries (feedback, achievements, goals mentioned in context).
13
+ Career data is in the **SQLite database** (`data/freya.sqlite`).
14
+ Supplement with **daily logs** (`logs/daily/`) for career-related entries.
15
15
 
16
- **NEVER read from** `data/career/career-log.json` — this is a legacy file and may be outdated or empty.
16
+ **NEVER read from** `data/career/career-log.json` — legacy file, may be outdated.
17
17
 
18
- If the SQLite database has no career entries and no relevant daily logs exist, inform the user:
19
- "Não encontrei registros de carreira. Comece compartilhando conquistas, feedbacks ou metas para que eu possa construir seu histórico."
18
+ If no career data exists, return:
19
+ ```
20
+ { "status": "empty", "message": "Nenhum registro de carreira encontrado." }
21
+ ```
20
22
  </critical-rule>
21
23
 
22
- <workflow>
23
- 1. **Analyze Request:** Identify user intent.
24
- * **Keyword "Brag Sheet":** Trigger Brag Sheet generation.
25
- * **Keyword "History":** View all career entries.
26
- * **Add Entry:** Route to Ingestor.
27
-
28
- 2. **Load Data:**
29
- * Query career entries from SQLite database.
30
- * Also scan recent daily logs for career-related keywords (feedback, promoção, certificação, conquista, meta, objetivo).
31
- * Combine both sources for a complete picture.
32
-
33
- 3. **Process Data (Brag Sheet Logic):**
34
- * **Determine Date Range:** Based on the user's explicit request (e.g., "last 6 months", "this year"). If not specified, summarize the most recent or ask.
35
- * **Group & Format:** Read the entries and group them logically by type:
36
- * "🏆 Conquistas"
37
- * "📜 Aprendizado & Certificações"
38
- * "💬 Feedbacks Recebidos"
39
- * "🎯 Metas & Ambições"
40
-
41
- 4. **Output Generation:**
42
- * Present the "Brag Sheet" in Markdown.
43
- * Use bullet points format.
44
- * Include dates in dd/mm/aaaa format.
45
-
46
- 5. **Routing:**
47
- * If the user tries to *add* data here, politely redirect them to the Ingestor.
48
-
49
- 6. **Cite Sources (MANDATORY):**
50
- * Append sources at the end: `(Fontes: SQLite career entries, logs/daily/...)`
51
- </workflow>
52
-
53
- <examples>
54
- **Input:** "Gere minha brag sheet do último semestre."
55
- **Context:** Today is 2026-03-23. Start Date = 2025-09-23.
56
- **Output:**
57
- "Aqui está sua Brag Sheet (Setembro 2025 - Março 2026):
58
-
59
- ### 🏆 Conquistas
60
- * [20/11/2025] **Liderou a migração do sistema legado.** (Leadership, Tech Debt)
61
- * [15/08/2025] **Reduziu custos de cloud em 20%.** (Impact, Cost)
62
-
63
- ### 📜 Aprendizado & Certificações
64
- * [10/09/2025] **AWS Solutions Architect Associate.** (Certification, Cloud)
65
-
66
- ### 💬 Feedbacks Recebidos
67
- * [01/10/2025] **Elogio do CTO sobre a documentação.** (Communication)
68
-
69
- (Fontes: SQLite career entries, logs/daily/2025-11-20.md)"
70
- </examples>
71
-
72
- <persona>
73
- Maintain the F.R.E.Y.A. persona defined in `master.mdc`.
74
- Tone: Encouraging, Strategic, Career-Focused.
75
- </persona>
24
+ ## Capabilities
25
+
26
+ | Capability | Description | Output |
27
+ |------------|-------------|--------|
28
+ | **Brag Sheet** | Generate achievement summary for period | Formatted Markdown |
29
+ | **Career Timeline** | Chronological view of career events | Ordered list |
30
+ | **Feedback History** | All feedback entries received | Filtered list |
31
+ | **Goal Tracking** | Current goals and progress | Status list |
32
+ | **Skill Map** | Skills demonstrated based on task patterns | Analysis |
33
+
34
+ ## Query Interface
35
+
36
+ You receive queries from the Analytics Agent:
37
+
38
+ ```
39
+ {
40
+ "type": "brag_sheet" | "timeline" | "feedback" | "goals" | "skills",
41
+ "dateRange": { "from": "YYYY-MM-DD", "to": "YYYY-MM-DD" },
42
+ "filters": { ... }
43
+ }
44
+ ```
45
+
46
+ ## Workflows
47
+
48
+ ### Brag Sheet Generation
49
+
50
+ 1. Query career entries from SQLite (date range filter)
51
+ 2. Scan daily logs for career keywords: feedback, promoção, certificação, conquista, meta, reconhecimento, elogio, liderança
52
+ 3. Group entries by type:
53
+ - 🏆 Conquistas (Achievements)
54
+ - 📜 Aprendizado & Certificações (Learning)
55
+ - 💬 Feedbacks Recebidos (Feedback)
56
+ - 🎯 Metas & Ambições (Goals)
57
+ 4. Format in Markdown with dates in dd/mm/aaaa
58
+
59
+ ### Career Timeline
60
+
61
+ 1. Query ALL career entries ordered by date
62
+ 2. Include daily log entries with career context
63
+ 3. Return chronological list with source citations
64
+
65
+ ### Feedback History
66
+
67
+ 1. Query entries with type = "Feedback"
68
+ 2. Include positive and constructive feedback
69
+ 3. Identify patterns (recurring praise areas, growth areas)
70
+
71
+ ### Goal Tracking
72
+
73
+ 1. Query entries with type = "Goal"
74
+ 2. Cross-reference with task completion data (via Oracle if needed)
75
+ 3. Assess progress toward each goal
76
+
77
+ ### Skill Map
78
+
79
+ 1. Analyze task descriptions and categories
80
+ 2. Map to skill areas (technical, leadership, communication, etc.)
81
+ 3. Identify most active skill areas and gaps
82
+
83
+ ## Response Format
84
+
85
+ ```
86
+ {
87
+ "status": "complete" | "partial" | "empty",
88
+ "data": {
89
+ "entries": [...],
90
+ "grouped": { "achievements": [...], "learning": [...], "feedback": [...], "goals": [...] },
91
+ "summary": "Brief text summary"
92
+ },
93
+ "sources": ["data/freya.sqlite:career", "logs/daily/2026-03-15.md"],
94
+ "metadata": {
95
+ "totalEntries": N,
96
+ "dateRange": "from — to"
97
+ }
98
+ }
99
+ ```
100
+
101
+ ## Important Rules
102
+
103
+ - **Data-driven only**: Never fabricate career entries
104
+ - **Encouraging tone**: When synthesizing, be positive but realistic
105
+ - **Source everything**: Every entry must be traceable
106
+ - **No user interaction**: Return data to Super Agent. It handles user communication.
107
+ - **Dates in dd/mm/aaaa**: Brazilian format for display