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.
- package/.grimoire/install-manifest.yaml +2 -2
- package/bin/commands/report.js +200 -0
- package/bin/commands/session.js +217 -0
- package/bin/commands/story.js +215 -0
- package/bin/grimoire-cli.js +166 -48
- package/package.json +1 -1
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
# - SHA256 hashes for change detection
|
|
8
8
|
# - File types for categorization
|
|
9
9
|
#
|
|
10
|
-
version: 1.
|
|
11
|
-
generated_at: "2026-02-22T16:
|
|
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 };
|
package/bin/grimoire-cli.js
CHANGED
|
@@ -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 = '
|
|
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 (
|
|
344
|
+
try { projectName = JSON.parse(fs.readFileSync(projectPkg, 'utf8')).name || projectName; } catch (_) { }
|
|
338
345
|
}
|
|
339
346
|
|
|
340
|
-
// Read grimoire config
|
|
341
|
-
const
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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
|
-
//
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
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
|
|
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
|
-
|
|
361
|
-
|
|
362
|
-
|
|
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(
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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
|
|
495
|
-
grimoire whoami
|
|
496
|
-
grimoire doctor
|
|
497
|
-
grimoire update
|
|
498
|
-
grimoire update --dry-run
|
|
499
|
-
grimoire config list
|
|
500
|
-
grimoire config set <k> <v>
|
|
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
|
|
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
|
}
|