grimoire-framework 1.1.1 β 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/agent.js +38 -0
- package/bin/commands/config.js +181 -0
- package/bin/commands/hooks.js +137 -0
- package/bin/commands/memory.js +54 -3
- package/bin/commands/report.js +200 -0
- package/bin/commands/session.js +217 -0
- package/bin/commands/squads.js +162 -0
- package/bin/commands/story.js +215 -0
- package/bin/commands/update.js +35 -6
- package/bin/grimoire-cli.js +203 -72
- 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-
|
|
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:
|
package/bin/commands/agent.js
CHANGED
|
@@ -100,6 +100,7 @@ async function run(args) {
|
|
|
100
100
|
switch (sub) {
|
|
101
101
|
case 'create': await createWizard(); break;
|
|
102
102
|
case 'edit': await editAgent(args[1]); break;
|
|
103
|
+
case 'validate': validateAgent(args[1]); break;
|
|
103
104
|
case 'remove':
|
|
104
105
|
case 'delete': await removeAgent(args[1]); break;
|
|
105
106
|
case 'list':
|
|
@@ -191,6 +192,43 @@ Para sincronizar com outras IDEs:
|
|
|
191
192
|
`);
|
|
192
193
|
}
|
|
193
194
|
|
|
195
|
+
function validateAgent(id) {
|
|
196
|
+
if (!id) { console.log('Usage: grimoire agent validate <name>'); return; }
|
|
197
|
+
const file = path.join(process.cwd(), '.codex', 'agents', `${id}.md`);
|
|
198
|
+
if (!fs.existsSync(file)) { console.log(`β Agent "${id}" not found in .codex/agents/`); return; }
|
|
199
|
+
|
|
200
|
+
const content = fs.readFileSync(file, 'utf8');
|
|
201
|
+
const checks = [
|
|
202
|
+
{ name: 'YAML block (`\`\`\`yaml`)', pass: content.includes('```yaml') },
|
|
203
|
+
{ name: 'activation-instructions', pass: content.includes('activation-instructions') },
|
|
204
|
+
{ name: 'STEP 1 / STEP 2 / STEP 3', pass: /STEP 1/.test(content) && /STEP 2/.test(content) },
|
|
205
|
+
{ name: 'agent.name defined', pass: /\bname:\s*\S+/.test(content) },
|
|
206
|
+
{ name: 'agent.id defined', pass: /\bid:\s*\S+/.test(content) },
|
|
207
|
+
{ name: 'agent.icon defined', pass: /\bicon:\s*\S+/.test(content) },
|
|
208
|
+
{ name: 'greeting_levels present', pass: content.includes('greeting_levels') },
|
|
209
|
+
{ name: 'signature_closing present', pass: content.includes('signature_closing') },
|
|
210
|
+
{ name: 'ACTIVATION-NOTICE present', pass: content.includes('ACTIVATION-NOTICE') },
|
|
211
|
+
{ name: '*exit command referenced', pass: content.includes('*exit') },
|
|
212
|
+
];
|
|
213
|
+
|
|
214
|
+
const passed = checks.filter(c => c.pass).length;
|
|
215
|
+
const score = Math.round((passed / checks.length) * 100);
|
|
216
|
+
const emoji = score === 100 ? 'π’' : score >= 70 ? 'π‘' : 'π΄';
|
|
217
|
+
|
|
218
|
+
console.log(`\nπ Agent Validation: @${id}\n${'β'.repeat(40)}`);
|
|
219
|
+
checks.forEach(c => console.log(` ${c.pass ? 'β
' : 'β'} ${c.name}`));
|
|
220
|
+
console.log(`${'β'.repeat(40)}`);
|
|
221
|
+
console.log(` ${emoji} Score: ${score}/100 (${passed}/${checks.length} checks)\n`);
|
|
222
|
+
|
|
223
|
+
if (score < 100) {
|
|
224
|
+
console.log(' Fixes sugeridos:');
|
|
225
|
+
checks.filter(c => !c.pass).forEach(c => console.log(` β Adicionar: ${c.name}`));
|
|
226
|
+
console.log();
|
|
227
|
+
} else {
|
|
228
|
+
console.log(' β
Agente vΓ‘lido e pronto para uso!\n');
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
194
232
|
async function editAgent(id) {
|
|
195
233
|
if (!id) { console.log('Usage: grimoire agent edit <name>'); return; }
|
|
196
234
|
const file = path.join(process.cwd(), '.codex', 'agents', `${id}.md`);
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* grimoire config β Project-level configuration
|
|
3
|
+
*
|
|
4
|
+
* grimoire config get <key> LΓͺ valor de uma chave
|
|
5
|
+
* grimoire config set <key> <value> Define valor de uma chave
|
|
6
|
+
* grimoire config list Lista todas as configuraΓ§Γ΅es
|
|
7
|
+
* grimoire config reset Reseta para os defaults
|
|
8
|
+
*
|
|
9
|
+
* Config file: .grimoire/config.yaml
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
|
|
17
|
+
// ββ Defaults βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
18
|
+
const DEFAULTS = {
|
|
19
|
+
default_agent: 'grimoire-master',
|
|
20
|
+
memory_auto_save: false,
|
|
21
|
+
update_check_interval: 24, // hours
|
|
22
|
+
metrics_enabled: true,
|
|
23
|
+
hooks_enabled: false,
|
|
24
|
+
marketplace_cache_ttl: 60, // minutes
|
|
25
|
+
language: 'pt-BR',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const DESCRIPTIONS = {
|
|
29
|
+
default_agent: 'Agente padrΓ£o ao iniciar o IDE',
|
|
30
|
+
memory_auto_save: 'Salvar memΓ³ria automaticamente ao encerrar sessΓ£o',
|
|
31
|
+
update_check_interval: 'Intervalo de horas entre verificaΓ§Γ΅es de atualizaΓ§Γ£o',
|
|
32
|
+
metrics_enabled: 'Habilitar coleta de mΓ©tricas de produtividade',
|
|
33
|
+
hooks_enabled: 'Habilitar git hooks para auto-tracking de commits',
|
|
34
|
+
marketplace_cache_ttl: 'Tempo de cache do marketplace em minutos',
|
|
35
|
+
language: 'Idioma preferido para prompts e saΓdas',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// ββ File location ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
39
|
+
function findConfigFile() {
|
|
40
|
+
const cwd = process.cwd();
|
|
41
|
+
const direct = path.join(cwd, '.grimoire', 'config.yaml');
|
|
42
|
+
const subdir = path.join(cwd, 'grimoire', '.grimoire', 'config.yaml');
|
|
43
|
+
if (fs.existsSync(direct)) return direct;
|
|
44
|
+
if (fs.existsSync(subdir)) return subdir;
|
|
45
|
+
// Return direct path for writing even if it doesn't exist
|
|
46
|
+
const grimoireDir = path.join(cwd, '.grimoire');
|
|
47
|
+
if (fs.existsSync(grimoireDir)) return direct;
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ββ Minimal YAML parser/writer βββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
52
|
+
function parseYaml(content) {
|
|
53
|
+
const result = {};
|
|
54
|
+
for (const line of content.split('\n')) {
|
|
55
|
+
const stripped = line.trim();
|
|
56
|
+
if (!stripped || stripped.startsWith('#')) continue;
|
|
57
|
+
const colonIdx = stripped.indexOf(':');
|
|
58
|
+
if (colonIdx === -1) continue;
|
|
59
|
+
const key = stripped.slice(0, colonIdx).trim();
|
|
60
|
+
let val = stripped.slice(colonIdx + 1).trim();
|
|
61
|
+
// Parse types
|
|
62
|
+
if (val === 'true') val = true;
|
|
63
|
+
else if (val === 'false') val = false;
|
|
64
|
+
else if (!isNaN(val) && val !== '') val = Number(val);
|
|
65
|
+
result[key] = val;
|
|
66
|
+
}
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function toYaml(obj) {
|
|
71
|
+
const lines = ['# Grimoire Framework β Project Configuration', '# grimoire config set <key> <value> to edit', ''];
|
|
72
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
73
|
+
const desc = DESCRIPTIONS[k];
|
|
74
|
+
if (desc) lines.push(`# ${desc}`);
|
|
75
|
+
lines.push(`${k}: ${v}`);
|
|
76
|
+
lines.push('');
|
|
77
|
+
}
|
|
78
|
+
return lines.join('\n');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function loadConfig() {
|
|
82
|
+
const file = findConfigFile();
|
|
83
|
+
if (!file || !fs.existsSync(file)) return { ...DEFAULTS };
|
|
84
|
+
try {
|
|
85
|
+
return { ...DEFAULTS, ...parseYaml(fs.readFileSync(file, 'utf8')) };
|
|
86
|
+
} catch (_) { return { ...DEFAULTS }; }
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function saveConfig(obj) {
|
|
90
|
+
const cwd = process.cwd();
|
|
91
|
+
let grimoireDir = path.join(cwd, '.grimoire');
|
|
92
|
+
if (!fs.existsSync(grimoireDir)) {
|
|
93
|
+
const sub = path.join(cwd, 'grimoire', '.grimoire');
|
|
94
|
+
if (fs.existsSync(sub)) grimoireDir = sub;
|
|
95
|
+
else { console.error('β .grimoire/ not found. Run: npx grimoire-framework install'); return false; }
|
|
96
|
+
}
|
|
97
|
+
const file = path.join(grimoireDir, 'config.yaml');
|
|
98
|
+
fs.writeFileSync(file, toYaml(obj), 'utf8');
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ββ Public API βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
103
|
+
function getConfigValue(key) {
|
|
104
|
+
return loadConfig()[key];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ββ Commands βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
108
|
+
function run(args) {
|
|
109
|
+
const sub = args[0];
|
|
110
|
+
const key = args[1];
|
|
111
|
+
const value = args[2];
|
|
112
|
+
|
|
113
|
+
switch (sub) {
|
|
114
|
+
case 'set': configSet(key, value); break;
|
|
115
|
+
case 'reset': configReset(); break;
|
|
116
|
+
case 'get':
|
|
117
|
+
if (!key) { configList(); break; }
|
|
118
|
+
configGet(key); break;
|
|
119
|
+
case 'list':
|
|
120
|
+
default: configList(); break;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function configGet(key) {
|
|
125
|
+
if (!key) { configList(); return; }
|
|
126
|
+
const config = loadConfig();
|
|
127
|
+
if (!(key in config)) {
|
|
128
|
+
console.log(`β Chave desconhecida: "${key}"`);
|
|
129
|
+
console.log(` Chaves vΓ‘lidas: ${Object.keys(DEFAULTS).join(', ')}\n`);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
console.log(`${key} = ${config[key]}`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function configSet(key, value) {
|
|
136
|
+
if (!key || value === undefined) {
|
|
137
|
+
console.log('Usage: grimoire config set <key> <value>');
|
|
138
|
+
console.log('Ex: grimoire config set default_agent dev\n');
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (!(key in DEFAULTS)) {
|
|
142
|
+
console.log(`β Chave desconhecida: "${key}"`);
|
|
143
|
+
console.log(` Chaves vΓ‘lidas: ${Object.keys(DEFAULTS).join(', ')}\n`);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const config = loadConfig();
|
|
147
|
+
// Type coerce
|
|
148
|
+
let coerced = value;
|
|
149
|
+
if (value === 'true') coerced = true;
|
|
150
|
+
else if (value === 'false') coerced = false;
|
|
151
|
+
else if (!isNaN(value)) coerced = Number(value);
|
|
152
|
+
|
|
153
|
+
config[key] = coerced;
|
|
154
|
+
if (saveConfig(config)) {
|
|
155
|
+
console.log(`β
${key} = ${coerced}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function configList() {
|
|
160
|
+
const config = loadConfig();
|
|
161
|
+
const file = findConfigFile();
|
|
162
|
+
console.log(`\nβοΈ Grimoire Config${file && fs.existsSync(file) ? ` (${path.relative(process.cwd(), file)})` : ' (defaults)'}\n`);
|
|
163
|
+
console.log(' ' + 'β'.repeat(48));
|
|
164
|
+
for (const [k, v] of Object.entries(DEFAULTS)) {
|
|
165
|
+
const current = config[k];
|
|
166
|
+
const isDef = current === v;
|
|
167
|
+
const marker = isDef ? ' ' : 'βοΈ ';
|
|
168
|
+
console.log(` ${marker} ${k.padEnd(28)} ${String(current)}`);
|
|
169
|
+
}
|
|
170
|
+
console.log(' ' + 'β'.repeat(48));
|
|
171
|
+
console.log(` π‘ grimoire config set <key> <value> para alterar`);
|
|
172
|
+
console.log(` π‘ grimoire config reset para restaurar defaults\n`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function configReset() {
|
|
176
|
+
if (saveConfig({ ...DEFAULTS })) {
|
|
177
|
+
console.log('β
ConfiguraΓ§Γ£o restaurada para os defaults.\n');
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
module.exports = { run, getConfigValue, loadConfig };
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* grimoire hooks β Git Hook Integration for Auto-Metrics Tracking
|
|
3
|
+
*
|
|
4
|
+
* grimoire hooks install Instala post-commit hook no repositΓ³rio atual
|
|
5
|
+
* grimoire hooks uninstall Remove o hook
|
|
6
|
+
* grimoire hooks status Verifica se o hook estΓ‘ instalado
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
|
|
14
|
+
const HOOK_MARKER = '# grimoire-hook-v1';
|
|
15
|
+
const HOOK_CONTENT = `#!/bin/sh
|
|
16
|
+
${HOOK_MARKER}
|
|
17
|
+
# Auto-installed by Grimoire Framework
|
|
18
|
+
# Remove with: grimoire hooks uninstall
|
|
19
|
+
|
|
20
|
+
# Get commit message (first line)
|
|
21
|
+
COMMIT_MSG=$(git log -1 --pretty=%B 2>/dev/null | head -1)
|
|
22
|
+
|
|
23
|
+
# Track the commit in grimoire metrics (non-blocking)
|
|
24
|
+
grimoire metrics track commit "$COMMIT_MSG" 2>/dev/null &
|
|
25
|
+
|
|
26
|
+
exit 0
|
|
27
|
+
`;
|
|
28
|
+
|
|
29
|
+
function findGitDir() {
|
|
30
|
+
let dir = process.cwd();
|
|
31
|
+
for (let i = 0; i < 8; i++) {
|
|
32
|
+
if (fs.existsSync(path.join(dir, '.git'))) return dir;
|
|
33
|
+
const parent = path.dirname(dir);
|
|
34
|
+
if (parent === dir) break;
|
|
35
|
+
dir = parent;
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function run(args) {
|
|
41
|
+
const sub = args[0] || 'status';
|
|
42
|
+
switch (sub) {
|
|
43
|
+
case 'install': install(); break;
|
|
44
|
+
case 'uninstall': uninstall(); break;
|
|
45
|
+
case 'status':
|
|
46
|
+
default: status(); break;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function install() {
|
|
51
|
+
const gitRoot = findGitDir();
|
|
52
|
+
if (!gitRoot) {
|
|
53
|
+
console.error('β RepositΓ³rio git nΓ£o encontrado. Execute dentro de um projeto git.');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const hooksDir = path.join(gitRoot, '.git', 'hooks');
|
|
58
|
+
const hookFile = path.join(hooksDir, 'post-commit');
|
|
59
|
+
|
|
60
|
+
if (!fs.existsSync(hooksDir)) fs.mkdirSync(hooksDir, { recursive: true });
|
|
61
|
+
|
|
62
|
+
// Check if hook already exists and isn't ours
|
|
63
|
+
if (fs.existsSync(hookFile)) {
|
|
64
|
+
const existing = fs.readFileSync(hookFile, 'utf8');
|
|
65
|
+
if (existing.includes(HOOK_MARKER)) {
|
|
66
|
+
console.log('βΉοΈ Hook jΓ‘ instalado. Use "grimoire hooks status" para verificar.');
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
// Append to existing hook
|
|
70
|
+
const updated = existing.trimEnd() + '\n\n' + HOOK_CONTENT;
|
|
71
|
+
fs.writeFileSync(hookFile, updated, 'utf8');
|
|
72
|
+
} else {
|
|
73
|
+
fs.writeFileSync(hookFile, HOOK_CONTENT, 'utf8');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Make executable (Unix/Mac β no-op on Windows but harmless)
|
|
77
|
+
try { fs.chmodSync(hookFile, '755'); } catch (_) { }
|
|
78
|
+
|
|
79
|
+
console.log(`
|
|
80
|
+
β
grimoire hooks instalado!
|
|
81
|
+
|
|
82
|
+
Hook: .git/hooks/post-commit
|
|
83
|
+
Efeito: commits sΓ£o automaticamente registrados em grimoire metrics
|
|
84
|
+
|
|
85
|
+
PrΓ³ximo passo: faΓ§a um git commit e execute:
|
|
86
|
+
grimoire metrics --period week
|
|
87
|
+
|
|
88
|
+
Para remover: grimoire hooks uninstall
|
|
89
|
+
`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function uninstall() {
|
|
93
|
+
const gitRoot = findGitDir();
|
|
94
|
+
if (!gitRoot) { console.error('β RepositΓ³rio git nΓ£o encontrado.'); return; }
|
|
95
|
+
|
|
96
|
+
const hookFile = path.join(gitRoot, '.git', 'hooks', 'post-commit');
|
|
97
|
+
if (!fs.existsSync(hookFile)) {
|
|
98
|
+
console.log('βΉοΈ Nenhum hook encontrado.');
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const content = fs.readFileSync(hookFile, 'utf8');
|
|
103
|
+
if (!content.includes(HOOK_MARKER)) {
|
|
104
|
+
console.log('βΉοΈ Hook nΓ£o foi instalado pelo Grimoire. Nada removido.');
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// If our hook is the only content, delete the file
|
|
109
|
+
const lines = content.split('\n');
|
|
110
|
+
const otherLines = lines.filter(l => !l.includes(HOOK_MARKER) && !l.includes('grimoire metrics track'));
|
|
111
|
+
const cleaned = otherLines.join('\n').trim();
|
|
112
|
+
|
|
113
|
+
if (!cleaned || cleaned === '#!/bin/sh') {
|
|
114
|
+
fs.unlinkSync(hookFile);
|
|
115
|
+
console.log('β
Hook removido (arquivo deletado).');
|
|
116
|
+
} else {
|
|
117
|
+
fs.writeFileSync(hookFile, cleaned + '\n', 'utf8');
|
|
118
|
+
console.log('β
Hook grimoire removido (conteΓΊdo do hook preservado).');
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function status() {
|
|
123
|
+
const gitRoot = findGitDir();
|
|
124
|
+
if (!gitRoot) { console.log('β οΈ RepositΓ³rio git nΓ£o encontrado.\n'); return; }
|
|
125
|
+
|
|
126
|
+
const hookFile = path.join(gitRoot, '.git', 'hooks', 'post-commit');
|
|
127
|
+
const exists = fs.existsSync(hookFile);
|
|
128
|
+
const isOurs = exists && fs.readFileSync(hookFile, 'utf8').includes(HOOK_MARKER);
|
|
129
|
+
|
|
130
|
+
console.log('\nπͺ Grimoire Hooks\n' + 'β'.repeat(40));
|
|
131
|
+
console.log(` post-commit: ${isOurs ? 'β
instalado' : exists ? 'β οΈ arquivo existe (nΓ£o Γ© nosso)' : 'β nΓ£o instalado'}`);
|
|
132
|
+
if (!isOurs) console.log(`\n Para instalar: grimoire hooks install`);
|
|
133
|
+
else console.log(`\n Para remover: grimoire hooks uninstall`);
|
|
134
|
+
console.log();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
module.exports = { run };
|
package/bin/commands/memory.js
CHANGED
|
@@ -35,6 +35,9 @@ async function run(args) {
|
|
|
35
35
|
case 'save':
|
|
36
36
|
await saveMemory(args.slice(1), memoryDir);
|
|
37
37
|
break;
|
|
38
|
+
case 'list-tags':
|
|
39
|
+
await listTags(memoryDir);
|
|
40
|
+
break;
|
|
38
41
|
case 'list':
|
|
39
42
|
await listSessions(memoryDir);
|
|
40
43
|
break;
|
|
@@ -67,7 +70,22 @@ async function run(args) {
|
|
|
67
70
|
* Saves memory using JSONL append (O(1) complexity)
|
|
68
71
|
*/
|
|
69
72
|
async function saveMemory(args, memoryDir) {
|
|
70
|
-
|
|
73
|
+
// Extract --tag value if present
|
|
74
|
+
const tagIdx = args.findIndex(a => a === '--tag' || a.startsWith('--tag='));
|
|
75
|
+
let tag = null;
|
|
76
|
+
let filteredArgs = [...args];
|
|
77
|
+
if (tagIdx !== -1) {
|
|
78
|
+
if (args[tagIdx].includes('=')) {
|
|
79
|
+
tag = args[tagIdx].split('=')[1];
|
|
80
|
+
} else {
|
|
81
|
+
tag = args[tagIdx + 1];
|
|
82
|
+
filteredArgs.splice(tagIdx, 2);
|
|
83
|
+
}
|
|
84
|
+
if (!tag) { filteredArgs.splice(tagIdx, 1); }
|
|
85
|
+
else { filteredArgs = filteredArgs.filter(a => a !== args[tagIdx] && a !== tag); }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const content = filteredArgs.join(' ');
|
|
71
89
|
if (!content) {
|
|
72
90
|
console.error('β Please provide content to save.');
|
|
73
91
|
return;
|
|
@@ -78,7 +96,8 @@ async function saveMemory(args, memoryDir) {
|
|
|
78
96
|
|
|
79
97
|
const entry = {
|
|
80
98
|
timestamp: new Date().toISOString(),
|
|
81
|
-
content: content
|
|
99
|
+
content: content,
|
|
100
|
+
...(tag ? { tag } : {}),
|
|
82
101
|
};
|
|
83
102
|
|
|
84
103
|
// Ensure file exists for lockfile
|
|
@@ -100,7 +119,8 @@ async function saveMemory(args, memoryDir) {
|
|
|
100
119
|
|
|
101
120
|
// Append entry as a new line in JSONL format
|
|
102
121
|
await fs.appendFile(sessionFile, JSON.stringify(entry) + '\n', 'utf8');
|
|
103
|
-
|
|
122
|
+
const tagLabel = tag ? ` [#${tag}]` : '';
|
|
123
|
+
console.log(`β
Memory appended to session ${today}${tagLabel}`);
|
|
104
124
|
} catch (err) {
|
|
105
125
|
console.error(`β Failed to acquire lock for memory file: ${err.message}`);
|
|
106
126
|
} finally {
|
|
@@ -120,6 +140,37 @@ async function listSessions(memoryDir) {
|
|
|
120
140
|
files.forEach(f => console.log(` - ${f.replace('.jsonl', '').replace('.json', '')}`));
|
|
121
141
|
}
|
|
122
142
|
|
|
143
|
+
/**
|
|
144
|
+
* Lists all unique tags across all sessions
|
|
145
|
+
*/
|
|
146
|
+
async function listTags(memoryDir) {
|
|
147
|
+
const sessionsDir = path.join(memoryDir, 'sessions');
|
|
148
|
+
let files;
|
|
149
|
+
try { files = (await fs.readdir(sessionsDir)).filter(f => f.endsWith('.jsonl')); }
|
|
150
|
+
catch (_) { files = []; }
|
|
151
|
+
|
|
152
|
+
const tagCount = {};
|
|
153
|
+
for (const file of files) {
|
|
154
|
+
const raw = await fs.readFile(path.join(sessionsDir, file), 'utf8').catch(() => '');
|
|
155
|
+
for (const line of raw.split('\n').filter(l => l.trim())) {
|
|
156
|
+
try {
|
|
157
|
+
const entry = JSON.parse(line);
|
|
158
|
+
if (entry.tag) tagCount[entry.tag] = (tagCount[entry.tag] || 0) + 1;
|
|
159
|
+
} catch (_) { }
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const tags = Object.entries(tagCount).sort((a, b) => b[1] - a[1]);
|
|
164
|
+
console.log('\nπ·οΈ Grimoire Memory Tags:\n');
|
|
165
|
+
if (tags.length === 0) {
|
|
166
|
+
console.log(' (nenhuma tag registrada)');
|
|
167
|
+
console.log(' Use: grimoire memory save --tag <nome> "texto"\n');
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
tags.forEach(([tag, count]) => console.log(` #${tag.padEnd(20)} ${count}Γ`));
|
|
171
|
+
console.log(`\n Para filtrar: grimoire memory search --tag <nome>\n`);
|
|
172
|
+
}
|
|
173
|
+
|
|
123
174
|
/**
|
|
124
175
|
* Shows session parsing JSONL or legacy JSON
|
|
125
176
|
*/
|
|
@@ -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 };
|