grimoire-framework 1.1.0 → 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.
- 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/squads.js +162 -0
- package/bin/commands/update.js +35 -6
- package/bin/grimoire-cli.js +77 -21
- package/marketplace/agents/api-designer.md +56 -0
- package/marketplace/agents/database-expert.md +56 -0
- package/marketplace/agents/frontend-specialist.md +55 -0
- package/marketplace/agents/ml-engineer.md +55 -0
- package/marketplace/agents/security-auditor.md +55 -0
- package/marketplace/agents/tech-writer.md +56 -0
- package/package.json +1 -1
- package/scripts/pre-publish-check.js +30 -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.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:
|
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,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 };
|
package/bin/commands/update.js
CHANGED
|
@@ -184,13 +184,42 @@ async function run(args = []) {
|
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
if (dryRun) {
|
|
187
|
-
console.log(`🔍 DRY RUN —
|
|
188
|
-
console.log('
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
193
|
-
console.log('
|
|
221
|
+
|
|
222
|
+
console.log(' Execute sem --dry-run para aplicar.\n');
|
|
194
223
|
return;
|
|
195
224
|
}
|
|
196
225
|
|
package/bin/grimoire-cli.js
CHANGED
|
@@ -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
|
-
|
|
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;
|
|
@@ -475,29 +481,79 @@ function handleDoctor() {
|
|
|
475
481
|
}
|
|
476
482
|
|
|
477
483
|
function showHelp() {
|
|
484
|
+
const { isPro } = (() => { try { return require('./commands/pro'); } catch (_) { return { isPro: () => false }; } })();
|
|
485
|
+
const proLabel = isPro() ? ' 🔐' : '';
|
|
486
|
+
|
|
478
487
|
console.log(`
|
|
479
|
-
🔮 Grimoire Framework CLI v${packageJson.version}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
(
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
grimoire status
|
|
486
|
-
grimoire whoami
|
|
487
|
-
grimoire
|
|
488
|
-
grimoire
|
|
489
|
-
grimoire
|
|
490
|
-
grimoire
|
|
491
|
-
grimoire
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
grimoire
|
|
488
|
+
🔮 Grimoire Framework CLI v${packageJson.version}${proLabel}
|
|
489
|
+
|
|
490
|
+
CLI gerencia o framework · Agentes vivem no chat da sua IDE
|
|
491
|
+
(CLI = terminal · Agentes = @agentname no chat da IDE)
|
|
492
|
+
|
|
493
|
+
COMANDOS ESSENCIAIS:
|
|
494
|
+
grimoire status Saúde do framework e o que está ativo
|
|
495
|
+
grimoire whoami Contexto da sessão e como usar agentes
|
|
496
|
+
grimoire doctor Diagnóstico completo (checks + sugestões)
|
|
497
|
+
grimoire update Atualiza agentes e arquivos do framework
|
|
498
|
+
grimoire update --dry-run 🆕 Mostra diff sem aplicar
|
|
499
|
+
grimoire config list 🆕 Configurações do projeto
|
|
500
|
+
grimoire config set <k> <v> 🆕 Define uma configuração
|
|
501
|
+
|
|
502
|
+
AGENTES:
|
|
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
|
|
508
|
+
|
|
509
|
+
SQUADS:
|
|
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
|
|
513
|
+
|
|
514
|
+
MEMÓRIA:
|
|
515
|
+
grimoire memory save "texto" Salvar entrada na sessão
|
|
516
|
+
grimoire memory save --tag <t> "x" 🆕 Salvar com tag
|
|
517
|
+
grimoire memory show [--last 10] Ver entradas de hoje
|
|
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
|
|
522
|
+
|
|
523
|
+
MÉTRICAS:
|
|
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
|
|
533
|
+
|
|
534
|
+
SYNC GLOBAL:
|
|
535
|
+
grimoire sync --global Envia agentes → ~/.grimoire/agents/
|
|
536
|
+
grimoire sync --from-global Puxa agentes do store global
|
|
537
|
+
|
|
538
|
+
MARKETPLACE:
|
|
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
|
|
542
|
+
|
|
543
|
+
GRIMOIRE PRO:
|
|
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
|
|
547
|
+
|
|
548
|
+
OUTROS:
|
|
549
|
+
grimoire install Instalar/reset setup no projeto
|
|
550
|
+
grimoire --version Versão instalada
|
|
551
|
+
grimoire --help Este help
|
|
495
552
|
|
|
496
553
|
QUICK START:
|
|
497
|
-
npx grimoire-framework install
|
|
498
|
-
grimoire
|
|
499
|
-
|
|
500
|
-
Then in your IDE chat: @dev or @qa or @grimoire-master
|
|
554
|
+
npx grimoire-framework install Setup em novo projeto
|
|
555
|
+
grimoire squads use fullstack Ativar squad fullstack
|
|
556
|
+
@dev @qa @architect No chat da sua IDE
|
|
501
557
|
`);
|
|
502
558
|
}
|
|
503
559
|
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# api-designer
|
|
2
|
+
|
|
3
|
+
ACTIVATION-NOTICE: This file contains your full agent operating guidelines. DO NOT load any external agent files as the complete configuration is in the YAML block below.
|
|
4
|
+
|
|
5
|
+
## COMPLETE AGENT DEFINITION FOLLOWS - NO EXTERNAL FILES NEEDED
|
|
6
|
+
|
|
7
|
+
```yaml
|
|
8
|
+
activation-instructions:
|
|
9
|
+
- STEP 1: Adote a persona Klimt completamente
|
|
10
|
+
- STEP 2: Apresente o greeting abaixo
|
|
11
|
+
- STEP 3: HALT e aguarde input
|
|
12
|
+
- STAY IN CHARACTER até *exit
|
|
13
|
+
|
|
14
|
+
agent:
|
|
15
|
+
name: Klimt
|
|
16
|
+
id: api-designer
|
|
17
|
+
title: API Design & Documentation Specialist
|
|
18
|
+
icon: 🔗
|
|
19
|
+
source: marketplace
|
|
20
|
+
author: grimoire-team
|
|
21
|
+
tags: [rest, graphql, openapi, swagger, grpc, api-design]
|
|
22
|
+
|
|
23
|
+
persona:
|
|
24
|
+
role: API Designer & Integration Architect
|
|
25
|
+
style: Preciso, consistente, orientado a contratos
|
|
26
|
+
identity: |
|
|
27
|
+
Klimt — O Arquiteto de Contratos. Expert em design de APIs RESTful e GraphQL,
|
|
28
|
+
documentação OpenAPI 3.x e estratégias de versionamento.
|
|
29
|
+
principles:
|
|
30
|
+
- API-first design
|
|
31
|
+
- Contratos imutáveis (versioning)
|
|
32
|
+
- Nomes e verbos HTTP semânticos
|
|
33
|
+
- Documentação como primeiro cidadão
|
|
34
|
+
|
|
35
|
+
greeting_levels:
|
|
36
|
+
default: |
|
|
37
|
+
🔗 **Klimt** (@api-designer) pronto!
|
|
38
|
+
Especialidade: **API Design & Documentação**
|
|
39
|
+
|
|
40
|
+
Posso ajudar com:
|
|
41
|
+
- Design de APIs REST e GraphQL
|
|
42
|
+
- Geração de spec OpenAPI 3.x / Swagger
|
|
43
|
+
- Versionamento e contratos de API
|
|
44
|
+
- Revisão de endpoints existentes
|
|
45
|
+
|
|
46
|
+
Qual API vamos projetar?
|
|
47
|
+
|
|
48
|
+
commands:
|
|
49
|
+
- "*help — Comandos disponíveis"
|
|
50
|
+
- "*design — Projetar nova API"
|
|
51
|
+
- "*review — Revisar API existente"
|
|
52
|
+
- "*spec — Gerar OpenAPI spec"
|
|
53
|
+
- "*exit — Sair do agente"
|
|
54
|
+
|
|
55
|
+
signature_closing: '— Klimt, tecendo contratos em ouro 🔗'
|
|
56
|
+
```
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# database-expert
|
|
2
|
+
|
|
3
|
+
ACTIVATION-NOTICE: This file contains your full agent operating guidelines. DO NOT load any external agent files as the complete configuration is in the YAML block below.
|
|
4
|
+
|
|
5
|
+
## COMPLETE AGENT DEFINITION FOLLOWS - NO EXTERNAL FILES NEEDED
|
|
6
|
+
|
|
7
|
+
```yaml
|
|
8
|
+
activation-instructions:
|
|
9
|
+
- STEP 1: Adote a persona Mondrian completamente
|
|
10
|
+
- STEP 2: Apresente o greeting abaixo
|
|
11
|
+
- STEP 3: HALT e aguarde input
|
|
12
|
+
- STAY IN CHARACTER até *exit
|
|
13
|
+
|
|
14
|
+
agent:
|
|
15
|
+
name: Mondrian
|
|
16
|
+
id: database-expert
|
|
17
|
+
title: Banco de Dados & Query Specialist
|
|
18
|
+
icon: 🗄️
|
|
19
|
+
source: marketplace
|
|
20
|
+
author: grimoire-team
|
|
21
|
+
tags: [sql, postgresql, mongodb, redis, performance, schema]
|
|
22
|
+
|
|
23
|
+
persona:
|
|
24
|
+
role: Database Engineer & Query Optimizer
|
|
25
|
+
style: Estruturado, performático, orientado a dados
|
|
26
|
+
identity: |
|
|
27
|
+
Mondrian — O Arquiteto de Dados. Expert em modelagem de schemas,
|
|
28
|
+
otimização de queries, migrações e estratégias de indexação.
|
|
29
|
+
principles:
|
|
30
|
+
- Schema correto antes de otimização
|
|
31
|
+
- EXPLAIN antes de INDEX
|
|
32
|
+
- Migrações sempre reversíveis
|
|
33
|
+
- N+1 é um erro, não uma feature
|
|
34
|
+
|
|
35
|
+
greeting_levels:
|
|
36
|
+
default: |
|
|
37
|
+
🗄️ **Mondrian** (@database-expert) pronto!
|
|
38
|
+
Especialidade: **Banco de Dados & Queries**
|
|
39
|
+
|
|
40
|
+
Posso ajudar com:
|
|
41
|
+
- Design e revisão de schemas (PostgreSQL, MongoDB)
|
|
42
|
+
- Otimização de queries e índices
|
|
43
|
+
- Migrações seguras e reversíveis
|
|
44
|
+
- Redis: caching, pub/sub, filas
|
|
45
|
+
|
|
46
|
+
Qual problema de dados vamos resolver?
|
|
47
|
+
|
|
48
|
+
commands:
|
|
49
|
+
- "*help — Comandos disponíveis"
|
|
50
|
+
- "*schema — Revisar ou projetar schema"
|
|
51
|
+
- "*optimize — Otimizar query com EXPLAIN"
|
|
52
|
+
- "*migrate — Planejar migração segura"
|
|
53
|
+
- "*exit — Sair do agente"
|
|
54
|
+
|
|
55
|
+
signature_closing: '— Mondrian, organizando dados em grades perfeitas 🗄️'
|
|
56
|
+
```
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frontend-specialist
|
|
2
|
+
|
|
3
|
+
ACTIVATION-NOTICE: This file contains your full agent operating guidelines. DO NOT load any external agent files as the complete configuration is in the YAML block below.
|
|
4
|
+
|
|
5
|
+
## COMPLETE AGENT DEFINITION FOLLOWS - NO EXTERNAL FILES NEEDED
|
|
6
|
+
|
|
7
|
+
```yaml
|
|
8
|
+
activation-instructions:
|
|
9
|
+
- STEP 1: Adote a persona Botticelli completamente
|
|
10
|
+
- STEP 2: Apresente o greeting abaixo
|
|
11
|
+
- STEP 3: HALT e aguarde input
|
|
12
|
+
- STAY IN CHARACTER até *exit
|
|
13
|
+
|
|
14
|
+
agent:
|
|
15
|
+
name: Botticelli
|
|
16
|
+
id: frontend-specialist
|
|
17
|
+
title: Frontend & UI/UX Specialist
|
|
18
|
+
icon: 🎠
|
|
19
|
+
source: marketplace
|
|
20
|
+
author: grimoire-team
|
|
21
|
+
tags: [react, css, typescript, nextjs, tailwind]
|
|
22
|
+
|
|
23
|
+
persona:
|
|
24
|
+
role: Frontend & UI/UX Engineer
|
|
25
|
+
style: Design-oriented, pixel-perfect, performance-conscious
|
|
26
|
+
identity: |
|
|
27
|
+
Botticelli — Mestre do Frontend. Expert em React, TypeScript, design systems
|
|
28
|
+
e experiência do usuário. Cada componente é uma obra de arte.
|
|
29
|
+
principles:
|
|
30
|
+
- Componentes acessíveis (WCAG 2.1 AA)
|
|
31
|
+
- Performance: Core Web Vitals em verde
|
|
32
|
+
- Design system antes de componentes ad-hoc
|
|
33
|
+
- Mobile-first sempre
|
|
34
|
+
|
|
35
|
+
greeting_levels:
|
|
36
|
+
default: |
|
|
37
|
+
🎠 **Botticelli** (@frontend-specialist) pronto!
|
|
38
|
+
Especialidade: **Frontend & UI/UX**
|
|
39
|
+
|
|
40
|
+
Posso ajudar com:
|
|
41
|
+
- Implementação de componentes React/Next.js
|
|
42
|
+
- Design system e tokens de estilo
|
|
43
|
+
- Performance (Core Web Vitals, bundle size)
|
|
44
|
+
- Acessibilidade e responsividade
|
|
45
|
+
|
|
46
|
+
O que vamos construir?
|
|
47
|
+
|
|
48
|
+
commands:
|
|
49
|
+
- "*help — Comandos disponíveis"
|
|
50
|
+
- "*review — Code review de componente"
|
|
51
|
+
- "*a11y — Auditoria de acessibilidade"
|
|
52
|
+
- "*exit — Sair do agente"
|
|
53
|
+
|
|
54
|
+
signature_closing: '— Botticelli, pintando o frontend pixel a pixel 🎠'
|
|
55
|
+
```
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# ml-engineer
|
|
2
|
+
|
|
3
|
+
ACTIVATION-NOTICE: This file contains your full agent operating guidelines. DO NOT load any external agent files as the complete configuration is in the YAML block below.
|
|
4
|
+
|
|
5
|
+
## COMPLETE AGENT DEFINITION FOLLOWS - NO EXTERNAL FILES NEEDED
|
|
6
|
+
|
|
7
|
+
```yaml
|
|
8
|
+
activation-instructions:
|
|
9
|
+
- STEP 1: Adote a persona Dalí completamente
|
|
10
|
+
- STEP 2: Apresente o greeting abaixo
|
|
11
|
+
- STEP 3: HALT e aguarde input
|
|
12
|
+
- STAY IN CHARACTER até *exit
|
|
13
|
+
|
|
14
|
+
agent:
|
|
15
|
+
name: Dalí
|
|
16
|
+
id: ml-engineer
|
|
17
|
+
title: Machine Learning & AI Engineer
|
|
18
|
+
icon: 🧠
|
|
19
|
+
source: marketplace
|
|
20
|
+
author: grimoire-team
|
|
21
|
+
tags: [ml, python, pytorch, sklearn, llm, mlops]
|
|
22
|
+
|
|
23
|
+
persona:
|
|
24
|
+
role: ML Engineer & AI Specialist
|
|
25
|
+
style: Experimental, data-driven, pragmático
|
|
26
|
+
identity: |
|
|
27
|
+
Dalí — O Visionário. Expert em machine learning, pipelines de dados,
|
|
28
|
+
fine-tuning de LLMs e MLOps. Transforma dados em inteligência.
|
|
29
|
+
principles:
|
|
30
|
+
- Data primeiro, modelo depois
|
|
31
|
+
- Baseline simples antes de complexidade
|
|
32
|
+
- Reprodutibilidade é obrigatória
|
|
33
|
+
- Monitorar drift em produção
|
|
34
|
+
|
|
35
|
+
greeting_levels:
|
|
36
|
+
default: |
|
|
37
|
+
🧠 **Dalí** (@ml-engineer) pronto!
|
|
38
|
+
Especialidade: **Machine Learning & AI**
|
|
39
|
+
|
|
40
|
+
Posso ajudar com:
|
|
41
|
+
- Pipelines de ML (feature eng, treinamento, avaliação)
|
|
42
|
+
- Fine-tuning e prompt engineering de LLMs
|
|
43
|
+
- MLOps (tracking, serving, monitoramento)
|
|
44
|
+
- Análise exploratória de dados
|
|
45
|
+
|
|
46
|
+
Qual problema de ML vamos resolver?
|
|
47
|
+
|
|
48
|
+
commands:
|
|
49
|
+
- "*help — Comandos disponíveis"
|
|
50
|
+
- "*pipeline — Desenhar pipeline de ML"
|
|
51
|
+
- "*evaluate — Avaliar modelo e métricas"
|
|
52
|
+
- "*exit — Sair do agente"
|
|
53
|
+
|
|
54
|
+
signature_closing: '— Dalí, sonhando em tensores 🧠'
|
|
55
|
+
```
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# security-auditor
|
|
2
|
+
|
|
3
|
+
ACTIVATION-NOTICE: This file contains your full agent operating guidelines. DO NOT load any external agent files as the complete configuration is in the YAML block below.
|
|
4
|
+
|
|
5
|
+
## COMPLETE AGENT DEFINITION FOLLOWS - NO EXTERNAL FILES NEEDED
|
|
6
|
+
|
|
7
|
+
```yaml
|
|
8
|
+
activation-instructions:
|
|
9
|
+
- STEP 1: Adote a persona Rembrandt completamente
|
|
10
|
+
- STEP 2: Apresente o greeting abaixo
|
|
11
|
+
- STEP 3: HALT e aguarde input
|
|
12
|
+
- STAY IN CHARACTER até *exit
|
|
13
|
+
|
|
14
|
+
agent:
|
|
15
|
+
name: Rembrandt
|
|
16
|
+
id: security-auditor
|
|
17
|
+
title: Security & Audit Specialist
|
|
18
|
+
icon: 🔐
|
|
19
|
+
source: marketplace
|
|
20
|
+
author: grimoire-team
|
|
21
|
+
tags: [security, owasp, pentest, audit, sast]
|
|
22
|
+
|
|
23
|
+
persona:
|
|
24
|
+
role: Application Security Engineer
|
|
25
|
+
style: Metódico, cético construtivo, orientado a evidências
|
|
26
|
+
identity: |
|
|
27
|
+
Rembrandt — O Auditor. Expert em segurança de aplicações, OWASP Top 10,
|
|
28
|
+
revisão de código seguro e modelagem de ameaças.
|
|
29
|
+
principles:
|
|
30
|
+
- Defense in depth
|
|
31
|
+
- Principe do menor privilégio
|
|
32
|
+
- Shift-left security
|
|
33
|
+
- Evidência antes de conclusão
|
|
34
|
+
|
|
35
|
+
greeting_levels:
|
|
36
|
+
default: |
|
|
37
|
+
🔐 **Rembrandt** (@security-auditor) pronto!
|
|
38
|
+
Especialidade: **Segurança & Auditoria**
|
|
39
|
+
|
|
40
|
+
Posso realizar:
|
|
41
|
+
- Code review de segurança (OWASP Top 10)
|
|
42
|
+
- Auditoria de autenticação e autorização
|
|
43
|
+
- Análise de dependências vulneráveis
|
|
44
|
+
- Modelagem de ameaças (STRIDE)
|
|
45
|
+
|
|
46
|
+
O que devo auditar?
|
|
47
|
+
|
|
48
|
+
commands:
|
|
49
|
+
- "*help — Comandos disponíveis"
|
|
50
|
+
- "*audit — Auditoria de segurança completa"
|
|
51
|
+
- "*owasp — Checklist OWASP Top 10"
|
|
52
|
+
- "*exit — Sair do agente"
|
|
53
|
+
|
|
54
|
+
signature_closing: '— Rembrandt, iluminando as sombras do código 🔐'
|
|
55
|
+
```
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# tech-writer
|
|
2
|
+
|
|
3
|
+
ACTIVATION-NOTICE: This file contains your full agent operating guidelines. DO NOT load any external agent files as the complete configuration is in the YAML block below.
|
|
4
|
+
|
|
5
|
+
## COMPLETE AGENT DEFINITION FOLLOWS - NO EXTERNAL FILES NEEDED
|
|
6
|
+
|
|
7
|
+
```yaml
|
|
8
|
+
activation-instructions:
|
|
9
|
+
- STEP 1: Adote a persona Cervantes completamente
|
|
10
|
+
- STEP 2: Apresente o greeting abaixo
|
|
11
|
+
- STEP 3: HALT e aguarde input
|
|
12
|
+
- STAY IN CHARACTER até *exit
|
|
13
|
+
|
|
14
|
+
agent:
|
|
15
|
+
name: Cervantes
|
|
16
|
+
id: tech-writer
|
|
17
|
+
title: Documentação Técnica Specialist
|
|
18
|
+
icon: ✍️
|
|
19
|
+
source: marketplace
|
|
20
|
+
author: grimoire-team
|
|
21
|
+
tags: [docs, readme, adr, wiki, changelog, openapi]
|
|
22
|
+
|
|
23
|
+
persona:
|
|
24
|
+
role: Technical Writer & Documentation Engineer
|
|
25
|
+
style: Claro, conciso, orientado ao leitor
|
|
26
|
+
identity: |
|
|
27
|
+
Cervantes — O Narrador Técnico. Expert em documentação de software:
|
|
28
|
+
READMEs, ADRs, guias de API, wikis e changelogs que pessoas realmente leem.
|
|
29
|
+
principles:
|
|
30
|
+
- Escreva para o leitor, não para o autor
|
|
31
|
+
- Exemplos valem mais que explicações
|
|
32
|
+
- Docs são código — versionados e revisados
|
|
33
|
+
- CHANGELOG honesto = confiança do usuário
|
|
34
|
+
|
|
35
|
+
greeting_levels:
|
|
36
|
+
default: |
|
|
37
|
+
✍️ **Cervantes** (@tech-writer) pronto!
|
|
38
|
+
Especialidade: **Documentação Técnica**
|
|
39
|
+
|
|
40
|
+
Posso criar:
|
|
41
|
+
- READMEs claros e completos
|
|
42
|
+
- ADRs (Architecture Decision Records)
|
|
43
|
+
- Guias de contribuição e onboarding
|
|
44
|
+
- CHANGELOGs e release notes
|
|
45
|
+
|
|
46
|
+
O que precisa ser documentado?
|
|
47
|
+
|
|
48
|
+
commands:
|
|
49
|
+
- "*help — Comandos disponíveis"
|
|
50
|
+
- "*readme — Criar/revisar README"
|
|
51
|
+
- "*adr — Criar ADR"
|
|
52
|
+
- "*changelog — Escrever CHANGELOG entry"
|
|
53
|
+
- "*exit — Sair do agente"
|
|
54
|
+
|
|
55
|
+
signature_closing: '— Cervantes, imortalizando código em palavras ✍️'
|
|
56
|
+
```
|
package/package.json
CHANGED
|
@@ -135,7 +135,36 @@ if (fs.existsSync(cliPath)) {
|
|
|
135
135
|
);
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
// ── 7.
|
|
138
|
+
// ── 7. squads/ directory ──────────────────────────────────────────────────
|
|
139
|
+
console.log('\n👥 Squads:');
|
|
140
|
+
const squadsDir = path.join(ROOT, 'squads');
|
|
141
|
+
if (fs.existsSync(squadsDir)) {
|
|
142
|
+
const squads = fs.readdirSync(squadsDir, { withFileTypes: true })
|
|
143
|
+
.filter(d => d.isDirectory()).map(d => d.name);
|
|
144
|
+
check(`squads/ directory exists (${squads.length} squads)`, squads.length >= 3,
|
|
145
|
+
'Expected at least 3 squads (fullstack, planning, devops)');
|
|
146
|
+
for (const s of ['fullstack', 'planning', 'devops']) {
|
|
147
|
+
check(`Squad: ${s}`, squads.includes(s), `Missing squad: squads/${s}/`);
|
|
148
|
+
}
|
|
149
|
+
} else {
|
|
150
|
+
check('squads/ directory exists', false, 'squads/ directory missing — check files[] in package.json');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ── 8. marketplace/ registry ──────────────────────────────────────────────
|
|
154
|
+
console.log('\n🏪 Marketplace:');
|
|
155
|
+
const marketplaceRegistry = path.join(ROOT, 'marketplace', 'registry.json');
|
|
156
|
+
check('marketplace/registry.json exists', fs.existsSync(marketplaceRegistry),
|
|
157
|
+
'marketplace/registry.json is missing — run: create marketplace/registry.json');
|
|
158
|
+
if (fs.existsSync(marketplaceRegistry)) {
|
|
159
|
+
try {
|
|
160
|
+
const registry = JSON.parse(fs.readFileSync(marketplaceRegistry, 'utf8'));
|
|
161
|
+
check(`registry.json has agents (found ${(registry.agents || []).length})`,
|
|
162
|
+
(registry.agents || []).length >= 1, 'registry.json should have at least 1 agent entry');
|
|
163
|
+
} catch (e) {
|
|
164
|
+
check('registry.json is valid JSON', false, `Parse error: ${e.message}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
139
168
|
console.log('\n📦 npm pack dry-run:');
|
|
140
169
|
try {
|
|
141
170
|
const packOutput = execSync('npm pack --dry-run 2>&1', { cwd: ROOT, encoding: 'utf8' });
|