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
@@ -1,5 +1,12 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
+ import {
4
+ checkChainproofExists,
5
+ checkChainproofIntegrity,
6
+ checkChainproofUnsigned,
7
+ } from './doctor-checks-chainproof.js';
8
+
9
+ export { checkChainproofExists, checkChainproofIntegrity, checkChainproofUnsigned };
3
10
 
4
11
  export function runAllChecks(projectDir, scan) {
5
12
  const issues = [];
@@ -21,6 +28,9 @@ export function runAllChecks(projectDir, scan) {
21
28
  issues.push(...checkDuplicateCode(projectDir));
22
29
  issues.push(...checkDeadFeatures(projectDir, scan));
23
30
  issues.push(...checkAIPrompts(projectDir));
31
+ issues.push(...checkChainproofExists(projectDir));
32
+ issues.push(...checkChainproofIntegrity(projectDir));
33
+ issues.push(...checkChainproofUnsigned(projectDir));
24
34
 
25
35
  return issues.sort((a, b) => {
26
36
  const order = { critical: 0, warning: 1, info: 2 };
@@ -36,7 +46,7 @@ function checkClaudeMdLength(projectDir, scan) {
36
46
  return [{
37
47
  severity: lines > 500 ? 'critical' : 'warning',
38
48
  title: `CLAUDE.md is ${lines} lines (recommended limit: 150)`,
39
- impact: 'Instructions are being dropped Claude Code ignores rules randomly when context is too large',
49
+ impact: 'Instructions are being dropped. Claude Code ignores rules randomly when context is too large',
40
50
  files: ['CLAUDE.md'],
41
51
  autoFixable: false,
42
52
  promptId: 'CLAUDE_MD_TOO_LONG',
@@ -68,7 +78,7 @@ function checkUnauthEndpoints(projectDir, scan) {
68
78
  issues.push({
69
79
  severity: 'critical',
70
80
  title: `Unauthenticated endpoint: ${relPath}:${i + 1}`,
71
- impact: 'Security vulnerability data accessible without login',
81
+ impact: 'Security vulnerability, data accessible without login',
72
82
  files: [`${relPath}:${i + 1}`],
73
83
  autoFixable: false,
74
84
  promptId: 'UNAUTH_ENDPOINT',
@@ -86,11 +96,14 @@ function checkUnauthEndpoints(projectDir, scan) {
86
96
  const apiDir = path.join(projectDir, srcDir, 'app', 'api');
87
97
  if (!fs.existsSync(apiDir)) return issues;
88
98
 
89
- const routeFiles = findFiles(apiDir, '.ts').concat(findFiles(apiDir, '.tsx'));
99
+ const routeFiles = findFiles(apiDir, '.ts').concat(findFiles(apiDir, '.tsx'))
100
+ .concat(findFiles(apiDir, '.js')).concat(findFiles(apiDir, '.jsx'));
90
101
  for (const file of routeFiles) {
91
- if (path.basename(file) === 'route.ts' || path.basename(file) === 'route.tsx') {
102
+ const basename = path.basename(file);
103
+ if (basename === 'route.ts' || basename === 'route.tsx' ||
104
+ basename === 'route.js' || basename === 'route.jsx') {
92
105
  // Skip health endpoints
93
- if (file.includes('/health/')) continue;
106
+ if (file.includes(`${path.sep}health${path.sep}`)) continue;
94
107
 
95
108
  const content = fs.readFileSync(file, 'utf-8');
96
109
  if (!content.includes('getServerSession') && !content.includes('auth(') &&
@@ -118,7 +131,7 @@ function checkUnauthEndpoints(projectDir, scan) {
118
131
  return [{
119
132
  severity,
120
133
  title: `${issues.length} endpoints have no authentication`,
121
- impact: 'Security vulnerability data accessible without login',
134
+ impact: 'Security vulnerability, data accessible without login',
122
135
  files: allFiles,
123
136
  autoFixable: false,
124
137
  promptId: 'UNAUTH_ENDPOINT',
@@ -161,7 +174,7 @@ function checkFlakyTestPatterns(projectDir) {
161
174
  issues.push({
162
175
  severity: 'critical',
163
176
  title: `${flakyFiles.length} tests use waitForTimeout() or hardcoded delays`,
164
- impact: 'Tests fail randomly unreliable CI',
177
+ impact: 'Tests fail randomly, which means unreliable CI',
165
178
  files: flakyFiles,
166
179
  autoFixable: false,
167
180
  promptId: 'FLAKY_TESTS',
@@ -191,7 +204,7 @@ function checkCrossDomainImports(projectDir, scan) {
191
204
  issues.push({
192
205
  severity: 'critical',
193
206
  title: `Cross-domain import: ${path.relative(projectDir, file)} imports from ${backendDir}`,
194
- impact: 'Build will break or bundle server code into client security risk',
207
+ impact: 'Build will break or bundle server code into client. This is a security risk',
195
208
  files: [path.relative(projectDir, file)],
196
209
  autoFixable: false,
197
210
  promptId: 'CROSS_DOMAIN_IMPORT',
@@ -206,7 +219,7 @@ function checkCrossDomainImports(projectDir, scan) {
206
219
  return [{
207
220
  severity: 'critical',
208
221
  title: `${issues.length} cross-domain imports (frontend importing backend or vice versa)`,
209
- impact: 'Build will break or bundle server code into client security risk',
222
+ impact: 'Build will break or bundle server code into client. This is a security risk',
210
223
  files: allFiles,
211
224
  autoFixable: false,
212
225
  promptId: 'CROSS_DOMAIN_IMPORT',
@@ -252,7 +265,7 @@ function checkBareExcepts(projectDir) {
252
265
  }];
253
266
  }
254
267
 
255
- function checkMissingHealthEndpoint(projectDir, scan) {
268
+ function checkMissingHealthEndpoint(projectDir, _scan) {
256
269
  // Search for health endpoints by file path or content
257
270
  const patterns = ['/health', '/healthz', '/api/health'];
258
271
  const searchDirs = ['src', 'backend', 'app', 'frontend/src'];
@@ -286,7 +299,7 @@ function checkMissingHealthEndpoint(projectDir, scan) {
286
299
  }];
287
300
  }
288
301
 
289
- function checkMissingGracefulShutdown(projectDir, scan) {
302
+ function checkMissingGracefulShutdown(projectDir, _scan) {
290
303
  const searchDirs = ['src', 'backend', 'app', 'frontend/src'];
291
304
  const signals = ['SIGTERM', 'SIGINT', 'process.on', 'signal.signal', 'lifespan'];
292
305
 
@@ -318,7 +331,7 @@ function checkMissingUAT(scan) {
318
331
  return [{
319
332
  severity: 'warning',
320
333
  title: 'No UAT scenarios',
321
- impact: 'No formal acceptance criteria features may not work as intended',
334
+ impact: 'No formal acceptance criteria, so features may not work as intended',
322
335
  files: [],
323
336
  autoFixable: false,
324
337
  promptId: 'MISSING_UAT',
@@ -340,7 +353,7 @@ function checkScatteredAPICalls(projectDir, scan) {
340
353
  // Skip files that ARE the API client
341
354
  if (['api.ts', 'api.js', 'apiClient.ts', 'apiClient.js', 'fetcher.ts', 'fetcher.js'].includes(basename)) continue;
342
355
  // Skip non-component files
343
- if (file.includes('/lib/') || file.includes('/services/') || file.includes('/utils/')) continue;
356
+ if (file.includes(`${path.sep}lib${path.sep}`) || file.includes(`${path.sep}services${path.sep}`) || file.includes(`${path.sep}utils${path.sep}`)) continue;
344
357
 
345
358
  const content = fs.readFileSync(file, 'utf-8');
346
359
  if (/fetch\s*\(\s*['"`]\/api\//.test(content) || /fetch\s*\(\s*['"`]http/.test(content) ||
@@ -387,7 +400,7 @@ function checkLargeFiles(projectDir) {
387
400
 
388
401
  return [{
389
402
  severity: 'info',
390
- title: `${largeFiles.length} files over 500 lines candidates for splitting`,
403
+ title: `${largeFiles.length} files over 500 lines (candidates for splitting)`,
391
404
  impact: 'Large files are hard to navigate, review, and test',
392
405
  files: largeFiles.map(f => `${f.file} (${f.lines} lines)`),
393
406
  autoFixable: false,
@@ -406,7 +419,7 @@ function checkMissingScopedClaudeMd(projectDir, scan) {
406
419
  if (!fs.existsSync(path.join(projectDir, frontendDir, 'CLAUDE.md'))) {
407
420
  issues.push({
408
421
  severity: 'info',
409
- title: `No ${frontendDir}/CLAUDE.md frontend rules not scoped`,
422
+ title: `No ${frontendDir}/CLAUDE.md, frontend rules not scoped`,
410
423
  impact: 'Claude Code loads all rules even when working only on frontend',
411
424
  files: [],
412
425
  autoFixable: false,
@@ -418,7 +431,7 @@ function checkMissingScopedClaudeMd(projectDir, scan) {
418
431
  if (!fs.existsSync(path.join(projectDir, backendDir, 'CLAUDE.md'))) {
419
432
  issues.push({
420
433
  severity: 'info',
421
- title: `No ${backendDir}/CLAUDE.md backend rules not scoped`,
434
+ title: `No ${backendDir}/CLAUDE.md, backend rules not scoped`,
422
435
  impact: 'Claude Code loads all rules even when working only on backend',
423
436
  files: [],
424
437
  autoFixable: false,
@@ -430,11 +443,16 @@ function checkMissingScopedClaudeMd(projectDir, scan) {
430
443
  return issues;
431
444
  }
432
445
 
433
- function checkUnusedDependencies(projectDir, scan) {
446
+ function checkUnusedDependencies(projectDir, _scan) {
434
447
  const pkgPath = path.join(projectDir, 'package.json');
435
448
  if (!fs.existsSync(pkgPath)) return [];
436
449
 
437
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
450
+ let pkg;
451
+ try {
452
+ pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
453
+ } catch {
454
+ return [];
455
+ }
438
456
  const deps = Object.keys(pkg.dependencies || {});
439
457
  if (deps.length === 0) return [];
440
458
 
@@ -526,7 +544,7 @@ function checkHardcodedValues(projectDir) {
526
544
  return [{
527
545
  severity: 'warning',
528
546
  title: `${hardcoded.length} hardcoded values that should be in environment variables`,
529
- impact: 'Cannot change config without code changes breaks deployment',
547
+ impact: 'Cannot change config without code changes, which breaks deployment',
530
548
  files: hardcoded,
531
549
  autoFixable: false,
532
550
  promptId: 'HARDCODED_VALUES',
@@ -709,7 +727,7 @@ function checkAIPrompts(projectDir) {
709
727
 
710
728
  return [{
711
729
  severity: 'info',
712
- title: `${issues.length} files have inline AI prompts consider a prompts file`,
730
+ title: `${issues.length} files have inline AI prompts. Consider a prompts file`,
713
731
  impact: 'Inline prompts are hard to version, test, and iterate on',
714
732
  files: issues,
715
733
  autoFixable: false,
@@ -741,3 +759,4 @@ function findFiles(dir, ext) {
741
759
 
742
760
  return results;
743
761
  }
762
+
@@ -1,7 +1,7 @@
1
1
  const PROMPT_TEMPLATES = {
2
2
  CLAUDE_MD_TOO_LONG: (issue) => {
3
3
  const lines = issue.title.match(/(\d+) lines/)?.[1] || '?';
4
- return `Read CLAUDE.md. It's ${lines} lines too long for Claude Code to follow reliably (target: <150).
4
+ return `Read CLAUDE.md. It's ${lines} lines, too long for Claude Code to follow reliably (target: <150).
5
5
 
6
6
  Propose a split:
7
7
  - What stays in CLAUDE.md (universal rules, commands, pitfalls)
@@ -25,7 +25,7 @@ Write a test for each endpoint confirming 401 without auth.`;
25
25
 
26
26
  FLAKY_TESTS: (issue) => {
27
27
  const fileList = (issue.files || []).map(f => `- ${f}`).join('\n');
28
- return `These tests use waitForTimeout() or hardcoded delays they will fail randomly:
28
+ return `These tests use waitForTimeout() or hardcoded delays. They will fail randomly:
29
29
  ${fileList}
30
30
 
31
31
  For each:
@@ -55,10 +55,10 @@ ${fileList}
55
55
  For each: replace with a specific exception type.
56
56
  - If catching expected errors: except ValueError, except KeyError, etc.
57
57
  - If catching any exception for logging: except Exception as e: with logging
58
- - Never use bare except: it hides bugs`;
58
+ - Never use bare except: because it hides bugs`;
59
59
  },
60
60
 
61
- MISSING_HEALTH: (issue) => {
61
+ MISSING_HEALTH: (_issue) => {
62
62
  return `Add a health check endpoint to the application.
63
63
 
64
64
  For FastAPI: Add a /health endpoint that returns {"status": "ok"} and optionally checks database connectivity.
@@ -68,7 +68,7 @@ For both: Include database connectivity check if a database is configured.
68
68
  This endpoint is used by load balancers and monitoring to verify the app is running.`;
69
69
  },
70
70
 
71
- MISSING_SHUTDOWN: (issue) => {
71
+ MISSING_SHUTDOWN: (_issue) => {
72
72
  return `Add graceful shutdown handling to the application.
73
73
 
74
74
  For FastAPI: Use the lifespan context manager to handle startup/shutdown events.
@@ -114,7 +114,7 @@ For each:
114
114
  3. Update imports
115
115
  4. Run tests after each extraction
116
116
 
117
- Don't refactor all at once one file per session.`;
117
+ Don't refactor all at once. Do one file per session.`;
118
118
  },
119
119
 
120
120
  MISSING_SCOPED_CLAUDE_MD: (issue) => {
@@ -177,7 +177,7 @@ ${fileList}
177
177
  Extract all prompts into a dedicated prompts file (e.g., src/lib/prompts.ts or prompts/). This makes prompts easier to version, A/B test, and iterate on without changing application code.`;
178
178
  },
179
179
 
180
- DEVFORGE_UPDATE: (issue) => {
180
+ DEVFORGE_UPDATE: (_issue) => {
181
181
  return `A newer version of DevForge is available.
182
182
 
183
183
  Run: npm install -g forgedev@latest
@@ -221,7 +221,7 @@ ${prompt}
221
221
  sessionNum++;
222
222
  }
223
223
 
224
- return `# Fix Prompts Run These in Claude Code (In Order)
224
+ return `# Fix Prompts - Run These in Claude Code (In Order)
225
225
 
226
226
  ${sections.join('\n')}
227
227
  Run each as a separate Claude Code session. /clear between sessions.
@@ -232,7 +232,7 @@ export function generateReport(issues, projectName) {
232
232
  const critical = issues.filter(i => i.severity === 'critical');
233
233
  const warnings = issues.filter(i => i.severity === 'warning');
234
234
  const info = issues.filter(i => i.severity === 'info');
235
- let report = `# DevForge Doctor Report ${projectName}
235
+ let report = `# DevForge Doctor Report - ${projectName}
236
236
  Generated: ${new Date().toISOString().split('T')[0]}
237
237
 
238
238
  ## Summary
package/src/doctor.js CHANGED
@@ -9,7 +9,7 @@ import { askDoctorAction } from './prompts.js';
9
9
 
10
10
  export async function runDoctor(projectDir) {
11
11
  console.log('');
12
- console.log(chalk.bold.cyan(' 🔨 DevForge Doctor') + chalk.dim('Project Health Check'));
12
+ console.log(chalk.bold.cyan(' 🔨 DevForge Doctor') + chalk.dim(' Project Health Check'));
13
13
  console.log('');
14
14
  console.log(' Scanning...');
15
15
  console.log('');
@@ -45,7 +45,7 @@ export async function runDoctor(projectDir) {
45
45
  });
46
46
  }
47
47
  } catch {
48
- // Silently ignore network issues shouldn't block doctor
48
+ // Silently ignore. Network issues shouldn't block doctor
49
49
  }
50
50
 
51
51
  if (issues.length === 0) {
@@ -247,8 +247,7 @@ async function autoFixSafe(issues, projectDir) {
247
247
  fs.chmodSync(hookPath, '755');
248
248
  console.log(chalk.green(` ✓ Fixed ${path.join('.claude/hooks', hookFile)} permissions (chmod +x)`));
249
249
  fixed++;
250
- } catch {
251
- // Skip
250
+ } catch { // Permission change failed on this hook, continue
252
251
  }
253
252
  }
254
253
  }
@@ -262,6 +261,8 @@ async function autoFixSafe(issues, projectDir) {
262
261
 
263
262
  if (!content.includes('.claude/todos')) entriesToAdd.push('.claude/todos');
264
263
  if (!content.includes('.claude/plans')) entriesToAdd.push('.claude/plans');
264
+ if (!content.includes('.env.local')) entriesToAdd.push('.env.local');
265
+ if (!content.includes('.env.*.local')) entriesToAdd.push('.env.*.local');
265
266
 
266
267
  if (entriesToAdd.length > 0) {
267
268
  const addition = '\n# Claude Code temp files\n' + entriesToAdd.join('\n') + '\n';
@@ -271,6 +272,38 @@ async function autoFixSafe(issues, projectDir) {
271
272
  }
272
273
  }
273
274
 
275
+ // Create missing .env.example if .env exists but no example
276
+ const envPath = path.join(projectDir, '.env');
277
+ const envExamplePath = path.join(projectDir, '.env.example');
278
+ if (fs.existsSync(envPath) && !fs.existsSync(envExamplePath)) {
279
+ const envContent = fs.readFileSync(envPath, 'utf-8');
280
+ const exampleContent = envContent
281
+ .split('\n')
282
+ .map(line => {
283
+ if (!line.includes('=') || line.startsWith('#')) return line;
284
+ const key = line.split('=')[0];
285
+ return `${key}=`;
286
+ })
287
+ .join('\n');
288
+ fs.writeFileSync(envExamplePath, exampleContent, 'utf-8');
289
+ console.log(chalk.green(' ✓ Created .env.example from .env (values stripped)'));
290
+ fixed++;
291
+ }
292
+
293
+ // Create missing CLAUDE.md for subdirectories in polyglot projects
294
+ const frontendDir = path.join(projectDir, 'frontend');
295
+ const backendDir = path.join(projectDir, 'backend');
296
+ if (fs.existsSync(frontendDir) && fs.existsSync(backendDir)) {
297
+ for (const subdir of ['frontend', 'backend']) {
298
+ const subdirClaudeMd = path.join(projectDir, subdir, 'CLAUDE.md');
299
+ if (!fs.existsSync(subdirClaudeMd)) {
300
+ fs.writeFileSync(subdirClaudeMd, `# ${subdir}\n\nSee root CLAUDE.md for project-level rules. This file contains ${subdir}-specific overrides.\n`, 'utf-8');
301
+ console.log(chalk.green(` ✓ Created ${subdir}/CLAUDE.md (scoped)`));
302
+ fixed++;
303
+ }
304
+ }
305
+ }
306
+
274
307
  const skipped = issues.filter(i => !i.autoFixable).length;
275
308
 
276
309
  if (fixed === 0) {
package/src/guided.js CHANGED
@@ -91,7 +91,7 @@ export function analyzeDescription(text) {
91
91
  suggestedServiceType = 'web_app';
92
92
  }
93
93
 
94
- // AI service override if AI is the primary focus
94
+ // AI service override: if AI is the primary focus
95
95
  if (features.includes('ai') && !features.includes('dashboard') && !features.includes('payments')) {
96
96
  suggestedServiceType = 'ai_service';
97
97
  }
@@ -114,7 +114,7 @@ export function mapFeaturesToStack(analysis) {
114
114
  return stackConfig;
115
115
  }
116
116
 
117
- export function buildGuidedPreview(analysis, stackConfig) {
117
+ export function buildGuidedPreview(analysis, _stackConfig) {
118
118
  const lines = [];
119
119
 
120
120
  lines.push(chalk.bold(' What you\'ll get:'));
@@ -208,7 +208,7 @@ npm run dev`;
208
208
 
209
209
  DevForge created a starter version of your app with all the basic
210
210
  building blocks in place. Think of it like getting a house with the
211
- foundation, walls, and plumbing already done you just need to
211
+ foundation, walls, and plumbing already done. You just need to
212
212
  furnish and decorate it.
213
213
 
214
214
  ## Your Project Structure (Plain English)
package/src/index.js CHANGED
@@ -2,7 +2,7 @@ import path from 'node:path';
2
2
  import fs from 'node:fs';
3
3
  import { execSync } from 'node:child_process';
4
4
  import chalk from 'chalk';
5
- import { log, toKebabCase } from './utils.js';
5
+ import { log, toKebabCase, copyEnvCmd } from './utils.js';
6
6
  import { askServiceType, askRefinements, askNewMode, confirmStack } from './prompts.js';
7
7
  import { recommend } from './recommender.js';
8
8
  import { compose } from './composer.js';
@@ -12,9 +12,9 @@ import { generateUAT } from './uat-generator.js';
12
12
  export async function runNew(projectName) {
13
13
  const safeName = toKebabCase(projectName);
14
14
 
15
- // Validate project name must be a clean kebab-case identifier
16
- if (!/^[a-z0-9][a-z0-9-]*$/.test(safeName)) {
17
- log.error('Project name must start with a letter or number and contain only lowercase letters, numbers, and hyphens.');
15
+ // Validate project name. Must be a clean kebab-case identifier
16
+ if (!/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/.test(safeName)) {
17
+ log.error('Project name must start with a letter, contain only lowercase letters, numbers, and hyphens, and not have consecutive or trailing hyphens.');
18
18
  process.exit(1);
19
19
  }
20
20
 
@@ -26,7 +26,7 @@ export async function runNew(projectName) {
26
26
  }
27
27
 
28
28
  console.log('');
29
- console.log(chalk.bold.cyan(' 🔨 DevForge') + chalk.dim('Let\'s build something.'));
29
+ console.log(chalk.bold.cyan(' 🔨 DevForge') + chalk.dim(' Let\'s build something.'));
30
30
  console.log('');
31
31
 
32
32
  const mode = await askNewMode();
@@ -62,7 +62,7 @@ async function runDeveloperFlow(safeName, outputDir) {
62
62
 
63
63
  export async function scaffold(outputDir, stackConfig) {
64
64
  console.log('');
65
- const totalSteps = stackConfig.claudeCode ? 4 : 3;
65
+ const totalSteps = stackConfig.claudeCode ? 5 : 4;
66
66
  let step = 0;
67
67
 
68
68
  step++;
@@ -79,6 +79,11 @@ export async function scaffold(outputDir, stackConfig) {
79
79
  log.step(step, totalSteps, 'Generating UAT templates...');
80
80
  await generateUAT(outputDir, stackConfig);
81
81
 
82
+ step++;
83
+ log.step(step, totalSteps, 'Initializing ChainProof trust chain...');
84
+ const { initChainproof } = await import('./chainproof-bridge.js');
85
+ initChainproof(outputDir);
86
+
82
87
  step++;
83
88
  log.step(step, totalSteps, 'Initializing git repository...');
84
89
  initGit(outputDir);
@@ -93,7 +98,7 @@ function initGit(outputDir) {
93
98
  try {
94
99
  execSync('git init', { cwd: outputDir, stdio: 'ignore' });
95
100
  } catch {
96
- // git not available skip silently
101
+ log.dim(' git not available, skipping repository initialization');
97
102
  }
98
103
  }
99
104
 
@@ -109,7 +114,7 @@ export function printNextSteps(projectName, config, isGuided = false) {
109
114
  console.log(chalk.bold(' 📖 New to coding? Here\'s what to do next:'));
110
115
  console.log(' 1. Open this folder in VS Code (or any code editor)');
111
116
  console.log(' 2. If you have Claude Code installed, type /workflows');
112
- console.log(' it will guide you step by step');
117
+ console.log(' It will guide you step by step');
113
118
  console.log(' 3. Or read docs/getting-started.md for a beginner-friendly walkthrough');
114
119
  console.log('');
115
120
  return;
@@ -120,7 +125,7 @@ export function printNextSteps(projectName, config, isGuided = false) {
120
125
 
121
126
  if (config.stackId === 'nextjs-fullstack') {
122
127
  console.log(' npm install');
123
- console.log(' cp .env.example .env');
128
+ console.log(` ${copyEnvCmd()}`);
124
129
  console.log(' npx prisma db push');
125
130
  console.log(' npm run dev');
126
131
  } else if (config.stackId === 'fastapi-backend') {
@@ -128,7 +133,7 @@ export function printNextSteps(projectName, config, isGuided = false) {
128
133
  console.log(' python -m venv venv');
129
134
  console.log(' source venv/bin/activate # or venv\\Scripts\\activate on Windows');
130
135
  console.log(' pip install -r requirements.txt');
131
- console.log(' cp .env.example .env');
136
+ console.log(` ${copyEnvCmd()}`);
132
137
  console.log(' uvicorn app.main:app --reload');
133
138
  } else if (config.stackId === 'polyglot-fullstack') {
134
139
  console.log(' docker compose up -d postgres');
@@ -136,6 +141,21 @@ export function printNextSteps(projectName, config, isGuided = false) {
136
141
  console.log(' cd frontend && npm install && npm run dev');
137
142
  console.log(' # Backend:');
138
143
  console.log(' cd backend && pip install -r requirements.txt && uvicorn app.main:app --reload');
144
+ } else if (config.stackId === 'react-express') {
145
+ console.log(' # Frontend:');
146
+ console.log(' cd frontend && npm install && npm run dev');
147
+ console.log(' # Backend (in a separate terminal):');
148
+ console.log(` cd backend && npm install && ${copyEnvCmd()} && npx prisma db push && npm run dev`);
149
+ } else if (config.stackId === 'remix-fullstack') {
150
+ console.log(' npm install');
151
+ console.log(` ${copyEnvCmd()}`);
152
+ console.log(' npx prisma db push');
153
+ console.log(' npm run dev');
154
+ } else if (config.stackId === 'hono-api') {
155
+ console.log(' npm install');
156
+ console.log(` ${copyEnvCmd()}`);
157
+ console.log(' npx prisma db push');
158
+ console.log(' npm run dev');
139
159
  }
140
160
 
141
161
  if (config.claudeCode) {
@@ -149,6 +169,7 @@ export function printNextSteps(projectName, config, isGuided = false) {
149
169
  console.log(chalk.dim(' - .claude/commands/ (audit, verify, pre-pr)'));
150
170
  console.log(chalk.dim(' - docs/prompt-library.md'));
151
171
  console.log(chalk.dim(' - docs/uat/ (acceptance testing)'));
172
+ console.log(chalk.dim(' - .chainproof/ (trust chain tracking)'));
152
173
  }
153
174
 
154
175
  console.log('');
package/src/init-mode.js CHANGED
@@ -1,13 +1,15 @@
1
1
  import path from 'node:path';
2
+ import fs from 'node:fs';
2
3
  import chalk from 'chalk';
3
- import { log } from './utils.js';
4
+ import { log, ROOT_DIR, ensureDir } from './utils.js';
4
5
  import { scanProject } from './scanner.js';
5
6
  import { generateClaudeConfig } from './claude-configurator.js';
6
7
  import { generateUAT } from './uat-generator.js';
8
+ import { initChainproof } from './chainproof-bridge.js';
7
9
 
8
10
  export async function runInit(projectDir) {
9
11
  console.log('');
10
- console.log(chalk.bold.cyan(' 🔨 DevForge') + chalk.dim('Adding dev guardrails'));
12
+ console.log(chalk.bold.cyan(' 🔨 DevForge') + chalk.dim(' Adding dev guardrails'));
11
13
  console.log('');
12
14
  console.log(' Scanning...');
13
15
  console.log('');
@@ -51,38 +53,61 @@ export async function runInit(projectDir) {
51
53
  // Generate Claude Code infrastructure (skips CLAUDE.md if it exists, merges settings.json if it exists)
52
54
  const skipClaudeMd = scan.infrastructure.hasClaudeMd;
53
55
  const mergeSettings = scan.infrastructure.hasHooks;
54
- await generateClaudeConfig(projectDir, stackConfig, { skipClaudeMd, mergeSettings });
56
+ const { agents, commands } = await generateClaudeConfig(projectDir, stackConfig, { skipClaudeMd, mergeSettings });
55
57
 
56
58
  if (!skipClaudeMd) {
57
- console.log(chalk.green(' ✓ ') + 'CLAUDE.md project context + rules');
59
+ console.log(chalk.green(' ✓ ') + 'CLAUDE.md (project context + rules)');
58
60
  } else {
59
- console.log(chalk.yellow(' ⊘ ') + `CLAUDE.md exists (tip: run ${chalk.cyan('/optimize-claude-md')} to slim it)`);
61
+ console.log(chalk.yellow(' ⊘ ') + `CLAUDE.md already exists (tip: run ${chalk.cyan('/optimize-claude-md')} to slim it)`);
60
62
  }
61
63
 
62
64
  if (!scan.infrastructure.hasHooks) {
63
- console.log(chalk.green(' ✓ ') + '.claude/hooks/ auto-lint, quality gate, file protection');
65
+ console.log(chalk.green(' ✓ ') + '.claude/hooks/ (auto-lint, quality gate, file protection)');
64
66
  } else {
65
- console.log(chalk.yellow(' ⊘ ') + '.claude/hooks/ already configured');
67
+ console.log(chalk.yellow(' ⊘ ') + '.claude/hooks/ already configured');
66
68
  }
67
69
 
68
70
  if (!scan.infrastructure.hasAgents) {
69
- console.log(chalk.green(' ✓ ') + '.claude/agents/ code quality, security, spec validator');
71
+ console.log(chalk.green(' ✓ ') + `.claude/agents/ (${agents.count} agents installed)`);
72
+ } else {
73
+ console.log(chalk.green(' ✓ ') + `.claude/agents/ (${agents.count} agents synced)`);
70
74
  }
71
75
  if (!scan.infrastructure.hasCommands) {
72
- console.log(chalk.green(' ✓ ') + '.claude/commands/ help, status, next, done, audit, pre-pr');
76
+ console.log(chalk.green(' ✓ ') + `.claude/commands/ (${commands.count} commands installed)`);
77
+ } else {
78
+ console.log(chalk.green(' ✓ ') + `.claude/commands/ (${commands.count} commands synced)`);
73
79
  }
74
80
  if (!scan.infrastructure.hasSkills) {
75
- console.log(chalk.green(' ✓ ') + '.claude/skills/ framework-specific knowledge');
81
+ console.log(chalk.green(' ✓ ') + '.claude/skills/ (framework-specific knowledge)');
82
+ }
83
+
84
+ // Report orphan cleanup
85
+ const removedFiles = [...agents.removed, ...commands.removed];
86
+ if (removedFiles.length > 0) {
87
+ const removedNames = removedFiles.map(f => f.replace('.md', ''));
88
+ console.log(chalk.dim(' - ') + `Cleaned up ${removedFiles.length} outdated file${removedFiles.length > 1 ? 's' : ''}: ${removedNames.join(', ')}`);
76
89
  }
77
90
 
78
91
  // Generate UAT templates
79
92
  if (!scan.infrastructure.hasUAT) {
80
93
  await generateUAT(projectDir, stackConfig);
81
- console.log(chalk.green(' ✓ ') + 'docs/uat/ acceptance test templates');
94
+ console.log(chalk.green(' ✓ ') + 'docs/uat/ (acceptance test templates)');
82
95
  } else {
83
- console.log(chalk.yellow(' ⊘ ') + 'docs/uat/ already exists');
96
+ console.log(chalk.yellow(' ⊘ ') + 'docs/uat/ already exists');
84
97
  }
85
98
 
99
+ // Initialize ChainProof trust chain
100
+ const chainproofDir = path.join(projectDir, '.chainproof');
101
+ if (!fs.existsSync(chainproofDir)) {
102
+ initChainproof(projectDir);
103
+ console.log(chalk.green(' ✓ ') + '.chainproof/ (trust chain initialized)');
104
+ } else {
105
+ console.log(chalk.yellow(' ⊘ ') + '.chainproof/ already exists');
106
+ }
107
+
108
+ // Install ChainProof MCP server and configure .mcp.json
109
+ setupChainproofMcp(projectDir);
110
+
86
111
  console.log('');
87
112
  log.success(' Done. Your project is now configured for Claude Code.');
88
113
  console.log('');
@@ -136,3 +161,42 @@ export function buildConfigFromScan(scan) {
136
161
 
137
162
  return config;
138
163
  }
164
+
165
+ export function setupChainproofMcp(projectDir) {
166
+ // Copy MCP server script into .chainproof/
167
+ const serverSrc = path.join(ROOT_DIR, 'templates', 'chainproof', 'base', '.chainproof', 'mcp-server.mjs');
168
+ const serverDest = path.join(projectDir, '.chainproof', 'mcp-server.mjs');
169
+ if (fs.existsSync(serverSrc) && !fs.existsSync(serverDest)) {
170
+ ensureDir(path.dirname(serverDest));
171
+ fs.copyFileSync(serverSrc, serverDest);
172
+ }
173
+
174
+ // Configure .mcp.json (merge if exists)
175
+ const mcpPath = path.join(projectDir, '.mcp.json');
176
+ const chainproofMcpEntry = {
177
+ command: 'node',
178
+ args: ['.chainproof/mcp-server.mjs'],
179
+ env: {},
180
+ };
181
+
182
+ if (fs.existsSync(mcpPath)) {
183
+ try {
184
+ const existing = JSON.parse(fs.readFileSync(mcpPath, 'utf-8'));
185
+ existing.mcpServers = existing.mcpServers || {};
186
+ if (!existing.mcpServers.chainproof) {
187
+ existing.mcpServers.chainproof = chainproofMcpEntry;
188
+ fs.writeFileSync(mcpPath, JSON.stringify(existing, null, 2) + '\n', 'utf-8');
189
+ console.log(chalk.green(' ✓ ') + '.mcp.json (added ChainProof server)');
190
+ } else {
191
+ console.log(chalk.yellow(' ⊘ ') + '.mcp.json already has ChainProof configured');
192
+ }
193
+ } catch {
194
+ // Corrupted .mcp.json, don't touch it
195
+ console.log(chalk.yellow(' ⊘ ') + '.mcp.json exists but could not be parsed');
196
+ }
197
+ } else {
198
+ const mcpConfig = { mcpServers: { chainproof: chainproofMcpEntry } };
199
+ fs.writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2) + '\n', 'utf-8');
200
+ console.log(chalk.green(' ✓ ') + '.mcp.json (ChainProof MCP auto-configured)');
201
+ }
202
+ }