cc-starter 1.0.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/LICENSE +21 -0
- package/README.md +149 -0
- package/bin/cc-starter.js +55 -0
- package/lib/constants.js +32 -0
- package/lib/detect.js +109 -0
- package/lib/plugins.js +74 -0
- package/lib/scaffold.js +261 -0
- package/lib/wizard.js +99 -0
- package/package.json +30 -0
- package/template/CLAUDE.md.hbs +44 -0
- package/template/claude/commands/kickstart.md +16 -0
- package/template/claude/memory/MEMORY.md +11 -0
- package/template/claude/project/README.md +7 -0
- package/template/claude/reference/README.md +7 -0
- package/template/claude/rules/01-general.md +36 -0
- package/template/claude/rules/02-code-standards.md +23 -0
- package/template/claude/rules/03-dev-ops.md +20 -0
- package/template/claude/settings.json +4 -0
- package/template/scripts/stats/cocomo.js +178 -0
- package/template/scripts/stats/project-report.js +640 -0
- package/template/scripts/stats/vibe-code.js +533 -0
- package/template/scripts/stats/vibe-stats.js +249 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// cc-starter Token Tracker
|
|
4
|
+
// Track token savings from vibe-coded tooling vs traditional approaches.
|
|
5
|
+
// CommonJS, zero external dependencies.
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
const STATS_FILE = path.join(process.cwd(), '.vibe-stats.json');
|
|
11
|
+
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Helpers
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
function loadStats() {
|
|
17
|
+
if (!fs.existsSync(STATS_FILE)) {
|
|
18
|
+
return { sessions: [], totalSaved: 0, operationsCount: 0, created: new Date().toISOString() };
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
return JSON.parse(fs.readFileSync(STATS_FILE, 'utf-8'));
|
|
22
|
+
} catch {
|
|
23
|
+
console.error('Error: could not parse .vibe-stats.json — starting fresh.');
|
|
24
|
+
return { sessions: [], totalSaved: 0, operationsCount: 0, created: new Date().toISOString() };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function saveStats(stats) {
|
|
29
|
+
fs.writeFileSync(STATS_FILE, JSON.stringify(stats, null, 2), 'utf-8');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function fmt(n) {
|
|
33
|
+
return Number(n).toLocaleString('en-US');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function estimateTokens(charCount) {
|
|
37
|
+
return Math.ceil(charCount / 4);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// Commands
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
function cmdLog(args) {
|
|
45
|
+
const operation = args[0];
|
|
46
|
+
const traditionalSize = parseInt(args[1], 10);
|
|
47
|
+
const vibeSize = parseInt(args[2], 10);
|
|
48
|
+
const file = args[3] || null;
|
|
49
|
+
|
|
50
|
+
if (!operation || isNaN(traditionalSize) || isNaN(vibeSize)) {
|
|
51
|
+
console.error('Usage: vibe-stats log <operation> <traditional-size> <vibe-size> [file]');
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const saved = traditionalSize - vibeSize;
|
|
56
|
+
const percentage = traditionalSize > 0 ? Math.round((saved / traditionalSize) * 100) : 0;
|
|
57
|
+
|
|
58
|
+
const entry = {
|
|
59
|
+
timestamp: new Date().toISOString(),
|
|
60
|
+
operation,
|
|
61
|
+
file,
|
|
62
|
+
traditionalTokens: traditionalSize,
|
|
63
|
+
vibeModeTokens: vibeSize,
|
|
64
|
+
savedTokens: saved,
|
|
65
|
+
percentage,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const stats = loadStats();
|
|
69
|
+
stats.sessions.push(entry);
|
|
70
|
+
stats.totalSaved += saved;
|
|
71
|
+
stats.operationsCount += 1;
|
|
72
|
+
saveStats(stats);
|
|
73
|
+
|
|
74
|
+
console.log(`Logged: ${operation} — saved ${fmt(saved)} tokens (${percentage}%)`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function cmdReport() {
|
|
78
|
+
const stats = loadStats();
|
|
79
|
+
|
|
80
|
+
if (stats.operationsCount === 0) {
|
|
81
|
+
console.log('No operations recorded yet. Use "vibe-stats log" to add entries.');
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Period
|
|
86
|
+
const created = new Date(stats.created);
|
|
87
|
+
const now = new Date();
|
|
88
|
+
const days = Math.max(1, Math.ceil((now - created) / (1000 * 60 * 60 * 24)));
|
|
89
|
+
|
|
90
|
+
console.log('');
|
|
91
|
+
console.log('=== cc-starter Token Tracker — Report ===');
|
|
92
|
+
console.log('');
|
|
93
|
+
console.log(`Period: ${created.toISOString().slice(0, 10)} → ${now.toISOString().slice(0, 10)} (${days} day${days !== 1 ? 's' : ''})`);
|
|
94
|
+
console.log(`Operations: ${fmt(stats.operationsCount)}`);
|
|
95
|
+
console.log(`Total saved: ${fmt(stats.totalSaved)} tokens`);
|
|
96
|
+
console.log('');
|
|
97
|
+
|
|
98
|
+
// Breakdown by operation type
|
|
99
|
+
const byOp = {};
|
|
100
|
+
for (const s of stats.sessions) {
|
|
101
|
+
if (!byOp[s.operation]) {
|
|
102
|
+
byOp[s.operation] = { count: 0, saved: 0, traditional: 0, vibe: 0 };
|
|
103
|
+
}
|
|
104
|
+
byOp[s.operation].count += 1;
|
|
105
|
+
byOp[s.operation].saved += s.savedTokens;
|
|
106
|
+
byOp[s.operation].traditional += s.traditionalTokens;
|
|
107
|
+
byOp[s.operation].vibe += s.vibeModeTokens;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
console.log('--- Breakdown by operation ---');
|
|
111
|
+
const ops = Object.keys(byOp).sort((a, b) => byOp[b].saved - byOp[a].saved);
|
|
112
|
+
for (const op of ops) {
|
|
113
|
+
const d = byOp[op];
|
|
114
|
+
const pct = d.traditional > 0 ? Math.round((d.saved / d.traditional) * 100) : 0;
|
|
115
|
+
console.log(` ${op}: ${fmt(d.saved)} tokens saved across ${d.count} op${d.count !== 1 ? 's' : ''} (${pct}% avg reduction)`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Recent 10
|
|
119
|
+
const recent = stats.sessions.slice(-10).reverse();
|
|
120
|
+
console.log('');
|
|
121
|
+
console.log('--- Recent operations (last 10) ---');
|
|
122
|
+
for (const s of recent) {
|
|
123
|
+
const ts = s.timestamp.slice(0, 16).replace('T', ' ');
|
|
124
|
+
const fileStr = s.file ? ` [${s.file}]` : '';
|
|
125
|
+
console.log(` ${ts} ${s.operation}${fileStr} saved ${fmt(s.savedTokens)} (${s.percentage}%)`);
|
|
126
|
+
}
|
|
127
|
+
console.log('');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function cmdEstimate(args) {
|
|
131
|
+
const filePath = args[0];
|
|
132
|
+
if (!filePath) {
|
|
133
|
+
console.error('Usage: vibe-stats estimate <file>');
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const resolved = path.resolve(filePath);
|
|
138
|
+
if (!fs.existsSync(resolved)) {
|
|
139
|
+
console.error(`File not found: ${resolved}`);
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const content = fs.readFileSync(resolved, 'utf-8');
|
|
144
|
+
const chars = content.length;
|
|
145
|
+
const lines = content.split('\n').length;
|
|
146
|
+
const tokens = estimateTokens(chars);
|
|
147
|
+
|
|
148
|
+
console.log(`File: ${path.basename(resolved)}`);
|
|
149
|
+
console.log(`Chars: ${fmt(chars)}`);
|
|
150
|
+
console.log(`Lines: ${fmt(lines)}`);
|
|
151
|
+
console.log(`Tokens: ~${fmt(tokens)} (estimated at 1 token per 4 chars)`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function cmdCompare(args) {
|
|
155
|
+
const file1 = args[0];
|
|
156
|
+
const file2 = args[1];
|
|
157
|
+
if (!file1 || !file2) {
|
|
158
|
+
console.error('Usage: vibe-stats compare <file1> <file2>');
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const resolved1 = path.resolve(file1);
|
|
163
|
+
const resolved2 = path.resolve(file2);
|
|
164
|
+
|
|
165
|
+
for (const f of [resolved1, resolved2]) {
|
|
166
|
+
if (!fs.existsSync(f)) {
|
|
167
|
+
console.error(`File not found: ${f}`);
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const content1 = fs.readFileSync(resolved1, 'utf-8');
|
|
173
|
+
const content2 = fs.readFileSync(resolved2, 'utf-8');
|
|
174
|
+
const tokens1 = estimateTokens(content1.length);
|
|
175
|
+
const tokens2 = estimateTokens(content2.length);
|
|
176
|
+
const diff = tokens1 - tokens2;
|
|
177
|
+
const pct = tokens1 > 0 ? Math.round((Math.abs(diff) / tokens1) * 100) : 0;
|
|
178
|
+
|
|
179
|
+
console.log(`File 1: ${path.basename(resolved1)} — ~${fmt(tokens1)} tokens`);
|
|
180
|
+
console.log(`File 2: ${path.basename(resolved2)} — ~${fmt(tokens2)} tokens`);
|
|
181
|
+
console.log('');
|
|
182
|
+
if (diff > 0) {
|
|
183
|
+
console.log(`File 1 is larger by ~${fmt(diff)} tokens (${pct}% more)`);
|
|
184
|
+
} else if (diff < 0) {
|
|
185
|
+
console.log(`File 2 is larger by ~${fmt(Math.abs(diff))} tokens (${pct}% more)`);
|
|
186
|
+
} else {
|
|
187
|
+
console.log('Both files have approximately the same token count.');
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function cmdSummary() {
|
|
192
|
+
const stats = loadStats();
|
|
193
|
+
if (stats.operationsCount === 0) {
|
|
194
|
+
console.log('No operations recorded yet.');
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
console.log(`Total: ${fmt(stats.totalSaved)} tokens saved across ${fmt(stats.operationsCount)} operations`);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function cmdReset(args) {
|
|
201
|
+
if (!args.includes('--confirm')) {
|
|
202
|
+
console.error('Usage: vibe-stats reset --confirm');
|
|
203
|
+
console.error('This will delete all recorded statistics.');
|
|
204
|
+
process.exit(1);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (fs.existsSync(STATS_FILE)) {
|
|
208
|
+
fs.unlinkSync(STATS_FILE);
|
|
209
|
+
}
|
|
210
|
+
console.log('Statistics reset.');
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ---------------------------------------------------------------------------
|
|
214
|
+
// Main
|
|
215
|
+
// ---------------------------------------------------------------------------
|
|
216
|
+
|
|
217
|
+
const [command, ...args] = process.argv.slice(2);
|
|
218
|
+
|
|
219
|
+
switch (command) {
|
|
220
|
+
case 'log':
|
|
221
|
+
cmdLog(args);
|
|
222
|
+
break;
|
|
223
|
+
case 'report':
|
|
224
|
+
cmdReport();
|
|
225
|
+
break;
|
|
226
|
+
case 'estimate':
|
|
227
|
+
cmdEstimate(args);
|
|
228
|
+
break;
|
|
229
|
+
case 'compare':
|
|
230
|
+
cmdCompare(args);
|
|
231
|
+
break;
|
|
232
|
+
case 'summary':
|
|
233
|
+
cmdSummary();
|
|
234
|
+
break;
|
|
235
|
+
case 'reset':
|
|
236
|
+
cmdReset(args);
|
|
237
|
+
break;
|
|
238
|
+
default:
|
|
239
|
+
console.log('cc-starter Token Tracker');
|
|
240
|
+
console.log('');
|
|
241
|
+
console.log('Commands:');
|
|
242
|
+
console.log(' log <operation> <traditional-size> <vibe-size> [file] Log a token savings entry');
|
|
243
|
+
console.log(' report Show detailed statistics');
|
|
244
|
+
console.log(' estimate <file> Estimate tokens in a file');
|
|
245
|
+
console.log(' compare <file1> <file2> Compare token counts');
|
|
246
|
+
console.log(' summary One-liner quick check');
|
|
247
|
+
console.log(' reset --confirm Reset all statistics');
|
|
248
|
+
break;
|
|
249
|
+
}
|