forgedev 1.3.0 → 1.4.1

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 (46) hide show
  1. package/bin/chainproof.js +1 -1
  2. package/bin/devforge.js +1 -1
  3. package/package.json +1 -1
  4. package/src/chainproof-bridge.js +9 -2
  5. package/src/ci-mode.js +3 -4
  6. package/src/claude-configurator.js +114 -58
  7. package/src/composer.js +17 -128
  8. package/src/doctor-checks-chainproof.js +14 -11
  9. package/src/doctor-checks.js +3 -19
  10. package/src/index.js +6 -34
  11. package/src/init-mode.js +14 -3
  12. package/src/recommender.js +319 -310
  13. package/src/uat-generator.js +105 -93
  14. package/src/update.js +56 -12
  15. package/src/utils.js +245 -116
  16. package/templates/auth/jwt-custom/backend/app/api/auth.py.template +39 -45
  17. package/templates/auth/jwt-custom/backend/app/core/security.py.template +44 -37
  18. package/templates/backend/express/package.json.template +35 -33
  19. package/templates/backend/express/src/lib/prisma.ts.template +32 -0
  20. package/templates/backend/express/src/routes/health.ts.template +35 -27
  21. package/templates/backend/fastapi/backend/app/main.py.template +67 -60
  22. package/templates/backend/fastapi/backend/requirements.txt.template +18 -16
  23. package/templates/backend/hono/package.json.template +33 -31
  24. package/templates/backend/hono/src/lib/prisma.ts.template +32 -0
  25. package/templates/backend/hono/src/routes/health.ts.template +38 -27
  26. package/templates/base/.gitignore.template +32 -29
  27. package/templates/claude-code/commands/workflows.md +52 -52
  28. package/templates/database/prisma-postgres/prisma/schema.prisma.template +19 -18
  29. package/templates/database/sqlalchemy-postgres/backend/alembic/env.py.template +39 -40
  30. package/templates/database/sqlalchemy-postgres/backend/alembic.ini.template +38 -36
  31. package/templates/database/sqlalchemy-postgres/backend/app/db/session.py.template +50 -48
  32. package/templates/frontend/nextjs/package.json.template +45 -43
  33. package/templates/frontend/remix/package.json.template +41 -39
  34. package/templates/infra/docker/.dockerignore.template +16 -0
  35. package/templates/infra/docker-compose/docker-compose.yml.template +22 -19
  36. package/templates/infra/github-actions/.github/workflows/ci.yml.template +61 -52
  37. package/templates/infra/k8s/k8s/deployment.yml.template +70 -0
  38. package/templates/infra/k8s/k8s/hpa.yml.template +24 -0
  39. package/templates/infra/k8s/k8s/ingress.yml.template +26 -0
  40. package/templates/infra/k8s/k8s/kustomization.yml.template +13 -0
  41. package/templates/infra/k8s/k8s/namespace.yml.template +4 -0
  42. package/templates/infra/k8s/k8s/networkpolicy.yml.template +41 -0
  43. package/templates/infra/k8s/k8s/secrets.yml.template +10 -0
  44. package/templates/infra/k8s/k8s/service.yml.template +15 -0
  45. package/templates/testing/load/k6/README.md.template +48 -0
  46. package/templates/testing/load/k6/load-test.js.template +57 -0
@@ -8,13 +8,13 @@ export async function generateUAT(outputDir, stackConfig) {
8
8
  generateBusinessRulesPlaceholder(outputDir, stackConfig);
9
9
  }
10
10
 
11
- function buildScenarios(config) {
12
- const scenarios = [];
13
- let id = 1;
11
+ function nextId(counter) {
12
+ return `UAT-${String(counter.id++).padStart(3, '0')}`;
13
+ }
14
14
 
15
- // Health check: every project gets this
16
- scenarios.push({
17
- id: `UAT-${String(id++).padStart(3, '0')}`,
15
+ function healthScenarios(counter) {
16
+ return [{
17
+ id: nextId(counter),
18
18
  feature: 'Health Check',
19
19
  priority: 'P0',
20
20
  preconditions: 'Application is running',
@@ -24,44 +24,46 @@ function buildScenarios(config) {
24
24
  'Verify response body contains status: "ok"',
25
25
  ],
26
26
  expected: 'Health endpoint responds with 200 and status "ok"',
27
- });
27
+ }];
28
+ }
28
29
 
29
- // Database connectivity
30
- if (config.database) {
31
- scenarios.push({
32
- id: `UAT-${String(id++).padStart(3, '0')}`,
33
- feature: 'Database Connectivity',
34
- priority: 'P0',
35
- preconditions: 'Application and database are running',
36
- steps: [
37
- 'Send GET request to deep health endpoint (/healthz)',
38
- 'Verify response includes database status',
39
- 'Verify database status is "connected"',
40
- ],
41
- expected: 'Deep health check confirms database connectivity',
42
- });
43
- }
30
+ function databaseScenarios(counter, config) {
31
+ if (!config.database) return [];
32
+ return [{
33
+ id: nextId(counter),
34
+ feature: 'Database Connectivity',
35
+ priority: 'P0',
36
+ preconditions: 'Application and database are running',
37
+ steps: [
38
+ 'Send GET request to deep health endpoint (/healthz)',
39
+ 'Verify response includes database status',
40
+ 'Verify database status is "connected"',
41
+ ],
42
+ expected: 'Deep health check confirms database connectivity',
43
+ }];
44
+ }
44
45
 
45
- // Homepage (frontend projects)
46
- if (config.frontend) {
47
- scenarios.push({
48
- id: `UAT-${String(id++).padStart(3, '0')}`,
49
- feature: 'Home Page Load',
50
- priority: 'P0',
51
- preconditions: 'Application is running',
52
- steps: [
53
- 'Navigate to the application root URL',
54
- 'Verify the page loads without errors',
55
- 'Verify the page title is correct',
56
- ],
57
- expected: 'Home page loads successfully with correct title',
58
- });
59
- }
46
+ function frontendScenarios(counter, config) {
47
+ if (!config.frontend) return [];
48
+ return [{
49
+ id: nextId(counter),
50
+ feature: 'Home Page Load',
51
+ priority: 'P0',
52
+ preconditions: 'Application is running',
53
+ steps: [
54
+ 'Navigate to the application root URL',
55
+ 'Verify the page loads without errors',
56
+ 'Verify the page title is correct',
57
+ ],
58
+ expected: 'Home page loads successfully with correct title',
59
+ }];
60
+ }
60
61
 
61
- // Authentication
62
- if (config.auth) {
63
- scenarios.push({
64
- id: `UAT-${String(id++).padStart(3, '0')}`,
62
+ function authScenarios(counter, config) {
63
+ if (!config.auth) return [];
64
+ const scenarios = [
65
+ {
66
+ id: nextId(counter),
65
67
  feature: 'User Registration',
66
68
  priority: 'P0',
67
69
  preconditions: 'Application is running, no existing test user',
@@ -72,10 +74,9 @@ function buildScenarios(config) {
72
74
  'Verify account is created successfully',
73
75
  ],
74
76
  expected: 'User account is created and user is authenticated',
75
- });
76
-
77
- scenarios.push({
78
- id: `UAT-${String(id++).padStart(3, '0')}`,
77
+ },
78
+ {
79
+ id: nextId(counter),
79
80
  feature: 'User Login',
80
81
  priority: 'P0',
81
82
  preconditions: 'Test user account exists',
@@ -86,10 +87,9 @@ function buildScenarios(config) {
86
87
  'Verify user is redirected to dashboard/home',
87
88
  ],
88
89
  expected: 'User is authenticated and redirected appropriately',
89
- });
90
-
91
- scenarios.push({
92
- id: `UAT-${String(id++).padStart(3, '0')}`,
90
+ },
91
+ {
92
+ id: nextId(counter),
93
93
  feature: 'Invalid Login',
94
94
  priority: 'P1',
95
95
  preconditions: 'Application is running',
@@ -100,55 +100,67 @@ function buildScenarios(config) {
100
100
  'Verify error message is displayed',
101
101
  ],
102
102
  expected: 'User sees a generic error message, no sensitive info leaked',
103
- });
103
+ },
104
+ ];
104
105
 
105
- if (config.frontend) {
106
- scenarios.push({
107
- id: `UAT-${String(id++).padStart(3, '0')}`,
108
- feature: 'Protected Route Access',
109
- priority: 'P1',
110
- preconditions: 'User is not authenticated',
111
- steps: [
112
- 'Navigate to a protected route (e.g., /dashboard)',
113
- 'Verify redirect to login page',
114
- 'Log in with valid credentials',
115
- 'Verify redirect back to protected route',
116
- ],
117
- expected: 'Unauthenticated users are redirected to login, then back after auth',
118
- });
119
- }
106
+ if (config.frontend) {
107
+ scenarios.push({
108
+ id: nextId(counter),
109
+ feature: 'Protected Route Access',
110
+ priority: 'P1',
111
+ preconditions: 'User is not authenticated',
112
+ steps: [
113
+ 'Navigate to a protected route (e.g., /dashboard)',
114
+ 'Verify redirect to login page',
115
+ 'Log in with valid credentials',
116
+ 'Verify redirect back to protected route',
117
+ ],
118
+ expected: 'Unauthenticated users are redirected to login, then back after auth',
119
+ });
120
120
  }
121
121
 
122
- // Error handling
123
- scenarios.push({
124
- id: `UAT-${String(id++).padStart(3, '0')}`,
125
- feature: 'Structured Error Response',
126
- priority: 'P1',
127
- preconditions: 'Application is running',
128
- steps: [
129
- 'Send a request to a non-existent endpoint',
130
- 'Verify response has structured error format',
131
- 'Verify no stack traces or internal details are leaked',
132
- ],
133
- expected: 'Error response follows format: { error: { code, message } }',
134
- });
122
+ return scenarios;
123
+ }
135
124
 
136
- // Graceful shutdown
137
- scenarios.push({
138
- id: `UAT-${String(id).padStart(3, '0')}`,
139
- feature: 'Graceful Shutdown',
140
- priority: 'P1',
141
- preconditions: 'Application is running with active connections',
142
- steps: [
143
- 'Send SIGTERM to the application process',
144
- 'Verify in-flight requests complete',
145
- 'Verify database connections are closed',
146
- 'Verify process exits with code 0',
147
- ],
148
- expected: 'Application shuts down gracefully without dropping requests',
149
- });
125
+ function errorAndShutdownScenarios(counter) {
126
+ return [
127
+ {
128
+ id: nextId(counter),
129
+ feature: 'Structured Error Response',
130
+ priority: 'P1',
131
+ preconditions: 'Application is running',
132
+ steps: [
133
+ 'Send a request to a non-existent endpoint',
134
+ 'Verify response has structured error format',
135
+ 'Verify no stack traces or internal details are leaked',
136
+ ],
137
+ expected: 'Error response follows format: { error: { code, message } }',
138
+ },
139
+ {
140
+ id: nextId(counter),
141
+ feature: 'Graceful Shutdown',
142
+ priority: 'P1',
143
+ preconditions: 'Application is running with active connections',
144
+ steps: [
145
+ 'Send SIGTERM to the application process',
146
+ 'Verify in-flight requests complete',
147
+ 'Verify database connections are closed',
148
+ 'Verify process exits with code 0',
149
+ ],
150
+ expected: 'Application shuts down gracefully without dropping requests',
151
+ },
152
+ ];
153
+ }
150
154
 
151
- return scenarios;
155
+ function buildScenarios(config) {
156
+ const counter = { id: 1 };
157
+ return [
158
+ ...healthScenarios(counter),
159
+ ...databaseScenarios(counter, config),
160
+ ...frontendScenarios(counter, config),
161
+ ...authScenarios(counter, config),
162
+ ...errorAndShutdownScenarios(counter),
163
+ ];
152
164
  }
153
165
 
154
166
  function generateUATTemplate(outputDir, config, scenarios) {
package/src/update.js CHANGED
@@ -1,6 +1,40 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
1
3
  import chalk from 'chalk';
2
- import { log } from './utils.js';
3
- import { checkForUpdate } from './update-check.js';
4
+ import { log, ROOT_DIR } from './utils.js';
5
+ import { checkForUpdate, compareVersions } from './update-check.js';
6
+
7
+ /**
8
+ * Check if the current directory has stale DevForge infrastructure.
9
+ * Returns { stale, installedVersion, cliVersion } or null if no infrastructure found.
10
+ */
11
+ function checkInfrastructure(projectDir) {
12
+ const versionFile = path.join(projectDir, '.claude', '.devforge-version');
13
+ if (!fs.existsSync(versionFile)) {
14
+ return null;
15
+ }
16
+
17
+ let pkg;
18
+ try {
19
+ pkg = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, 'package.json'), 'utf-8'));
20
+ } catch {
21
+ return null;
22
+ }
23
+
24
+ try {
25
+ const installed = JSON.parse(fs.readFileSync(versionFile, 'utf-8'));
26
+ if (typeof installed.version !== 'string') return null;
27
+ const stale = compareVersions(pkg.version, installed.version) > 0;
28
+ return {
29
+ stale,
30
+ installedVersion: installed.version,
31
+ cliVersion: pkg.version,
32
+ };
33
+ } catch {
34
+ // Corrupted version file — treat as stale
35
+ return { stale: true, installedVersion: 'unknown', cliVersion: pkg.version };
36
+ }
37
+ }
4
38
 
5
39
  export async function runUpdate() {
6
40
  console.log('');
@@ -18,16 +52,26 @@ export async function runUpdate() {
18
52
  if (!result.updateAvailable) {
19
53
  log.success(` ✓ You're on the latest version (${result.current})`);
20
54
  console.log('');
21
- return;
55
+ } else {
56
+ log.warn(` Update available: ${result.current} → ${result.latest}`);
57
+ console.log('');
58
+ console.log(' To update:');
59
+ console.log(` ${chalk.bold('npm install -g forgedev@latest')} ${chalk.dim('(if installed globally)')}`);
60
+ console.log(` ${chalk.bold('npx forgedev@latest new my-app')} ${chalk.dim('(one-time use)')}`);
61
+ console.log('');
22
62
  }
23
63
 
24
- log.warn(` Update available: ${result.current} ${result.latest}`);
25
- console.log('');
26
- console.log(' To update:');
27
- console.log(` ${chalk.bold('npm install -g forgedev@latest')} ${chalk.dim('(if installed globally)')}`);
28
- console.log(` ${chalk.bold('npx forgedev@latest new my-app')} ${chalk.dim('(one-time use)')}`);
29
- console.log('');
30
- log.dim(' To update an existing project\'s Claude Code infrastructure:');
31
- log.dim(' cd my-project && npx forgedev@latest init');
32
- console.log('');
64
+ // Check if current directory has stale DevForge infrastructure
65
+ const infra = checkInfrastructure(process.cwd());
66
+ if (infra && infra.stale) {
67
+ console.log(chalk.yellow(' ') + chalk.bold('Project infrastructure is outdated'));
68
+ console.log(chalk.dim(` Installed: v${infra.installedVersion} → CLI: v${infra.cliVersion}`));
69
+ console.log('');
70
+ console.log(' New commands, agents, and hooks are available but not deployed here.');
71
+ console.log(` Run ${chalk.bold.cyan('npx forgedev init')} to sync this project's .claude/ infrastructure.`);
72
+ console.log('');
73
+ } else if (infra && !infra.stale) {
74
+ log.success(` ✓ Project infrastructure is up to date (v${infra.installedVersion})`);
75
+ console.log('');
76
+ }
33
77
  }