vibe-me 2.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.
- package/README.md +475 -85
- package/Vibe.bat +14 -10
- package/bin/vibes.js +634 -111
- package/package.json +2 -2
- package/templates/ai.md +49 -0
- package/templates/business.md +52 -0
- package/templates/context.md +48 -0
- package/templates/decisions.md +8 -5
- package/templates/experiments.md +36 -0
- package/templates/market.md +39 -0
- package/templates/metrics.md +35 -0
- package/templates/product.md +54 -0
- package/templates/risks.md +44 -0
- package/templates/users.md +47 -0
package/bin/vibes.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// vibes —
|
|
3
|
+
// vibes — The complete semantic layer for any project
|
|
4
|
+
// Repository memory + Product memory + Business memory + AI instructions
|
|
4
5
|
// Zero dependencies. Single file. Works with npx.
|
|
5
6
|
|
|
6
7
|
const fs = require('fs');
|
|
@@ -14,12 +15,24 @@ const VIBE_DIR = '.vibe';
|
|
|
14
15
|
const DOCS_DIR = 'docs';
|
|
15
16
|
const TEMPLATES_DIR = path.join(__dirname, '..', 'templates');
|
|
16
17
|
const SPEC_DIR = path.join(__dirname, '..', 'spec');
|
|
18
|
+
const HUB_CONFIG = path.join(process.env.USERPROFILE || process.env.HOME || '', '.vibes-hub');
|
|
17
19
|
|
|
20
|
+
// All .vibe/ files — the full suite, always installed
|
|
18
21
|
const VIBE_FILES = [
|
|
22
|
+
// Core — repository memory
|
|
19
23
|
'purpose.md', 'architecture.md', 'flows.md',
|
|
20
|
-
'entities.md', 'decisions.md', 'state.json'
|
|
24
|
+
'entities.md', 'decisions.md', 'state.json',
|
|
25
|
+
// Living context
|
|
26
|
+
'context.md',
|
|
27
|
+
// AI agent guide
|
|
28
|
+
'ai.md',
|
|
29
|
+
// Product memory
|
|
30
|
+
'product.md', 'users.md', 'metrics.md', 'experiments.md',
|
|
31
|
+
// Business memory
|
|
32
|
+
'business.md', 'market.md', 'risks.md',
|
|
21
33
|
];
|
|
22
34
|
|
|
35
|
+
// All docs/ files
|
|
23
36
|
const DOCS_FILES = [
|
|
24
37
|
'README.md', 'topology.md', 'architecture.md', 'api.md',
|
|
25
38
|
'issues.md', 'resolved.md', 'roadmap.md', 'developer_guide.md',
|
|
@@ -31,7 +44,7 @@ const DOCS_EXTRA = ['decisions/0001-template.md'];
|
|
|
31
44
|
const SPEC_FILE = 'VIBE_GUIDE.md';
|
|
32
45
|
|
|
33
46
|
// ─────────────────────────────────────────────
|
|
34
|
-
// Colors (
|
|
47
|
+
// Colors (raw ANSI, zero dependencies)
|
|
35
48
|
// ─────────────────────────────────────────────
|
|
36
49
|
|
|
37
50
|
const bold = (s) => `\x1b[1m${s}\x1b[0m`;
|
|
@@ -40,7 +53,6 @@ const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
|
|
|
40
53
|
const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
|
|
41
54
|
const dim = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
42
55
|
const red = (s) => `\x1b[31m${s}\x1b[0m`;
|
|
43
|
-
const magenta = (s) => `\x1b[35m${s}\x1b[0m`;
|
|
44
56
|
|
|
45
57
|
// ─────────────────────────────────────────────
|
|
46
58
|
// Helpers
|
|
@@ -60,6 +72,8 @@ function getProjectName(dir) {
|
|
|
60
72
|
['Cargo.toml', /^name\s*=\s*"(.+)"/m],
|
|
61
73
|
['go.mod', /^module\s+(.+)/m],
|
|
62
74
|
['pyproject.toml', /^name\s*=\s*"(.+)"/m],
|
|
75
|
+
['setup.py', /name\s*=\s*['"](.+?)['"]/m],
|
|
76
|
+
['CMakeLists.txt', /project\s*\(\s*(\S+)/m],
|
|
63
77
|
]) {
|
|
64
78
|
const p = path.join(dir, file);
|
|
65
79
|
if (!exists(p)) continue;
|
|
@@ -95,13 +109,24 @@ function cmdInit(targetDir) {
|
|
|
95
109
|
|
|
96
110
|
console.log('');
|
|
97
111
|
console.log(bold(' ⚡ vibes init'));
|
|
98
|
-
console.log(dim(` Creating .vibe/
|
|
112
|
+
console.log(dim(` Creating .vibe/ for ${cyan(name)} — full suite`));
|
|
99
113
|
console.log('');
|
|
100
114
|
|
|
101
115
|
fs.mkdirSync(vibeDir, { recursive: true });
|
|
102
116
|
|
|
117
|
+
// Group labels for visual output
|
|
118
|
+
const groups = {
|
|
119
|
+
'purpose.md': '── Core (repository memory) ──',
|
|
120
|
+
'context.md': '── Living Context ──',
|
|
121
|
+
'ai.md': '── AI Agent Guide ──',
|
|
122
|
+
'product.md': '── Product Memory ──',
|
|
123
|
+
'business.md': '── Business Memory ──',
|
|
124
|
+
};
|
|
125
|
+
|
|
103
126
|
let count = 0;
|
|
104
127
|
for (const file of VIBE_FILES) {
|
|
128
|
+
if (groups[file]) console.log(dim(` ${groups[file]}`));
|
|
129
|
+
|
|
105
130
|
const src = path.join(TEMPLATES_DIR, file);
|
|
106
131
|
const dest = path.join(vibeDir, file);
|
|
107
132
|
if (file === 'state.json') {
|
|
@@ -117,6 +142,7 @@ function cmdInit(targetDir) {
|
|
|
117
142
|
|
|
118
143
|
// Copy guide
|
|
119
144
|
copy(path.join(SPEC_DIR, SPEC_FILE), path.join(vibeDir, SPEC_FILE));
|
|
145
|
+
console.log('');
|
|
120
146
|
console.log(green(' ✔ ') + dim('.vibe/') + SPEC_FILE + dim(' (agent instructions)'));
|
|
121
147
|
count++;
|
|
122
148
|
|
|
@@ -130,29 +156,24 @@ function cmdDocs(targetDir) {
|
|
|
130
156
|
const name = getProjectName(targetDir);
|
|
131
157
|
|
|
132
158
|
if (exists(docsDir)) {
|
|
133
|
-
// Check if it has content already
|
|
134
159
|
const contents = fs.readdirSync(docsDir);
|
|
135
160
|
if (contents.length > 0) {
|
|
136
161
|
console.log(yellow('\n ⚠ docs/ already exists with ' + contents.length + ' items.'));
|
|
137
|
-
console.log(dim('
|
|
162
|
+
console.log(dim(' Adding missing files only.\n'));
|
|
138
163
|
}
|
|
139
164
|
}
|
|
140
165
|
|
|
141
166
|
console.log('');
|
|
142
167
|
console.log(bold(' 📄 vibes docs'));
|
|
143
|
-
console.log(dim(` Creating docs/
|
|
168
|
+
console.log(dim(` Creating docs/ for ${cyan(name)}`));
|
|
144
169
|
console.log('');
|
|
145
170
|
|
|
146
|
-
// Create docs dir and subdirs
|
|
147
171
|
fs.mkdirSync(docsDir, { recursive: true });
|
|
148
172
|
for (const sub of DOCS_DIRS) {
|
|
149
173
|
fs.mkdirSync(path.join(docsDir, sub), { recursive: true });
|
|
150
174
|
}
|
|
151
175
|
|
|
152
|
-
let created = 0;
|
|
153
|
-
let skipped = 0;
|
|
154
|
-
|
|
155
|
-
// Copy doc files (skip if they already exist)
|
|
176
|
+
let created = 0, skipped = 0;
|
|
156
177
|
for (const file of [...DOCS_FILES, ...DOCS_EXTRA]) {
|
|
157
178
|
const src = path.join(TEMPLATES_DIR, 'docs', file);
|
|
158
179
|
const dest = path.join(docsDir, file);
|
|
@@ -167,19 +188,8 @@ function cmdDocs(targetDir) {
|
|
|
167
188
|
}
|
|
168
189
|
|
|
169
190
|
console.log('');
|
|
170
|
-
if (skipped > 0) {
|
|
171
|
-
|
|
172
|
-
} else {
|
|
173
|
-
console.log(green(` ✔ Created ${created} files in docs/`));
|
|
174
|
-
}
|
|
175
|
-
console.log('');
|
|
176
|
-
console.log(bold(' Next steps:'));
|
|
177
|
-
console.log('');
|
|
178
|
-
console.log(' Tell your AI agent:');
|
|
179
|
-
console.log('');
|
|
180
|
-
console.log(cyan(' "Read .vibe/VIBE_GUIDE.md for context, then analyze'));
|
|
181
|
-
console.log(cyan(' the codebase and fill out all skeleton files in docs/.'));
|
|
182
|
-
console.log(cyan(' Ask me any questions you can\'t answer from the code."'));
|
|
191
|
+
if (skipped > 0) console.log(green(` ✔ Created ${created} files`) + dim(`, skipped ${skipped} existing`));
|
|
192
|
+
else console.log(green(` ✔ Created ${created} files in docs/`));
|
|
183
193
|
console.log('');
|
|
184
194
|
}
|
|
185
195
|
|
|
@@ -197,12 +207,129 @@ function cmdCheck(targetDir) {
|
|
|
197
207
|
console.log('');
|
|
198
208
|
console.log(bold(' 🔍 vibes check'));
|
|
199
209
|
|
|
200
|
-
let passed = 0, failed = 0, warnings = 0;
|
|
210
|
+
let passed = 0, failed = 0, warnings = 0, na = 0;
|
|
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
|
+
}
|
|
201
240
|
|
|
202
|
-
|
|
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/ ───
|
|
203
330
|
if (hasVibe) {
|
|
204
331
|
console.log('');
|
|
205
|
-
console.log(dim(' ── .vibe/
|
|
332
|
+
console.log(dim(' ── .vibe/ ──'));
|
|
206
333
|
console.log('');
|
|
207
334
|
|
|
208
335
|
for (const file of VIBE_FILES) {
|
|
@@ -212,34 +339,35 @@ function cmdCheck(targetDir) {
|
|
|
212
339
|
const content = fs.readFileSync(fp, 'utf-8').trim();
|
|
213
340
|
const lines = content.split('\n').length;
|
|
214
341
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
if (file === 'decisions.md' && (content.match(/^## Why /gm) || []).length < 2) warn = 'fewer than 2 decisions';
|
|
221
|
-
if (file === 'entities.md' && !content.includes('What depends on it')) warn = 'missing "What depends on it?" fields';
|
|
222
|
-
if (file === 'flows.md' && (content.match(/^## \d+\./gm) || []).length < 2) warn = 'fewer than 2 flows';
|
|
223
|
-
if (file === 'state.json') {
|
|
224
|
-
try {
|
|
225
|
-
const s = JSON.parse(content);
|
|
226
|
-
if (!s.vibe_updated) warn = 'missing vibe_updated timestamp';
|
|
227
|
-
else {
|
|
228
|
-
const days = (Date.now() - new Date(s.vibe_updated).getTime()) / 86400000;
|
|
229
|
-
if (days > 30) warn = `last updated ${Math.floor(days)} days ago`;
|
|
230
|
-
}
|
|
231
|
-
} catch { console.log(red(' ✖ ') + file + ' — invalid JSON'); failed++; continue; }
|
|
342
|
+
// N/A check
|
|
343
|
+
if (content.match(/^N\/A/m)) {
|
|
344
|
+
console.log(dim(' ⊘ ') + file + dim(' — N/A (not applicable)'));
|
|
345
|
+
na++;
|
|
346
|
+
continue;
|
|
232
347
|
}
|
|
233
348
|
|
|
234
|
-
|
|
235
|
-
|
|
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++;
|
|
363
|
+
}
|
|
236
364
|
}
|
|
237
365
|
}
|
|
238
366
|
|
|
239
|
-
//
|
|
367
|
+
// ─── Run checks on docs/ ───
|
|
240
368
|
if (hasDocs) {
|
|
241
369
|
console.log('');
|
|
242
|
-
console.log(dim(' ── docs/
|
|
370
|
+
console.log(dim(' ── docs/ ──'));
|
|
243
371
|
console.log('');
|
|
244
372
|
|
|
245
373
|
for (const file of DOCS_FILES) {
|
|
@@ -249,15 +377,31 @@ function cmdCheck(targetDir) {
|
|
|
249
377
|
const content = fs.readFileSync(fp, 'utf-8').trim();
|
|
250
378
|
const lines = content.split('\n').length;
|
|
251
379
|
|
|
252
|
-
|
|
253
|
-
|
|
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
|
+
}
|
|
254
394
|
}
|
|
255
395
|
}
|
|
256
396
|
|
|
397
|
+
// ─── Summary ───
|
|
257
398
|
console.log('');
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
399
|
+
let result = '';
|
|
400
|
+
if (failed > 0) result = red(`${failed} failed`);
|
|
401
|
+
if (warnings > 0) result += (result ? ', ' : '') + yellow(`${warnings} warnings`);
|
|
402
|
+
if (na > 0) result += (result ? ', ' : '') + dim(`${na} N/A`);
|
|
403
|
+
result += (result ? ', ' : '') + green(`${passed} passed`);
|
|
404
|
+
console.log(` Result: ${result}`);
|
|
261
405
|
console.log('');
|
|
262
406
|
}
|
|
263
407
|
|
|
@@ -270,23 +414,25 @@ function cmdStatus(targetDir) {
|
|
|
270
414
|
console.log(bold(` 📊 ${name}`));
|
|
271
415
|
console.log('');
|
|
272
416
|
|
|
273
|
-
// .vibe status
|
|
274
417
|
if (exists(vibeDir)) {
|
|
275
|
-
|
|
418
|
+
// Count filled vs N/A vs empty
|
|
419
|
+
let filled = 0, naCount = 0, empty = 0;
|
|
420
|
+
for (const f of VIBE_FILES) {
|
|
276
421
|
const fp = path.join(vibeDir, f);
|
|
277
|
-
if (!exists(fp))
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
422
|
+
if (!exists(fp)) { empty++; continue; }
|
|
423
|
+
const content = fs.readFileSync(fp, 'utf-8').trim();
|
|
424
|
+
if (content.match(/^N\/A/m)) naCount++;
|
|
425
|
+
else if (content.split('\n').length >= 10) filled++;
|
|
426
|
+
else empty++;
|
|
427
|
+
}
|
|
428
|
+
const icon = empty === 0 ? green('✔') : filled > 0 ? yellow('◐') : red('✖');
|
|
429
|
+
console.log(` ${icon} .vibe/ ${filled} filled, ${naCount} N/A, ${empty} empty (${VIBE_FILES.length} total)`);
|
|
282
430
|
|
|
283
|
-
// Show state.json health if available
|
|
284
431
|
const statePath = path.join(vibeDir, 'state.json');
|
|
285
432
|
if (exists(statePath)) {
|
|
286
433
|
try {
|
|
287
434
|
const s = JSON.parse(fs.readFileSync(statePath, 'utf-8'));
|
|
288
435
|
if (s.health) console.log(dim(` health: ${s.health}`));
|
|
289
|
-
if (s.version) console.log(dim(` version: ${s.version}`));
|
|
290
436
|
if (s.vibe_updated) {
|
|
291
437
|
const days = Math.floor((Date.now() - new Date(s.vibe_updated).getTime()) / 86400000);
|
|
292
438
|
console.log(dim(` updated: ${days === 0 ? 'today' : days + ' days ago'}`));
|
|
@@ -297,7 +443,6 @@ function cmdStatus(targetDir) {
|
|
|
297
443
|
console.log(red(' ✖') + ' .vibe/ not initialized');
|
|
298
444
|
}
|
|
299
445
|
|
|
300
|
-
// docs status
|
|
301
446
|
if (exists(docsDir)) {
|
|
302
447
|
const filled = DOCS_FILES.filter(f => {
|
|
303
448
|
const fp = path.join(docsDir, f);
|
|
@@ -321,17 +466,10 @@ function cmdAll(targetDir) {
|
|
|
321
466
|
const vibeDir = path.join(targetDir, VIBE_DIR);
|
|
322
467
|
const docsDir = path.join(targetDir, DOCS_DIR);
|
|
323
468
|
|
|
324
|
-
if (!exists(vibeDir))
|
|
325
|
-
|
|
326
|
-
} else {
|
|
327
|
-
console.log(yellow('\n ⊘ .vibe/ already exists, skipping init'));
|
|
328
|
-
}
|
|
469
|
+
if (!exists(vibeDir)) cmdInit(targetDir);
|
|
470
|
+
else console.log(yellow('\n ⊘ .vibe/ already exists, skipping init'));
|
|
329
471
|
|
|
330
|
-
|
|
331
|
-
cmdDocs(targetDir);
|
|
332
|
-
} else {
|
|
333
|
-
cmdDocs(targetDir); // docs command already handles existing files gracefully
|
|
334
|
-
}
|
|
472
|
+
cmdDocs(targetDir);
|
|
335
473
|
}
|
|
336
474
|
|
|
337
475
|
function cmdReset(targetDir) {
|
|
@@ -345,56 +483,439 @@ function cmdReset(targetDir) {
|
|
|
345
483
|
cmdAll(targetDir);
|
|
346
484
|
}
|
|
347
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
|
+
|
|
348
873
|
function printNextSteps() {
|
|
349
874
|
console.log('');
|
|
350
|
-
console.log(bold('
|
|
875
|
+
console.log(bold(' What to do next:'));
|
|
351
876
|
console.log('');
|
|
352
|
-
console.log('
|
|
353
|
-
console.log(' 2. Tell it:');
|
|
877
|
+
console.log(' Tell your AI coding agent:');
|
|
354
878
|
console.log('');
|
|
355
879
|
console.log(cyan(' "Read .vibe/VIBE_GUIDE.md, then analyze this codebase'));
|
|
356
|
-
console.log(cyan(' and fill out all the skeleton files in .vibe/'));
|
|
880
|
+
console.log(cyan(' and fill out all the skeleton files in .vibe/ and docs/.'));
|
|
881
|
+
console.log(cyan(' For files that aren\'t relevant (like business.md for a'));
|
|
882
|
+
console.log(cyan(' library), write N/A with a brief explanation.'));
|
|
357
883
|
console.log(cyan(' Ask me any questions you can\'t answer from the code."'));
|
|
358
884
|
console.log('');
|
|
359
|
-
console.log(' 3. Run ' + cyan('vibes docs') + ' to scaffold operational docs too');
|
|
360
|
-
console.log(' 4. Review the output, commit to version control');
|
|
361
|
-
console.log('');
|
|
362
885
|
}
|
|
363
886
|
|
|
364
887
|
function printHelp() {
|
|
365
888
|
console.log('');
|
|
366
|
-
console.log(bold(' vibes') + ' —
|
|
889
|
+
console.log(bold(' vibes') + ' — The complete semantic layer for any project');
|
|
367
890
|
console.log('');
|
|
368
891
|
console.log(' ' + bold('Usage:'));
|
|
369
892
|
console.log('');
|
|
370
|
-
console.log(' vibes init
|
|
371
|
-
console.log(' vibes docs
|
|
372
|
-
console.log(' vibes all
|
|
373
|
-
console.log(' vibes check
|
|
374
|
-
console.log(' vibes status
|
|
375
|
-
console.log(' vibes reset
|
|
376
|
-
console.log('
|
|
377
|
-
console.log('');
|
|
378
|
-
console.log('
|
|
379
|
-
console.log('
|
|
380
|
-
console.log('
|
|
381
|
-
console.log('
|
|
382
|
-
console.log('
|
|
383
|
-
console.log(' ' +
|
|
384
|
-
console.log('
|
|
385
|
-
console.log('');
|
|
386
|
-
console.log(
|
|
387
|
-
console.log('
|
|
388
|
-
console.log('
|
|
389
|
-
console.log('
|
|
390
|
-
console.log(' ' + dim('
|
|
391
|
-
console.log('
|
|
392
|
-
console.log('
|
|
393
|
-
console.log('
|
|
394
|
-
console.log('
|
|
395
|
-
console.log('
|
|
396
|
-
console.log(' ' + dim('├── glossary.md — Project-specific terms'));
|
|
397
|
-
console.log(' ' + dim('└── decisions/ — Architecture decision records'));
|
|
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');
|
|
905
|
+
console.log('');
|
|
906
|
+
console.log(' ' + bold('.vibe/') + dim(' — Project memory'));
|
|
907
|
+
console.log(dim(' Core: purpose · architecture · flows · entities · decisions · state'));
|
|
908
|
+
console.log(dim(' Context: context (living state, updated weekly)'));
|
|
909
|
+
console.log(dim(' AI: ai (agent constraints, safe zones, rules)'));
|
|
910
|
+
console.log(dim(' Product: product · users · metrics · experiments'));
|
|
911
|
+
console.log(dim(' Business: business · market · risks'));
|
|
912
|
+
console.log('');
|
|
913
|
+
console.log(' ' + bold('docs/') + dim(' — Operational documentation'));
|
|
914
|
+
console.log(dim(' README · topology · architecture · api · issues · resolved'));
|
|
915
|
+
console.log(dim(' roadmap · developer_guide · troubleshooting · glossary · decisions/'));
|
|
916
|
+
console.log('');
|
|
917
|
+
console.log(dim(' Files that aren\'t relevant to your project? Mark them N/A.'));
|
|
918
|
+
console.log(dim(' Your AI agent will figure out which ones apply.'));
|
|
398
919
|
console.log('');
|
|
399
920
|
}
|
|
400
921
|
|
|
@@ -413,6 +934,8 @@ switch (command) {
|
|
|
413
934
|
case 'check': cmdCheck(targetDir); break;
|
|
414
935
|
case 'status': cmdStatus(targetDir); break;
|
|
415
936
|
case 'reset': cmdReset(targetDir); break;
|
|
937
|
+
case 'export': cmdExport('.', args[1]); break;
|
|
938
|
+
case 'hub': cmdHub(args[1]); break;
|
|
416
939
|
case 'help': case '--help': case '-h': printHelp(); break;
|
|
417
940
|
default:
|
|
418
941
|
console.log(red(`\n Unknown command: ${command}`));
|