grimoire-framework 1.1.1 → 1.2.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.1.1
11
- generated_at: "2026-02-22T15:03:35.007Z"
10
+ version: 1.2.0
11
+ generated_at: "2026-02-22T16:41:00.136Z"
12
12
  generator: scripts/generate-install-manifest.js
13
13
  file_count: 1011
14
14
  files:
@@ -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 };
@@ -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
- const content = args.join(' ');
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
- console.log(`✅ Memory appended to session ${today} (JSONL)`);
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,162 @@
1
+ /**
2
+ * grimoire squads — Extended Squad Management
3
+ *
4
+ * grimoire squads list Lista squads disponíveis
5
+ * grimoire squads use <squad> Ativa squad e gera prompt para IDE
6
+ * grimoire squads info <squad> Detalhes do squad
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const path = require('path');
12
+ const fs = require('fs');
13
+
14
+ function findSquadsDir() {
15
+ const cwd = process.cwd();
16
+ const local = path.join(cwd, '.grimoire', 'squads');
17
+ const src = path.join(__dirname, '..', '..', 'squads'); // in node_modules
18
+ const srcAlt = path.join(cwd, 'squads');
19
+ if (fs.existsSync(local)) return local;
20
+ if (fs.existsSync(srcAlt)) return srcAlt;
21
+ if (fs.existsSync(src)) return src;
22
+ return null;
23
+ }
24
+
25
+ function readSquadYaml(squadDir) {
26
+ const yamlFile = path.join(squadDir, 'squad.yaml');
27
+ if (!fs.existsSync(yamlFile)) return null;
28
+ const raw = fs.readFileSync(yamlFile, 'utf8');
29
+
30
+ // Minimal YAML parser for the squad.yaml format
31
+ const squad = { agents: [], workflows: [] };
32
+ let currentList = null;
33
+ for (const line of raw.split('\n')) {
34
+ const stripped = line.trim();
35
+ if (stripped.startsWith('name:')) squad.name = stripped.slice(5).trim().replace(/^"|"$/g, '');
36
+ else if (stripped.startsWith('description:')) squad.description = stripped.slice(12).trim().replace(/^"|"$/g, '');
37
+ else if (stripped === 'agents:') currentList = 'agents';
38
+ else if (stripped === 'workflows:') currentList = 'workflows';
39
+ else if (stripped.startsWith('- id:')) { if (currentList === 'workflows') squad.workflows.push({ id: stripped.slice(5).trim() }); }
40
+ else if (stripped.startsWith('- ') && currentList === 'agents') squad.agents.push(stripped.slice(2).trim());
41
+ }
42
+ return squad;
43
+ }
44
+
45
+ function run(args) {
46
+ const sub = args[0];
47
+ const name = args[1];
48
+
49
+ switch (sub) {
50
+ case 'use': squadUse(name); break;
51
+ case 'info': squadInfo(name); break;
52
+ case 'list':
53
+ default: listSquads(); break;
54
+ }
55
+ }
56
+
57
+ // ── List ───────────────────────────────────────────────────────────────────────
58
+ function listSquads() {
59
+ const dir = findSquadsDir();
60
+ if (!dir) { console.log('\n⚠️ Squads not found. Run: npx grimoire-framework update\n'); return; }
61
+
62
+ const squads = fs.readdirSync(dir, { withFileTypes: true })
63
+ .filter(d => d.isDirectory())
64
+ .map(d => {
65
+ const sq = readSquadYaml(path.join(dir, d.name));
66
+ const agents = sq?.agents?.map(a => `@${a}`).join(', ') || '';
67
+ return { id: d.name, name: sq?.name || d.name, desc: sq?.description || '', agents };
68
+ });
69
+
70
+ console.log('\n👥 Squads disponíveis:\n');
71
+ squads.forEach(s => {
72
+ console.log(` 📦 ${s.id} — ${s.name}`);
73
+ console.log(` ${s.desc}`);
74
+ if (s.agents) console.log(` Agentes: ${s.agents}`);
75
+ console.log();
76
+ });
77
+ console.log(` 💡 Para ativar: grimoire squads use <squad>\n`);
78
+ }
79
+
80
+ // ── Use ────────────────────────────────────────────────────────────────────────
81
+ function squadUse(name) {
82
+ if (!name) { console.log('Usage: grimoire squads use <squad>\nEx: grimoire squads use fullstack\n'); return; }
83
+
84
+ const dir = findSquadsDir();
85
+ if (!dir) { console.log('\n⚠️ Squads not found. Run: npx grimoire-framework update\n'); return; }
86
+
87
+ const squadDir = path.join(dir, name);
88
+ if (!fs.existsSync(squadDir)) {
89
+ console.log(`❌ Squad "${name}" não encontrado.`);
90
+ console.log(` Squads disponíveis: grimoire squads list\n`);
91
+ return;
92
+ }
93
+
94
+ const squad = readSquadYaml(squadDir);
95
+ if (!squad) { console.log(`❌ squad.yaml inválido em squads/${name}/`); return; }
96
+
97
+ const agentMentions = squad.agents.map(a => `@${a}`).join(', ');
98
+ const agentList = squad.agents.map(a => `@${a}`).join(' · ');
99
+
100
+ // Try to detect project name
101
+ let projectName = '';
102
+ try {
103
+ const pkg = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf8'));
104
+ projectName = pkg.name || '';
105
+ } catch (_) { }
106
+
107
+ const dateStr = new Date().toLocaleDateString('pt-BR');
108
+ const context = projectName ? `${projectName} | ${dateStr}` : dateStr;
109
+
110
+ // Workflows
111
+ const workflowList = squad.workflows?.length
112
+ ? squad.workflows.map(w => ` - \`${w.id}\`: grimoire squads workflow ${name} ${w.id}`).join('\n')
113
+ : ' (nenhum workflow definido)';
114
+
115
+ const prompt = `Ative o squad **${squad.name}**: ${agentMentions}.
116
+ Contexto: ${context}
117
+ Descrição: ${squad.description}
118
+
119
+ Para iniciar: peça para ${squad.agents[0] ? `@${squad.agents[0]}` : 'qualquer agente'} apresentar o squad e sugerir o próximo passo.`;
120
+
121
+ console.log(`
122
+ 👥 Squad: ${squad.name}
123
+ ${'─'.repeat(50)}
124
+
125
+ Descrição: ${squad.description}
126
+ Agentes: ${agentList}
127
+
128
+ Workflows disponíveis:
129
+ ${workflowList}
130
+
131
+ ${'─'.repeat(50)}
132
+ 📋 Prompt para colar no chat da IDE:
133
+ ${'─'.repeat(50)}
134
+
135
+ ${prompt}
136
+
137
+ ${'─'.repeat(50)}
138
+ 💡 Copie o bloco acima e cole no chat da sua IDE (Gemini, Cursor, Claude)
139
+ `);
140
+ }
141
+
142
+ // ── Info ───────────────────────────────────────────────────────────────────────
143
+ function squadInfo(name) {
144
+ if (!name) { listSquads(); return; }
145
+ const dir = findSquadsDir();
146
+ const squadDir = dir && path.join(dir, name);
147
+ if (!squadDir || !fs.existsSync(squadDir)) {
148
+ console.log(`❌ Squad "${name}" não encontrado. Use: grimoire squads list\n`); return;
149
+ }
150
+ const squad = readSquadYaml(squadDir);
151
+ const workflows = fs.existsSync(path.join(squadDir, 'workflows'))
152
+ ? fs.readdirSync(path.join(squadDir, 'workflows')).filter(f => f.endsWith('.md'))
153
+ : [];
154
+
155
+ console.log(`\n👥 ${squad?.name || name}\n${'─'.repeat(40)}`);
156
+ console.log(` Descrição: ${squad?.description || '—'}`);
157
+ console.log(` Agentes: ${squad?.agents?.map(a => `@${a}`).join(', ') || '—'}`);
158
+ console.log(` Workflows: ${workflows.join(', ') || '(nenhum)'}`);
159
+ console.log(`\n grimoire squads use ${name} ← gerar prompt de ativação\n`);
160
+ }
161
+
162
+ module.exports = { run };
@@ -184,13 +184,42 @@ async function run(args = []) {
184
184
  }
185
185
 
186
186
  if (dryRun) {
187
- console.log(`🔍 DRY RUN — would update v${installed} → v${latest}\n`);
188
- console.log(' Directories that would be synced:');
189
- for (const { dest } of FRAMEWORK_SYNC_DIRS) {
190
- console.log(` • ${dest}`);
187
+ console.log(`🔍 DRY RUN — v${installed} → v${latest}\n`);
188
+ console.log(' Arquivos que seriam modificados:\n');
189
+
190
+ const frameworkRoot = path.join(cwd, 'node_modules', 'grimoire-framework');
191
+ if (!fs.existsSync(frameworkRoot)) {
192
+ console.log(' ⚠️ grimoire-framework não encontrado em node_modules.');
193
+ console.log(' Para um diff completo, rode npm install antes.\n');
194
+ } else {
195
+ let anyChange = false;
196
+ for (const { src, dest } of FRAMEWORK_SYNC_DIRS) {
197
+ const srcDir = path.join(frameworkRoot, src);
198
+ const destDir = path.join(cwd, dest);
199
+ if (!fs.existsSync(srcDir)) continue;
200
+
201
+ const srcFiles = new Set(fs.readdirSync(srcDir).filter(f => f.endsWith('.md') || f.endsWith('.yaml') || f.endsWith('.json')));
202
+ const destFiles = fs.existsSync(destDir)
203
+ ? new Set(fs.readdirSync(destDir).filter(f => f.endsWith('.md') || f.endsWith('.yaml') || f.endsWith('.json')))
204
+ : new Set();
205
+
206
+ const updated = [...srcFiles].filter(f => destFiles.has(f));
207
+ const added = [...srcFiles].filter(f => !destFiles.has(f));
208
+ const preserved = [...destFiles].filter(f => !srcFiles.has(f));
209
+
210
+ if (updated.length + added.length + preserved.length > 0) {
211
+ console.log(` 📁 ${dest}/`);
212
+ updated.forEach(f => console.log(` 📝 ${f} ← atualizado (framework)`));
213
+ added.forEach(f => console.log(` ➕ ${f} ← novo`));
214
+ preserved.forEach(f => console.log(` ✏️ ${f} ← preservado (customização)`));
215
+ console.log();
216
+ anyChange = true;
217
+ }
218
+ }
219
+ if (!anyChange) console.log(' (nenhuma mudança detectada)\n');
191
220
  }
192
- console.log('\n Custom files (only in project) would be PRESERVED.');
193
- console.log(' Run without --dry-run to apply.\n');
221
+
222
+ console.log(' Execute sem --dry-run para aplicar.\n');
194
223
  return;
195
224
  }
196
225
 
@@ -107,7 +107,7 @@ async function main() {
107
107
  await require('./commands/metrics').run(args.slice(1));
108
108
  break;
109
109
  case 'squads':
110
- handleSquads(args.slice(1));
110
+ await require('./commands/squads').run(args.slice(1));
111
111
  break;
112
112
  case 'agents':
113
113
  handleAgents(args.slice(1));
@@ -124,6 +124,12 @@ async function main() {
124
124
  case 'pro':
125
125
  await require('./commands/pro').run(args.slice(1));
126
126
  break;
127
+ case 'config':
128
+ require('./commands/config').run(args.slice(1));
129
+ break;
130
+ case 'hooks':
131
+ require('./commands/hooks').run(args.slice(1));
132
+ break;
127
133
  case 'status':
128
134
  handleStatus();
129
135
  break;
@@ -489,48 +495,55 @@ COMANDOS ESSENCIAIS:
489
495
  grimoire whoami Contexto da sessão e como usar agentes
490
496
  grimoire doctor Diagnóstico completo (checks + sugestões)
491
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
492
501
 
493
502
  AGENTES:
494
- grimoire agents list Lista todos os agentes com personas
495
- grimoire agent create 🆕 Wizard para criar agente customizado
496
- grimoire agent list Lista agentes (com custom)
497
- grimoire agent edit <n> Editar agente no $EDITOR
498
- grimoire agent remove <n> Remover agente customizado
503
+ grimoire agents list Lista todos os agentes com personas
504
+ grimoire agent create 🆕 Wizard para criar agente customizado
505
+ grimoire agent validate <n> 🆕 Valida YAML e campos obrigatórios
506
+ grimoire agent edit <n> Editar agente no EDITOR
507
+ grimoire agent remove <n> Remover agente customizado
499
508
 
500
509
  SQUADS:
501
- grimoire squads list Lista squads disponíveis (fullstack/planning/devops)
510
+ grimoire squads list Lista squads (fullstack/planning/devops)
511
+ grimoire squads use <squad> 🆕 Gera prompt de ativação para IDE
512
+ grimoire squads info <squad> 🆕 Detalhes do squad
502
513
 
503
514
  MEMÓRIA:
504
515
  grimoire memory save "texto" Salvar entrada na sessão
516
+ grimoire memory save --tag <t> "x" 🆕 Salvar com tag
505
517
  grimoire memory show [--last 10] Ver entradas de hoje
506
- grimoire memory search "termo" 🆕 Buscar em todas as sessões
507
- grimoire memory export 🆕 Exportar para Markdown
508
- grimoire memory clear --older-than 30 🆕 Limpar sessões antigas
509
- grimoire memory list Listar todas as sessões
518
+ grimoire memory search "termo" Buscar em todas as sessões
519
+ grimoire memory list-tags 🆕 Listar todas as tags
520
+ grimoire memory export Exportar para Markdown
521
+ grimoire memory clear --older-than 30 Limpar sessões antigas
510
522
 
511
523
  MÉTRICAS:
512
- grimoire metrics Dashboard do mês atual
513
- grimoire metrics --period week 🆕 Últimos 7 dias
514
- grimoire metrics --period all 🆕 Histórico completo
515
- grimoire metrics export --csv 🆕 Exportar para CSV
516
- grimoire metrics track <tipo> [msg] 🆕 Registrar evento manual
524
+ grimoire metrics Dashboard do mês atual
525
+ grimoire metrics --period week Últimos 7 dias
526
+ grimoire metrics export --csv Exportar para CSV
527
+ grimoire metrics track <t> [msg] Registrar evento manual
528
+
529
+ HOOKS (git auto-tracking):
530
+ grimoire hooks install 🆕 Instala post-commit hook
531
+ grimoire hooks uninstall 🆕 Remove o hook
532
+ grimoire hooks status 🆕 Verifica se o hook está ativo
517
533
 
518
534
  SYNC GLOBAL:
519
- grimoire sync --global 🆕 Envia agentes → ~/.grimoire/agents/
520
- grimoire sync --from-global 🆕 Puxa agentes do store global
521
- grimoire sync --list 🆕 Lista agentes globais
535
+ grimoire sync --global Envia agentes → ~/.grimoire/agents/
536
+ grimoire sync --from-global Puxa agentes do store global
522
537
 
523
538
  MARKETPLACE:
524
- grimoire marketplace list 🆕 Navega agentes da comunidade
525
- grimoire marketplace install <slug> 🆕 Instala agente do marketplace
526
- grimoire marketplace search <termo> 🆕 Busca por especialidade
527
- grimoire marketplace submit 🆕 Guia para publicar seu agente
539
+ grimoire marketplace list Navega agentes da comunidade
540
+ grimoire marketplace install <slug> Instala agente do marketplace
541
+ grimoire marketplace search <termo> Busca por especialidade
528
542
 
529
543
  GRIMOIRE PRO:
530
- grimoire pro activate <chave> 🆕 Ativa licença Pro
531
- grimoire pro status 🆕 Status da licença e features
532
- grimoire pro features 🆕 Lista features Pro
533
- grimoire pro deactivate 🆕 Remove licença
544
+ grimoire pro activate <chave> Ativa licença Pro
545
+ grimoire pro status Status da licença e features
546
+ grimoire pro features Lista features Pro
534
547
 
535
548
  OUTROS:
536
549
  grimoire install Instalar/reset setup no projeto
@@ -539,7 +552,7 @@ OUTROS:
539
552
 
540
553
  QUICK START:
541
554
  npx grimoire-framework install Setup em novo projeto
542
- grimoire status Tudo funcionando?
555
+ grimoire squads use fullstack Ativar squad fullstack
543
556
  @dev @qa @architect No chat da sua IDE
544
557
  `);
545
558
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grimoire-framework",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "description": "Grimoire: AI-Orchestrated System for Full Stack Development - Core Framework",
5
5
  "publishConfig": {
6
6
  "access": "public"