@codebakers/cli 3.3.18 → 3.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.
@@ -10,6 +10,120 @@ const fs_1 = require("fs");
10
10
  const path_1 = require("path");
11
11
  const config_js_1 = require("../config.js");
12
12
  const api_js_1 = require("../lib/api.js");
13
+ // Pre-commit hook script for session enforcement
14
+ const PRE_COMMIT_SCRIPT = `#!/bin/sh
15
+ # CodeBakers Pre-Commit Hook - Session Enforcement
16
+ # Blocks commits unless AI called discover_patterns and validate_complete
17
+ node "$(dirname "$0")/validate-session.js"
18
+ exit $?
19
+ `;
20
+ const VALIDATE_SESSION_SCRIPT = `#!/usr/bin/env node
21
+ const fs = require('fs');
22
+ const path = require('path');
23
+ const RED = '\\x1b[31m', GREEN = '\\x1b[32m', YELLOW = '\\x1b[33m', CYAN = '\\x1b[36m', RESET = '\\x1b[0m';
24
+ function log(c, m) { console.log(c + m + RESET); }
25
+
26
+ async function validate() {
27
+ const stateFile = path.join(process.cwd(), '.codebakers.json');
28
+ if (!fs.existsSync(stateFile)) return { valid: true, reason: 'not-codebakers' };
29
+
30
+ let state;
31
+ try { state = JSON.parse(fs.readFileSync(stateFile, 'utf-8')); }
32
+ catch { return { valid: false, reason: 'invalid-state' }; }
33
+
34
+ if (!state.serverEnforced) return { valid: true, reason: 'legacy' };
35
+
36
+ const v = state.lastValidation;
37
+ if (!v) return { valid: false, reason: 'no-validation', msg: 'AI must call validate_complete before commit' };
38
+ if (!v.passed) return { valid: false, reason: 'failed', msg: 'Validation failed - fix issues first' };
39
+
40
+ const age = Date.now() - new Date(v.timestamp).getTime();
41
+ if (age > 30 * 60 * 1000) return { valid: false, reason: 'stale', msg: 'Validation expired - call validate_complete again' };
42
+
43
+ return { valid: true, reason: 'ok' };
44
+ }
45
+
46
+ async function main() {
47
+ console.log(''); log(CYAN, ' 🍪 CodeBakers Pre-Commit');
48
+ const r = await validate();
49
+ if (r.valid) { log(GREEN, ' ✓ Commit allowed'); console.log(''); process.exit(0); }
50
+ else { log(RED, ' ✗ Blocked: ' + r.reason); if (r.msg) log(YELLOW, ' ' + r.msg);
51
+ console.log(''); log(YELLOW, ' Bypass: git commit --no-verify'); console.log(''); process.exit(1); }
52
+ }
53
+ main().catch(e => { log(RED, ' Error: ' + e.message); process.exit(1); });
54
+ `;
55
+ /**
56
+ * Install pre-commit hook for session enforcement
57
+ */
58
+ function installPrecommitHook(cwd) {
59
+ const gitDir = (0, path_1.join)(cwd, '.git');
60
+ if (!(0, fs_1.existsSync)(gitDir)) {
61
+ console.log(chalk_1.default.gray(' ⏭️ Skipping pre-commit hook (not a git repo)'));
62
+ return;
63
+ }
64
+ const hooksDir = (0, path_1.join)(gitDir, 'hooks');
65
+ if (!(0, fs_1.existsSync)(hooksDir)) {
66
+ (0, fs_1.mkdirSync)(hooksDir, { recursive: true });
67
+ }
68
+ // Write pre-commit hook
69
+ const preCommitPath = (0, path_1.join)(hooksDir, 'pre-commit');
70
+ (0, fs_1.writeFileSync)(preCommitPath, PRE_COMMIT_SCRIPT);
71
+ try {
72
+ (0, fs_1.chmodSync)(preCommitPath, '755');
73
+ }
74
+ catch { /* Windows */ }
75
+ // Write validation script
76
+ const validatePath = (0, path_1.join)(hooksDir, 'validate-session.js');
77
+ (0, fs_1.writeFileSync)(validatePath, VALIDATE_SESSION_SCRIPT);
78
+ console.log(chalk_1.default.green(' ✓ Installed pre-commit hook (blocks commits without validation)'));
79
+ }
80
+ // Ultra-minimal CLAUDE.md for v6.0 - ALL instructions come from server
81
+ const MINIMAL_CLAUDE_MD = `# CodeBakers v6.0
82
+
83
+ **MANDATORY: Before writing ANY code, call the \`discover_patterns\` MCP tool.**
84
+
85
+ \`\`\`
86
+ Tool: discover_patterns
87
+ Args: { task: "description of what you're building" }
88
+ \`\`\`
89
+
90
+ The server will return:
91
+ - All coding patterns and rules you must follow
92
+ - Test requirements
93
+ - Validation instructions
94
+
95
+ **You cannot write code without calling this tool first. The server tracks compliance.**
96
+
97
+ ---
98
+ *CodeBakers v6.0 - Server-Enforced*
99
+ `;
100
+ // Ultra-minimal .cursorrules for v6.0 - ALL instructions come from server
101
+ const MINIMAL_CURSORRULES = `# CodeBakers v6.0
102
+
103
+ MANDATORY: Before writing ANY code, call the discover_patterns MCP tool.
104
+
105
+ Tool: discover_patterns
106
+ Args: { task: "description of what you're building" }
107
+
108
+ The server returns all patterns, rules, and test requirements.
109
+ You cannot write code without calling this tool first.
110
+ `;
111
+ /**
112
+ * Compare semantic versions (returns true if v1 < v2)
113
+ */
114
+ function isVersionLessThan(v1, v2) {
115
+ const parts1 = v1.split('.').map(Number);
116
+ const parts2 = v2.split('.').map(Number);
117
+ for (let i = 0; i < 3; i++) {
118
+ const p1 = parts1[i] || 0;
119
+ const p2 = parts2[i] || 0;
120
+ if (p1 < p2)
121
+ return true;
122
+ if (p1 > p2)
123
+ return false;
124
+ }
125
+ return false;
126
+ }
13
127
  /**
14
128
  * Confirm download to server (non-blocking, fire-and-forget)
15
129
  */
@@ -28,6 +142,119 @@ async function confirmDownload(apiUrl, apiKey, data) {
28
142
  // Silently ignore - this is just for analytics
29
143
  }
30
144
  }
145
+ /**
146
+ * Get current installed version from .claude/.version.json
147
+ */
148
+ function getCurrentVersion(cwd) {
149
+ const versionFile = (0, path_1.join)(cwd, '.claude', '.version.json');
150
+ const codebakersFile = (0, path_1.join)(cwd, '.codebakers.json');
151
+ try {
152
+ if ((0, fs_1.existsSync)(versionFile)) {
153
+ const data = JSON.parse((0, fs_1.readFileSync)(versionFile, 'utf-8'));
154
+ return data.version || null;
155
+ }
156
+ if ((0, fs_1.existsSync)(codebakersFile)) {
157
+ const data = JSON.parse((0, fs_1.readFileSync)(codebakersFile, 'utf-8'));
158
+ return data.version || null;
159
+ }
160
+ }
161
+ catch {
162
+ // Ignore errors
163
+ }
164
+ return null;
165
+ }
166
+ /**
167
+ * Backup old files before migration
168
+ */
169
+ function backupOldFiles(cwd) {
170
+ const backupDir = (0, path_1.join)(cwd, '.codebakers', 'backup', new Date().toISOString().replace(/[:.]/g, '-'));
171
+ (0, fs_1.mkdirSync)(backupDir, { recursive: true });
172
+ // Backup CLAUDE.md
173
+ const claudeMd = (0, path_1.join)(cwd, 'CLAUDE.md');
174
+ if ((0, fs_1.existsSync)(claudeMd)) {
175
+ (0, fs_1.copyFileSync)(claudeMd, (0, path_1.join)(backupDir, 'CLAUDE.md'));
176
+ }
177
+ // Backup .cursorrules
178
+ const cursorrules = (0, path_1.join)(cwd, '.cursorrules');
179
+ if ((0, fs_1.existsSync)(cursorrules)) {
180
+ (0, fs_1.copyFileSync)(cursorrules, (0, path_1.join)(backupDir, '.cursorrules'));
181
+ }
182
+ // Backup .claude folder
183
+ const claudeDir = (0, path_1.join)(cwd, '.claude');
184
+ if ((0, fs_1.existsSync)(claudeDir)) {
185
+ const claudeBackup = (0, path_1.join)(backupDir, '.claude');
186
+ (0, fs_1.mkdirSync)(claudeBackup, { recursive: true });
187
+ const files = (0, fs_1.readdirSync)(claudeDir);
188
+ for (const file of files) {
189
+ const src = (0, path_1.join)(claudeDir, file);
190
+ const dest = (0, path_1.join)(claudeBackup, file);
191
+ try {
192
+ (0, fs_1.copyFileSync)(src, dest);
193
+ }
194
+ catch {
195
+ // Ignore copy errors
196
+ }
197
+ }
198
+ }
199
+ console.log(chalk_1.default.gray(` Backup saved to: ${backupDir}`));
200
+ }
201
+ /**
202
+ * Migrate to v6.0 server-enforced patterns
203
+ */
204
+ function migrateToV6(cwd) {
205
+ console.log(chalk_1.default.yellow('\n 📦 Migrating to v6.0 Server-Enforced Patterns...\n'));
206
+ // Backup old files
207
+ console.log(chalk_1.default.gray(' Backing up old files...'));
208
+ backupOldFiles(cwd);
209
+ // Replace CLAUDE.md with minimal version
210
+ const claudeMd = (0, path_1.join)(cwd, 'CLAUDE.md');
211
+ (0, fs_1.writeFileSync)(claudeMd, MINIMAL_CLAUDE_MD);
212
+ console.log(chalk_1.default.green(' ✓ Updated CLAUDE.md (minimal server-enforced version)'));
213
+ // Replace .cursorrules with minimal version
214
+ const cursorrules = (0, path_1.join)(cwd, '.cursorrules');
215
+ (0, fs_1.writeFileSync)(cursorrules, MINIMAL_CURSORRULES);
216
+ console.log(chalk_1.default.green(' ✓ Updated .cursorrules (minimal server-enforced version)'));
217
+ // Delete .claude folder (patterns now come from server)
218
+ const claudeDir = (0, path_1.join)(cwd, '.claude');
219
+ if ((0, fs_1.existsSync)(claudeDir)) {
220
+ try {
221
+ (0, fs_1.rmSync)(claudeDir, { recursive: true, force: true });
222
+ console.log(chalk_1.default.green(' ✓ Removed .claude/ folder (patterns now server-side)'));
223
+ }
224
+ catch (error) {
225
+ console.log(chalk_1.default.yellow(' ⚠️ Could not remove .claude/ folder - please delete manually'));
226
+ }
227
+ }
228
+ // Create .codebakers directory if it doesn't exist
229
+ const codebakersDir = (0, path_1.join)(cwd, '.codebakers');
230
+ if (!(0, fs_1.existsSync)(codebakersDir)) {
231
+ (0, fs_1.mkdirSync)(codebakersDir, { recursive: true });
232
+ }
233
+ // Update version in .codebakers.json
234
+ const stateFile = (0, path_1.join)(cwd, '.codebakers.json');
235
+ let state = {};
236
+ if ((0, fs_1.existsSync)(stateFile)) {
237
+ try {
238
+ state = JSON.parse((0, fs_1.readFileSync)(stateFile, 'utf-8'));
239
+ }
240
+ catch {
241
+ // Ignore errors
242
+ }
243
+ }
244
+ state.version = '6.0';
245
+ state.migratedAt = new Date().toISOString();
246
+ state.serverEnforced = true;
247
+ (0, fs_1.writeFileSync)(stateFile, JSON.stringify(state, null, 2));
248
+ // Auto-install pre-commit hook for enforcement
249
+ installPrecommitHook(cwd);
250
+ console.log(chalk_1.default.green('\n ✅ Migration to v6.0 complete!\n'));
251
+ console.log(chalk_1.default.cyan(' What changed:'));
252
+ console.log(chalk_1.default.gray(' - Patterns are now fetched from server in real-time'));
253
+ console.log(chalk_1.default.gray(' - discover_patterns creates a server-tracked session'));
254
+ console.log(chalk_1.default.gray(' - validate_complete verifies with server before completion'));
255
+ console.log(chalk_1.default.gray(' - Pre-commit hook blocks commits without validation'));
256
+ console.log(chalk_1.default.gray(' - No local pattern files needed\n'));
257
+ }
31
258
  /**
32
259
  * Upgrade CodeBakers patterns to the latest version
33
260
  */
@@ -36,8 +263,9 @@ async function upgrade() {
36
263
  const cwd = process.cwd();
37
264
  const claudeMdPath = (0, path_1.join)(cwd, 'CLAUDE.md');
38
265
  const claudeDir = (0, path_1.join)(cwd, '.claude');
266
+ const codebakersJson = (0, path_1.join)(cwd, '.codebakers.json');
39
267
  // Check if this is a CodeBakers project
40
- if (!(0, fs_1.existsSync)(claudeMdPath) && !(0, fs_1.existsSync)(claudeDir)) {
268
+ if (!(0, fs_1.existsSync)(claudeMdPath) && !(0, fs_1.existsSync)(claudeDir) && !(0, fs_1.existsSync)(codebakersJson)) {
41
269
  console.log(chalk_1.default.yellow(' No CodeBakers installation found in this directory.\n'));
42
270
  console.log(chalk_1.default.gray(' Run `codebakers install` to set up patterns first.\n'));
43
271
  return;
@@ -58,10 +286,59 @@ async function upgrade() {
58
286
  console.log(chalk_1.default.yellow(' Not logged in. Run `codebakers setup` first.\n'));
59
287
  return;
60
288
  }
61
- // Fetch latest patterns
62
- const spinner = (0, ora_1.default)('Fetching latest patterns...').start();
289
+ // Check current version and determine if migration is needed
290
+ const currentVersion = getCurrentVersion(cwd);
291
+ const spinner = (0, ora_1.default)('Checking server for latest version...').start();
63
292
  try {
64
293
  const apiUrl = (0, config_js_1.getApiUrl)();
294
+ // Check latest version from server
295
+ const versionResponse = await fetch(`${apiUrl}/api/content/version`, {
296
+ method: 'GET',
297
+ headers: {
298
+ 'Authorization': `Bearer ${apiKey}`,
299
+ },
300
+ });
301
+ if (!versionResponse.ok) {
302
+ throw new Error('Failed to check version');
303
+ }
304
+ const versionData = await versionResponse.json();
305
+ const latestVersion = versionData.version;
306
+ spinner.succeed(`Latest version: v${latestVersion}`);
307
+ // Check if we need to migrate to v6.0
308
+ const needsV6Migration = currentVersion && isVersionLessThan(currentVersion, '6.0') &&
309
+ !isVersionLessThan(latestVersion, '6.0');
310
+ if (needsV6Migration || (!currentVersion && !isVersionLessThan(latestVersion, '6.0'))) {
311
+ // Need to migrate to v6.0 server-enforced patterns
312
+ migrateToV6(cwd);
313
+ // Confirm migration to server
314
+ confirmDownload(apiUrl, apiKey, {
315
+ version: '6.0',
316
+ moduleCount: 0, // No local modules in v6
317
+ cliVersion: (0, api_js_1.getCliVersion)(),
318
+ command: 'upgrade-v6-migration',
319
+ }).catch(() => { });
320
+ return;
321
+ }
322
+ // For v6.0+, just confirm the installation is up to date
323
+ const stateFile = (0, path_1.join)(cwd, '.codebakers.json');
324
+ let state = {};
325
+ if ((0, fs_1.existsSync)(stateFile)) {
326
+ try {
327
+ state = JSON.parse((0, fs_1.readFileSync)(stateFile, 'utf-8'));
328
+ }
329
+ catch {
330
+ // Ignore
331
+ }
332
+ }
333
+ if (state.serverEnforced) {
334
+ // Already on v6.0+ server-enforced mode
335
+ console.log(chalk_1.default.green('\n ✅ Already using v6.0 server-enforced patterns!\n'));
336
+ console.log(chalk_1.default.gray(' Patterns are fetched from server in real-time.'));
337
+ console.log(chalk_1.default.gray(' No local updates needed.\n'));
338
+ return;
339
+ }
340
+ // Legacy upgrade for pre-6.0 versions (fetch full content)
341
+ const contentSpinner = (0, ora_1.default)('Fetching latest patterns...').start();
65
342
  const response = await fetch(`${apiUrl}/api/content`, {
66
343
  method: 'GET',
67
344
  headers: {
@@ -73,7 +350,7 @@ async function upgrade() {
73
350
  throw new Error(error.error || 'Failed to fetch patterns');
74
351
  }
75
352
  const content = await response.json();
76
- spinner.succeed(`Patterns v${content.version} downloaded`);
353
+ contentSpinner.succeed(`Patterns v${content.version} downloaded`);
77
354
  // Count what we're updating
78
355
  const moduleCount = Object.keys(content.modules).length;
79
356
  console.log(chalk_1.default.gray(` Updating ${moduleCount} modules...\n`));
@@ -99,6 +376,9 @@ async function upgrade() {
99
376
  updatedAt: new Date().toISOString(),
100
377
  cliVersion: (0, api_js_1.getCliVersion)(),
101
378
  };
379
+ if (!(0, fs_1.existsSync)(claudeDir)) {
380
+ (0, fs_1.mkdirSync)(claudeDir, { recursive: true });
381
+ }
102
382
  (0, fs_1.writeFileSync)((0, path_1.join)(claudeDir, '.version.json'), JSON.stringify(versionInfo, null, 2));
103
383
  console.log(chalk_1.default.green(' ✓ Version info saved'));
104
384
  // Confirm download to server (non-blocking)
package/dist/index.js CHANGED
@@ -11,6 +11,7 @@ const install_js_1 = require("./commands/install.js");
11
11
  const status_js_1 = require("./commands/status.js");
12
12
  const uninstall_js_1 = require("./commands/uninstall.js");
13
13
  const install_hook_js_1 = require("./commands/install-hook.js");
14
+ const install_precommit_js_1 = require("./commands/install-precommit.js");
14
15
  const doctor_js_1 = require("./commands/doctor.js");
15
16
  const init_js_1 = require("./commands/init.js");
16
17
  const serve_js_1 = require("./commands/serve.js");
@@ -400,6 +401,10 @@ program
400
401
  .command('uninstall-hook')
401
402
  .description('Remove the CodeBakers hook from Claude Code')
402
403
  .action(install_hook_js_1.uninstallHook);
404
+ program
405
+ .command('install-precommit')
406
+ .description('Install git pre-commit hook for session enforcement (v6.0)')
407
+ .action(install_precommit_js_1.installPrecommit);
403
408
  program
404
409
  .command('doctor')
405
410
  .description('Check if CodeBakers is set up correctly')