devbonzai 2.2.207 → 2.2.209

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devbonzai",
3
- "version": "2.2.207",
3
+ "version": "2.2.209",
4
4
  "description": "Quickly set up a local file server in any repository for browser-based file access",
5
5
  "main": "cli.js",
6
6
  "bin": {
@@ -1,10 +1,33 @@
1
1
  const { spawn, execSync } = require('child_process');
2
2
  const { ROOT } = require('../config');
3
3
 
4
+ // Token estimation: ~4 characters per token (rough approximation for English text)
5
+ function estimateTokens(text) {
6
+ if (!text) return 0;
7
+ return Math.ceil(text.length / 4);
8
+ }
9
+
10
+ // Cost estimation based on Claude 3.5 Sonnet pricing ($/1M tokens)
11
+ // Input: $3/1M tokens, Output: $15/1M tokens
12
+ function estimateCost(inputTokens, outputTokens) {
13
+ const inputCostPerMillion = 3.0;
14
+ const outputCostPerMillion = 15.0;
15
+ const inputCost = (inputTokens / 1_000_000) * inputCostPerMillion;
16
+ const outputCost = (outputTokens / 1_000_000) * outputCostPerMillion;
17
+ return {
18
+ inputCost: parseFloat(inputCost.toFixed(6)),
19
+ outputCost: parseFloat(outputCost.toFixed(6)),
20
+ totalCost: parseFloat((inputCost + outputCost).toFixed(6))
21
+ };
22
+ }
23
+
4
24
  function promptAgentStreamHandler(req, res) {
5
25
  console.log('🔵 [prompt_agent_stream] Endpoint hit');
6
26
  const { prompt } = req.body;
7
27
  console.log('🔵 [prompt_agent_stream] Received prompt:', prompt ? `${prompt.substring(0, 50)}...` : 'none');
28
+
29
+ // Start execution timer
30
+ const startTime = Date.now();
8
31
 
9
32
  if (!prompt || typeof prompt !== 'string') {
10
33
  console.log('❌ [prompt_agent_stream] Error: prompt required');
@@ -56,8 +79,8 @@ function promptAgentStreamHandler(req, res) {
56
79
  // Ignore
57
80
  }
58
81
 
59
- // Send starting event
60
- sendEvent('start', { beforeCommit });
82
+ // Send starting event with timestamp
83
+ sendEvent('start', { beforeCommit, startTimestamp: startTime });
61
84
 
62
85
  // Set up file change tracking with real-time updates
63
86
  const changedFiles = new Set();
@@ -121,19 +144,34 @@ function promptAgentStreamHandler(req, res) {
121
144
  if (res.destroyed || res.closed) {
122
145
  console.log('⚠️ [prompt_agent_stream] Response already closed, cannot send timeout events');
123
146
  } else {
124
- responseSent = true;
125
- sendEvent('error', {
126
- error: 'Process timeout',
127
- message: `cursor-agent exceeded timeout of ${timeoutMs / 1000} seconds`
128
- });
129
- sendEvent('complete', {
130
- code: -1,
131
- stdout,
132
- stderr,
133
- changedFiles: Array.from(changedFiles),
134
- beforeCommit,
135
- afterCommit: ''
136
- });
147
+ responseSent = true;
148
+
149
+ // Calculate metrics
150
+ const executionTimeMs = Date.now() - startTime;
151
+ const inputTokens = estimateTokens(prompt);
152
+ const outputTokens = estimateTokens(stdout);
153
+ const costEstimate = estimateCost(inputTokens, outputTokens);
154
+
155
+ sendEvent('error', {
156
+ error: 'Process timeout',
157
+ message: `cursor-agent exceeded timeout of ${timeoutMs / 1000} seconds`
158
+ });
159
+ sendEvent('complete', {
160
+ code: -1,
161
+ stdout,
162
+ stderr,
163
+ changedFiles: Array.from(changedFiles),
164
+ beforeCommit,
165
+ afterCommit: '',
166
+ metrics: {
167
+ executionTimeMs,
168
+ executionTimeSec: parseFloat((executionTimeMs / 1000).toFixed(2)),
169
+ inputTokens,
170
+ outputTokens,
171
+ totalTokens: inputTokens + outputTokens,
172
+ costEstimate
173
+ }
174
+ });
137
175
  // Send stop event after complete
138
176
  sendEvent('stop', {});
139
177
  res.end();
@@ -210,6 +248,19 @@ function promptAgentStreamHandler(req, res) {
210
248
  if (res.destroyed || res.closed) {
211
249
  console.log('⚠️ [prompt_agent_stream] Response already closed, cannot send complete event');
212
250
  } else {
251
+ // Calculate metrics
252
+ const executionTimeMs = Date.now() - startTime;
253
+ const inputTokens = estimateTokens(prompt);
254
+ const outputTokens = estimateTokens(stdout);
255
+ const costEstimate = estimateCost(inputTokens, outputTokens);
256
+
257
+ console.log('📊 [prompt_agent_stream] Metrics:', {
258
+ executionTimeMs,
259
+ inputTokens,
260
+ outputTokens,
261
+ costEstimate
262
+ });
263
+
213
264
  // Send events and only set flag after successful send
214
265
  sendEvent('complete', {
215
266
  code,
@@ -217,7 +268,15 @@ function promptAgentStreamHandler(req, res) {
217
268
  stderr,
218
269
  changedFiles: Array.from(changedFiles),
219
270
  beforeCommit,
220
- afterCommit
271
+ afterCommit,
272
+ metrics: {
273
+ executionTimeMs,
274
+ executionTimeSec: parseFloat((executionTimeMs / 1000).toFixed(2)),
275
+ inputTokens,
276
+ outputTokens,
277
+ totalTokens: inputTokens + outputTokens,
278
+ costEstimate
279
+ }
221
280
  });
222
281
  // Send stop event after complete
223
282
  sendEvent('stop', {});
@@ -1,180 +1,178 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const glob = require('glob');
4
- const { spawn } = require('child_process');
5
- const { listAllFiles } = require('../utils/fileList');
6
- const { getIgnorePatterns, shouldIgnore } = require('../utils/ignore');
7
-
8
1
  module.exports = async function scanStandards(req, res) {
9
2
  console.log('🔵 [scan_standards] Endpoint hit');
10
3
 
11
- try {
12
- const { projectPath, standards } = req.body;
13
- console.log('🔵 [scan_standards] projectPath:', projectPath);
14
- console.log('🔵 [scan_standards] standards:', Array.isArray(standards) ? standards.length + ' rules' : 'string input');
15
-
16
- if (!projectPath || !standards) {
17
- console.log('❌ [scan_standards] Error: projectPath and standards required');
18
- return res.status(400).json({ error: 'projectPath and standards required' });
4
+ // Return sample response immediately
5
+ const violations = [
6
+ {
7
+ "rule": "React Best Practices",
8
+ "file": "src/App.js",
9
+ "description": "Component file exceeds 200 lines (701 lines). Components should be split into smaller, focused components.",
10
+ "severity": "high"
11
+ },
12
+ {
13
+ "rule": "React Best Practices",
14
+ "file": "src/components/StatusChecklistModal.js",
15
+ "description": "Component file exceeds 200 lines (1320 lines). Component should be split into smaller, focused components.",
16
+ "severity": "high"
17
+ },
18
+ {
19
+ "rule": "React Best Practices",
20
+ "file": "src/components/code/ElementsGenerator.js",
21
+ "description": "Component file exceeds 200 lines (1097 lines). Component should be split into smaller, focused components.",
22
+ "severity": "high"
23
+ },
24
+ {
25
+ "rule": "React Best Practices",
26
+ "file": "src/components/JobsSidebar.js",
27
+ "description": "Component file exceeds 200 lines (652 lines). Component should be split into smaller, focused components.",
28
+ "severity": "high"
29
+ },
30
+ {
31
+ "rule": "React Best Practices",
32
+ "file": "src/components/jobs/JobsSidebar.js",
33
+ "description": "Component file exceeds 200 lines (554 lines). Component should be split into smaller, focused components.",
34
+ "severity": "high"
35
+ },
36
+ {
37
+ "rule": "React Best Practices",
38
+ "file": "src/components/jobs/JobCard.js",
39
+ "description": "Component file exceeds 200 lines (514 lines). Component should be split into smaller, focused components.",
40
+ "severity": "high"
41
+ },
42
+ {
43
+ "rule": "React Best Practices",
44
+ "file": "src/components/UniversalNode.js",
45
+ "description": "Component file exceeds 200 lines (538 lines). Component should be split into smaller, focused components.",
46
+ "severity": "high"
47
+ },
48
+ {
49
+ "rule": "React Best Practices",
50
+ "file": "src/App.js",
51
+ "description": "Component contains data fetching directly (scanCodeQuality, scanStandards calls in useEffect). Data fetching should be in custom hooks, not components.",
52
+ "severity": "high"
53
+ },
54
+ {
55
+ "rule": "React Best Practices",
56
+ "file": "src/components/StatusChecklistModal.js",
57
+ "description": "Component contains data fetching directly (fetch calls to /analyze_prompt). Data fetching should be in custom hooks, not components.",
58
+ "severity": "high"
59
+ },
60
+ {
61
+ "rule": "React Best Practices",
62
+ "file": "src/components/code/ElementsGenerator.js",
63
+ "description": "Component contains data fetching directly (multiple fetch calls for delete, move, write operations). Data fetching should be in custom hooks, not components.",
64
+ "severity": "high"
65
+ },
66
+ {
67
+ "rule": "React Best Practices",
68
+ "file": "src/components/JobsSidebar.js",
69
+ "description": "Component contains data fetching directly (fetch calls to /analyze_prompt, /prompt_agent_stream, /revert_job). Data fetching should be in custom hooks, not components.",
70
+ "severity": "high"
71
+ },
72
+ {
73
+ "rule": "React Best Practices",
74
+ "file": "src/components/jobs/JobsSidebar.js",
75
+ "description": "Component contains data fetching directly (fetch calls to /analyze_prompt, /prompt_agent_stream, /revert_job). Data fetching should be in custom hooks, not components.",
76
+ "severity": "high"
77
+ },
78
+ {
79
+ "rule": "React Best Practices",
80
+ "file": "src/components/FileList.js",
81
+ "description": "Component contains data fetching directly (fetch call to /list). Data fetching should be in custom hooks, not components.",
82
+ "severity": "medium"
83
+ },
84
+ {
85
+ "rule": "React Best Practices",
86
+ "file": "src/components/FileBrowser.js",
87
+ "description": "Component contains data fetching directly (fetch call to /read for line counts). Data fetching should be in custom hooks, not components.",
88
+ "severity": "medium"
89
+ },
90
+ {
91
+ "rule": "React Best Practices",
92
+ "file": "src/components/UniversalNode.js",
93
+ "description": "Component contains data fetching directly (fetch calls to /open-cursor and /read). Data fetching should be in custom hooks, not components.",
94
+ "severity": "medium"
95
+ },
96
+ {
97
+ "rule": "React Best Practices",
98
+ "file": "src/App.js",
99
+ "description": "Component contains business logic mixed with UI rendering (file operations, job management logic). Business logic should be in hooks or services, not components.",
100
+ "severity": "high"
101
+ },
102
+ {
103
+ "rule": "React Best Practices",
104
+ "file": "src/components/StatusChecklistModal.js",
105
+ "description": "Component contains business logic mixed with UI rendering (job creation, fix operations). Business logic should be in hooks or services, not components.",
106
+ "severity": "high"
107
+ },
108
+ {
109
+ "rule": "React Best Practices",
110
+ "file": "src/components/code/ElementsGenerator.js",
111
+ "description": "Component contains business logic mixed with UI rendering (file operations, tree manipulation). Business logic should be in hooks or services, not components.",
112
+ "severity": "high"
113
+ },
114
+ {
115
+ "rule": "React Best Practices",
116
+ "file": "src/components/JobsSidebar.js",
117
+ "description": "Component contains business logic mixed with UI rendering (job execution, state management). Business logic should be in hooks or services, not components.",
118
+ "severity": "high"
119
+ },
120
+ {
121
+ "rule": "React Best Practices",
122
+ "file": "src/App.js",
123
+ "description": "Props drilling detected - many props passed down multiple component levels (jobs, hoveredJobId, onJobsChange, etc.). Should use context for props beyond 2 levels.",
124
+ "severity": "medium"
125
+ },
126
+ {
127
+ "rule": "Feature-Based Organization",
128
+ "file": "src/components/JobsSidebar.js",
129
+ "description": "Duplicate file exists: both src/components/JobsSidebar.js and src/components/jobs/JobsSidebar.js contain similar functionality. Feature folders should be self-contained with no duplicates.",
130
+ "severity": "medium"
131
+ },
132
+ {
133
+ "rule": "Clean Architecture",
134
+ "file": "src/App.js",
135
+ "description": "Business logic is not independent of frameworks - React components contain business logic that should be in framework-independent services.",
136
+ "severity": "high"
137
+ },
138
+ {
139
+ "rule": "Clean Architecture",
140
+ "file": "src/components/StatusChecklistModal.js",
141
+ "description": "Business logic is not independent of frameworks - React component contains business logic that should be in framework-independent services.",
142
+ "severity": "high"
143
+ },
144
+ {
145
+ "rule": "Clean Architecture",
146
+ "file": "src/components/code/ElementsGenerator.js",
147
+ "description": "Business logic is not independent of frameworks - React component contains business logic that should be in framework-independent services.",
148
+ "severity": "high"
149
+ },
150
+ {
151
+ "rule": "Layered Architecture",
152
+ "file": "src/App.js",
153
+ "description": "Presentation layer contains data access (direct fetch calls). Data access should be in a separate data layer, not presentation layer.",
154
+ "severity": "high"
155
+ },
156
+ {
157
+ "rule": "Layered Architecture",
158
+ "file": "src/components/StatusChecklistModal.js",
159
+ "description": "Presentation layer contains data access (direct fetch calls). Data access should be in a separate data layer, not presentation layer.",
160
+ "severity": "high"
161
+ },
162
+ {
163
+ "rule": "Layered Architecture",
164
+ "file": "src/components/code/ElementsGenerator.js",
165
+ "description": "Presentation layer contains data access (direct fetch calls). Data access should be in a separate data layer, not presentation layer.",
166
+ "severity": "high"
167
+ },
168
+ {
169
+ "rule": "Layered Architecture",
170
+ "file": "src/components/JobsSidebar.js",
171
+ "description": "Presentation layer contains data access (direct fetch calls). Data access should be in a separate data layer, not presentation layer.",
172
+ "severity": "high"
19
173
  }
20
-
21
- // Get file structure for context
22
- console.log('🔵 [scan_standards] Getting file tree...');
23
- const fileTree = getFileTree(projectPath);
24
- console.log('🔵 [scan_standards] File tree entries:', fileTree.split('\n').length);
25
-
26
- // Sample a few key files for AI to analyze (don't send entire codebase)
27
- console.log('🔵 [scan_standards] Getting sample files...');
28
- const sampleFiles = getSampleFiles(projectPath, 10);
29
- console.log('🔵 [scan_standards] Sample files found:', sampleFiles.length);
30
- sampleFiles.forEach(f => console.log(' 📄', f.path, `(${f.size} bytes)`));
31
-
32
- const prompt = `Analyze this codebase for architectural violations.
33
-
34
- PROJECT STRUCTURE:
35
- ${fileTree}
36
-
37
- SAMPLE FILES:
38
- ${sampleFiles.map(f => `=== ${f.path} ===\n${f.content.substring(0, 500)}...\n`).join('\n')}
39
-
40
- STANDARDS TO CHECK:
41
- ${Array.isArray(standards) ? standards.map((s, i) => `${i + 1}. ${s}`).join('\n') : standards}
42
-
43
- CRITICAL: Your response must be ONLY a valid JSON array. No markdown, no explanation, no summary text.
44
- Start your response with [ and end with ]. Nothing else.
45
-
46
- Output format:
47
- [{"rule":"rule name","file":"path/to/file","description":"what violates it","severity":"high|medium|low"}]
48
-
49
- Empty if no violations: []`;
50
-
51
- console.log('🔵 [scan_standards] Prompt length:', prompt.length, 'chars');
52
-
53
- // Use cursor-agent like prompt_agent does
54
- const args = ['--print', '--force', '--workspace', projectPath, prompt];
55
- console.log('🔵 [scan_standards] Spawning cursor-agent...');
56
-
57
- const proc = spawn('cursor-agent', args, {
58
- cwd: projectPath,
59
- env: process.env,
60
- stdio: ['ignore', 'pipe', 'pipe']
61
- });
62
-
63
- console.log('🔵 [scan_standards] Process spawned, PID:', proc.pid);
64
-
65
- let stdout = '';
66
- let stderr = '';
67
-
68
- proc.stdout.on('data', (d) => {
69
- const data = d.toString();
70
- console.log('📤 [scan_standards] stdout chunk:', data.length, 'bytes');
71
- stdout += data;
72
- });
73
-
74
- proc.stderr.on('data', (d) => {
75
- const data = d.toString();
76
- console.log('⚠️ [scan_standards] stderr chunk:', data.length, 'bytes');
77
- stderr += data;
78
- });
79
-
80
- proc.on('error', (error) => {
81
- console.error('❌ [scan_standards] Process error:', error.message);
82
- return res.status(500).json({ error: error.message });
83
- });
84
-
85
- proc.on('close', (code) => {
86
- console.log('🔵 [scan_standards] Process closed with code:', code);
87
- console.log('🔵 [scan_standards] stdout total:', stdout.length, 'bytes');
88
- console.log('🔵 [scan_standards] stderr total:', stderr.length, 'bytes');
89
-
90
- if (code !== 0) {
91
- console.error('❌ [scan_standards] Failed with code:', code);
92
- console.error('❌ [scan_standards] stderr:', stderr);
93
- return res.status(500).json({ error: `Process exited with code ${code}`, stderr });
94
- }
95
-
96
- // Parse AI response
97
- console.log('🔵 [scan_standards] Parsing response...');
98
- const jsonMatch = stdout.match(/\[[\s\S]*\]/);
99
-
100
- if (!jsonMatch) {
101
- console.log('⚠️ [scan_standards] No JSON array found in response');
102
- console.log('⚠️ [scan_standards] Raw stdout:', stdout.substring(0, 500));
103
- }
104
-
105
- try {
106
- const violations = jsonMatch ? JSON.parse(jsonMatch[0]) : [];
107
- console.log('✅ [scan_standards] Found', violations.length, 'violations');
108
- res.json({ violations });
109
- } catch (parseError) {
110
- console.error('❌ [scan_standards] JSON parse error:', parseError.message);
111
- res.status(500).json({ error: 'Failed to parse response', raw: stdout });
112
- }
113
- });
114
-
115
- } catch (error) {
116
- console.error('❌ [scan_standards] Error:', error.message);
117
- res.status(500).json({ error: error.message });
118
- }
119
- };
120
-
121
- function getFileTree(projectPath) {
122
- try {
123
- const files = listAllFiles(projectPath);
124
- // Filter out virtual files (.function, .class, .method) for cleaner tree
125
- const realFiles = files.filter(f =>
126
- !f.endsWith('.function') &&
127
- !f.endsWith('.class') &&
128
- !f.endsWith('.method')
129
- );
130
- return realFiles.slice(0, 200).join('\n'); // Limit to 200 entries
131
- } catch (e) {
132
- console.error('❌ [scan_standards] Error getting file tree:', e);
133
- return 'Unable to read file tree';
134
- }
135
- }
136
-
137
- function getSampleFiles(projectPath, limit = 10) {
138
- const ignorePatterns = getIgnorePatterns();
139
-
140
- // Common build/output directories to always ignore
141
- const defaultIgnoreGlobs = [
142
- '**/node_modules/**',
143
- '**/dist/**',
144
- '**/build/**',
145
- '**/static/**',
146
- '**/out/**',
147
- '**/.next/**',
148
- '**/*.min.js',
149
- '**/*.bundle.js',
150
- '**/*.chunk.js'
151
174
  ];
152
175
 
153
- // Get representative files
154
- const files = glob.sync('**/*.{js,ts,jsx,tsx,py,java,go}', {
155
- cwd: projectPath,
156
- nodir: true,
157
- ignore: defaultIgnoreGlobs
158
- });
159
-
160
- // Filter using ignore patterns and sort by size, take top N
161
- return files
162
- .filter(f => !shouldIgnore(f, ignorePatterns))
163
- .map(f => {
164
- const fullPath = path.join(projectPath, f);
165
- try {
166
- const stat = fs.statSync(fullPath);
167
- const content = fs.readFileSync(fullPath, 'utf8');
168
- return {
169
- path: f,
170
- content,
171
- size: stat.size
172
- };
173
- } catch (e) {
174
- return null;
175
- }
176
- })
177
- .filter(Boolean)
178
- .sort((a, b) => b.size - a.size)
179
- .slice(0, limit);
180
- }
176
+ console.log('✅ [scan_standards] Returning', violations.length, 'sample violations');
177
+ res.json({ violations });
178
+ };