forgedev 1.0.0 → 1.0.2
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/CLAUDE.md +3 -3
- package/README.md +246 -246
- package/bin/devforge.js +4 -4
- package/package.json +33 -33
- package/src/claude-configurator.js +260 -260
- package/src/cli.js +119 -119
- package/src/composer.js +214 -214
- package/src/doctor-checks.js +743 -743
- package/src/doctor-prompts.js +295 -295
- package/src/doctor.js +281 -281
- package/src/guided.js +315 -315
- package/src/index.js +148 -148
- package/src/init-mode.js +138 -134
- package/src/prompts.js +155 -155
- package/src/scanner.js +368 -368
- package/templates/claude-code/agents/code-quality-reviewer.md +41 -41
- package/templates/claude-code/agents/production-readiness.md +55 -55
- package/templates/claude-code/agents/security-reviewer.md +41 -41
- package/templates/claude-code/agents/spec-validator.md +34 -34
- package/templates/claude-code/agents/uat-validator.md +37 -37
- package/templates/claude-code/claude-md/base.md +33 -33
- package/templates/claude-code/commands/done.md +19 -19
- package/templates/claude-code/commands/generate-prd.md +45 -45
- package/templates/claude-code/commands/generate-uat.md +35 -35
- package/templates/claude-code/commands/next.md +20 -20
- package/templates/claude-code/commands/optimize-claude-md.md +31 -31
- package/templates/claude-code/commands/status.md +24 -24
- package/templates/claude-code/commands/workflows.md +26 -0
- package/templates/claude-code/hooks/polyglot.json +36 -36
- package/templates/claude-code/hooks/python.json +36 -36
- package/templates/claude-code/hooks/scripts/autofix-polyglot.sh +16 -16
- package/templates/claude-code/hooks/scripts/autofix-python.sh +14 -14
- package/templates/claude-code/hooks/scripts/autofix-typescript.sh +14 -14
- package/templates/claude-code/hooks/scripts/guard-protected-files.sh +21 -21
- package/templates/claude-code/hooks/typescript.json +36 -36
- package/templates/claude-code/commands/help.md +0 -26
package/src/index.js
CHANGED
|
@@ -1,148 +1,148 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
import fs from 'node:fs';
|
|
3
|
-
import { execSync } from 'node:child_process';
|
|
4
|
-
import chalk from 'chalk';
|
|
5
|
-
import { log, toKebabCase } from './utils.js';
|
|
6
|
-
import { askServiceType, askRefinements, askNewMode, confirmStack } from './prompts.js';
|
|
7
|
-
import { recommend } from './recommender.js';
|
|
8
|
-
import { compose } from './composer.js';
|
|
9
|
-
import { generateClaudeConfig } from './claude-configurator.js';
|
|
10
|
-
import { generateUAT } from './uat-generator.js';
|
|
11
|
-
|
|
12
|
-
export async function runNew(projectName) {
|
|
13
|
-
const safeName = toKebabCase(projectName);
|
|
14
|
-
const outputDir = path.resolve(process.cwd(), safeName);
|
|
15
|
-
|
|
16
|
-
if (fs.existsSync(outputDir)) {
|
|
17
|
-
log.error(`Directory "${safeName}" already exists. Choose a different name or delete it first.`);
|
|
18
|
-
process.exit(1);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
console.log('');
|
|
22
|
-
console.log(chalk.bold.cyan(' 🔨 DevForge') + chalk.dim(' — Let\'s build something.'));
|
|
23
|
-
console.log('');
|
|
24
|
-
|
|
25
|
-
const mode = await askNewMode();
|
|
26
|
-
|
|
27
|
-
if (mode === 'guided') {
|
|
28
|
-
const { runGuidedFlow } = await import('./guided.js');
|
|
29
|
-
await runGuidedFlow(safeName, outputDir, scaffold);
|
|
30
|
-
} else {
|
|
31
|
-
await runDeveloperFlow(safeName, outputDir);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async function runDeveloperFlow(safeName, outputDir) {
|
|
36
|
-
const serviceType = await askServiceType();
|
|
37
|
-
const refinements = await askRefinements(serviceType);
|
|
38
|
-
const stackConfig = recommend(serviceType, refinements);
|
|
39
|
-
|
|
40
|
-
if (stackConfig.supported === false) {
|
|
41
|
-
log.warn(stackConfig.message);
|
|
42
|
-
process.exit(0);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
stackConfig.projectName = safeName;
|
|
46
|
-
|
|
47
|
-
const confirmed = await confirmStack(stackConfig);
|
|
48
|
-
if (!confirmed) {
|
|
49
|
-
log.info('Cancelled.');
|
|
50
|
-
process.exit(0);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
await scaffold(outputDir, stackConfig);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export async function scaffold(outputDir, stackConfig) {
|
|
57
|
-
console.log('');
|
|
58
|
-
const totalSteps = stackConfig.claudeCode ? 4 : 3;
|
|
59
|
-
let step = 0;
|
|
60
|
-
|
|
61
|
-
step++;
|
|
62
|
-
log.step(step, totalSteps, 'Scaffolding project structure...');
|
|
63
|
-
await compose(outputDir, stackConfig);
|
|
64
|
-
|
|
65
|
-
if (stackConfig.claudeCode) {
|
|
66
|
-
step++;
|
|
67
|
-
log.step(step, totalSteps, 'Generating Claude Code infrastructure...');
|
|
68
|
-
await generateClaudeConfig(outputDir, stackConfig);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
step++;
|
|
72
|
-
log.step(step, totalSteps, 'Generating UAT templates...');
|
|
73
|
-
await generateUAT(outputDir, stackConfig);
|
|
74
|
-
|
|
75
|
-
step++;
|
|
76
|
-
log.step(step, totalSteps, 'Initializing git repository...');
|
|
77
|
-
initGit(outputDir);
|
|
78
|
-
|
|
79
|
-
console.log('');
|
|
80
|
-
log.success('Done! Your project is ready.');
|
|
81
|
-
console.log('');
|
|
82
|
-
printNextSteps(stackConfig.projectName, stackConfig);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function initGit(outputDir) {
|
|
86
|
-
try {
|
|
87
|
-
execSync('git init', { cwd: outputDir, stdio: 'ignore' });
|
|
88
|
-
} catch {
|
|
89
|
-
// git not available — skip silently
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export function printNextSteps(projectName, config, isGuided = false) {
|
|
94
|
-
if (isGuided) {
|
|
95
|
-
console.log(chalk.bold(' To start building:'));
|
|
96
|
-
console.log(` cd ${projectName}`);
|
|
97
|
-
console.log(' npm install');
|
|
98
|
-
console.log(' npm run dev');
|
|
99
|
-
console.log('');
|
|
100
|
-
console.log(` Then open your browser to ${chalk.cyan('http://localhost:3000')}`);
|
|
101
|
-
console.log('');
|
|
102
|
-
console.log(chalk.bold(' 📖 New to coding? Here\'s what to do next:'));
|
|
103
|
-
console.log(' 1. Open this folder in VS Code (or any code editor)');
|
|
104
|
-
console.log(' 2. If you have Claude Code installed, type /
|
|
105
|
-
console.log(' — it will guide you step by step');
|
|
106
|
-
console.log(' 3. Or read docs/getting-started.md for a beginner-friendly walkthrough');
|
|
107
|
-
console.log('');
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
console.log(chalk.bold(' Next steps:'));
|
|
112
|
-
console.log(` cd ${projectName}`);
|
|
113
|
-
|
|
114
|
-
if (config.stackId === 'nextjs-fullstack') {
|
|
115
|
-
console.log(' npm install');
|
|
116
|
-
console.log(' cp .env.example .env');
|
|
117
|
-
console.log(' npx prisma db push');
|
|
118
|
-
console.log(' npm run dev');
|
|
119
|
-
} else if (config.stackId === 'fastapi-backend') {
|
|
120
|
-
console.log(' cd backend');
|
|
121
|
-
console.log(' python -m venv venv');
|
|
122
|
-
console.log(' source venv/bin/activate # or venv\\Scripts\\activate on Windows');
|
|
123
|
-
console.log(' pip install -r requirements.txt');
|
|
124
|
-
console.log(' cp .env.example .env');
|
|
125
|
-
console.log(' uvicorn app.main:app --reload');
|
|
126
|
-
} else if (config.stackId === 'polyglot-fullstack') {
|
|
127
|
-
console.log(' docker compose up -d postgres');
|
|
128
|
-
console.log(' # Frontend:');
|
|
129
|
-
console.log(' cd frontend && npm install && npm run dev');
|
|
130
|
-
console.log(' # Backend:');
|
|
131
|
-
console.log(' cd backend && pip install -r requirements.txt && uvicorn app.main:app --reload');
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if (config.claudeCode) {
|
|
135
|
-
console.log('');
|
|
136
|
-
console.log(chalk.bold(' Claude Code:'));
|
|
137
|
-
console.log(' Your project includes pre-configured Claude Code infrastructure:');
|
|
138
|
-
console.log(chalk.dim(' - CLAUDE.md (project context + rules)'));
|
|
139
|
-
console.log(chalk.dim(' - .claude/hooks/ (auto-lint, quality gates)'));
|
|
140
|
-
console.log(chalk.dim(' - .claude/skills/ (framework knowledge)'));
|
|
141
|
-
console.log(chalk.dim(' - .claude/agents/ (verification chain)'));
|
|
142
|
-
console.log(chalk.dim(' - .claude/commands/ (audit, verify, pre-pr)'));
|
|
143
|
-
console.log(chalk.dim(' - docs/prompt-library.md'));
|
|
144
|
-
console.log(chalk.dim(' - docs/uat/ (acceptance testing)'));
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
console.log('');
|
|
148
|
-
}
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import { execSync } from 'node:child_process';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { log, toKebabCase } from './utils.js';
|
|
6
|
+
import { askServiceType, askRefinements, askNewMode, confirmStack } from './prompts.js';
|
|
7
|
+
import { recommend } from './recommender.js';
|
|
8
|
+
import { compose } from './composer.js';
|
|
9
|
+
import { generateClaudeConfig } from './claude-configurator.js';
|
|
10
|
+
import { generateUAT } from './uat-generator.js';
|
|
11
|
+
|
|
12
|
+
export async function runNew(projectName) {
|
|
13
|
+
const safeName = toKebabCase(projectName);
|
|
14
|
+
const outputDir = path.resolve(process.cwd(), safeName);
|
|
15
|
+
|
|
16
|
+
if (fs.existsSync(outputDir)) {
|
|
17
|
+
log.error(`Directory "${safeName}" already exists. Choose a different name or delete it first.`);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
console.log('');
|
|
22
|
+
console.log(chalk.bold.cyan(' 🔨 DevForge') + chalk.dim(' — Let\'s build something.'));
|
|
23
|
+
console.log('');
|
|
24
|
+
|
|
25
|
+
const mode = await askNewMode();
|
|
26
|
+
|
|
27
|
+
if (mode === 'guided') {
|
|
28
|
+
const { runGuidedFlow } = await import('./guided.js');
|
|
29
|
+
await runGuidedFlow(safeName, outputDir, scaffold);
|
|
30
|
+
} else {
|
|
31
|
+
await runDeveloperFlow(safeName, outputDir);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function runDeveloperFlow(safeName, outputDir) {
|
|
36
|
+
const serviceType = await askServiceType();
|
|
37
|
+
const refinements = await askRefinements(serviceType);
|
|
38
|
+
const stackConfig = recommend(serviceType, refinements);
|
|
39
|
+
|
|
40
|
+
if (stackConfig.supported === false) {
|
|
41
|
+
log.warn(stackConfig.message);
|
|
42
|
+
process.exit(0);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
stackConfig.projectName = safeName;
|
|
46
|
+
|
|
47
|
+
const confirmed = await confirmStack(stackConfig);
|
|
48
|
+
if (!confirmed) {
|
|
49
|
+
log.info('Cancelled.');
|
|
50
|
+
process.exit(0);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
await scaffold(outputDir, stackConfig);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function scaffold(outputDir, stackConfig) {
|
|
57
|
+
console.log('');
|
|
58
|
+
const totalSteps = stackConfig.claudeCode ? 4 : 3;
|
|
59
|
+
let step = 0;
|
|
60
|
+
|
|
61
|
+
step++;
|
|
62
|
+
log.step(step, totalSteps, 'Scaffolding project structure...');
|
|
63
|
+
await compose(outputDir, stackConfig);
|
|
64
|
+
|
|
65
|
+
if (stackConfig.claudeCode) {
|
|
66
|
+
step++;
|
|
67
|
+
log.step(step, totalSteps, 'Generating Claude Code infrastructure...');
|
|
68
|
+
await generateClaudeConfig(outputDir, stackConfig);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
step++;
|
|
72
|
+
log.step(step, totalSteps, 'Generating UAT templates...');
|
|
73
|
+
await generateUAT(outputDir, stackConfig);
|
|
74
|
+
|
|
75
|
+
step++;
|
|
76
|
+
log.step(step, totalSteps, 'Initializing git repository...');
|
|
77
|
+
initGit(outputDir);
|
|
78
|
+
|
|
79
|
+
console.log('');
|
|
80
|
+
log.success('Done! Your project is ready.');
|
|
81
|
+
console.log('');
|
|
82
|
+
printNextSteps(stackConfig.projectName, stackConfig);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function initGit(outputDir) {
|
|
86
|
+
try {
|
|
87
|
+
execSync('git init', { cwd: outputDir, stdio: 'ignore' });
|
|
88
|
+
} catch {
|
|
89
|
+
// git not available — skip silently
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function printNextSteps(projectName, config, isGuided = false) {
|
|
94
|
+
if (isGuided) {
|
|
95
|
+
console.log(chalk.bold(' To start building:'));
|
|
96
|
+
console.log(` cd ${projectName}`);
|
|
97
|
+
console.log(' npm install');
|
|
98
|
+
console.log(' npm run dev');
|
|
99
|
+
console.log('');
|
|
100
|
+
console.log(` Then open your browser to ${chalk.cyan('http://localhost:3000')}`);
|
|
101
|
+
console.log('');
|
|
102
|
+
console.log(chalk.bold(' 📖 New to coding? Here\'s what to do next:'));
|
|
103
|
+
console.log(' 1. Open this folder in VS Code (or any code editor)');
|
|
104
|
+
console.log(' 2. If you have Claude Code installed, type /workflows');
|
|
105
|
+
console.log(' — it will guide you step by step');
|
|
106
|
+
console.log(' 3. Or read docs/getting-started.md for a beginner-friendly walkthrough');
|
|
107
|
+
console.log('');
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
console.log(chalk.bold(' Next steps:'));
|
|
112
|
+
console.log(` cd ${projectName}`);
|
|
113
|
+
|
|
114
|
+
if (config.stackId === 'nextjs-fullstack') {
|
|
115
|
+
console.log(' npm install');
|
|
116
|
+
console.log(' cp .env.example .env');
|
|
117
|
+
console.log(' npx prisma db push');
|
|
118
|
+
console.log(' npm run dev');
|
|
119
|
+
} else if (config.stackId === 'fastapi-backend') {
|
|
120
|
+
console.log(' cd backend');
|
|
121
|
+
console.log(' python -m venv venv');
|
|
122
|
+
console.log(' source venv/bin/activate # or venv\\Scripts\\activate on Windows');
|
|
123
|
+
console.log(' pip install -r requirements.txt');
|
|
124
|
+
console.log(' cp .env.example .env');
|
|
125
|
+
console.log(' uvicorn app.main:app --reload');
|
|
126
|
+
} else if (config.stackId === 'polyglot-fullstack') {
|
|
127
|
+
console.log(' docker compose up -d postgres');
|
|
128
|
+
console.log(' # Frontend:');
|
|
129
|
+
console.log(' cd frontend && npm install && npm run dev');
|
|
130
|
+
console.log(' # Backend:');
|
|
131
|
+
console.log(' cd backend && pip install -r requirements.txt && uvicorn app.main:app --reload');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (config.claudeCode) {
|
|
135
|
+
console.log('');
|
|
136
|
+
console.log(chalk.bold(' Claude Code:'));
|
|
137
|
+
console.log(' Your project includes pre-configured Claude Code infrastructure:');
|
|
138
|
+
console.log(chalk.dim(' - CLAUDE.md (project context + rules)'));
|
|
139
|
+
console.log(chalk.dim(' - .claude/hooks/ (auto-lint, quality gates)'));
|
|
140
|
+
console.log(chalk.dim(' - .claude/skills/ (framework knowledge)'));
|
|
141
|
+
console.log(chalk.dim(' - .claude/agents/ (verification chain)'));
|
|
142
|
+
console.log(chalk.dim(' - .claude/commands/ (audit, verify, pre-pr)'));
|
|
143
|
+
console.log(chalk.dim(' - docs/prompt-library.md'));
|
|
144
|
+
console.log(chalk.dim(' - docs/uat/ (acceptance testing)'));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
console.log('');
|
|
148
|
+
}
|
package/src/init-mode.js
CHANGED
|
@@ -1,134 +1,138 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
import chalk from 'chalk';
|
|
3
|
-
import { log } from './utils.js';
|
|
4
|
-
import { scanProject } from './scanner.js';
|
|
5
|
-
import { generateClaudeConfig } from './claude-configurator.js';
|
|
6
|
-
import { generateUAT } from './uat-generator.js';
|
|
7
|
-
|
|
8
|
-
export async function runInit(projectDir) {
|
|
9
|
-
console.log('');
|
|
10
|
-
console.log(chalk.bold.cyan(' 🔨 DevForge') + chalk.dim(' — Adding dev guardrails'));
|
|
11
|
-
console.log('');
|
|
12
|
-
console.log(' Scanning...');
|
|
13
|
-
console.log('');
|
|
14
|
-
|
|
15
|
-
const scan = scanProject(projectDir);
|
|
16
|
-
|
|
17
|
-
if (scan.stackId === 'unknown') {
|
|
18
|
-
log.warn('Could not detect a supported stack in this directory.');
|
|
19
|
-
log.dim(' Supported: Next.js, FastAPI, or polyglot (Next.js + FastAPI)');
|
|
20
|
-
log.dim(' Make sure you\'re in the project root directory.');
|
|
21
|
-
process.exit(1);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// Print what was detected
|
|
25
|
-
const detected = [];
|
|
26
|
-
if (scan.frontend.detected) {
|
|
27
|
-
detected.push(`${scan.frontend.framework} (${scan.frontend.language})`);
|
|
28
|
-
}
|
|
29
|
-
if (scan.backend.detected) {
|
|
30
|
-
detected.push(`${scan.backend.framework} (${scan.backend.language})`);
|
|
31
|
-
}
|
|
32
|
-
if (scan.database.detected) {
|
|
33
|
-
detected.push(`${scan.database.type} (${scan.database.orm})`);
|
|
34
|
-
}
|
|
35
|
-
if (scan.testing.unit) detected.push(scan.testing.unit);
|
|
36
|
-
if (scan.testing.e2e) detected.push(scan.testing.e2e);
|
|
37
|
-
if (scan.testing.backend) detected.push(scan.testing.backend);
|
|
38
|
-
|
|
39
|
-
console.log(chalk.bold(' Detected: ') + detected.join(' + '));
|
|
40
|
-
if (scan.infrastructure.hasClaudeMd) {
|
|
41
|
-
console.log(chalk.dim(` CLAUDE.md exists (${scan.infrastructure.claudeMdLines} lines)`));
|
|
42
|
-
}
|
|
43
|
-
console.log('');
|
|
44
|
-
|
|
45
|
-
// Build a synthetic stackConfig from scan results
|
|
46
|
-
const stackConfig = buildConfigFromScan(scan);
|
|
47
|
-
|
|
48
|
-
// Install infrastructure
|
|
49
|
-
console.log(chalk.bold(' Installing:'));
|
|
50
|
-
|
|
51
|
-
// Generate Claude Code infrastructure (skips CLAUDE.md if it exists, merges settings.json if it exists)
|
|
52
|
-
const skipClaudeMd = scan.infrastructure.hasClaudeMd;
|
|
53
|
-
const mergeSettings = scan.infrastructure.hasHooks;
|
|
54
|
-
await generateClaudeConfig(projectDir, stackConfig, { skipClaudeMd, mergeSettings });
|
|
55
|
-
|
|
56
|
-
if (!skipClaudeMd) {
|
|
57
|
-
console.log(chalk.green(' ✓ ') + 'CLAUDE.md — project context + rules');
|
|
58
|
-
} else {
|
|
59
|
-
console.log(chalk.yellow(' ⊘ ') + `CLAUDE.md — exists (tip: run ${chalk.cyan('/
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (!scan.infrastructure.hasHooks) {
|
|
63
|
-
console.log(chalk.green(' ✓ ') + '.claude/hooks/ — auto-lint, quality gate, file protection');
|
|
64
|
-
} else {
|
|
65
|
-
console.log(chalk.yellow(' ⊘ ') + '.claude/hooks/ — already configured');
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (!scan.infrastructure.hasAgents) {
|
|
69
|
-
console.log(chalk.green(' ✓ ') + '.claude/agents/ — code quality, security, spec validator');
|
|
70
|
-
}
|
|
71
|
-
if (!scan.infrastructure.hasCommands) {
|
|
72
|
-
console.log(chalk.green(' ✓ ') + '.claude/commands/ — help, status, next, done, audit, pre-pr');
|
|
73
|
-
}
|
|
74
|
-
if (!scan.infrastructure.hasSkills) {
|
|
75
|
-
console.log(chalk.green(' ✓ ') + '.claude/skills/ — framework-specific knowledge');
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Generate UAT templates
|
|
79
|
-
if (!scan.infrastructure.hasUAT) {
|
|
80
|
-
await generateUAT(projectDir, stackConfig);
|
|
81
|
-
console.log(chalk.green(' ✓ ') + 'docs/uat/ — acceptance test templates');
|
|
82
|
-
} else {
|
|
83
|
-
console.log(chalk.yellow(' ⊘ ') + 'docs/uat/ — already exists');
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
console.log('');
|
|
87
|
-
log.success(' Done.
|
|
88
|
-
console.log('');
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { log } from './utils.js';
|
|
4
|
+
import { scanProject } from './scanner.js';
|
|
5
|
+
import { generateClaudeConfig } from './claude-configurator.js';
|
|
6
|
+
import { generateUAT } from './uat-generator.js';
|
|
7
|
+
|
|
8
|
+
export async function runInit(projectDir) {
|
|
9
|
+
console.log('');
|
|
10
|
+
console.log(chalk.bold.cyan(' 🔨 DevForge') + chalk.dim(' — Adding dev guardrails'));
|
|
11
|
+
console.log('');
|
|
12
|
+
console.log(' Scanning...');
|
|
13
|
+
console.log('');
|
|
14
|
+
|
|
15
|
+
const scan = scanProject(projectDir);
|
|
16
|
+
|
|
17
|
+
if (scan.stackId === 'unknown') {
|
|
18
|
+
log.warn('Could not detect a supported stack in this directory.');
|
|
19
|
+
log.dim(' Supported: Next.js, FastAPI, or polyglot (Next.js + FastAPI)');
|
|
20
|
+
log.dim(' Make sure you\'re in the project root directory.');
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Print what was detected
|
|
25
|
+
const detected = [];
|
|
26
|
+
if (scan.frontend.detected) {
|
|
27
|
+
detected.push(`${scan.frontend.framework} (${scan.frontend.language})`);
|
|
28
|
+
}
|
|
29
|
+
if (scan.backend.detected) {
|
|
30
|
+
detected.push(`${scan.backend.framework} (${scan.backend.language})`);
|
|
31
|
+
}
|
|
32
|
+
if (scan.database.detected) {
|
|
33
|
+
detected.push(`${scan.database.type} (${scan.database.orm})`);
|
|
34
|
+
}
|
|
35
|
+
if (scan.testing.unit) detected.push(scan.testing.unit);
|
|
36
|
+
if (scan.testing.e2e) detected.push(scan.testing.e2e);
|
|
37
|
+
if (scan.testing.backend) detected.push(scan.testing.backend);
|
|
38
|
+
|
|
39
|
+
console.log(chalk.bold(' Detected: ') + detected.join(' + '));
|
|
40
|
+
if (scan.infrastructure.hasClaudeMd) {
|
|
41
|
+
console.log(chalk.dim(` CLAUDE.md exists (${scan.infrastructure.claudeMdLines} lines)`));
|
|
42
|
+
}
|
|
43
|
+
console.log('');
|
|
44
|
+
|
|
45
|
+
// Build a synthetic stackConfig from scan results
|
|
46
|
+
const stackConfig = buildConfigFromScan(scan);
|
|
47
|
+
|
|
48
|
+
// Install infrastructure
|
|
49
|
+
console.log(chalk.bold(' Installing:'));
|
|
50
|
+
|
|
51
|
+
// Generate Claude Code infrastructure (skips CLAUDE.md if it exists, merges settings.json if it exists)
|
|
52
|
+
const skipClaudeMd = scan.infrastructure.hasClaudeMd;
|
|
53
|
+
const mergeSettings = scan.infrastructure.hasHooks;
|
|
54
|
+
await generateClaudeConfig(projectDir, stackConfig, { skipClaudeMd, mergeSettings });
|
|
55
|
+
|
|
56
|
+
if (!skipClaudeMd) {
|
|
57
|
+
console.log(chalk.green(' ✓ ') + 'CLAUDE.md — project context + rules');
|
|
58
|
+
} else {
|
|
59
|
+
console.log(chalk.yellow(' ⊘ ') + `CLAUDE.md — exists (tip: run ${chalk.cyan('/optimize-claude-md')} to slim it)`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!scan.infrastructure.hasHooks) {
|
|
63
|
+
console.log(chalk.green(' ✓ ') + '.claude/hooks/ — auto-lint, quality gate, file protection');
|
|
64
|
+
} else {
|
|
65
|
+
console.log(chalk.yellow(' ⊘ ') + '.claude/hooks/ — already configured');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!scan.infrastructure.hasAgents) {
|
|
69
|
+
console.log(chalk.green(' ✓ ') + '.claude/agents/ — code quality, security, spec validator');
|
|
70
|
+
}
|
|
71
|
+
if (!scan.infrastructure.hasCommands) {
|
|
72
|
+
console.log(chalk.green(' ✓ ') + '.claude/commands/ — help, status, next, done, audit, pre-pr');
|
|
73
|
+
}
|
|
74
|
+
if (!scan.infrastructure.hasSkills) {
|
|
75
|
+
console.log(chalk.green(' ✓ ') + '.claude/skills/ — framework-specific knowledge');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Generate UAT templates
|
|
79
|
+
if (!scan.infrastructure.hasUAT) {
|
|
80
|
+
await generateUAT(projectDir, stackConfig);
|
|
81
|
+
console.log(chalk.green(' ✓ ') + 'docs/uat/ — acceptance test templates');
|
|
82
|
+
} else {
|
|
83
|
+
console.log(chalk.yellow(' ⊘ ') + 'docs/uat/ — already exists');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
console.log('');
|
|
87
|
+
log.success(' Done. Your project is now configured for Claude Code.');
|
|
88
|
+
console.log('');
|
|
89
|
+
console.log(chalk.bold(' Next steps:'));
|
|
90
|
+
console.log(` 1. Open Claude Code in this directory: ${chalk.cyan('claude')}`);
|
|
91
|
+
console.log(` 2. Type ${chalk.cyan('/workflows')} to see available workflows`);
|
|
92
|
+
console.log('');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function buildConfigFromScan(scan) {
|
|
96
|
+
const config = {
|
|
97
|
+
projectName: scan.projectName,
|
|
98
|
+
stackId: scan.stackId,
|
|
99
|
+
serviceType: scan.stackId === 'fastapi-backend' ? 'api_service' : 'full_stack',
|
|
100
|
+
frontend: null,
|
|
101
|
+
backend: null,
|
|
102
|
+
database: null,
|
|
103
|
+
auth: scan.auth.detected ? scan.auth.type : null,
|
|
104
|
+
testing: scan.testing,
|
|
105
|
+
ai: scan.ai,
|
|
106
|
+
realtime: false,
|
|
107
|
+
fileUploads: false,
|
|
108
|
+
deployment: scan.deployment || 'docker',
|
|
109
|
+
claudeCode: true,
|
|
110
|
+
templateModules: [],
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
if (scan.frontend.detected) {
|
|
114
|
+
config.frontend = {
|
|
115
|
+
framework: scan.frontend.framework,
|
|
116
|
+
language: scan.frontend.language,
|
|
117
|
+
styling: 'tailwind',
|
|
118
|
+
ui: 'shadcn',
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (scan.backend.detected) {
|
|
123
|
+
config.backend = {
|
|
124
|
+
framework: scan.backend.framework,
|
|
125
|
+
language: scan.backend.language,
|
|
126
|
+
orm: scan.database.detected ? scan.database.orm : (scan.backend.language === 'python' ? 'sqlalchemy' : 'prisma'),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (scan.database.detected) {
|
|
131
|
+
config.database = {
|
|
132
|
+
type: scan.database.type,
|
|
133
|
+
orm: scan.database.orm,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return config;
|
|
138
|
+
}
|