grimoire-framework 1.5.0 โ†’ 1.7.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.5.0
11
- generated_at: "2026-02-22T17:38:38.314Z"
10
+ version: 1.7.0
11
+ generated_at: "2026-02-22T18:11:23.264Z"
12
12
  generator: scripts/generate-install-manifest.js
13
13
  file_count: 1011
14
14
  files:
@@ -0,0 +1,242 @@
1
+ /**
2
+ * grimoire analyze โ€” Project Stack Analyzer
3
+ *
4
+ * ๐ŸŽจ Da Vinci: detecta stack, frameworks, estrutura e sugere atividades
5
+ *
6
+ * grimoire analyze Analisa pasta atual e sugere atividades
7
+ * grimoire analyze --save Salva anรกlise como memรณrias no RAG
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
+ if (fs.existsSync(path.join(cwd, '.grimoire'))) return path.join(cwd, '.grimoire');
18
+ if (fs.existsSync(path.join(cwd, 'grimoire', '.grimoire'))) return path.join(cwd, 'grimoire', '.grimoire');
19
+ return null;
20
+ }
21
+
22
+ // โ”€โ”€ Detectors โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
23
+ function detectFromPackageJson(cwd) {
24
+ const pkgPath = path.join(cwd, 'package.json');
25
+ if (!fs.existsSync(pkgPath)) return null;
26
+
27
+ let pkg;
28
+ try { pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); } catch (_) { return null; }
29
+
30
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
31
+ const result = { name: pkg.name, version: pkg.version, runtime: 'Node.js', frameworks: [], tools: [], suggestions: [] };
32
+
33
+ // Frameworks
34
+ if (deps['next']) result.frameworks.push('Next.js');
35
+ if (deps['nuxt']) result.frameworks.push('Nuxt.js');
36
+ if (deps['react']) result.frameworks.push('React');
37
+ if (deps['vue']) result.frameworks.push('Vue');
38
+ if (deps['@angular/core']) result.frameworks.push('Angular');
39
+ if (deps['svelte']) result.frameworks.push('Svelte');
40
+ if (deps['express']) result.frameworks.push('Express');
41
+ if (deps['fastify']) result.frameworks.push('Fastify');
42
+ if (deps['@nestjs/core']) result.frameworks.push('NestJS');
43
+ if (deps['hono']) result.frameworks.push('Hono');
44
+
45
+ // Databases
46
+ if (deps['prisma'] || deps['@prisma/client']) result.tools.push('Prisma ORM');
47
+ if (deps['mongoose']) result.tools.push('MongoDB/Mongoose');
48
+ if (deps['sequelize']) result.tools.push('Sequelize');
49
+ if (deps['drizzle-orm']) result.tools.push('Drizzle ORM');
50
+ if (deps['pg']) result.tools.push('PostgreSQL');
51
+ if (deps['mysql2']) result.tools.push('MySQL');
52
+ if (deps['sqlite3'] || deps['better-sqlite3']) result.tools.push('SQLite');
53
+ if (deps['redis'] || deps['ioredis']) result.tools.push('Redis');
54
+
55
+ // Auth
56
+ if (deps['jsonwebtoken'] || deps['jose']) result.tools.push('JWT Auth');
57
+ if (deps['passport']) result.tools.push('Passport.js');
58
+ if (deps['next-auth']) result.tools.push('NextAuth');
59
+ if (deps['clerk']) result.tools.push('Clerk');
60
+
61
+ // Testing
62
+ if (deps['jest']) result.tools.push('Jest');
63
+ if (deps['vitest']) result.tools.push('Vitest');
64
+ if (deps['cypress']) result.tools.push('Cypress E2E');
65
+ if (deps['playwright']) result.tools.push('Playwright E2E');
66
+
67
+ // TypeScript
68
+ if (deps['typescript'] || pkg.scripts?.['typecheck']) result.tools.push('TypeScript');
69
+
70
+ // Hosting/Deploy
71
+ if (fs.existsSync(path.join(cwd, 'vercel.json'))) result.tools.push('Vercel');
72
+ if (fs.existsSync(path.join(cwd, 'railway.json'))) result.tools.push('Railway');
73
+ if (fs.existsSync(path.join(cwd, '.fly.toml'))) result.tools.push('Fly.io');
74
+
75
+ return result;
76
+ }
77
+
78
+ function detectFromFiles(cwd) {
79
+ const result = { runtime: null, frameworks: [], tools: [] };
80
+
81
+ if (fs.existsSync(path.join(cwd, 'requirements.txt')) || fs.existsSync(path.join(cwd, 'pyproject.toml'))) {
82
+ result.runtime = 'Python';
83
+ try {
84
+ const req = fs.readFileSync(path.join(cwd, 'requirements.txt'), 'utf8');
85
+ if (req.includes('fastapi')) result.frameworks.push('FastAPI');
86
+ if (req.includes('django')) result.frameworks.push('Django');
87
+ if (req.includes('flask')) result.frameworks.push('Flask');
88
+ } catch (_) { }
89
+ }
90
+
91
+ if (fs.existsSync(path.join(cwd, 'Cargo.toml'))) result.runtime = 'Rust';
92
+ if (fs.existsSync(path.join(cwd, 'go.mod'))) result.runtime = 'Go';
93
+ if (fs.existsSync(path.join(cwd, 'pom.xml'))) result.runtime = 'Java/Maven';
94
+ if (fs.existsSync(path.join(cwd, 'build.gradle'))) result.runtime = 'Java/Gradle';
95
+
96
+ if (fs.existsSync(path.join(cwd, 'docker-compose.yml'))) result.tools.push('Docker Compose');
97
+ if (fs.existsSync(path.join(cwd, 'Dockerfile'))) result.tools.push('Docker');
98
+ if (fs.existsSync(path.join(cwd, '.github', 'workflows'))) result.tools.push('GitHub Actions CI/CD');
99
+ if (fs.existsSync(path.join(cwd, 'terraform'))) result.tools.push('Terraform');
100
+
101
+ return result;
102
+ }
103
+
104
+ function countFiles(cwd) {
105
+ const counts = {};
106
+ function walk(dir, depth = 0) {
107
+ if (depth > 3) return;
108
+ try {
109
+ for (const f of fs.readdirSync(dir)) {
110
+ if (f.startsWith('.') || f === 'node_modules' || f === '.git') continue;
111
+ const full = path.join(dir, f);
112
+ const stat = fs.statSync(full);
113
+ if (stat.isDirectory()) { walk(full, depth + 1); }
114
+ else {
115
+ const ext = path.extname(f).toLowerCase();
116
+ if (ext) counts[ext] = (counts[ext] || 0) + 1;
117
+ }
118
+ }
119
+ } catch (_) { }
120
+ }
121
+ walk(cwd);
122
+ return counts;
123
+ }
124
+
125
+ function generateSuggestions(pkg, files, extra) {
126
+ const suggestions = [];
127
+
128
+ const testFiles = (files['.test.js'] || 0) + (files['.spec.js'] || 0) + (files['.test.ts'] || 0);
129
+ if (testFiles === 0) suggestions.push({ title: 'Configurar cobertura de testes', priority: 'alta' });
130
+
131
+ if (!extra.has_ci) suggestions.push({ title: 'Configurar CI/CD (GitHub Actions)', priority: 'mรฉdia' });
132
+ if (!extra.has_readme) suggestions.push({ title: 'Criar/melhorar README.md', priority: 'mรฉdia' });
133
+ if (!extra.has_env_example) suggestions.push({ title: 'Criar .env.example', priority: 'baixa' });
134
+ if (!extra.has_docker) suggestions.push({ title: 'Containerizar com Docker', priority: 'baixa' });
135
+
136
+ if (pkg?.frameworks?.includes('Next.js') || pkg?.frameworks?.includes('React')) {
137
+ suggestions.push({ title: 'Auditoria de acessibilidade (a11y)', priority: 'mรฉdia' });
138
+ suggestions.push({ title: 'Implementar SEO/meta tags', priority: 'baixa' });
139
+ }
140
+
141
+ if (pkg?.tools?.includes('PostgreSQL') || pkg?.tools?.includes('Prisma ORM')) {
142
+ suggestions.push({ title: 'Implementar connection pooling', priority: 'mรฉdia' });
143
+ }
144
+
145
+ return suggestions.slice(0, 6);
146
+ }
147
+
148
+ // โ”€โ”€ run โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
149
+ async function run(args) {
150
+ const save = args.includes('--save');
151
+ const cwd = process.cwd();
152
+ const sep = 'โ”€'.repeat(52);
153
+
154
+ console.log(`\n๐Ÿ” Grimoire Analyze โ€” ${path.basename(cwd)}\n${sep}\n`);
155
+ console.log(' Analisando estrutura do projeto...\n');
156
+
157
+ // Detect
158
+ const pkg = detectFromPackageJson(cwd);
159
+ const extra = detectFromFiles(cwd);
160
+ const files = countFiles(cwd);
161
+
162
+ const runtime = pkg?.runtime || extra.runtime || 'Desconhecido';
163
+ const frameworks = [...(pkg?.frameworks || []), ...(extra.frameworks || [])];
164
+ const tools = [...(pkg?.tools || []), ...(extra.tools || [])];
165
+
166
+ const extraFlags = {
167
+ has_ci: fs.existsSync(path.join(cwd, '.github', 'workflows')),
168
+ has_readme: fs.existsSync(path.join(cwd, 'README.md')),
169
+ has_env_example: fs.existsSync(path.join(cwd, '.env.example')),
170
+ has_docker: fs.existsSync(path.join(cwd, 'Dockerfile')) || fs.existsSync(path.join(cwd, 'docker-compose.yml')),
171
+ };
172
+
173
+ // Output
174
+ if (pkg?.name) console.log(` ๐Ÿ“ฆ Projeto: ${pkg.name}${pkg.version ? ` v${pkg.version}` : ''}`);
175
+ console.log(` โš™๏ธ Runtime: ${runtime}`);
176
+ if (frameworks.length) console.log(` ๐Ÿ—๏ธ Frameworks: ${frameworks.join(', ')}`);
177
+ if (tools.length) console.log(` ๐Ÿ”ง Tools: ${tools.join(', ')}`);
178
+
179
+ // File counts
180
+ const topExts = Object.entries(files).sort((a, b) => b[1] - a[1]).slice(0, 5);
181
+ if (topExts.length) {
182
+ console.log(`\n ๐Ÿ“ Arquivos: ${topExts.map(([e, n]) => `${n}ร—${e}`).join(' ')}`);
183
+ }
184
+
185
+ // Check flags
186
+ console.log('\n ๐Ÿ“‹ Checklist:');
187
+ console.log(` ${extraFlags.has_readme ? 'โœ…' : 'โŒ'} README.md`);
188
+ console.log(` ${extraFlags.has_env_example ? 'โœ…' : 'โŒ'} .env.example`);
189
+ console.log(` ${extraFlags.has_ci ? 'โœ…' : 'โŒ'} CI/CD configurado`);
190
+ console.log(` ${extraFlags.has_docker ? 'โœ…' : 'โŒ'} Docker`);
191
+
192
+ // Suggestions
193
+ const suggestions = generateSuggestions(pkg, files, extraFlags);
194
+ if (suggestions.length) {
195
+ console.log('\n ๐Ÿ’ก Atividades sugeridas para o Backlog:\n');
196
+ suggestions.forEach((s, i) =>
197
+ console.log(` ${i + 1}. [${s.priority.toUpperCase()}] ${s.title}`)
198
+ );
199
+ }
200
+
201
+ // Save to grimoire
202
+ if (save || suggestions.length > 0) {
203
+ const grimoireDir = findGrimoireDir();
204
+ if (grimoireDir) {
205
+ // Save as memory
206
+ if (save) {
207
+ const { run: memRun } = require('./memory');
208
+ const stackSummary = `Stack: ${runtime}${frameworks.length ? ' + ' + frameworks.join(' + ') : ''}${tools.length ? ' ยท ' + tools.slice(0, 3).join(', ') : ''}`;
209
+ await memRun(['save', '--tag', 'stack', stackSummary]);
210
+ }
211
+
212
+ // Create suggested stories
213
+ if (suggestions.length > 0) {
214
+ console.log('\n Criar atividades sugeridas no Backlog? [S/N] ');
215
+ if (process.stdin.isTTY) {
216
+ const readline = require('readline');
217
+ readline.emitKeypressEvents(process.stdin);
218
+ process.stdin.setRawMode?.(true);
219
+ await new Promise(resolve => {
220
+ process.stdin.once('keypress', (str) => {
221
+ process.stdin.setRawMode?.(false);
222
+ if ((str || '').toLowerCase() === 's') {
223
+ console.log('\n Criando...');
224
+ const { run: storyRun } = require('./story');
225
+ suggestions.forEach(s => storyRun(['create', s.title, '--backlog']));
226
+ console.log(` โœ… ${suggestions.length} atividades criadas no Backlog\n`);
227
+ } else {
228
+ console.log(' Pulado.\n');
229
+ }
230
+ resolve();
231
+ });
232
+ });
233
+ }
234
+ }
235
+ }
236
+ }
237
+
238
+ console.log(`\n${sep}`);
239
+ console.log(' grimoire dashboard โ† ver kanban\n');
240
+ }
241
+
242
+ module.exports = { run };
@@ -0,0 +1,358 @@
1
+ /**
2
+ * grimoire dashboard โ€” Kanban TUI
3
+ *
4
+ * ๐ŸŽญ Design: Matisse (UX) ยท ๐ŸŽจ Da Vinci (Dev) ยท ๐Ÿ›๏ธ Gaudรญ (Arquitetura)
5
+ *
6
+ * Exibe kanban com colunas: BACKLOG | EM ANDAMENTO | CONCLUรDO
7
+ * Com wizard se nenhum projeto for detectado.
8
+ */
9
+
10
+ 'use strict';
11
+
12
+ const path = require('path');
13
+ const fs = require('fs');
14
+ const os = require('os');
15
+ const readline = require('readline');
16
+
17
+ // โ”€โ”€ Paths โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
18
+ function findGrimoireDir() {
19
+ const cwd = process.cwd();
20
+ if (fs.existsSync(path.join(cwd, '.grimoire'))) return path.join(cwd, '.grimoire');
21
+ if (fs.existsSync(path.join(cwd, 'grimoire', '.grimoire'))) return path.join(cwd, 'grimoire', '.grimoire');
22
+ return null;
23
+ }
24
+
25
+ function getProjectName(grimoireDir) {
26
+ try {
27
+ const pkg = path.join(path.dirname(grimoireDir), 'package.json');
28
+ if (fs.existsSync(pkg)) return JSON.parse(fs.readFileSync(pkg, 'utf8')).name || 'Projeto';
29
+ } catch (_) { }
30
+ return path.basename(path.dirname(grimoireDir));
31
+ }
32
+
33
+ function loadConfig(grimoireDir) {
34
+ const f = path.join(grimoireDir, 'config.yaml');
35
+ if (!fs.existsSync(f)) return {};
36
+ const cfg = {};
37
+ fs.readFileSync(f, 'utf8').split('\n').forEach(l => {
38
+ const m = l.match(/^(\w[\w_]*)\s*:\s*(.+)$/);
39
+ if (m) cfg[m[1].trim()] = m[2].trim();
40
+ });
41
+ return cfg;
42
+ }
43
+
44
+ function loadStories(grimoireDir) {
45
+ const storiesDir = path.join(grimoireDir, 'stories');
46
+ if (!fs.existsSync(storiesDir)) return [];
47
+ return fs.readdirSync(storiesDir)
48
+ .filter(f => f.endsWith('.json'))
49
+ .map(f => { try { return JSON.parse(fs.readFileSync(path.join(storiesDir, f), 'utf8')); } catch (_) { return null; } })
50
+ .filter(Boolean)
51
+ .sort((a, b) => a.createdAt > b.createdAt ? 1 : -1);
52
+ }
53
+
54
+ // โ”€โ”€ Time helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
55
+ function fmtDate(iso) {
56
+ if (!iso) return 'โ€”';
57
+ return iso.slice(0, 10).split('-').slice(1).join('/'); // MM/DD
58
+ }
59
+
60
+ function timeSpent(story) {
61
+ if (!story.startedAt) return null;
62
+ const end = story.completedAt ? new Date(story.completedAt) : new Date();
63
+ const ms = end - new Date(story.startedAt);
64
+ const h = Math.floor(ms / 3600000);
65
+ const m = Math.floor((ms % 3600000) / 60000);
66
+ return `${h}h ${m}min`;
67
+ }
68
+
69
+ // โ”€โ”€ RAG status (quick check) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
70
+ function ragStatus(config) {
71
+ const p = config.embedding_provider;
72
+ if (!p || p === 'tfidf') return null;
73
+ return p;
74
+ }
75
+
76
+ // โ”€โ”€ Today's memory count โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
77
+ function todayMemCount(grimoireDir) {
78
+ const today = new Date().toISOString().split('T')[0];
79
+ const f = path.join(grimoireDir, 'memory', 'sessions', `${today}.jsonl`);
80
+ if (!fs.existsSync(f)) return 0;
81
+ return fs.readFileSync(f, 'utf8').split('\n').filter(l => l.trim()).length;
82
+ }
83
+
84
+ // โ”€โ”€ Terminal width โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
85
+ function termWidth() {
86
+ return Math.max(process.stdout.columns || 80, 80);
87
+ }
88
+
89
+ // โ”€โ”€ Box-drawing helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
90
+ const C = {
91
+ tl: 'โ•”', tr: 'โ•—', bl: 'โ•š', br: 'โ•',
92
+ h: 'โ•', v: 'โ•‘',
93
+ ml: 'โ• ', mr: 'โ•ฃ', mt: 'โ•ฆ', mb: 'โ•ฉ', mc: 'โ•ฌ',
94
+ sep: 'โ”€', lsep: 'โ”œ', rsep: 'โ”ค',
95
+ };
96
+
97
+ function pad(str, len, char = ' ') {
98
+ const visible = stripAnsi(str).length;
99
+ const diff = len - visible;
100
+ return diff > 0 ? str + char.repeat(diff) : str;
101
+ }
102
+
103
+ function stripAnsi(str) {
104
+ return String(str).replace(/\x1b\[[0-9;]*m/g, '');
105
+ }
106
+
107
+ function truncate(str, len) {
108
+ const clean = stripAnsi(str);
109
+ if (clean.length <= len) return str;
110
+ return clean.slice(0, len - 1) + 'โ€ฆ';
111
+ }
112
+
113
+ // โ”€โ”€ Colors โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
114
+ const dim = s => `\x1b[2m${s}\x1b[0m`;
115
+ const bold = s => `\x1b[1m${s}\x1b[0m`;
116
+ const cyan = s => `\x1b[36m${s}\x1b[0m`;
117
+ const yellow = s => `\x1b[33m${s}\x1b[0m`;
118
+ const green = s => `\x1b[32m${s}\x1b[0m`;
119
+ const red = s => `\x1b[31m${s}\x1b[0m`;
120
+ const magenta = s => `\x1b[35m${s}\x1b[0m`;
121
+
122
+ // โ”€โ”€ Status icons โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
123
+ function statusIcon(status) {
124
+ if (status === 'done') return green('โœ“');
125
+ if (status === 'in_progress') return yellow('โ—');
126
+ return dim('โ—‹');
127
+ }
128
+
129
+ // โ”€โ”€ Render single card โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
130
+ function renderCard(story, colW) {
131
+ const inner = colW - 2; // minus borders
132
+ const lines = [];
133
+
134
+ // ID + icon
135
+ const idStr = bold(cyan(truncate(story.id, 8)));
136
+ const icon = statusIcon(story.status);
137
+ lines.push(` ${icon} ${idStr}`);
138
+
139
+ // Title
140
+ lines.push(` ${truncate(story.title, inner - 1)}`);
141
+
142
+ // Dates
143
+ if (story.status === 'backlog') {
144
+ const dead = story.deliveryDate ? `Entrega: ${fmtDate(story.deliveryDate)}` : dim('Sem prazo');
145
+ lines.push(` ${dim(truncate(dead, inner - 1))}`);
146
+ } else if (story.status === 'in_progress') {
147
+ const start = `Inรญcio: ${fmtDate(story.startedAt || story.createdAt)}`;
148
+ const dead = story.deliveryDate ? `Entrega: ${fmtDate(story.deliveryDate)}` : '';
149
+ lines.push(` ${dim(truncate(start, inner - 1))}`);
150
+ if (dead) lines.push(` ${dim(truncate(dead, inner - 1))}`);
151
+ const spent = timeSpent(story);
152
+ if (spent) lines.push(` ${dim('โฑ')} ${dim(spent)}`);
153
+ } else {
154
+ // done
155
+ const done = story.completedAt ? `โœ“ ${fmtDate(story.completedAt)}` : '';
156
+ const spent = timeSpent(story);
157
+ if (done) lines.push(` ${green(truncate(done, inner - 1))}`);
158
+ if (spent) lines.push(` ${dim('โฑ')} ${dim(spent)}`);
159
+ }
160
+
161
+ return lines;
162
+ }
163
+
164
+ // โ”€โ”€ Render full kanban โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
165
+ function renderKanban(stories, grimoireDir, config) {
166
+ const W = termWidth();
167
+ const colW = Math.floor((W - 4) / 3); // 3 cols, 4 borders
168
+
169
+ const backlog = stories.filter(s => s.status === 'backlog');
170
+ const inProgress = stories.filter(s => s.status === 'in_progress');
171
+ const done = stories.filter(s => s.status === 'done').slice(-5); // last 5
172
+
173
+ const output = [];
174
+
175
+ // Header
176
+ const projectName = getProjectName(grimoireDir);
177
+ const today = new Date().toISOString().split('T')[0];
178
+ const rag = ragStatus(config);
179
+ const ragBadge = rag ? cyan(` ๐Ÿค– ${rag}`) : '';
180
+ const headerText = bold(`๐Ÿ”ฎ Grimoire ยท ${projectName}`) + ` ${dim(today)}${ragBadge}`;
181
+ const headerInner = pad(headerText, W - 2);
182
+ output.push(C.tl + C.h.repeat(W - 2) + C.tr);
183
+ output.push(C.v + headerInner + C.v);
184
+
185
+ // Column headers separator
186
+ output.push(C.ml + C.h.repeat(colW) + C.mt + C.h.repeat(colW) + C.mt + C.h.repeat(W - 2 - colW * 2 - 2) + C.mr);
187
+
188
+ const col1H = bold(' ๐Ÿ“‹ BACKLOG') + dim(` (${backlog.length})`);
189
+ const col2H = bold(' ๐Ÿ”„ EM ANDAMENTO') + dim(` (${inProgress.length})`);
190
+ const col3H = bold(' โœ… CONCLUรDO') + dim(` (${done.length})`);
191
+ const colW3 = W - 2 - colW * 2 - 2;
192
+ output.push(C.v + pad(col1H, colW) + C.v + pad(col2H, colW) + C.v + pad(col3H, colW3) + C.v);
193
+
194
+ // Row separator
195
+ output.push(C.ml + C.h.repeat(colW) + C.mc + C.h.repeat(colW) + C.mc + C.h.repeat(colW3) + C.mr);
196
+
197
+ // Cards
198
+ const col1Cards = backlog.map(s => renderCard(s, colW));
199
+ const col2Cards = inProgress.map(s => renderCard(s, colW));
200
+ const col3Cards = done.map(s => renderCard(s, colW3 + 2));
201
+
202
+ const maxRows = Math.max(col1Cards.flat().length + col1Cards.length,
203
+ col2Cards.flat().length + col2Cards.length,
204
+ col3Cards.flat().length + col3Cards.length, 3);
205
+
206
+ let r1 = 0, r2 = 0, r3 = 0;
207
+ let ci1 = 0, ci2 = 0, ci3 = 0;
208
+
209
+ for (let row = 0; row < maxRows; row++) {
210
+ const getCell = (cards, ci, ri) => {
211
+ if (ci >= cards.length) return { line: '', nextCi: ci, nextRi: ri };
212
+ const card = cards[ci];
213
+ if (ri < card.length) return { line: card[ri], nextCi: ci, nextRi: ri + 1 };
214
+ if (ri === card.length) return { line: dim(C.sep.repeat(Math.max(0, colW - 2))), nextCi: ci + 1, nextRi: 0 };
215
+ return { line: '', nextCi: ci + 1, nextRi: 0 };
216
+ };
217
+
218
+ const { line: l1, nextCi: nc1, nextRi: nr1 } = getCell(col1Cards, ci1, r1);
219
+ const { line: l2, nextCi: nc2, nextRi: nr2 } = getCell(col2Cards, ci2, r2);
220
+ const { line: l3, nextCi: nc3, nextRi: nr3 } = getCell(col3Cards, ci3, r3);
221
+
222
+ ci1 = nc1; r1 = nr1; ci2 = nc2; r2 = nr2; ci3 = nc3; r3 = nr3;
223
+
224
+ output.push(
225
+ C.v + pad(l1, colW) + C.v + pad(l2, colW) + C.v + pad(l3, colW3) + C.v
226
+ );
227
+ }
228
+
229
+ // Footer separator
230
+ output.push(C.ml + C.h.repeat(colW) + C.mb + C.h.repeat(colW) + C.mb + C.h.repeat(colW3) + C.mr);
231
+
232
+ // Status bar
233
+ const memCount = todayMemCount(grimoireDir);
234
+ const squad = config.default_squad || '';
235
+ const statusBar = dim(` ๐Ÿง  ${memCount} mem hoje`) +
236
+ (squad ? dim(` ยท squad: ${squad}`) : '') +
237
+ ` ยท ${dim('[N]')}ova ${dim('[S]')}ession ${dim('[Enter]')} Detalhar ${dim('[Q]')}sair`;
238
+ output.push(C.v + pad(statusBar, W - 2) + C.v);
239
+ output.push(C.bl + C.h.repeat(W - 2) + C.br);
240
+
241
+ console.log('\n' + output.join('\n'));
242
+
243
+ return { backlog, inProgress, done: stories.filter(s => s.status === 'done') };
244
+ }
245
+
246
+ // โ”€โ”€ Interactive keys โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
247
+ function waitForKey(stories, grimoireDir) {
248
+ if (!process.stdin.isTTY) return;
249
+
250
+ readline.emitKeypressEvents(process.stdin);
251
+ process.stdin.setRawMode?.(true);
252
+ process.stdin.resume();
253
+
254
+ process.stdin.once('keypress', (str, key) => {
255
+ process.stdin.setRawMode?.(false);
256
+ process.stdin.pause();
257
+
258
+ const k = (key?.name || str || '').toLowerCase();
259
+
260
+ if (k === 'q' || key?.ctrl && k === 'c') {
261
+ console.log('');
262
+ process.exit(0);
263
+ }
264
+
265
+ if (k === 'n') {
266
+ // New story โ€” delegate to story create wizard
267
+ console.log('\n๐Ÿ“ Criando nova atividade...');
268
+ console.log(' grimoire story create "Tรญtulo" [--deadline YYYY-MM-DD]\n');
269
+ process.exit(0);
270
+ }
271
+
272
+ if (k === 's') {
273
+ console.log('\n๐Ÿš€ Iniciando sessรฃo...');
274
+ try { require('./session').run([]); }
275
+ catch (_) { console.log(' grimoire session start\n'); process.exit(0); }
276
+ return;
277
+ }
278
+
279
+ if (k === 'return' || k === 'enter') {
280
+ // Show current in_progress story details
281
+ const active = stories.find(s => s.status === 'in_progress');
282
+ if (active) {
283
+ try { require('./story').run(['show', active.id]); }
284
+ catch (_) { }
285
+ } else {
286
+ console.log('\n Nenhuma atividade em andamento.\n');
287
+ }
288
+ process.exit(0);
289
+ }
290
+
291
+ // Any other key โ€” exit
292
+ console.log('');
293
+ process.exit(0);
294
+ });
295
+ }
296
+
297
+ // โ”€โ”€ No-project wizard โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
298
+ async function showNoProjectWizard() {
299
+ const cwd = process.cwd();
300
+ const W = Math.min(termWidth(), 50);
301
+
302
+ console.log('\n' + 'โ•”' + 'โ•'.repeat(W - 2) + 'โ•—');
303
+ console.log('โ•‘' + pad(bold(' ๐Ÿ”ฎ Grimoire Framework'), W - 2) + 'โ•‘');
304
+ console.log('โ•‘' + pad(dim(` ${cwd}`), W - 2) + 'โ•‘');
305
+ console.log('โ• ' + 'โ•'.repeat(W - 2) + 'โ•ฃ');
306
+ console.log('โ•‘' + pad(yellow(' โš ๏ธ Nenhum projeto detectado.'), W - 2) + 'โ•‘');
307
+ console.log('โ•‘' + ' '.repeat(W - 2) + 'โ•‘');
308
+ console.log('โ•‘' + pad(' O que vocรช quer fazer?', W - 2) + 'โ•‘');
309
+ console.log('โ•‘' + ' '.repeat(W - 2) + 'โ•‘');
310
+ console.log('โ•‘' + pad(' โฏ ๐Ÿ†• Novo projeto [N]', W - 2) + 'โ•‘');
311
+ console.log('โ•‘' + pad(' ๐Ÿ” Analisar pasta atual [A]', W - 2) + 'โ•‘');
312
+ console.log('โ•‘' + pad(' โœ– Sair [Q]', W - 2) + 'โ•‘');
313
+ console.log('โ•š' + 'โ•'.repeat(W - 2) + 'โ•\n');
314
+
315
+ if (!process.stdin.isTTY) return;
316
+
317
+ readline.emitKeypressEvents(process.stdin);
318
+ process.stdin.setRawMode?.(true);
319
+ process.stdin.resume();
320
+
321
+ await new Promise(resolve => {
322
+ process.stdin.once('keypress', (str, key) => {
323
+ process.stdin.setRawMode?.(false);
324
+ process.stdin.pause();
325
+ const k = (key?.name || str || '').toLowerCase();
326
+
327
+ if (k === 'n') {
328
+ console.log('๐Ÿ†• Iniciando novo projeto...');
329
+ console.log(' npx grimoire-framework install\n');
330
+ } else if (k === 'a') {
331
+ console.log('๐Ÿ” Analisando pasta...');
332
+ try { require('./analyze').run([]); }
333
+ catch (_) { console.log(' grimoire analyze\n'); }
334
+ } else {
335
+ console.log('');
336
+ }
337
+ resolve();
338
+ });
339
+ });
340
+ }
341
+
342
+ // โ”€โ”€ run โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
343
+ async function run(args) {
344
+ const grimoireDir = findGrimoireDir();
345
+
346
+ if (!grimoireDir) {
347
+ await showNoProjectWizard();
348
+ return;
349
+ }
350
+
351
+ const config = loadConfig(grimoireDir);
352
+ const stories = loadStories(grimoireDir);
353
+
354
+ const { backlog } = renderKanban(stories, grimoireDir, config);
355
+ waitForKey(stories, grimoireDir);
356
+ }
357
+
358
+ module.exports = { run, renderKanban };
@@ -70,7 +70,7 @@ async function run(args) {
70
70
  }
71
71
  }
72
72
 
73
- // โ”€โ”€ Auto-update context (silent) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
73
+ // โ”€โ”€ Auto-update context + RAG index (silent) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
74
74
  function silentContextUpdate(grimoireDir) {
75
75
  try {
76
76
  const { doUpdate } = require('./context');
@@ -78,6 +78,24 @@ function silentContextUpdate(grimoireDir) {
78
78
  } catch (_) { } // never block memory save
79
79
  }
80
80
 
81
+ async function silentRagIndex(entry, date, grimoireDir) {
82
+ try {
83
+ const fs2 = require('fs');
84
+ const cfgF = require('path').join(grimoireDir, 'config.yaml');
85
+ const cfg = {};
86
+ if (fs2.existsSync(cfgF)) {
87
+ fs2.readFileSync(cfgF, 'utf8').split('\n').forEach(l => {
88
+ const m = l.match(/^(\w[\w_]*)\s*:\s*(.+)$/);
89
+ if (m) cfg[m[1].trim()] = m[2].trim();
90
+ });
91
+ }
92
+ // Only index if a real embedding provider is configured
93
+ if (!cfg.embedding_provider || cfg.embedding_provider === 'tfidf') return;
94
+ const { indexEntry } = require('./rag');
95
+ await indexEntry(entry, date, grimoireDir, cfg);
96
+ } catch (_) { }
97
+ }
98
+
81
99
  /**
82
100
  * Saves memory using JSONL append (O(1) complexity)
83
101
  */
@@ -167,6 +185,8 @@ async function saveMemory(args, memoryDir, grimoireDir) {
167
185
  console.log(`โœ… Memory saved${tagLabel}${storyLabel}`);
168
186
  // Auto-update .grimoire/context/ silently
169
187
  silentContextUpdate(grimoireDir);
188
+ // Auto-index for RAG (fire-and-forget)
189
+ silentRagIndex(entry, today, grimoireDir);
170
190
  } catch (err) {
171
191
  console.error(`โŒ Failed to acquire lock for memory file: ${err.message}`);
172
192
  } finally {