vibe-me 3.0.0 → 3.1.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.
Files changed (3) hide show
  1. package/README.md +92 -19
  2. package/bin/vibes.js +554 -38
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -200,9 +200,12 @@ Download [`Vibe.bat`](Vibe.bat), drop it in any project folder, and double-click
200
200
  | `vibes init` | Create `.vibe/` with the full suite — 15 files + guide |
201
201
  | `vibes docs` | Create `docs/` operational documentation — 11 files |
202
202
  | `vibes all` | Create both `.vibe/` and `docs/` at once |
203
- | `vibes check` | Validate all files for completeness and quality |
203
+ | `vibes check` | Validate all files catches unfilled templates, empty sections, missing structure |
204
204
  | `vibes status` | Quick health dashboard with filled/N-A/empty counts |
205
205
  | `vibes reset` | Delete and recreate everything |
206
+ | | |
207
+ | `vibes hub <path>` | Set up a central hub — git inits, creates `_insights/`, saves config |
208
+ | `vibes export` | Export `.vibe/` + `docs/` from current project to your hub |
206
209
  | `vibes help` | Show all commands |
207
210
 
208
211
  ---
@@ -257,10 +260,27 @@ Every line in `.vibe/` should pass this test:
257
260
 
258
261
  ## 🔍 Quality Validation
259
262
 
263
+ `vibes check` doesn't just count lines — it catches real problems:
264
+
260
265
  ```bash
261
266
  vibes check
262
267
  ```
263
268
 
269
+ **On a freshly scaffolded project (before AI fills it out):**
270
+ ```
271
+ 🔍 vibes check
272
+
273
+ ── .vibe/ ──
274
+
275
+ ✖ purpose.md — still contains template instructions (unfilled)
276
+ ✖ architecture.md — still contains template instructions (unfilled)
277
+ ✖ flows.md — still contains template instructions (unfilled)
278
+ ...
279
+
280
+ Result: 24 failed, 1 warnings, 0 passed
281
+ ```
282
+
283
+ **After your AI agent fills everything out:**
264
284
  ```
265
285
  🔍 vibes check
266
286
 
@@ -270,7 +290,7 @@ vibes check
270
290
  ✔ architecture.md — 86 lines
271
291
  ✔ flows.md — 144 lines
272
292
  ✔ entities.md — 135 lines
273
- decisions.md — 91 lines
293
+ decisions.md — 91 lines (no dependency graph fields)
274
294
  ✔ state.json — 82 lines
275
295
  ✔ context.md — 28 lines
276
296
  ✔ ai.md — 45 lines
@@ -280,25 +300,78 @@ vibes check
280
300
  ✔ experiments.md — 19 lines
281
301
  ⊘ business.md — N/A (not applicable)
282
302
  ⊘ market.md — N/A (not applicable)
283
- risks.md — 34 lines
284
-
285
- ── docs/ ──
286
-
287
- ✔ README.md — 45 lines
288
- topology.md — 120 lines
289
- architecture.md89 lines
290
- api.md 200 lines
291
- issues.md34 lines
292
- resolved.md67 lines
293
- roadmap.md 42 lines
294
- developer_guide.md95 lines
295
- ✔ troubleshooting.md — 55 lines
296
- ✔ glossary.md — 28 lines
297
-
298
- Result: 2 N/A, 23 passed
303
+ risks.md — 34 lines
304
+
305
+ Result: 2 N/A, 1 warnings, 22 passed
306
+ ```
307
+
308
+ What it catches:
309
+ - **Unfilled templates** files that still have `INSTRUCTIONS FOR AI AGENT` (hard fail)
310
+ - **Missing structure** — `purpose.md` without a "NOT do" section, `decisions.md` without dependency graph
311
+ - **Empty bullets/tables** placeholder content that wasn't filled in
312
+ - **Stale data** `state.json` not updated in 30+ days
313
+ - **Missing emotional reality** — `users.md` without frustrations, fears, or goals
314
+ - **N/A files** recognized and not penalized
315
+
316
+ ---
317
+
318
+ ## 🗂️ Vibes Hub Cross-Project Sync + Intelligence
319
+
320
+ When you're running vibes across multiple projects (and multiple computers), the **hub** lets you collect, compare, and analyze them all in one place.
321
+
322
+ ### One-time setup
323
+
324
+ ```bash
325
+ vibes hub ~/Documents/VibeHub
326
+ ```
327
+
328
+ This creates a folder with git, a README, and an `_insights/` intelligence layer — then walks you through connecting it to a private GitHub repo.
329
+
330
+ ### Export from any project
331
+
332
+ ```bash
333
+ cd your-project
334
+ vibes export
335
+ ```
336
+
337
+ Copies `.vibe/` and `docs/` into your hub, organized by project name. Auto-generates an `index.md` dashboard.
338
+
339
+ ### Sync across machines
340
+
341
+ Push from one machine, pull on another. Same private repo, all your projects.
342
+
343
+ ### Cross-project intelligence
344
+
345
+ After exporting 3+ projects, tell your AI agent:
346
+
347
+ > *"Read `_insights/ANALYZE.md` in my Vibes Hub and follow the instructions."*
348
+
349
+ The AI reads every project's `.vibe/` files and fills out:
350
+
351
+ | File | What it finds |
352
+ |:---|:---|
353
+ | `_insights/patterns.md` | Architecture, security, and tech stack patterns across projects |
354
+ | `_insights/standards.md` | Universal rules that should apply everywhere |
355
+ | `_insights/opportunities.md` | "Omni does X well → JustLegal should adopt it" |
356
+
357
+ ```
358
+ VibeHub/
359
+ ├── index.md Auto-generated dashboard
360
+ ├── _insights/
361
+ │ ├── ANALYZE.md Prompt for AI analysis
362
+ │ ├── patterns.md Cross-project patterns
363
+ │ ├── standards.md Universal standards
364
+ │ └── opportunities.md Transferable improvements
365
+ ├── Omni/
366
+ │ ├── .vibe/
367
+ │ └── docs/
368
+ ├── JustLegal/
369
+ │ ├── .vibe/
370
+ │ └── docs/
371
+ └── ...
299
372
  ```
300
373
 
301
- Files marked N/A are recognized as intentionally not applicable — not penalized.
374
+ One project's best practice becomes every project's standard.
302
375
 
303
376
  ---
304
377
 
package/bin/vibes.js CHANGED
@@ -15,6 +15,7 @@ const VIBE_DIR = '.vibe';
15
15
  const DOCS_DIR = 'docs';
16
16
  const TEMPLATES_DIR = path.join(__dirname, '..', 'templates');
17
17
  const SPEC_DIR = path.join(__dirname, '..', 'spec');
18
+ const HUB_CONFIG = path.join(process.env.USERPROFILE || process.env.HOME || '', '.vibes-hub');
18
19
 
19
20
  // All .vibe/ files — the full suite, always installed
20
21
  const VIBE_FILES = [
@@ -208,6 +209,124 @@ function cmdCheck(targetDir) {
208
209
 
209
210
  let passed = 0, failed = 0, warnings = 0, na = 0;
210
211
 
212
+ // ─── Universal quality checks ───
213
+ function checkQuality(content, file) {
214
+ const issues = [];
215
+
216
+ // FAIL: Still has template instruction comments
217
+ if (content.includes('INSTRUCTIONS FOR AI AGENT')) {
218
+ issues.push({ level: 'fail', msg: 'still contains template instructions (unfilled)' });
219
+ return issues;
220
+ }
221
+
222
+ // FAIL: Too short to be real content
223
+ const lines = content.split('\n').length;
224
+ if (lines < 8) {
225
+ issues.push({ level: 'fail', msg: `only ${lines} lines — needs real content` });
226
+ return issues;
227
+ }
228
+
229
+ // WARN: Has unfilled placeholder brackets like [Component Name]
230
+ const placeholders = content.match(/\[(?:Component|Persona|Experiment|Decision|Error|Deliverable|Feature|Issue)\s*\w*\]/gi);
231
+ if (placeholders && placeholders.length > 1) {
232
+ issues.push({ level: 'warn', msg: `${placeholders.length} unfilled placeholders (${placeholders[0]}, ...)` });
233
+ }
234
+
235
+ // WARN: Empty bullet points (lines that are just "- " or "- \n")
236
+ const emptyBullets = (content.match(/^- ?\s*$/gm) || []).length;
237
+ if (emptyBullets > 2) {
238
+ issues.push({ level: 'warn', msg: `${emptyBullets} empty bullet points` });
239
+ }
240
+
241
+ // WARN: Empty table rows (| | | |)
242
+ const emptyRows = (content.match(/^\|\s*\|\s*\|\s*\|/gm) || []).length;
243
+ if (emptyRows > 1) {
244
+ issues.push({ level: 'warn', msg: `${emptyRows} empty table rows` });
245
+ }
246
+
247
+ return issues;
248
+ }
249
+
250
+ // ─── File-specific structure checks ───
251
+ function checkStructure(content, file) {
252
+ const issues = [];
253
+
254
+ switch (file) {
255
+ case 'purpose.md':
256
+ if (!content.toLowerCase().includes('not do') && !content.toLowerCase().includes('does not'))
257
+ issues.push({ level: 'warn', msg: 'missing "NOT do" / scope boundary section' });
258
+ if (!content.toLowerCase().includes('who'))
259
+ issues.push({ level: 'warn', msg: 'doesn\'t mention who uses it' });
260
+ break;
261
+
262
+ case 'architecture.md':
263
+ if (!content.includes('→') && !content.includes('->') && !content.includes('connects') && !content.includes('talks to'))
264
+ issues.push({ level: 'warn', msg: 'no data flow described (missing → or connection language)' });
265
+ break;
266
+
267
+ case 'flows.md':
268
+ const flowHeaders = (content.match(/^##\s+/gm) || []).length;
269
+ if (flowHeaders < 2) issues.push({ level: 'warn', msg: `only ${flowHeaders} flow(s) — most projects have 3+` });
270
+ break;
271
+
272
+ case 'entities.md':
273
+ if (!content.toLowerCase().includes('depends') && !content.toLowerCase().includes('relationship'))
274
+ issues.push({ level: 'warn', msg: 'no dependency/relationship info for entities' });
275
+ break;
276
+
277
+ case 'decisions.md':
278
+ const decisionHeaders = (content.match(/^##\s+/gm) || []).length;
279
+ if (decisionHeaders < 2) issues.push({ level: 'warn', msg: `only ${decisionHeaders} decision(s) — aim for 3+` });
280
+ if (!content.includes('Depends On') && !content.includes('Threatened By') && !content.includes('depends on'))
281
+ issues.push({ level: 'warn', msg: 'no dependency graph fields (Depends On, Threatened By)' });
282
+ break;
283
+
284
+ case 'state.json':
285
+ try {
286
+ const s = JSON.parse(content);
287
+ if (!s.vibe_updated) issues.push({ level: 'warn', msg: 'missing vibe_updated timestamp' });
288
+ else {
289
+ const days = (Date.now() - new Date(s.vibe_updated).getTime()) / 86400000;
290
+ if (days > 30) issues.push({ level: 'warn', msg: `last updated ${Math.floor(days)} days ago — stale?` });
291
+ }
292
+ if (!s.project) issues.push({ level: 'warn', msg: 'missing project name' });
293
+ } catch {
294
+ issues.push({ level: 'fail', msg: 'invalid JSON' });
295
+ }
296
+ break;
297
+
298
+ case 'context.md':
299
+ if (!content.includes('## Current Focus'))
300
+ issues.push({ level: 'warn', msg: 'missing "Current Focus" section' });
301
+ else {
302
+ const focusIdx = content.indexOf('## Current Focus');
303
+ const nextSection = content.indexOf('##', focusIdx + 16);
304
+ const focusContent = content.substring(focusIdx, nextSection > 0 ? nextSection : undefined);
305
+ const focusBullets = (focusContent.match(/^- .{3,}/gm) || []).length;
306
+ if (focusBullets === 0) issues.push({ level: 'warn', msg: 'Current Focus has no entries' });
307
+ }
308
+ break;
309
+
310
+ case 'ai.md':
311
+ if (!content.toLowerCase().includes('never') && !content.toLowerCase().includes('don\'t') && !content.toLowerCase().includes('do not'))
312
+ issues.push({ level: 'warn', msg: 'no constraints defined — what should agents NOT touch?' });
313
+ break;
314
+
315
+ case 'product.md':
316
+ if (!content.toLowerCase().includes('retention') && !content.toLowerCase().includes('why') && !content.toLowerCase().includes('value'))
317
+ issues.push({ level: 'warn', msg: 'missing value prop or retention drivers' });
318
+ break;
319
+
320
+ case 'users.md':
321
+ if (!content.toLowerCase().includes('frustrat') && !content.toLowerCase().includes('fear') && !content.toLowerCase().includes('pain') && !content.toLowerCase().includes('goal'))
322
+ issues.push({ level: 'warn', msg: 'missing emotional reality (frustrations, fears, goals)' });
323
+ break;
324
+ }
325
+
326
+ return issues;
327
+ }
328
+
329
+ // ─── Run checks on .vibe/ ───
211
330
  if (hasVibe) {
212
331
  console.log('');
213
332
  console.log(dim(' ── .vibe/ ──'));
@@ -220,44 +339,32 @@ function cmdCheck(targetDir) {
220
339
  const content = fs.readFileSync(fp, 'utf-8').trim();
221
340
  const lines = content.split('\n').length;
222
341
 
223
- // Check for N/A files (product/business layers marked not applicable)
224
- if (content.match(/^N\/A/m) || content.match(/^"?N\/A/m)) {
342
+ // N/A check
343
+ if (content.match(/^N\/A/m)) {
225
344
  console.log(dim(' ⊘ ') + file + dim(' — N/A (not applicable)'));
226
345
  na++;
227
346
  continue;
228
347
  }
229
348
 
230
- if (lines < 5) { console.log(yellow(' ⚠ ') + file + ` — looks empty (${lines} lines)`); warnings++; continue; }
231
-
232
- // File-specific checks
233
- let warn = null;
234
- if (file === 'purpose.md' && !content.toLowerCase().includes('not do')) warn = 'missing "NOT do" section';
235
- if (file === 'decisions.md' && (content.match(/^## Why /gm) || []).length < 2) warn = 'fewer than 2 decisions';
236
- if (file === 'entities.md' && !content.includes('What depends on it')) warn = 'missing dependency fields';
237
- if (file === 'flows.md' && (content.match(/^## \d+\./gm) || []).length < 2) warn = 'fewer than 2 flows';
238
- if (file === 'state.json') {
239
- try {
240
- const s = JSON.parse(content);
241
- if (!s.vibe_updated) warn = 'missing vibe_updated';
242
- else {
243
- const days = (Date.now() - new Date(s.vibe_updated).getTime()) / 86400000;
244
- if (days > 30) warn = `stale (${Math.floor(days)} days old)`;
245
- }
246
- } catch { console.log(red(' ✖ ') + file + ' — invalid JSON'); failed++; continue; }
247
- }
248
- if (file === 'context.md') {
249
- if (!content.includes('## Current Focus') || content.match(/^- $/m)) warn = 'Current Focus is empty';
250
- }
251
- if (file === 'decisions.md' && !content.includes('Depends On') && !content.includes('Threatened By')) {
252
- // Only warn if decisions exist but lack graph relationships
253
- if ((content.match(/^## Why /gm) || []).length >= 2) warn = 'decisions missing relationship fields (Depends On, Threatened By)';
349
+ // Run quality + structure checks
350
+ const allIssues = [...checkQuality(content, file), ...checkStructure(content, file)];
351
+ const fails = allIssues.filter(i => i.level === 'fail');
352
+ const warns = allIssues.filter(i => i.level === 'warn');
353
+
354
+ if (fails.length > 0) {
355
+ console.log(red(' ✖ ') + file + ' ' + fails[0].msg);
356
+ failed++;
357
+ } else if (warns.length > 0) {
358
+ console.log(yellow(' ⚠ ') + file + ` — ${lines} lines` + yellow(` (${warns.map(w => w.msg).join('; ')})`));
359
+ warnings++;
360
+ } else {
361
+ console.log(green(' ✔ ') + file + ` — ${lines} lines`);
362
+ passed++;
254
363
  }
255
-
256
- if (warn) { console.log(yellow(' ⚠ ') + file + ` — ${warn}`); warnings++; }
257
- else { console.log(green(' ✔ ') + file + ` — ${lines} lines`); passed++; }
258
364
  }
259
365
  }
260
366
 
367
+ // ─── Run checks on docs/ ───
261
368
  if (hasDocs) {
262
369
  console.log('');
263
370
  console.log(dim(' ── docs/ ──'));
@@ -266,13 +373,28 @@ function cmdCheck(targetDir) {
266
373
  for (const file of DOCS_FILES) {
267
374
  const fp = path.join(docsDir, file);
268
375
  if (!exists(fp)) { console.log(yellow(' ⚠ ') + file + ' — missing'); warnings++; continue; }
376
+
269
377
  const content = fs.readFileSync(fp, 'utf-8').trim();
270
378
  const lines = content.split('\n').length;
271
- if (lines < 5) { console.log(yellow(' ⚠ ') + file + ` — looks empty (${lines} lines)`); warnings++; }
272
- else { console.log(green(' ✔ ') + file + ` — ${lines} lines`); passed++; }
379
+
380
+ const allIssues = checkQuality(content, file);
381
+ const fails = allIssues.filter(i => i.level === 'fail');
382
+ const warns = allIssues.filter(i => i.level === 'warn');
383
+
384
+ if (fails.length > 0) {
385
+ console.log(red(' ✖ ') + file + ' — ' + fails[0].msg);
386
+ failed++;
387
+ } else if (warns.length > 0) {
388
+ console.log(yellow(' ⚠ ') + file + ` — ${lines} lines` + yellow(` (${warns.map(w => w.msg).join('; ')})`));
389
+ warnings++;
390
+ } else {
391
+ console.log(green(' ✔ ') + file + ` — ${lines} lines`);
392
+ passed++;
393
+ }
273
394
  }
274
395
  }
275
396
 
397
+ // ─── Summary ───
276
398
  console.log('');
277
399
  let result = '';
278
400
  if (failed > 0) result = red(`${failed} failed`);
@@ -361,6 +483,393 @@ function cmdReset(targetDir) {
361
483
  cmdAll(targetDir);
362
484
  }
363
485
 
486
+ function copyDirRecursive(src, dest) {
487
+ if (!exists(src)) return 0;
488
+ fs.mkdirSync(dest, { recursive: true });
489
+ let count = 0;
490
+ for (const item of fs.readdirSync(src)) {
491
+ const srcPath = path.join(src, item);
492
+ const destPath = path.join(dest, item);
493
+ const stat = fs.statSync(srcPath);
494
+ if (stat.isDirectory()) {
495
+ count += copyDirRecursive(srcPath, destPath);
496
+ } else {
497
+ if (item === 'VIBE_GUIDE.md') continue;
498
+ fs.writeFileSync(destPath, fs.readFileSync(srcPath));
499
+ count++;
500
+ }
501
+ }
502
+ return count;
503
+ }
504
+
505
+ function cmdExport(targetDir, hubPathArg) {
506
+ targetDir = path.resolve(targetDir);
507
+ let hubPath = hubPathArg;
508
+ if (!hubPath && exists(HUB_CONFIG)) {
509
+ hubPath = fs.readFileSync(HUB_CONFIG, 'utf-8').trim();
510
+ }
511
+ if (!hubPath) {
512
+ console.log(red('\n ✖ No hub path specified.'));
513
+ console.log('');
514
+ console.log(' First time? Set your hub path:');
515
+ console.log(cyan(' vibes hub C:\\Users\\You\\Documents\\VibeHub'));
516
+ console.log('');
517
+ console.log(' Then run:');
518
+ console.log(cyan(' vibes export'));
519
+ console.log('');
520
+ process.exit(1);
521
+ }
522
+
523
+ hubPath = path.resolve(hubPath);
524
+ const name = getProjectName(targetDir);
525
+ const projectHub = path.join(hubPath, name);
526
+
527
+ console.log('');
528
+ console.log(bold(' 📤 vibes export'));
529
+ console.log(dim(` Exporting ${cyan(name)} → ${hubPath}`));
530
+ console.log('');
531
+
532
+ let total = 0;
533
+
534
+ const vibeDir = path.join(targetDir, VIBE_DIR);
535
+ if (exists(vibeDir)) {
536
+ const count = copyDirRecursive(vibeDir, path.join(projectHub, VIBE_DIR));
537
+ console.log(green(' ✔ ') + `.vibe/ — ${count} files`);
538
+ total += count;
539
+ } else {
540
+ console.log(yellow(' ⚠ ') + '.vibe/ not found, skipping');
541
+ }
542
+
543
+ const docsDir = path.join(targetDir, DOCS_DIR);
544
+ if (exists(docsDir)) {
545
+ const count = copyDirRecursive(docsDir, path.join(projectHub, DOCS_DIR));
546
+ console.log(green(' ✔ ') + `docs/ — ${count} files`);
547
+ total += count;
548
+ } else {
549
+ console.log(yellow(' ⚠ ') + 'docs/ not found, skipping');
550
+ }
551
+
552
+ const meta = { project: name, exported_at: now(), source: targetDir };
553
+ fs.writeFileSync(path.join(projectHub, 'export.json'), JSON.stringify(meta, null, 2), 'utf-8');
554
+ total++;
555
+
556
+ console.log('');
557
+ console.log(green(` ✔ Exported ${total} files to ${projectHub}`));
558
+
559
+ generateHubIndex(hubPath);
560
+
561
+ console.log('');
562
+ console.log(dim(' Commit and push your hub repo to sync across machines:'));
563
+ console.log(cyan(` cd "${hubPath}" && git add -A && git commit -m "export ${name}" && git push`));
564
+ console.log('');
565
+ }
566
+
567
+ function generateHubIndex(hubPath) {
568
+ const projects = [];
569
+ for (const dir of fs.readdirSync(hubPath)) {
570
+ const dirPath = path.join(hubPath, dir);
571
+ if (!fs.statSync(dirPath).isDirectory()) continue;
572
+ if (dir.startsWith('.') || dir.startsWith('_')) continue;
573
+
574
+ const exportFile = path.join(dirPath, 'export.json');
575
+ const vibeDir = path.join(dirPath, VIBE_DIR);
576
+ const docsDir = path.join(dirPath, DOCS_DIR);
577
+
578
+ let exportedAt = 'unknown';
579
+ if (exists(exportFile)) {
580
+ try {
581
+ const meta = JSON.parse(fs.readFileSync(exportFile, 'utf-8'));
582
+ exportedAt = meta.exported_at ? meta.exported_at.split('T')[0] : 'unknown';
583
+ } catch {}
584
+ }
585
+
586
+ let vibeFilled = 0, vibeNA = 0, vibeTotal = 0;
587
+ if (exists(vibeDir)) {
588
+ for (const f of VIBE_FILES) {
589
+ const fp = path.join(vibeDir, f);
590
+ if (!exists(fp)) continue;
591
+ vibeTotal++;
592
+ const content = fs.readFileSync(fp, 'utf-8').trim();
593
+ if (content.match(/^N\/A/m)) vibeNA++;
594
+ else if (content.split('\n').length >= 10) vibeFilled++;
595
+ }
596
+ }
597
+
598
+ let docsFilled = 0;
599
+ if (exists(docsDir)) {
600
+ for (const f of DOCS_FILES) {
601
+ const fp = path.join(docsDir, f);
602
+ if (!exists(fp)) continue;
603
+ if (fs.readFileSync(fp, 'utf-8').trim().split('\n').length >= 10) docsFilled++;
604
+ }
605
+ }
606
+
607
+ let purpose = '';
608
+ const purposeFile = path.join(vibeDir, 'purpose.md');
609
+ if (exists(purposeFile)) {
610
+ for (const line of fs.readFileSync(purposeFile, 'utf-8').split('\n')) {
611
+ const t = line.trim();
612
+ if (t && !t.startsWith('#') && !t.startsWith('<!--') && !t.startsWith('-->') && !t.startsWith('[')) {
613
+ purpose = t.substring(0, 80);
614
+ break;
615
+ }
616
+ }
617
+ }
618
+
619
+ projects.push({ name: dir, exportedAt, vibeFilled, vibeNA, vibeTotal, docsFilled, purpose });
620
+ }
621
+
622
+ if (projects.length === 0) return;
623
+
624
+ let md = `# Vibes Hub\n\n`;
625
+ md += `> Auto-generated index. Last updated: ${now().split('T')[0]}\n\n`;
626
+ md += `| Project | .vibe/ | docs/ | Last Export | Purpose |\n`;
627
+ md += `|---|---|---|---|---|\n`;
628
+ for (const p of projects.sort((a, b) => a.name.localeCompare(b.name))) {
629
+ const vs = p.vibeTotal > 0 ? `${p.vibeFilled}/${p.vibeTotal}` + (p.vibeNA > 0 ? ` (${p.vibeNA} N/A)` : '') : '—';
630
+ const ds = p.docsFilled > 0 ? `${p.docsFilled}/${DOCS_FILES.length}` : '—';
631
+ md += `| [${p.name}](./${p.name}/) | ${vs} | ${ds} | ${p.exportedAt} | ${p.purpose} |\n`;
632
+ }
633
+ md += `\n---\n\n**${projects.length} projects** tracked.\n`;
634
+
635
+ fs.writeFileSync(path.join(hubPath, 'index.md'), md, 'utf-8');
636
+ console.log(green(' ✔ ') + 'Updated hub index.md');
637
+ }
638
+
639
+ function cmdHub(hubPathArg) {
640
+ if (!hubPathArg) {
641
+ if (exists(HUB_CONFIG)) {
642
+ const saved = fs.readFileSync(HUB_CONFIG, 'utf-8').trim();
643
+ console.log(`\n Hub path: ${cyan(saved)}\n`);
644
+ } else {
645
+ console.log(yellow('\n No hub configured.'));
646
+ console.log(dim(' Set one with: vibes hub <path>\n'));
647
+ }
648
+ return;
649
+ }
650
+
651
+ const resolved = path.resolve(hubPathArg);
652
+ const isNew = !exists(resolved) || fs.readdirSync(resolved).length === 0;
653
+
654
+ // Create directory
655
+ fs.mkdirSync(resolved, { recursive: true });
656
+
657
+ console.log('');
658
+ console.log(bold(' 🗂️ vibes hub setup'));
659
+ console.log('');
660
+
661
+ if (isNew) {
662
+ // Initialize git
663
+ const { execSync } = require('child_process');
664
+ try {
665
+ execSync('git init', { cwd: resolved, stdio: 'ignore' });
666
+ console.log(green(' ✔ ') + 'Created directory & initialized git');
667
+ } catch {
668
+ console.log(green(' ✔ ') + 'Created directory');
669
+ console.log(yellow(' ⚠ ') + 'Could not git init (git not found?)');
670
+ }
671
+
672
+ // Create README
673
+ const readme = [
674
+ '# Vibes Hub',
675
+ '',
676
+ 'Central collection of `.vibe/` and `docs/` files from all my projects.',
677
+ '',
678
+ '## How this works',
679
+ '',
680
+ '1. Run `vibes export` from any project directory',
681
+ '2. Commit and push: `git add -A && git commit -m "update" && git push`',
682
+ '3. Pull on another machine: `git pull`',
683
+ '',
684
+ '## Projects',
685
+ '',
686
+ 'See [index.md](index.md) for auto-generated dashboard.',
687
+ '',
688
+ '## Cross-Project Intelligence',
689
+ '',
690
+ 'The `_insights/` folder is where AI analyzes patterns across all your projects.',
691
+ '',
692
+ '- **[ANALYZE.md](_insights/ANALYZE.md)** — Copy-paste prompt for your AI agent',
693
+ '- **[patterns.md](_insights/patterns.md)** — Architecture, security, and tech patterns',
694
+ '- **[standards.md](_insights/standards.md)** — Universal rules for all projects',
695
+ '- **[opportunities.md](_insights/opportunities.md)** — What one project can learn from another',
696
+ '',
697
+ 'Export 3+ projects, then run the analysis prompt to find cross-project improvements.',
698
+ '',
699
+ ].join('\n');
700
+ fs.writeFileSync(path.join(resolved, 'README.md'), readme, 'utf-8');
701
+ console.log(green(' ✔ ') + 'Created README.md');
702
+
703
+ // Create .gitignore
704
+ fs.writeFileSync(path.join(resolved, '.gitignore'), 'node_modules/\n.DS_Store\nThumbs.db\n', 'utf-8');
705
+ console.log(green(' ✔ ') + 'Created .gitignore');
706
+
707
+ // Create _insights/ intelligence layer
708
+ const insightsDir = path.join(resolved, '_insights');
709
+ fs.mkdirSync(insightsDir, { recursive: true });
710
+
711
+ // Analysis prompt — the AI reads this to know what to do
712
+ const analyze = [
713
+ '# Hub Analysis Prompt',
714
+ '',
715
+ '> Copy-paste this to your AI agent after exporting 3+ projects.',
716
+ '',
717
+ '---',
718
+ '',
719
+ '```',
720
+ 'I have a Vibes Hub — a central collection of .vibe/ and docs/ files',
721
+ 'from all my projects. Read every project folder in this hub and:',
722
+ '',
723
+ '1. CROSS-PROJECT PATTERNS',
724
+ ' - What architectural patterns appear across multiple projects?',
725
+ ' - What security approaches are used? Are they consistent?',
726
+ ' - What tech stack choices are shared? Where do they diverge?',
727
+ ' - What naming conventions or coding standards are consistent?',
728
+ '',
729
+ '2. BEST PRACTICES TO REPLICATE',
730
+ ' - Which project has the best documentation quality?',
731
+ ' - Which project solved a problem that others still have?',
732
+ ' - What did one project do exceptionally well that could set',
733
+ ' the standard for all projects?',
734
+ ' - Are there security, testing, or deployment practices from',
735
+ ' one project that others are missing?',
736
+ '',
737
+ '3. TRANSFERABLE IMPROVEMENTS',
738
+ ' - For each project, list specific improvements it could adopt',
739
+ ' from another project. Be concrete: "Project A could adopt',
740
+ ' Project B\'s approach to X because Y."',
741
+ ' - Identify shared problems that could have a shared solution.',
742
+ '',
743
+ '4. RISKS AND GAPS',
744
+ ' - Which projects have risks that could affect other projects?',
745
+ ' - Are there shared dependencies that create portfolio risk?',
746
+ ' - Which projects have missing or weak documentation areas?',
747
+ '',
748
+ '5. UNIVERSAL STANDARDS',
749
+ ' - Based on what works best across projects, propose standards',
750
+ ' that should apply to ALL projects going forward.',
751
+ ' - These could be file size limits, security practices, naming',
752
+ ' conventions, documentation requirements, etc.',
753
+ '',
754
+ 'Write your findings into the _insights/ folder:',
755
+ '- patterns.md — Cross-project patterns',
756
+ '- standards.md — Proposed universal standards',
757
+ '- opportunities.md — Specific transferable improvements',
758
+ '',
759
+ 'Be specific. Reference actual project names and files.',
760
+ '```',
761
+ '',
762
+ ].join('\n');
763
+ fs.writeFileSync(path.join(insightsDir, 'ANALYZE.md'), analyze, 'utf-8');
764
+
765
+ // Patterns skeleton
766
+ const patterns = [
767
+ '# Cross-Project Patterns',
768
+ '',
769
+ '> Auto-populated by AI analysis. Run the prompt in ANALYZE.md.',
770
+ '',
771
+ '## Architecture Patterns',
772
+ '',
773
+ '<!-- What architectural approaches appear across multiple projects? -->',
774
+ '',
775
+ '## Security Patterns',
776
+ '',
777
+ '<!-- How do projects handle auth, encryption, secrets? -->',
778
+ '',
779
+ '## Shared Tech Stack',
780
+ '',
781
+ '<!-- Languages, frameworks, databases used across projects -->',
782
+ '',
783
+ '| Technology | Used In | Notes |',
784
+ '|---|---|---|',
785
+ '| | | |',
786
+ '',
787
+ '## Common Anti-Patterns',
788
+ '',
789
+ '<!-- Mistakes or bad patterns that appear in multiple projects -->',
790
+ '',
791
+ ].join('\n');
792
+ fs.writeFileSync(path.join(insightsDir, 'patterns.md'), patterns, 'utf-8');
793
+
794
+ // Standards skeleton
795
+ const standards = [
796
+ '# Universal Standards',
797
+ '',
798
+ '> Standards that should apply to ALL projects, based on what works best.',
799
+ '',
800
+ '## Code Standards',
801
+ '',
802
+ '<!-- File size limits, naming conventions, banned patterns -->',
803
+ '',
804
+ '## Documentation Standards',
805
+ '',
806
+ '<!-- What every project must document, minimum quality bar -->',
807
+ '',
808
+ '## Security Standards',
809
+ '',
810
+ '<!-- Auth patterns, encryption requirements, secrets management -->',
811
+ '',
812
+ '## AI Agent Standards',
813
+ '',
814
+ '<!-- Rules every AI agent must follow across all projects -->',
815
+ '',
816
+ ].join('\n');
817
+ fs.writeFileSync(path.join(insightsDir, 'standards.md'), standards, 'utf-8');
818
+
819
+ // Opportunities skeleton
820
+ const opportunities = [
821
+ '# Transferable Improvements',
822
+ '',
823
+ '> Specific things one project does well that others should adopt.',
824
+ '',
825
+ '## [Source Project] → [Target Project]',
826
+ '',
827
+ '**What:** [Specific practice or solution]',
828
+ '',
829
+ '**Why:** [Why the target project would benefit]',
830
+ '',
831
+ '**How:** [What the target project needs to change]',
832
+ '',
833
+ '---',
834
+ '',
835
+ '<!-- Copy the template above for each transferable improvement. -->',
836
+ '',
837
+ ].join('\n');
838
+ fs.writeFileSync(path.join(insightsDir, 'opportunities.md'), opportunities, 'utf-8');
839
+
840
+ console.log(green(' ✔ ') + 'Created _insights/ (analysis prompt + templates)');
841
+ } else {
842
+ console.log(green(' ✔ ') + 'Directory already exists');
843
+ }
844
+
845
+ // Save config
846
+ fs.writeFileSync(HUB_CONFIG, resolved, 'utf-8');
847
+ console.log(green(' ✔ ') + `Hub path saved: ${cyan(resolved)}`);
848
+
849
+ console.log('');
850
+ console.log(bold(' Next — connect to GitHub:'));
851
+ console.log('');
852
+ console.log(dim(' 1. Go to ') + cyan('github.com/new'));
853
+ console.log(dim(' Name: ') + bold('VibeHub') + dim(' (or whatever you want)'));
854
+ console.log(dim(' Visibility: ') + bold('Private'));
855
+ console.log(dim(' Click "Create repository"'));
856
+ console.log('');
857
+ console.log(dim(' 2. Run these commands:'));
858
+ console.log('');
859
+ console.log(cyan(` cd "${resolved}"`));
860
+ console.log(cyan(' git remote add origin git@github.com:YOUR_USERNAME/VibeHub.git'));
861
+ console.log(cyan(' git add -A && git commit -m "init hub" && git push -u origin main'));
862
+ console.log('');
863
+ console.log(dim(' After that, exporting from any project is just:'));
864
+ console.log('');
865
+ console.log(cyan(' vibes export'));
866
+ console.log(cyan(` cd "${resolved}" && git add -A && git commit -m "update" && git push`));
867
+ console.log('');
868
+ console.log(dim(' On another computer, clone the repo and run:'));
869
+ console.log(cyan(' vibes hub <path-to-cloned-repo>'));
870
+ console.log('');
871
+ }
872
+
364
873
  function printNextSteps() {
365
874
  console.log('');
366
875
  console.log(bold(' What to do next:'));
@@ -381,13 +890,18 @@ function printHelp() {
381
890
  console.log('');
382
891
  console.log(' ' + bold('Usage:'));
383
892
  console.log('');
384
- console.log(' vibes init Create .vibe/ with the full suite (15 files + guide)');
385
- console.log(' vibes docs Create docs/ operational documentation (11 files)');
386
- console.log(' vibes all Create both .vibe/ and docs/ at once');
387
- console.log(' vibes check Validate all files for completeness');
388
- console.log(' vibes status Quick health dashboard');
389
- console.log(' vibes reset Delete and recreate everything');
390
- console.log(' vibes help Show this help message');
893
+ console.log(' vibes init Create .vibe/ with the full suite (15 files + guide)');
894
+ console.log(' vibes docs Create docs/ operational documentation (11 files)');
895
+ console.log(' vibes all Create both .vibe/ and docs/ at once');
896
+ console.log(' vibes check Validate all files for completeness');
897
+ console.log(' vibes status Quick health dashboard');
898
+ console.log(' vibes reset Delete and recreate everything');
899
+ console.log('');
900
+ console.log(' ' + bold('Hub (cross-project sync):'));
901
+ console.log('');
902
+ console.log(' vibes hub <path> Set your central hub directory (saved globally)');
903
+ console.log(' vibes export Export .vibe/ + docs/ to your hub');
904
+ console.log(' vibes export <path> Export to a specific hub path');
391
905
  console.log('');
392
906
  console.log(' ' + bold('.vibe/') + dim(' — Project memory'));
393
907
  console.log(dim(' Core: purpose · architecture · flows · entities · decisions · state'));
@@ -420,6 +934,8 @@ switch (command) {
420
934
  case 'check': cmdCheck(targetDir); break;
421
935
  case 'status': cmdStatus(targetDir); break;
422
936
  case 'reset': cmdReset(targetDir); break;
937
+ case 'export': cmdExport('.', args[1]); break;
938
+ case 'hub': cmdHub(args[1]); break;
423
939
  case 'help': case '--help': case '-h': printHelp(); break;
424
940
  default:
425
941
  console.log(red(`\n Unknown command: ${command}`));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibe-me",
3
- "version": "3.0.0",
3
+ "version": "3.1.0",
4
4
  "description": "The complete semantic layer for any project. Repository memory, product memory, business memory, AI instructions, and living context — so any human or AI can understand your project without reading the source.",
5
5
  "keywords": [
6
6
  "vibes",