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.
Files changed (167) hide show
  1. package/README.md +58 -10
  2. package/bin/chainproof.js +126 -0
  3. package/bin/devforge.js +2 -1
  4. package/package.json +33 -7
  5. package/src/chainproof-bridge.js +330 -0
  6. package/src/ci-mode.js +85 -0
  7. package/src/claude-configurator.js +87 -49
  8. package/src/cli.js +35 -12
  9. package/src/composer.js +159 -34
  10. package/src/doctor-checks-chainproof.js +106 -0
  11. package/src/doctor-checks.js +39 -20
  12. package/src/doctor-prompts.js +9 -9
  13. package/src/doctor.js +37 -4
  14. package/src/guided.js +3 -3
  15. package/src/index.js +31 -10
  16. package/src/init-mode.js +64 -11
  17. package/src/menu.js +178 -0
  18. package/src/prompts.js +5 -12
  19. package/src/recommender.js +134 -10
  20. package/src/scanner.js +57 -2
  21. package/src/uat-generator.js +204 -189
  22. package/src/update-check.js +9 -4
  23. package/src/update.js +1 -1
  24. package/src/utils.js +65 -6
  25. package/templates/ai/guardrails-py/backend/app/ai/__init__.py +29 -0
  26. package/templates/ai/guardrails-py/backend/app/ai/audit_log.py +133 -0
  27. package/templates/ai/guardrails-py/backend/app/ai/client.py.template +323 -0
  28. package/templates/ai/guardrails-py/backend/app/ai/health.py.template +157 -0
  29. package/templates/ai/guardrails-py/backend/app/ai/input_guard.py +98 -0
  30. package/templates/ai/guardrails-ts/src/lib/ai/audit-log.ts.template +164 -0
  31. package/templates/ai/guardrails-ts/src/lib/ai/client.ts.template +403 -0
  32. package/templates/ai/guardrails-ts/src/lib/ai/health.ts.template +165 -0
  33. package/templates/ai/guardrails-ts/src/lib/ai/index.ts.template +17 -0
  34. package/templates/ai/guardrails-ts/src/lib/ai/input-guard.ts.template +124 -0
  35. package/templates/auth/nextauth/src/lib/auth.ts.template +12 -7
  36. package/templates/backend/express/Dockerfile.template +18 -0
  37. package/templates/backend/express/package.json.template +33 -0
  38. package/templates/backend/express/src/index.ts.template +34 -0
  39. package/templates/backend/express/src/routes/health.ts.template +27 -0
  40. package/templates/backend/express/tsconfig.json +17 -0
  41. package/templates/backend/fastapi/backend/Dockerfile.template +5 -0
  42. package/templates/backend/fastapi/backend/app/api/health.py.template +1 -1
  43. package/templates/backend/fastapi/backend/app/core/config.py.template +1 -1
  44. package/templates/backend/fastapi/backend/app/core/errors.py +1 -1
  45. package/templates/backend/fastapi/backend/app/main.py.template +3 -1
  46. package/templates/backend/fastapi/backend/requirements.txt.template +2 -0
  47. package/templates/backend/hono/Dockerfile.template +18 -0
  48. package/templates/backend/hono/package.json.template +31 -0
  49. package/templates/backend/hono/src/index.ts.template +32 -0
  50. package/templates/backend/hono/src/routes/health.ts.template +27 -0
  51. package/templates/backend/hono/tsconfig.json +18 -0
  52. package/templates/base/docs/plans/.gitkeep +0 -0
  53. package/templates/base/docs/uat/UAT_CHECKLIST.csv.template +2 -0
  54. package/templates/base/docs/uat/UAT_TEMPLATE.md.template +22 -0
  55. package/templates/chainproof/base/.chainproof/config.json.template +11 -0
  56. package/templates/chainproof/base/.chainproof/mcp-server.mjs +310 -0
  57. package/templates/chainproof/base/.mcp.json +9 -0
  58. package/templates/chainproof/fastapi/.chainproof/middleware.json.template +14 -0
  59. package/templates/chainproof/nextjs/.chainproof/hooks.json.template +19 -0
  60. package/templates/chainproof/polyglot/.chainproof/config.json.template +21 -0
  61. package/templates/claude-code/agents/architect.md +25 -11
  62. package/templates/claude-code/agents/build-error-resolver.md +22 -7
  63. package/templates/claude-code/agents/chief-of-staff.md +42 -8
  64. package/templates/claude-code/agents/code-quality-reviewer.md +15 -1
  65. package/templates/claude-code/agents/database-reviewer.md +16 -2
  66. package/templates/claude-code/agents/deep-reviewer.md +191 -0
  67. package/templates/claude-code/agents/doc-updater.md +19 -5
  68. package/templates/claude-code/agents/docs-lookup.md +19 -5
  69. package/templates/claude-code/agents/e2e-runner.md +26 -12
  70. package/templates/claude-code/agents/enforcement-gate.md +102 -0
  71. package/templates/claude-code/agents/frontend-builder.md +188 -0
  72. package/templates/claude-code/agents/harness-optimizer.md +61 -0
  73. package/templates/claude-code/agents/loop-operator.md +27 -12
  74. package/templates/claude-code/agents/planner.md +21 -7
  75. package/templates/claude-code/agents/product-strategist.md +138 -0
  76. package/templates/claude-code/agents/production-readiness.md +14 -0
  77. package/templates/claude-code/agents/prompt-auditor.md +115 -0
  78. package/templates/claude-code/agents/refactor-cleaner.md +22 -8
  79. package/templates/claude-code/agents/security-reviewer.md +15 -0
  80. package/templates/claude-code/agents/spec-validator.md +45 -1
  81. package/templates/claude-code/agents/tdd-guide.md +21 -7
  82. package/templates/claude-code/agents/uat-validator.md +18 -0
  83. package/templates/claude-code/claude-md/base.md +15 -7
  84. package/templates/claude-code/claude-md/fastapi.md +8 -8
  85. package/templates/claude-code/claude-md/fullstack.md +6 -6
  86. package/templates/claude-code/claude-md/hono.md +18 -0
  87. package/templates/claude-code/claude-md/nextjs.md +5 -5
  88. package/templates/claude-code/claude-md/remix.md +18 -0
  89. package/templates/claude-code/commands/audit-security.md +14 -0
  90. package/templates/claude-code/commands/audit-spec.md +14 -0
  91. package/templates/claude-code/commands/audit-wiring.md +14 -0
  92. package/templates/claude-code/commands/build-fix.md +28 -0
  93. package/templates/claude-code/commands/build-ui.md +59 -0
  94. package/templates/claude-code/commands/code-review.md +54 -26
  95. package/templates/claude-code/commands/fix-loop.md +211 -0
  96. package/templates/claude-code/commands/full-audit.md +37 -8
  97. package/templates/claude-code/commands/generate-prd.md +1 -1
  98. package/templates/claude-code/commands/generate-sdd.md +74 -0
  99. package/templates/claude-code/commands/generate-uat.md +107 -35
  100. package/templates/claude-code/commands/help.md +68 -0
  101. package/templates/claude-code/commands/live-uat.md +268 -0
  102. package/templates/claude-code/commands/optimize-claude-md.md +15 -1
  103. package/templates/claude-code/commands/plan.md +3 -3
  104. package/templates/claude-code/commands/pre-pr.md +57 -19
  105. package/templates/claude-code/commands/product-strategist.md +21 -0
  106. package/templates/claude-code/commands/resume-session.md +10 -10
  107. package/templates/claude-code/commands/run-uat.md +59 -2
  108. package/templates/claude-code/commands/save-session.md +10 -10
  109. package/templates/claude-code/commands/simplify.md +36 -0
  110. package/templates/claude-code/commands/tdd.md +17 -18
  111. package/templates/claude-code/commands/verify-all.md +24 -0
  112. package/templates/claude-code/commands/verify-intent.md +55 -0
  113. package/templates/claude-code/commands/workflows.md +52 -37
  114. package/templates/claude-code/hooks/polyglot.json +10 -1
  115. package/templates/claude-code/hooks/python.json +10 -1
  116. package/templates/claude-code/hooks/scripts/autofix-polyglot.mjs +20 -10
  117. package/templates/claude-code/hooks/scripts/autofix-python.mjs +4 -5
  118. package/templates/claude-code/hooks/scripts/autofix-typescript.mjs +4 -4
  119. package/templates/claude-code/hooks/scripts/code-hygiene.mjs +293 -0
  120. package/templates/claude-code/hooks/scripts/guard-protected-files.mjs +2 -2
  121. package/templates/claude-code/hooks/scripts/pre-commit-gate.mjs +207 -0
  122. package/templates/claude-code/hooks/typescript.json +10 -1
  123. package/templates/claude-code/skills/ai-prompts/SKILL.md +119 -41
  124. package/templates/claude-code/skills/git-workflow/SKILL.md +6 -6
  125. package/templates/claude-code/skills/nextjs/SKILL.md +1 -1
  126. package/templates/claude-code/skills/playwright/SKILL.md +6 -5
  127. package/templates/claude-code/skills/security-api/SKILL.md +1 -1
  128. package/templates/claude-code/skills/security-web/SKILL.md +2 -1
  129. package/templates/claude-code/skills/testing-patterns/SKILL.md +9 -9
  130. package/templates/database/prisma-postgres/{.env.example → .env.example.template} +1 -0
  131. package/templates/database/sqlalchemy-postgres/{.env.example → .env.example.template} +1 -0
  132. package/templates/docs-portal/fastapi/backend/app/portal/__init__.py +0 -0
  133. package/templates/docs-portal/fastapi/backend/app/portal/__pycache__/docs_reader.cpython-314.pyc +0 -0
  134. package/templates/docs-portal/fastapi/backend/app/portal/docs_reader.py +201 -0
  135. package/templates/docs-portal/fastapi/backend/app/portal/html_renderer.py +229 -0
  136. package/templates/docs-portal/fastapi/backend/app/portal/router.py.template +35 -0
  137. package/templates/docs-portal/nextjs/src/app/portal/[category]/[slug]/page.tsx +81 -0
  138. package/templates/docs-portal/nextjs/src/app/portal/[category]/page.tsx +65 -0
  139. package/templates/docs-portal/nextjs/src/app/portal/layout.tsx.template +54 -0
  140. package/templates/docs-portal/nextjs/src/app/portal/page.tsx +85 -0
  141. package/templates/docs-portal/nextjs/src/components/portal/markdown-renderer.tsx +101 -0
  142. package/templates/docs-portal/nextjs/src/components/portal/mobile-portal-nav.tsx +81 -0
  143. package/templates/docs-portal/nextjs/src/components/portal/portal-nav.tsx +86 -0
  144. package/templates/docs-portal/nextjs/src/lib/docs.ts +139 -0
  145. package/templates/frontend/nextjs/package.json.template +3 -1
  146. package/templates/frontend/react/index.html.template +12 -0
  147. package/templates/frontend/react/package.json.template +34 -0
  148. package/templates/frontend/react/src/App.tsx.template +10 -0
  149. package/templates/frontend/react/src/index.css +1 -0
  150. package/templates/frontend/react/src/main.tsx +10 -0
  151. package/templates/frontend/react/tsconfig.json +17 -0
  152. package/templates/frontend/react/vite.config.ts.template +15 -0
  153. package/templates/frontend/react/vitest.config.ts +9 -0
  154. package/templates/frontend/remix/app/root.tsx.template +31 -0
  155. package/templates/frontend/remix/app/routes/_index.tsx.template +19 -0
  156. package/templates/frontend/remix/app/routes/api.health.ts.template +10 -0
  157. package/templates/frontend/remix/app/tailwind.css +1 -0
  158. package/templates/frontend/remix/package.json.template +39 -0
  159. package/templates/frontend/remix/tsconfig.json +18 -0
  160. package/templates/frontend/remix/vite.config.ts.template +7 -0
  161. package/templates/infra/github-actions/.github/workflows/ci.yml.template +52 -0
  162. package/templates/testing/pytest/backend/tests/__init__.py +0 -0
  163. package/templates/testing/pytest/backend/tests/conftest.py.template +11 -0
  164. package/templates/testing/pytest/backend/tests/test_health.py.template +10 -0
  165. package/templates/testing/vitest/vitest.config.ts.template +18 -0
  166. package/CLAUDE.md +0 -38
  167. 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 let me pick (for developers)' },
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 just describe it like you\'re explaining it to a friend.\n\n ',
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 create it!' },
61
- { value: 'adjust', name: 'No let me adjust' },
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 adapt choices based on service type
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?',
@@ -1,6 +1,9 @@
1
1
  import chalk from 'chalk';
2
2
 
3
- export const SUPPORTED_STACKS = ['nextjs-fullstack', 'fastapi-backend', 'polyglot-fullstack'];
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 unsupported(serviceType, lang, 'FastAPI (Python)');
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, lang);
62
+ return unsupported(serviceType);
54
63
 
55
64
  default:
56
- return unsupported(serviceType, lang);
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, lang, suggestion) {
149
- const msg = suggestion
150
- ? `"${serviceType}" with "${lang}" is not yet supported. Try: ${suggestion}`
151
- : `"${serviceType}" is not yet supported in V1. Supported: web_app, api_service (Python), full_stack, ai_service`;
152
- return { supported: false, message: msg };
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
- lines.push(` ${chalk.bold('Frontend:')} ${config.frontend.framework} + ${config.frontend.language} + ${config.frontend.styling} + ${config.frontend.ui}`);
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
- result.directory = 'src';
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
  }