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,178 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// COCOMO-II Semi-Detached cost estimation
|
|
3
|
+
// Usage: node scripts/stats/cocomo.js
|
|
4
|
+
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const path = require("path");
|
|
7
|
+
|
|
8
|
+
// ── Configuration ──────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
const SKIP_DIRS = new Set([
|
|
11
|
+
"node_modules", ".git", ".next", "dist", "build",
|
|
12
|
+
".cache", "__pycache__", ".venv", "target", "vendor", ".claude",
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
const SKIP_FILES = new Set([
|
|
16
|
+
"package-lock.json", "yarn.lock", "pnpm-lock.yaml",
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
const EXT_MAP = {
|
|
20
|
+
".ts": "TypeScript",
|
|
21
|
+
".tsx": "TypeScript",
|
|
22
|
+
".js": "JavaScript",
|
|
23
|
+
".jsx": "JavaScript",
|
|
24
|
+
".py": "Python",
|
|
25
|
+
".go": "Go",
|
|
26
|
+
".rs": "Rust",
|
|
27
|
+
".java": "Java",
|
|
28
|
+
".cs": "C#",
|
|
29
|
+
".rb": "Ruby",
|
|
30
|
+
".php": "PHP",
|
|
31
|
+
".css": "CSS",
|
|
32
|
+
".scss": "CSS",
|
|
33
|
+
".html": "HTML",
|
|
34
|
+
".json": "JSON",
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const HOURS_PER_MONTH = 168;
|
|
38
|
+
const DEFAULT_HOURLY_RATE = 80;
|
|
39
|
+
|
|
40
|
+
// ── Helpers ────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
function readConfig(dir) {
|
|
43
|
+
const cfgPath = path.join(dir, ".cc-starter.json");
|
|
44
|
+
try {
|
|
45
|
+
const raw = fs.readFileSync(cfgPath, "utf-8");
|
|
46
|
+
return JSON.parse(raw);
|
|
47
|
+
} catch {
|
|
48
|
+
return {};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function countLines(filePath) {
|
|
53
|
+
const buf = fs.readFileSync(filePath);
|
|
54
|
+
if (buf.length === 0) return 0;
|
|
55
|
+
let count = 1;
|
|
56
|
+
for (let i = 0; i < buf.length; i++) {
|
|
57
|
+
if (buf[i] === 0x0a) count++;
|
|
58
|
+
}
|
|
59
|
+
// If the file ends with a newline, don't count the trailing empty line
|
|
60
|
+
if (buf[buf.length - 1] === 0x0a) count--;
|
|
61
|
+
return count;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function walkDir(dir, counts) {
|
|
65
|
+
let entries;
|
|
66
|
+
try {
|
|
67
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
68
|
+
} catch {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
for (const entry of entries) {
|
|
73
|
+
const name = entry.name;
|
|
74
|
+
|
|
75
|
+
if (entry.isDirectory()) {
|
|
76
|
+
if (!SKIP_DIRS.has(name)) {
|
|
77
|
+
walkDir(path.join(dir, name), counts);
|
|
78
|
+
}
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!entry.isFile()) continue;
|
|
83
|
+
|
|
84
|
+
// Skip minified files
|
|
85
|
+
if (name.endsWith(".min.js") || name.endsWith(".min.css")) continue;
|
|
86
|
+
if (SKIP_FILES.has(name)) continue;
|
|
87
|
+
|
|
88
|
+
const ext = path.extname(name).toLowerCase();
|
|
89
|
+
const lang = EXT_MAP[ext];
|
|
90
|
+
if (!lang) continue;
|
|
91
|
+
|
|
92
|
+
const fullPath = path.join(dir, name);
|
|
93
|
+
const lines = countLines(fullPath);
|
|
94
|
+
|
|
95
|
+
// JSON: skip large generated files (package-lock etc.)
|
|
96
|
+
if (lang === "JSON" && lines >= 1000) continue;
|
|
97
|
+
|
|
98
|
+
counts[lang] = (counts[lang] || 0) + lines;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function fmt(n) {
|
|
103
|
+
return n.toLocaleString("en-US");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ── COCOMO-II Semi-Detached ────────────────────────────────────
|
|
107
|
+
|
|
108
|
+
function cocomo(totalLines) {
|
|
109
|
+
const kloc = totalLines / 1000;
|
|
110
|
+
const effort = 3.0 * Math.pow(kloc, 1.12); // Person-Months
|
|
111
|
+
const schedule = 2.5 * Math.pow(effort, 0.35); // Months
|
|
112
|
+
const teamSize = effort / schedule; // Developers
|
|
113
|
+
return { kloc, effort, schedule, teamSize };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ── Main ───────────────────────────────────────────────────────
|
|
117
|
+
|
|
118
|
+
function main() {
|
|
119
|
+
const root = process.cwd();
|
|
120
|
+
const config = readConfig(root);
|
|
121
|
+
const hourlyRate = config.hourlyRate || DEFAULT_HOURLY_RATE;
|
|
122
|
+
|
|
123
|
+
// Count LOC
|
|
124
|
+
const counts = {};
|
|
125
|
+
walkDir(root, counts);
|
|
126
|
+
|
|
127
|
+
const languages = Object.entries(counts)
|
|
128
|
+
.sort((a, b) => b[1] - a[1]);
|
|
129
|
+
|
|
130
|
+
const totalLines = languages.reduce((s, [, c]) => s + c, 0);
|
|
131
|
+
|
|
132
|
+
if (totalLines === 0) {
|
|
133
|
+
console.log("\n No source files found in the current directory.\n");
|
|
134
|
+
process.exit(0);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const est = cocomo(totalLines);
|
|
138
|
+
const cost = est.effort * HOURS_PER_MONTH * hourlyRate;
|
|
139
|
+
|
|
140
|
+
// Find longest language name for alignment
|
|
141
|
+
const maxLangLen = Math.max(...languages.map(([l]) => l.length), 5);
|
|
142
|
+
const maxNumLen = Math.max(...languages.map(([, c]) => fmt(c).length), fmt(totalLines).length);
|
|
143
|
+
|
|
144
|
+
// ── Output ─────────────────────────────────────────────────
|
|
145
|
+
|
|
146
|
+
console.log();
|
|
147
|
+
console.log(" COCOMO-II Project Estimation");
|
|
148
|
+
console.log(" " + "\u2550".repeat(32));
|
|
149
|
+
console.log();
|
|
150
|
+
console.log(" Lines of Code:");
|
|
151
|
+
|
|
152
|
+
for (const [lang, count] of languages) {
|
|
153
|
+
const label = lang.padEnd(maxLangLen);
|
|
154
|
+
const num = fmt(count).padStart(maxNumLen);
|
|
155
|
+
console.log(` ${label} ${num}`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
console.log(" " + "\u2500".repeat(maxLangLen + maxNumLen + 2));
|
|
159
|
+
|
|
160
|
+
const totalLabel = "Total".padEnd(maxLangLen);
|
|
161
|
+
const totalNum = fmt(totalLines).padStart(maxNumLen);
|
|
162
|
+
console.log(` ${totalLabel} ${totalNum}`);
|
|
163
|
+
|
|
164
|
+
console.log();
|
|
165
|
+
console.log(" Estimation (Semi-Detached):");
|
|
166
|
+
console.log(` Effort: ${est.effort.toFixed(1).padStart(8)} Person-Months`);
|
|
167
|
+
console.log(` Schedule: ${est.schedule.toFixed(1).padStart(8)} Months`);
|
|
168
|
+
console.log(` Team Size: ${est.teamSize.toFixed(1).padStart(8)} Developers`);
|
|
169
|
+
const costStr = "\u20AC" + fmt(Math.round(cost));
|
|
170
|
+
console.log(` Cost: ${costStr.padStart(8)} (at \u20AC${fmt(hourlyRate)}/h)`);
|
|
171
|
+
|
|
172
|
+
console.log();
|
|
173
|
+
console.log(" Note: COCOMO-II provides rough order-of-magnitude estimates.");
|
|
174
|
+
console.log(` Based on ${fmt(HOURS_PER_MONTH)}h/month at configured rate of \u20AC${fmt(hourlyRate)}/h.`);
|
|
175
|
+
console.log();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
main();
|