forgedev 1.2.0 → 1.4.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 +57 -10
- package/bin/chainproof.js +126 -0
- package/bin/devforge.js +1 -1
- package/package.json +25 -7
- package/src/chainproof-bridge.js +330 -0
- package/src/ci-mode.js +85 -0
- package/src/claude-configurator.js +171 -78
- package/src/cli.js +30 -7
- package/src/composer.js +242 -214
- 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 +76 -12
- package/src/menu.js +178 -0
- package/src/prompts.js +5 -12
- package/src/recommender.js +163 -30
- package/src/scanner.js +57 -2
- package/src/uat-generator.js +204 -189
- package/src/update-check.js +9 -4
- package/src/update.js +57 -13
- package/src/utils.js +162 -5
- 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/.gitignore.template +3 -0
- package/templates/base/docs/uat/UAT_TEMPLATE.md.template +1 -1
- 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 +19 -5
- package/templates/claude-code/agents/chief-of-staff.md +42 -8
- package/templates/claude-code/agents/code-quality-reviewer.md +14 -0
- package/templates/claude-code/agents/database-reviewer.md +15 -1
- 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 +36 -1
- package/templates/claude-code/agents/loop-operator.md +27 -13
- package/templates/claude-code/agents/planner.md +21 -7
- package/templates/claude-code/agents/product-strategist.md +24 -10
- 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 +14 -0
- package/templates/claude-code/agents/spec-validator.md +15 -1
- package/templates/claude-code/agents/tdd-guide.md +21 -7
- package/templates/claude-code/agents/uat-validator.md +14 -0
- package/templates/claude-code/claude-md/base.md +14 -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 +53 -31
- package/templates/claude-code/commands/fix-loop.md +211 -0
- package/templates/claude-code/commands/full-audit.md +36 -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 -40
- 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 +2 -2
- package/templates/claude-code/hooks/scripts/autofix-python.mjs +1 -1
- package/templates/claude-code/hooks/scripts/autofix-typescript.mjs +1 -1
- package/templates/claude-code/hooks/scripts/code-hygiene.mjs +293 -0
- 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 +5 -5
- package/templates/claude-code/skills/nextjs/SKILL.md +1 -1
- package/templates/claude-code/skills/playwright/SKILL.md +5 -5
- package/templates/claude-code/skills/security-api/SKILL.md +1 -1
- package/templates/claude-code/skills/security-web/SKILL.md +1 -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/__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 +3 -0
- package/templates/infra/k8s/k8s/deployment.yml.template +70 -0
- package/templates/infra/k8s/k8s/hpa.yml.template +24 -0
- package/templates/infra/k8s/k8s/ingress.yml.template +26 -0
- package/templates/infra/k8s/k8s/kustomization.yml.template +13 -0
- package/templates/infra/k8s/k8s/namespace.yml.template +4 -0
- package/templates/infra/k8s/k8s/networkpolicy.yml.template +41 -0
- package/templates/infra/k8s/k8s/secrets.yml.template +10 -0
- package/templates/infra/k8s/k8s/service.yml.template +15 -0
- package/templates/testing/load/k6/README.md.template +48 -0
- package/templates/testing/load/k6/load-test.js.template +57 -0
- package/docs/00-README.md +0 -310
- package/docs/01-universal-prompt-library.md +0 -1049
- package/docs/02-claude-code-mastery-playbook.md +0 -283
- package/docs/03-multi-agent-verification.md +0 -565
- package/docs/04-errata-and-verification-checklist.md +0 -284
- package/docs/05-universal-scaffolder-vision.md +0 -452
- package/docs/06-confidence-assessment-and-repo-prompt.md +0 -407
- package/docs/errata.md +0 -58
- package/docs/multi-agent-verification.md +0 -66
- package/docs/playbook.md +0 -95
- package/docs/prompt-library.md +0 -160
- package/docs/uat/UAT_CHECKLIST.csv +0 -9
- package/docs/uat/UAT_TEMPLATE.md +0 -163
- package/templates/claude-code/commands/done.md +0 -19
- /package/{docs/plans/.gitkeep → templates/docs-portal/fastapi/backend/app/portal/__init__.py} +0 -0
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,25 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
import { getStackMetadata } from './utils.js';
|
|
3
|
+
|
|
4
|
+
export const SUPPORTED_STACKS = Object.keys(
|
|
5
|
+
// Import the metadata keys to keep SUPPORTED_STACKS in sync automatically.
|
|
6
|
+
// getStackMetadata returns null for unknown keys, so we call it to get the map.
|
|
7
|
+
(() => {
|
|
8
|
+
const stacks = {};
|
|
9
|
+
for (const id of ['nextjs-fullstack', 'fastapi-backend', 'polyglot-fullstack', 'react-express', 'remix-fullstack', 'hono-api']) {
|
|
10
|
+
if (getStackMetadata(id)) stacks[id] = true;
|
|
11
|
+
}
|
|
12
|
+
return stacks;
|
|
13
|
+
})()
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
// Modules included in every stack
|
|
17
|
+
const UNIVERSAL_MODULES = [
|
|
18
|
+
{ path: 'testing/load', prefix: '' },
|
|
19
|
+
{ path: 'infra/docker-compose', prefix: '' },
|
|
20
|
+
{ path: 'infra/github-actions', prefix: '' },
|
|
21
|
+
{ path: 'infra/k8s', prefix: '' },
|
|
22
|
+
];
|
|
4
23
|
|
|
5
24
|
export function recommend(serviceType, refinements) {
|
|
6
25
|
const config = {
|
|
@@ -24,19 +43,25 @@ export function recommend(serviceType, refinements) {
|
|
|
24
43
|
|
|
25
44
|
switch (serviceType) {
|
|
26
45
|
case 'web_app':
|
|
46
|
+
if (refinements.framework === 'remix') {
|
|
47
|
+
return buildRemixFullstack(config, refinements);
|
|
48
|
+
}
|
|
27
49
|
return buildNextjsFullstack(config, refinements);
|
|
28
50
|
|
|
29
51
|
case 'full_stack':
|
|
30
52
|
if (lang === 'python') {
|
|
31
53
|
return buildPolyglotFullstack(config, refinements);
|
|
32
54
|
}
|
|
55
|
+
if (refinements.backend === 'express') {
|
|
56
|
+
return buildReactExpress(config, refinements);
|
|
57
|
+
}
|
|
33
58
|
return buildNextjsFullstack(config, refinements);
|
|
34
59
|
|
|
35
60
|
case 'api_service':
|
|
36
61
|
if (lang === 'python') {
|
|
37
62
|
return buildFastapiBackend(config, refinements);
|
|
38
63
|
}
|
|
39
|
-
return
|
|
64
|
+
return buildHonoApi(config, refinements);
|
|
40
65
|
|
|
41
66
|
case 'ai_service':
|
|
42
67
|
if (lang === 'python') {
|
|
@@ -50,13 +75,42 @@ export function recommend(serviceType, refinements) {
|
|
|
50
75
|
case 'extension':
|
|
51
76
|
case 'microservice':
|
|
52
77
|
case 'describe':
|
|
53
|
-
return unsupported(serviceType
|
|
78
|
+
return unsupported(serviceType);
|
|
54
79
|
|
|
55
80
|
default:
|
|
56
|
-
return unsupported(serviceType
|
|
81
|
+
return unsupported(serviceType);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ─── Shared tail logic for all builders ─────────────────────────────
|
|
86
|
+
// Appends chainproof, auth, and AI modules so each builder only defines
|
|
87
|
+
// its stack-specific modules.
|
|
88
|
+
|
|
89
|
+
function finalizeConfig(config, refinements, { authModules = [], chainproofModules = ['chainproof/base'], aiModules = [] } = {}) {
|
|
90
|
+
// Auth
|
|
91
|
+
if (refinements.auth && authModules.length > 0) {
|
|
92
|
+
for (const mod of authModules) {
|
|
93
|
+
config.templateModules.push(typeof mod === 'string' ? { path: mod, prefix: '' } : mod);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ChainProof (always)
|
|
98
|
+
for (const mod of chainproofModules) {
|
|
99
|
+
config.templateModules.push(typeof mod === 'string' ? { path: mod, prefix: '' } : mod);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// AI guardrails
|
|
103
|
+
if (refinements.ai && aiModules.length > 0) {
|
|
104
|
+
for (const mod of aiModules) {
|
|
105
|
+
config.templateModules.push(typeof mod === 'string' ? { path: mod, prefix: '' } : mod);
|
|
106
|
+
}
|
|
57
107
|
}
|
|
108
|
+
|
|
109
|
+
return config;
|
|
58
110
|
}
|
|
59
111
|
|
|
112
|
+
// ─── Stack builders ─────────────────────────────────────────────────
|
|
113
|
+
|
|
60
114
|
function buildNextjsFullstack(config, refinements) {
|
|
61
115
|
config.stackId = 'nextjs-fullstack';
|
|
62
116
|
config.frontend = { framework: 'nextjs', language: 'typescript', styling: 'tailwind', ui: 'shadcn' };
|
|
@@ -71,18 +125,18 @@ function buildNextjsFullstack(config, refinements) {
|
|
|
71
125
|
config.templateModules = [
|
|
72
126
|
{ path: 'base', prefix: '' },
|
|
73
127
|
{ path: 'frontend/nextjs', prefix: '' },
|
|
128
|
+
{ path: 'docs-portal/nextjs', prefix: '' },
|
|
74
129
|
{ path: 'database/prisma-postgres', prefix: '' },
|
|
75
130
|
{ path: 'testing/vitest', prefix: '' },
|
|
76
131
|
{ path: 'testing/playwright', prefix: '' },
|
|
77
|
-
|
|
78
|
-
{ path: 'infra/github-actions', prefix: '' },
|
|
132
|
+
...UNIVERSAL_MODULES,
|
|
79
133
|
];
|
|
80
134
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
135
|
+
return finalizeConfig(config, refinements, {
|
|
136
|
+
authModules: ['auth/nextauth'],
|
|
137
|
+
chainproofModules: ['chainproof/base', 'chainproof/nextjs'],
|
|
138
|
+
aiModules: ['ai/guardrails-ts'],
|
|
139
|
+
});
|
|
86
140
|
}
|
|
87
141
|
|
|
88
142
|
function buildFastapiBackend(config, refinements) {
|
|
@@ -99,17 +153,17 @@ function buildFastapiBackend(config, refinements) {
|
|
|
99
153
|
config.templateModules = [
|
|
100
154
|
{ path: 'base', prefix: '' },
|
|
101
155
|
{ path: 'backend/fastapi', prefix: '' },
|
|
156
|
+
{ path: 'docs-portal/fastapi', prefix: '' },
|
|
102
157
|
{ path: 'database/sqlalchemy-postgres', prefix: '' },
|
|
103
158
|
{ path: 'testing/pytest', prefix: '' },
|
|
104
|
-
|
|
105
|
-
{ path: 'infra/github-actions', prefix: '' },
|
|
159
|
+
...UNIVERSAL_MODULES,
|
|
106
160
|
];
|
|
107
161
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
162
|
+
return finalizeConfig(config, refinements, {
|
|
163
|
+
authModules: ['auth/jwt-custom'],
|
|
164
|
+
chainproofModules: ['chainproof/base', 'chainproof/fastapi'],
|
|
165
|
+
aiModules: ['ai/guardrails-py'],
|
|
166
|
+
});
|
|
113
167
|
}
|
|
114
168
|
|
|
115
169
|
function buildPolyglotFullstack(config, refinements) {
|
|
@@ -127,29 +181,107 @@ function buildPolyglotFullstack(config, refinements) {
|
|
|
127
181
|
config.templateModules = [
|
|
128
182
|
{ path: 'base', prefix: '' },
|
|
129
183
|
{ path: 'frontend/nextjs', prefix: 'frontend' },
|
|
184
|
+
{ path: 'docs-portal/nextjs', prefix: 'frontend' },
|
|
130
185
|
{ path: 'backend/fastapi', prefix: '' },
|
|
131
186
|
{ path: 'database/prisma-postgres', prefix: 'frontend' },
|
|
132
187
|
{ path: 'database/sqlalchemy-postgres', prefix: '' },
|
|
133
188
|
{ path: 'testing/vitest', prefix: 'frontend' },
|
|
134
189
|
{ path: 'testing/playwright', prefix: '' },
|
|
135
190
|
{ path: 'testing/pytest', prefix: '' },
|
|
136
|
-
|
|
137
|
-
{ path: 'infra/github-actions', prefix: '' },
|
|
191
|
+
...UNIVERSAL_MODULES,
|
|
138
192
|
];
|
|
139
193
|
|
|
194
|
+
return finalizeConfig(config, refinements, {
|
|
195
|
+
authModules: [{ path: 'auth/nextauth', prefix: 'frontend' }, 'auth/jwt-custom'],
|
|
196
|
+
chainproofModules: ['chainproof/base', 'chainproof/polyglot'],
|
|
197
|
+
aiModules: [{ path: 'ai/guardrails-ts', prefix: 'frontend' }, 'ai/guardrails-py'],
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function buildReactExpress(config, refinements) {
|
|
202
|
+
config.stackId = 'react-express';
|
|
203
|
+
config.frontend = { framework: 'react', language: 'typescript', styling: 'tailwind', ui: null };
|
|
204
|
+
config.backend = { framework: 'express', language: 'typescript', orm: 'prisma' };
|
|
205
|
+
config.database = { type: 'postgresql', orm: 'prisma' };
|
|
206
|
+
config.testing = { unit: 'vitest', e2e: null, backend: 'vitest' };
|
|
207
|
+
|
|
140
208
|
if (refinements.auth) {
|
|
141
|
-
config.
|
|
142
|
-
config.templateModules.push({ path: 'auth/jwt-custom', prefix: '' });
|
|
209
|
+
config.auth = 'jwt-custom';
|
|
143
210
|
}
|
|
144
211
|
|
|
145
|
-
|
|
212
|
+
config.templateModules = [
|
|
213
|
+
{ path: 'base', prefix: '' },
|
|
214
|
+
{ path: 'frontend/react', prefix: 'frontend' },
|
|
215
|
+
{ path: 'backend/express', prefix: 'backend' },
|
|
216
|
+
{ path: 'database/prisma-postgres', prefix: 'backend' },
|
|
217
|
+
{ path: 'testing/vitest', prefix: 'frontend' },
|
|
218
|
+
{ path: 'testing/vitest', prefix: 'backend' },
|
|
219
|
+
...UNIVERSAL_MODULES,
|
|
220
|
+
];
|
|
221
|
+
|
|
222
|
+
return finalizeConfig(config, refinements, {
|
|
223
|
+
chainproofModules: ['chainproof/base'],
|
|
224
|
+
aiModules: [{ path: 'ai/guardrails-ts', prefix: 'backend' }],
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function buildRemixFullstack(config, refinements) {
|
|
229
|
+
config.stackId = 'remix-fullstack';
|
|
230
|
+
config.frontend = { framework: 'remix', language: 'typescript', styling: 'tailwind', ui: null };
|
|
231
|
+
config.backend = { framework: 'remix', language: 'typescript', orm: 'prisma' };
|
|
232
|
+
config.database = { type: 'postgresql', orm: 'prisma' };
|
|
233
|
+
config.testing = { unit: 'vitest', e2e: null, backend: null };
|
|
234
|
+
|
|
235
|
+
if (refinements.auth) {
|
|
236
|
+
config.auth = 'remix-auth';
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
config.templateModules = [
|
|
240
|
+
{ path: 'base', prefix: '' },
|
|
241
|
+
{ path: 'frontend/remix', prefix: '' },
|
|
242
|
+
{ path: 'database/prisma-postgres', prefix: '' },
|
|
243
|
+
{ path: 'testing/vitest', prefix: '' },
|
|
244
|
+
...UNIVERSAL_MODULES,
|
|
245
|
+
];
|
|
246
|
+
|
|
247
|
+
// No auth template module — jwt-custom is Python-only, nextauth is Next.js-only.
|
|
248
|
+
// Remix auth scaffolding is planned for a future release.
|
|
249
|
+
return finalizeConfig(config, refinements, {
|
|
250
|
+
chainproofModules: ['chainproof/base'],
|
|
251
|
+
aiModules: ['ai/guardrails-ts'],
|
|
252
|
+
});
|
|
146
253
|
}
|
|
147
254
|
|
|
148
|
-
function
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
255
|
+
function buildHonoApi(config, refinements) {
|
|
256
|
+
config.stackId = 'hono-api';
|
|
257
|
+
config.frontend = null;
|
|
258
|
+
config.backend = { framework: 'hono', language: 'typescript', orm: 'prisma' };
|
|
259
|
+
config.database = { type: 'postgresql', orm: 'prisma' };
|
|
260
|
+
config.testing = { unit: 'vitest', e2e: null, backend: 'vitest' };
|
|
261
|
+
|
|
262
|
+
if (refinements.auth) {
|
|
263
|
+
config.auth = 'jwt-custom';
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
config.templateModules = [
|
|
267
|
+
{ path: 'base', prefix: '' },
|
|
268
|
+
{ path: 'backend/hono', prefix: '' },
|
|
269
|
+
{ path: 'database/prisma-postgres', prefix: '' },
|
|
270
|
+
{ path: 'testing/vitest', prefix: '' },
|
|
271
|
+
...UNIVERSAL_MODULES,
|
|
272
|
+
];
|
|
273
|
+
|
|
274
|
+
return finalizeConfig(config, refinements, {
|
|
275
|
+
chainproofModules: ['chainproof/base'],
|
|
276
|
+
aiModules: ['ai/guardrails-ts'],
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function unsupported(serviceType) {
|
|
281
|
+
return {
|
|
282
|
+
supported: false,
|
|
283
|
+
message: `"${serviceType}" is not yet supported in V1. Supported: web_app, full_stack, api_service, ai_service`,
|
|
284
|
+
};
|
|
153
285
|
}
|
|
154
286
|
|
|
155
287
|
export function formatStackSummary(config) {
|
|
@@ -160,7 +292,8 @@ export function formatStackSummary(config) {
|
|
|
160
292
|
const lines = [];
|
|
161
293
|
|
|
162
294
|
if (config.frontend) {
|
|
163
|
-
|
|
295
|
+
const uiPart = config.frontend.ui ? ` + ${config.frontend.ui}` : '';
|
|
296
|
+
lines.push(` ${chalk.bold('Frontend:')} ${config.frontend.framework} + ${config.frontend.language} + ${config.frontend.styling}${uiPart}`);
|
|
164
297
|
}
|
|
165
298
|
if (config.backend) {
|
|
166
299
|
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
|
}
|