bonzai-burn 1.0.23 ā 1.0.24
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/package.json +2 -5
- package/payload-bonzai/config.json +16 -7
- package/src/analyzer.js +362 -0
- package/src/bburn.js +34 -475
- package/src/index.js +3 -4
- package/payload-bonzai/specs.md +0 -9
- package/src/baccept.js +0 -60
- package/src/brevert.js +0 -59
package/package.json
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bonzai-burn",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.24",
|
|
4
4
|
"description": "Git branch-based cleanup tool with bburn, baccept, and brevert commands",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
7
7
|
"bin": {
|
|
8
8
|
"bonzai-burn": "./src/index.js",
|
|
9
9
|
"bburn": "./src/bburn.js",
|
|
10
|
-
"
|
|
11
|
-
"brevert": "./src/brevert.js",
|
|
12
|
-
"bgraph": "./src/bgraph.js",
|
|
13
|
-
"bonzai-mcp": "./src/mcp-server.js"
|
|
10
|
+
"bgraph": "./src/bgraph.js"
|
|
14
11
|
},
|
|
15
12
|
"keywords": [
|
|
16
13
|
"git",
|
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
{
|
|
2
|
-
"
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
"customChecks": {
|
|
3
|
+
"requirements": "Remove unused imports and variables. Remove all console log statements."
|
|
4
|
+
},
|
|
5
5
|
"lineLimit": {
|
|
6
|
-
"enabled":
|
|
7
|
-
"limit":
|
|
6
|
+
"enabled": true,
|
|
7
|
+
"limit": 300,
|
|
8
8
|
"prompt": "Split any file with over {{ linelimit }} lines into smaller files."
|
|
9
9
|
},
|
|
10
10
|
"folderLimit": {
|
|
11
|
-
"enabled":
|
|
12
|
-
"limit":
|
|
11
|
+
"enabled": true,
|
|
12
|
+
"limit": 15,
|
|
13
13
|
"prompt": "Split any folder with over {{ folderlimit }} items into smaller, compartmentalized folders."
|
|
14
|
+
},
|
|
15
|
+
"testCheck": {
|
|
16
|
+
"enabled": false,
|
|
17
|
+
"patterns": {
|
|
18
|
+
".vue": ".test.js",
|
|
19
|
+
".jsx": ".test.jsx",
|
|
20
|
+
".tsx": ".test.tsx"
|
|
21
|
+
},
|
|
22
|
+
"prompt": "Create test files for components that are missing them."
|
|
14
23
|
}
|
|
15
24
|
}
|
package/src/analyzer.js
ADDED
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Static analyzer for codebase
|
|
7
|
+
* Runs ESLint, TypeScript, and custom checks based on config.json
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* List all files recursively, respecting common ignore patterns
|
|
12
|
+
*/
|
|
13
|
+
function listAllFiles(dir, basePath = '') {
|
|
14
|
+
const ignorePatterns = ['node_modules', '.git', '.DS_Store', 'dist', 'build', 'coverage', 'bonzai'];
|
|
15
|
+
let results = [];
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
19
|
+
|
|
20
|
+
for (const entry of entries) {
|
|
21
|
+
const fullPath = path.join(dir, entry.name);
|
|
22
|
+
const relativePath = path.join(basePath, entry.name);
|
|
23
|
+
|
|
24
|
+
if (ignorePatterns.some(p => entry.name === p) || entry.name.startsWith('.')) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (entry.isDirectory()) {
|
|
29
|
+
results = results.concat(listAllFiles(fullPath, relativePath));
|
|
30
|
+
} else {
|
|
31
|
+
results.push({
|
|
32
|
+
path: relativePath,
|
|
33
|
+
fullPath: fullPath
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
} catch (e) {
|
|
38
|
+
// Directory access error, skip
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return results;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Run ESLint to detect unused imports and variables
|
|
46
|
+
*/
|
|
47
|
+
function runEslintAnalysis(rootDir) {
|
|
48
|
+
const issues = [];
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
execSync('which eslint', { encoding: 'utf-8', stdio: 'pipe' });
|
|
52
|
+
} catch {
|
|
53
|
+
return { issues, skipped: true, reason: 'ESLint not installed' };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const result = execSync(
|
|
58
|
+
`eslint "${rootDir}" --format json --rule "no-unused-vars: error" 2>/dev/null || true`,
|
|
59
|
+
{ encoding: 'utf-8', stdio: 'pipe', maxBuffer: 50 * 1024 * 1024 }
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
if (result.trim()) {
|
|
63
|
+
const eslintOutput = JSON.parse(result);
|
|
64
|
+
|
|
65
|
+
for (const file of eslintOutput) {
|
|
66
|
+
for (const msg of file.messages || []) {
|
|
67
|
+
if (msg.ruleId && msg.ruleId.includes('no-unused')) {
|
|
68
|
+
issues.push({
|
|
69
|
+
file: path.relative(rootDir, file.filePath),
|
|
70
|
+
line: msg.line,
|
|
71
|
+
message: msg.message,
|
|
72
|
+
rule: msg.ruleId
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
} catch (e) {
|
|
79
|
+
return { issues, skipped: true, reason: 'ESLint analysis failed' };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return { issues, skipped: false };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Run TypeScript compiler to check for unused locals
|
|
87
|
+
*/
|
|
88
|
+
function runTypeScriptAnalysis(rootDir) {
|
|
89
|
+
const issues = [];
|
|
90
|
+
const tsconfigPath = path.join(rootDir, 'tsconfig.json');
|
|
91
|
+
|
|
92
|
+
if (!fs.existsSync(tsconfigPath)) {
|
|
93
|
+
return { issues, skipped: true, reason: 'No tsconfig.json found' };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
execSync('which tsc', { encoding: 'utf-8', stdio: 'pipe' });
|
|
98
|
+
} catch {
|
|
99
|
+
return { issues, skipped: true, reason: 'TypeScript not installed' };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const result = execSync(
|
|
104
|
+
`cd "${rootDir}" && tsc --noEmit --noUnusedLocals --noUnusedParameters 2>&1 || true`,
|
|
105
|
+
{ encoding: 'utf-8', stdio: 'pipe', maxBuffer: 50 * 1024 * 1024 }
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const lines = result.split('\n');
|
|
109
|
+
const errorRegex = /^(.+)\((\d+),(\d+)\):\s*error\s+TS(\d+):\s*(.+)$/;
|
|
110
|
+
|
|
111
|
+
for (const line of lines) {
|
|
112
|
+
const match = line.match(errorRegex);
|
|
113
|
+
if (match) {
|
|
114
|
+
const [, filePath, lineNum, , errorCode, message] = match;
|
|
115
|
+
// TS6133 = unused variable, TS6196 = unused parameter
|
|
116
|
+
if (['6133', '6196', '6198'].includes(errorCode)) {
|
|
117
|
+
issues.push({
|
|
118
|
+
file: path.relative(rootDir, filePath),
|
|
119
|
+
line: parseInt(lineNum, 10),
|
|
120
|
+
message: message,
|
|
121
|
+
rule: `TS${errorCode}`
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
} catch (e) {
|
|
127
|
+
return { issues, skipped: true, reason: 'TypeScript analysis failed' };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return { issues, skipped: false };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Check files against line limit
|
|
135
|
+
*/
|
|
136
|
+
function checkLineLimits(files, config) {
|
|
137
|
+
const issues = [];
|
|
138
|
+
const cfg = config.lineLimit || {};
|
|
139
|
+
|
|
140
|
+
if (!cfg.enabled) {
|
|
141
|
+
return { issues, skipped: true, reason: 'Disabled in config' };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const maxLines = cfg.limit || 500;
|
|
145
|
+
|
|
146
|
+
for (const file of files) {
|
|
147
|
+
// Skip non-code files
|
|
148
|
+
if (file.path.endsWith('.json') || file.path.endsWith('.lock') || file.path.endsWith('.css')) {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
const content = fs.readFileSync(file.fullPath, 'utf-8');
|
|
154
|
+
const lineCount = content.split('\n').length;
|
|
155
|
+
|
|
156
|
+
if (lineCount > maxLines) {
|
|
157
|
+
issues.push({
|
|
158
|
+
file: file.path,
|
|
159
|
+
count: lineCount,
|
|
160
|
+
limit: maxLines
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
} catch (e) {
|
|
164
|
+
// Can't read file, skip
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
issues.sort((a, b) => b.count - a.count);
|
|
169
|
+
return { issues, skipped: false, prompt: cfg.prompt };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Check folders against item limit
|
|
174
|
+
*/
|
|
175
|
+
function checkFolderLimits(files, config) {
|
|
176
|
+
const issues = [];
|
|
177
|
+
const cfg = config.folderLimit || {};
|
|
178
|
+
|
|
179
|
+
if (!cfg.enabled) {
|
|
180
|
+
return { issues, skipped: true, reason: 'Disabled in config' };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const maxItems = cfg.limit || 20;
|
|
184
|
+
const folderCounts = {};
|
|
185
|
+
|
|
186
|
+
for (const file of files) {
|
|
187
|
+
const dir = path.dirname(file.path);
|
|
188
|
+
if (!folderCounts[dir]) {
|
|
189
|
+
folderCounts[dir] = 0;
|
|
190
|
+
}
|
|
191
|
+
folderCounts[dir]++;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
for (const [folder, count] of Object.entries(folderCounts)) {
|
|
195
|
+
if (count > maxItems) {
|
|
196
|
+
issues.push({
|
|
197
|
+
file: folder,
|
|
198
|
+
count: count,
|
|
199
|
+
limit: maxItems
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
issues.sort((a, b) => b.count - a.count);
|
|
205
|
+
return { issues, skipped: false, prompt: cfg.prompt };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Check for missing test files
|
|
210
|
+
*/
|
|
211
|
+
function checkMissingTests(files, config) {
|
|
212
|
+
const issues = [];
|
|
213
|
+
const cfg = config.testCheck || {};
|
|
214
|
+
|
|
215
|
+
if (!cfg.enabled) {
|
|
216
|
+
return { issues, skipped: true, reason: 'Disabled in config' };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const patterns = cfg.patterns || {
|
|
220
|
+
'.vue': '.test.js',
|
|
221
|
+
'.jsx': '.test.jsx',
|
|
222
|
+
'.tsx': '.test.tsx'
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
const testFiles = new Set(
|
|
226
|
+
files
|
|
227
|
+
.filter(f => f.path.includes('.test.') || f.path.includes('.spec.'))
|
|
228
|
+
.map(f => f.path.toLowerCase())
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
for (const file of files) {
|
|
232
|
+
const ext = path.extname(file.path);
|
|
233
|
+
const testExt = patterns[ext];
|
|
234
|
+
|
|
235
|
+
if (!testExt) continue;
|
|
236
|
+
if (file.path.includes('.test.') || file.path.includes('.spec.')) continue;
|
|
237
|
+
if (!file.path.startsWith('src/') && !file.path.startsWith('components/')) continue;
|
|
238
|
+
|
|
239
|
+
const baseName = path.basename(file.path, ext);
|
|
240
|
+
const hasTest = [...testFiles].some(t => t.includes(baseName.toLowerCase()) && t.includes('.test.'));
|
|
241
|
+
|
|
242
|
+
if (!hasTest) {
|
|
243
|
+
issues.push({
|
|
244
|
+
file: file.path,
|
|
245
|
+
expectedTest: `${baseName}${testExt}`
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return { issues, skipped: false, prompt: cfg.prompt };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Main analyzer function
|
|
255
|
+
*/
|
|
256
|
+
export async function analyze(rootDir = process.cwd(), config = {}) {
|
|
257
|
+
const startTime = Date.now();
|
|
258
|
+
const files = listAllFiles(rootDir);
|
|
259
|
+
|
|
260
|
+
// Run all checks
|
|
261
|
+
const eslint = runEslintAnalysis(rootDir);
|
|
262
|
+
const typescript = runTypeScriptAnalysis(rootDir);
|
|
263
|
+
const lineLimit = checkLineLimits(files, config);
|
|
264
|
+
const folderLimit = checkFolderLimits(files, config);
|
|
265
|
+
const missingTests = checkMissingTests(files, config);
|
|
266
|
+
|
|
267
|
+
const duration = Date.now() - startTime;
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
eslint,
|
|
271
|
+
typescript,
|
|
272
|
+
lineLimit,
|
|
273
|
+
folderLimit,
|
|
274
|
+
missingTests,
|
|
275
|
+
customRequirements: config.customChecks?.requirements || null,
|
|
276
|
+
filesScanned: files.length,
|
|
277
|
+
durationMs: duration
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Format analysis results for display
|
|
283
|
+
*/
|
|
284
|
+
export function formatAnalysisResults(results) {
|
|
285
|
+
let output = '';
|
|
286
|
+
let totalIssues = 0;
|
|
287
|
+
|
|
288
|
+
// ESLint issues
|
|
289
|
+
if (!results.eslint.skipped && results.eslint.issues.length > 0) {
|
|
290
|
+
output += `šļø UNUSED CODE (ESLint) - ${results.eslint.issues.length} issues\n`;
|
|
291
|
+
for (const issue of results.eslint.issues.slice(0, 15)) {
|
|
292
|
+
output += ` ${issue.file}:${issue.line} - ${issue.message}\n`;
|
|
293
|
+
}
|
|
294
|
+
if (results.eslint.issues.length > 15) {
|
|
295
|
+
output += ` ... and ${results.eslint.issues.length - 15} more\n`;
|
|
296
|
+
}
|
|
297
|
+
output += '\n';
|
|
298
|
+
totalIssues += results.eslint.issues.length;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// TypeScript issues
|
|
302
|
+
if (!results.typescript.skipped && results.typescript.issues.length > 0) {
|
|
303
|
+
output += `š· UNUSED CODE (TypeScript) - ${results.typescript.issues.length} issues\n`;
|
|
304
|
+
for (const issue of results.typescript.issues.slice(0, 15)) {
|
|
305
|
+
output += ` ${issue.file}:${issue.line} - ${issue.message}\n`;
|
|
306
|
+
}
|
|
307
|
+
if (results.typescript.issues.length > 15) {
|
|
308
|
+
output += ` ... and ${results.typescript.issues.length - 15} more\n`;
|
|
309
|
+
}
|
|
310
|
+
output += '\n';
|
|
311
|
+
totalIssues += results.typescript.issues.length;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Line limit issues
|
|
315
|
+
if (!results.lineLimit.skipped && results.lineLimit.issues.length > 0) {
|
|
316
|
+
output += `š FILES OVER LINE LIMIT - ${results.lineLimit.issues.length} files\n`;
|
|
317
|
+
for (const issue of results.lineLimit.issues) {
|
|
318
|
+
output += ` ${issue.file} - ${issue.count} lines (limit: ${issue.limit})\n`;
|
|
319
|
+
}
|
|
320
|
+
if (results.lineLimit.prompt) {
|
|
321
|
+
output += `\n ā ${results.lineLimit.prompt.replace(/\{\{\s*linelimit\s*\}\}/gi, results.lineLimit.issues[0]?.limit || '')}\n`;
|
|
322
|
+
}
|
|
323
|
+
output += '\n';
|
|
324
|
+
totalIssues += results.lineLimit.issues.length;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Folder limit issues
|
|
328
|
+
if (!results.folderLimit.skipped && results.folderLimit.issues.length > 0) {
|
|
329
|
+
output += `š FOLDERS OVER ITEM LIMIT - ${results.folderLimit.issues.length} folders\n`;
|
|
330
|
+
for (const issue of results.folderLimit.issues) {
|
|
331
|
+
output += ` ${issue.file}/ - ${issue.count} items (limit: ${issue.limit})\n`;
|
|
332
|
+
}
|
|
333
|
+
if (results.folderLimit.prompt) {
|
|
334
|
+
output += `\n ā ${results.folderLimit.prompt.replace(/\{\{\s*folderlimit\s*\}\}/gi, results.folderLimit.issues[0]?.limit || '')}\n`;
|
|
335
|
+
}
|
|
336
|
+
output += '\n';
|
|
337
|
+
totalIssues += results.folderLimit.issues.length;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Missing tests
|
|
341
|
+
if (!results.missingTests.skipped && results.missingTests.issues.length > 0) {
|
|
342
|
+
output += `š§Ŗ MISSING TESTS - ${results.missingTests.issues.length} files\n`;
|
|
343
|
+
for (const issue of results.missingTests.issues.slice(0, 10)) {
|
|
344
|
+
output += ` ${issue.file} ā needs ${issue.expectedTest}\n`;
|
|
345
|
+
}
|
|
346
|
+
if (results.missingTests.issues.length > 10) {
|
|
347
|
+
output += ` ... and ${results.missingTests.issues.length - 10} more\n`;
|
|
348
|
+
}
|
|
349
|
+
output += '\n';
|
|
350
|
+
totalIssues += results.missingTests.issues.length;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Custom requirements
|
|
354
|
+
if (results.customRequirements) {
|
|
355
|
+
output += `š CUSTOM REQUIREMENTS\n`;
|
|
356
|
+
output += ` ${results.customRequirements}\n\n`;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return { output, totalIssues };
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
export default { analyze, formatAnalysisResults };
|
package/src/bburn.js
CHANGED
|
@@ -1,499 +1,58 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { execSync, spawn } from 'child_process';
|
|
3
|
-
import crypto from 'crypto';
|
|
4
2
|
import fs from 'fs';
|
|
5
|
-
import { join
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
-
const __dirname = dirname(__filename);
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { analyze, formatAnalysisResults } from './analyzer.js';
|
|
10
5
|
|
|
11
6
|
const BONZAI_DIR = 'bonzai';
|
|
12
|
-
const SPECS_FILE = 'specs.md';
|
|
13
7
|
const CONFIG_FILE = 'config.json';
|
|
14
8
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
function
|
|
20
|
-
const
|
|
21
|
-
let provider = null;
|
|
22
|
-
|
|
23
|
-
for (let i = 0; i < args.length; i++) {
|
|
24
|
-
if (args[i] === '--provider' || args[i] === '-p') {
|
|
25
|
-
provider = args[i + 1];
|
|
26
|
-
break;
|
|
27
|
-
}
|
|
28
|
-
if (args[i].startsWith('--provider=')) {
|
|
29
|
-
provider = args[i].split('=')[1];
|
|
30
|
-
break;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Use config default if no CLI arg provided
|
|
35
|
-
if (!provider) {
|
|
36
|
-
provider = configDefault;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const validProviders = ['claude', 'cursor'];
|
|
40
|
-
if (!validProviders.includes(provider)) {
|
|
41
|
-
console.error(`ā Invalid provider: "${provider}". Must be one of: ${validProviders.join(', ')}`);
|
|
42
|
-
process.exit(1);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return provider;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function initializeBonzai() {
|
|
49
|
-
const bonzaiPath = join(process.cwd(), BONZAI_DIR);
|
|
50
|
-
const specsPath = join(bonzaiPath, SPECS_FILE);
|
|
51
|
-
const configPath = join(bonzaiPath, CONFIG_FILE);
|
|
52
|
-
|
|
53
|
-
// Check if bonzai/ folder exists
|
|
54
|
-
if (!fs.existsSync(bonzaiPath)) {
|
|
55
|
-
fs.mkdirSync(bonzaiPath, { recursive: true });
|
|
56
|
-
console.log(`š Created ${BONZAI_DIR}/ folder`);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Copy specs.md from package template
|
|
60
|
-
if (!fs.existsSync(specsPath)) {
|
|
61
|
-
fs.copyFileSync(join(TEMPLATE_DIR, SPECS_FILE), specsPath);
|
|
62
|
-
console.log(`š Created ${BONZAI_DIR}/${SPECS_FILE}`);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Copy config.json from package template
|
|
66
|
-
if (!fs.existsSync(configPath)) {
|
|
67
|
-
fs.copyFileSync(join(TEMPLATE_DIR, CONFIG_FILE), configPath);
|
|
68
|
-
console.log(`āļø Created ${BONZAI_DIR}/${CONFIG_FILE}`);
|
|
69
|
-
console.log(`\nā ļø Please edit ${BONZAI_DIR}/${SPECS_FILE} to define your cleanup rules before running bburn.\n`);
|
|
70
|
-
process.exit(0);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function ensureBonzaiDir() {
|
|
75
|
-
const bonzaiPath = join(process.cwd(), BONZAI_DIR);
|
|
76
|
-
const specsPath = join(bonzaiPath, SPECS_FILE);
|
|
77
|
-
const configPath = join(bonzaiPath, CONFIG_FILE);
|
|
78
|
-
|
|
79
|
-
if (!fs.existsSync(bonzaiPath)) {
|
|
80
|
-
fs.mkdirSync(bonzaiPath, { recursive: true });
|
|
81
|
-
console.log(`š Created ${BONZAI_DIR}/ folder\n`);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (!fs.existsSync(specsPath)) {
|
|
85
|
-
fs.copyFileSync(join(TEMPLATE_DIR, SPECS_FILE), specsPath);
|
|
86
|
-
console.log(`š Created ${BONZAI_DIR}/${SPECS_FILE} - edit this file to define your cleanup specs\n`);
|
|
87
|
-
}
|
|
9
|
+
/**
|
|
10
|
+
* Load project config from bonzai/config.json
|
|
11
|
+
* This is the source of truth for all burn configuration
|
|
12
|
+
*/
|
|
13
|
+
function loadConfig() {
|
|
14
|
+
const configPath = join(process.cwd(), BONZAI_DIR, CONFIG_FILE);
|
|
88
15
|
|
|
89
16
|
if (!fs.existsSync(configPath)) {
|
|
90
|
-
|
|
91
|
-
console.
|
|
17
|
+
console.error(`ā No config found at ${BONZAI_DIR}/${CONFIG_FILE}`);
|
|
18
|
+
console.error(` Run 'bonzai-burn' to initialize.\n`);
|
|
19
|
+
process.exit(1);
|
|
92
20
|
}
|
|
93
21
|
|
|
94
|
-
return { specsPath, configPath };
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function loadConfig(configPath) {
|
|
98
22
|
try {
|
|
99
23
|
const content = fs.readFileSync(configPath, 'utf-8');
|
|
100
24
|
return JSON.parse(content);
|
|
101
|
-
} catch {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function loadSpecs(specsPath, config) {
|
|
107
|
-
let content = fs.readFileSync(specsPath, 'utf-8');
|
|
108
|
-
|
|
109
|
-
// Process lineLimit if enabled
|
|
110
|
-
if (config.lineLimit?.enabled) {
|
|
111
|
-
content = content.replace(/\{\{\s*linelimit\s*\}\}/gi, config.lineLimit.limit);
|
|
112
|
-
} else {
|
|
113
|
-
// Remove lines containing {{ linelimit }} if disabled
|
|
114
|
-
content = content.split('\n')
|
|
115
|
-
.filter(line => !/\{\{\s*linelimit\s*\}\}/i.test(line))
|
|
116
|
-
.join('\n');
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Process folderLimit if enabled
|
|
120
|
-
if (config.folderLimit?.enabled) {
|
|
121
|
-
content = content.replace(/\{\{\s*folderlimit\s*\}\}/gi, config.folderLimit.limit);
|
|
122
|
-
} else {
|
|
123
|
-
// Remove lines containing {{ folderlimit }} if disabled
|
|
124
|
-
content = content.split('\n')
|
|
125
|
-
.filter(line => !/\{\{\s*folderlimit\s*\}\}/i.test(line))
|
|
126
|
-
.join('\n');
|
|
25
|
+
} catch (e) {
|
|
26
|
+
console.error(`ā Could not parse ${BONZAI_DIR}/${CONFIG_FILE}`);
|
|
27
|
+
process.exit(1);
|
|
127
28
|
}
|
|
128
|
-
|
|
129
|
-
return `You are a code cleanup assistant. Follow these specifications:\n\n${content}`;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function exec(command) {
|
|
133
|
-
return execSync(command, { encoding: 'utf-8', stdio: 'pipe' }).trim();
|
|
134
29
|
}
|
|
135
30
|
|
|
136
|
-
function
|
|
137
|
-
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function executeClaude(requirements, config) {
|
|
141
|
-
// Check if Claude CLI exists
|
|
142
|
-
try {
|
|
143
|
-
execSync('which claude', { encoding: 'utf-8', stdio: 'pipe' });
|
|
144
|
-
} catch (error) {
|
|
145
|
-
throw new Error(
|
|
146
|
-
'Claude Code CLI not found.\n' +
|
|
147
|
-
'Install it with: npm install -g @anthropic-ai/claude-code'
|
|
148
|
-
);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const debugMode = config.debugMode === true || config.debugMode === 'true';
|
|
152
|
-
|
|
153
|
-
// Debug mode: run Claude with visible output
|
|
154
|
-
if (debugMode) {
|
|
155
|
-
console.log('š Running in debug mode...\n');
|
|
156
|
-
return new Promise((resolve, reject) => {
|
|
157
|
-
const args = [
|
|
158
|
-
'-p', requirements,
|
|
159
|
-
'--allowedTools', 'Read,Write,Edit,Bash',
|
|
160
|
-
'--permission-mode', 'dontAsk'
|
|
161
|
-
];
|
|
162
|
-
|
|
163
|
-
const claude = spawn('claude', args, {
|
|
164
|
-
stdio: 'inherit'
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
claude.on('close', (code) => {
|
|
168
|
-
if (code === 0) {
|
|
169
|
-
resolve();
|
|
170
|
-
} else {
|
|
171
|
-
reject(new Error(`Claude exited with code ${code}`));
|
|
172
|
-
}
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
claude.on('error', (err) => {
|
|
176
|
-
reject(new Error(`Failed to execute Claude: ${err.message}`));
|
|
177
|
-
});
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Headless mode with token tracking
|
|
182
|
-
let totalInputTokens = 0;
|
|
183
|
-
let totalOutputTokens = 0;
|
|
184
|
-
let lastToolName = '';
|
|
185
|
-
|
|
186
|
-
return new Promise((resolve, reject) => {
|
|
187
|
-
const args = [
|
|
188
|
-
'-p', requirements,
|
|
189
|
-
'--allowedTools', 'Read,Write,Edit,Bash',
|
|
190
|
-
'--permission-mode', 'dontAsk',
|
|
191
|
-
'--output-format', 'stream-json',
|
|
192
|
-
'--verbose'
|
|
193
|
-
];
|
|
194
|
-
|
|
195
|
-
const claude = spawn('claude', args, {
|
|
196
|
-
stdio: ['inherit', 'pipe', 'pipe']
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
let buffer = '';
|
|
200
|
-
|
|
201
|
-
claude.stdout.on('data', (data) => {
|
|
202
|
-
buffer += data.toString();
|
|
203
|
-
|
|
204
|
-
// Process complete JSON lines
|
|
205
|
-
const lines = buffer.split('\n');
|
|
206
|
-
buffer = lines.pop(); // Keep incomplete line in buffer
|
|
207
|
-
|
|
208
|
-
for (const line of lines) {
|
|
209
|
-
if (!line.trim()) continue;
|
|
210
|
-
|
|
211
|
-
try {
|
|
212
|
-
const event = JSON.parse(line);
|
|
31
|
+
async function main() {
|
|
32
|
+
console.log('\nš„ Bonzai Burn - Code Analysis\n');
|
|
213
33
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
const usage = event.message.usage;
|
|
217
|
-
if (usage.input_tokens) totalInputTokens += usage.input_tokens;
|
|
218
|
-
if (usage.output_tokens) totalOutputTokens += usage.output_tokens;
|
|
219
|
-
}
|
|
34
|
+
// Load config - source of truth
|
|
35
|
+
const config = loadConfig();
|
|
220
36
|
|
|
221
|
-
|
|
222
|
-
if (event.type === 'content_block_start' && event.content_block?.type === 'tool_use') {
|
|
223
|
-
lastToolName = event.content_block.name || '';
|
|
224
|
-
}
|
|
37
|
+
console.log('Scanning...\n');
|
|
225
38
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
lastToolName = '';
|
|
230
|
-
}
|
|
39
|
+
// Run analysis
|
|
40
|
+
const results = await analyze(process.cwd(), config);
|
|
41
|
+
const { output, totalIssues } = formatAnalysisResults(results);
|
|
231
42
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
totalOutputTokens = event.usage.output_tokens || totalOutputTokens;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
} catch (e) {
|
|
242
|
-
// Not valid JSON, skip
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
claude.stderr.on('data', (data) => {
|
|
248
|
-
// Show errors but don't clutter with minor stderr
|
|
249
|
-
const msg = data.toString().trim();
|
|
250
|
-
if (msg && !msg.includes('ExperimentalWarning')) {
|
|
251
|
-
console.error(msg);
|
|
252
|
-
}
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
claude.on('close', (code) => {
|
|
256
|
-
// Print token summary
|
|
257
|
-
console.log(`\nš Tokens: ${totalInputTokens.toLocaleString()} in / ${totalOutputTokens.toLocaleString()} out`);
|
|
258
|
-
|
|
259
|
-
if (code === 0) {
|
|
260
|
-
resolve();
|
|
261
|
-
} else {
|
|
262
|
-
reject(new Error(`Claude exited with code ${code}`));
|
|
263
|
-
}
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
claude.on('error', (err) => {
|
|
267
|
-
reject(new Error(`Failed to execute Claude: ${err.message}`));
|
|
268
|
-
});
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
function getToolIcon(toolName) {
|
|
273
|
-
const icons = {
|
|
274
|
-
'Read': 'š',
|
|
275
|
-
'Write': 'āļø',
|
|
276
|
-
'Edit': 'š§',
|
|
277
|
-
'Bash': 'š»',
|
|
278
|
-
'Glob': 'š',
|
|
279
|
-
'Grep': 'š'
|
|
280
|
-
};
|
|
281
|
-
return icons[toolName] || 'š¹';
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
function executeCursor(requirements, config) {
|
|
285
|
-
// Check if cursor-agent CLI exists
|
|
286
|
-
try {
|
|
287
|
-
execSync('which cursor-agent', { encoding: 'utf-8', stdio: 'pipe' });
|
|
288
|
-
} catch (error) {
|
|
289
|
-
throw new Error(
|
|
290
|
-
'cursor-agent CLI not found.\n' +
|
|
291
|
-
'Install it with: npm install -g cursor-agent'
|
|
292
|
-
);
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
const debugMode = config.debugMode === true || config.debugMode === 'true';
|
|
296
|
-
|
|
297
|
-
// Debug mode: run cursor-agent with verbose output
|
|
298
|
-
if (debugMode) {
|
|
299
|
-
console.log('š Running in debug mode...\n');
|
|
300
|
-
return new Promise((resolve, reject) => {
|
|
301
|
-
const args = ['-p', requirements];
|
|
302
|
-
|
|
303
|
-
const cursor = spawn('cursor-agent', args, {
|
|
304
|
-
stdio: 'inherit'
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
cursor.on('close', (code) => {
|
|
308
|
-
if (code === 0) {
|
|
309
|
-
resolve();
|
|
310
|
-
} else {
|
|
311
|
-
reject(new Error(`cursor-agent exited with code ${code}`));
|
|
312
|
-
}
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
cursor.on('error', (err) => {
|
|
316
|
-
reject(new Error(`Failed to execute cursor-agent: ${err.message}`));
|
|
317
|
-
});
|
|
318
|
-
});
|
|
43
|
+
// Display results
|
|
44
|
+
if (totalIssues > 0 || results.customRequirements) {
|
|
45
|
+
console.log(output);
|
|
46
|
+
} else {
|
|
47
|
+
console.log('ā No issues found\n');
|
|
319
48
|
}
|
|
320
49
|
|
|
321
|
-
//
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
let lastToolName = '';
|
|
325
|
-
|
|
326
|
-
return new Promise((resolve, reject) => {
|
|
327
|
-
const args = [
|
|
328
|
-
'-p', requirements,
|
|
329
|
-
'--output-format', 'stream-json'
|
|
330
|
-
];
|
|
331
|
-
|
|
332
|
-
const cursor = spawn('cursor-agent', args, {
|
|
333
|
-
stdio: ['inherit', 'pipe', 'pipe']
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
let buffer = '';
|
|
337
|
-
|
|
338
|
-
cursor.stdout.on('data', (data) => {
|
|
339
|
-
buffer += data.toString();
|
|
340
|
-
|
|
341
|
-
// Process complete JSON lines
|
|
342
|
-
const lines = buffer.split('\n');
|
|
343
|
-
buffer = lines.pop(); // Keep incomplete line in buffer
|
|
344
|
-
|
|
345
|
-
for (const line of lines) {
|
|
346
|
-
if (!line.trim()) continue;
|
|
347
|
-
|
|
348
|
-
try {
|
|
349
|
-
const event = JSON.parse(line);
|
|
350
|
-
|
|
351
|
-
// Track tokens from assistant messages
|
|
352
|
-
if (event.type === 'assistant' && event.message?.usage) {
|
|
353
|
-
const usage = event.message.usage;
|
|
354
|
-
if (usage.input_tokens) totalInputTokens += usage.input_tokens;
|
|
355
|
-
if (usage.output_tokens) totalOutputTokens += usage.output_tokens;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
// Show tool usage updates
|
|
359
|
-
if (event.type === 'content_block_start' && event.content_block?.type === 'tool_use') {
|
|
360
|
-
lastToolName = event.content_block.name || '';
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
if (event.type === 'content_block_stop' && lastToolName) {
|
|
364
|
-
const icon = getToolIcon(lastToolName);
|
|
365
|
-
console.log(` ${icon} ${lastToolName}`);
|
|
366
|
-
lastToolName = '';
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
// Show result events with usage info
|
|
370
|
-
if (event.type === 'result') {
|
|
371
|
-
if (event.usage) {
|
|
372
|
-
totalInputTokens = event.usage.input_tokens || totalInputTokens;
|
|
373
|
-
totalOutputTokens = event.usage.output_tokens || totalOutputTokens;
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
} catch (e) {
|
|
378
|
-
// Not valid JSON, skip
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
cursor.stderr.on('data', (data) => {
|
|
384
|
-
const msg = data.toString().trim();
|
|
385
|
-
if (msg && !msg.includes('ExperimentalWarning')) {
|
|
386
|
-
console.error(msg);
|
|
387
|
-
}
|
|
388
|
-
});
|
|
389
|
-
|
|
390
|
-
cursor.on('close', (code) => {
|
|
391
|
-
// Print token summary
|
|
392
|
-
console.log(`\nš Tokens: ${totalInputTokens.toLocaleString()} in / ${totalOutputTokens.toLocaleString()} out`);
|
|
393
|
-
|
|
394
|
-
if (code === 0) {
|
|
395
|
-
resolve();
|
|
396
|
-
} else {
|
|
397
|
-
reject(new Error(`cursor-agent exited with code ${code}`));
|
|
398
|
-
}
|
|
399
|
-
});
|
|
400
|
-
|
|
401
|
-
cursor.on('error', (err) => {
|
|
402
|
-
reject(new Error(`Failed to execute cursor-agent: ${err.message}`));
|
|
403
|
-
});
|
|
404
|
-
});
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
async function burn() {
|
|
408
|
-
try {
|
|
409
|
-
// Initialize bonzai folder and specs.md on first execution
|
|
410
|
-
initializeBonzai();
|
|
411
|
-
|
|
412
|
-
// Ensure bonzai directory and specs file exist
|
|
413
|
-
const { specsPath, configPath } = ensureBonzaiDir();
|
|
414
|
-
const config = loadConfig(configPath);
|
|
415
|
-
const specs = loadSpecs(specsPath, config);
|
|
416
|
-
|
|
417
|
-
// Determine provider: CLI arg overrides config
|
|
418
|
-
const provider = parseProvider(config.provider || 'claude');
|
|
419
|
-
|
|
420
|
-
// Check if in git repo
|
|
421
|
-
try {
|
|
422
|
-
exec('git rev-parse --git-dir');
|
|
423
|
-
} catch {
|
|
424
|
-
console.error('ā Not a git repository');
|
|
425
|
-
process.exit(1);
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
// Get current branch
|
|
429
|
-
const originalBranch = exec('git branch --show-current');
|
|
430
|
-
|
|
431
|
-
// Handle uncommitted changes - auto-commit to current branch
|
|
432
|
-
const hasChanges = exec('git status --porcelain') !== '';
|
|
433
|
-
let madeWipCommit = false;
|
|
434
|
-
|
|
435
|
-
if (hasChanges) {
|
|
436
|
-
const timestamp = Date.now();
|
|
437
|
-
console.log('š¾ Auto-committing your work...');
|
|
438
|
-
exec('git add -A');
|
|
439
|
-
exec(`git commit -m "WIP: pre-burn checkpoint ${timestamp}"`);
|
|
440
|
-
madeWipCommit = true;
|
|
441
|
-
console.log(`ā Work saved on ${originalBranch}\n`);
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
// Generate unique branch name with short UUID
|
|
445
|
-
const shortId = crypto.randomUUID().slice(0, 8);
|
|
446
|
-
const burnBranch = `bonzai-burn-${shortId}`;
|
|
447
|
-
|
|
448
|
-
console.log(`š Starting from: ${originalBranch}`);
|
|
449
|
-
console.log(`šæ Creating: ${burnBranch}\n`);
|
|
450
|
-
|
|
451
|
-
// Create burn branch from current position
|
|
452
|
-
exec(`git checkout -b ${burnBranch}`);
|
|
453
|
-
|
|
454
|
-
// Save metadata for revert
|
|
455
|
-
exec(`git config bonzai.originalBranch ${originalBranch}`);
|
|
456
|
-
exec(`git config bonzai.burnBranch ${burnBranch}`);
|
|
457
|
-
exec(`git config bonzai.madeWipCommit ${madeWipCommit}`);
|
|
458
|
-
|
|
459
|
-
console.log(`š Specs loaded from: ${BONZAI_DIR}/${SPECS_FILE}`);
|
|
460
|
-
console.log(`š¤ Provider: ${provider}`);
|
|
461
|
-
console.log(`š Debug mode: ${config.debugMode === true || config.debugMode === 'true' ? 'on' : 'off'}`);
|
|
462
|
-
console.log('š„ Running Bonzai burn...\n');
|
|
463
|
-
|
|
464
|
-
const startTime = Date.now();
|
|
465
|
-
|
|
466
|
-
// Execute with the selected provider
|
|
467
|
-
if (provider === 'cursor') {
|
|
468
|
-
await executeCursor(specs, config);
|
|
469
|
-
} else {
|
|
470
|
-
await executeClaude(specs, config);
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
const duration = Math.round((Date.now() - startTime) / 1000);
|
|
474
|
-
|
|
475
|
-
console.log(`\nā Burn complete (${duration}s)\n`);
|
|
476
|
-
|
|
477
|
-
// Commit burn changes
|
|
478
|
-
const burnTimestamp = Date.now();
|
|
479
|
-
exec('git add -A');
|
|
480
|
-
exec(`git commit -m "bonzai burn ${burnTimestamp}" --allow-empty`);
|
|
481
|
-
|
|
482
|
-
console.log('Files changed from original:');
|
|
483
|
-
execVisible(`git diff --stat ${originalBranch}..${burnBranch}`);
|
|
484
|
-
|
|
485
|
-
console.log(`\nā
Changes applied on: ${burnBranch}`);
|
|
486
|
-
console.log(`š Full diff: git diff ${originalBranch}`);
|
|
487
|
-
console.log(`\nā Keep changes: baccept`);
|
|
488
|
-
console.log(`ā Discard: brevert\n`);
|
|
489
|
-
|
|
490
|
-
} catch (error) {
|
|
491
|
-
console.error('ā Burn failed:', error.message);
|
|
492
|
-
if (error.message.includes('Claude Code CLI not found')) {
|
|
493
|
-
console.error('\n' + error.message);
|
|
494
|
-
}
|
|
495
|
-
process.exit(1);
|
|
496
|
-
}
|
|
50
|
+
// Summary
|
|
51
|
+
console.log('ā'.repeat(50));
|
|
52
|
+
console.log(`Found ${totalIssues} issues across ${results.filesScanned} files (${results.durationMs}ms)\n`);
|
|
497
53
|
}
|
|
498
54
|
|
|
499
|
-
|
|
55
|
+
main().catch((error) => {
|
|
56
|
+
console.error('Error:', error.message);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
});
|
package/src/index.js
CHANGED
|
@@ -19,11 +19,10 @@ function init() {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
mkdirSync(bonzaiPath, { recursive: true });
|
|
22
|
-
copyFileSync(join(TEMPLATE_DIR, 'specs.md'), join(bonzaiPath, 'specs.md'));
|
|
23
22
|
copyFileSync(join(TEMPLATE_DIR, 'config.json'), join(bonzaiPath, 'config.json'));
|
|
24
|
-
console.log(`š Created ${BONZAI_DIR}/ folder with
|
|
25
|
-
console.log(`š Edit ${BONZAI_DIR}/
|
|
26
|
-
console.log(`š„ Run 'bburn' to
|
|
23
|
+
console.log(`š Created ${BONZAI_DIR}/ folder with config.json`);
|
|
24
|
+
console.log(`š Edit ${BONZAI_DIR}/config.json to configure your burn rules`);
|
|
25
|
+
console.log(`š„ Run 'bburn' to analyze your codebase`);
|
|
27
26
|
console.log('');
|
|
28
27
|
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
29
28
|
console.log('ā ā');
|
package/payload-bonzai/specs.md
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
# Bonzai Specs
|
|
2
|
-
|
|
3
|
-
Define your cleanup requirements below. bburn will follow these instructions.
|
|
4
|
-
|
|
5
|
-
## Custom Requirements:
|
|
6
|
-
- Remove unused imports and variables
|
|
7
|
-
- Remove all console log statements
|
|
8
|
-
- Split any file with over {{ linelimit }} lines into smaller files
|
|
9
|
-
- Split any folder with over {{ folderlimit }} items into smaller, compartmentalized folders
|
package/src/baccept.js
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { execSync } from 'child_process';
|
|
3
|
-
|
|
4
|
-
function exec(command) {
|
|
5
|
-
return execSync(command, { encoding: 'utf-8', stdio: 'pipe' }).trim();
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
function execVisible(command) {
|
|
9
|
-
execSync(command, { stdio: 'inherit' });
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
async function accept() {
|
|
13
|
-
try {
|
|
14
|
-
// Get saved metadata
|
|
15
|
-
let originalBranch;
|
|
16
|
-
let burnBranch;
|
|
17
|
-
let madeWipCommit;
|
|
18
|
-
|
|
19
|
-
try {
|
|
20
|
-
originalBranch = exec('git config bonzai.originalBranch');
|
|
21
|
-
burnBranch = exec('git config bonzai.burnBranch');
|
|
22
|
-
madeWipCommit = exec('git config bonzai.madeWipCommit') === 'true';
|
|
23
|
-
} catch {
|
|
24
|
-
console.error('ā No burn to accept');
|
|
25
|
-
console.error('Run bburn first');
|
|
26
|
-
process.exit(1);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
console.log(`ā
Accepting burn changes...`);
|
|
30
|
-
console.log(` Merging: ${burnBranch} ā ${originalBranch}\n`);
|
|
31
|
-
|
|
32
|
-
// Checkout original branch
|
|
33
|
-
execVisible(`git checkout ${originalBranch}`);
|
|
34
|
-
|
|
35
|
-
// Merge burn branch into original
|
|
36
|
-
execVisible(`git merge ${burnBranch} -m "Accept bonzai burn from ${burnBranch}"`);
|
|
37
|
-
|
|
38
|
-
// If we made a WIP commit, we need to handle it
|
|
39
|
-
// The merge already includes the burn changes on top of the WIP commit
|
|
40
|
-
// So we can optionally squash or leave as-is
|
|
41
|
-
if (madeWipCommit) {
|
|
42
|
-
console.log('\nš” Note: Your pre-burn WIP commit was preserved in the history.');
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Clean up metadata
|
|
46
|
-
exec('git config --unset bonzai.originalBranch');
|
|
47
|
-
exec('git config --unset bonzai.burnBranch');
|
|
48
|
-
exec('git config --unset bonzai.madeWipCommit');
|
|
49
|
-
|
|
50
|
-
console.log(`\nā Burn accepted and merged`);
|
|
51
|
-
console.log(`Now on: ${originalBranch}`);
|
|
52
|
-
console.log(`Branch kept: ${burnBranch}\n`);
|
|
53
|
-
|
|
54
|
-
} catch (error) {
|
|
55
|
-
console.error('ā Accept failed:', error.message);
|
|
56
|
-
process.exit(1);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
accept();
|
package/src/brevert.js
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { execSync } from 'child_process';
|
|
3
|
-
|
|
4
|
-
function exec(command) {
|
|
5
|
-
return execSync(command, { encoding: 'utf-8', stdio: 'pipe' }).trim();
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
function execVisible(command) {
|
|
9
|
-
execSync(command, { stdio: 'inherit' });
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
async function revert() {
|
|
13
|
-
try {
|
|
14
|
-
// Get saved metadata
|
|
15
|
-
let originalBranch;
|
|
16
|
-
let burnBranch;
|
|
17
|
-
let madeWipCommit;
|
|
18
|
-
|
|
19
|
-
try {
|
|
20
|
-
originalBranch = exec('git config bonzai.originalBranch');
|
|
21
|
-
burnBranch = exec('git config bonzai.burnBranch');
|
|
22
|
-
madeWipCommit = exec('git config bonzai.madeWipCommit') === 'true';
|
|
23
|
-
} catch {
|
|
24
|
-
console.error('ā No burn to revert');
|
|
25
|
-
console.error('Run bburn first');
|
|
26
|
-
process.exit(1);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
console.log(`š Reverting burn...`);
|
|
30
|
-
console.log(` Discarding: ${burnBranch}\n`);
|
|
31
|
-
|
|
32
|
-
// Checkout original branch
|
|
33
|
-
execVisible(`git checkout ${originalBranch}`);
|
|
34
|
-
|
|
35
|
-
// Delete burn branch
|
|
36
|
-
execVisible(`git branch -D ${burnBranch}`);
|
|
37
|
-
|
|
38
|
-
// Undo WIP commit if we made one
|
|
39
|
-
if (madeWipCommit) {
|
|
40
|
-
console.log('ā©ļø Undoing WIP commit...');
|
|
41
|
-
exec('git reset HEAD~1');
|
|
42
|
-
console.log('ā Back to uncommitted changes\n');
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Clean up metadata
|
|
46
|
-
exec('git config --unset bonzai.originalBranch');
|
|
47
|
-
exec('git config --unset bonzai.burnBranch');
|
|
48
|
-
exec('git config --unset bonzai.madeWipCommit');
|
|
49
|
-
|
|
50
|
-
console.log(`ā Burn fully reverted`);
|
|
51
|
-
console.log(`Back on: ${originalBranch}\n`);
|
|
52
|
-
|
|
53
|
-
} catch (error) {
|
|
54
|
-
console.error('ā Revert failed:', error.message);
|
|
55
|
-
process.exit(1);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
revert();
|