@codebakers/cli 3.3.18 → 3.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/dist/commands/install-precommit.d.ts +1 -0
- package/dist/commands/install-precommit.js +229 -0
- package/dist/commands/upgrade.js +284 -4
- package/dist/index.js +5 -0
- package/dist/mcp/server.js +325 -256
- package/package.json +1 -1
- package/src/commands/install-precommit.ts +234 -0
- package/src/commands/upgrade.ts +314 -5
- package/src/index.ts +6 -0
- package/src/mcp/server.ts +335 -266
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function installPrecommit(): Promise<void>;
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.installPrecommit = installPrecommit;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const fs_1 = require("fs");
|
|
9
|
+
const path_1 = require("path");
|
|
10
|
+
const PRE_COMMIT_SCRIPT = `#!/bin/sh
|
|
11
|
+
# CodeBakers Pre-Commit Hook - Session Enforcement
|
|
12
|
+
# Blocks commits unless AI called discover_patterns and validate_complete
|
|
13
|
+
|
|
14
|
+
# Run the validation script
|
|
15
|
+
node "$(dirname "$0")/validate-session.js"
|
|
16
|
+
exit $?
|
|
17
|
+
`;
|
|
18
|
+
const VALIDATE_SESSION_SCRIPT = `#!/usr/bin/env node
|
|
19
|
+
/**
|
|
20
|
+
* CodeBakers Pre-Commit Validation
|
|
21
|
+
* Blocks commits unless a valid session exists
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const fs = require('fs');
|
|
25
|
+
const path = require('path');
|
|
26
|
+
|
|
27
|
+
const RED = '\\x1b[31m';
|
|
28
|
+
const GREEN = '\\x1b[32m';
|
|
29
|
+
const YELLOW = '\\x1b[33m';
|
|
30
|
+
const CYAN = '\\x1b[36m';
|
|
31
|
+
const RESET = '\\x1b[0m';
|
|
32
|
+
|
|
33
|
+
function log(color, message) {
|
|
34
|
+
console.log(color + message + RESET);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function validateSession() {
|
|
38
|
+
const cwd = process.cwd();
|
|
39
|
+
const stateFile = path.join(cwd, '.codebakers.json');
|
|
40
|
+
|
|
41
|
+
// Check if this is a CodeBakers project
|
|
42
|
+
if (!fs.existsSync(stateFile)) {
|
|
43
|
+
return { valid: true, reason: 'not-codebakers-project' };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let state;
|
|
47
|
+
try {
|
|
48
|
+
state = JSON.parse(fs.readFileSync(stateFile, 'utf-8'));
|
|
49
|
+
} catch (error) {
|
|
50
|
+
return { valid: false, reason: 'invalid-state-file' };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Check if v6.0 server-enforced mode
|
|
54
|
+
if (!state.serverEnforced) {
|
|
55
|
+
return { valid: true, reason: 'legacy-project' };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Check for session token (means discover_patterns was called)
|
|
59
|
+
const sessionToken = state.currentSessionToken;
|
|
60
|
+
if (!sessionToken) {
|
|
61
|
+
// Check if there's a recent passed validation
|
|
62
|
+
const lastValidation = state.lastValidation;
|
|
63
|
+
if (!lastValidation || !lastValidation.passed) {
|
|
64
|
+
return {
|
|
65
|
+
valid: false,
|
|
66
|
+
reason: 'no-session',
|
|
67
|
+
message: 'No active CodeBakers session.\\nAI must call discover_patterns before writing code.'
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Check session expiry
|
|
73
|
+
const sessionExpiry = state.sessionExpiresAt;
|
|
74
|
+
if (sessionExpiry && new Date(sessionExpiry) < new Date()) {
|
|
75
|
+
return {
|
|
76
|
+
valid: false,
|
|
77
|
+
reason: 'session-expired',
|
|
78
|
+
message: 'CodeBakers session has expired.\\nAI must call discover_patterns again.'
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Check if validation was completed
|
|
83
|
+
const lastValidation = state.lastValidation;
|
|
84
|
+
if (!lastValidation) {
|
|
85
|
+
return {
|
|
86
|
+
valid: false,
|
|
87
|
+
reason: 'no-validation',
|
|
88
|
+
message: 'No validation completed.\\nAI must call validate_complete before committing.'
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Check if validation passed
|
|
93
|
+
if (!lastValidation.passed) {
|
|
94
|
+
const issues = lastValidation.issues?.map(i => i.message || i).join(', ') || 'Unknown issues';
|
|
95
|
+
return {
|
|
96
|
+
valid: false,
|
|
97
|
+
reason: 'validation-failed',
|
|
98
|
+
message: 'Validation failed: ' + issues + '\\nAI must fix issues and call validate_complete again.'
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Check if validation is recent (within last 30 minutes)
|
|
103
|
+
const validationTime = new Date(lastValidation.timestamp);
|
|
104
|
+
const thirtyMinutesAgo = new Date(Date.now() - 30 * 60 * 1000);
|
|
105
|
+
if (validationTime < thirtyMinutesAgo) {
|
|
106
|
+
return {
|
|
107
|
+
valid: false,
|
|
108
|
+
reason: 'validation-stale',
|
|
109
|
+
message: 'Validation is stale (older than 30 minutes).\\nAI must call validate_complete again.'
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return { valid: true, reason: 'session-valid' };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function main() {
|
|
117
|
+
console.log('');
|
|
118
|
+
log(CYAN, ' 🍪 CodeBakers Pre-Commit Validation');
|
|
119
|
+
console.log('');
|
|
120
|
+
|
|
121
|
+
const result = await validateSession();
|
|
122
|
+
|
|
123
|
+
if (result.valid) {
|
|
124
|
+
if (result.reason === 'not-codebakers-project') {
|
|
125
|
+
log(GREEN, ' ✓ Not a CodeBakers project - commit allowed');
|
|
126
|
+
} else if (result.reason === 'legacy-project') {
|
|
127
|
+
log(GREEN, ' ✓ Legacy project (pre-6.0) - commit allowed');
|
|
128
|
+
} else {
|
|
129
|
+
log(GREEN, ' ✓ Valid CodeBakers session - commit allowed');
|
|
130
|
+
}
|
|
131
|
+
console.log('');
|
|
132
|
+
process.exit(0);
|
|
133
|
+
} else {
|
|
134
|
+
log(RED, ' ✗ Commit blocked: ' + result.reason);
|
|
135
|
+
console.log('');
|
|
136
|
+
if (result.message) {
|
|
137
|
+
log(YELLOW, ' ' + result.message.split('\\n').join('\\n '));
|
|
138
|
+
}
|
|
139
|
+
console.log('');
|
|
140
|
+
log(CYAN, ' How to fix:');
|
|
141
|
+
log(RESET, ' 1. AI must call discover_patterns before writing code');
|
|
142
|
+
log(RESET, ' 2. AI must call validate_complete before saying "done"');
|
|
143
|
+
log(RESET, ' 3. Both tools must pass for commits to be allowed');
|
|
144
|
+
console.log('');
|
|
145
|
+
log(YELLOW, ' To bypass (not recommended): git commit --no-verify');
|
|
146
|
+
console.log('');
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
main().catch(error => {
|
|
152
|
+
log(RED, ' Error: ' + error.message);
|
|
153
|
+
process.exit(1);
|
|
154
|
+
});
|
|
155
|
+
`;
|
|
156
|
+
async function installPrecommit() {
|
|
157
|
+
console.log(chalk_1.default.blue('\n CodeBakers Pre-Commit Hook Installation\n'));
|
|
158
|
+
const cwd = process.cwd();
|
|
159
|
+
// Check if this is a git repository
|
|
160
|
+
const gitDir = (0, path_1.join)(cwd, '.git');
|
|
161
|
+
if (!(0, fs_1.existsSync)(gitDir)) {
|
|
162
|
+
console.log(chalk_1.default.red(' ✗ Not a git repository'));
|
|
163
|
+
console.log(chalk_1.default.gray(' Initialize git first: git init\n'));
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
// Check if this is a CodeBakers project
|
|
167
|
+
const stateFile = (0, path_1.join)(cwd, '.codebakers.json');
|
|
168
|
+
if (!(0, fs_1.existsSync)(stateFile)) {
|
|
169
|
+
console.log(chalk_1.default.yellow(' ⚠️ No .codebakers.json found'));
|
|
170
|
+
console.log(chalk_1.default.gray(' Run codebakers upgrade first to enable server enforcement\n'));
|
|
171
|
+
}
|
|
172
|
+
// Create hooks directory if it doesn't exist
|
|
173
|
+
const hooksDir = (0, path_1.join)(gitDir, 'hooks');
|
|
174
|
+
if (!(0, fs_1.existsSync)(hooksDir)) {
|
|
175
|
+
(0, fs_1.mkdirSync)(hooksDir, { recursive: true });
|
|
176
|
+
}
|
|
177
|
+
// Write the pre-commit hook
|
|
178
|
+
const preCommitPath = (0, path_1.join)(hooksDir, 'pre-commit');
|
|
179
|
+
(0, fs_1.writeFileSync)(preCommitPath, PRE_COMMIT_SCRIPT);
|
|
180
|
+
// Make it executable (Unix only, Windows ignores this)
|
|
181
|
+
try {
|
|
182
|
+
(0, fs_1.chmodSync)(preCommitPath, '755');
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
// Windows doesn't support chmod
|
|
186
|
+
}
|
|
187
|
+
console.log(chalk_1.default.green(' ✓ Created pre-commit hook'));
|
|
188
|
+
// Write the validation script
|
|
189
|
+
const validatePath = (0, path_1.join)(hooksDir, 'validate-session.js');
|
|
190
|
+
(0, fs_1.writeFileSync)(validatePath, VALIDATE_SESSION_SCRIPT);
|
|
191
|
+
console.log(chalk_1.default.green(' ✓ Created validation script'));
|
|
192
|
+
// Check if husky is being used
|
|
193
|
+
const huskyDir = (0, path_1.join)(cwd, '.husky');
|
|
194
|
+
if ((0, fs_1.existsSync)(huskyDir)) {
|
|
195
|
+
// Also install in husky
|
|
196
|
+
const huskyPreCommit = (0, path_1.join)(huskyDir, 'pre-commit');
|
|
197
|
+
let huskyContent = '';
|
|
198
|
+
if ((0, fs_1.existsSync)(huskyPreCommit)) {
|
|
199
|
+
huskyContent = (0, fs_1.readFileSync)(huskyPreCommit, 'utf-8');
|
|
200
|
+
if (!huskyContent.includes('validate-session')) {
|
|
201
|
+
huskyContent += '\n# CodeBakers session enforcement\nnode .git/hooks/validate-session.js\n';
|
|
202
|
+
(0, fs_1.writeFileSync)(huskyPreCommit, huskyContent);
|
|
203
|
+
console.log(chalk_1.default.green(' ✓ Added to existing husky pre-commit'));
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
console.log(chalk_1.default.gray(' ✓ Husky hook already configured'));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
huskyContent = '#!/usr/bin/env sh\n. "$(dirname -- "$0")/_/husky.sh"\n\n# CodeBakers session enforcement\nnode .git/hooks/validate-session.js\n';
|
|
211
|
+
(0, fs_1.writeFileSync)(huskyPreCommit, huskyContent);
|
|
212
|
+
try {
|
|
213
|
+
(0, fs_1.chmodSync)(huskyPreCommit, '755');
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
// Windows
|
|
217
|
+
}
|
|
218
|
+
console.log(chalk_1.default.green(' ✓ Created husky pre-commit hook'));
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
console.log(chalk_1.default.green('\n ✅ Pre-commit hook installed!\n'));
|
|
222
|
+
console.log(chalk_1.default.cyan(' What this does:'));
|
|
223
|
+
console.log(chalk_1.default.gray(' - Blocks commits unless AI called discover_patterns'));
|
|
224
|
+
console.log(chalk_1.default.gray(' - Blocks commits unless AI called validate_complete'));
|
|
225
|
+
console.log(chalk_1.default.gray(' - Requires validation to pass before committing'));
|
|
226
|
+
console.log(chalk_1.default.gray(' - Validation expires after 30 minutes\n'));
|
|
227
|
+
console.log(chalk_1.default.yellow(' To bypass (not recommended):'));
|
|
228
|
+
console.log(chalk_1.default.gray(' git commit --no-verify\n'));
|
|
229
|
+
}
|
package/dist/commands/upgrade.js
CHANGED
|
@@ -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
|
-
//
|
|
62
|
-
const
|
|
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
|
-
|
|
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')
|