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.
Files changed (183) hide show
  1. package/README.md +57 -10
  2. package/bin/chainproof.js +126 -0
  3. package/bin/devforge.js +1 -1
  4. package/package.json +25 -7
  5. package/src/chainproof-bridge.js +330 -0
  6. package/src/ci-mode.js +85 -0
  7. package/src/claude-configurator.js +171 -78
  8. package/src/cli.js +30 -7
  9. package/src/composer.js +242 -214
  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 +76 -12
  17. package/src/menu.js +178 -0
  18. package/src/prompts.js +5 -12
  19. package/src/recommender.js +163 -30
  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 +57 -13
  24. package/src/utils.js +162 -5
  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/.gitignore.template +3 -0
  53. package/templates/base/docs/uat/UAT_TEMPLATE.md.template +1 -1
  54. package/templates/chainproof/base/.chainproof/config.json.template +11 -0
  55. package/templates/chainproof/base/.chainproof/mcp-server.mjs +310 -0
  56. package/templates/chainproof/base/.mcp.json +9 -0
  57. package/templates/chainproof/fastapi/.chainproof/middleware.json.template +14 -0
  58. package/templates/chainproof/nextjs/.chainproof/hooks.json.template +19 -0
  59. package/templates/chainproof/polyglot/.chainproof/config.json.template +21 -0
  60. package/templates/claude-code/agents/architect.md +25 -11
  61. package/templates/claude-code/agents/build-error-resolver.md +19 -5
  62. package/templates/claude-code/agents/chief-of-staff.md +42 -8
  63. package/templates/claude-code/agents/code-quality-reviewer.md +14 -0
  64. package/templates/claude-code/agents/database-reviewer.md +15 -1
  65. package/templates/claude-code/agents/deep-reviewer.md +191 -0
  66. package/templates/claude-code/agents/doc-updater.md +19 -5
  67. package/templates/claude-code/agents/docs-lookup.md +19 -5
  68. package/templates/claude-code/agents/e2e-runner.md +26 -12
  69. package/templates/claude-code/agents/enforcement-gate.md +102 -0
  70. package/templates/claude-code/agents/frontend-builder.md +188 -0
  71. package/templates/claude-code/agents/harness-optimizer.md +36 -1
  72. package/templates/claude-code/agents/loop-operator.md +27 -13
  73. package/templates/claude-code/agents/planner.md +21 -7
  74. package/templates/claude-code/agents/product-strategist.md +24 -10
  75. package/templates/claude-code/agents/production-readiness.md +14 -0
  76. package/templates/claude-code/agents/prompt-auditor.md +115 -0
  77. package/templates/claude-code/agents/refactor-cleaner.md +22 -8
  78. package/templates/claude-code/agents/security-reviewer.md +14 -0
  79. package/templates/claude-code/agents/spec-validator.md +15 -1
  80. package/templates/claude-code/agents/tdd-guide.md +21 -7
  81. package/templates/claude-code/agents/uat-validator.md +14 -0
  82. package/templates/claude-code/claude-md/base.md +14 -7
  83. package/templates/claude-code/claude-md/fastapi.md +8 -8
  84. package/templates/claude-code/claude-md/fullstack.md +6 -6
  85. package/templates/claude-code/claude-md/hono.md +18 -0
  86. package/templates/claude-code/claude-md/nextjs.md +5 -5
  87. package/templates/claude-code/claude-md/remix.md +18 -0
  88. package/templates/claude-code/commands/audit-security.md +14 -0
  89. package/templates/claude-code/commands/audit-spec.md +14 -0
  90. package/templates/claude-code/commands/audit-wiring.md +14 -0
  91. package/templates/claude-code/commands/build-fix.md +28 -0
  92. package/templates/claude-code/commands/build-ui.md +59 -0
  93. package/templates/claude-code/commands/code-review.md +53 -31
  94. package/templates/claude-code/commands/fix-loop.md +211 -0
  95. package/templates/claude-code/commands/full-audit.md +36 -8
  96. package/templates/claude-code/commands/generate-prd.md +1 -1
  97. package/templates/claude-code/commands/generate-sdd.md +74 -0
  98. package/templates/claude-code/commands/generate-uat.md +107 -35
  99. package/templates/claude-code/commands/help.md +68 -0
  100. package/templates/claude-code/commands/live-uat.md +268 -0
  101. package/templates/claude-code/commands/optimize-claude-md.md +15 -1
  102. package/templates/claude-code/commands/plan.md +3 -3
  103. package/templates/claude-code/commands/pre-pr.md +57 -19
  104. package/templates/claude-code/commands/product-strategist.md +21 -0
  105. package/templates/claude-code/commands/resume-session.md +10 -10
  106. package/templates/claude-code/commands/run-uat.md +59 -2
  107. package/templates/claude-code/commands/save-session.md +10 -10
  108. package/templates/claude-code/commands/simplify.md +36 -0
  109. package/templates/claude-code/commands/tdd.md +17 -18
  110. package/templates/claude-code/commands/verify-all.md +24 -0
  111. package/templates/claude-code/commands/verify-intent.md +55 -0
  112. package/templates/claude-code/commands/workflows.md +52 -40
  113. package/templates/claude-code/hooks/polyglot.json +10 -1
  114. package/templates/claude-code/hooks/python.json +10 -1
  115. package/templates/claude-code/hooks/scripts/autofix-polyglot.mjs +2 -2
  116. package/templates/claude-code/hooks/scripts/autofix-python.mjs +1 -1
  117. package/templates/claude-code/hooks/scripts/autofix-typescript.mjs +1 -1
  118. package/templates/claude-code/hooks/scripts/code-hygiene.mjs +293 -0
  119. package/templates/claude-code/hooks/scripts/pre-commit-gate.mjs +207 -0
  120. package/templates/claude-code/hooks/typescript.json +10 -1
  121. package/templates/claude-code/skills/ai-prompts/SKILL.md +119 -41
  122. package/templates/claude-code/skills/git-workflow/SKILL.md +5 -5
  123. package/templates/claude-code/skills/nextjs/SKILL.md +1 -1
  124. package/templates/claude-code/skills/playwright/SKILL.md +5 -5
  125. package/templates/claude-code/skills/security-api/SKILL.md +1 -1
  126. package/templates/claude-code/skills/security-web/SKILL.md +1 -1
  127. package/templates/claude-code/skills/testing-patterns/SKILL.md +9 -9
  128. package/templates/database/prisma-postgres/{.env.example → .env.example.template} +1 -0
  129. package/templates/database/sqlalchemy-postgres/{.env.example → .env.example.template} +1 -0
  130. package/templates/docs-portal/fastapi/backend/app/portal/__pycache__/docs_reader.cpython-314.pyc +0 -0
  131. package/templates/docs-portal/fastapi/backend/app/portal/docs_reader.py +201 -0
  132. package/templates/docs-portal/fastapi/backend/app/portal/html_renderer.py +229 -0
  133. package/templates/docs-portal/fastapi/backend/app/portal/router.py.template +35 -0
  134. package/templates/docs-portal/nextjs/src/app/portal/[category]/[slug]/page.tsx +81 -0
  135. package/templates/docs-portal/nextjs/src/app/portal/[category]/page.tsx +65 -0
  136. package/templates/docs-portal/nextjs/src/app/portal/layout.tsx.template +54 -0
  137. package/templates/docs-portal/nextjs/src/app/portal/page.tsx +85 -0
  138. package/templates/docs-portal/nextjs/src/components/portal/markdown-renderer.tsx +101 -0
  139. package/templates/docs-portal/nextjs/src/components/portal/mobile-portal-nav.tsx +81 -0
  140. package/templates/docs-portal/nextjs/src/components/portal/portal-nav.tsx +86 -0
  141. package/templates/docs-portal/nextjs/src/lib/docs.ts +139 -0
  142. package/templates/frontend/nextjs/package.json.template +3 -1
  143. package/templates/frontend/react/index.html.template +12 -0
  144. package/templates/frontend/react/package.json.template +34 -0
  145. package/templates/frontend/react/src/App.tsx.template +10 -0
  146. package/templates/frontend/react/src/index.css +1 -0
  147. package/templates/frontend/react/src/main.tsx +10 -0
  148. package/templates/frontend/react/tsconfig.json +17 -0
  149. package/templates/frontend/react/vite.config.ts.template +15 -0
  150. package/templates/frontend/react/vitest.config.ts +9 -0
  151. package/templates/frontend/remix/app/root.tsx.template +31 -0
  152. package/templates/frontend/remix/app/routes/_index.tsx.template +19 -0
  153. package/templates/frontend/remix/app/routes/api.health.ts.template +10 -0
  154. package/templates/frontend/remix/app/tailwind.css +1 -0
  155. package/templates/frontend/remix/package.json.template +39 -0
  156. package/templates/frontend/remix/tsconfig.json +18 -0
  157. package/templates/frontend/remix/vite.config.ts.template +7 -0
  158. package/templates/infra/github-actions/.github/workflows/ci.yml.template +3 -0
  159. package/templates/infra/k8s/k8s/deployment.yml.template +70 -0
  160. package/templates/infra/k8s/k8s/hpa.yml.template +24 -0
  161. package/templates/infra/k8s/k8s/ingress.yml.template +26 -0
  162. package/templates/infra/k8s/k8s/kustomization.yml.template +13 -0
  163. package/templates/infra/k8s/k8s/namespace.yml.template +4 -0
  164. package/templates/infra/k8s/k8s/networkpolicy.yml.template +41 -0
  165. package/templates/infra/k8s/k8s/secrets.yml.template +10 -0
  166. package/templates/infra/k8s/k8s/service.yml.template +15 -0
  167. package/templates/testing/load/k6/README.md.template +48 -0
  168. package/templates/testing/load/k6/load-test.js.template +57 -0
  169. package/docs/00-README.md +0 -310
  170. package/docs/01-universal-prompt-library.md +0 -1049
  171. package/docs/02-claude-code-mastery-playbook.md +0 -283
  172. package/docs/03-multi-agent-verification.md +0 -565
  173. package/docs/04-errata-and-verification-checklist.md +0 -284
  174. package/docs/05-universal-scaffolder-vision.md +0 -452
  175. package/docs/06-confidence-assessment-and-repo-prompt.md +0 -407
  176. package/docs/errata.md +0 -58
  177. package/docs/multi-agent-verification.md +0 -66
  178. package/docs/playbook.md +0 -95
  179. package/docs/prompt-library.md +0 -160
  180. package/docs/uat/UAT_CHECKLIST.csv +0 -9
  181. package/docs/uat/UAT_TEMPLATE.md +0 -163
  182. package/templates/claude-code/commands/done.md +0 -19
  183. /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 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,25 @@
1
1
  import chalk from 'chalk';
2
-
3
- export const SUPPORTED_STACKS = ['nextjs-fullstack', 'fastapi-backend', 'polyglot-fullstack'];
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 unsupported(serviceType, lang, 'FastAPI (Python)');
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, lang);
78
+ return unsupported(serviceType);
54
79
 
55
80
  default:
56
- return unsupported(serviceType, lang);
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
- { path: 'infra/docker-compose', prefix: '' },
78
- { path: 'infra/github-actions', prefix: '' },
132
+ ...UNIVERSAL_MODULES,
79
133
  ];
80
134
 
81
- if (refinements.auth) {
82
- config.templateModules.push({ path: 'auth/nextauth', prefix: '' });
83
- }
84
-
85
- return config;
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
- { path: 'infra/docker-compose', prefix: '' },
105
- { path: 'infra/github-actions', prefix: '' },
159
+ ...UNIVERSAL_MODULES,
106
160
  ];
107
161
 
108
- if (refinements.auth) {
109
- config.templateModules.push({ path: 'auth/jwt-custom', prefix: '' });
110
- }
111
-
112
- return config;
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
- { path: 'infra/docker-compose', prefix: '' },
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.templateModules.push({ path: 'auth/nextauth', prefix: 'frontend' });
142
- config.templateModules.push({ path: 'auth/jwt-custom', prefix: '' });
209
+ config.auth = 'jwt-custom';
143
210
  }
144
211
 
145
- return config;
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 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 };
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
- lines.push(` ${chalk.bold('Frontend:')} ${config.frontend.framework} + ${config.frontend.language} + ${config.frontend.styling} + ${config.frontend.ui}`);
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
- 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
  }