@cccarv82/freya 1.0.3

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.
@@ -0,0 +1,102 @@
1
+ ---
2
+ description: F.R.E.Y.A. Oracle Agent
3
+ globs:
4
+ alwaysApply: false
5
+ ---
6
+
7
+ # Oracle Agent (FREYA Sub-module)
8
+
9
+ This agent is responsible for retrieving information from the local knowledge base and synthesizing answers.
10
+
11
+ <critical-rule>
12
+ **ANTI-HALLUCINATION:**
13
+ You must ONLY answer based on the content of the JSON files you read.
14
+ If you cannot find the file or the data is missing, say "I have no records for this project."
15
+ Do not invent status updates.
16
+ </critical-rule>
17
+
18
+ <workflow>
19
+ 1. **Analyze Query:** Identify the target entity (Project, Client, Career topic, or Task).
20
+ * *Example:* "Status do projeto Vivo 5G" -> Target: "Vivo", "5G".
21
+ * *Example:* "O que tenho pra fazer?" -> Target: "Tasks", Filter: "DO_NOW".
22
+
23
+ 2. **Route Search:**
24
+ * **If Task Query:**
25
+ * **Keywords:** "Tarefa", "Task", "To-Do", "Fazer", "Agenda", "Delegado".
26
+ * **Target File:** `data/tasks/task-log.json`.
27
+ * **Action:** Read file directly.
28
+ * **If Project Query:**
29
+ * Proceed to Project Lookup (Glob search).
30
+ * **Strategy:** Search recursively in `data/Clients`.
31
+ * **Pattern:** `data/Clients/**/*{keyword}*/**/status.json` (Case insensitive if possible, or try multiple variations).
32
+ * **Tool:** Use `Glob` to find matching paths.
33
+ * *Example:* For "Vivo", glob `data/Clients/**/*vivo*/**/status.json`.
34
+
35
+ 3. **Read Data & Synthesize:**
36
+ * **Task Logic:**
37
+ * Read `task-log.json`.
38
+ * Accept either format `{ tasks: [...] }` or `{ schemaVersion: 1, tasks: [...] }`.
39
+ * **Filter Logic:**
40
+ * "Fazer" / "Tasks" / "To-Do" -> `category == DO_NOW` AND `status == PENDING`.
41
+ * "Agenda" / "Schedule" -> `category == SCHEDULE` AND `status == PENDING`.
42
+ * "Delegado" / "Delegate" -> `category == DELEGATE` AND `status == PENDING`.
43
+ * "Tudo" / "All" -> All `PENDING` tasks.
44
+ * **Sort:** By priority (High > Medium > Low) then Date.
45
+ * **Output Structure:**
46
+ * **Context:** "Aqui estão suas tarefas pendentes [{Category}]:"
47
+ * **List:** Bullet points.
48
+ * Format: `* [ID-Short] {Description} ({Priority})`
49
+ * **Empty:** "Você não tem tarefas pendentes nesta categoria."
50
+
51
+ * **Project Logic:**
52
+ * If matches found: Read the `status.json` file(s).
53
+ * If multiple matches: Ask user to clarify OR summarize all if they seem related.
54
+ * If no matches: Return "I have no records for this project." (or prompt to list all clients).
55
+
56
+ 4. **Synthesize Answer (Summarization - Project Only):**
57
+ * **Goal:** Provide an executive summary, not a JSON dump.
58
+ * **Parsing Logic:**
59
+ 1. Extract `currentStatus` (or `currentStatus.summary` if object).
60
+ 2. Check `active` flag.
61
+ 3. **Archival Check:**
62
+ * If `active` is `false`:
63
+ * **Check Intent:** Does the query contain words like "history", "archive", "antigo", "histórico", "passado", "old"?
64
+ * **If NO:** Return "This project is archived. Ask explicitly for 'history' to view details."
65
+ * **If YES:** Proceed to extraction, but mark as `[ARCHIVED]`.
66
+ 4. Extract the last 3 entries from the `history` array.
67
+ * **No Data Case:** If `history` is empty, state: "Project initialized but no updates recorded yet."
68
+ * **Output Structure:**
69
+ * **Context:** One sentence confirming what is being shown. **Prefix with `[ARCHIVED]` if the project is inactive.** (e.g., "[ARCHIVED] Analisei o histórico do projeto...").
70
+ * **Current Status:** The value of `currentStatus`.
71
+ * **Recent Updates:** Bullet points of the last 3 entries.
72
+ * Format: `* **YYYY-MM-DD:** {Content}`
73
+
74
+ 5. **Cite Sources (MANDATORY):**
75
+ * At the very end of the response, append the file path used.
76
+ * Format: `(Source: {filepath})`
77
+ </workflow>
78
+
79
+ <examples>
80
+ **Input:** "Como está o projeto 5G?"
81
+ **Data Found:** `status.json` with 50 entries.
82
+ **Output:**
83
+ "Contexto compreendido. Aqui está o status atual do projeto 5G.
84
+
85
+ **Status Atual:**
86
+ Em atraso devido a condições climáticas.
87
+
88
+ **Últimas Atualizações:**
89
+ * **2025-12-12:** Atraso reportado por chuva na infraestrutura.
90
+ * **2025-12-10:** Reunião de alinhamento com stakeholders.
91
+ * **2025-12-08:** Início da fase de testes.
92
+
93
+ (Source: data/Clients/vivo/5g/status.json)"
94
+ </examples>
95
+
96
+ <persona>
97
+ Maintain the F.R.E.Y.A. persona defined in `master.mdc`.
98
+ Tone: Analytical, Precise, Data-Driven.
99
+ Signature:
100
+ — FREYA
101
+ Assistente Responsiva com Otimização Aprimorada
102
+ </persona>
@@ -0,0 +1,31 @@
1
+ ---
2
+ description: F.R.E.Y.A. Entry Point
3
+ globs:
4
+ alwaysApply: false
5
+ ---
6
+
7
+ # F.R.E.Y.A. - AI Agent System
8
+
9
+ To invoke the assistant, simply type: `@freya`
10
+
11
+ <agent-entry>
12
+ 1. **Trigger:** User types `@freya` or mentions `@freya`.
13
+ 2. **Action:** Load `@.agent/rules/freya/agents/master.mdc`.
14
+ 3. **Behavior:** The Master Agent (FREYA) will interpret the request and route to the appropriate sub-module or answer directly.
15
+ </agent-entry>
16
+
17
+ <menu-display>
18
+ If the user just types `@freya` or asks for help, display:
19
+
20
+ ```
21
+ — F.R.E.Y.A. —
22
+ Assistente Responsiva com Otimização Aprimorada
23
+
24
+ Como posso ajudar você hoje?
25
+
26
+ [1] Ingest Log (Status, Blockers, Career) -> Triggers @.agent/rules/freya/agents/ingestor.mdc
27
+ [2] Oracle Query (Project History, Decisions) -> Triggers @.agent/rules/freya/agents/oracle.mdc
28
+ [3] Career Coach (Brag Sheet, Goals) -> Triggers @.agent/rules/freya/agents/coach.mdc
29
+ [4] General Assistance
30
+ ```
31
+ </menu-display>
@@ -0,0 +1,50 @@
1
+ # F.R.E.Y.A. - Fully Responsive Enhanced Yield Assistant
2
+
3
+ > **Sua Assistente de Produtividade Local-First para sua IDE.**
4
+
5
+ F.R.E.Y.A. é um sistema de agentes de IA projetado para organizar seu trabalho, gerenciar status de projetos, rastrear tarefas e registrar sua evolução de carreira, tudo através de uma interface de chat simples e direta.
6
+
7
+ ## 🌟 Principais Recursos
8
+
9
+ * **Ingestão Universal:** Registre updates, blockers e notas mentais em linguagem natural.
10
+ * **Gestão de Tarefas:** Crie, liste e conclua tarefas ("Lembre-me de fazer X", "Minhas tarefas", "Terminei X").
11
+ * **Oráculo:** Pergunte sobre o status de qualquer projeto ("Como está o projeto X?").
12
+ * **Career Coach:** Gere "Brag Sheets" automáticas para suas avaliações de desempenho.
13
+ * **Relatórios Automatizados:** Gere resumos semanais, dailies, relatório de Scrum Master e relatórios executivos.
14
+ * **Blockers & Riscos:** Gere um relatório rápido de blockers priorizados por severidade.
15
+ * **Saúde do Sistema:** Valide a integridade dos seus dados locais com um comando.
16
+ * **Git Automation:** Gere commits inteligentes automaticamente. A Freya analisa suas mudanças e escreve a mensagem para você.
17
+ * **Privacidade Total:** Seus dados (JSON e Markdown) ficam 100% locais na sua máquina.
18
+
19
+ ## 🚀 Como Usar
20
+
21
+ 1. Abra esta pasta na **sua IDE**.
22
+ 2. No chat da IDE (ex: Ctrl+L / Cmd+L), digite:
23
+ > `@freya Ajuda`
24
+ 3. Siga as instruções da assistente.
25
+
26
+ ### Comandos Rápidos
27
+ Você pode pedir para a FREYA executar estas tarefas diretamente no chat, ou rodar via terminal:
28
+
29
+ * **Checar integridade:** "Verifique a saúde do sistema" (ou `npm run health`)
30
+ * **Migrar dados (se necessário):** `npm run migrate` (adiciona `schemaVersion` em logs antigos)
31
+ * **Relatório Profissional (Executivo):** "Gere o status report" (ou `npm run status`)
32
+ * **Relatório Scrum Master (semanal):** `npm run sm-weekly`
33
+ * **Relatório de blockers:** `npm run blockers`
34
+ * **Relatório semanal (legado):** "Gere o relatório semanal" (ou `npm run report`)
35
+ * **Resumo daily (legado):** "Gere o daily" (ou `npm run daily`)
36
+
37
+ ## 📘 Documentação Completa
38
+
39
+ Para um guia detalhado de comandos e exemplos, consulte o **[Guia do Usuário](USER_GUIDE.md)** incluído nesta pasta.
40
+
41
+ ## Estrutura de Pastas
42
+
43
+ * `.agent/`: O "cérebro" da IA (Regras e Personas).
44
+ * `data/`: O "banco de dados" (JSONs dos seus projetos, tarefas e carreira).
45
+ * `logs/`: O "diário" (Histórico bruto de tudo que você digitou).
46
+ * `docs/reports/`: Relatórios gerados automaticamente.
47
+ * `scripts/`: Ferramentas de automação e validação.
48
+
49
+ ---
50
+ *F.R.E.Y.A. v1.0 - Release 2025-12-13*
@@ -0,0 +1,160 @@
1
+ # Guia do Usuário F.R.E.Y.A. v1.0
2
+
3
+ Bem-vindo à F.R.E.Y.A. (Fully Responsive Enhanced Yield Assistant).
4
+ Este sistema foi projetado para ser seu assistente pessoal de produtividade, operando diretamente no seu ambiente de desenvolvimento (IDE), com foco total em privacidade (Local-First) e eficiência.
5
+
6
+ ## 🚀 Como Iniciar
7
+
8
+ Para interagir com a assistente, basta chamá-la no chat da sua IDE:
9
+
10
+ > `@freya [sua mensagem]`
11
+
12
+ A FREYA adotará uma persona calma, analítica e proativa para ajudá-lo.
13
+
14
+ ---
15
+
16
+ ## 📝 Funcionalidades Principais
17
+
18
+ ### 1. Ingestão Universal (Logging)
19
+ Fale naturalmente. A FREYA entende contextos misturados e organiza tudo para você.
20
+
21
+ * **Atualização de Projeto:**
22
+ > "Reunião com o cliente Vivo, o projeto 5G vai atrasar por causa da chuva."
23
+ * *Resultado:* Atualiza `data/Clients/vivo/5g/status.json`.
24
+
25
+ * **Registro de Carreira:**
26
+ > "Recebi um feedback incrível do CTO sobre a apresentação de ontem."
27
+ * *Resultado:* Salva em `data/career/career-log.json` com a tag "Feedback".
28
+
29
+ * **Blockers:**
30
+ > "Estou travado na API de pagamento do Itaú."
31
+ * *Resultado:* Registra um blocker no projeto correspondente.
32
+
33
+ **Nota de Segurança:** Tudo o que você digita é salvo imediatamente em `logs/daily/YYYY-MM-DD.md` antes de qualquer processamento, garantindo que você nunca perca uma anotação.
34
+
35
+ ### 2. O Oráculo (Consulta de Status)
36
+ Recupere o contexto de qualquer projeto instantaneamente.
37
+
38
+ * **Status Rápido:**
39
+ > "Como está o projeto Vivo 5G?"
40
+ * *Resultado:* Resumo executivo do status atual e das últimas 3 atualizações.
41
+
42
+ * **Anti-Alucinação:**
43
+ A FREYA sempre citará a fonte da informação (ex: `(Source: data/Clients/vivo/5g/status.json)`). Se ela não souber, ela dirá explicitamente.
44
+
45
+ ### 3. Career Coach & Brag Sheets
46
+ Gerencie sua evolução profissional sem sair do editor.
47
+
48
+ * **Gerar Brag Sheet:**
49
+ > "Gere minha brag sheet do último semestre."
50
+ * *Resultado:* Uma lista formatada de suas conquistas, aprendizados e feedbacks, pronta para copiar e colar na sua autoavaliação.
51
+
52
+ ### 4. Gestão de Ciclo de Vida (Arquivamento)
53
+ Mantenha seu foco limpo arquivando projetos antigos.
54
+
55
+ * **Arquivar Projeto:**
56
+ > "Arquivar o projeto Vivo Legado."
57
+ * *Resultado:* O projeto para de aparecer nas buscas diárias.
58
+
59
+ * **Busca Histórica:**
60
+ > "O que fizemos no projeto antigo da Vivo?"
61
+ * *Resultado:* A FREYA busca nos arquivos mortos (identificados com `[ARCHIVED]`).
62
+
63
+ ### 5. Gestão de Tarefas
64
+ Organize seu dia-a-dia com um sistema de tarefas integrado.
65
+
66
+ * **Criar Tarefa:**
67
+ > "Lembre-me de revisar o PR #123 amanhã."
68
+ * *Resultado:* Cria uma nova tarefa pendente em `data/tasks/task-log.json`.
69
+
70
+ * **Listar Tarefas:**
71
+ > "Quais são minhas tarefas pendentes?"
72
+ * *Resultado:* Lista suas tarefas abertas, priorizando as urgentes (`DO_NOW`).
73
+
74
+ * **Concluir Tarefa:**
75
+ > "Terminei a revisão do PR #123."
76
+ * *Resultado:* Marca a tarefa como `COMPLETED` e registra a data de conclusão.
77
+
78
+ ### 6. Relatórios Automatizados
79
+ Transforme seus logs em relatórios úteis sem esforço. Peça à FREYA no chat e ela executará os scripts para você.
80
+
81
+ * **Relatório de Status Profissional (Executivo):**
82
+ > "Gerar status report", "Relatório Executivo"
83
+ * *Resultado:* Gera um relatório Markdown completo com Resumo Executivo, Entregas, Status de Projetos e Bloqueios. Ideal para enviar stakeholders.
84
+ * *Manual:* `npm run status -- --period [daily|weekly]`
85
+
86
+ * **Relatório Scrum Master (Semanal):**
87
+ > "Gerar relatório SM" ou "Relatório Scrum Master"
88
+ * *Resultado:* Gera um report semanal focado em resumo, wins, blockers/riscos e foco da próxima semana.
89
+ * *Manual:* `npm run sm-weekly`
90
+
91
+ * **Relatório de Blockers (priorizado por severidade):**
92
+ > "Gerar relatório de blockers"
93
+ * *Resultado:* Lista blockers abertos ordenados por severidade e idade, pra ficar fácil priorizar.
94
+ * *Manual:* `npm run blockers`
95
+
96
+ * **Relatório Semanal (Legado):**
97
+ > "Gerar relatório semanal"
98
+ * *Resultado:* A FREYA executa o script e avisa onde o arquivo Markdown foi salvo em `docs/reports/`, exibindo um resumo.
99
+ * *Manual:* `npm run report`
100
+
101
+ * **Resumo Daily (Legado):**
102
+ > "Gerar daily" ou "Resumo diário"
103
+ * *Resultado:* A FREYA gera e exibe o texto "Ontem / Hoje / Bloqueios" diretamente no chat.
104
+ * *Manual:* `npm run daily`
105
+
106
+ ### 7. Migração de Dados (schemaVersion)
107
+ Se você atualizou a FREYA e tem logs antigos, rode a migração para padronizar os JSONs.
108
+
109
+ * **Migrar dados:**
110
+ > `npm run migrate`
111
+ * *Resultado:* adiciona `schemaVersion` aos arquivos conhecidos (`task-log.json`, `career-log.json`, `blocker-log.json`).
112
+ * *Segurança:* se algum JSON estiver corrompido, ele é movido para quarentena (não é perdido).
113
+
114
+ ### 8. Saúde do Sistema
115
+ Garanta que seus dados locais estão íntegros.
116
+
117
+ * **Health Check:**
118
+ > "Checar saúde do sistema" ou "Verificar integridade"
119
+ * *Resultado:* A FREYA roda o diagnóstico e reporta se todos os JSONs estão válidos ou se há erros para corrigir.
120
+ * *Manual:* `npm run health`
121
+
122
+ ### 9. Git Automation
123
+ Deixe a Freya cuidar do versionamento básico do seu código.
124
+
125
+ * **Auto-Commit:**
126
+ > "Salvar alterações", "Gerar commit" ou "Commitar"
127
+ * *Resultado:* A Freya executa `git status`, analisa o `git diff` para entender o que mudou, gera uma mensagem de commit semântica e realiza o commit (`git add .` + `git commit`).
128
+ * *Nota:* Ela sempre pedirá confirmação ou avisará se não houver mudanças.
129
+
130
+ ### 10. Detecção Implícita de Tarefas
131
+ A Freya agora entende suas intenções futuras sem precisar de comandos explícitos.
132
+
133
+ * **Detecção Inteligente:**
134
+ > "O projeto X atrasou porque *preciso configurar o servidor*."
135
+ * *Resultado:* A Freya cria automaticamente a tarefa "Configurar o servidor" e a vincula ao projeto X.
136
+ * *Palavras-chave:* "preciso", "tenho que", "falta", "vou", "pendente".
137
+
138
+ ---
139
+
140
+ ## 💡 Dicas de Uso
141
+
142
+ 1. **Seja Específico:** Mencionar o nome do Cliente ou Projeto ajuda a FREYA a categorizar corretamente.
143
+ 2. **Múltiplos Contextos:** Você pode misturar assuntos:
144
+ > "O projeto Alpha está verde, mas preciso estudar Kubernetes para minha certificação."
145
+ (Ela vai atualizar o projeto Alpha E adicionar uma meta de estudo no seu log de carreira).
146
+ 3. **Idioma:** A FREYA responde nativamente em **Português (Brasil)**. Se precisar de inglês, basta pedir: "Switch to English please".
147
+
148
+ ---
149
+
150
+ ## 📂 Onde estão meus dados?
151
+
152
+ Tudo fica no seu computador, dentro da pasta do projeto:
153
+ * `data/Clients/`: Histórico dos projetos.
154
+ * `data/career/`: Seu log de carreira.
155
+ * `data/tasks/`: Seu log de tarefas.
156
+ * `logs/daily/`: Log bruto diário (Markdown).
157
+ * `docs/reports/`: Relatórios gerados.
158
+
159
+ ---
160
+ *F.R.E.Y.A. — Assistente Responsiva com Otimização Aprimorada*
@@ -0,0 +1,4 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "blockers": []
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "entries": []
4
+ }
@@ -0,0 +1,66 @@
1
+ # F.R.E.Y.A. Data Schemas
2
+
3
+ This document defines the JSON structure for the local knowledge base.
4
+
5
+ ## Career Log (`data/career/career-log.json`)
6
+
7
+ Stores professional achievements, feedback, and certifications.
8
+
9
+ ```json
10
+ {
11
+ "entries": [
12
+ {
13
+ "id": "uuid-v4",
14
+ "date": "YYYY-MM-DD",
15
+ "type": "Achievement" | "Feedback" | "Certification" | "Goal",
16
+ "description": "Text description of the item.",
17
+ "tags": ["tag1", "tag2"],
18
+ "source": "Optional origin (e.g. 'Meeting with X')"
19
+ }
20
+ ]
21
+ }
22
+ ```
23
+
24
+ ## Project Status (`data/Clients/{client_slug}/{project_slug}/status.json`)
25
+
26
+ Stores the ongoing status and history of a project.
27
+
28
+ ```json
29
+ {
30
+ "client": "String (Display Name, e.g., 'Vivo')",
31
+ "project": "String (Display Name, e.g., '5G')",
32
+ "active": true,
33
+ "archivedAt": "YYYY-MM-DD (Optional, present if active=false)",
34
+ "currentStatus": "String (Latest status summary)",
35
+ "lastUpdated": "YYYY-MM-DD",
36
+ "history": [
37
+ {
38
+ "date": "YYYY-MM-DD",
39
+ "type": "Status" | "Decision" | "Risk" | "Blocker",
40
+ "content": "String (The update details)",
41
+ "tags": ["String"]
42
+ }
43
+ ]
44
+ }
45
+ ```
46
+
47
+ ## Task Log (`data/tasks/task-log.json`)
48
+
49
+ Centralized storage for personal tasks and to-dos.
50
+
51
+ ```json
52
+ {
53
+ "tasks": [
54
+ {
55
+ "id": "String (UUID or timestamp-slug)",
56
+ "description": "String",
57
+ "category": "DO_NOW" | "SCHEDULE" | "DELEGATE" | "IGNORE",
58
+ "status": "PENDING" | "COMPLETED" | "ARCHIVED",
59
+ "createdAt": "YYYY-MM-DDTHH:mm:ssZ",
60
+ "completedAt": "YYYY-MM-DDTHH:mm:ssZ (Optional)",
61
+ "projectSlug": "String (Optional, link to project)",
62
+ "priority": "high" | "medium" | "low" (Optional)
63
+ }
64
+ ]
65
+ }
66
+ ```
@@ -0,0 +1,4 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "tasks": []
4
+ }
@@ -0,0 +1,215 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ const { toIsoDate, safeParseToMs, isWithinRange } = require('./lib/date-utils');
5
+
6
+ const BLOCKERS_FILE = path.join(__dirname, '../data/blockers/blocker-log.json');
7
+ const REPORT_DIR = path.join(__dirname, '../docs/reports');
8
+
9
+ const SEVERITY_ORDER = {
10
+ CRITICAL: 0,
11
+ HIGH: 1,
12
+ MEDIUM: 2,
13
+ LOW: 3,
14
+ };
15
+
16
+ const RESOLVED_STATUSES = new Set(['RESOLVED', 'CLOSED', 'DONE', 'FIXED']);
17
+
18
+ function normalizeStatus(blocker) {
19
+ const raw = blocker.status || blocker.state || blocker.currentStatus;
20
+ if (!raw) return 'UNKNOWN';
21
+ return String(raw).trim().toUpperCase();
22
+ }
23
+
24
+ function normalizeSeverity(blocker) {
25
+ const raw = blocker.severity || blocker.priority || blocker.level;
26
+ if (!raw) return 'UNSPECIFIED';
27
+ const value = String(raw).trim().toUpperCase();
28
+ if (value.includes('CRIT')) return 'CRITICAL';
29
+ if (value.includes('HIGH')) return 'HIGH';
30
+ if (value.includes('MED')) return 'MEDIUM';
31
+ if (value.includes('LOW')) return 'LOW';
32
+ return value;
33
+ }
34
+
35
+ function getCreatedAt(blocker) {
36
+ const candidates = [
37
+ blocker.createdAt,
38
+ blocker.created_at,
39
+ blocker.openedAt,
40
+ blocker.opened_at,
41
+ blocker.reportedAt,
42
+ blocker.reported_at,
43
+ blocker.date,
44
+ blocker.loggedAt,
45
+ ];
46
+ for (const value of candidates) {
47
+ const ms = safeParseToMs(value);
48
+ if (Number.isFinite(ms)) return ms;
49
+ }
50
+ return NaN;
51
+ }
52
+
53
+ function getResolvedAt(blocker) {
54
+ const candidates = [
55
+ blocker.resolvedAt,
56
+ blocker.resolved_at,
57
+ blocker.closedAt,
58
+ blocker.closed_at,
59
+ blocker.completedAt,
60
+ ];
61
+ for (const value of candidates) {
62
+ const ms = safeParseToMs(value);
63
+ if (Number.isFinite(ms)) return ms;
64
+ }
65
+ return NaN;
66
+ }
67
+
68
+ function isOpen(blocker) {
69
+ const status = normalizeStatus(blocker);
70
+ if (RESOLVED_STATUSES.has(status)) return false;
71
+ const resolvedAt = getResolvedAt(blocker);
72
+ return !Number.isFinite(resolvedAt);
73
+ }
74
+
75
+ function getBlockerTitle(blocker) {
76
+ return (
77
+ blocker.title ||
78
+ blocker.summary ||
79
+ blocker.description ||
80
+ blocker.content ||
81
+ blocker.text ||
82
+ 'Untitled blocker'
83
+ );
84
+ }
85
+
86
+ function formatAgeDays(createdMs, nowMs) {
87
+ if (!Number.isFinite(createdMs)) return null;
88
+ const ageMs = Math.max(0, nowMs - createdMs);
89
+ return Math.floor(ageMs / (24 * 60 * 60 * 1000));
90
+ }
91
+
92
+ function ensureReportDir() {
93
+ if (!fs.existsSync(REPORT_DIR)) {
94
+ fs.mkdirSync(REPORT_DIR, { recursive: true });
95
+ }
96
+ }
97
+
98
+ function loadBlockers() {
99
+ if (!fs.existsSync(BLOCKERS_FILE)) {
100
+ return [];
101
+ }
102
+ try {
103
+ const raw = fs.readFileSync(BLOCKERS_FILE, 'utf8');
104
+ const data = JSON.parse(raw);
105
+ return Array.isArray(data.blockers) ? data.blockers : [];
106
+ } catch (err) {
107
+ console.error(`Error reading blockers file: ${err.message}`);
108
+ return [];
109
+ }
110
+ }
111
+
112
+ function generateReport() {
113
+ const now = new Date();
114
+ const nowMs = now.getTime();
115
+ const reportDate = toIsoDate(now);
116
+ const blockers = loadBlockers();
117
+
118
+ const statusCounts = new Map();
119
+ blockers.forEach(blocker => {
120
+ const status = normalizeStatus(blocker);
121
+ statusCounts.set(status, (statusCounts.get(status) || 0) + 1);
122
+ });
123
+
124
+ const openBlockers = blockers.filter(isOpen);
125
+ openBlockers.sort((a, b) => {
126
+ const severityA = normalizeSeverity(a);
127
+ const severityB = normalizeSeverity(b);
128
+ const rankA = SEVERITY_ORDER[severityA] ?? 99;
129
+ const rankB = SEVERITY_ORDER[severityB] ?? 99;
130
+ if (rankA !== rankB) return rankA - rankB;
131
+ const ageA = getCreatedAt(a);
132
+ const ageB = getCreatedAt(b);
133
+ const msA = Number.isFinite(ageA) ? ageA : Number.MAX_SAFE_INTEGER;
134
+ const msB = Number.isFinite(ageB) ? ageB : Number.MAX_SAFE_INTEGER;
135
+ return msA - msB;
136
+ });
137
+
138
+ const sevenDaysAgo = new Date(nowMs - 7 * 24 * 60 * 60 * 1000);
139
+ const resolvedRecent = blockers.filter(blocker => {
140
+ const resolvedAt = getResolvedAt(blocker);
141
+ if (!Number.isFinite(resolvedAt)) return false;
142
+ return isWithinRange(resolvedAt, sevenDaysAgo, now);
143
+ });
144
+ resolvedRecent.sort((a, b) => {
145
+ const msA = getResolvedAt(a);
146
+ const msB = getResolvedAt(b);
147
+ return msB - msA;
148
+ });
149
+
150
+ let report = `# Blockers Report - ${reportDate}\n\n`;
151
+ report += '## Summary\n';
152
+ report += `- Total blockers: ${blockers.length}\n`;
153
+ if (statusCounts.size === 0) {
154
+ report += '- Status counts: None\n\n';
155
+ } else {
156
+ report += '- Status counts:\n';
157
+ const statuses = Array.from(statusCounts.keys()).sort();
158
+ statuses.forEach(status => {
159
+ report += ` - ${status}: ${statusCounts.get(status)}\n`;
160
+ });
161
+ report += '\n';
162
+ }
163
+
164
+ report += '## Open Blockers\n';
165
+ if (openBlockers.length === 0) {
166
+ report += 'None.\n\n';
167
+ } else {
168
+ openBlockers.forEach(blocker => {
169
+ const title = getBlockerTitle(blocker);
170
+ const status = normalizeStatus(blocker);
171
+ const severity = normalizeSeverity(blocker);
172
+ const createdMs = getCreatedAt(blocker);
173
+ const createdDate = Number.isFinite(createdMs) ? toIsoDate(createdMs) : 'Unknown';
174
+ const ageDays = formatAgeDays(createdMs, nowMs);
175
+ const project = blocker.project || blocker.projectName || blocker.projectSlug;
176
+ const client = blocker.client || blocker.clientName || blocker.clientSlug;
177
+ const metaParts = [
178
+ `Status: ${status}`,
179
+ project ? `Project: ${project}` : null,
180
+ client ? `Client: ${client}` : null,
181
+ `Created: ${createdDate}`,
182
+ ageDays === null ? null : `Age: ${ageDays}d`,
183
+ ].filter(Boolean);
184
+ report += `- [${severity}] ${title} (${metaParts.join('; ')})\n`;
185
+ });
186
+ report += '\n';
187
+ }
188
+
189
+ report += '## Resolved Blockers (Last 7 Days)\n';
190
+ if (resolvedRecent.length === 0) {
191
+ report += 'None.\n';
192
+ } else {
193
+ resolvedRecent.forEach(blocker => {
194
+ const title = getBlockerTitle(blocker);
195
+ const severity = normalizeSeverity(blocker);
196
+ const resolvedMs = getResolvedAt(blocker);
197
+ const resolvedDate = Number.isFinite(resolvedMs) ? toIsoDate(resolvedMs) : 'Unknown';
198
+ const project = blocker.project || blocker.projectName || blocker.projectSlug;
199
+ const client = blocker.client || blocker.clientName || blocker.clientSlug;
200
+ const metaParts = [
201
+ project ? `Project: ${project}` : null,
202
+ client ? `Client: ${client}` : null,
203
+ `Resolved: ${resolvedDate}`,
204
+ ].filter(Boolean);
205
+ report += `- [${severity}] ${title} (${metaParts.join('; ')})\n`;
206
+ });
207
+ }
208
+
209
+ ensureReportDir();
210
+ const outputPath = path.join(REPORT_DIR, `blockers-${reportDate}.md`);
211
+ fs.writeFileSync(outputPath, report);
212
+ console.log(report);
213
+ }
214
+
215
+ generateReport();