forgedev 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/README.md +57 -10
  2. package/bin/chainproof.js +126 -0
  3. package/package.json +25 -7
  4. package/src/chainproof-bridge.js +330 -0
  5. package/src/ci-mode.js +85 -0
  6. package/src/claude-configurator.js +86 -49
  7. package/src/cli.js +30 -7
  8. package/src/composer.js +159 -34
  9. package/src/doctor-checks-chainproof.js +106 -0
  10. package/src/doctor-checks.js +39 -20
  11. package/src/doctor-prompts.js +9 -9
  12. package/src/doctor.js +37 -4
  13. package/src/guided.js +3 -3
  14. package/src/index.js +31 -10
  15. package/src/init-mode.js +64 -11
  16. package/src/menu.js +178 -0
  17. package/src/prompts.js +5 -12
  18. package/src/recommender.js +134 -10
  19. package/src/scanner.js +57 -2
  20. package/src/uat-generator.js +204 -189
  21. package/src/update-check.js +9 -4
  22. package/src/update.js +1 -1
  23. package/src/utils.js +64 -5
  24. package/templates/ai/guardrails-py/backend/app/ai/__init__.py +29 -0
  25. package/templates/ai/guardrails-py/backend/app/ai/audit_log.py +133 -0
  26. package/templates/ai/guardrails-py/backend/app/ai/client.py.template +323 -0
  27. package/templates/ai/guardrails-py/backend/app/ai/health.py.template +157 -0
  28. package/templates/ai/guardrails-py/backend/app/ai/input_guard.py +98 -0
  29. package/templates/ai/guardrails-ts/src/lib/ai/audit-log.ts.template +164 -0
  30. package/templates/ai/guardrails-ts/src/lib/ai/client.ts.template +403 -0
  31. package/templates/ai/guardrails-ts/src/lib/ai/health.ts.template +165 -0
  32. package/templates/ai/guardrails-ts/src/lib/ai/index.ts.template +17 -0
  33. package/templates/ai/guardrails-ts/src/lib/ai/input-guard.ts.template +124 -0
  34. package/templates/auth/nextauth/src/lib/auth.ts.template +12 -7
  35. package/templates/backend/express/Dockerfile.template +18 -0
  36. package/templates/backend/express/package.json.template +33 -0
  37. package/templates/backend/express/src/index.ts.template +34 -0
  38. package/templates/backend/express/src/routes/health.ts.template +27 -0
  39. package/templates/backend/express/tsconfig.json +17 -0
  40. package/templates/backend/fastapi/backend/Dockerfile.template +5 -0
  41. package/templates/backend/fastapi/backend/app/api/health.py.template +1 -1
  42. package/templates/backend/fastapi/backend/app/core/config.py.template +1 -1
  43. package/templates/backend/fastapi/backend/app/core/errors.py +1 -1
  44. package/templates/backend/fastapi/backend/app/main.py.template +3 -1
  45. package/templates/backend/fastapi/backend/requirements.txt.template +2 -0
  46. package/templates/backend/hono/Dockerfile.template +18 -0
  47. package/templates/backend/hono/package.json.template +31 -0
  48. package/templates/backend/hono/src/index.ts.template +32 -0
  49. package/templates/backend/hono/src/routes/health.ts.template +27 -0
  50. package/templates/backend/hono/tsconfig.json +18 -0
  51. package/templates/base/docs/uat/UAT_TEMPLATE.md.template +1 -1
  52. package/templates/chainproof/base/.chainproof/config.json.template +11 -0
  53. package/templates/chainproof/base/.chainproof/mcp-server.mjs +310 -0
  54. package/templates/chainproof/base/.mcp.json +9 -0
  55. package/templates/chainproof/fastapi/.chainproof/middleware.json.template +14 -0
  56. package/templates/chainproof/nextjs/.chainproof/hooks.json.template +19 -0
  57. package/templates/chainproof/polyglot/.chainproof/config.json.template +21 -0
  58. package/templates/claude-code/agents/architect.md +25 -11
  59. package/templates/claude-code/agents/build-error-resolver.md +19 -5
  60. package/templates/claude-code/agents/chief-of-staff.md +42 -8
  61. package/templates/claude-code/agents/code-quality-reviewer.md +14 -0
  62. package/templates/claude-code/agents/database-reviewer.md +15 -1
  63. package/templates/claude-code/agents/deep-reviewer.md +191 -0
  64. package/templates/claude-code/agents/doc-updater.md +19 -5
  65. package/templates/claude-code/agents/docs-lookup.md +19 -5
  66. package/templates/claude-code/agents/e2e-runner.md +26 -12
  67. package/templates/claude-code/agents/enforcement-gate.md +102 -0
  68. package/templates/claude-code/agents/frontend-builder.md +188 -0
  69. package/templates/claude-code/agents/harness-optimizer.md +36 -1
  70. package/templates/claude-code/agents/loop-operator.md +27 -13
  71. package/templates/claude-code/agents/planner.md +21 -7
  72. package/templates/claude-code/agents/product-strategist.md +24 -10
  73. package/templates/claude-code/agents/production-readiness.md +14 -0
  74. package/templates/claude-code/agents/prompt-auditor.md +115 -0
  75. package/templates/claude-code/agents/refactor-cleaner.md +22 -8
  76. package/templates/claude-code/agents/security-reviewer.md +14 -0
  77. package/templates/claude-code/agents/spec-validator.md +15 -1
  78. package/templates/claude-code/agents/tdd-guide.md +21 -7
  79. package/templates/claude-code/agents/uat-validator.md +14 -0
  80. package/templates/claude-code/claude-md/base.md +14 -7
  81. package/templates/claude-code/claude-md/fastapi.md +8 -8
  82. package/templates/claude-code/claude-md/fullstack.md +6 -6
  83. package/templates/claude-code/claude-md/hono.md +18 -0
  84. package/templates/claude-code/claude-md/nextjs.md +5 -5
  85. package/templates/claude-code/claude-md/remix.md +18 -0
  86. package/templates/claude-code/commands/audit-security.md +14 -0
  87. package/templates/claude-code/commands/audit-spec.md +14 -0
  88. package/templates/claude-code/commands/audit-wiring.md +14 -0
  89. package/templates/claude-code/commands/build-fix.md +28 -0
  90. package/templates/claude-code/commands/build-ui.md +59 -0
  91. package/templates/claude-code/commands/code-review.md +53 -31
  92. package/templates/claude-code/commands/fix-loop.md +211 -0
  93. package/templates/claude-code/commands/full-audit.md +36 -8
  94. package/templates/claude-code/commands/generate-prd.md +1 -1
  95. package/templates/claude-code/commands/generate-sdd.md +74 -0
  96. package/templates/claude-code/commands/generate-uat.md +107 -35
  97. package/templates/claude-code/commands/help.md +68 -0
  98. package/templates/claude-code/commands/live-uat.md +268 -0
  99. package/templates/claude-code/commands/optimize-claude-md.md +15 -1
  100. package/templates/claude-code/commands/plan.md +3 -3
  101. package/templates/claude-code/commands/pre-pr.md +57 -19
  102. package/templates/claude-code/commands/product-strategist.md +21 -0
  103. package/templates/claude-code/commands/resume-session.md +10 -10
  104. package/templates/claude-code/commands/run-uat.md +59 -2
  105. package/templates/claude-code/commands/save-session.md +10 -10
  106. package/templates/claude-code/commands/simplify.md +36 -0
  107. package/templates/claude-code/commands/tdd.md +17 -18
  108. package/templates/claude-code/commands/verify-all.md +24 -0
  109. package/templates/claude-code/commands/verify-intent.md +55 -0
  110. package/templates/claude-code/commands/workflows.md +52 -40
  111. package/templates/claude-code/hooks/polyglot.json +10 -1
  112. package/templates/claude-code/hooks/python.json +10 -1
  113. package/templates/claude-code/hooks/scripts/autofix-polyglot.mjs +2 -2
  114. package/templates/claude-code/hooks/scripts/autofix-python.mjs +1 -1
  115. package/templates/claude-code/hooks/scripts/autofix-typescript.mjs +1 -1
  116. package/templates/claude-code/hooks/scripts/code-hygiene.mjs +293 -0
  117. package/templates/claude-code/hooks/scripts/pre-commit-gate.mjs +207 -0
  118. package/templates/claude-code/hooks/typescript.json +10 -1
  119. package/templates/claude-code/skills/ai-prompts/SKILL.md +119 -41
  120. package/templates/claude-code/skills/git-workflow/SKILL.md +5 -5
  121. package/templates/claude-code/skills/nextjs/SKILL.md +1 -1
  122. package/templates/claude-code/skills/playwright/SKILL.md +5 -5
  123. package/templates/claude-code/skills/security-api/SKILL.md +1 -1
  124. package/templates/claude-code/skills/security-web/SKILL.md +1 -1
  125. package/templates/claude-code/skills/testing-patterns/SKILL.md +9 -9
  126. package/templates/database/prisma-postgres/{.env.example → .env.example.template} +1 -0
  127. package/templates/database/sqlalchemy-postgres/{.env.example → .env.example.template} +1 -0
  128. package/templates/docs-portal/fastapi/backend/app/portal/__pycache__/docs_reader.cpython-314.pyc +0 -0
  129. package/templates/docs-portal/fastapi/backend/app/portal/docs_reader.py +201 -0
  130. package/templates/docs-portal/fastapi/backend/app/portal/html_renderer.py +229 -0
  131. package/templates/docs-portal/fastapi/backend/app/portal/router.py.template +35 -0
  132. package/templates/docs-portal/nextjs/src/app/portal/[category]/[slug]/page.tsx +81 -0
  133. package/templates/docs-portal/nextjs/src/app/portal/[category]/page.tsx +65 -0
  134. package/templates/docs-portal/nextjs/src/app/portal/layout.tsx.template +54 -0
  135. package/templates/docs-portal/nextjs/src/app/portal/page.tsx +85 -0
  136. package/templates/docs-portal/nextjs/src/components/portal/markdown-renderer.tsx +101 -0
  137. package/templates/docs-portal/nextjs/src/components/portal/mobile-portal-nav.tsx +81 -0
  138. package/templates/docs-portal/nextjs/src/components/portal/portal-nav.tsx +86 -0
  139. package/templates/docs-portal/nextjs/src/lib/docs.ts +139 -0
  140. package/templates/frontend/nextjs/package.json.template +3 -1
  141. package/templates/frontend/react/index.html.template +12 -0
  142. package/templates/frontend/react/package.json.template +34 -0
  143. package/templates/frontend/react/src/App.tsx.template +10 -0
  144. package/templates/frontend/react/src/index.css +1 -0
  145. package/templates/frontend/react/src/main.tsx +10 -0
  146. package/templates/frontend/react/tsconfig.json +17 -0
  147. package/templates/frontend/react/vite.config.ts.template +15 -0
  148. package/templates/frontend/react/vitest.config.ts +9 -0
  149. package/templates/frontend/remix/app/root.tsx.template +31 -0
  150. package/templates/frontend/remix/app/routes/_index.tsx.template +19 -0
  151. package/templates/frontend/remix/app/routes/api.health.ts.template +10 -0
  152. package/templates/frontend/remix/app/tailwind.css +1 -0
  153. package/templates/frontend/remix/package.json.template +39 -0
  154. package/templates/frontend/remix/tsconfig.json +18 -0
  155. package/templates/frontend/remix/vite.config.ts.template +7 -0
  156. package/templates/infra/github-actions/.github/workflows/ci.yml.template +3 -0
  157. package/docs/00-README.md +0 -310
  158. package/docs/01-universal-prompt-library.md +0 -1049
  159. package/docs/02-claude-code-mastery-playbook.md +0 -283
  160. package/docs/03-multi-agent-verification.md +0 -565
  161. package/docs/04-errata-and-verification-checklist.md +0 -284
  162. package/docs/05-universal-scaffolder-vision.md +0 -452
  163. package/docs/06-confidence-assessment-and-repo-prompt.md +0 -407
  164. package/docs/errata.md +0 -58
  165. package/docs/multi-agent-verification.md +0 -66
  166. package/docs/playbook.md +0 -95
  167. package/docs/prompt-library.md +0 -160
  168. package/docs/uat/UAT_CHECKLIST.csv +0 -9
  169. package/docs/uat/UAT_TEMPLATE.md +0 -163
  170. package/templates/claude-code/commands/done.md +0 -19
  171. /package/{docs/plans/.gitkeep → templates/docs-portal/fastapi/backend/app/portal/__init__.py} +0 -0
@@ -0,0 +1,330 @@
1
+ /**
2
+ * ChainProof bridge - pure Node.js trust chain operations.
3
+ * Uses node:crypto for Ed25519 + SHA-256, no Python dependency needed.
4
+ * All operations are file-based, reading and writing to .chainproof/.
5
+ */
6
+
7
+ import fs from 'node:fs';
8
+ import path from 'node:path';
9
+ import { createHash, generateKeyPairSync, sign, verify, randomUUID } from 'node:crypto';
10
+
11
+ // --- Crypto primitives (interoperable with chainproof/core/crypto.py) ---
12
+
13
+ export function hashContent(content) {
14
+ return createHash('sha256').update(content, 'utf-8').digest('hex');
15
+ }
16
+
17
+ export function buildSignablePayload(entry) {
18
+ // Length-prefixed using UTF-8 byte length for cross-language interop.
19
+ // JS .length and Python len() diverge on emoji; Buffer.byteLength is stable.
20
+ const fields = [
21
+ entry.prevHash,
22
+ entry.contentHash,
23
+ entry.timestamp,
24
+ entry.entryType,
25
+ entry.sessionId,
26
+ ];
27
+ return fields.map((f) => {
28
+ const s = String(f ?? '');
29
+ return `${Buffer.byteLength(s, 'utf-8')}:${s}`;
30
+ }).join(':');
31
+ }
32
+
33
+ export function signEntry(payload, privateKeyPem) {
34
+ const signature = sign(null, Buffer.from(payload, 'utf-8'), privateKeyPem);
35
+ return signature.toString('base64');
36
+ }
37
+
38
+ export function verifySignature(payload, signatureB64, publicKeyPem) {
39
+ try {
40
+ return verify(
41
+ null,
42
+ Buffer.from(payload, 'utf-8'),
43
+ publicKeyPem,
44
+ Buffer.from(signatureB64, 'base64')
45
+ );
46
+ } catch {
47
+ // Invalid key or malformed signature
48
+ return false;
49
+ }
50
+ }
51
+
52
+ export function computeChainHash(prevHash, contentHash) {
53
+ return createHash('sha256')
54
+ .update(prevHash + contentHash, 'utf-8')
55
+ .digest('hex');
56
+ }
57
+
58
+ export function generateKeypair() {
59
+ const { publicKey, privateKey } = generateKeyPairSync('ed25519', {
60
+ publicKeyEncoding: { type: 'spki', format: 'pem' },
61
+ privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
62
+ });
63
+ return { publicKey, privateKey };
64
+ }
65
+
66
+ // --- High-level chain operations ---
67
+
68
+ const GENESIS_HASH = '0'.repeat(64);
69
+
70
+ export function initChainproof(projectDir) {
71
+ const cpDir = path.join(projectDir, '.chainproof');
72
+ const keysDir = path.join(cpDir, 'keys');
73
+
74
+ fs.mkdirSync(cpDir, { recursive: true });
75
+ fs.mkdirSync(keysDir, { recursive: true });
76
+
77
+ // Config — merge with any template-composed config (from templates/chainproof/)
78
+ const configPath = path.join(cpDir, 'config.json');
79
+ const projectName = path.basename(projectDir);
80
+ const defaults = {
81
+ version: '1.0.0',
82
+ projectName,
83
+ hashAlgorithm: 'sha256',
84
+ signatureAlgorithm: 'ed25519',
85
+ createdAt: new Date().toISOString(),
86
+ };
87
+
88
+ if (fs.existsSync(configPath)) {
89
+ // Template already composed a config — merge our defaults under it
90
+ try {
91
+ const existing = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
92
+ const merged = { ...defaults, ...existing, createdAt: existing.createdAt || defaults.createdAt };
93
+ fs.writeFileSync(configPath, JSON.stringify(merged, null, 2) + '\n', 'utf-8');
94
+ } catch {
95
+ // Corrupted config, overwrite with defaults
96
+ fs.writeFileSync(configPath, JSON.stringify(defaults, null, 2) + '\n', 'utf-8');
97
+ }
98
+ } else {
99
+ fs.writeFileSync(configPath, JSON.stringify(defaults, null, 2) + '\n', 'utf-8');
100
+ }
101
+
102
+ // Chain — only create if missing
103
+ const chainPath = path.join(cpDir, 'chain.json');
104
+ if (!fs.existsSync(chainPath)) {
105
+ fs.writeFileSync(
106
+ chainPath,
107
+ JSON.stringify({ entries: [], currentHash: GENESIS_HASH }, null, 2) + '\n',
108
+ 'utf-8'
109
+ );
110
+ }
111
+
112
+ // Artifacts — only create if missing
113
+ const artifactsPath = path.join(cpDir, 'artifacts.json');
114
+ if (!fs.existsSync(artifactsPath)) {
115
+ fs.writeFileSync(
116
+ artifactsPath,
117
+ JSON.stringify({ artifacts: [] }, null, 2) + '\n',
118
+ 'utf-8'
119
+ );
120
+ }
121
+
122
+ // Generate keypair only if missing
123
+ const privateKeyPath = path.join(keysDir, 'private.pem');
124
+ const publicKeyPath = path.join(keysDir, 'public.pem');
125
+ if (!fs.existsSync(privateKeyPath) || !fs.existsSync(publicKeyPath)) {
126
+ const { publicKey, privateKey } = generateKeypair();
127
+ fs.writeFileSync(privateKeyPath, privateKey, { encoding: 'utf-8', mode: 0o600 });
128
+ fs.writeFileSync(publicKeyPath, publicKey, 'utf-8');
129
+ }
130
+
131
+ // Gitignore for keys
132
+ const gitignorePath = path.join(cpDir, '.gitignore');
133
+ if (!fs.existsSync(gitignorePath)) {
134
+ fs.writeFileSync(gitignorePath, 'keys/\n', 'utf-8');
135
+ }
136
+ }
137
+
138
+ export function recordDecision(projectDir, entry) {
139
+ if (!entry || typeof entry.content !== 'string') {
140
+ throw new Error('entry.content must be a non-empty string');
141
+ }
142
+ if (entry.content.length > 1_048_576) {
143
+ throw new Error('entry.content exceeds 1MB limit');
144
+ }
145
+
146
+ const cpDir = path.join(projectDir, '.chainproof');
147
+ const chainPath = path.join(cpDir, 'chain.json');
148
+
149
+ let chain;
150
+ try {
151
+ chain = JSON.parse(fs.readFileSync(chainPath, 'utf-8'));
152
+ } catch (err) {
153
+ throw new Error(`Failed to read chain.json: ${err.message}`, { cause: err });
154
+ }
155
+
156
+ const contentHash = hashContent(entry.content);
157
+ const prevHash = chain.currentHash;
158
+ const chainHash = computeChainHash(prevHash, contentHash);
159
+ const timestamp = new Date().toISOString();
160
+ const entryType = entry.entryType || 'decision';
161
+ const sessionId = entry.sessionId || 'default';
162
+
163
+ // Build the full entry before signing so all metadata is covered
164
+ const nllEntry = {
165
+ id: randomUUID(),
166
+ timestamp,
167
+ entryType,
168
+ content: entry.content,
169
+ contentHash,
170
+ prevHash,
171
+ chainHash,
172
+ signature: null,
173
+ sessionId,
174
+ };
175
+
176
+ // Sign the full canonical payload (not just content)
177
+ const privateKeyPath = path.join(cpDir, 'keys', 'private.pem');
178
+ if (fs.existsSync(privateKeyPath)) {
179
+ const privateKey = fs.readFileSync(privateKeyPath, 'utf-8');
180
+ const payload = buildSignablePayload(nllEntry);
181
+ nllEntry.signature = signEntry(payload, privateKey);
182
+ }
183
+
184
+ chain.entries.push(nllEntry);
185
+ chain.currentHash = chainHash;
186
+
187
+ // Atomic write: write to temp file first, then rename
188
+ const tmpPath = chainPath + '.tmp';
189
+ fs.writeFileSync(tmpPath, JSON.stringify(chain, null, 2) + '\n', 'utf-8');
190
+ fs.renameSync(tmpPath, chainPath);
191
+
192
+ return nllEntry;
193
+ }
194
+
195
+ export function recordCodeArtifact(projectDir, artifact) {
196
+ const cpDir = path.join(projectDir, '.chainproof');
197
+ const artifactsPath = path.join(cpDir, 'artifacts.json');
198
+
199
+ let data;
200
+ try {
201
+ data = JSON.parse(fs.readFileSync(artifactsPath, 'utf-8'));
202
+ } catch (err) {
203
+ throw new Error(`Failed to read artifacts.json: ${err.message}`, { cause: err });
204
+ }
205
+
206
+ const record = {
207
+ id: randomUUID(),
208
+ timestamp: new Date().toISOString(),
209
+ filePath: artifact.filePath,
210
+ contentHash: artifact.contentHash || hashContent(artifact.content || ''),
211
+ language: artifact.language || null,
212
+ generator: artifact.generator || null,
213
+ promptHash: artifact.promptHash || null,
214
+ nllEntryId: artifact.nllEntryId || null,
215
+ };
216
+
217
+ data.artifacts.push(record);
218
+
219
+ // Atomic write: write to temp file first, then rename
220
+ const tmpPath = artifactsPath + '.tmp';
221
+ fs.writeFileSync(tmpPath, JSON.stringify(data, null, 2) + '\n', 'utf-8');
222
+ fs.renameSync(tmpPath, artifactsPath);
223
+
224
+ return record;
225
+ }
226
+
227
+ export function verifyChain(projectDir) {
228
+ const cpDir = path.join(projectDir, '.chainproof');
229
+ const chainPath = path.join(cpDir, 'chain.json');
230
+
231
+ if (!fs.existsSync(chainPath)) {
232
+ return { valid: false, errors: ['chain.json not found'] };
233
+ }
234
+
235
+ const chain = JSON.parse(fs.readFileSync(chainPath, 'utf-8'));
236
+ const errors = [];
237
+ let expectedHash = GENESIS_HASH;
238
+
239
+ for (let i = 0; i < chain.entries.length; i++) {
240
+ const entry = chain.entries[i];
241
+
242
+ // Verify prev_hash links correctly
243
+ if (entry.prevHash !== expectedHash) {
244
+ errors.push(
245
+ `Entry ${i}: prevHash mismatch (expected ${expectedHash.slice(0, 8)}..., got ${entry.prevHash.slice(0, 8)}...)`
246
+ );
247
+ }
248
+
249
+ // Verify content hash
250
+ const actualContentHash = hashContent(entry.content);
251
+ if (entry.contentHash !== actualContentHash) {
252
+ errors.push(`Entry ${i}: contentHash mismatch (content was tampered)`);
253
+ }
254
+
255
+ // Verify chain hash
256
+ const actualChainHash = computeChainHash(entry.prevHash, entry.contentHash);
257
+ if (entry.chainHash !== actualChainHash) {
258
+ errors.push(`Entry ${i}: chainHash mismatch`);
259
+ }
260
+
261
+ // Verify signature covers full payload (not just content)
262
+ if (entry.signature) {
263
+ const publicKeyPath = path.join(cpDir, 'keys', 'public.pem');
264
+ if (fs.existsSync(publicKeyPath)) {
265
+ const publicKey = fs.readFileSync(publicKeyPath, 'utf-8');
266
+ const payload = buildSignablePayload(entry);
267
+ if (!verifySignature(payload, entry.signature, publicKey)) {
268
+ errors.push(`Entry ${i}: invalid signature`);
269
+ }
270
+ }
271
+ }
272
+
273
+ expectedHash = entry.chainHash;
274
+ }
275
+
276
+ // Verify current hash matches last entry
277
+ if (chain.entries.length > 0 && chain.currentHash !== expectedHash) {
278
+ errors.push('currentHash does not match last entry chainHash');
279
+ }
280
+
281
+ return { valid: errors.length === 0, errors };
282
+ }
283
+
284
+ export function getChainStatus(projectDir) {
285
+ const cpDir = path.join(projectDir, '.chainproof');
286
+ const chainPath = path.join(cpDir, 'chain.json');
287
+ const configPath = path.join(cpDir, 'config.json');
288
+
289
+ if (!fs.existsSync(cpDir)) {
290
+ return { initialized: false };
291
+ }
292
+
293
+ let chain;
294
+ try {
295
+ chain = JSON.parse(fs.readFileSync(chainPath, 'utf-8'));
296
+ } catch (err) {
297
+ throw new Error(`Failed to read chain.json: ${err.message}`, { cause: err });
298
+ }
299
+ let config = {};
300
+ if (fs.existsSync(configPath)) {
301
+ try {
302
+ config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
303
+ } catch (err) {
304
+ throw new Error(`Failed to read config.json: ${err.message}`, { cause: err });
305
+ }
306
+ }
307
+
308
+ const artifactsPath = path.join(cpDir, 'artifacts.json');
309
+ let artifacts = { artifacts: [] };
310
+ if (fs.existsSync(artifactsPath)) {
311
+ try {
312
+ artifacts = JSON.parse(fs.readFileSync(artifactsPath, 'utf-8'));
313
+ } catch (err) {
314
+ throw new Error(`Failed to read artifacts.json: ${err.message}`, { cause: err });
315
+ }
316
+ }
317
+
318
+ const unsignedCount = chain.entries.filter((e) => !e.signature).length;
319
+
320
+ return {
321
+ initialized: true,
322
+ projectName: config.projectName || path.basename(projectDir),
323
+ entryCount: chain.entries.length,
324
+ artifactCount: artifacts.artifacts.length,
325
+ currentHash: chain.currentHash,
326
+ unsignedEntries: unsignedCount,
327
+ createdAt: config.createdAt || null,
328
+ lastEntry: chain.entries.length > 0 ? chain.entries[chain.entries.length - 1].timestamp : null,
329
+ };
330
+ }
package/src/ci-mode.js ADDED
@@ -0,0 +1,85 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import chalk from 'chalk';
4
+ import { log } from './utils.js';
5
+ import { scanProject, detectStack } from './scanner.js';
6
+ import { runAllChecks } from './doctor-checks.js';
7
+ import { generateReport } from './doctor-prompts.js';
8
+
9
+ export async function runCI(projectDir) {
10
+ const resolvedDir = path.resolve(projectDir);
11
+
12
+ if (!fs.existsSync(resolvedDir)) {
13
+ log.error(`Directory not found: ${resolvedDir}`);
14
+ process.exit(1);
15
+ }
16
+
17
+ console.log('');
18
+ log.info(' DevForge CI Automated project health check');
19
+ console.log('');
20
+
21
+ // Scan project
22
+ const scan = scanProject(resolvedDir);
23
+ const stack = detectStack(resolvedDir, scan);
24
+ log.dim(` Stack: ${stack || 'unknown'}`);
25
+
26
+ // Run all checks
27
+ const issues = runAllChecks(resolvedDir, scan);
28
+
29
+ const critical = issues.filter(i => i.severity === 'critical');
30
+ const warnings = issues.filter(i => i.severity === 'warning');
31
+ const info = issues.filter(i => i.severity === 'info');
32
+
33
+ // Output results
34
+ console.log('');
35
+ if (critical.length > 0) {
36
+ log.error(` CRITICAL: ${critical.length} issue${critical.length > 1 ? 's' : ''}`);
37
+ for (const issue of critical) {
38
+ console.error(` - ${issue.title}`);
39
+ if (issue.files?.length) {
40
+ for (const detail of issue.files.slice(0, 5)) {
41
+ console.error(chalk.dim(` ${detail}`));
42
+ }
43
+ if (issue.files.length > 5) {
44
+ console.error(chalk.dim(` ... and ${issue.files.length - 5} more`));
45
+ }
46
+ }
47
+ }
48
+ }
49
+
50
+ if (warnings.length > 0) {
51
+ log.warn(` WARNINGS: ${warnings.length} issue${warnings.length > 1 ? 's' : ''}`);
52
+ for (const issue of warnings) {
53
+ console.error(` - ${issue.title}`);
54
+ }
55
+ }
56
+
57
+ if (info.length > 0) {
58
+ log.dim(` INFO: ${info.length} suggestion${info.length > 1 ? 's' : ''}`);
59
+ }
60
+
61
+ if (critical.length === 0 && warnings.length === 0) {
62
+ log.success(' All checks passed!');
63
+ }
64
+
65
+ // Save report if docs/ exists
66
+ const docsDir = path.join(resolvedDir, 'docs');
67
+ if (fs.existsSync(docsDir)) {
68
+ const reportPath = path.join(docsDir, 'ci-report.md');
69
+ const projectName = path.basename(resolvedDir);
70
+ const report = generateReport(issues, projectName);
71
+ fs.writeFileSync(reportPath, report, 'utf-8');
72
+ log.dim(` Report saved to docs/ci-report.md`);
73
+ }
74
+
75
+ console.log('');
76
+
77
+ // Summary line for CI parsers
78
+ const total = issues.length;
79
+ console.log(`DevForge CI: ${critical.length} critical, ${warnings.length} warnings, ${info.length} info (${total} total)`);
80
+
81
+ // Exit with error code if critical issues found
82
+ if (critical.length > 0) {
83
+ process.exit(1);
84
+ }
85
+ }
@@ -1,6 +1,6 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import { ROOT_DIR, ensureDir, writeFile, readTemplate, replaceVars, log } from './utils.js';
3
+ import { ROOT_DIR, ensureDir, writeFile, readTemplate, replaceVars, getStackCommands } from './utils.js';
4
4
 
5
5
  const CLAUDE_TEMPLATES_DIR = path.join(ROOT_DIR, 'templates', 'claude-code');
6
6
  const DOCS_DIR = path.join(ROOT_DIR, 'docs');
@@ -16,6 +16,15 @@ export async function generateClaudeConfig(outputDir, stackConfig, options = {})
16
16
  generateAgents(outputDir, stackConfig, vars);
17
17
  generateCommands(outputDir, stackConfig, vars);
18
18
  copyPromptLibrary(outputDir);
19
+ stampVersion(outputDir);
20
+ }
21
+
22
+ function stampVersion(outputDir) {
23
+ const pkg = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, 'package.json'), 'utf-8'));
24
+ writeFile(path.join(outputDir, '.claude', '.devforge-version'), JSON.stringify({
25
+ version: pkg.version,
26
+ generatedAt: new Date().toISOString(),
27
+ }, null, 2) + '\n');
19
28
  }
20
29
 
21
30
  function buildClaudeVars(config) {
@@ -24,68 +33,76 @@ function buildClaudeVars(config) {
24
33
  PROJECT_NAME_PASCAL: config.projectName.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(''),
25
34
  };
26
35
 
36
+ // Commands vary by stack (shared with composer)
37
+ Object.assign(vars, getStackCommands(config.stackId));
38
+
27
39
  if (config.stackId === 'nextjs-fullstack') {
28
40
  vars.STACK_SUMMARY = 'Next.js 15 (App Router) + TypeScript + Tailwind CSS + Prisma + PostgreSQL';
29
- vars.LINT_COMMAND = 'npx eslint .';
30
- vars.TYPE_CHECK_COMMAND = 'npx tsc --noEmit';
31
- vars.TEST_COMMAND = 'npx vitest run';
32
- vars.BUILD_COMMAND = 'npm run build';
33
- vars.DEV_COMMAND = 'npm run dev';
34
- vars.DIR_MAP = `- src/app/ — Next.js App Router pages and API routes
35
- - src/lib/ — Shared utilities, database client, error handling
36
- - src/components/ — React components
37
- - prisma/ — Database schema and migrations
38
- - e2e/ — Playwright E2E tests`;
41
+ vars.DIR_MAP = `- src/app/ - Next.js App Router pages and API routes
42
+ - src/lib/ - Shared utilities, database client, error handling
43
+ - src/components/ - React components
44
+ - prisma/ - Database schema and migrations
45
+ - e2e/ - Playwright E2E tests`;
39
46
  } else if (config.stackId === 'fastapi-backend') {
40
47
  vars.STACK_SUMMARY = 'FastAPI + Python + SQLAlchemy 2.0 + PostgreSQL + Alembic';
41
- vars.LINT_COMMAND = 'ruff check .';
42
- vars.TYPE_CHECK_COMMAND = 'pyright';
43
- vars.TEST_COMMAND = 'pytest';
44
- vars.BUILD_COMMAND = 'docker build -t app .';
45
- vars.DEV_COMMAND = 'uvicorn app.main:app --reload';
46
- vars.DIR_MAP = `- backend/app/ FastAPI application
47
- - backend/app/api/ API route handlers
48
- - backend/app/core/ Config, security, error handling
49
- - backend/app/db/ — Database session and models
50
- - backend/app/models/ — SQLAlchemy models
51
- - backend/app/schemas/ — Pydantic schemas
52
- - backend/tests/ — Pytest tests
53
- - backend/alembic/ — Database migrations`;
48
+ vars.DIR_MAP = `- backend/app/ - FastAPI application
49
+ - backend/app/api/ - API route handlers
50
+ - backend/app/core/ - Config, security, error handling
51
+ - backend/app/db/ - Database session and models
52
+ - backend/app/models/ - SQLAlchemy models
53
+ - backend/app/schemas/ - Pydantic schemas
54
+ - backend/tests/ - Pytest tests
55
+ - backend/alembic/ - Database migrations`;
54
56
  } else if (config.stackId === 'polyglot-fullstack') {
55
57
  vars.STACK_SUMMARY = 'Next.js 15 (frontend) + FastAPI (backend) + PostgreSQL';
56
- vars.LINT_COMMAND = 'cd frontend && npx eslint . && cd ../backend && ruff check .';
57
- vars.TYPE_CHECK_COMMAND = 'cd frontend && npx tsc --noEmit';
58
- vars.TEST_COMMAND = 'cd frontend && npx vitest run && cd ../backend && pytest';
59
- vars.BUILD_COMMAND = 'docker compose build';
60
- vars.DEV_COMMAND = 'docker compose up';
61
- vars.DIR_MAP = `- frontend/ Next.js 15 App Router application
62
- - frontend/src/app/ Pages and API routes
63
- - frontend/src/lib/ Shared utilities
64
- - backend/ FastAPI application
65
- - backend/app/api/ API route handlers
66
- - backend/app/core/ Config, security, error handling
67
- - backend/app/db/ Database session and models
68
- - e2e/ Playwright E2E tests`;
58
+ vars.DIR_MAP = `- frontend/ - Next.js 15 App Router application
59
+ - frontend/src/app/ - Pages and API routes
60
+ - frontend/src/lib/ - Shared utilities
61
+ - backend/ - FastAPI application
62
+ - backend/app/api/ - API route handlers
63
+ - backend/app/core/ - Config, security, error handling
64
+ - backend/app/db/ - Database session and models
65
+ - e2e/ - Playwright E2E tests`;
66
+ } else if (config.stackId === 'react-express') {
67
+ vars.STACK_SUMMARY = 'React (Vite) + Express + TypeScript + Prisma + PostgreSQL';
68
+ vars.DIR_MAP = `- frontend/ - React (Vite) SPA
69
+ - frontend/src/ - React components and pages
70
+ - backend/ - Express API server
71
+ - backend/src/ - Express routes and middleware
72
+ - backend/src/routes/ - API route handlers
73
+ - backend/prisma/ - Database schema and migrations`;
74
+ } else if (config.stackId === 'remix-fullstack') {
75
+ vars.STACK_SUMMARY = 'Remix + Vite + TypeScript + Tailwind CSS + Prisma + PostgreSQL';
76
+ vars.DIR_MAP = `- app/ - Remix application
77
+ - app/routes/ - File-based routes and API resource routes
78
+ - app/routes/api.health.ts - Health check endpoint
79
+ - prisma/ - Database schema and migrations`;
80
+ } else if (config.stackId === 'hono-api') {
81
+ vars.STACK_SUMMARY = 'Hono + TypeScript + Prisma + PostgreSQL';
82
+ vars.DIR_MAP = `- src/ - Hono application
83
+ - src/routes/ - API route handlers
84
+ - src/routes/health.ts - Health check endpoints
85
+ - prisma/ - Database schema and migrations`;
69
86
  }
70
87
 
71
88
  // Build skills list for CLAUDE.md reference
72
89
  const skillsList = [
73
- '- `@.claude/skills/git-workflow/` Git branching, commits, and PR workflow',
74
- '- `@.claude/skills/testing-patterns/` Test pyramid, AAA pattern, mocking strategies',
90
+ '- `@.claude/skills/git-workflow/`: Git branching, commits, and PR workflow',
91
+ '- `@.claude/skills/testing-patterns/`: Test pyramid, AAA pattern, mocking strategies',
75
92
  ];
76
93
  if (config.frontend?.framework === 'nextjs') {
77
- skillsList.push('- `@.claude/skills/nextjs/` Next.js patterns and conventions');
78
- skillsList.push('- `@.claude/skills/security-web/` Frontend security practices');
94
+ skillsList.push('- `@.claude/skills/nextjs/`: Next.js patterns and conventions');
95
+ skillsList.push('- `@.claude/skills/security-web/`: Frontend security practices');
79
96
  }
80
97
  if (config.backend?.framework === 'fastapi') {
81
- skillsList.push('- `@.claude/skills/fastapi/` FastAPI patterns and conventions');
82
- skillsList.push('- `@.claude/skills/security-api/` API security practices');
98
+ skillsList.push('- `@.claude/skills/fastapi/`: FastAPI patterns and conventions');
99
+ skillsList.push('- `@.claude/skills/security-api/`: API security practices');
83
100
  }
84
101
  if (config.testing?.e2e === 'playwright') {
85
- skillsList.push('- `@.claude/skills/playwright/` E2E testing patterns');
102
+ skillsList.push('- `@.claude/skills/playwright/`: E2E testing patterns');
86
103
  }
87
104
  if (config.ai) {
88
- skillsList.push('- `@.claude/skills/ai-prompts/` AI/LLM prompt patterns');
105
+ skillsList.push('- `@.claude/skills/ai-prompts/`: AI/LLM prompt patterns');
89
106
  }
90
107
  vars.SKILLS_LIST = skillsList.length > 0 ? skillsList.join('\n') : '- (none generated)';
91
108
 
@@ -105,6 +122,12 @@ function generateClaudeMd(outputDir, config, vars) {
105
122
  stackSection = readTemplate(path.join(CLAUDE_TEMPLATES_DIR, 'claude-md', 'fastapi.md'));
106
123
  } else if (config.stackId === 'polyglot-fullstack') {
107
124
  stackSection = readTemplate(path.join(CLAUDE_TEMPLATES_DIR, 'claude-md', 'fullstack.md'));
125
+ } else if (config.stackId === 'react-express') {
126
+ stackSection = readTemplate(path.join(CLAUDE_TEMPLATES_DIR, 'claude-md', 'fullstack.md'));
127
+ } else if (config.stackId === 'remix-fullstack') {
128
+ stackSection = readTemplate(path.join(CLAUDE_TEMPLATES_DIR, 'claude-md', 'remix.md'));
129
+ } else if (config.stackId === 'hono-api') {
130
+ stackSection = readTemplate(path.join(CLAUDE_TEMPLATES_DIR, 'claude-md', 'hono.md'));
108
131
  }
109
132
 
110
133
  content = content.replace('{{STACK_SPECIFIC_RULES}}', stackSection);
@@ -115,9 +138,9 @@ function generateClaudeMd(outputDir, config, vars) {
115
138
 
116
139
  function generateHooks(outputDir, config, options = {}) {
117
140
  let hookFile;
118
- let scriptFiles = ['guard-protected-files.mjs'];
141
+ const scriptFiles = ['guard-protected-files.mjs', 'code-hygiene.mjs', 'pre-commit-gate.mjs'];
119
142
 
120
- if (config.stackId === 'nextjs-fullstack') {
143
+ if (config.stackId === 'nextjs-fullstack' || config.stackId === 'remix-fullstack' || config.stackId === 'hono-api') {
121
144
  hookFile = 'typescript.json';
122
145
  scriptFiles.push('autofix-typescript.mjs');
123
146
  } else if (config.stackId === 'fastapi-backend') {
@@ -126,6 +149,9 @@ function generateHooks(outputDir, config, options = {}) {
126
149
  } else if (config.stackId === 'polyglot-fullstack') {
127
150
  hookFile = 'polyglot.json';
128
151
  scriptFiles.push('autofix-polyglot.mjs');
152
+ } else if (config.stackId === 'react-express') {
153
+ hookFile = 'typescript.json';
154
+ scriptFiles.push('autofix-typescript.mjs');
129
155
  }
130
156
 
131
157
  const settingsPath = path.join(outputDir, '.claude', 'settings.json');
@@ -178,7 +204,7 @@ function deepMergeSettings(existing, incoming) {
178
204
  }
179
205
 
180
206
  function generateSkills(outputDir, config) {
181
- // Universal skills always included
207
+ // Universal skills, always included
182
208
  const skillsToInclude = ['git-workflow', 'testing-patterns'];
183
209
 
184
210
  if (config.frontend?.framework === 'nextjs') {
@@ -227,6 +253,10 @@ function generateAgents(outputDir, config, vars) {
227
253
  'loop-operator.md',
228
254
  'harness-optimizer.md',
229
255
  'product-strategist.md',
256
+ 'prompt-auditor.md',
257
+ 'frontend-builder.md',
258
+ 'enforcement-gate.md',
259
+ 'deep-reviewer.md',
230
260
  ];
231
261
 
232
262
  for (const agent of agents) {
@@ -245,7 +275,6 @@ function generateCommands(outputDir, config, vars) {
245
275
  'workflows.md',
246
276
  'status.md',
247
277
  'next.md',
248
- 'done.md',
249
278
  'verify-all.md',
250
279
  'audit-spec.md',
251
280
  'audit-wiring.md',
@@ -257,11 +286,19 @@ function generateCommands(outputDir, config, vars) {
257
286
  'optimize-claude-md.md',
258
287
  'plan.md',
259
288
  'build-fix.md',
289
+ 'fix-loop.md',
260
290
  'code-review.md',
261
291
  'tdd.md',
262
292
  'save-session.md',
263
293
  'resume-session.md',
264
294
  'full-audit.md',
295
+ 'verify-intent.md',
296
+ 'build-ui.md',
297
+ 'help.md',
298
+ 'live-uat.md',
299
+ 'simplify.md',
300
+ 'generate-sdd.md',
301
+ 'product-strategist.md',
265
302
  ];
266
303
 
267
304
  for (const cmd of commands) {