forgedev 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.
Files changed (99) hide show
  1. package/CLAUDE.md +38 -0
  2. package/LICENSE +21 -0
  3. package/README.md +246 -0
  4. package/bin/devforge.js +4 -0
  5. package/package.json +33 -0
  6. package/src/claude-configurator.js +260 -0
  7. package/src/cli.js +119 -0
  8. package/src/composer.js +214 -0
  9. package/src/doctor-checks.js +743 -0
  10. package/src/doctor-prompts.js +295 -0
  11. package/src/doctor.js +281 -0
  12. package/src/guided.js +315 -0
  13. package/src/index.js +148 -0
  14. package/src/init-mode.js +134 -0
  15. package/src/prompts.js +155 -0
  16. package/src/recommender.js +186 -0
  17. package/src/scanner.js +368 -0
  18. package/src/uat-generator.js +189 -0
  19. package/src/utils.js +57 -0
  20. package/templates/auth/jwt-custom/backend/app/api/auth.py.template +45 -0
  21. package/templates/auth/jwt-custom/backend/app/api/deps.py.template +16 -0
  22. package/templates/auth/jwt-custom/backend/app/core/security.py.template +34 -0
  23. package/templates/auth/nextauth/src/app/api/auth/[...nextauth]/route.ts.template +3 -0
  24. package/templates/auth/nextauth/src/lib/auth.ts.template +30 -0
  25. package/templates/auth/nextauth/src/middleware.ts.template +14 -0
  26. package/templates/backend/fastapi/backend/Dockerfile.template +12 -0
  27. package/templates/backend/fastapi/backend/app/__init__.py +0 -0
  28. package/templates/backend/fastapi/backend/app/api/__init__.py +0 -0
  29. package/templates/backend/fastapi/backend/app/api/health.py.template +32 -0
  30. package/templates/backend/fastapi/backend/app/core/__init__.py +0 -0
  31. package/templates/backend/fastapi/backend/app/core/config.py.template +25 -0
  32. package/templates/backend/fastapi/backend/app/core/errors.py +37 -0
  33. package/templates/backend/fastapi/backend/app/core/retry.py +32 -0
  34. package/templates/backend/fastapi/backend/app/main.py.template +58 -0
  35. package/templates/backend/fastapi/backend/app/models/__init__.py +0 -0
  36. package/templates/backend/fastapi/backend/app/schemas/__init__.py +0 -0
  37. package/templates/backend/fastapi/backend/pyproject.toml.template +19 -0
  38. package/templates/backend/fastapi/backend/requirements.txt.template +14 -0
  39. package/templates/base/.gitignore.template +29 -0
  40. package/templates/base/README.md.template +25 -0
  41. package/templates/claude-code/agents/code-quality-reviewer.md +41 -0
  42. package/templates/claude-code/agents/production-readiness.md +55 -0
  43. package/templates/claude-code/agents/security-reviewer.md +41 -0
  44. package/templates/claude-code/agents/spec-validator.md +34 -0
  45. package/templates/claude-code/agents/uat-validator.md +37 -0
  46. package/templates/claude-code/claude-md/base.md +33 -0
  47. package/templates/claude-code/claude-md/fastapi.md +12 -0
  48. package/templates/claude-code/claude-md/fullstack.md +12 -0
  49. package/templates/claude-code/claude-md/nextjs.md +11 -0
  50. package/templates/claude-code/commands/audit-security.md +11 -0
  51. package/templates/claude-code/commands/audit-spec.md +9 -0
  52. package/templates/claude-code/commands/audit-wiring.md +17 -0
  53. package/templates/claude-code/commands/done.md +19 -0
  54. package/templates/claude-code/commands/generate-prd.md +45 -0
  55. package/templates/claude-code/commands/generate-uat.md +35 -0
  56. package/templates/claude-code/commands/help.md +26 -0
  57. package/templates/claude-code/commands/next.md +20 -0
  58. package/templates/claude-code/commands/optimize-claude-md.md +31 -0
  59. package/templates/claude-code/commands/pre-pr.md +19 -0
  60. package/templates/claude-code/commands/run-uat.md +21 -0
  61. package/templates/claude-code/commands/status.md +24 -0
  62. package/templates/claude-code/commands/verify-all.md +11 -0
  63. package/templates/claude-code/hooks/polyglot.json +36 -0
  64. package/templates/claude-code/hooks/python.json +36 -0
  65. package/templates/claude-code/hooks/scripts/autofix-polyglot.sh +16 -0
  66. package/templates/claude-code/hooks/scripts/autofix-python.sh +14 -0
  67. package/templates/claude-code/hooks/scripts/autofix-typescript.sh +14 -0
  68. package/templates/claude-code/hooks/scripts/guard-protected-files.sh +21 -0
  69. package/templates/claude-code/hooks/typescript.json +36 -0
  70. package/templates/claude-code/skills/ai-prompts/SKILL.md +43 -0
  71. package/templates/claude-code/skills/fastapi/SKILL.md +38 -0
  72. package/templates/claude-code/skills/nextjs/SKILL.md +39 -0
  73. package/templates/claude-code/skills/playwright/SKILL.md +37 -0
  74. package/templates/claude-code/skills/security-api/SKILL.md +47 -0
  75. package/templates/claude-code/skills/security-web/SKILL.md +41 -0
  76. package/templates/database/prisma-postgres/.env.example +1 -0
  77. package/templates/database/prisma-postgres/prisma/schema.prisma.template +18 -0
  78. package/templates/database/sqlalchemy-postgres/.env.example +1 -0
  79. package/templates/database/sqlalchemy-postgres/backend/alembic/env.py.template +40 -0
  80. package/templates/database/sqlalchemy-postgres/backend/alembic/versions/.gitkeep +0 -0
  81. package/templates/database/sqlalchemy-postgres/backend/alembic.ini.template +36 -0
  82. package/templates/database/sqlalchemy-postgres/backend/app/db/__init__.py +0 -0
  83. package/templates/database/sqlalchemy-postgres/backend/app/db/base.py +5 -0
  84. package/templates/database/sqlalchemy-postgres/backend/app/db/session.py.template +48 -0
  85. package/templates/frontend/nextjs/next.config.ts.template +7 -0
  86. package/templates/frontend/nextjs/package.json.template +41 -0
  87. package/templates/frontend/nextjs/postcss.config.mjs +7 -0
  88. package/templates/frontend/nextjs/src/app/api/health/route.ts.template +10 -0
  89. package/templates/frontend/nextjs/src/app/globals.css +1 -0
  90. package/templates/frontend/nextjs/src/app/layout.tsx.template +22 -0
  91. package/templates/frontend/nextjs/src/app/page.tsx.template +10 -0
  92. package/templates/frontend/nextjs/src/lib/db.ts.template +40 -0
  93. package/templates/frontend/nextjs/src/lib/errors.ts +28 -0
  94. package/templates/frontend/nextjs/src/lib/utils.ts +6 -0
  95. package/templates/frontend/nextjs/tsconfig.json +23 -0
  96. package/templates/infra/docker-compose/docker-compose.yml.template +19 -0
  97. package/templates/testing/playwright/e2e/example.spec.ts.template +15 -0
  98. package/templates/testing/playwright/playwright.config.ts.template +22 -0
  99. package/templates/testing/vitest/src/__tests__/example.test.ts.template +12 -0
package/src/cli.js ADDED
@@ -0,0 +1,119 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs';
3
+ import chalk from 'chalk';
4
+ import { log } from './utils.js';
5
+
6
+ export async function parseCommand(args) {
7
+ if (args.includes('--version') || args.includes('-v')) {
8
+ const pkg = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf-8'));
9
+ console.log(pkg.version);
10
+ process.exit(0);
11
+ }
12
+
13
+ if (args.includes('--help') || args.includes('-h')) {
14
+ showHelp();
15
+ process.exit(0);
16
+ }
17
+
18
+ const command = args[0];
19
+
20
+ if (!command) {
21
+ showUsage();
22
+ process.exit(0);
23
+ }
24
+
25
+ if (command === 'new') {
26
+ const projectName = args[1];
27
+ if (!projectName) {
28
+ log.error('Usage: devforge new <project-name>');
29
+ log.dim(' Example: devforge new my-app');
30
+ process.exit(1);
31
+ }
32
+ const { runNew } = await import('./index.js');
33
+ await runNew(projectName);
34
+ return;
35
+ }
36
+
37
+ if (command === 'init') {
38
+ const { runInit } = await import('./init-mode.js');
39
+ await runInit(process.cwd());
40
+ return;
41
+ }
42
+
43
+ if (command === 'doctor') {
44
+ const { runDoctor } = await import('./doctor.js');
45
+ await runDoctor(process.cwd());
46
+ return;
47
+ }
48
+
49
+ // Shorthand: devforge my-app → devforge new my-app
50
+ if (!command.startsWith('-')) {
51
+ const targetDir = path.resolve(process.cwd(), command);
52
+ if (fs.existsSync(targetDir)) {
53
+ console.log('');
54
+ log.warn(`"${command}" already exists. Did you mean:`);
55
+ console.log(` ${chalk.bold('devforge init')} Add dev guardrails to current project`);
56
+ console.log(` ${chalk.bold('devforge doctor')} Diagnose and optimize current project`);
57
+ console.log('');
58
+ process.exit(1);
59
+ }
60
+ const { runNew } = await import('./index.js');
61
+ await runNew(command);
62
+ return;
63
+ }
64
+
65
+ showUsage();
66
+ }
67
+
68
+ function showUsage() {
69
+ console.log(`
70
+ ${chalk.bold.cyan('DevForge')} — AI-first project scaffolding
71
+
72
+ ${chalk.bold('Usage:')}
73
+ devforge new <name> Create a new project
74
+ devforge init Add dev guardrails to current project
75
+ devforge doctor Diagnose and optimize current project
76
+ devforge <name> Shorthand for ${chalk.dim('devforge new <name>')}
77
+
78
+ ${chalk.bold('Options:')}
79
+ -h, --help Show this help message
80
+ -v, --version Show version number
81
+
82
+ Run ${chalk.cyan('devforge new --help')} for more details.
83
+ `);
84
+ }
85
+
86
+ function showHelp() {
87
+ console.log(`
88
+ ${chalk.bold.cyan('DevForge')} — Universal, AI-first project scaffolding
89
+
90
+ ${chalk.bold('Commands:')}
91
+
92
+ ${chalk.bold('devforge new <name>')}
93
+ Create a new project. Choose between:
94
+ ${chalk.dim('• Guided mode — describe what you want in plain English')}
95
+ ${chalk.dim('• Developer mode — pick your stack directly')}
96
+
97
+ ${chalk.bold('devforge init')}
98
+ Add Claude Code infrastructure to an existing project.
99
+ Auto-detects your stack and installs hooks, agents, commands,
100
+ skills, and UAT templates. Zero questions asked.
101
+
102
+ ${chalk.bold('devforge doctor')}
103
+ Diagnose and optimize an existing project.
104
+ Checks for: oversized CLAUDE.md, unauthenticated endpoints,
105
+ flaky tests, dead code, duplicate code, and more.
106
+ Generates Claude Code prompts to fix each issue.
107
+
108
+ ${chalk.bold('Supported stacks (V1):')}
109
+ - Next.js full-stack (TypeScript + Prisma + PostgreSQL)
110
+ - FastAPI backend (Python + SQLAlchemy + PostgreSQL)
111
+ - Polyglot full-stack (Next.js + FastAPI monorepo)
112
+
113
+ ${chalk.bold('Examples:')}
114
+ devforge new my-app
115
+ devforge my-saas-platform
116
+ devforge init
117
+ devforge doctor
118
+ `);
119
+ }
@@ -0,0 +1,214 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { ROOT_DIR, ensureDir, readTemplate, replaceVars, toPascalCase, toSnakeCase, log } from './utils.js';
4
+
5
+ const TEMPLATES_DIR = path.join(ROOT_DIR, 'templates');
6
+
7
+ export async function compose(outputDir, stackConfig) {
8
+ const variables = buildVariables(stackConfig);
9
+
10
+ ensureDir(outputDir);
11
+
12
+ for (const mod of stackConfig.templateModules) {
13
+ const templateDir = path.join(TEMPLATES_DIR, mod.path);
14
+ if (!fs.existsSync(templateDir)) {
15
+ log.warn(`Template module not found: ${mod.path} — skipping`);
16
+ continue;
17
+ }
18
+ processTemplateDir(templateDir, outputDir, variables, mod.prefix || '');
19
+ }
20
+
21
+ // Inject auth dependencies into package.json if auth is enabled
22
+ injectAuthDependencies(outputDir, stackConfig);
23
+ }
24
+
25
+ export function buildVariables(stackConfig) {
26
+ const vars = {
27
+ PROJECT_NAME: stackConfig.projectName,
28
+ PROJECT_NAME_PASCAL: toPascalCase(stackConfig.projectName),
29
+ PROJECT_NAME_SNAKE: toSnakeCase(stackConfig.projectName),
30
+ };
31
+
32
+ if (stackConfig.frontend) {
33
+ vars.FRONTEND_FRAMEWORK = stackConfig.frontend.framework;
34
+ vars.FRONTEND_LANGUAGE = stackConfig.frontend.language;
35
+ vars.FRONTEND_STYLING = stackConfig.frontend.styling;
36
+ vars.FRONTEND_UI = stackConfig.frontend.ui || '';
37
+ }
38
+
39
+ if (stackConfig.backend) {
40
+ vars.BACKEND_FRAMEWORK = stackConfig.backend.framework;
41
+ vars.BACKEND_LANGUAGE = stackConfig.backend.language;
42
+ vars.BACKEND_ORM = stackConfig.backend.orm;
43
+ }
44
+
45
+ if (stackConfig.database) {
46
+ vars.DATABASE_TYPE = stackConfig.database.type;
47
+ vars.DATABASE_ORM = stackConfig.database.orm;
48
+ }
49
+
50
+ vars.AUTH_TYPE = stackConfig.auth || 'none';
51
+ vars.DEPLOYMENT = stackConfig.deployment || 'docker';
52
+
53
+ // Commands vary by stack
54
+ if (stackConfig.stackId === 'nextjs-fullstack') {
55
+ vars.LINT_COMMAND = 'npx eslint .';
56
+ vars.TYPE_CHECK_COMMAND = 'npx tsc --noEmit';
57
+ vars.TEST_COMMAND = 'npx vitest run';
58
+ vars.BUILD_COMMAND = 'npm run build';
59
+ vars.DEV_COMMAND = 'npm run dev';
60
+ vars.STACK_DESCRIPTION = 'Next.js full-stack application with TypeScript, Tailwind CSS, Prisma, and PostgreSQL';
61
+ vars.EXTRA_IGNORES = '';
62
+ } else if (stackConfig.stackId === 'fastapi-backend') {
63
+ vars.LINT_COMMAND = 'ruff check .';
64
+ vars.TYPE_CHECK_COMMAND = 'pyright';
65
+ vars.TEST_COMMAND = 'pytest';
66
+ vars.BUILD_COMMAND = 'docker build -t app .';
67
+ vars.DEV_COMMAND = 'uvicorn app.main:app --reload';
68
+ vars.STACK_DESCRIPTION = 'FastAPI backend service with SQLAlchemy, PostgreSQL, and Alembic';
69
+ vars.EXTRA_IGNORES = '\n# Python\n__pycache__/\n*.pyc\nvenv/\n.venv/\n*.egg-info/';
70
+ } else if (stackConfig.stackId === 'polyglot-fullstack') {
71
+ vars.LINT_COMMAND = 'cd frontend && npx eslint . && cd ../backend && ruff check .';
72
+ vars.TYPE_CHECK_COMMAND = 'cd frontend && npx tsc --noEmit';
73
+ vars.TEST_COMMAND = 'cd frontend && npx vitest run && cd ../backend && pytest';
74
+ vars.BUILD_COMMAND = 'docker compose build';
75
+ vars.DEV_COMMAND = 'docker compose up';
76
+ vars.STACK_DESCRIPTION = 'Full-stack application with Next.js frontend and FastAPI backend';
77
+ vars.EXTRA_IGNORES = '\n# Python\n__pycache__/\n*.pyc\nvenv/\n.venv/\n*.egg-info/';
78
+ }
79
+
80
+ // Setup commands for README
81
+ vars.SETUP_COMMANDS = buildSetupCommands(stackConfig);
82
+ vars.AVAILABLE_SCRIPTS = buildAvailableScripts(stackConfig);
83
+
84
+ return vars;
85
+ }
86
+
87
+ function buildSetupCommands(config) {
88
+ if (config.stackId === 'nextjs-fullstack') {
89
+ return `npm install
90
+ cp .env.example .env
91
+ npx prisma db push
92
+ npm run dev`;
93
+ }
94
+ if (config.stackId === 'fastapi-backend') {
95
+ return `cd backend
96
+ python -m venv venv
97
+ source venv/bin/activate
98
+ pip install -r requirements.txt
99
+ cp .env.example .env
100
+ uvicorn app.main:app --reload`;
101
+ }
102
+ if (config.stackId === 'polyglot-fullstack') {
103
+ return `docker compose up -d postgres
104
+ # Frontend
105
+ cd frontend && npm install && npm run dev
106
+ # Backend
107
+ cd backend && pip install -r requirements.txt && uvicorn app.main:app --reload`;
108
+ }
109
+ return '';
110
+ }
111
+
112
+ function buildAvailableScripts(config) {
113
+ if (config.stackId === 'nextjs-fullstack') {
114
+ return `- \`npm run dev\` — Start development server
115
+ - \`npm run build\` — Production build
116
+ - \`npm run lint\` — Run ESLint
117
+ - \`npx prisma studio\` — Database GUI
118
+ - \`npx vitest\` — Run unit tests
119
+ - \`npx playwright test\` — Run E2E tests`;
120
+ }
121
+ if (config.stackId === 'fastapi-backend') {
122
+ return `- \`uvicorn app.main:app --reload\` — Start dev server
123
+ - \`pytest\` — Run tests
124
+ - \`ruff check .\` — Run linter
125
+ - \`alembic upgrade head\` — Run migrations`;
126
+ }
127
+ if (config.stackId === 'polyglot-fullstack') {
128
+ return `- \`docker compose up\` — Start all services
129
+ - \`docker compose up -d postgres\` — Start database only
130
+ - Frontend: \`cd frontend && npm run dev\`
131
+ - Backend: \`cd backend && uvicorn app.main:app --reload\``;
132
+ }
133
+ return '';
134
+ }
135
+
136
+ export function processTemplateDir(templateDir, outputDir, variables, prefix) {
137
+ const entries = walkDir(templateDir);
138
+
139
+ for (const filePath of entries) {
140
+ const relativePath = path.relative(templateDir, filePath);
141
+ let outputRelative = prefix ? path.join(prefix, relativePath) : relativePath;
142
+
143
+ if (outputRelative.endsWith('.template')) {
144
+ // Process template: replace vars, strip .template extension
145
+ const content = readTemplate(filePath);
146
+ const processed = replaceVars(content, variables);
147
+ const outputPath = path.join(outputDir, outputRelative.replace(/\.template$/, ''));
148
+ ensureDir(path.dirname(outputPath));
149
+ fs.writeFileSync(outputPath, processed, 'utf-8');
150
+ } else if (path.basename(filePath) === '.gitkeep') {
151
+ // Create the directory but don't copy .gitkeep itself
152
+ ensureDir(path.join(outputDir, path.dirname(outputRelative)));
153
+ } else {
154
+ // Binary copy
155
+ const outputPath = path.join(outputDir, outputRelative);
156
+ ensureDir(path.dirname(outputPath));
157
+ fs.copyFileSync(filePath, outputPath);
158
+ }
159
+ }
160
+ }
161
+
162
+ function walkDir(dir) {
163
+ const results = [];
164
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
165
+ for (const entry of entries) {
166
+ const fullPath = path.join(dir, entry.name);
167
+ if (entry.isDirectory()) {
168
+ results.push(...walkDir(fullPath));
169
+ } else {
170
+ results.push(fullPath);
171
+ }
172
+ }
173
+ return results;
174
+ }
175
+
176
+ function injectAuthDependencies(outputDir, stackConfig) {
177
+ if (!stackConfig.auth || stackConfig.auth === 'none') return;
178
+
179
+ // Next.js projects with nextauth
180
+ if (stackConfig.auth === 'nextauth' || stackConfig.auth === 'both') {
181
+ const pkgPaths = [
182
+ path.join(outputDir, 'package.json'),
183
+ path.join(outputDir, 'frontend', 'package.json'),
184
+ ];
185
+ for (const pkgPath of pkgPaths) {
186
+ if (!fs.existsSync(pkgPath)) continue;
187
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
188
+ pkg.dependencies = pkg.dependencies || {};
189
+ pkg.dependencies['next-auth'] = '^5.0.0-beta.25';
190
+ pkg.dependencies['@auth/prisma-adapter'] = '^2.7.4';
191
+ pkg.dependencies['bcryptjs'] = '^2.4.3';
192
+ pkg.devDependencies = pkg.devDependencies || {};
193
+ pkg.devDependencies['@types/bcryptjs'] = '^2.4.6';
194
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf-8');
195
+ }
196
+ }
197
+
198
+ // FastAPI projects with jwt-custom
199
+ if (stackConfig.auth === 'jwt-custom' || stackConfig.auth === 'both') {
200
+ const reqPaths = [
201
+ path.join(outputDir, 'requirements.txt'),
202
+ path.join(outputDir, 'backend', 'requirements.txt'),
203
+ ];
204
+ for (const reqPath of reqPaths) {
205
+ if (!fs.existsSync(reqPath)) continue;
206
+ const content = fs.readFileSync(reqPath, 'utf-8');
207
+ const authDeps = ['python-jose[cryptography]', 'passlib[bcrypt]', 'python-multipart'];
208
+ const additions = authDeps.filter(dep => !content.includes(dep.split('[')[0]));
209
+ if (additions.length > 0) {
210
+ fs.writeFileSync(reqPath, content.trimEnd() + '\n' + additions.join('\n') + '\n', 'utf-8');
211
+ }
212
+ }
213
+ }
214
+ }