forgedev 1.1.3 → 1.3.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/README.md +58 -10
- package/bin/chainproof.js +126 -0
- package/bin/devforge.js +2 -1
- package/package.json +33 -7
- package/src/chainproof-bridge.js +330 -0
- package/src/ci-mode.js +85 -0
- package/src/claude-configurator.js +87 -49
- package/src/cli.js +35 -12
- package/src/composer.js +159 -34
- package/src/doctor-checks-chainproof.js +106 -0
- package/src/doctor-checks.js +39 -20
- package/src/doctor-prompts.js +9 -9
- package/src/doctor.js +37 -4
- package/src/guided.js +3 -3
- package/src/index.js +31 -10
- package/src/init-mode.js +64 -11
- package/src/menu.js +178 -0
- package/src/prompts.js +5 -12
- package/src/recommender.js +134 -10
- package/src/scanner.js +57 -2
- package/src/uat-generator.js +204 -189
- package/src/update-check.js +9 -4
- package/src/update.js +1 -1
- package/src/utils.js +65 -6
- package/templates/ai/guardrails-py/backend/app/ai/__init__.py +29 -0
- package/templates/ai/guardrails-py/backend/app/ai/audit_log.py +133 -0
- package/templates/ai/guardrails-py/backend/app/ai/client.py.template +323 -0
- package/templates/ai/guardrails-py/backend/app/ai/health.py.template +157 -0
- package/templates/ai/guardrails-py/backend/app/ai/input_guard.py +98 -0
- package/templates/ai/guardrails-ts/src/lib/ai/audit-log.ts.template +164 -0
- package/templates/ai/guardrails-ts/src/lib/ai/client.ts.template +403 -0
- package/templates/ai/guardrails-ts/src/lib/ai/health.ts.template +165 -0
- package/templates/ai/guardrails-ts/src/lib/ai/index.ts.template +17 -0
- package/templates/ai/guardrails-ts/src/lib/ai/input-guard.ts.template +124 -0
- package/templates/auth/nextauth/src/lib/auth.ts.template +12 -7
- package/templates/backend/express/Dockerfile.template +18 -0
- package/templates/backend/express/package.json.template +33 -0
- package/templates/backend/express/src/index.ts.template +34 -0
- package/templates/backend/express/src/routes/health.ts.template +27 -0
- package/templates/backend/express/tsconfig.json +17 -0
- package/templates/backend/fastapi/backend/Dockerfile.template +5 -0
- package/templates/backend/fastapi/backend/app/api/health.py.template +1 -1
- package/templates/backend/fastapi/backend/app/core/config.py.template +1 -1
- package/templates/backend/fastapi/backend/app/core/errors.py +1 -1
- package/templates/backend/fastapi/backend/app/main.py.template +3 -1
- package/templates/backend/fastapi/backend/requirements.txt.template +2 -0
- package/templates/backend/hono/Dockerfile.template +18 -0
- package/templates/backend/hono/package.json.template +31 -0
- package/templates/backend/hono/src/index.ts.template +32 -0
- package/templates/backend/hono/src/routes/health.ts.template +27 -0
- package/templates/backend/hono/tsconfig.json +18 -0
- package/templates/base/docs/plans/.gitkeep +0 -0
- package/templates/base/docs/uat/UAT_CHECKLIST.csv.template +2 -0
- package/templates/base/docs/uat/UAT_TEMPLATE.md.template +22 -0
- package/templates/chainproof/base/.chainproof/config.json.template +11 -0
- package/templates/chainproof/base/.chainproof/mcp-server.mjs +310 -0
- package/templates/chainproof/base/.mcp.json +9 -0
- package/templates/chainproof/fastapi/.chainproof/middleware.json.template +14 -0
- package/templates/chainproof/nextjs/.chainproof/hooks.json.template +19 -0
- package/templates/chainproof/polyglot/.chainproof/config.json.template +21 -0
- package/templates/claude-code/agents/architect.md +25 -11
- package/templates/claude-code/agents/build-error-resolver.md +22 -7
- package/templates/claude-code/agents/chief-of-staff.md +42 -8
- package/templates/claude-code/agents/code-quality-reviewer.md +15 -1
- package/templates/claude-code/agents/database-reviewer.md +16 -2
- package/templates/claude-code/agents/deep-reviewer.md +191 -0
- package/templates/claude-code/agents/doc-updater.md +19 -5
- package/templates/claude-code/agents/docs-lookup.md +19 -5
- package/templates/claude-code/agents/e2e-runner.md +26 -12
- package/templates/claude-code/agents/enforcement-gate.md +102 -0
- package/templates/claude-code/agents/frontend-builder.md +188 -0
- package/templates/claude-code/agents/harness-optimizer.md +61 -0
- package/templates/claude-code/agents/loop-operator.md +27 -12
- package/templates/claude-code/agents/planner.md +21 -7
- package/templates/claude-code/agents/product-strategist.md +138 -0
- package/templates/claude-code/agents/production-readiness.md +14 -0
- package/templates/claude-code/agents/prompt-auditor.md +115 -0
- package/templates/claude-code/agents/refactor-cleaner.md +22 -8
- package/templates/claude-code/agents/security-reviewer.md +15 -0
- package/templates/claude-code/agents/spec-validator.md +45 -1
- package/templates/claude-code/agents/tdd-guide.md +21 -7
- package/templates/claude-code/agents/uat-validator.md +18 -0
- package/templates/claude-code/claude-md/base.md +15 -7
- package/templates/claude-code/claude-md/fastapi.md +8 -8
- package/templates/claude-code/claude-md/fullstack.md +6 -6
- package/templates/claude-code/claude-md/hono.md +18 -0
- package/templates/claude-code/claude-md/nextjs.md +5 -5
- package/templates/claude-code/claude-md/remix.md +18 -0
- package/templates/claude-code/commands/audit-security.md +14 -0
- package/templates/claude-code/commands/audit-spec.md +14 -0
- package/templates/claude-code/commands/audit-wiring.md +14 -0
- package/templates/claude-code/commands/build-fix.md +28 -0
- package/templates/claude-code/commands/build-ui.md +59 -0
- package/templates/claude-code/commands/code-review.md +54 -26
- package/templates/claude-code/commands/fix-loop.md +211 -0
- package/templates/claude-code/commands/full-audit.md +37 -8
- package/templates/claude-code/commands/generate-prd.md +1 -1
- package/templates/claude-code/commands/generate-sdd.md +74 -0
- package/templates/claude-code/commands/generate-uat.md +107 -35
- package/templates/claude-code/commands/help.md +68 -0
- package/templates/claude-code/commands/live-uat.md +268 -0
- package/templates/claude-code/commands/optimize-claude-md.md +15 -1
- package/templates/claude-code/commands/plan.md +3 -3
- package/templates/claude-code/commands/pre-pr.md +57 -19
- package/templates/claude-code/commands/product-strategist.md +21 -0
- package/templates/claude-code/commands/resume-session.md +10 -10
- package/templates/claude-code/commands/run-uat.md +59 -2
- package/templates/claude-code/commands/save-session.md +10 -10
- package/templates/claude-code/commands/simplify.md +36 -0
- package/templates/claude-code/commands/tdd.md +17 -18
- package/templates/claude-code/commands/verify-all.md +24 -0
- package/templates/claude-code/commands/verify-intent.md +55 -0
- package/templates/claude-code/commands/workflows.md +52 -37
- package/templates/claude-code/hooks/polyglot.json +10 -1
- package/templates/claude-code/hooks/python.json +10 -1
- package/templates/claude-code/hooks/scripts/autofix-polyglot.mjs +20 -10
- package/templates/claude-code/hooks/scripts/autofix-python.mjs +4 -5
- package/templates/claude-code/hooks/scripts/autofix-typescript.mjs +4 -4
- package/templates/claude-code/hooks/scripts/code-hygiene.mjs +293 -0
- package/templates/claude-code/hooks/scripts/guard-protected-files.mjs +2 -2
- package/templates/claude-code/hooks/scripts/pre-commit-gate.mjs +207 -0
- package/templates/claude-code/hooks/typescript.json +10 -1
- package/templates/claude-code/skills/ai-prompts/SKILL.md +119 -41
- package/templates/claude-code/skills/git-workflow/SKILL.md +6 -6
- package/templates/claude-code/skills/nextjs/SKILL.md +1 -1
- package/templates/claude-code/skills/playwright/SKILL.md +6 -5
- package/templates/claude-code/skills/security-api/SKILL.md +1 -1
- package/templates/claude-code/skills/security-web/SKILL.md +2 -1
- package/templates/claude-code/skills/testing-patterns/SKILL.md +9 -9
- package/templates/database/prisma-postgres/{.env.example → .env.example.template} +1 -0
- package/templates/database/sqlalchemy-postgres/{.env.example → .env.example.template} +1 -0
- package/templates/docs-portal/fastapi/backend/app/portal/__init__.py +0 -0
- package/templates/docs-portal/fastapi/backend/app/portal/__pycache__/docs_reader.cpython-314.pyc +0 -0
- package/templates/docs-portal/fastapi/backend/app/portal/docs_reader.py +201 -0
- package/templates/docs-portal/fastapi/backend/app/portal/html_renderer.py +229 -0
- package/templates/docs-portal/fastapi/backend/app/portal/router.py.template +35 -0
- package/templates/docs-portal/nextjs/src/app/portal/[category]/[slug]/page.tsx +81 -0
- package/templates/docs-portal/nextjs/src/app/portal/[category]/page.tsx +65 -0
- package/templates/docs-portal/nextjs/src/app/portal/layout.tsx.template +54 -0
- package/templates/docs-portal/nextjs/src/app/portal/page.tsx +85 -0
- package/templates/docs-portal/nextjs/src/components/portal/markdown-renderer.tsx +101 -0
- package/templates/docs-portal/nextjs/src/components/portal/mobile-portal-nav.tsx +81 -0
- package/templates/docs-portal/nextjs/src/components/portal/portal-nav.tsx +86 -0
- package/templates/docs-portal/nextjs/src/lib/docs.ts +139 -0
- package/templates/frontend/nextjs/package.json.template +3 -1
- package/templates/frontend/react/index.html.template +12 -0
- package/templates/frontend/react/package.json.template +34 -0
- package/templates/frontend/react/src/App.tsx.template +10 -0
- package/templates/frontend/react/src/index.css +1 -0
- package/templates/frontend/react/src/main.tsx +10 -0
- package/templates/frontend/react/tsconfig.json +17 -0
- package/templates/frontend/react/vite.config.ts.template +15 -0
- package/templates/frontend/react/vitest.config.ts +9 -0
- package/templates/frontend/remix/app/root.tsx.template +31 -0
- package/templates/frontend/remix/app/routes/_index.tsx.template +19 -0
- package/templates/frontend/remix/app/routes/api.health.ts.template +10 -0
- package/templates/frontend/remix/app/tailwind.css +1 -0
- package/templates/frontend/remix/package.json.template +39 -0
- package/templates/frontend/remix/tsconfig.json +18 -0
- package/templates/frontend/remix/vite.config.ts.template +7 -0
- package/templates/infra/github-actions/.github/workflows/ci.yml.template +52 -0
- package/templates/testing/pytest/backend/tests/__init__.py +0 -0
- package/templates/testing/pytest/backend/tests/conftest.py.template +11 -0
- package/templates/testing/pytest/backend/tests/test_health.py.template +10 -0
- package/templates/testing/vitest/vitest.config.ts.template +18 -0
- package/CLAUDE.md +0 -38
- package/templates/claude-code/commands/done.md +0 -19
package/src/menu.js
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { select, input, Separator } from '@inquirer/prompts';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
|
|
5
|
+
const COMMAND_GROUPS = [
|
|
6
|
+
{
|
|
7
|
+
name: 'Getting Started',
|
|
8
|
+
commands: [
|
|
9
|
+
{ value: 'new', label: 'Create a new project', description: 'Scaffold a full project with AI-powered stack selection' },
|
|
10
|
+
{ value: 'init', label: 'Add guardrails to existing project', description: 'Install hooks, agents, and commands into your current codebase' },
|
|
11
|
+
],
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
name: 'Daily Workflow',
|
|
15
|
+
commands: [
|
|
16
|
+
{ value: 'status', label: '/status', description: 'Run all checks and show a project dashboard' },
|
|
17
|
+
{ value: 'next', label: '/next', description: 'Figure out what to work on next' },
|
|
18
|
+
{ value: 'done', label: '/done', description: 'Verify current task is actually complete before moving on' },
|
|
19
|
+
{ value: 'help', label: '/help', description: 'Not sure what to do? Get guided to the right workflow' },
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: 'Development',
|
|
24
|
+
commands: [
|
|
25
|
+
{ value: 'plan', label: '/plan', description: 'Create a comprehensive implementation plan before writing code' },
|
|
26
|
+
{ value: 'tdd', label: '/tdd', description: 'Write failing tests first, then implement (test-driven)' },
|
|
27
|
+
{ value: 'build-fix', label: '/build-fix', description: 'Fix build, lint, and type errors with minimal changes' },
|
|
28
|
+
{ value: 'build-ui', label: '/build-ui', description: 'Build frontend UI components with AI-powered generation' },
|
|
29
|
+
{ value: 'code-review', label: '/code-review', description: 'Review uncommitted changes for issues and quality' },
|
|
30
|
+
{ value: 'simplify', label: '/simplify', description: 'Find duplicate code, long files, and extract shared utilities' },
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: 'Verification',
|
|
35
|
+
commands: [
|
|
36
|
+
{ value: 'verify-all', label: '/verify-all', description: 'Run the full verification chain on current changes' },
|
|
37
|
+
{ value: 'full-audit', label: '/full-audit', description: 'Run every audit and review agent in a single pass' },
|
|
38
|
+
{ value: 'audit-spec', label: '/audit-spec', description: 'Validate code against the specification' },
|
|
39
|
+
{ value: 'audit-wiring', label: '/audit-wiring', description: 'Verify API endpoints are wired between frontend and backend' },
|
|
40
|
+
{ value: 'audit-security', label: '/audit-security', description: 'Run a focused security audit on changed files' },
|
|
41
|
+
{ value: 'verify-intent', label: '/verify-intent', description: 'Check Intent Verification Protocol compliance' },
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'Release',
|
|
46
|
+
commands: [
|
|
47
|
+
{ value: 'pre-pr', label: '/pre-pr', description: 'Run the complete pre-PR checklist before creating a pull request' },
|
|
48
|
+
{ value: 'run-uat', label: '/run-uat', description: 'Execute UAT verification against test scenarios' },
|
|
49
|
+
{ value: 'live-uat', label: '/live-uat', description: 'Run live UAT by interacting with the running application' },
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'Generation',
|
|
54
|
+
commands: [
|
|
55
|
+
{ value: 'generate-prd', label: '/generate-prd', description: 'Generate a Product Requirements Document' },
|
|
56
|
+
{ value: 'generate-sdd', label: '/generate-sdd', description: 'Generate a Software Design Document from the codebase' },
|
|
57
|
+
{ value: 'generate-uat', label: '/generate-uat', description: 'Generate UAT test scenarios for the project' },
|
|
58
|
+
{ value: 'optimize-claude-md', label: '/optimize-claude-md', description: 'Analyze and optimize the CLAUDE.md file' },
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: 'Session',
|
|
63
|
+
commands: [
|
|
64
|
+
{ value: 'save-session', label: '/save-session', description: 'Save current session state for later' },
|
|
65
|
+
{ value: 'resume-session', label: '/resume-session', description: 'Load a saved session and pick up where you left off' },
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: 'Project Tools',
|
|
70
|
+
commands: [
|
|
71
|
+
{ value: 'doctor', label: 'Diagnose project', description: 'Find and fix issues like dead code, flaky tests, oversized files' },
|
|
72
|
+
{ value: 'ci', label: 'CI health check', description: 'Non-interactive health checks for CI/CD pipelines' },
|
|
73
|
+
{ value: 'update', label: 'Check for updates', description: 'See if a newer version of DevForge is available' },
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
// Commands that are Claude Code slash commands (run inside Claude Code, not the terminal)
|
|
79
|
+
const CLAUDE_COMMANDS = new Set([
|
|
80
|
+
'status', 'next', 'done', 'help', 'plan', 'tdd', 'build-fix', 'build-ui',
|
|
81
|
+
'code-review', 'simplify', 'verify-all', 'full-audit', 'audit-spec', 'generate-sdd',
|
|
82
|
+
'audit-wiring', 'audit-security', 'verify-intent', 'pre-pr', 'run-uat',
|
|
83
|
+
'live-uat', 'generate-prd', 'generate-uat', 'optimize-claude-md',
|
|
84
|
+
'save-session', 'resume-session',
|
|
85
|
+
]);
|
|
86
|
+
|
|
87
|
+
export async function showInteractiveMenu() {
|
|
88
|
+
console.log('');
|
|
89
|
+
console.log(` ${chalk.bold.cyan('DevForge')} ${chalk.dim('AI-first project scaffolding')}`);
|
|
90
|
+
console.log('');
|
|
91
|
+
|
|
92
|
+
// Build choices with separators for groups
|
|
93
|
+
const choices = [];
|
|
94
|
+
let recommendation = null;
|
|
95
|
+
|
|
96
|
+
// Detect recommendation based on project state
|
|
97
|
+
try {
|
|
98
|
+
if (!fs.existsSync('package.json') && !fs.existsSync('pyproject.toml')) {
|
|
99
|
+
recommendation = 'new';
|
|
100
|
+
} else if (!fs.existsSync('.claude')) {
|
|
101
|
+
recommendation = 'init';
|
|
102
|
+
}
|
|
103
|
+
} catch { /* ignore */ }
|
|
104
|
+
|
|
105
|
+
for (const group of COMMAND_GROUPS) {
|
|
106
|
+
choices.push(new Separator(chalk.dim(` ${group.name}`)));
|
|
107
|
+
for (const cmd of group.commands) {
|
|
108
|
+
const isRecommended = cmd.value === recommendation;
|
|
109
|
+
const badge = isRecommended ? chalk.green(' recommended') : '';
|
|
110
|
+
const label = CLAUDE_COMMANDS.has(cmd.value)
|
|
111
|
+
? chalk.cyan(cmd.label)
|
|
112
|
+
: chalk.bold(cmd.label);
|
|
113
|
+
|
|
114
|
+
choices.push({
|
|
115
|
+
value: cmd.value,
|
|
116
|
+
name: `${label}${badge} ${chalk.dim(cmd.description)}`,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const selected = await select({
|
|
122
|
+
message: 'What would you like to do?',
|
|
123
|
+
choices,
|
|
124
|
+
pageSize: 15,
|
|
125
|
+
loop: false,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
return selected;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export async function handleMenuSelection(selected) {
|
|
132
|
+
// CLI commands — execute directly
|
|
133
|
+
if (selected === 'new') {
|
|
134
|
+
const name = await input({ message: 'Project name:' });
|
|
135
|
+
if (!name) return;
|
|
136
|
+
const { runNew } = await import('./index.js');
|
|
137
|
+
await runNew(name);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (selected === 'init') {
|
|
142
|
+
const { runInit } = await import('./init-mode.js');
|
|
143
|
+
await runInit(process.cwd());
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (selected === 'doctor') {
|
|
148
|
+
const { runDoctor } = await import('./doctor.js');
|
|
149
|
+
await runDoctor(process.cwd());
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (selected === 'ci') {
|
|
154
|
+
const { runCI } = await import('./ci-mode.js');
|
|
155
|
+
await runCI(process.cwd());
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (selected === 'update') {
|
|
160
|
+
const { runUpdate } = await import('./update.js');
|
|
161
|
+
await runUpdate();
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Claude Code commands — show how to run them
|
|
166
|
+
if (CLAUDE_COMMANDS.has(selected)) {
|
|
167
|
+
console.log('');
|
|
168
|
+
console.log(` ${chalk.cyan('/')}${chalk.bold.cyan(selected)} is a Claude Code command.`);
|
|
169
|
+
console.log('');
|
|
170
|
+
console.log(` To use it, open ${chalk.bold('Claude Code')} in your project and type:`);
|
|
171
|
+
console.log(` ${chalk.green(`/${selected}`)}`);
|
|
172
|
+
console.log('');
|
|
173
|
+
console.log(` ${chalk.dim('These commands work inside Claude Code (the AI assistant).')}`);
|
|
174
|
+
console.log(` ${chalk.dim('They run agents that analyze, review, and modify your code.')}`);
|
|
175
|
+
console.log('');
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
}
|
package/src/prompts.js
CHANGED
|
@@ -21,19 +21,12 @@ const LANGUAGE_CHOICES = [
|
|
|
21
21
|
{ value: 'rust', name: 'Rust' },
|
|
22
22
|
];
|
|
23
23
|
|
|
24
|
-
const DEPLOYMENT_CHOICES = [
|
|
25
|
-
{ value: 'docker', name: 'Docker / Docker Compose' },
|
|
26
|
-
{ value: 'vercel', name: 'Vercel' },
|
|
27
|
-
{ value: 'aws', name: 'AWS' },
|
|
28
|
-
{ value: 'gcp', name: 'Google Cloud' },
|
|
29
|
-
];
|
|
30
|
-
|
|
31
24
|
export async function askNewMode() {
|
|
32
25
|
const mode = await select({
|
|
33
26
|
message: 'How would you like to start?',
|
|
34
27
|
choices: [
|
|
35
28
|
{ value: 'guided', name: '💬 Describe what you want to build (recommended for beginners)' },
|
|
36
|
-
{ value: 'developer', name: '⚡ I know my stack
|
|
29
|
+
{ value: 'developer', name: '⚡ I know my stack, let me pick (for developers)' },
|
|
37
30
|
],
|
|
38
31
|
});
|
|
39
32
|
return mode;
|
|
@@ -41,7 +34,7 @@ export async function askNewMode() {
|
|
|
41
34
|
|
|
42
35
|
export async function askDescription() {
|
|
43
36
|
const description = await input({
|
|
44
|
-
message: 'Tell me about what you want to build.\n Don\'t worry about technical details
|
|
37
|
+
message: 'Tell me about what you want to build.\n Don\'t worry about technical details. Just describe it like you\'re explaining it to a friend.\n\n ',
|
|
45
38
|
});
|
|
46
39
|
return description;
|
|
47
40
|
}
|
|
@@ -57,8 +50,8 @@ export async function askGuidedConfirm() {
|
|
|
57
50
|
const choice = await select({
|
|
58
51
|
message: 'Sound right?',
|
|
59
52
|
choices: [
|
|
60
|
-
{ value: 'yes', name: 'Yes
|
|
61
|
-
{ value: 'adjust', name: 'No
|
|
53
|
+
{ value: 'yes', name: 'Yes, create it!' },
|
|
54
|
+
{ value: 'adjust', name: 'No, let me adjust' },
|
|
62
55
|
],
|
|
63
56
|
});
|
|
64
57
|
return choice;
|
|
@@ -88,7 +81,7 @@ export async function askServiceType() {
|
|
|
88
81
|
export async function askRefinements(serviceType) {
|
|
89
82
|
const refinements = {};
|
|
90
83
|
|
|
91
|
-
// Q1: Language preference
|
|
84
|
+
// Q1: Language preference. Adapt choices based on service type
|
|
92
85
|
if (['api_service', 'cli_tool', 'microservice'].includes(serviceType)) {
|
|
93
86
|
refinements.language = await select({
|
|
94
87
|
message: 'Language preference?',
|
package/src/recommender.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
|
|
3
|
-
export const SUPPORTED_STACKS = [
|
|
3
|
+
export const SUPPORTED_STACKS = [
|
|
4
|
+
'nextjs-fullstack', 'fastapi-backend', 'polyglot-fullstack',
|
|
5
|
+
'react-express', 'remix-fullstack', 'hono-api',
|
|
6
|
+
];
|
|
4
7
|
|
|
5
8
|
export function recommend(serviceType, refinements) {
|
|
6
9
|
const config = {
|
|
@@ -24,19 +27,25 @@ export function recommend(serviceType, refinements) {
|
|
|
24
27
|
|
|
25
28
|
switch (serviceType) {
|
|
26
29
|
case 'web_app':
|
|
30
|
+
if (refinements.framework === 'remix') {
|
|
31
|
+
return buildRemixFullstack(config, refinements);
|
|
32
|
+
}
|
|
27
33
|
return buildNextjsFullstack(config, refinements);
|
|
28
34
|
|
|
29
35
|
case 'full_stack':
|
|
30
36
|
if (lang === 'python') {
|
|
31
37
|
return buildPolyglotFullstack(config, refinements);
|
|
32
38
|
}
|
|
39
|
+
if (refinements.backend === 'express') {
|
|
40
|
+
return buildReactExpress(config, refinements);
|
|
41
|
+
}
|
|
33
42
|
return buildNextjsFullstack(config, refinements);
|
|
34
43
|
|
|
35
44
|
case 'api_service':
|
|
36
45
|
if (lang === 'python') {
|
|
37
46
|
return buildFastapiBackend(config, refinements);
|
|
38
47
|
}
|
|
39
|
-
return
|
|
48
|
+
return buildHonoApi(config, refinements);
|
|
40
49
|
|
|
41
50
|
case 'ai_service':
|
|
42
51
|
if (lang === 'python') {
|
|
@@ -50,10 +59,10 @@ export function recommend(serviceType, refinements) {
|
|
|
50
59
|
case 'extension':
|
|
51
60
|
case 'microservice':
|
|
52
61
|
case 'describe':
|
|
53
|
-
return unsupported(serviceType
|
|
62
|
+
return unsupported(serviceType);
|
|
54
63
|
|
|
55
64
|
default:
|
|
56
|
-
return unsupported(serviceType
|
|
65
|
+
return unsupported(serviceType);
|
|
57
66
|
}
|
|
58
67
|
}
|
|
59
68
|
|
|
@@ -71,6 +80,7 @@ function buildNextjsFullstack(config, refinements) {
|
|
|
71
80
|
config.templateModules = [
|
|
72
81
|
{ path: 'base', prefix: '' },
|
|
73
82
|
{ path: 'frontend/nextjs', prefix: '' },
|
|
83
|
+
{ path: 'docs-portal/nextjs', prefix: '' },
|
|
74
84
|
{ path: 'database/prisma-postgres', prefix: '' },
|
|
75
85
|
{ path: 'testing/vitest', prefix: '' },
|
|
76
86
|
{ path: 'testing/playwright', prefix: '' },
|
|
@@ -82,6 +92,13 @@ function buildNextjsFullstack(config, refinements) {
|
|
|
82
92
|
config.templateModules.push({ path: 'auth/nextauth', prefix: '' });
|
|
83
93
|
}
|
|
84
94
|
|
|
95
|
+
config.templateModules.push({ path: 'chainproof/base', prefix: '' });
|
|
96
|
+
config.templateModules.push({ path: 'chainproof/nextjs', prefix: '' });
|
|
97
|
+
|
|
98
|
+
if (refinements.ai) {
|
|
99
|
+
config.templateModules.push({ path: 'ai/guardrails-ts', prefix: '' });
|
|
100
|
+
}
|
|
101
|
+
|
|
85
102
|
return config;
|
|
86
103
|
}
|
|
87
104
|
|
|
@@ -99,6 +116,7 @@ function buildFastapiBackend(config, refinements) {
|
|
|
99
116
|
config.templateModules = [
|
|
100
117
|
{ path: 'base', prefix: '' },
|
|
101
118
|
{ path: 'backend/fastapi', prefix: '' },
|
|
119
|
+
{ path: 'docs-portal/fastapi', prefix: '' },
|
|
102
120
|
{ path: 'database/sqlalchemy-postgres', prefix: '' },
|
|
103
121
|
{ path: 'testing/pytest', prefix: '' },
|
|
104
122
|
{ path: 'infra/docker-compose', prefix: '' },
|
|
@@ -109,6 +127,13 @@ function buildFastapiBackend(config, refinements) {
|
|
|
109
127
|
config.templateModules.push({ path: 'auth/jwt-custom', prefix: '' });
|
|
110
128
|
}
|
|
111
129
|
|
|
130
|
+
config.templateModules.push({ path: 'chainproof/base', prefix: '' });
|
|
131
|
+
config.templateModules.push({ path: 'chainproof/fastapi', prefix: '' });
|
|
132
|
+
|
|
133
|
+
if (refinements.ai) {
|
|
134
|
+
config.templateModules.push({ path: 'ai/guardrails-py', prefix: '' });
|
|
135
|
+
}
|
|
136
|
+
|
|
112
137
|
return config;
|
|
113
138
|
}
|
|
114
139
|
|
|
@@ -127,6 +152,7 @@ function buildPolyglotFullstack(config, refinements) {
|
|
|
127
152
|
config.templateModules = [
|
|
128
153
|
{ path: 'base', prefix: '' },
|
|
129
154
|
{ path: 'frontend/nextjs', prefix: 'frontend' },
|
|
155
|
+
{ path: 'docs-portal/nextjs', prefix: 'frontend' },
|
|
130
156
|
{ path: 'backend/fastapi', prefix: '' },
|
|
131
157
|
{ path: 'database/prisma-postgres', prefix: 'frontend' },
|
|
132
158
|
{ path: 'database/sqlalchemy-postgres', prefix: '' },
|
|
@@ -142,14 +168,111 @@ function buildPolyglotFullstack(config, refinements) {
|
|
|
142
168
|
config.templateModules.push({ path: 'auth/jwt-custom', prefix: '' });
|
|
143
169
|
}
|
|
144
170
|
|
|
171
|
+
config.templateModules.push({ path: 'chainproof/base', prefix: '' });
|
|
172
|
+
config.templateModules.push({ path: 'chainproof/polyglot', prefix: '' });
|
|
173
|
+
|
|
174
|
+
if (refinements.ai) {
|
|
175
|
+
config.templateModules.push({ path: 'ai/guardrails-ts', prefix: 'frontend' });
|
|
176
|
+
config.templateModules.push({ path: 'ai/guardrails-py', prefix: '' });
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return config;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function buildReactExpress(config, refinements) {
|
|
183
|
+
config.stackId = 'react-express';
|
|
184
|
+
config.frontend = { framework: 'react', language: 'typescript', styling: 'tailwind', ui: null };
|
|
185
|
+
config.backend = { framework: 'express', language: 'typescript', orm: 'prisma' };
|
|
186
|
+
config.database = { type: 'postgresql', orm: 'prisma' };
|
|
187
|
+
config.testing = { unit: 'vitest', e2e: null, backend: 'vitest' };
|
|
188
|
+
|
|
189
|
+
if (refinements.auth) {
|
|
190
|
+
config.auth = 'jwt-custom';
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
config.templateModules = [
|
|
194
|
+
{ path: 'base', prefix: '' },
|
|
195
|
+
{ path: 'frontend/react', prefix: 'frontend' },
|
|
196
|
+
{ path: 'backend/express', prefix: 'backend' },
|
|
197
|
+
{ path: 'database/prisma-postgres', prefix: 'backend' },
|
|
198
|
+
{ path: 'testing/vitest', prefix: 'frontend' },
|
|
199
|
+
{ path: 'testing/vitest', prefix: 'backend' },
|
|
200
|
+
{ path: 'infra/docker-compose', prefix: '' },
|
|
201
|
+
{ path: 'infra/github-actions', prefix: '' },
|
|
202
|
+
];
|
|
203
|
+
|
|
204
|
+
config.templateModules.push({ path: 'chainproof/base', prefix: '' });
|
|
205
|
+
|
|
206
|
+
if (refinements.ai) {
|
|
207
|
+
config.templateModules.push({ path: 'ai/guardrails-ts', prefix: 'backend' });
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return config;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function buildRemixFullstack(config, refinements) {
|
|
214
|
+
config.stackId = 'remix-fullstack';
|
|
215
|
+
config.frontend = { framework: 'remix', language: 'typescript', styling: 'tailwind', ui: null };
|
|
216
|
+
config.backend = { framework: 'remix', language: 'typescript', orm: 'prisma' };
|
|
217
|
+
config.database = { type: 'postgresql', orm: 'prisma' };
|
|
218
|
+
config.testing = { unit: 'vitest', e2e: null, backend: null };
|
|
219
|
+
|
|
220
|
+
if (refinements.auth) {
|
|
221
|
+
config.auth = 'nextauth';
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
config.templateModules = [
|
|
225
|
+
{ path: 'base', prefix: '' },
|
|
226
|
+
{ path: 'frontend/remix', prefix: '' },
|
|
227
|
+
{ path: 'database/prisma-postgres', prefix: '' },
|
|
228
|
+
{ path: 'testing/vitest', prefix: '' },
|
|
229
|
+
{ path: 'infra/docker-compose', prefix: '' },
|
|
230
|
+
{ path: 'infra/github-actions', prefix: '' },
|
|
231
|
+
];
|
|
232
|
+
|
|
233
|
+
config.templateModules.push({ path: 'chainproof/base', prefix: '' });
|
|
234
|
+
|
|
235
|
+
if (refinements.ai) {
|
|
236
|
+
config.templateModules.push({ path: 'ai/guardrails-ts', prefix: '' });
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return config;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function buildHonoApi(config, refinements) {
|
|
243
|
+
config.stackId = 'hono-api';
|
|
244
|
+
config.frontend = null;
|
|
245
|
+
config.backend = { framework: 'hono', language: 'typescript', orm: 'prisma' };
|
|
246
|
+
config.database = { type: 'postgresql', orm: 'prisma' };
|
|
247
|
+
config.testing = { unit: 'vitest', e2e: null, backend: 'vitest' };
|
|
248
|
+
|
|
249
|
+
if (refinements.auth) {
|
|
250
|
+
config.auth = 'jwt-custom';
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
config.templateModules = [
|
|
254
|
+
{ path: 'base', prefix: '' },
|
|
255
|
+
{ path: 'backend/hono', prefix: '' },
|
|
256
|
+
{ path: 'database/prisma-postgres', prefix: '' },
|
|
257
|
+
{ path: 'testing/vitest', prefix: '' },
|
|
258
|
+
{ path: 'infra/docker-compose', prefix: '' },
|
|
259
|
+
{ path: 'infra/github-actions', prefix: '' },
|
|
260
|
+
];
|
|
261
|
+
|
|
262
|
+
config.templateModules.push({ path: 'chainproof/base', prefix: '' });
|
|
263
|
+
|
|
264
|
+
if (refinements.ai) {
|
|
265
|
+
config.templateModules.push({ path: 'ai/guardrails-ts', prefix: '' });
|
|
266
|
+
}
|
|
267
|
+
|
|
145
268
|
return config;
|
|
146
269
|
}
|
|
147
270
|
|
|
148
|
-
function unsupported(serviceType
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
: `"${serviceType}" is not yet supported in V1. Supported: web_app,
|
|
152
|
-
|
|
271
|
+
function unsupported(serviceType) {
|
|
272
|
+
return {
|
|
273
|
+
supported: false,
|
|
274
|
+
message: `"${serviceType}" is not yet supported in V1. Supported: web_app, full_stack, api_service, ai_service`,
|
|
275
|
+
};
|
|
153
276
|
}
|
|
154
277
|
|
|
155
278
|
export function formatStackSummary(config) {
|
|
@@ -160,7 +283,8 @@ export function formatStackSummary(config) {
|
|
|
160
283
|
const lines = [];
|
|
161
284
|
|
|
162
285
|
if (config.frontend) {
|
|
163
|
-
|
|
286
|
+
const uiPart = config.frontend.ui ? ` + ${config.frontend.ui}` : '';
|
|
287
|
+
lines.push(` ${chalk.bold('Frontend:')} ${config.frontend.framework} + ${config.frontend.language} + ${config.frontend.styling}${uiPart}`);
|
|
164
288
|
}
|
|
165
289
|
if (config.backend) {
|
|
166
290
|
lines.push(` ${chalk.bold('Backend:')} ${config.backend.framework} + ${config.backend.language} + ${config.backend.orm}`);
|
package/src/scanner.js
CHANGED
|
@@ -13,6 +13,7 @@ export function scanProject(projectDir) {
|
|
|
13
13
|
testing: detectTesting(projectDir),
|
|
14
14
|
deployment: detectDeployment(projectDir),
|
|
15
15
|
ai: detectAI(projectDir),
|
|
16
|
+
monorepo: detectMonorepo(projectDir),
|
|
16
17
|
|
|
17
18
|
infrastructure: {
|
|
18
19
|
hasClaudeMd: false,
|
|
@@ -22,6 +23,7 @@ export function scanProject(projectDir) {
|
|
|
22
23
|
hasCommands: false,
|
|
23
24
|
hasSkills: false,
|
|
24
25
|
hasUAT: false,
|
|
26
|
+
devforgeVersion: null,
|
|
25
27
|
},
|
|
26
28
|
|
|
27
29
|
files: {
|
|
@@ -88,7 +90,8 @@ function detectFrontend(projectDir) {
|
|
|
88
90
|
result.detected = true;
|
|
89
91
|
result.framework = 'nextjs';
|
|
90
92
|
result.language = fileExists(projectDir, 'tsconfig.json') ? 'typescript' : 'javascript';
|
|
91
|
-
|
|
93
|
+
// Next.js supports both src/app and app/ at root
|
|
94
|
+
result.directory = dirExists(projectDir, 'src') ? 'src' : '.';
|
|
92
95
|
return result;
|
|
93
96
|
}
|
|
94
97
|
|
|
@@ -307,6 +310,49 @@ function detectAI(projectDir) {
|
|
|
307
310
|
return false;
|
|
308
311
|
}
|
|
309
312
|
|
|
313
|
+
function detectMonorepo(projectDir) {
|
|
314
|
+
const result = { detected: false, type: null, workspaces: [] };
|
|
315
|
+
|
|
316
|
+
// npm/yarn workspaces
|
|
317
|
+
const pkg = readJsonSafe(projectDir, 'package.json');
|
|
318
|
+
if (pkg?.workspaces) {
|
|
319
|
+
result.detected = true;
|
|
320
|
+
result.type = 'npm-workspaces';
|
|
321
|
+
result.workspaces = Array.isArray(pkg.workspaces) ? pkg.workspaces : (pkg.workspaces.packages || []);
|
|
322
|
+
return result;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// pnpm workspaces
|
|
326
|
+
if (fileExists(projectDir, 'pnpm-workspace.yaml')) {
|
|
327
|
+
result.detected = true;
|
|
328
|
+
result.type = 'pnpm-workspaces';
|
|
329
|
+
return result;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Turborepo
|
|
333
|
+
if (fileExists(projectDir, 'turbo.json')) {
|
|
334
|
+
result.detected = true;
|
|
335
|
+
result.type = 'turborepo';
|
|
336
|
+
return result;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Nx
|
|
340
|
+
if (fileExists(projectDir, 'nx.json')) {
|
|
341
|
+
result.detected = true;
|
|
342
|
+
result.type = 'nx';
|
|
343
|
+
return result;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Lerna
|
|
347
|
+
if (fileExists(projectDir, 'lerna.json')) {
|
|
348
|
+
result.detected = true;
|
|
349
|
+
result.type = 'lerna';
|
|
350
|
+
return result;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return result;
|
|
354
|
+
}
|
|
355
|
+
|
|
310
356
|
function detectClaudeInfra(projectDir) {
|
|
311
357
|
const infra = {
|
|
312
358
|
hasClaudeMd: false,
|
|
@@ -316,6 +362,8 @@ function detectClaudeInfra(projectDir) {
|
|
|
316
362
|
hasCommands: false,
|
|
317
363
|
hasSkills: false,
|
|
318
364
|
hasUAT: false,
|
|
365
|
+
hasChainproof: false,
|
|
366
|
+
devforgeVersion: null,
|
|
319
367
|
};
|
|
320
368
|
|
|
321
369
|
if (fileExists(projectDir, 'CLAUDE.md')) {
|
|
@@ -329,6 +377,13 @@ function detectClaudeInfra(projectDir) {
|
|
|
329
377
|
infra.hasCommands = dirHasFiles(projectDir, '.claude/commands');
|
|
330
378
|
infra.hasSkills = dirHasFiles(projectDir, '.claude/skills');
|
|
331
379
|
infra.hasUAT = dirExists(projectDir, 'docs/uat');
|
|
380
|
+
infra.hasChainproof = dirExists(projectDir, '.chainproof');
|
|
381
|
+
|
|
382
|
+
// Check template version
|
|
383
|
+
const versionFile = readJsonSafe(projectDir, '.claude/.devforge-version');
|
|
384
|
+
if (versionFile?.version) {
|
|
385
|
+
infra.devforgeVersion = versionFile.version;
|
|
386
|
+
}
|
|
332
387
|
|
|
333
388
|
return infra;
|
|
334
389
|
}
|
|
@@ -362,7 +417,7 @@ function readJsonSafe(base, file) {
|
|
|
362
417
|
if (!fs.existsSync(p)) return null;
|
|
363
418
|
try {
|
|
364
419
|
return JSON.parse(fs.readFileSync(p, 'utf-8'));
|
|
365
|
-
} catch {
|
|
420
|
+
} catch { // Malformed JSON in config file
|
|
366
421
|
return null;
|
|
367
422
|
}
|
|
368
423
|
}
|