grimoire-framework 1.2.0 → 1.3.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.
@@ -7,8 +7,8 @@
7
7
  # - SHA256 hashes for change detection
8
8
  # - File types for categorization
9
9
  #
10
- version: 1.2.0
11
- generated_at: "2026-02-22T16:41:00.136Z"
10
+ version: 1.3.0
11
+ generated_at: "2026-02-22T16:50:35.905Z"
12
12
  generator: scripts/generate-install-manifest.js
13
13
  file_count: 1011
14
14
  files:
@@ -0,0 +1,200 @@
1
+ /**
2
+ * grimoire report — Daily / Weekly Consolidated Report
3
+ *
4
+ * grimoire report Relatório de hoje
5
+ * grimoire report --period week Última semana (7 dias)
6
+ * grimoire report --period all Histórico completo
7
+ * grimoire report --md Exporta para .grimoire/reports/YYYY-MM-DD.md
8
+ */
9
+
10
+ 'use strict';
11
+
12
+ const path = require('path');
13
+ const fs = require('fs');
14
+
15
+ function findGrimoireDir() {
16
+ const cwd = process.cwd();
17
+ const direct = path.join(cwd, '.grimoire');
18
+ const sub = path.join(cwd, 'grimoire', '.grimoire');
19
+ if (fs.existsSync(direct)) return direct;
20
+ if (fs.existsSync(sub)) return sub;
21
+ return null;
22
+ }
23
+
24
+ // ── Date helpers ───────────────────────────────────────────────────────────────
25
+ function today() { return new Date().toISOString().split('T')[0]; }
26
+ function daysAgo(n) {
27
+ const d = new Date();
28
+ d.setDate(d.getDate() - n);
29
+ return d.toISOString().split('T')[0];
30
+ }
31
+ function dateRange(period) {
32
+ if (period === 'week') return { from: daysAgo(6), to: today(), label: 'semana' };
33
+ if (period === 'month') return { from: daysAgo(29), to: today(), label: 'mês' };
34
+ return { from: '2000-01-01', to: today(), label: 'histórico' };
35
+ }
36
+
37
+ // ── Helpers: read JSONL sessions ───────────────────────────────────────────────
38
+ function readSessionEntries(grimoireDir, from, to) {
39
+ const sessionsDir = path.join(grimoireDir, 'memory', 'sessions');
40
+ if (!fs.existsSync(sessionsDir)) return [];
41
+ const files = fs.readdirSync(sessionsDir)
42
+ .filter(f => f.endsWith('.jsonl') && f.slice(0, 10) >= from && f.slice(0, 10) <= to);
43
+ const entries = [];
44
+ for (const f of files) {
45
+ const raw = fs.readFileSync(path.join(sessionsDir, f), 'utf8');
46
+ for (const line of raw.split('\n').filter(l => l.trim())) {
47
+ try { entries.push({ date: f.slice(0, 10), ...JSON.parse(line) }); } catch (_) { }
48
+ }
49
+ }
50
+ return entries;
51
+ }
52
+
53
+ // ── Helpers: read metrics JSONL ────────────────────────────────────────────────
54
+ function readMetricsEntries(grimoireDir, from, to) {
55
+ const metricsDir = path.join(grimoireDir, 'metrics');
56
+ if (!fs.existsSync(metricsDir)) return [];
57
+ const files = fs.readdirSync(metricsDir)
58
+ .filter(f => f.endsWith('.jsonl') && f.slice(0, 10) >= from && f.slice(0, 10) <= to);
59
+ const entries = [];
60
+ for (const f of files) {
61
+ const raw = fs.readFileSync(path.join(metricsDir, f), 'utf8');
62
+ for (const line of raw.split('\n').filter(l => l.trim())) {
63
+ try { entries.push({ date: f.slice(0, 10), ...JSON.parse(line) }); } catch (_) { }
64
+ }
65
+ }
66
+ return entries;
67
+ }
68
+
69
+ // ── Read stories ───────────────────────────────────────────────────────────────
70
+ function readStories(grimoireDir, from, to) {
71
+ const storiesDir = path.join(grimoireDir, 'stories');
72
+ if (!fs.existsSync(storiesDir)) return [];
73
+ return fs.readdirSync(storiesDir)
74
+ .filter(f => f.endsWith('.json'))
75
+ .map(f => { try { return JSON.parse(fs.readFileSync(path.join(storiesDir, f), 'utf8')); } catch (_) { return null; } })
76
+ .filter(Boolean)
77
+ .filter(s => s.createdAt && s.createdAt.slice(0, 10) >= from);
78
+ }
79
+
80
+ // ── Build report object ────────────────────────────────────────────────────────
81
+ function buildReport(grimoireDir, period) {
82
+ const { from, to, label } = dateRange(period);
83
+ const memEntries = readSessionEntries(grimoireDir, from, to);
84
+ const metricEntries = readMetricsEntries(grimoireDir, from, to);
85
+ const stories = readStories(grimoireDir, from, to);
86
+
87
+ // Tag buckets
88
+ const byTag = {};
89
+ for (const e of memEntries) {
90
+ if (e.tag) {
91
+ byTag[e.tag] = byTag[e.tag] || [];
92
+ byTag[e.tag].push(e);
93
+ }
94
+ }
95
+
96
+ // Agent usage from metrics
97
+ const agentUse = {};
98
+ for (const e of metricEntries) {
99
+ if (e.agent) agentUse[e.agent] = (agentUse[e.agent] || 0) + 1;
100
+ if (e.type === 'agent_session' && e.agent) agentUse[e.agent] = (agentUse[e.agent] || 0) + 1;
101
+ }
102
+ const topAgents = Object.entries(agentUse).sort((a, b) => b[1] - a[1]).slice(0, 5);
103
+
104
+ // Metrics summary
105
+ const sessions = metricEntries.filter(e => e.type === 'session_start' || e.type === 'session' || e.type === 'agent_session').length;
106
+ const commits = metricEntries.filter(e => e.type === 'commit').length;
107
+ const done = stories.filter(s => s.status === 'done').length;
108
+ const open = stories.filter(s => s.status !== 'done').length;
109
+
110
+ return { from, to, label, memEntries, byTag, topAgents, sessions, commits, stories, done, open };
111
+ }
112
+
113
+ // ── Render text report ─────────────────────────────────────────────────────────
114
+ function renderReport(r) {
115
+ const lines = [];
116
+ const sep = '═'.repeat(50);
117
+ lines.push(`\n📊 Grimoire Report — ${r.to} (${r.label}: ${r.from} → ${r.to})`);
118
+ lines.push(sep);
119
+
120
+ // Metrics
121
+ lines.push('\n📈 Métricas:');
122
+ lines.push(` Sessões: ${r.sessions} Commits: ${r.commits} Stories: ${r.done} concluídas / ${r.open} abertas`);
123
+ if (r.topAgents.length) {
124
+ lines.push(` Agentes: ${r.topAgents.map(([a, n]) => `@${a} (${n}×)`).join(' · ')}`);
125
+ }
126
+
127
+ // Memory by tag
128
+ const tagKeys = Object.keys(r.byTag);
129
+ if (tagKeys.length > 0) {
130
+ lines.push('\n🧠 Memória por tag:');
131
+ for (const tag of tagKeys) {
132
+ lines.push(`\n #${tag}:`);
133
+ for (const e of r.byTag[tag].slice(0, 5)) {
134
+ lines.push(` ${e.date} ${e.content}`);
135
+ }
136
+ }
137
+ }
138
+
139
+ // Untagged entries summary
140
+ const untagged = r.memEntries.filter(e => !e.tag);
141
+ if (untagged.length) {
142
+ lines.push(`\n🗒️ Memórias sem tag: ${untagged.length} entradas`);
143
+ untagged.slice(0, 3).forEach(e => lines.push(` ${e.date} ${e.content}`));
144
+ if (untagged.length > 3) lines.push(` ... e mais ${untagged.length - 3}`);
145
+ }
146
+
147
+ // Stories
148
+ if (r.stories.length > 0) {
149
+ lines.push('\n📋 Stories:');
150
+ r.stories.filter(s => s.status === 'done').forEach(s =>
151
+ lines.push(` ✅ [${s.id}] ${s.title} — concluída`)
152
+ );
153
+ r.stories.filter(s => s.status !== 'done').forEach(s =>
154
+ lines.push(` 🔄 [${s.id}] ${s.title} — em andamento`)
155
+ );
156
+ }
157
+
158
+ if (r.memEntries.length === 0 && r.sessions === 0 && r.stories.length === 0) {
159
+ lines.push('\n (nenhuma atividade registrada no período)');
160
+ lines.push(' Dica: grimoire memory save "texto" · grimoire metrics track session');
161
+ }
162
+
163
+ lines.push('\n' + sep + '\n');
164
+ return lines.join('\n');
165
+ }
166
+
167
+ // ── Main run ───────────────────────────────────────────────────────────────────
168
+ function run(args) {
169
+ const grimoireDir = findGrimoireDir();
170
+ if (!grimoireDir) {
171
+ console.error('❌ .grimoire/ not found. Run: npx grimoire-framework install');
172
+ return;
173
+ }
174
+
175
+ const periodIdx = args.findIndex(a => a === '--period' || a.startsWith('--period='));
176
+ let period = 'day';
177
+ if (periodIdx !== -1) {
178
+ period = args[periodIdx].includes('=')
179
+ ? args[periodIdx].split('=')[1]
180
+ : (args[periodIdx + 1] || 'day');
181
+ } else if (args.includes('week')) {
182
+ period = 'week';
183
+ }
184
+
185
+ const exportMd = args.includes('--md');
186
+ const report = buildReport(grimoireDir, period);
187
+ const text = renderReport(report);
188
+
189
+ console.log(text);
190
+
191
+ if (exportMd) {
192
+ const reportsDir = path.join(grimoireDir, 'reports');
193
+ if (!fs.existsSync(reportsDir)) fs.mkdirSync(reportsDir, { recursive: true });
194
+ const filename = path.join(reportsDir, `${report.to}-${period}.md`);
195
+ fs.writeFileSync(filename, text.replace(/\n/g, '\n'), 'utf8');
196
+ console.log(`✅ Relatório exportado: ${path.relative(process.cwd(), filename)}\n`);
197
+ }
198
+ }
199
+
200
+ module.exports = { run };
@@ -0,0 +1,217 @@
1
+ /**
2
+ * grimoire session — Session Bootstrap Command
3
+ *
4
+ * grimoire session start Gera prompt de início de sessão
5
+ * grimoire session start --squad <squad> Com squad específico
6
+ * grimoire session start --copy (legacy alias)
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const path = require('path');
12
+ const fs = require('fs');
13
+
14
+ function findGrimoireDir() {
15
+ const cwd = process.cwd();
16
+ const direct = path.join(cwd, '.grimoire');
17
+ const sub = path.join(cwd, 'grimoire', '.grimoire');
18
+ if (fs.existsSync(direct)) return direct;
19
+ if (fs.existsSync(sub)) return sub;
20
+ return null;
21
+ }
22
+
23
+ function findSquadsDir() {
24
+ const cwd = process.cwd();
25
+ const home = path.join(cwd, '.grimoire', 'squads');
26
+ const local = path.join(cwd, 'squads');
27
+ const pkg = path.join(cwd, 'node_modules', 'grimoire-framework', 'squads');
28
+ if (fs.existsSync(home)) return home;
29
+ if (fs.existsSync(local)) return local;
30
+ if (fs.existsSync(pkg)) return pkg;
31
+ return null;
32
+ }
33
+
34
+ function readSquadYaml(squadDir) {
35
+ const yamlFile = path.join(squadDir, 'squad.yaml');
36
+ if (!fs.existsSync(yamlFile)) return null;
37
+ const raw = fs.readFileSync(yamlFile, 'utf8');
38
+ const squad = { agents: [] };
39
+ for (const line of raw.split('\n')) {
40
+ const s = line.trim();
41
+ if (s.startsWith('name:')) squad.name = s.slice(5).trim().replace(/^"|"$/g, '');
42
+ if (s.startsWith('description:')) squad.description = s.slice(12).trim().replace(/^"|"$/g, '');
43
+ if (s.startsWith('- ') && squad._inAgents) squad.agents.push(s.slice(2).trim());
44
+ if (s === 'agents:') squad._inAgents = true;
45
+ else if (s.endsWith(':')) squad._inAgents = false;
46
+ }
47
+ delete squad._inAgents;
48
+ return squad;
49
+ }
50
+
51
+ function getLastMemoryEntries(grimoireDir, tag, n = 3) {
52
+ const sessionsDir = path.join(grimoireDir, 'memory', 'sessions');
53
+ if (!fs.existsSync(sessionsDir)) return [];
54
+ const files = fs.readdirSync(sessionsDir).filter(f => f.endsWith('.jsonl')).sort().reverse();
55
+ const results = [];
56
+ for (const f of files) {
57
+ if (results.length >= n) break;
58
+ const raw = fs.readFileSync(path.join(sessionsDir, f), 'utf8');
59
+ const lines = raw.split('\n').filter(l => l.trim()).reverse();
60
+ for (const line of lines) {
61
+ if (results.length >= n) break;
62
+ try {
63
+ const e = JSON.parse(line);
64
+ if (!tag || e.tag === tag) results.push(e);
65
+ } catch (_) { }
66
+ }
67
+ }
68
+ return results;
69
+ }
70
+
71
+ function loadConfig(grimoireDir) {
72
+ const file = path.join(grimoireDir, 'config.yaml');
73
+ if (!fs.existsSync(file)) return {};
74
+ const result = {};
75
+ for (const line of fs.readFileSync(file, 'utf8').split('\n')) {
76
+ const s = line.trim();
77
+ if (!s || s.startsWith('#')) continue;
78
+ const i = s.indexOf(':');
79
+ if (i === -1) continue;
80
+ let val = s.slice(i + 1).trim();
81
+ if (val === 'true') val = true;
82
+ else if (val === 'false') val = false;
83
+ result[s.slice(0, i).trim()] = val;
84
+ }
85
+ return result;
86
+ }
87
+
88
+ // ── run ───────────────────────────────────────────────────────────────────────
89
+ function run(args) {
90
+ const sub = args[0];
91
+ if (sub === 'start' || !sub) {
92
+ sessionStart(args.slice(sub === 'start' ? 1 : 0));
93
+ } else {
94
+ console.log('Usage: grimoire session start [--squad <squad>]\n');
95
+ }
96
+ }
97
+
98
+ function sessionStart(args) {
99
+ const grimoireDir = findGrimoireDir();
100
+ if (!grimoireDir) {
101
+ console.error('❌ .grimoire/ not found. Run: npx grimoire-framework install');
102
+ return;
103
+ }
104
+
105
+ // Detect project name
106
+ let projectName = '';
107
+ try {
108
+ const pkg = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf8'));
109
+ projectName = pkg.name || '';
110
+ } catch (_) { }
111
+
112
+ // Squad
113
+ const squadIdx = args.findIndex(a => a === '--squad' || a.startsWith('--squad='));
114
+ let squadName = null;
115
+ if (squadIdx !== -1) {
116
+ squadName = args[squadIdx].includes('=') ? args[squadIdx].split('=')[1] : args[squadIdx + 1];
117
+ }
118
+ // Fallback to config
119
+ const config = loadConfig(grimoireDir);
120
+ if (!squadName && config.default_squad) squadName = config.default_squad;
121
+
122
+ let squadInfo = null;
123
+ if (squadName) {
124
+ const squadsDir = findSquadsDir();
125
+ if (squadsDir && fs.existsSync(path.join(squadsDir, squadName))) {
126
+ squadInfo = readSquadYaml(path.join(squadsDir, squadName));
127
+ if (squadInfo) squadInfo.id = squadName;
128
+ }
129
+ }
130
+
131
+ // Active stories
132
+ let activeStories = [];
133
+ try {
134
+ const { getActiveStories } = require('./story');
135
+ activeStories = getActiveStories(grimoireDir).slice(0, 5);
136
+ } catch (_) { }
137
+
138
+ // Recent memory by tag
139
+ const decisions = getLastMemoryEntries(grimoireDir, 'decisão', 3);
140
+ const patterns = getLastMemoryEntries(grimoireDir, 'padrão', 2);
141
+
142
+ // Default agent
143
+ const defaultAgent = config.default_agent || 'dev';
144
+
145
+ // Build prompt
146
+ const now = new Date();
147
+ const dateStr = now.toLocaleDateString('pt-BR');
148
+ const timeStr = now.toTimeString().slice(0, 5);
149
+ const sep = '═'.repeat(52);
150
+
151
+ const lines = [];
152
+ lines.push(`${sep}`);
153
+ lines.push(`🚀 INÍCIO DE SESSÃO — ${projectName || 'projeto'}`);
154
+ lines.push(`Data: ${dateStr} ${timeStr}${squadInfo ? ` | Squad: ${squadInfo.id}` : ''}`);
155
+ lines.push('');
156
+
157
+ if (squadInfo) {
158
+ const agentList = squadInfo.agents.map(a => `@${a}`).join(' · ');
159
+ lines.push(`Squad ativo: ${agentList}`);
160
+ } else {
161
+ lines.push(`Agente padrão: @${defaultAgent}`);
162
+ }
163
+
164
+ if (config.metrics_enabled !== false) lines.push('Métricas: ✅ ativas');
165
+ if (config.hooks_enabled) lines.push('Hooks: ✅ instalado');
166
+ lines.push('');
167
+
168
+ if (activeStories.length > 0) {
169
+ lines.push('Stories abertas:');
170
+ activeStories.forEach(s => lines.push(` [IN PROGRESS] [${s.id}] — ${s.title}`));
171
+ lines.push('');
172
+ }
173
+
174
+ if (decisions.length > 0) {
175
+ lines.push('Últimas decisões (#decisão):');
176
+ decisions.forEach(d => lines.push(` - ${d.content}`));
177
+ lines.push('');
178
+ }
179
+
180
+ if (patterns.length > 0) {
181
+ lines.push('Padrões ativos (#padrão):');
182
+ patterns.forEach(p => lines.push(` - ${p.content}`));
183
+ lines.push('');
184
+ }
185
+
186
+ const firstStory = activeStories[0];
187
+ const firstAgent = squadInfo?.agents[0] || defaultAgent;
188
+ if (firstStory) {
189
+ lines.push(`Contexto: Continue o desenvolvimento com base nas stories abertas.`);
190
+ lines.push(`Para iniciar: @${firstAgent} apresente o estado atual de [${firstStory.id}] ${firstStory.title}.`);
191
+ } else {
192
+ lines.push(`Para iniciar: @${firstAgent} apresente-se e sugira o próximo passo.`);
193
+ }
194
+
195
+ lines.push(sep);
196
+
197
+ const prompt = lines.join('\n');
198
+
199
+ console.log('\n📋 Prompt de sessão (copie e cole no chat da IDE):\n');
200
+ console.log(prompt);
201
+ console.log('\n💡 Atalhos:');
202
+ console.log(' grimoire story create "título" ← criar nova story');
203
+ console.log(' grimoire memory save "observação" ← salvar decisão');
204
+ console.log(' grimoire report --period week ← relatório da semana\n');
205
+
206
+ // Track session start in metrics
207
+ try {
208
+ const metricsDir = path.join(grimoireDir, 'metrics');
209
+ if (fs.existsSync(metricsDir)) {
210
+ const today = now.toISOString().split('T')[0];
211
+ const mFile = path.join(metricsDir, `${today}.jsonl`);
212
+ fs.appendFileSync(mFile, JSON.stringify({ timestamp: now.toISOString(), type: 'session_start', squad: squadName || null }) + '\n');
213
+ }
214
+ } catch (_) { }
215
+ }
216
+
217
+ module.exports = { run };
@@ -0,0 +1,215 @@
1
+ /**
2
+ * grimoire story — Story Tracking CLI
3
+ *
4
+ * grimoire story create "Título da story" Cria nova story
5
+ * grimoire story list Lista stories ativas
6
+ * grimoire story list --all Lista todas (inclui concluídas)
7
+ * grimoire story done <id> Marca story como concluída
8
+ * grimoire story show <id> Detalhes da story
9
+ * grimoire story delete <id> Remove story
10
+ */
11
+
12
+ 'use strict';
13
+
14
+ const path = require('path');
15
+ const fs = require('fs');
16
+
17
+ function findGrimoireDir() {
18
+ const cwd = process.cwd();
19
+ const direct = path.join(cwd, '.grimoire');
20
+ const sub = path.join(cwd, 'grimoire', '.grimoire');
21
+ if (fs.existsSync(direct)) return direct;
22
+ if (fs.existsSync(sub)) return sub;
23
+ return null;
24
+ }
25
+
26
+ function getStoriesDir(grimoireDir) {
27
+ const d = path.join(grimoireDir, 'stories');
28
+ if (!fs.existsSync(d)) fs.mkdirSync(d, { recursive: true });
29
+ return d;
30
+ }
31
+
32
+ function loadStories(storiesDir) {
33
+ return fs.readdirSync(storiesDir)
34
+ .filter(f => f.endsWith('.json'))
35
+ .map(f => {
36
+ try { return JSON.parse(fs.readFileSync(path.join(storiesDir, f), 'utf8')); }
37
+ catch (_) { return null; }
38
+ })
39
+ .filter(Boolean)
40
+ .sort((a, b) => a.createdAt > b.createdAt ? 1 : -1);
41
+ }
42
+
43
+ function nextId(stories) {
44
+ const nums = stories.map(s => parseInt(s.id.replace(/\D/g, '')) || 0);
45
+ const max = nums.length ? Math.max(...nums) : 0;
46
+ return `US-${String(max + 1).padStart(3, '0')}`;
47
+ }
48
+
49
+ // ── run ───────────────────────────────────────────────────────────────────────
50
+ function run(args) {
51
+ const sub = args[0];
52
+ const rest = args.slice(1);
53
+
54
+ const grimoireDir = findGrimoireDir();
55
+ if (!grimoireDir) {
56
+ console.error('❌ .grimoire/ not found. Run: npx grimoire-framework install');
57
+ return;
58
+ }
59
+ const storiesDir = getStoriesDir(grimoireDir);
60
+
61
+ switch (sub) {
62
+ case 'create': storyCreate(rest, storiesDir, grimoireDir); break;
63
+ case 'done': storyDone(rest[0], storiesDir, grimoireDir); break;
64
+ case 'delete':
65
+ case 'remove': storyDelete(rest[0], storiesDir); break;
66
+ case 'show': storyShow(rest[0], storiesDir); break;
67
+ case 'list':
68
+ default: storyList(rest, storiesDir); break;
69
+ }
70
+ }
71
+
72
+ // ── create ────────────────────────────────────────────────────────────────────
73
+ function storyCreate(args, storiesDir, grimoireDir) {
74
+ const title = args.filter(a => !a.startsWith('--')).join(' ');
75
+ if (!title) {
76
+ console.log('Usage: grimoire story create "Título da story"\n');
77
+ return;
78
+ }
79
+ const stories = loadStories(storiesDir);
80
+ const id = nextId(stories);
81
+ const story = {
82
+ id,
83
+ title,
84
+ status: 'in_progress',
85
+ createdAt: new Date().toISOString(),
86
+ doneAt: null,
87
+ tags: [],
88
+ notes: [],
89
+ };
90
+ const filename = path.join(storiesDir, `${id}.json`);
91
+ fs.writeFileSync(filename, JSON.stringify(story, null, 2), 'utf8');
92
+
93
+ // Track in metrics
94
+ try {
95
+ const metricsDir = path.join(grimoireDir, 'metrics');
96
+ if (fs.existsSync(metricsDir)) {
97
+ const today = new Date().toISOString().split('T')[0];
98
+ const mFile = path.join(metricsDir, `${today}.jsonl`);
99
+ fs.appendFileSync(mFile, JSON.stringify({ timestamp: new Date().toISOString(), type: 'story_create', story: id, title }) + '\n');
100
+ }
101
+ } catch (_) { }
102
+
103
+ console.log(`\n✅ Story criada: [${id}] ${title}\n`);
104
+ console.log(` Status: 🔄 em andamento`);
105
+ console.log(` Para concluir: grimoire story done ${id}`);
106
+ console.log(` Para ver: grimoire story show ${id}\n`);
107
+ }
108
+
109
+ // ── list ──────────────────────────────────────────────────────────────────────
110
+ function storyList(args, storiesDir) {
111
+ const all = args.includes('--all');
112
+ const stories = loadStories(storiesDir);
113
+ const filtered = all ? stories : stories.filter(s => s.status !== 'done');
114
+
115
+ console.log(`\n📋 Stories${all ? '' : ' ativas'} (${filtered.length}):\n`);
116
+
117
+ if (filtered.length === 0) {
118
+ if (!all) {
119
+ console.log(' (nenhuma story ativa)');
120
+ console.log(' grimoire story create "Título" para criar uma nova\n');
121
+ } else {
122
+ console.log(' (nenhuma story)\n');
123
+ }
124
+ return;
125
+ }
126
+
127
+ const done = filtered.filter(s => s.status === 'done');
128
+ const open = filtered.filter(s => s.status !== 'done');
129
+
130
+ open.forEach(s => console.log(` 🔄 [${s.id}] ${s.title}`));
131
+ done.forEach(s => {
132
+ const d = s.doneAt ? ` — concluída ${s.doneAt.slice(0, 10)}` : '';
133
+ console.log(` ✅ [${s.id}] ${s.title}${d}`);
134
+ });
135
+ console.log(`\n grimoire story done <id> ← marcar como concluída`);
136
+ console.log(` grimoire story show <id> ← ver detalhes\n`);
137
+ }
138
+
139
+ // ── done ──────────────────────────────────────────────────────────────────────
140
+ function storyDone(id, storiesDir, grimoireDir) {
141
+ if (!id) { console.log('Usage: grimoire story done <id>\nEx: grimoire story done US-001\n'); return; }
142
+ const storyFile = path.join(storiesDir, id.endsWith('.json') ? id : `${id}.json`);
143
+ if (!fs.existsSync(storyFile)) {
144
+ console.log(`❌ Story "${id}" not found. Use: grimoire story list\n`); return;
145
+ }
146
+ const story = JSON.parse(fs.readFileSync(storyFile, 'utf8'));
147
+ story.status = 'done';
148
+ story.doneAt = new Date().toISOString();
149
+ fs.writeFileSync(storyFile, JSON.stringify(story, null, 2), 'utf8');
150
+
151
+ // Track in metrics
152
+ try {
153
+ const metricsDir = path.join(grimoireDir, 'metrics');
154
+ if (fs.existsSync(metricsDir)) {
155
+ const today = new Date().toISOString().split('T')[0];
156
+ const mFile = path.join(metricsDir, `${today}.jsonl`);
157
+ fs.appendFileSync(mFile, JSON.stringify({ timestamp: new Date().toISOString(), type: 'story_complete', story: id, title: story.title }) + '\n');
158
+ }
159
+ } catch (_) { }
160
+
161
+ console.log(`\n✅ [${story.id}] ${story.title} — marcada como concluída! 🎉\n`);
162
+ }
163
+
164
+ // ── show ──────────────────────────────────────────────────────────────────────
165
+ function storyShow(id, storiesDir) {
166
+ if (!id) { storyList([], storiesDir); return; }
167
+ const storyFile = path.join(storiesDir, id.endsWith('.json') ? id : `${id}.json`);
168
+ if (!fs.existsSync(storyFile)) {
169
+ console.log(`❌ Story "${id}" not found.\n`); return;
170
+ }
171
+ const s = JSON.parse(fs.readFileSync(storyFile, 'utf8'));
172
+ const statusIcon = s.status === 'done' ? '✅' : '🔄';
173
+ console.log(`\n${statusIcon} [${s.id}] ${s.title}`);
174
+ console.log(`${'─'.repeat(50)}`);
175
+ console.log(` Status: ${s.status}`);
176
+ console.log(` Criada: ${s.createdAt.slice(0, 10)}`);
177
+ if (s.doneAt) console.log(` Concluída: ${s.doneAt.slice(0, 10)}`);
178
+ if (s.notes && s.notes.length) {
179
+ console.log('\n Notas:');
180
+ s.notes.forEach(n => console.log(` - ${n}`));
181
+ }
182
+ console.log();
183
+ }
184
+
185
+ // ── delete ────────────────────────────────────────────────────────────────────
186
+ function storyDelete(id, storiesDir) {
187
+ if (!id) { console.log('Usage: grimoire story delete <id>\n'); return; }
188
+ const storyFile = path.join(storiesDir, id.endsWith('.json') ? id : `${id}.json`);
189
+ if (!fs.existsSync(storyFile)) { console.log(`❌ Story "${id}" not found.\n`); return; }
190
+ fs.unlinkSync(storyFile);
191
+ console.log(`✅ Story "${id}" removida.\n`);
192
+ }
193
+
194
+ // ── Public API for use by other modules ───────────────────────────────────────
195
+ function getActiveStories(grimoireDir) {
196
+ const dir = path.join(grimoireDir, 'stories');
197
+ if (!fs.existsSync(dir)) return [];
198
+ return fs.readdirSync(dir)
199
+ .filter(f => f.endsWith('.json'))
200
+ .map(f => { try { return JSON.parse(fs.readFileSync(path.join(dir, f), 'utf8')); } catch (_) { return null; } })
201
+ .filter(Boolean)
202
+ .filter(s => s.status !== 'done')
203
+ .sort((a, b) => a.createdAt > b.createdAt ? -1 : 1);
204
+ }
205
+
206
+ function getAllStories(grimoireDir) {
207
+ const dir = path.join(grimoireDir, 'stories');
208
+ if (!fs.existsSync(dir)) return [];
209
+ return fs.readdirSync(dir)
210
+ .filter(f => f.endsWith('.json'))
211
+ .map(f => { try { return JSON.parse(fs.readFileSync(path.join(dir, f), 'utf8')); } catch (_) { return null; } })
212
+ .filter(Boolean);
213
+ }
214
+
215
+ module.exports = { run, getActiveStories, getAllStories };
@@ -133,6 +133,15 @@ async function main() {
133
133
  case 'status':
134
134
  handleStatus();
135
135
  break;
136
+ case 'report':
137
+ require('./commands/report').run(args.slice(1));
138
+ break;
139
+ case 'story':
140
+ require('./commands/story').run(args.slice(1));
141
+ break;
142
+ case 'session':
143
+ require('./commands/session').run(args.slice(1));
144
+ break;
136
145
  case 'whoami':
137
146
  handleWhoami();
138
147
  break;
@@ -321,12 +330,10 @@ function handleWhoami() {
321
330
 
322
331
  // Detect IDE from environment
323
332
  const inCursor = process.env.CURSOR_TRACE_ID || process.env.VSCODE_PID;
324
- const inVSCode = process.env.VSCODE_PID && !inCursor;
325
333
  const inGemini = process.env.GEMINI_CLI || process.env.CLOUD_SHELL;
326
334
  const inClaude = process.env.CLAUDE_AGENT;
327
- let ide = 'Unknown IDE';
335
+ let ide = 'Terminal';
328
336
  if (inCursor) ide = 'Cursor';
329
- else if (inVSCode) ide = 'VS Code';
330
337
  else if (inGemini) ide = 'Gemini CLI';
331
338
  else if (inClaude) ide = 'Claude';
332
339
 
@@ -334,55 +341,112 @@ function handleWhoami() {
334
341
  const projectPkg = path.join(cwd, 'package.json');
335
342
  let projectName = path.basename(cwd);
336
343
  if (fs.existsSync(projectPkg)) {
337
- try { projectName = JSON.parse(fs.readFileSync(projectPkg, 'utf8')).name || projectName; } catch (e) { }
344
+ try { projectName = JSON.parse(fs.readFileSync(projectPkg, 'utf8')).name || projectName; } catch (_) { }
338
345
  }
339
346
 
340
- // Read grimoire config if exists
341
- const configPath = path.join(cwd, '.grimoire', 'config.json');
342
- let config = {};
343
- if (fs.existsSync(configPath)) {
344
- try { config = JSON.parse(fs.readFileSync(configPath, 'utf8')); } catch (e) { }
347
+ // Read grimoire config (.grimoire/config.yaml)
348
+ const configFile = (() => {
349
+ const d = path.join(cwd, '.grimoire', 'config.yaml');
350
+ const s = path.join(cwd, 'grimoire', '.grimoire', 'config.yaml');
351
+ return fs.existsSync(d) ? d : fs.existsSync(s) ? s : null;
352
+ })();
353
+ const config = {};
354
+ if (configFile) {
355
+ for (const line of fs.readFileSync(configFile, 'utf8').split('\n')) {
356
+ const t = line.trim();
357
+ if (!t || t.startsWith('#')) continue;
358
+ const i = t.indexOf(':');
359
+ if (i === -1) continue;
360
+ let v = t.slice(i + 1).trim();
361
+ if (v === 'true') v = true; else if (v === 'false') v = false;
362
+ config[t.slice(0, i).trim()] = v;
363
+ }
345
364
  }
346
365
 
347
- // Available agents with personas
348
- const agentNames = {
349
- 'dev': 'Da Vinci', 'qa': 'Dürer', 'sm': 'Monet', 'devops': 'Boccioni',
350
- 'ux-design-expert': 'Matisse', 'grimoire-master': 'Michelangelo',
351
- 'analyst': 'Vermeer', 'architect': 'Gaudí', 'data-engineer': 'Escher',
352
- 'pm': 'Raphael', 'po': 'Velázquez', 'squad-creator': 'Rodin',
353
- };
366
+ // Git hooks check
367
+ const gitHookFile = path.join(cwd, '.git', 'hooks', 'post-commit');
368
+ const hooksInstalled = fs.existsSync(gitHookFile) &&
369
+ fs.readFileSync(gitHookFile, 'utf8').includes('grimoire-hook-v1');
370
+
371
+ // Pro status
372
+ let proStatus = '⭕ Community';
373
+ try {
374
+ const { isPro } = require('./commands/pro');
375
+ if (isPro()) proStatus = '🔐 Pro (ativo)';
376
+ } catch (_) { }
354
377
 
378
+ // Agents
355
379
  const agentsDir = path.join(cwd, '.codex', 'agents');
356
- const availableAgents = fs.existsSync(agentsDir)
380
+ const allAgents = fs.existsSync(agentsDir)
357
381
  ? fs.readdirSync(agentsDir).filter(f => f.endsWith('.md')).map(f => f.replace('.md', ''))
358
382
  : [];
383
+ const coreAgentIds = ['grimoire-master', 'dev', 'qa', 'architect', 'pm', 'po', 'sm', 'devops', 'data-engineer', 'analyst', 'ux-design-expert', 'squad-creator'];
384
+ const coreCount = allAgents.filter(a => coreAgentIds.includes(a)).length;
385
+ const customCount = allAgents.filter(a => !coreAgentIds.includes(a)).length;
386
+
387
+ // Memory stats
388
+ const memDir = path.join(cwd, '.grimoire', 'memory', 'sessions');
389
+ let memEntries = 0;
390
+ const tagSet = new Set();
391
+ if (fs.existsSync(memDir)) {
392
+ const today = new Date().toISOString().split('T')[0];
393
+ const todayFile = path.join(memDir, `${today}.jsonl`);
394
+ if (fs.existsSync(todayFile)) {
395
+ const lines = fs.readFileSync(todayFile, 'utf8').split('\n').filter(l => l.trim());
396
+ memEntries = lines.length;
397
+ for (const l of lines) {
398
+ try { const e = JSON.parse(l); if (e.tag) tagSet.add(e.tag); } catch (_) { }
399
+ }
400
+ }
401
+ }
359
402
 
360
- const agentList = availableAgents
361
- .map(a => agentNames[a] ? `@${a} (${agentNames[a]})` : `@${a}`)
362
- .join(', ');
403
+ // Stories
404
+ let activeStories = [], totalStories = 0;
405
+ try {
406
+ const { getActiveStories, getAllStories } = require('./commands/story');
407
+ const grimoireDir = path.join(cwd, '.grimoire');
408
+ activeStories = getActiveStories(grimoireDir);
409
+ totalStories = getAllStories(grimoireDir).length;
410
+ } catch (_) { }
411
+
412
+ const agentNames = {
413
+ 'dev': 'Da Vinci', 'qa': 'Dürer', 'sm': 'Monet', 'devops': 'Boccioni',
414
+ 'ux-design-expert': 'Matisse', 'grimoire-master': 'Michelangelo', 'analyst': 'Vermeer',
415
+ 'architect': 'Gaudí', 'data-engineer': 'Escher', 'pm': 'Raphael', 'po': 'Velázquez', 'squad-creator': 'Rodin'
416
+ };
363
417
 
364
418
  console.log(`
365
419
  🧙 Grimoire — Who Am I?
366
- ${'='.repeat(40)}
367
- 📌 You are: ${process.env.USER || process.env.USERNAME || 'unknown user'}
368
- 📂 Project: ${projectName}
369
- 📍 Directory: ${cwd}
370
- 💻 IDE: ${ide}
371
- 🔮 Framework: Grimoire v${packageJson.version}
372
- ${config.squad ? `👥 Squad: ${config.squad}` : ''}
373
-
374
- 🧙 Available Agents:
375
- ${agentList || '(none found — run grimoire install)'}
376
-
377
- 💡 How to activate an agent:
378
- In your IDE chat, type: @dev or @qa or @grimoire-master
379
- The agent will greet you by their persona name (Da Vinci, Dürer, etc.)
380
-
381
- 🔧 Framework vs CLI:
382
- CLI commands → Run in terminal: grimoire status | grimoire agents list
383
- Agent chat → Activate in IDE: @agentname (e.g. @dev, @qa)
384
- These are DIFFERENT interfaces — the CLI manages the framework,
385
- agents respond inside your IDE's AI chat window.
420
+ ${'='.repeat(44)}
421
+
422
+ Projeto: ${projectName}
423
+ Diretório: ${cwd}
424
+ IDE: ${ide}
425
+ Framework: Grimoire v${packageJson.version}
426
+ Pro: ${proStatus}
427
+
428
+ ⚙️ Config (.grimoire/config.yaml):
429
+ default_agent: @${config.default_agent || 'grimoire-master'}
430
+ metrics: ${config.metrics_enabled !== false ? '✅ ativo' : '⭕ desabilitado'}
431
+ hooks git: ${hooksInstalled ? '✅ instalado' : '⭕ não instalado'}
432
+ linguagem: ${config.language || 'pt-BR'}
433
+
434
+ 🤖 Agentes (${allAgents.length}):
435
+ ${coreCount} core${customCount > 0 ? ` · ${customCount} custom` : ''}
436
+ Rota rápida: ${allAgents.slice(0, 4).map(a => `@${a}`).join(' · ')}...
437
+
438
+ 📋 Stories:
439
+ ${activeStories.length} abertas${totalStories > 0 ? ` | ${totalStories - activeStories.length} concluídas` : ''}
440
+ ${activeStories.slice(0, 3).map(s => ` 🔄 [${s.id}] ${s.title}`).join('\n') || ''}
441
+
442
+ 🧠 Memória (hoje):
443
+ ${memEntries} entradas${tagSet.size > 0 ? ' Tags: ' + [...tagSet].map(t => '#' + t).join(' · ') : ''}
444
+
445
+ 💡 Comandos úteis:
446
+ grimoire session start ← prompt de início de sessão
447
+ grimoire story list ← stories ativas
448
+ grimoire report ← relatório de hoje
449
+ grimoire config list ← ver configurações
386
450
  `);
387
451
  }
388
452
 
@@ -471,6 +535,48 @@ function handleDoctor() {
471
535
  ok(false, 'package.json found');
472
536
  }
473
537
 
538
+ // ── New checks: hooks, config, pro, marketplace, stories ────────
539
+ console.log('\n🪝 Git Hooks (auto-metrics):');
540
+ const gitHook = path.join(cwd, '.git', 'hooks', 'post-commit');
541
+ const hookInstalled = check(gitHook) && fs.readFileSync(gitHook, 'utf8').includes('grimoire-hook-v1');
542
+ warn(hookInstalled, 'git post-commit hook (auto-tracking de commits)',
543
+ 'Run: grimoire hooks install');
544
+
545
+ console.log('\n⚙️ Config (.grimoire/config.yaml):');
546
+ const configYaml = path.join(cwd, '.grimoire', 'config.yaml');
547
+ warn(check(configYaml), '.grimoire/config.yaml presente',
548
+ 'Run: grimoire config set default_agent dev');
549
+ if (check(configYaml)) {
550
+ try {
551
+ const cfg = fs.readFileSync(configYaml, 'utf8');
552
+ const hasKeys = cfg.includes('default_agent') || cfg.includes('metrics_enabled');
553
+ warn(hasKeys, 'Config tem chaves válidas', 'Run: grimoire config list');
554
+ } catch (_) { }
555
+ }
556
+
557
+ console.log('\n🔐 Grimoire Pro:');
558
+ let proActive = false;
559
+ try { const { isPro } = require('./commands/pro'); proActive = isPro(); } catch (_) { }
560
+ warn(true, `Licença: ${proActive ? '🔐 Pro ativa' : 'Community (gratuito)'}`,
561
+ proActive ? null : 'grimoire pro activate <chave> para ativar Pro');
562
+
563
+ console.log('\n🏪 Marketplace:');
564
+ const mktDir = path.join(cwd, '.grimoire', 'marketplace');
565
+ const installedMkt = check(mktDir) ? fs.readdirSync(mktDir).filter(f => f.endsWith('.md')).length : 0;
566
+ warn(true, `Agentes do marketplace instalados: ${installedMkt}`,
567
+ installedMkt === 0 ? 'grimoire marketplace list para explorar agentes da comunidade' : null);
568
+
569
+ console.log('\n📋 Stories:');
570
+ let activeCount = 0;
571
+ try {
572
+ const { getActiveStories } = require('./commands/story');
573
+ activeCount = getActiveStories(path.join(cwd, '.grimoire')).length;
574
+ warn(true, `Stories ativas: ${activeCount}`,
575
+ activeCount === 0 ? 'grimoire story create "Título" para começar' : null);
576
+ } catch (_) {
577
+ warn(false, 'stories.js não disponível');
578
+ }
579
+
474
580
  // ── Other IDEs ────────────────────────────────────────────────
475
581
  console.log('\n🔗 Other IDE Integration:');
476
582
  warn(check(path.join(cwd, '.cursor', 'rules')), '.cursor/rules (Cursor)');
@@ -478,6 +584,7 @@ function handleDoctor() {
478
584
 
479
585
  console.log('\n Run \'grimoire status\' for full framework state.');
480
586
  console.log(' Run \'npx grimoire-framework update --force\' to resync all files.\n');
587
+
481
588
  }
482
589
 
483
590
  function showHelp() {
@@ -491,13 +598,23 @@ function showHelp() {
491
598
  (CLI = terminal · Agentes = @agentname no chat da IDE)
492
599
 
493
600
  COMANDOS ESSENCIAIS:
494
- grimoire status Saúde do framework e o que está ativo
495
- grimoire whoami Contexto da sessão e como usar agentes
496
- grimoire doctor Diagnóstico completo (checks + sugestões)
497
- grimoire update Atualiza agentes e arquivos do framework
498
- grimoire update --dry-run 🆕 Mostra diff sem aplicar
499
- grimoire config list 🆕 Configurações do projeto
500
- grimoire config set <k> <v> 🆕 Define uma configuração
601
+ grimoire status Saúde do framework
602
+ grimoire whoami Contexto completo (config+pro+stories)
603
+ grimoire doctor Diagnóstico completo
604
+ grimoire update Atualiza agentes e arquivos
605
+ grimoire update --dry-run Mostra diff sem aplicar
606
+ grimoire config list Configurações do projeto
607
+ grimoire config set <k> <v> Define uma configuração
608
+
609
+ SESSÃO & WORKFLOW:
610
+ grimoire session start 🆕 Prompt de início de sessão para IDE
611
+ grimoire session start --squad <s> 🆕 Com squad específico
612
+ grimoire story create "Título" 🆕 Criar story
613
+ grimoire story list 🆕 Ver stories ativas
614
+ grimoire story done US-001 🆕 Marcar concluída
615
+ grimoire report 🆕 Relatório de hoje
616
+ grimoire report --period week 🆕 Relatório da semana
617
+ grimoire report --md 🆕 Exportar relatório
501
618
 
502
619
  AGENTES:
503
620
  grimoire agents list Lista todos os agentes com personas
@@ -552,7 +669,8 @@ OUTROS:
552
669
 
553
670
  QUICK START:
554
671
  npx grimoire-framework install Setup em novo projeto
555
- grimoire squads use fullstack Ativar squad fullstack
672
+ grimoire session start Gerar prompt de sessão
673
+ grimoire squads use fullstack Ativar squad
556
674
  @dev @qa @architect No chat da sua IDE
557
675
  `);
558
676
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grimoire-framework",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Grimoire: AI-Orchestrated System for Full Stack Development - Core Framework",
5
5
  "publishConfig": {
6
6
  "access": "public"