forgedev 1.2.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -10
- package/bin/chainproof.js +126 -0
- package/bin/devforge.js +1 -1
- package/package.json +25 -7
- package/src/chainproof-bridge.js +330 -0
- package/src/ci-mode.js +85 -0
- package/src/claude-configurator.js +171 -78
- package/src/cli.js +30 -7
- package/src/composer.js +242 -214
- package/src/doctor-checks-chainproof.js +106 -0
- package/src/doctor-checks.js +39 -20
- package/src/doctor-prompts.js +9 -9
- package/src/doctor.js +37 -4
- package/src/guided.js +3 -3
- package/src/index.js +31 -10
- package/src/init-mode.js +76 -12
- package/src/menu.js +178 -0
- package/src/prompts.js +5 -12
- package/src/recommender.js +163 -30
- package/src/scanner.js +57 -2
- package/src/uat-generator.js +204 -189
- package/src/update-check.js +9 -4
- package/src/update.js +57 -13
- package/src/utils.js +162 -5
- package/templates/ai/guardrails-py/backend/app/ai/__init__.py +29 -0
- package/templates/ai/guardrails-py/backend/app/ai/audit_log.py +133 -0
- package/templates/ai/guardrails-py/backend/app/ai/client.py.template +323 -0
- package/templates/ai/guardrails-py/backend/app/ai/health.py.template +157 -0
- package/templates/ai/guardrails-py/backend/app/ai/input_guard.py +98 -0
- package/templates/ai/guardrails-ts/src/lib/ai/audit-log.ts.template +164 -0
- package/templates/ai/guardrails-ts/src/lib/ai/client.ts.template +403 -0
- package/templates/ai/guardrails-ts/src/lib/ai/health.ts.template +165 -0
- package/templates/ai/guardrails-ts/src/lib/ai/index.ts.template +17 -0
- package/templates/ai/guardrails-ts/src/lib/ai/input-guard.ts.template +124 -0
- package/templates/auth/nextauth/src/lib/auth.ts.template +12 -7
- package/templates/backend/express/Dockerfile.template +18 -0
- package/templates/backend/express/package.json.template +33 -0
- package/templates/backend/express/src/index.ts.template +34 -0
- package/templates/backend/express/src/routes/health.ts.template +27 -0
- package/templates/backend/express/tsconfig.json +17 -0
- package/templates/backend/fastapi/backend/Dockerfile.template +5 -0
- package/templates/backend/fastapi/backend/app/api/health.py.template +1 -1
- package/templates/backend/fastapi/backend/app/core/config.py.template +1 -1
- package/templates/backend/fastapi/backend/app/core/errors.py +1 -1
- package/templates/backend/fastapi/backend/app/main.py.template +3 -1
- package/templates/backend/fastapi/backend/requirements.txt.template +2 -0
- package/templates/backend/hono/Dockerfile.template +18 -0
- package/templates/backend/hono/package.json.template +31 -0
- package/templates/backend/hono/src/index.ts.template +32 -0
- package/templates/backend/hono/src/routes/health.ts.template +27 -0
- package/templates/backend/hono/tsconfig.json +18 -0
- package/templates/base/.gitignore.template +3 -0
- package/templates/base/docs/uat/UAT_TEMPLATE.md.template +1 -1
- package/templates/chainproof/base/.chainproof/config.json.template +11 -0
- package/templates/chainproof/base/.chainproof/mcp-server.mjs +310 -0
- package/templates/chainproof/base/.mcp.json +9 -0
- package/templates/chainproof/fastapi/.chainproof/middleware.json.template +14 -0
- package/templates/chainproof/nextjs/.chainproof/hooks.json.template +19 -0
- package/templates/chainproof/polyglot/.chainproof/config.json.template +21 -0
- package/templates/claude-code/agents/architect.md +25 -11
- package/templates/claude-code/agents/build-error-resolver.md +19 -5
- package/templates/claude-code/agents/chief-of-staff.md +42 -8
- package/templates/claude-code/agents/code-quality-reviewer.md +14 -0
- package/templates/claude-code/agents/database-reviewer.md +15 -1
- package/templates/claude-code/agents/deep-reviewer.md +191 -0
- package/templates/claude-code/agents/doc-updater.md +19 -5
- package/templates/claude-code/agents/docs-lookup.md +19 -5
- package/templates/claude-code/agents/e2e-runner.md +26 -12
- package/templates/claude-code/agents/enforcement-gate.md +102 -0
- package/templates/claude-code/agents/frontend-builder.md +188 -0
- package/templates/claude-code/agents/harness-optimizer.md +36 -1
- package/templates/claude-code/agents/loop-operator.md +27 -13
- package/templates/claude-code/agents/planner.md +21 -7
- package/templates/claude-code/agents/product-strategist.md +24 -10
- package/templates/claude-code/agents/production-readiness.md +14 -0
- package/templates/claude-code/agents/prompt-auditor.md +115 -0
- package/templates/claude-code/agents/refactor-cleaner.md +22 -8
- package/templates/claude-code/agents/security-reviewer.md +14 -0
- package/templates/claude-code/agents/spec-validator.md +15 -1
- package/templates/claude-code/agents/tdd-guide.md +21 -7
- package/templates/claude-code/agents/uat-validator.md +14 -0
- package/templates/claude-code/claude-md/base.md +14 -7
- package/templates/claude-code/claude-md/fastapi.md +8 -8
- package/templates/claude-code/claude-md/fullstack.md +6 -6
- package/templates/claude-code/claude-md/hono.md +18 -0
- package/templates/claude-code/claude-md/nextjs.md +5 -5
- package/templates/claude-code/claude-md/remix.md +18 -0
- package/templates/claude-code/commands/audit-security.md +14 -0
- package/templates/claude-code/commands/audit-spec.md +14 -0
- package/templates/claude-code/commands/audit-wiring.md +14 -0
- package/templates/claude-code/commands/build-fix.md +28 -0
- package/templates/claude-code/commands/build-ui.md +59 -0
- package/templates/claude-code/commands/code-review.md +53 -31
- package/templates/claude-code/commands/fix-loop.md +211 -0
- package/templates/claude-code/commands/full-audit.md +36 -8
- package/templates/claude-code/commands/generate-prd.md +1 -1
- package/templates/claude-code/commands/generate-sdd.md +74 -0
- package/templates/claude-code/commands/generate-uat.md +107 -35
- package/templates/claude-code/commands/help.md +68 -0
- package/templates/claude-code/commands/live-uat.md +268 -0
- package/templates/claude-code/commands/optimize-claude-md.md +15 -1
- package/templates/claude-code/commands/plan.md +3 -3
- package/templates/claude-code/commands/pre-pr.md +57 -19
- package/templates/claude-code/commands/product-strategist.md +21 -0
- package/templates/claude-code/commands/resume-session.md +10 -10
- package/templates/claude-code/commands/run-uat.md +59 -2
- package/templates/claude-code/commands/save-session.md +10 -10
- package/templates/claude-code/commands/simplify.md +36 -0
- package/templates/claude-code/commands/tdd.md +17 -18
- package/templates/claude-code/commands/verify-all.md +24 -0
- package/templates/claude-code/commands/verify-intent.md +55 -0
- package/templates/claude-code/commands/workflows.md +52 -40
- package/templates/claude-code/hooks/polyglot.json +10 -1
- package/templates/claude-code/hooks/python.json +10 -1
- package/templates/claude-code/hooks/scripts/autofix-polyglot.mjs +2 -2
- package/templates/claude-code/hooks/scripts/autofix-python.mjs +1 -1
- package/templates/claude-code/hooks/scripts/autofix-typescript.mjs +1 -1
- package/templates/claude-code/hooks/scripts/code-hygiene.mjs +293 -0
- package/templates/claude-code/hooks/scripts/pre-commit-gate.mjs +207 -0
- package/templates/claude-code/hooks/typescript.json +10 -1
- package/templates/claude-code/skills/ai-prompts/SKILL.md +119 -41
- package/templates/claude-code/skills/git-workflow/SKILL.md +5 -5
- package/templates/claude-code/skills/nextjs/SKILL.md +1 -1
- package/templates/claude-code/skills/playwright/SKILL.md +5 -5
- package/templates/claude-code/skills/security-api/SKILL.md +1 -1
- package/templates/claude-code/skills/security-web/SKILL.md +1 -1
- package/templates/claude-code/skills/testing-patterns/SKILL.md +9 -9
- package/templates/database/prisma-postgres/{.env.example → .env.example.template} +1 -0
- package/templates/database/sqlalchemy-postgres/{.env.example → .env.example.template} +1 -0
- package/templates/docs-portal/fastapi/backend/app/portal/__pycache__/docs_reader.cpython-314.pyc +0 -0
- package/templates/docs-portal/fastapi/backend/app/portal/docs_reader.py +201 -0
- package/templates/docs-portal/fastapi/backend/app/portal/html_renderer.py +229 -0
- package/templates/docs-portal/fastapi/backend/app/portal/router.py.template +35 -0
- package/templates/docs-portal/nextjs/src/app/portal/[category]/[slug]/page.tsx +81 -0
- package/templates/docs-portal/nextjs/src/app/portal/[category]/page.tsx +65 -0
- package/templates/docs-portal/nextjs/src/app/portal/layout.tsx.template +54 -0
- package/templates/docs-portal/nextjs/src/app/portal/page.tsx +85 -0
- package/templates/docs-portal/nextjs/src/components/portal/markdown-renderer.tsx +101 -0
- package/templates/docs-portal/nextjs/src/components/portal/mobile-portal-nav.tsx +81 -0
- package/templates/docs-portal/nextjs/src/components/portal/portal-nav.tsx +86 -0
- package/templates/docs-portal/nextjs/src/lib/docs.ts +139 -0
- package/templates/frontend/nextjs/package.json.template +3 -1
- package/templates/frontend/react/index.html.template +12 -0
- package/templates/frontend/react/package.json.template +34 -0
- package/templates/frontend/react/src/App.tsx.template +10 -0
- package/templates/frontend/react/src/index.css +1 -0
- package/templates/frontend/react/src/main.tsx +10 -0
- package/templates/frontend/react/tsconfig.json +17 -0
- package/templates/frontend/react/vite.config.ts.template +15 -0
- package/templates/frontend/react/vitest.config.ts +9 -0
- package/templates/frontend/remix/app/root.tsx.template +31 -0
- package/templates/frontend/remix/app/routes/_index.tsx.template +19 -0
- package/templates/frontend/remix/app/routes/api.health.ts.template +10 -0
- package/templates/frontend/remix/app/tailwind.css +1 -0
- package/templates/frontend/remix/package.json.template +39 -0
- package/templates/frontend/remix/tsconfig.json +18 -0
- package/templates/frontend/remix/vite.config.ts.template +7 -0
- package/templates/infra/github-actions/.github/workflows/ci.yml.template +3 -0
- package/templates/infra/k8s/k8s/deployment.yml.template +70 -0
- package/templates/infra/k8s/k8s/hpa.yml.template +24 -0
- package/templates/infra/k8s/k8s/ingress.yml.template +26 -0
- package/templates/infra/k8s/k8s/kustomization.yml.template +13 -0
- package/templates/infra/k8s/k8s/namespace.yml.template +4 -0
- package/templates/infra/k8s/k8s/networkpolicy.yml.template +41 -0
- package/templates/infra/k8s/k8s/secrets.yml.template +10 -0
- package/templates/infra/k8s/k8s/service.yml.template +15 -0
- package/templates/testing/load/k6/README.md.template +48 -0
- package/templates/testing/load/k6/load-test.js.template +57 -0
- package/docs/00-README.md +0 -310
- package/docs/01-universal-prompt-library.md +0 -1049
- package/docs/02-claude-code-mastery-playbook.md +0 -283
- package/docs/03-multi-agent-verification.md +0 -565
- package/docs/04-errata-and-verification-checklist.md +0 -284
- package/docs/05-universal-scaffolder-vision.md +0 -452
- package/docs/06-confidence-assessment-and-repo-prompt.md +0 -407
- package/docs/errata.md +0 -58
- package/docs/multi-agent-verification.md +0 -66
- package/docs/playbook.md +0 -95
- package/docs/prompt-library.md +0 -160
- package/docs/uat/UAT_CHECKLIST.csv +0 -9
- package/docs/uat/UAT_TEMPLATE.md +0 -163
- package/templates/claude-code/commands/done.md +0 -19
- /package/{docs/plans/.gitkeep → templates/docs-portal/fastapi/backend/app/portal/__init__.py} +0 -0
package/src/doctor-checks.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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,
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
446
|
+
function checkUnusedDependencies(projectDir, _scan) {
|
|
434
447
|
const pkgPath = path.join(projectDir, 'package.json');
|
|
435
448
|
if (!fs.existsSync(pkgPath)) return [];
|
|
436
449
|
|
|
437
|
-
|
|
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
|
|
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
|
|
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
|
+
|
package/src/doctor-prompts.js
CHANGED
|
@@ -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
|
|
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
|
|
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:
|
|
58
|
+
- Never use bare except: because it hides bugs`;
|
|
59
59
|
},
|
|
60
60
|
|
|
61
|
-
MISSING_HEALTH: (
|
|
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: (
|
|
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
|
|
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: (
|
|
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
|
|
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
|
|
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('
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
16
|
-
if (!/^[a-z0-9][a-z0-9
|
|
17
|
-
log.error('Project name must start with a letter
|
|
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('
|
|
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 ?
|
|
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
|
-
|
|
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('
|
|
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(
|
|
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(
|
|
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('
|
|
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
|
|
59
|
+
console.log(chalk.green(' ✓ ') + 'CLAUDE.md (project context + rules)');
|
|
58
60
|
} else {
|
|
59
|
-
console.log(chalk.yellow(' ⊘ ') + `CLAUDE.md
|
|
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/
|
|
65
|
+
console.log(chalk.green(' ✓ ') + '.claude/hooks/ (auto-lint, quality gate, file protection)');
|
|
64
66
|
} else {
|
|
65
|
-
console.log(chalk.yellow(' ⊘ ') + '.claude/hooks/
|
|
67
|
+
console.log(chalk.yellow(' ⊘ ') + '.claude/hooks/ already configured');
|
|
66
68
|
}
|
|
67
69
|
|
|
68
70
|
if (!scan.infrastructure.hasAgents) {
|
|
69
|
-
console.log(chalk.green(' ✓ ') +
|
|
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(' ✓ ') +
|
|
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/
|
|
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/
|
|
94
|
+
console.log(chalk.green(' ✓ ') + 'docs/uat/ (acceptance test templates)');
|
|
82
95
|
} else {
|
|
83
|
-
console.log(chalk.yellow(' ⊘ ') + 'docs/uat/
|
|
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
|
+
}
|