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.
@@ -0,0 +1,261 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import Handlebars from 'handlebars';
5
+ import inquirer from 'inquirer';
6
+ import chalk from 'chalk';
7
+
8
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
+ const TEMPLATE_DIR = path.join(__dirname, '..', 'template');
10
+
11
+ /**
12
+ * Scaffold a cc-starter project into the given directory.
13
+ * @param {object} config - Wizard output
14
+ * @param {string} [cwd=process.cwd()] - Target directory
15
+ * @returns {Promise<{ filesCreated: number, filesSkipped: number }>}
16
+ */
17
+ export async function scaffold(config, cwd = process.cwd()) {
18
+ let filesCreated = 0;
19
+ let filesSkipped = 0;
20
+
21
+ // ── 1. .claude/ directory structure ────────────────────────────────
22
+ const claudeDir = path.join(cwd, '.claude');
23
+ const claudeExists = fs.existsSync(claudeDir);
24
+
25
+ let claudeAction = 'overwrite';
26
+ if (claudeExists) {
27
+ const answer = await inquirer.prompt([{
28
+ type: 'list',
29
+ name: 'action',
30
+ message: 'Existing .claude/ found. What should we do?',
31
+ choices: [
32
+ { name: 'Overwrite — replace all files', value: 'overwrite' },
33
+ { name: 'Merge — keep existing, add missing files only', value: 'merge' },
34
+ { name: 'Skip — leave .claude/ untouched', value: 'skip' }
35
+ ]
36
+ }]);
37
+ claudeAction = answer.action;
38
+ }
39
+
40
+ if (claudeAction === 'skip') {
41
+ console.log(chalk.yellow(' ⊘ .claude/') + chalk.dim(' skipped'));
42
+ filesSkipped += countFiles(path.join(TEMPLATE_DIR, 'claude'));
43
+ } else {
44
+ const result = copyClaudeDir(claudeAction, cwd);
45
+ filesCreated += result.created;
46
+ filesSkipped += result.skipped;
47
+ }
48
+
49
+ // ── 2. Render CLAUDE.md from Handlebars template ──────────────────
50
+ const claudeMdPath = path.join(cwd, 'CLAUDE.md');
51
+ const claudeMdExists = fs.existsSync(claudeMdPath);
52
+
53
+ let claudeMdAction = 'overwrite';
54
+ if (claudeMdExists) {
55
+ const answer = await inquirer.prompt([{
56
+ type: 'list',
57
+ name: 'action',
58
+ message: 'Existing CLAUDE.md found. What should we do?',
59
+ choices: [
60
+ { name: 'Overwrite — replace with cc-starter version', value: 'overwrite' },
61
+ { name: 'Append — add cc-starter section at the end', value: 'append' },
62
+ { name: 'Skip — leave CLAUDE.md untouched', value: 'skip' }
63
+ ]
64
+ }]);
65
+ claudeMdAction = answer.action;
66
+ }
67
+
68
+ if (claudeMdAction === 'skip') {
69
+ console.log(chalk.yellow(' ⊘ CLAUDE.md') + chalk.dim(' skipped'));
70
+ filesSkipped += 1;
71
+ } else {
72
+ renderClaudeMd(config, claudeMdAction, cwd);
73
+ filesCreated += 1;
74
+ console.log(chalk.green(' ✓ CLAUDE.md') + chalk.dim(` ${claudeMdAction === 'append' ? 'appended' : 'created'}`));
75
+ }
76
+
77
+ // ── 3. Copy scripts/stats/ (always overwrite) ─────────────────────
78
+ const scriptsResult = copyScriptsStats(cwd);
79
+ filesCreated += scriptsResult.created;
80
+ console.log(chalk.green(' ✓ scripts/stats/') + chalk.dim(` ${scriptsResult.created} files`));
81
+
82
+ // ── 4. Create .cc-starter.json (always overwrite) ──────────────────
83
+ const ccConfig = {
84
+ projectName: config.projectName,
85
+ hourlyRate: config.hourlyRate,
86
+ reportStyle: config.reportStyle,
87
+ createdAt: new Date().toISOString(),
88
+ version: '1.0.0'
89
+ };
90
+ fs.writeJsonSync(path.join(cwd, '.cc-starter.json'), ccConfig, { spaces: 2 });
91
+ filesCreated += 1;
92
+ console.log(chalk.green(' ✓ .cc-starter.json') + chalk.dim(' created'));
93
+
94
+ // ── 5. Update .gitignore ───────────────────────────────────────────
95
+ const gitignoreUpdated = updateGitignore(cwd);
96
+ if (gitignoreUpdated) {
97
+ filesCreated += 1;
98
+ console.log(chalk.green(' ✓ .gitignore') + chalk.dim(' updated'));
99
+ } else {
100
+ console.log(chalk.dim(' - .gitignore already up to date'));
101
+ }
102
+
103
+ console.log();
104
+ console.log(chalk.bold.green(` Done!`) + chalk.dim(` ${filesCreated} created, ${filesSkipped} skipped`));
105
+
106
+ return { filesCreated, filesSkipped };
107
+ }
108
+
109
+ // ── Helpers ────────────────────────────────────────────────────────────
110
+
111
+ /**
112
+ * Copy the template/claude/ tree into cwd/.claude/.
113
+ * In 'merge' mode, only copies files that don't already exist.
114
+ */
115
+ function copyClaudeDir(action, cwd) {
116
+ const src = path.join(TEMPLATE_DIR, 'claude');
117
+ const dest = path.join(cwd, '.claude');
118
+ let created = 0;
119
+ let skipped = 0;
120
+
121
+ const subdirs = [
122
+ { dir: 'rules', files: ['01-general.md', '02-code-standards.md', '03-dev-ops.md'] },
123
+ { dir: 'memory', files: ['MEMORY.md'] },
124
+ { dir: 'project', files: ['README.md'] },
125
+ { dir: 'reference', files: ['README.md'] },
126
+ { dir: 'commands', files: ['kickstart.md'] },
127
+ ];
128
+
129
+ // Also copy settings.json at the root of .claude/
130
+ const rootFiles = ['settings.json'];
131
+
132
+ for (const { dir, files } of subdirs) {
133
+ const destDir = path.join(dest, dir);
134
+ fs.ensureDirSync(destDir);
135
+ let dirCreated = 0;
136
+
137
+ for (const file of files) {
138
+ const srcFile = path.join(src, dir, file);
139
+ const destFile = path.join(destDir, file);
140
+
141
+ if (action === 'merge' && fs.existsSync(destFile)) {
142
+ skipped += 1;
143
+ continue;
144
+ }
145
+
146
+ fs.copySync(srcFile, destFile);
147
+ dirCreated += 1;
148
+ created += 1;
149
+ }
150
+
151
+ if (dirCreated > 0) {
152
+ console.log(chalk.green(` ✓ .claude/${dir}/`) + chalk.dim(` ${dirCreated} file${dirCreated !== 1 ? 's' : ''}`));
153
+ } else {
154
+ console.log(chalk.yellow(` ⊘ .claude/${dir}/`) + chalk.dim(' skipped (exists)'));
155
+ }
156
+ }
157
+
158
+ for (const file of rootFiles) {
159
+ const srcFile = path.join(src, file);
160
+ const destFile = path.join(dest, file);
161
+
162
+ if (action === 'merge' && fs.existsSync(destFile)) {
163
+ skipped += 1;
164
+ console.log(chalk.yellow(` ⊘ .claude/${file}`) + chalk.dim(' skipped (exists)'));
165
+ continue;
166
+ }
167
+
168
+ fs.copySync(srcFile, destFile);
169
+ created += 1;
170
+ console.log(chalk.green(` ✓ .claude/${file}`) + chalk.dim(' created'));
171
+ }
172
+
173
+ return { created, skipped };
174
+ }
175
+
176
+ /**
177
+ * Render CLAUDE.md from Handlebars template.
178
+ */
179
+ function renderClaudeMd(config, action, cwd) {
180
+ const templateSrc = fs.readFileSync(path.join(TEMPLATE_DIR, 'CLAUDE.md.hbs'), 'utf-8');
181
+ const template = Handlebars.compile(templateSrc);
182
+
183
+ const techStack = config.techStack || { languages: [], frameworks: [] };
184
+ const rendered = template({
185
+ projectName: config.projectName,
186
+ techStack
187
+ });
188
+
189
+ const destPath = path.join(cwd, 'CLAUDE.md');
190
+
191
+ if (action === 'append') {
192
+ const existing = fs.readFileSync(destPath, 'utf-8');
193
+ const separator = '\n\n---\n\n<!-- cc-starter section -->\n';
194
+ fs.writeFileSync(destPath, existing + separator + rendered, 'utf-8');
195
+ } else {
196
+ fs.writeFileSync(destPath, rendered, 'utf-8');
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Copy scripts/stats/ files (always overwrite).
202
+ */
203
+ function copyScriptsStats(cwd) {
204
+ const src = path.join(TEMPLATE_DIR, 'scripts', 'stats');
205
+ const dest = path.join(cwd, 'scripts', 'stats');
206
+ fs.ensureDirSync(dest);
207
+
208
+ const files = fs.readdirSync(src).filter(f => f.endsWith('.js'));
209
+ for (const file of files) {
210
+ fs.copySync(path.join(src, file), path.join(dest, file));
211
+ }
212
+
213
+ return { created: files.length };
214
+ }
215
+
216
+ /**
217
+ * Append cc-starter entries to .gitignore if not already present.
218
+ * @returns {boolean} true if .gitignore was modified
219
+ */
220
+ function updateGitignore(cwd) {
221
+ const gitignorePath = path.join(cwd, '.gitignore');
222
+ let content = '';
223
+
224
+ if (fs.existsSync(gitignorePath)) {
225
+ content = fs.readFileSync(gitignorePath, 'utf-8');
226
+ }
227
+
228
+ const marker = '# cc-starter';
229
+ if (content.includes(marker)) {
230
+ return false;
231
+ }
232
+
233
+ const block = [
234
+ '',
235
+ '# cc-starter',
236
+ '.vibe-stats.json',
237
+ 'stats/report.html',
238
+ ''
239
+ ].join('\n');
240
+
241
+ // Ensure we start on a new line
242
+ const prefix = content.length > 0 && !content.endsWith('\n') ? '\n' : '';
243
+ fs.writeFileSync(gitignorePath, content + prefix + block, 'utf-8');
244
+ return true;
245
+ }
246
+
247
+ /**
248
+ * Count files recursively in a directory.
249
+ */
250
+ function countFiles(dir) {
251
+ if (!fs.existsSync(dir)) return 0;
252
+ let count = 0;
253
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
254
+ if (entry.isDirectory()) {
255
+ count += countFiles(path.join(dir, entry.name));
256
+ } else {
257
+ count += 1;
258
+ }
259
+ }
260
+ return count;
261
+ }
package/lib/wizard.js ADDED
@@ -0,0 +1,99 @@
1
+ import inquirer from 'inquirer';
2
+ import chalk from 'chalk';
3
+ import { readFileSync } from 'fs';
4
+ import { basename, resolve } from 'path';
5
+ import {
6
+ PLUGIN_PRESETS,
7
+ ALL_PLUGINS,
8
+ REPORT_STYLES,
9
+ DEFAULT_HOURLY_RATE
10
+ } from './constants.js';
11
+
12
+ /**
13
+ * Detect project name from package.json or fall back to folder name.
14
+ */
15
+ function detectProjectName() {
16
+ try {
17
+ const pkg = JSON.parse(readFileSync(resolve('package.json'), 'utf-8'));
18
+ if (pkg.name) return pkg.name;
19
+ } catch {
20
+ // no package.json — fall through
21
+ }
22
+ return basename(resolve('.'));
23
+ }
24
+
25
+ /**
26
+ * Interactive setup wizard.
27
+ * @param {string[]} techStack - Auto-detected technologies (e.g. ['Node.js', 'TypeScript', 'React'])
28
+ * @returns {Promise<{projectName: string, hourlyRate: number, reportStyle: string, plugins: object[], techStack: string[]}>}
29
+ */
30
+ export async function wizard(techStack = []) {
31
+ console.log();
32
+ console.log(chalk.bold.cyan(' cc-starter') + chalk.dim(' — project kickstart wizard'));
33
+ console.log();
34
+
35
+ if (techStack.length > 0) {
36
+ console.log(chalk.dim(' Detected tech stack: ') + chalk.yellow(techStack.join(', ')));
37
+ console.log();
38
+ }
39
+
40
+ const defaultName = detectProjectName();
41
+
42
+ const answers = await inquirer.prompt([
43
+ {
44
+ type: 'input',
45
+ name: 'projectName',
46
+ message: 'Project name:',
47
+ default: defaultName
48
+ },
49
+ {
50
+ type: 'number',
51
+ name: 'hourlyRate',
52
+ message: 'Hourly rate for COCOMO estimation (\u20ac):',
53
+ default: DEFAULT_HOURLY_RATE
54
+ },
55
+ {
56
+ type: 'list',
57
+ name: 'reportStyle',
58
+ message: 'Report style:',
59
+ choices: Object.entries(REPORT_STYLES).map(([key, val]) => ({
60
+ name: val.label,
61
+ value: key
62
+ }))
63
+ },
64
+ {
65
+ type: 'list',
66
+ name: 'pluginPreset',
67
+ message: 'Plugin preset:',
68
+ choices: [
69
+ { name: `Minimal — ${PLUGIN_PRESETS.minimal.length} plugin`, value: 'minimal' },
70
+ { name: `Standard — ${PLUGIN_PRESETS.standard.length} plugins`, value: 'standard' },
71
+ { name: `Full — ${PLUGIN_PRESETS.full.length} plugins`, value: 'full' },
72
+ { name: 'Custom — pick individual plugins', value: 'custom' }
73
+ ]
74
+ },
75
+ {
76
+ type: 'checkbox',
77
+ name: 'customPlugins',
78
+ message: 'Select plugins:',
79
+ when: (ans) => ans.pluginPreset === 'custom',
80
+ choices: ALL_PLUGINS.map((p) => ({
81
+ name: `${p.name} ${chalk.dim('— ' + p.desc)}`,
82
+ value: p,
83
+ checked: false
84
+ }))
85
+ }
86
+ ]);
87
+
88
+ const plugins = answers.pluginPreset === 'custom'
89
+ ? answers.customPlugins || []
90
+ : PLUGIN_PRESETS[answers.pluginPreset];
91
+
92
+ return {
93
+ projectName: answers.projectName,
94
+ hourlyRate: answers.hourlyRate,
95
+ reportStyle: answers.reportStyle,
96
+ plugins,
97
+ techStack
98
+ };
99
+ }
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "cc-starter",
3
+ "version": "1.0.0",
4
+ "description": "Claude Code Project Kickstart — scaffolds an optimized dev environment with token-saving scripts, COCOMO estimation, and interactive plugin setup",
5
+ "type": "module",
6
+ "bin": {
7
+ "cc-starter": "bin/cc-starter.js"
8
+ },
9
+ "keywords": [
10
+ "claude-code",
11
+ "cli",
12
+ "scaffold",
13
+ "starter-kit",
14
+ "vibe-coding",
15
+ "token-savings",
16
+ "cocomo",
17
+ "developer-tools"
18
+ ],
19
+ "author": "Lars Fanter",
20
+ "license": "MIT",
21
+ "dependencies": {
22
+ "inquirer": "^9.2.0",
23
+ "chalk": "^5.3.0",
24
+ "fs-extra": "^11.2.0",
25
+ "handlebars": "^4.7.8"
26
+ },
27
+ "engines": {
28
+ "node": ">=18.0.0"
29
+ }
30
+ }
@@ -0,0 +1,44 @@
1
+ # CLAUDE.md - {{projectName}}
2
+
3
+ ## Project Overview
4
+ **Project:** {{projectName}}
5
+ {{#if techStack.languages.length}}
6
+ **Languages:** {{#each techStack.languages}}{{this}}{{#unless @last}}, {{/unless}}{{/each}}
7
+ {{/if}}
8
+ {{#if techStack.frameworks.length}}
9
+ **Frameworks:** {{#each techStack.frameworks}}{{this}}{{#unless @last}}, {{/unless}}{{/each}}
10
+ {{/if}}
11
+
12
+ ---
13
+
14
+ ## Quick Reference
15
+
16
+ ### Token-Saving Scripts
17
+ ```bash
18
+ node scripts/stats/vibe-code.js help # Code analysis (save ~90% tokens)
19
+ node scripts/stats/vibe-code.js types <f> # Extract types/interfaces
20
+ node scripts/stats/vibe-code.js tree [dir] # Clean directory tree
21
+ node scripts/stats/vibe-code.js imports <f> # Show imports
22
+ node scripts/stats/vibe-code.js funcs <f> # Function signatures
23
+ ```
24
+
25
+ ### Project Statistics
26
+ ```bash
27
+ node scripts/stats/cocomo.js # Project cost estimation
28
+ node scripts/stats/project-report.js # HTML statistics report
29
+ node scripts/stats/vibe-stats.js report # Token savings report
30
+ ```
31
+
32
+ ### Rules
33
+ See `.claude/rules/` for working rules:
34
+ - `01-general.md` — Task tracking, planning, verification
35
+ - `02-code-standards.md` — Code quality, security, testing
36
+ - `03-dev-ops.md` — Git, environment, deployment
37
+
38
+ ### Memory System
39
+ Persistent memory across sessions in `.claude/memory/`.
40
+ Index: `.claude/memory/MEMORY.md`
41
+
42
+ ---
43
+
44
+ *Generated by [cc-starter](https://www.npmjs.com/package/cc-starter)*
@@ -0,0 +1,16 @@
1
+ ---
2
+ name: kickstart
3
+ description: Re-run cc-starter setup or upgrade existing configuration
4
+ ---
5
+
6
+ To update your cc-starter configuration, run in your terminal:
7
+
8
+ ```bash
9
+ npx cc-starter
10
+ ```
11
+
12
+ This will:
13
+ - Update rules to the latest version
14
+ - Add any missing scripts
15
+ - Install new plugins
16
+ - Regenerate CLAUDE.md if desired
@@ -0,0 +1,11 @@
1
+ # Project Memory
2
+
3
+ This is the memory index for this project. Claude Code uses this to persist knowledge across sessions.
4
+
5
+ ## How It Works
6
+ - Each memory is stored as a separate .md file in this directory
7
+ - This file (MEMORY.md) is the index — keep it concise
8
+ - Memory types: user, feedback, project, reference
9
+
10
+ ## Memories
11
+ <!-- Add memory entries below as they are created -->
@@ -0,0 +1,7 @@
1
+ # Project Context
2
+
3
+ Add project-specific information here that Claude should know about:
4
+ - Architecture decisions
5
+ - Team conventions
6
+ - External service details
7
+ - Database schema notes
@@ -0,0 +1,7 @@
1
+ # Reference Documentation
2
+
3
+ Add pointers to reference material here:
4
+ - API documentation links
5
+ - Design system guides
6
+ - Architecture diagrams
7
+ - External tool documentation
@@ -0,0 +1,36 @@
1
+ # General Working Rules
2
+
3
+ ## 1. Task Tracking
4
+ For tasks with **3+ steps** ALWAYS:
5
+ - Use task tracking to manage work items
6
+ - Create each step as a task BEFORE starting
7
+ - Mark each task as completed immediately when done
8
+
9
+ ## 2. Keep Documentation Modular
10
+ - Don't create sprawling status files
11
+ - Updates belong in the matching doc (feature docs, architecture docs)
12
+ - Keep a clean separation of concerns
13
+
14
+ ## 3. Before Coding
15
+ - When unclear: Ask first, then implement
16
+ - When multiple approaches exist: Present options and wait for decision
17
+
18
+ ## 4. When Things Go Wrong: STOP & Re-plan
19
+ - If an approach isn't working: **Stop immediately**, don't keep pushing
20
+ - Re-plan instead of repeating the same mistake
21
+ - Find root causes — no temporary workarounds or hacks
22
+ - Ask yourself: "Is this the elegant solution or a quick-fix?"
23
+
24
+ ## 5. Verification: Diff Against Main Before "Done"
25
+ - Before claiming "done": run `git diff main` to check all changes are intentional
26
+ - Ask yourself: "Would a staff engineer approve this?"
27
+ - Build must be green
28
+ - No unintended changes in unrelated files
29
+
30
+ ## 6. Design Documentation
31
+ When making design decisions, document the process:
32
+ - Summary of final decisions
33
+ - Questions asked with options and chosen answers
34
+ - Reasoning for each choice
35
+ - Technical details
36
+ - Open points
@@ -0,0 +1,23 @@
1
+ # Code Standards
2
+
3
+ ## Security First
4
+ - Never commit secrets, API keys, or credentials
5
+ - Validate all external input
6
+ - Use parameterized queries for databases
7
+ - Follow OWASP Top 10 guidelines
8
+
9
+ ## Code Quality
10
+ - Write self-documenting code with clear naming
11
+ - Keep functions focused (single responsibility)
12
+ - Prefer composition over inheritance
13
+ - Handle errors explicitly — no silent catches
14
+
15
+ ## Testing
16
+ - Write tests for new features and bug fixes
17
+ - Test edge cases and error paths
18
+ - Don't mock what you don't own — use integration tests where possible
19
+
20
+ ## Internationalization (if applicable)
21
+ - No hardcoded user-facing strings
22
+ - Use your project's i18n solution consistently
23
+ - Keep translation files in sync across locales
@@ -0,0 +1,20 @@
1
+ # DevOps & Deployment Rules
2
+
3
+ ## Git Discipline
4
+ - Separate concerns in commits (app code vs. infrastructure vs. docs)
5
+ - Write descriptive commit messages explaining WHY, not just WHAT
6
+ - Never force-push to main/master without team agreement
7
+
8
+ ## Environment Variables
9
+ - New env vars must be documented
10
+ - Never commit .env files with real values
11
+ - Provide .env.example with placeholder values
12
+
13
+ ## Port Management
14
+ - Stick to one dev server port — don't spawn multiple
15
+ - Kill orphan processes before starting new dev servers
16
+
17
+ ## Build Verification
18
+ - Always run build/lint before committing
19
+ - Fix warnings, don't suppress them
20
+ - Keep CI/CD pipeline green
@@ -0,0 +1,4 @@
1
+ {
2
+ "enabledPlugins": {},
3
+ "alwaysThinkingEnabled": true
4
+ }