deepdebug-local-agent 1.0.18 → 1.0.19

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": "deepdebug-local-agent",
3
- "version": "1.0.18",
3
+ "version": "1.0.19",
4
4
  "description": "DeepDebug Local Agent - AI-powered code debugging assistant",
5
5
  "private": false,
6
6
  "type": "module",
package/src/exec-utils.js CHANGED
@@ -47,20 +47,38 @@ export function run(cmd, args, cwd, timeoutMs = 10 * 60 * 1000) {
47
47
  * @returns {Promise<{code: number, stdout: string, stderr: string, duration: number}>}
48
48
  */
49
49
  export async function compileAndTest({ language, buildTool, cwd, skipTests = false }) {
50
- console.log(`🔨 [COMPILE] Language: ${language}, BuildTool: ${buildTool}, SkipTests: ${skipTests}`);
50
+ console.log(`[BUILD] [COMPILE] Language: ${language}, BuildTool: ${buildTool}, SkipTests: ${skipTests}`);
51
51
 
52
52
  let result;
53
53
  const start = Date.now();
54
54
 
55
55
  try {
56
56
  if (language === "java" && buildTool === "maven") {
57
- // Maven: clean install (skip tests if requested)
57
+ // Prefer ./mvnw wrapper over system mvn
58
+ const fs = await import("fs");
59
+ const path = await import("path");
60
+ const hasMvnw = fs.existsSync(path.join(cwd, "mvnw"));
61
+ const mvnCmd = hasMvnw ? "./mvnw" : "mvn";
62
+
63
+ // Verify build tool is available before attempting
64
+ if (!hasMvnw) {
65
+ const check = await run("which mvn", [], cwd);
66
+ if (check.code !== 0) {
67
+ return {
68
+ code: 1,
69
+ stdout: "",
70
+ stderr: "Maven not found: neither ./mvnw nor mvn is available in this environment.",
71
+ duration: Date.now() - start
72
+ };
73
+ }
74
+ }
75
+
58
76
  const args = skipTests
59
- ? ["clean", "install", "-DskipTests", "-q"]
60
- : ["clean", "install", "-q"];
77
+ ? ["compile", "-q", "-DskipTests"]
78
+ : ["test", "-q"];
61
79
 
62
- console.log(`🔨 [COMPILE] Running: mvn ${args.join(" ")}`);
63
- result = await run("mvn", args, cwd);
80
+ console.log(`[BUILD] [COMPILE] Running: ${mvnCmd} ${args.join(" ")}`);
81
+ result = await run(mvnCmd, args, cwd);
64
82
  }
65
83
  else if (language === "java" && buildTool === "gradle") {
66
84
  // Gradle: clean build
@@ -68,12 +86,12 @@ export async function compileAndTest({ language, buildTool, cwd, skipTests = fal
68
86
  ? ["clean", "build", "-x", "test"]
69
87
  : ["clean", "build"];
70
88
 
71
- console.log(`🔨 [COMPILE] Running: ./gradlew ${args.join(" ")}`);
89
+ console.log(`[BUILD] [COMPILE] Running: ./gradlew ${args.join(" ")}`);
72
90
  result = await run("./gradlew", args, cwd);
73
91
  }
74
92
  else if (language === "node" || buildTool === "npm") {
75
93
  // Node/npm: install and optionally test
76
- console.log(`🔨 [COMPILE] Running: npm install`);
94
+ console.log(`[BUILD] [COMPILE] Running: npm install`);
77
95
  const installResult = await run("npm", ["install", "--silent"], cwd);
78
96
 
79
97
  if (installResult.code !== 0) {
@@ -81,7 +99,7 @@ export async function compileAndTest({ language, buildTool, cwd, skipTests = fal
81
99
  }
82
100
 
83
101
  if (!skipTests) {
84
- console.log(`🔨 [COMPILE] Running: npm test`);
102
+ console.log(`[BUILD] [COMPILE] Running: npm test`);
85
103
  result = await run("npm", ["test", "--silent"], cwd);
86
104
  } else {
87
105
  result = installResult;
@@ -89,7 +107,7 @@ export async function compileAndTest({ language, buildTool, cwd, skipTests = fal
89
107
  }
90
108
  else if (language === "node" || buildTool === "yarn") {
91
109
  // Yarn
92
- console.log(`🔨 [COMPILE] Running: yarn install`);
110
+ console.log(`[BUILD] [COMPILE] Running: yarn install`);
93
111
  const installResult = await run("yarn", ["install", "--silent"], cwd);
94
112
 
95
113
  if (installResult.code !== 0) {
@@ -97,7 +115,7 @@ export async function compileAndTest({ language, buildTool, cwd, skipTests = fal
97
115
  }
98
116
 
99
117
  if (!skipTests) {
100
- console.log(`🔨 [COMPILE] Running: yarn test`);
118
+ console.log(`[BUILD] [COMPILE] Running: yarn test`);
101
119
  result = await run("yarn", ["test", "--silent"], cwd);
102
120
  } else {
103
121
  result = installResult;
@@ -106,7 +124,7 @@ export async function compileAndTest({ language, buildTool, cwd, skipTests = fal
106
124
  else if (language === "python") {
107
125
  // Python: pytest
108
126
  if (!skipTests) {
109
- console.log(`🔨 [COMPILE] Running: pytest`);
127
+ console.log(`[BUILD] [COMPILE] Running: pytest`);
110
128
  result = await run("pytest", [], cwd);
111
129
  } else {
112
130
  // Python doesn't have a compile step, just return success
@@ -115,7 +133,7 @@ export async function compileAndTest({ language, buildTool, cwd, skipTests = fal
115
133
  }
116
134
  else if (language === "go") {
117
135
  // Go: build and optionally test
118
- console.log(`🔨 [COMPILE] Running: go build ./...`);
136
+ console.log(`[BUILD] [COMPILE] Running: go build ./...`);
119
137
  const buildResult = await run("go", ["build", "./..."], cwd);
120
138
 
121
139
  if (buildResult.code !== 0) {
@@ -123,7 +141,7 @@ export async function compileAndTest({ language, buildTool, cwd, skipTests = fal
123
141
  }
124
142
 
125
143
  if (!skipTests) {
126
- console.log(`🔨 [COMPILE] Running: go test ./...`);
144
+ console.log(`[BUILD] [COMPILE] Running: go test ./...`);
127
145
  result = await run("go", ["test", "./..."], cwd);
128
146
  } else {
129
147
  result = buildResult;
@@ -131,7 +149,7 @@ export async function compileAndTest({ language, buildTool, cwd, skipTests = fal
131
149
  }
132
150
  else if (language === ".net" || language === "dotnet") {
133
151
  // .NET: build and optionally test
134
- console.log(`🔨 [COMPILE] Running: dotnet build`);
152
+ console.log(`[BUILD] [COMPILE] Running: dotnet build`);
135
153
  const buildResult = await run("dotnet", ["build"], cwd);
136
154
 
137
155
  if (buildResult.code !== 0) {
@@ -139,14 +157,14 @@ export async function compileAndTest({ language, buildTool, cwd, skipTests = fal
139
157
  }
140
158
 
141
159
  if (!skipTests) {
142
- console.log(`🔨 [COMPILE] Running: dotnet test`);
160
+ console.log(`[BUILD] [COMPILE] Running: dotnet test`);
143
161
  result = await run("dotnet", ["test"], cwd);
144
162
  } else {
145
163
  result = buildResult;
146
164
  }
147
165
  }
148
166
  else {
149
- console.error(`❌ [COMPILE] Unsupported: ${language}/${buildTool}`);
167
+ console.error(`[ERR] [COMPILE] Unsupported: ${language}/${buildTool}`);
150
168
  return {
151
169
  code: 1,
152
170
  stdout: "",
@@ -157,18 +175,18 @@ export async function compileAndTest({ language, buildTool, cwd, skipTests = fal
157
175
 
158
176
  // Log result
159
177
  if (result.code === 0) {
160
- console.log(`✅ [COMPILE] Success in ${result.duration}ms`);
178
+ console.log(`[OK] [COMPILE] Success in ${result.duration}ms`);
161
179
  } else {
162
- console.error(`❌ [COMPILE] Failed with code ${result.code}`);
180
+ console.error(`[ERR] [COMPILE] Failed with code ${result.code}`);
163
181
  if (result.stderr) {
164
- console.error(`❌ [COMPILE] Error: ${result.stderr.substring(0, 500)}`);
182
+ console.error(`[ERR] [COMPILE] Error: ${result.stderr.substring(0, 500)}`);
165
183
  }
166
184
  }
167
185
 
168
186
  return result;
169
187
 
170
188
  } catch (err) {
171
- console.error(`❌ [COMPILE] Exception: ${err.message}`);
189
+ console.error(`[ERR] [COMPILE] Exception: ${err.message}`);
172
190
  return {
173
191
  code: 1,
174
192
  stdout: "",
package/src/server.js CHANGED
@@ -1795,28 +1795,48 @@ app.post("/workspace/test-local/compile", async (req, res) => {
1795
1795
  skipTests: true
1796
1796
  });
1797
1797
 
1798
- if (compileResult.code !== 0) {
1798
+ if (!compileResult.success) {
1799
1799
  TEST_LOCAL_STATE.status = "error";
1800
+
1801
+ const errorOutput = (compileResult.steps || [])
1802
+ .map(s => s.stderr || '')
1803
+ .filter(Boolean)
1804
+ .join('\n')
1805
+ .trim();
1806
+
1807
+ const stdoutOutput = (compileResult.steps || [])
1808
+ .map(s => s.stdout || '')
1809
+ .filter(Boolean)
1810
+ .join('\n')
1811
+ .trim();
1812
+
1813
+ const totalDuration = (compileResult.steps || [])
1814
+ .reduce((sum, s) => sum + (s.duration || 0), 0);
1815
+
1800
1816
  TEST_LOCAL_STATE.compilationResult = {
1801
1817
  success: false,
1802
- error: compileResult.stderr,
1803
- duration: compileResult.duration
1818
+ error: errorOutput || 'Compilation failed',
1819
+ duration: totalDuration
1804
1820
  };
1805
1821
 
1806
1822
  return res.json({
1807
1823
  ok: false,
1808
- error: compileResult.stderr,
1809
- stdout: compileResult.stdout,
1810
- duration: compileResult.duration
1824
+ error: errorOutput || 'Compilation failed',
1825
+ stdout: stdoutOutput,
1826
+ steps: compileResult.steps,
1827
+ duration: totalDuration
1811
1828
  });
1812
1829
  }
1813
1830
 
1831
+ const totalDuration = (compileResult.steps || [])
1832
+ .reduce((sum, s) => sum + (s.duration || 0), 0);
1833
+
1814
1834
  TEST_LOCAL_STATE.status = "compiled";
1815
1835
  TEST_LOCAL_STATE.compilationResult = {
1816
1836
  success: true,
1817
1837
  language: meta.language,
1818
1838
  buildTool: meta.buildTool,
1819
- duration: compileResult.duration
1839
+ duration: totalDuration
1820
1840
  };
1821
1841
 
1822
1842
  console.log("[TEST-LOCAL] Compilation successful");
@@ -1825,8 +1845,8 @@ app.post("/workspace/test-local/compile", async (req, res) => {
1825
1845
  ok: true,
1826
1846
  language: meta.language,
1827
1847
  buildTool: meta.buildTool,
1828
- duration: compileResult.duration,
1829
- stdout: compileResult.stdout
1848
+ duration: totalDuration,
1849
+ stdout: (compileResult.steps || []).map(s => s.stdout || '').filter(Boolean).join('\n').trim()
1830
1850
  });
1831
1851
  } catch (err) {
1832
1852
  console.error("[TEST-LOCAL] Compilation failed:", err.message);
@@ -5375,13 +5395,13 @@ app.post("/workspace/:workspaceId/run", async (req, res) => {
5375
5395
  // Connects Local Agent to Gateway no public URL needed
5376
5396
  // ============================================
5377
5397
  async function startWebSocketTunnel(port, gatewayUrl, apiKey, tenantId) {
5378
- if (!gatewayUrl || !apiKey || !tenantId) {
5379
- console.log('WebSocket tunnel skipped: missing gatewayUrl, apiKey or tenantId in ~/.deepdebug/config.json');
5398
+ if (!gatewayUrl || !tenantId) {
5399
+ console.log('WebSocket tunnel skipped: missing gatewayUrl or tenantId');
5380
5400
  return;
5381
5401
  }
5382
5402
 
5383
5403
  const wsUrl = gatewayUrl.replace(/^https:\/\//, 'wss://').replace(/^http:\/\//, 'ws://');
5384
- const fullUrl = `${wsUrl}/api/v1/agent/ws?tenantId=${encodeURIComponent(tenantId)}&apiKey=${encodeURIComponent(apiKey)}`;
5404
+ const fullUrl = `${wsUrl}/api/v1/agent/ws?tenantId=${encodeURIComponent(tenantId)}${apiKey ? '&apiKey=' + encodeURIComponent(apiKey) : ''}`;
5385
5405
 
5386
5406
  let reconnectDelay = 2000;
5387
5407
  let isShuttingDown = false;
@@ -5792,6 +5812,125 @@ app.get("/setup/auth-status", async (req, res) => {
5792
5812
  }
5793
5813
  });
5794
5814
 
5815
+ // ============================================
5816
+ // SPRINT 1 S1-T2: POST /workspace/test-local/run-single
5817
+ // Run a single test class (faster than full suite)
5818
+ // ============================================
5819
+
5820
+ /**
5821
+ * POST /workspace/test-local/run-single
5822
+ *
5823
+ * Runs a single test class by name much faster than running the full suite.
5824
+ * Used by the agentic loop (tool: run_single_test) to validate a specific fix
5825
+ * without waiting for all tests to complete.
5826
+ *
5827
+ * Body:
5828
+ * { className: "BookingServiceTest" }
5829
+ * OR { testFile: "src/test/java/com/example/BookingServiceTest.java" }
5830
+ *
5831
+ * Response:
5832
+ * { ok, className, passed, failed, skipped, total, duration, output, failures[], exitCode }
5833
+ */
5834
+ app.post("/workspace/test-local/run-single", async (req, res) => {
5835
+ const wsRoot = resolveWorkspaceRoot(req);
5836
+ if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
5837
+
5838
+ const { className, testFile } = req.body || {};
5839
+
5840
+ // Resolve class name from either param
5841
+ let resolvedClass = className;
5842
+ if (!resolvedClass && testFile) {
5843
+ resolvedClass = path.basename(testFile, '.java');
5844
+ }
5845
+
5846
+ if (!resolvedClass || !resolvedClass.trim()) {
5847
+ return res.status(400).json({
5848
+ ok: false,
5849
+ error: "className or testFile is required. Example: { className: 'BookingServiceTest' }"
5850
+ });
5851
+ }
5852
+
5853
+ resolvedClass = resolvedClass.trim();
5854
+ console.log(`[RUN-SINGLE] Running test class: ${resolvedClass} in ${wsRoot}`);
5855
+
5856
+ const startTime = Date.now();
5857
+
5858
+ try {
5859
+ const meta = await detectProject(wsRoot);
5860
+
5861
+ let result;
5862
+
5863
+ if (meta.buildTool === 'maven') {
5864
+ // mvn test -Dtest=ClassName -q (sem clean para ser mais rapido)
5865
+ result = await run('mvn', ['test', `-Dtest=${resolvedClass}`, '-q'], wsRoot);
5866
+ } else if (meta.buildTool === 'gradle') {
5867
+ const gradleCmd = fs.existsSync(path.join(wsRoot, 'gradlew')) ? './gradlew' : 'gradle';
5868
+ result = await run(gradleCmd, ['test', '--tests', `*.${resolvedClass}`], wsRoot);
5869
+ } else if (meta.language === 'node') {
5870
+ result = await run('npx', ['jest', resolvedClass, '--no-coverage'], wsRoot);
5871
+ } else {
5872
+ return res.status(400).json({
5873
+ ok: false,
5874
+ error: `Single test not supported for buildTool: ${meta.buildTool}. Use /workspace/test-local/compile instead.`
5875
+ });
5876
+ }
5877
+
5878
+ const duration = Date.now() - startTime;
5879
+ const output = (result.stdout || '') + (result.stderr || '');
5880
+
5881
+ // Parse Maven Surefire / JUnit output
5882
+ const testsRunMatch = output.match(/Tests run: (\d+)/);
5883
+ const failuresMatch = output.match(/Failures: (\d+)/);
5884
+ const errorsMatch = output.match(/Errors: (\d+)/);
5885
+ const skippedMatch = output.match(/Skipped: (\d+)/);
5886
+
5887
+ const total = testsRunMatch ? parseInt(testsRunMatch[1]) : 0;
5888
+ const failures = failuresMatch ? parseInt(failuresMatch[1]) : 0;
5889
+ const errors = errorsMatch ? parseInt(errorsMatch[1]) : 0;
5890
+ const skipped = skippedMatch ? parseInt(skippedMatch[1]) : 0;
5891
+ const passed = total - failures - errors - skipped;
5892
+ const success = result.code === 0 && failures === 0 && errors === 0;
5893
+
5894
+ // Extract failure names from output
5895
+ const failureDetails = [];
5896
+ const failureRegex = /FAILED\s+([^\n]+)/g;
5897
+ let match;
5898
+ while ((match = failureRegex.exec(output)) !== null) {
5899
+ failureDetails.push(match[1].trim());
5900
+ }
5901
+
5902
+ console.log(`[RUN-SINGLE] ${resolvedClass}: ${success ? 'PASSED' : 'FAILED'} ` +
5903
+ `(${duration}ms, ${passed}/${total})`);
5904
+
5905
+ res.json({
5906
+ ok: success,
5907
+ className: resolvedClass,
5908
+ passed,
5909
+ failed: failures + errors,
5910
+ skipped,
5911
+ total,
5912
+ duration,
5913
+ output: output.length > 8000 ? output.substring(0, 8000) + '\n...[truncated]' : output,
5914
+ failures: failureDetails,
5915
+ exitCode: result.code
5916
+ });
5917
+
5918
+ } catch (err) {
5919
+ const duration = Date.now() - startTime;
5920
+ console.error(`[RUN-SINGLE] Failed for ${resolvedClass}:`, err.message);
5921
+ res.status(500).json({
5922
+ ok: false,
5923
+ className: resolvedClass,
5924
+ error: err.message,
5925
+ duration,
5926
+ passed: 0,
5927
+ failed: 1,
5928
+ skipped: 0,
5929
+ total: 0
5930
+ });
5931
+ }
5932
+ });
5933
+
5795
5934
  // ============================================
5796
5935
  // START SERVER
5797
5936