groove-dev 0.25.0 → 0.25.2

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.
@@ -304,6 +304,18 @@ export class Daemon {
304
304
  // Scan codebase for workspace/structure awareness
305
305
  this.indexer.scan();
306
306
 
307
+ // Generate init map if none exists — baseline for all agents and journalist
308
+ const initMapCreated = this.indexer.generateInitMap();
309
+ if (initMapCreated) {
310
+ console.log('[Groove] Init map generated — GROOVE_PROJECT_MAP.md');
311
+ // Seed journalist with the init map so it maintains it from here
312
+ this.journalist.seedFromInitMap();
313
+ // Record the init scan as a cold-start skip — the first planner
314
+ // will read the map instead of spending 8K+ tokens scanning
315
+ this.tokens.recordColdStartSkipped();
316
+ this.audit.log('init.map', { stats: this.indexer.getStatus().stats });
317
+ }
318
+
307
319
  resolvePromise(this);
308
320
  });
309
321
  });
@@ -6,7 +6,8 @@
6
6
  // thousands of tokens exploring the file tree.
7
7
 
8
8
  import { readdirSync, statSync, readFileSync, writeFileSync, existsSync } from 'fs';
9
- import { resolve, relative, basename, join } from 'path';
9
+ import { resolve, relative, basename, join, extname } from 'path';
10
+ import { execSync } from 'child_process';
10
11
 
11
12
  const IGNORE_DIRS = new Set([
12
13
  'node_modules', '.git', '.groove', 'dist', 'build', '.next', '.nuxt',
@@ -321,4 +322,200 @@ export class CodebaseIndexer {
321
322
  stats: this.index?.stats || null,
322
323
  };
323
324
  }
325
+
326
+ /**
327
+ * Generate a comprehensive init map from the scan results.
328
+ * Writes GROOVE_PROJECT_MAP.md as the baseline that the Journalist
329
+ * maintains from this point forward. Only runs if no map exists yet.
330
+ * Returns true if a new map was generated.
331
+ */
332
+ generateInitMap() {
333
+ const rootDir = this.daemon.projectDir;
334
+ const mapPath = resolve(rootDir, 'GROOVE_PROJECT_MAP.md');
335
+
336
+ // Don't overwrite an existing journalist-maintained map
337
+ if (existsSync(mapPath)) return false;
338
+ if (!this.index) return false;
339
+
340
+ const { workspaces, keyFiles, stats, projectName, tree } = this.index;
341
+ const lines = [];
342
+
343
+ lines.push(`# GROOVE Project Map`);
344
+ lines.push('');
345
+ lines.push(`*Auto-generated by GROOVE init scan. The Journalist maintains this map going forward.*`);
346
+ lines.push('');
347
+
348
+ // ── Project overview
349
+ lines.push(`## Project: ${projectName}`);
350
+ lines.push('');
351
+ lines.push(`- **Files:** ${stats.totalFiles}`);
352
+ lines.push(`- **Directories:** ${stats.totalDirs}`);
353
+ lines.push(`- **Scanned:** ${new Date().toISOString()}`);
354
+
355
+ // ── Tech stack detection
356
+ const techStack = this._detectTechStack(rootDir);
357
+ if (techStack.length > 0) {
358
+ lines.push('');
359
+ lines.push(`## Tech Stack`);
360
+ lines.push('');
361
+ for (const tech of techStack) {
362
+ lines.push(`- ${tech}`);
363
+ }
364
+ }
365
+
366
+ // ── Workspaces
367
+ if (workspaces.length > 0) {
368
+ lines.push('');
369
+ lines.push(`## Workspaces (${workspaces.length})`);
370
+ lines.push('');
371
+ for (const ws of workspaces) {
372
+ lines.push(`- \`${ws.path}/\` — ${ws.name} (${ws.files} files, ${ws.dirs} subdirs)`);
373
+ }
374
+ }
375
+
376
+ // ── Directory structure (depth 0-2)
377
+ lines.push('');
378
+ lines.push(`## Structure`);
379
+ lines.push('');
380
+ const shallow = tree.filter((n) => n.depth <= 2 && n.children.length > 0);
381
+ for (const node of shallow) {
382
+ const indent = ' '.repeat(node.depth);
383
+ const name = node.path === '.' ? projectName : node.path.split('/').pop();
384
+ lines.push(`${indent}- \`${name}/\` (${node.files} files)`);
385
+ }
386
+
387
+ // ── Key files
388
+ if (keyFiles.length > 0) {
389
+ lines.push('');
390
+ lines.push(`## Key Files`);
391
+ lines.push('');
392
+ for (const f of keyFiles.slice(0, 40)) {
393
+ lines.push(`- ${f}`);
394
+ }
395
+ if (keyFiles.length > 40) {
396
+ lines.push(`- *(+${keyFiles.length - 40} more)*`);
397
+ }
398
+ }
399
+
400
+ // ── Entry points
401
+ const entryPoints = this._detectEntryPoints(rootDir);
402
+ if (entryPoints.length > 0) {
403
+ lines.push('');
404
+ lines.push(`## Entry Points`);
405
+ lines.push('');
406
+ for (const ep of entryPoints) {
407
+ lines.push(`- \`${ep}\``);
408
+ }
409
+ }
410
+
411
+ // ── Git info
412
+ const gitInfo = this._getGitInfo(rootDir);
413
+ if (gitInfo) {
414
+ lines.push('');
415
+ lines.push(`## Git`);
416
+ lines.push('');
417
+ lines.push(`- **Branch:** ${gitInfo.branch}`);
418
+ if (gitInfo.recentCommits.length > 0) {
419
+ lines.push(`- **Recent commits:**`);
420
+ for (const c of gitInfo.recentCommits) {
421
+ lines.push(` - ${c}`);
422
+ }
423
+ }
424
+ }
425
+
426
+ // ── File type breakdown
427
+ const breakdown = this._fileTypeBreakdown(rootDir);
428
+ if (breakdown.length > 0) {
429
+ lines.push('');
430
+ lines.push(`## File Types`);
431
+ lines.push('');
432
+ for (const { ext, count } of breakdown.slice(0, 15)) {
433
+ lines.push(`- \`${ext}\` — ${count} files`);
434
+ }
435
+ }
436
+
437
+ const content = lines.join('\n') + '\n';
438
+ try {
439
+ writeFileSync(mapPath, content);
440
+ return true;
441
+ } catch {
442
+ return false;
443
+ }
444
+ }
445
+
446
+ /** Detect tech stack from config files */
447
+ _detectTechStack(rootDir) {
448
+ const stack = [];
449
+ const pkg = this.readJson(resolve(rootDir, 'package.json'));
450
+ if (pkg) {
451
+ if (pkg.dependencies?.react || pkg.devDependencies?.react) stack.push('React');
452
+ if (pkg.dependencies?.next || pkg.devDependencies?.next) stack.push('Next.js');
453
+ if (pkg.dependencies?.vue || pkg.devDependencies?.vue) stack.push('Vue');
454
+ if (pkg.dependencies?.svelte || pkg.devDependencies?.svelte) stack.push('Svelte');
455
+ if (pkg.dependencies?.express || pkg.devDependencies?.express) stack.push('Express');
456
+ if (pkg.dependencies?.fastify || pkg.devDependencies?.fastify) stack.push('Fastify');
457
+ if (pkg.dependencies?.tailwindcss || pkg.devDependencies?.tailwindcss) stack.push('Tailwind CSS');
458
+ if (pkg.dependencies?.typescript || pkg.devDependencies?.typescript) stack.push('TypeScript');
459
+ if (pkg.dependencies?.vite || pkg.devDependencies?.vite) stack.push('Vite');
460
+ if (pkg.dependencies?.prisma || pkg.devDependencies?.prisma) stack.push('Prisma');
461
+ if (pkg.type === 'module') stack.push('ESM');
462
+ }
463
+ if (existsSync(resolve(rootDir, 'Cargo.toml'))) stack.push('Rust');
464
+ if (existsSync(resolve(rootDir, 'go.mod'))) stack.push('Go');
465
+ if (existsSync(resolve(rootDir, 'pyproject.toml'))) stack.push('Python');
466
+ if (existsSync(resolve(rootDir, 'Dockerfile'))) stack.push('Docker');
467
+ return stack;
468
+ }
469
+
470
+ /** Detect common entry point files */
471
+ _detectEntryPoints(rootDir) {
472
+ const candidates = [
473
+ 'src/index.ts', 'src/index.js', 'src/index.tsx', 'src/index.jsx',
474
+ 'src/main.ts', 'src/main.js', 'src/main.tsx', 'src/main.jsx',
475
+ 'src/app.ts', 'src/app.js', 'src/app.tsx', 'src/app.jsx',
476
+ 'src/App.tsx', 'src/App.jsx',
477
+ 'app/layout.tsx', 'app/page.tsx', 'pages/index.tsx', 'pages/index.js',
478
+ 'index.ts', 'index.js', 'index.html',
479
+ 'server.ts', 'server.js', 'main.go', 'main.rs', 'main.py',
480
+ ];
481
+ return candidates.filter((f) => existsSync(resolve(rootDir, f)));
482
+ }
483
+
484
+ /** Get git branch and recent commits */
485
+ _getGitInfo(rootDir) {
486
+ try {
487
+ const branch = execSync('git rev-parse --abbrev-ref HEAD', { cwd: rootDir, encoding: 'utf8', timeout: 5000 }).trim();
488
+ const log = execSync('git log --oneline -5 2>/dev/null', { cwd: rootDir, encoding: 'utf8', timeout: 5000 }).trim();
489
+ return {
490
+ branch,
491
+ recentCommits: log ? log.split('\n') : [],
492
+ };
493
+ } catch {
494
+ return null;
495
+ }
496
+ }
497
+
498
+ /** Count files by extension */
499
+ _fileTypeBreakdown(rootDir) {
500
+ const counts = {};
501
+ const walk = (dir, depth) => {
502
+ if (depth > 3) return;
503
+ try {
504
+ const entries = readdirSync(dir, { withFileTypes: true });
505
+ for (const e of entries) {
506
+ if (e.name.startsWith('.')) continue;
507
+ if (e.isDirectory()) {
508
+ if (!IGNORE_DIRS.has(e.name)) walk(resolve(dir, e.name), depth + 1);
509
+ } else if (e.isFile()) {
510
+ const ext = extname(e.name) || '(no ext)';
511
+ counts[ext] = (counts[ext] || 0) + 1;
512
+ }
513
+ }
514
+ } catch { /* */ }
515
+ };
516
+ walk(rootDir, 0);
517
+ return Object.entries(counts)
518
+ .map(([ext, count]) => ({ ext, count }))
519
+ .sort((a, b) => b.count - a.count);
520
+ }
324
521
  }
@@ -29,6 +29,23 @@ export class Journalist {
29
29
  this.interval = setInterval(() => this.cycle(), intervalMs);
30
30
  }
31
31
 
32
+ /**
33
+ * Seed the journalist with the init map generated by the indexer.
34
+ * Sets it as the initial synthesis so agents read it immediately.
35
+ */
36
+ seedFromInitMap() {
37
+ const mapPath = resolve(this.daemon.projectDir, 'GROOVE_PROJECT_MAP.md');
38
+ if (!existsSync(mapPath)) return;
39
+ try {
40
+ const content = readFileSync(mapPath, 'utf8');
41
+ this.lastSynthesis = {
42
+ projectMap: content,
43
+ decisions: '',
44
+ summary: 'Init scan: project structure mapped from filesystem analysis.',
45
+ };
46
+ } catch { /* non-fatal */ }
47
+ }
48
+
32
49
  stop() {
33
50
  if (this.interval) {
34
51
  clearInterval(this.interval);
@@ -50,6 +50,22 @@ Do NOT write code unless explicitly asked. Use your MCP tools to interact with e
50
50
  - Presenting findings in clear, actionable format
51
51
  Do NOT write code unless explicitly asked. Use your MCP tools (database queries, spreadsheets) to analyze data.
52
52
 
53
+ `,
54
+ creative: `You are a Creative Writing agent. You produce professional written content — copy, articles, scripts, proposals, briefs, and documentation. Focus on:
55
+ - Writing clear, compelling, well-structured content
56
+ - Adapting tone and style to the audience (formal, conversational, technical, marketing)
57
+ - Editing and polishing drafts for grammar, flow, and impact
58
+ - Researching topics to produce accurate, substantive writing
59
+ You CAN use code tools to create and edit text files, markdown documents, and structured content. For best results, apply a writing skill from the Marketplace that matches your task.
60
+
61
+ `,
62
+ slides: `You are a Slide Deck agent. You build presentation decks as HTML slides (Reveal.js) with optional PPTX export. Focus on:
63
+ - Creating clean, professional slide layouts with strong visual hierarchy
64
+ - Structuring content into clear sections with concise bullet points
65
+ - Building responsive HTML slides that look polished in the browser
66
+ - Generating a slides.json data file alongside HTML for PPTX conversion
67
+ For best results, apply a slide deck skill from the Marketplace. The skill provides templates, styling, and export automation.
68
+
53
69
  `,
54
70
  home: `You are a Smart Home automation agent. You have MCP integrations for Home Assistant. Focus on:
55
71
  - Monitoring and controlling smart home devices