deepdebug-local-agent 1.0.22 → 1.1.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.
Files changed (188) hide show
  1. package/.claude/context.md +76 -0
  2. package/.claude/skills/README.md +25 -0
  3. package/.claude/skills/tdd-debugging/SKILL.md +53 -0
  4. package/.claude/skills/tdd-debugging/references/anti-patterns.md +43 -0
  5. package/.claude/skills/tdd-debugging/references/command-reference.md +46 -0
  6. package/.claude/standards/README.md +51 -0
  7. package/.claude/standards/clean-code.md +75 -0
  8. package/.claude/standards/code-review-checklist.md +61 -0
  9. package/.claude/standards/layered-architecture.md +163 -0
  10. package/.claude/standards/node.md +45 -0
  11. package/.claude/standards/security-owasp.md +114 -0
  12. package/.claude/standards/simplicity.md +42 -0
  13. package/.claude/standards/solid.md +64 -0
  14. package/.claude/standards/testing.md +59 -0
  15. package/.cursor/rules/standards.mdc +17 -0
  16. package/.github/workflows/build-byoc-image.yml +315 -0
  17. package/.github/workflows/deploy-gcp.yml +102 -0
  18. package/.github/workflows/npm-publish.yml +69 -8
  19. package/AGENTS.md +21 -0
  20. package/CHANGELOG.md +69 -0
  21. package/CLAUDE.md +13 -0
  22. package/CLAUDE_CODE_AGENT_INFINITE.md +5 -3
  23. package/Dockerfile +4 -0
  24. package/Dockerfile.full +55 -3
  25. package/README.md +81 -0
  26. package/bin/install.js +16 -5
  27. package/cloudbuild-full.yaml +57 -0
  28. package/docker-compose.yml +5 -3
  29. package/docs/OBSERVABILITY.md +164 -0
  30. package/docs/byoc-agent-image.md +70 -0
  31. package/docs/graphify-packaging-plan.md +133 -0
  32. package/docs/graphify-validation-run-2026-10-28.md +243 -0
  33. package/docs/legacy-connectors.md +268 -0
  34. package/docs/releasing.md +43 -0
  35. package/docs/signed-commands.md +163 -0
  36. package/env.example +30 -0
  37. package/helm/Chart.yaml +2 -2
  38. package/helm/templates/deployment.yaml +13 -3
  39. package/helm/templates/networkpolicy.yaml +22 -0
  40. package/helm/templates/secret.yaml +1 -0
  41. package/helm/values.yaml +13 -14
  42. package/mcp-server.js +4 -1
  43. package/package.json +12 -4
  44. package/scripts/sync-fixtures.mjs +246 -0
  45. package/scripts/upgrade-observability-push.sh +227 -0
  46. package/src/adapters/as400/as400-parser.js +12 -0
  47. package/src/adapters/as400/index.js +134 -0
  48. package/src/adapters/cobol/cobol-parser.js +37 -0
  49. package/src/adapters/cobol/index.js +151 -0
  50. package/src/adapters/ibmmq/ibmmq-parser.js +23 -0
  51. package/src/adapters/ibmmq/index.js +137 -0
  52. package/src/adapters/index.js +79 -0
  53. package/src/adapters/oracle-forms/index.js +145 -0
  54. package/src/adapters/oracle-forms/oracle-forms-parser.js +11 -0
  55. package/src/adapters/sap/index.js +146 -0
  56. package/src/adapters/sap/sap-parser.js +41 -0
  57. package/src/ai-engine.js +22 -22
  58. package/src/config/trusted-signing-keys.json +3 -0
  59. package/src/detectors/build-tool-detector.js +50 -0
  60. package/src/detectors/multi-project-detector.js +22 -22
  61. package/src/detectors/service-detector.js +24 -24
  62. package/src/exec-utils.js +26 -4
  63. package/src/git/base-git-provider.js +117 -117
  64. package/src/git/bitbucket-provider.js +2 -2
  65. package/src/git/git-provider-registry.js +18 -18
  66. package/src/git/github-provider.js +13 -13
  67. package/src/git/multi-repo-pr.js +161 -0
  68. package/src/mcp-http-server.js +32 -16
  69. package/src/observability/bundle-files.js +418 -0
  70. package/src/observability/detect.js +151 -0
  71. package/src/observability/provision.js +212 -0
  72. package/src/observability/render-cli.js +59 -0
  73. package/src/observability/routes.js +88 -0
  74. package/src/patch/patch-engine.js +28 -28
  75. package/src/patch-manager.js +85 -85
  76. package/src/patch_bkp.js +14 -14
  77. package/src/ports.js +4 -4
  78. package/src/routes/workspace.route.js +47 -16
  79. package/src/runtimes/base-runtime.js +75 -75
  80. package/src/runtimes/java/gradle-runtime.js +17 -17
  81. package/src/runtimes/java/java-integrations.js +25 -25
  82. package/src/runtimes/java/maven-runtime.js +34 -34
  83. package/src/runtimes/node/node-integrations.js +1 -1
  84. package/src/runtimes/node/npm-runtime.js +14 -14
  85. package/src/runtimes/node/yarn-runtime.js +4 -4
  86. package/src/runtimes/runtime-registry.js +25 -25
  87. package/src/server-local.js +4 -4
  88. package/src/server.js +1412 -335
  89. package/src/siblings/burst-suppressor.js +89 -0
  90. package/src/siblings/capabilities.js +273 -0
  91. package/src/siblings/env-compat.cjs +58 -0
  92. package/src/siblings/incident-forwarder.js +320 -0
  93. package/src/siblings/logger.js +15 -0
  94. package/src/siblings/metrics.js +94 -0
  95. package/src/utils/agent-auth.js +184 -0
  96. package/src/utils/approval-token.js +56 -0
  97. package/src/utils/bind-host.js +29 -0
  98. package/src/utils/branch-refspec.js +98 -0
  99. package/src/utils/child-env.js +34 -0
  100. package/src/utils/environment-diagnostics.js +55 -55
  101. package/src/utils/exec-policy.js +962 -0
  102. package/src/utils/exec-utils.js +15 -15
  103. package/src/utils/file-count.js +46 -0
  104. package/src/utils/fs-utils.js +10 -10
  105. package/src/utils/multi-repo-open.js +132 -0
  106. package/src/utils/open-gate.js +183 -0
  107. package/src/utils/ranged-read.js +65 -0
  108. package/src/utils/request-guards.js +77 -0
  109. package/src/utils/request-signing.js +342 -0
  110. package/src/utils/root-recovery.js +244 -0
  111. package/src/utils/safe-exec-args.js +58 -0
  112. package/src/utils/sandbox.js +182 -0
  113. package/src/utils/search-outcome.js +56 -0
  114. package/src/utils/workspace-root.js +80 -0
  115. package/src/validators/syntax-validator.js +149 -0
  116. package/src/workspace/detect-port.js +16 -16
  117. package/src/workspace/file-reader.js +1 -1
  118. package/src/workspace/process-manager.js +46 -46
  119. package/src/workspace-manager.js +86 -24
  120. package/test/adapters/as400/as400.test.js +121 -0
  121. package/test/adapters/cobol/cobol.test.js +217 -0
  122. package/test/adapters/ibmmq/ibmmq.test.js +122 -0
  123. package/test/adapters/oracle-forms/oracle-forms.test.js +180 -0
  124. package/test/adapters/registry.test.js +108 -0
  125. package/test/adapters/sap/sap.test.js +142 -0
  126. package/test/agent-auth.test.js +194 -0
  127. package/test/bind-host.test.js +26 -0
  128. package/test/branch-refspec.test.js +224 -0
  129. package/test/burst-suppressor.test.js +60 -0
  130. package/test/capabilities-access.test.js +47 -0
  131. package/test/capabilities.test.js +87 -0
  132. package/test/child-env.test.js +23 -0
  133. package/test/env-compat.test.js +71 -0
  134. package/test/exec-policy.test.js +463 -0
  135. package/test/file-count.test.js +64 -0
  136. package/test/incident-forwarder.test.js +231 -0
  137. package/test/multi-repo-open.test.js +180 -0
  138. package/test/multi-repo-pr.test.js +129 -0
  139. package/test/observability-detect.test.js +199 -0
  140. package/test/open-gate.test.js +341 -0
  141. package/test/ranged-read.test.js +60 -0
  142. package/test/request-guards.test.js +91 -0
  143. package/test/request-signing.test.js +284 -0
  144. package/test/root-recovery-sidecar.test.js +171 -0
  145. package/test/root-recovery.test.js +274 -0
  146. package/test/safe-exec-args.test.js +58 -0
  147. package/test/sandbox.test.js +98 -0
  148. package/test/search-outcome.test.js +50 -0
  149. package/test/workspace-root.test.js +64 -0
  150. package/tools/graphify-validation/README.md +92 -0
  151. package/tools/graphify-validation/config/questions.json +31 -0
  152. package/tools/graphify-validation/config/repos.json +9 -0
  153. package/tools/graphify-validation/config/symbols.json +6 -0
  154. package/tools/graphify-validation/fixtures/spring/expected.json +34 -0
  155. package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/AppConfig.java +29 -0
  156. package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/AuditLogger.java +15 -0
  157. package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/Cache.java +7 -0
  158. package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/CheckoutService.java +25 -0
  159. package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/InMemoryCache.java +18 -0
  160. package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/OrderController.java +22 -0
  161. package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/PaymentGateway.java +5 -0
  162. package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/PaypalGateway.java +11 -0
  163. package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/RefundService.java +14 -0
  164. package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/StripeGateway.java +11 -0
  165. package/tools/graphify-validation/lib/affected_vs_rg.py +81 -0
  166. package/tools/graphify-validation/lib/audit_hook/sitecustomize.py +22 -0
  167. package/tools/graphify-validation/lib/graph_metrics.py +96 -0
  168. package/tools/graphify-validation/lib/gv_common.py +130 -0
  169. package/tools/graphify-validation/lib/licenses.py +47 -0
  170. package/tools/graphify-validation/lib/pip_audit_summary.py +18 -0
  171. package/tools/graphify-validation/lib/recall.py +115 -0
  172. package/tools/graphify-validation/lib/report.py +226 -0
  173. package/tools/graphify-validation/lib/run_all.py +237 -0
  174. package/tools/graphify-validation/lib/run_graphify.py +144 -0
  175. package/tools/graphify-validation/lib/rusage_helper.py +17 -0
  176. package/tools/graphify-validation/lib/sample_edges.py +246 -0
  177. package/tools/graphify-validation/lib/spring_fixture.py +81 -0
  178. package/tools/graphify-validation/requirements-py311.lock +508 -0
  179. package/tools/graphify-validation/requirements-tools.in +1 -0
  180. package/tools/graphify-validation/requirements-tools.lock +371 -0
  181. package/tools/graphify-validation/requirements.in +1 -0
  182. package/tools/graphify-validation/requirements.lock +502 -0
  183. package/tools/graphify-validation/run.sh +43 -0
  184. package/tools/graphify-validation/tests/test_harness.py +151 -0
  185. package/cloudbuild-agent-qa.yaml +0 -43
  186. package/cloudbuild-agent-staging.yaml +0 -40
  187. package/cloudbuild.yaml.deprecated +0 -42
  188. package/src/local-agent-source.tar.gz +0 -0
@@ -0,0 +1,146 @@
1
+ // src/adapters/sap/index.js
2
+ // SapLogAdapter — watches dev_w* and SLOG* files; emits 6-key error events.
3
+ // AD-1 lifecycle, AD-3 polling, AD-4 byte-position dedup, AD-5 per-file ordering.
4
+
5
+ import { EventEmitter } from 'node:events';
6
+ import { stat, open } from 'node:fs/promises';
7
+ import { join } from 'node:path';
8
+ import chokidar from 'chokidar';
9
+ import { SAP_DEV_W_REGEX, SAP_SLOG_REGEX, normalizeDevWMatch, normalizeSlogMatch } from './sap-parser.js';
10
+
11
+ const ADAPTER = 'sap';
12
+
13
+ export class SapLogAdapter extends EventEmitter {
14
+ constructor(config = {}) {
15
+ super();
16
+ this.sapDir = config.sapDir || '/usr/sap/PRD';
17
+ this.instanceNumber = config.instanceNumber || '00';
18
+ this.interval = config.interval ?? 5000;
19
+ this.replayOnStart = config.replayOnStart === true;
20
+ this._watchers = [];
21
+ this._offsets = new Map(); // filePath → byte offset
22
+ this._inodes = new Map(); // filePath → inode
23
+ this._isRunning = false;
24
+ }
25
+
26
+ get isRunning() { return this._isRunning; }
27
+
28
+ async start() {
29
+ if (this._isRunning) return;
30
+ this._isRunning = true;
31
+
32
+ const workGlob = join(this.sapDir, `D${this.instanceNumber}`, 'work', 'dev_w*');
33
+ const logGlob = join(this.sapDir, `D${this.instanceNumber}`, 'log', 'SLOG*');
34
+
35
+ const watcherOpts = {
36
+ usePolling: true,
37
+ interval: this.interval,
38
+ ignoreInitial: false, // we handle initial ourselves via TAIL seek
39
+ awaitWriteFinish: false,
40
+ };
41
+
42
+ const wWork = chokidar.watch(workGlob, watcherOpts);
43
+ const wSlog = chokidar.watch(logGlob, watcherOpts);
44
+
45
+ const handle = (filePath, kind) => {
46
+ // ENOENT-tolerant: stat() inside _readNew handles disappearance.
47
+ this._readNew(filePath, kind).catch(() => { /* swallow per ENOENT silence rule */ });
48
+ };
49
+
50
+ // 'add' fires for files present at watch start; honor TAIL/replay flag.
51
+ const onAdd = (kind) => async (filePath) => {
52
+ if (this.replayOnStart) {
53
+ this._offsets.set(filePath, 0);
54
+ } else {
55
+ try {
56
+ const st = await stat(filePath);
57
+ this._offsets.set(filePath, st.size);
58
+ this._inodes.set(filePath, st.ino);
59
+ return; // TAIL mode — don't emit pre-existing content
60
+ } catch { return; }
61
+ }
62
+ handle(filePath, kind);
63
+ };
64
+
65
+ wWork.on('add', onAdd('dev_w'));
66
+ wWork.on('change', (fp) => handle(fp, 'dev_w'));
67
+ wSlog.on('add', onAdd('slog'));
68
+ wSlog.on('change', (fp) => handle(fp, 'slog'));
69
+
70
+ // chokidar's 'error' events would otherwise bubble; swallow to keep ENOENT silent.
71
+ wWork.on('error', () => {});
72
+ wSlog.on('error', () => {});
73
+
74
+ this._watchers.push(wWork, wSlog);
75
+ }
76
+
77
+ async stop() {
78
+ if (!this._isRunning) return;
79
+ this._isRunning = false;
80
+ await Promise.all(this._watchers.map((w) => w.close().catch(() => {})));
81
+ this._watchers = [];
82
+ this._offsets.clear();
83
+ this._inodes.clear();
84
+ }
85
+
86
+ async _readNew(filePath, kind) {
87
+ let st;
88
+ try { st = await stat(filePath); } catch { return; } // ENOENT silence
89
+
90
+ const lastOffset = this._offsets.get(filePath) ?? 0;
91
+ const lastInode = this._inodes.get(filePath);
92
+
93
+ // Rotation: inode changed OR size shrunk → reset to 0
94
+ if ((lastInode !== undefined && st.ino !== lastInode) || st.size < lastOffset) {
95
+ this._offsets.set(filePath, 0);
96
+ this._inodes.set(filePath, st.ino);
97
+ return this._readNew(filePath, kind); // re-enter with offset=0
98
+ }
99
+ this._inodes.set(filePath, st.ino);
100
+
101
+ if (st.size <= lastOffset) return;
102
+
103
+ const fh = await open(filePath, 'r');
104
+ try {
105
+ const len = st.size - lastOffset;
106
+ const buf = Buffer.alloc(len);
107
+ await fh.read(buf, 0, len, lastOffset);
108
+ this._offsets.set(filePath, st.size);
109
+ // Tolerate CRLF and LF; split, drop trailing empty (no event for blank lines per AD-6 SAP empty-line trap).
110
+ const text = buf.toString('utf8');
111
+ const lines = text.split(/\r?\n/);
112
+ const sourceDir = filePath.split('/').slice(0, -1).join('/');
113
+ for (const line of lines) {
114
+ if (line.trim().length === 0) continue;
115
+ this._tryEmit(line, filePath, sourceDir, kind);
116
+ }
117
+ } finally {
118
+ await fh.close();
119
+ }
120
+ }
121
+
122
+ _tryEmit(line, filePath, sourceDir, kind) {
123
+ let errorCode = null;
124
+ if (kind === 'dev_w') {
125
+ const m = line.match(SAP_DEV_W_REGEX);
126
+ errorCode = normalizeDevWMatch(m);
127
+ } else if (kind === 'slog') {
128
+ const m = line.match(SAP_SLOG_REGEX);
129
+ errorCode = normalizeSlogMatch(m);
130
+ }
131
+ if (errorCode === null) return; // no recognizable code → no event
132
+
133
+ // 6-key frozen event payload (AD-4 contract — extra keys forbidden)
134
+ const event = Object.freeze({
135
+ adapter: ADAPTER,
136
+ errorCode,
137
+ file: filePath,
138
+ message: line,
139
+ source: sourceDir,
140
+ timestamp: new Date().toISOString(),
141
+ });
142
+ this.emit('error', event);
143
+ }
144
+ }
145
+
146
+ export default SapLogAdapter;
@@ -0,0 +1,41 @@
1
+ // src/adapters/sap/sap-parser.js
2
+ // T-2-03 mitigation: regexes exported as named constants for isolated testing.
3
+ // Adapter binds chokidar to dev_w* and SLOG* file globs only; these regexes
4
+ // never run against arbitrary text by the adapter itself.
5
+
6
+ // Matches ABAP work-process trace error lines:
7
+ // "M *** ERROR => Runtime error CONVT_NO_NUMBER"
8
+ // "M *** ERROR => Exception condition CX_SY_OPEN_SQL_DB raised"
9
+ // "B *** ERROR => SQL error in SELECT, sy-subrc = 4"
10
+ export const SAP_DEV_W_REGEX = /\*\*\* ERROR =>\s+(?:Runtime error|Exception condition|SQL error in [A-Z]+,)\s*([A-Z_]+(?:[A-Z_0-9]*)|sy-subrc\s*=\s*\d+)/i;
11
+
12
+ // Matches SLOG pipe-delimited error/fatal records:
13
+ // "| 2024-01-15 09:12:44 | E | 04 | Database error..., sy-subrc = 4 |"
14
+ // "| 2024-01-15 14:01:55 | F | 22 | Short dump 4815162343 created |"
15
+ export const SAP_SLOG_REGEX = /^\|\s*\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}\s*\|\s*([EF])\s*\|\s*\d+\s*\|\s*(.+?)\s*\|/;
16
+
17
+ // Normalizes captured payload to errorCode string (or null when no recognizable code).
18
+ export function normalizeDevWMatch(match) {
19
+ if (!match) return null;
20
+ const raw = (match[1] || '').trim();
21
+ if (/^sy-subrc/i.test(raw)) {
22
+ const m = raw.match(/=\s*(\d+)/);
23
+ return m ? `SY-SUBRC=${m[1]}` : 'SY-SUBRC';
24
+ }
25
+ return raw.toUpperCase();
26
+ }
27
+
28
+ export function normalizeSlogMatch(match) {
29
+ if (!match) return null;
30
+ const body = (match[2] || '').trim();
31
+ // Try to extract a known runtime-error keyword from the SLOG message body
32
+ const m = body.match(/(CONVT_NO_NUMBER|CX_SY_OPEN_SQL_DB|sy-subrc\s*=\s*\d+|Short dump \d+)/i);
33
+ if (!m) return null;
34
+ const cap = m[1].trim();
35
+ if (/^sy-subrc/i.test(cap)) {
36
+ const num = cap.match(/=\s*(\d+)/);
37
+ return num ? `SY-SUBRC=${num[1]}` : 'SY-SUBRC';
38
+ }
39
+ if (/^Short dump/i.test(cap)) return 'SHORT_DUMP';
40
+ return cap.toUpperCase();
41
+ }
package/src/ai-engine.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // ============================================
2
2
  // 🧠 AI VIBE CODING ENGINE
3
- // Ficheiro: ai-engine.js
3
+ // File: ai-engine.js
4
4
  // Coloca em: ~/deepdebug-local-agent/src/ai-engine.js
5
5
  // ============================================
6
6
 
@@ -15,15 +15,15 @@ const execAsync = promisify(exec);
15
15
  /**
16
16
  * AIVibeCodingEngine
17
17
  *
18
- * Sistema universal de auto-healing que usa AI para resolver
19
- * QUALQUER erro automaticamente - não apenas startup.
18
+ * Universal auto-healing system that uses AI to resolve
19
+ * ANY error automatically - not just startup.
20
20
  *
21
21
  * Features:
22
- * - Monitorização de erros em tempo real
23
- * - Classificação automática de erros
22
+ * - Real-time error monitoring
23
+ * - Automatic error classification
24
24
  * - Quick fixes locais (sem chamar AI)
25
- * - Escala para AI quando quick fixes falham
26
- * - Histórico de fixes para reutilização
25
+ * - Escalates to AI when quick fixes fail
26
+ * - Fix history for reuse
27
27
  */
28
28
  export class AIVibeCodingEngine extends EventEmitter {
29
29
  constructor(processManager, getWorkspaceRoot) {
@@ -34,12 +34,12 @@ export class AIVibeCodingEngine extends EventEmitter {
34
34
  this.maxRetries = 3;
35
35
  this.isActive = true;
36
36
 
37
- // Histórico
37
+ // History
38
38
  this.errorHistory = [];
39
39
  this.fixHistory = [];
40
40
  this.pendingFixes = [];
41
41
 
42
- // Estado
42
+ // State
43
43
  this.currentSession = null;
44
44
  this.lastSuccessfulConfig = null;
45
45
 
@@ -54,14 +54,14 @@ export class AIVibeCodingEngine extends EventEmitter {
54
54
  // ============================================
55
55
 
56
56
  setupErrorMonitoring() {
57
- // Monitorar logs do ProcessManager
57
+ // Monitor ProcessManager logs
58
58
  this.processManager.on('log', async ({ serviceId, message, type }) => {
59
59
  if (this.isActive && message && this.isError(message)) {
60
60
  await this.handleRuntimeError(serviceId, message, type);
61
61
  }
62
62
  });
63
63
 
64
- // Monitorar crashes
64
+ // Monitor crashes
65
65
  this.processManager.on('stopped', async ({ serviceId, code, signal }) => {
66
66
  if (this.isActive && code !== 0 && code !== null) {
67
67
  console.log(`🧠 [AI-Engine] Process ${serviceId} crashed (code: ${code})`);
@@ -69,7 +69,7 @@ export class AIVibeCodingEngine extends EventEmitter {
69
69
  }
70
70
  });
71
71
 
72
- // Monitorar erros de processo
72
+ // Monitor process errors
73
73
  this.processManager.on('error', async ({ serviceId, error }) => {
74
74
  if (this.isActive) {
75
75
  console.log(`🧠 [AI-Engine] Process error: ${error}`);
@@ -212,7 +212,7 @@ export class AIVibeCodingEngine extends EventEmitter {
212
212
  async handleRuntimeError(serviceId, errorMessage, type) {
213
213
  const classification = this.classifyError(errorMessage);
214
214
 
215
- // Debounce: evitar duplicatas em 5 segundos
215
+ // Debounce: avoid duplicates within 5 seconds
216
216
  const recentSimilar = this.errorHistory.find(e =>
217
217
  e.classification.type === classification.type &&
218
218
  Date.now() - e.timestamp < 5000
@@ -235,10 +235,10 @@ export class AIVibeCodingEngine extends EventEmitter {
235
235
  console.log(`🧠 [AI-Engine] Error detected: ${classification.type} (${classification.severity})`);
236
236
  console.log(` Message: ${errorMessage.substring(0, 150)}...`);
237
237
 
238
- // Emitir evento
238
+ // Emit the event
239
239
  this.emit('error_detected', errorEntry);
240
240
 
241
- // Se auto-fixável, preparar fix
241
+ // If auto-fixable, prepare a fix
242
242
  if (classification.autoFixable) {
243
243
  const quickFix = this.getQuickFix(classification.type, errorMessage);
244
244
  if (quickFix) {
@@ -357,14 +357,14 @@ export class AIVibeCodingEngine extends EventEmitter {
357
357
  attempts++;
358
358
  console.log(`\n🔄 [AI-Engine] Attempt ${attempts}/${this.maxRetries}`);
359
359
 
360
- // Aplicar fixes pendentes
360
+ // Apply pending fixes
361
361
  if (attempts > 1 && this.pendingFixes.length > 0) {
362
362
  const fix = this.pendingFixes.shift();
363
363
  console.log(`💡 [AI-Engine] Applying fix: ${fix.fix.description}`);
364
364
  currentConfig = await this.applyFix(currentConfig, fix.fix);
365
365
  }
366
366
 
367
- // Recompilar se necessário
367
+ // Recompile if needed
368
368
  if (currentConfig.recompile) {
369
369
  console.log('🔨 [AI-Engine] Recompiling...');
370
370
  const compileResult = await this.recompile();
@@ -379,7 +379,7 @@ export class AIVibeCodingEngine extends EventEmitter {
379
379
  delete currentConfig.recompile;
380
380
  }
381
381
 
382
- // Tentar iniciar
382
+ // Try to start
383
383
  const result = await this.attemptStart(currentConfig);
384
384
 
385
385
  this.currentSession.attempts.push({
@@ -419,14 +419,14 @@ export class AIVibeCodingEngine extends EventEmitter {
419
419
  lastError = result.error;
420
420
  console.log(`❌ [AI-Engine] Attempt ${attempts} failed: ${lastError?.substring(0, 100)}...`);
421
421
 
422
- // Obter fix
422
+ // Get a fix
423
423
  const fix = await this.getFix(result.error, result.logs, currentConfig);
424
424
 
425
425
  if (fix) {
426
426
  console.log(`💡 [AI-Engine] Fix: ${fix.description}`);
427
427
  currentConfig = await this.applyFix(currentConfig, fix);
428
428
  } else {
429
- // Tentar AI
429
+ // Try AI
430
430
  console.log(`🤖 [AI-Engine] Requesting AI analysis...`);
431
431
  const aiAnalysis = await this.analyzeWithAI('startup', lastError, currentConfig);
432
432
  if (aiAnalysis?.newConfig) {
@@ -536,7 +536,7 @@ export class AIVibeCodingEngine extends EventEmitter {
536
536
  return quickFix;
537
537
  }
538
538
 
539
- // Verificar histórico
539
+ // Check the history
540
540
  const similarFix = this.findSimilarFix(classification.type);
541
541
  if (similarFix) {
542
542
  console.log(`📚 [AI-Engine] Found similar fix in history`);
@@ -707,7 +707,7 @@ export class AIVibeCodingEngine extends EventEmitter {
707
707
  }
708
708
 
709
709
  try {
710
- // Importar detectProject dinamicamente para evitar dependência circular
710
+ // Import detectProject dynamically to avoid a circular dependency
711
711
  const { detectProject } = await import('./detectors.js');
712
712
  const meta = await detectProject(workspaceRoot);
713
713
 
@@ -0,0 +1,3 @@
1
+ [
2
+ { "keyId": "vh-2026-09", "publicKey": "MCowBQYDK2VwAyEAp1QFJcXW1Xgz+B0Qppsgqc87rZmwrggKYC3dh4n7aBg=" }
3
+ ]
@@ -0,0 +1,50 @@
1
+ /**
2
+ * build-tool-detector.js — PASSO 0 of the fix validation ladder.
3
+ *
4
+ * Thin facade over the battle-tested detectProject() (src/detectors.js) that
5
+ * normalizes its output into the {stack, tier} shape the auto-fix engine
6
+ * consumes. One source of truth: marker priority, multi-language and the
7
+ * "Swift wins" product rule all live in detectProject — this file only maps.
8
+ *
9
+ * Tiers (validation ladder):
10
+ * A = C1 structural + C2 syntax + C3 build (maven/gradle/node)
11
+ * B = C1 + C2, build unsupported on this host (xcode/swift — needs macOS)
12
+ * C = C1 only (anything unrecognized)
13
+ */
14
+ import { detectProject } from '../detectors.js';
15
+
16
+ const STACK_BY_BUILD_TOOL = {
17
+ maven: 'maven',
18
+ gradle: 'gradle',
19
+ npm: 'node',
20
+ yarn: 'node',
21
+ pnpm: 'node',
22
+ xcodebuild: 'xcode',
23
+ swiftpm: 'xcode',
24
+ cocoapods: 'xcode',
25
+ go: 'go',
26
+ pip: 'python',
27
+ poetry: 'python',
28
+ bundler: 'ruby',
29
+ rake: 'ruby',
30
+ };
31
+
32
+ const TIER_BY_STACK = {
33
+ maven: 'A',
34
+ gradle: 'A',
35
+ node: 'A',
36
+ xcode: 'B',
37
+ };
38
+
39
+ export async function detectBuildTool(workspacePath) {
40
+ const meta = await detectProject(workspacePath);
41
+ const buildTool = meta?.buildTool || 'unknown';
42
+ const stack = STACK_BY_BUILD_TOOL[buildTool] || 'unknown';
43
+ return {
44
+ stack,
45
+ tier: TIER_BY_STACK[stack] || 'C',
46
+ language: meta?.language || 'unknown',
47
+ buildTool,
48
+ marker: meta?.marker || null,
49
+ };
50
+ }
@@ -9,24 +9,24 @@ import { FileReader } from "../workspace/file-reader.js";
9
9
  /**
10
10
  * MultiProjectDetector
11
11
  *
12
- * Detecta múltiplos projetos dentro de um workspace
13
- * Escaneia subpastas em busca de projetos independentes (pom.xml, package.json, etc)
12
+ * Detects multiple projects within a workspace
13
+ * Scans subfolders looking for independent projects (pom.xml, package.json, etc)
14
14
  */
15
15
  export class MultiProjectDetector {
16
16
  constructor(workspaceRoot) {
17
17
  this.workspaceRoot = workspaceRoot;
18
- this.maxDepth = 2; // Escanear até 2 níveis de profundidade
18
+ this.maxDepth = 2; // Scan up to 2 levels deep
19
19
  }
20
20
 
21
21
  /**
22
- * Detecta todos os projetos no workspace
22
+ * Detects all projects in the workspace
23
23
  */
24
24
  async detectProjects() {
25
25
  console.log(`🔍 Scanning workspace for multiple projects: ${this.workspaceRoot}`);
26
26
 
27
27
  const projects = [];
28
28
 
29
- // 1️⃣ Verificar se o próprio workspace root é um projeto
29
+ // 1️⃣ Check whether the workspace root itself is a project
30
30
  if (await this.isProject(this.workspaceRoot)) {
31
31
  console.log(`✅ Root is a project`);
32
32
  projects.push({
@@ -36,13 +36,13 @@ export class MultiProjectDetector {
36
36
  });
37
37
  }
38
38
 
39
- // 2️⃣ Escanear subpastas em busca de projetos
39
+ // 2️⃣ Scan subfolders looking for projects
40
40
  const subProjects = await this.scanSubdirectories(this.workspaceRoot, 0);
41
41
  projects.push(...subProjects);
42
42
 
43
43
  console.log(`✅ Found ${projects.length} project(s) in workspace`);
44
44
 
45
- // 3️⃣ Detectar serviços para cada projeto
45
+ // 3️⃣ Detect services for each project
46
46
  const allServices = [];
47
47
  for (const project of projects) {
48
48
  console.log(`🔎 Detecting services in: ${project.name}`);
@@ -55,7 +55,7 @@ export class MultiProjectDetector {
55
55
  }
56
56
 
57
57
  /**
58
- * Verifica se um diretório é um projeto
58
+ * Checks whether a directory is a project
59
59
  */
60
60
  async isProject(dir) {
61
61
  const markers = [
@@ -67,12 +67,12 @@ export class MultiProjectDetector {
67
67
  'pyproject.toml', // Python Poetry
68
68
  'go.mod', // Go
69
69
  'Cargo.toml', // Rust
70
- '.csproj', // .NET (qualquer arquivo .csproj)
70
+ '.csproj', // .NET (any .csproj file)
71
71
  '.sln' // .NET solution
72
72
  ];
73
73
 
74
74
  for (const marker of markers) {
75
- // Para .csproj e .sln, procurar qualquer arquivo com extensão
75
+ // For .csproj and .sln, look for any file with that extension
76
76
  if (marker.startsWith('.')) {
77
77
  try {
78
78
  const files = await readdir(dir);
@@ -83,7 +83,7 @@ export class MultiProjectDetector {
83
83
  continue;
84
84
  }
85
85
  } else {
86
- // Para markers específicos, verificar se existe
86
+ // For specific markers, check whether they exist
87
87
  if (await exists(path.join(dir, marker))) {
88
88
  return true;
89
89
  }
@@ -94,7 +94,7 @@ export class MultiProjectDetector {
94
94
  }
95
95
 
96
96
  /**
97
- * Escaneia subdiretórios recursivamente
97
+ * Scans subdirectories recursively
98
98
  */
99
99
  async scanSubdirectories(dir, depth) {
100
100
  if (depth >= this.maxDepth) {
@@ -109,7 +109,7 @@ export class MultiProjectDetector {
109
109
  for (const entry of entries) {
110
110
  if (!entry.isDirectory()) continue;
111
111
 
112
- // Ignorar pastas comuns que não são projetos
112
+ // Ignore common folders that are not projects
113
113
  const ignoreDirs = [
114
114
  'node_modules', 'target', 'build', 'dist', 'out',
115
115
  '.git', '.idea', '.vscode', '__pycache__',
@@ -123,7 +123,7 @@ export class MultiProjectDetector {
123
123
 
124
124
  const fullPath = path.join(dir, entry.name);
125
125
 
126
- // Verificar se é um projeto
126
+ // Check whether it is a project
127
127
  if (await this.isProject(fullPath)) {
128
128
  console.log(`✅ Found project: ${entry.name}`);
129
129
  projects.push({
@@ -131,11 +131,11 @@ export class MultiProjectDetector {
131
131
  path: fullPath,
132
132
  isRoot: false
133
133
  });
134
- // Não escanear dentro de projetos encontrados
134
+ // Do not scan inside projects that were found
135
135
  continue;
136
136
  }
137
137
 
138
- // Continuar escaneando recursivamente
138
+ // Keep scanning recursively
139
139
  const subProjects = await this.scanSubdirectories(fullPath, depth + 1);
140
140
  projects.push(...subProjects);
141
141
  }
@@ -147,19 +147,19 @@ export class MultiProjectDetector {
147
147
  }
148
148
 
149
149
  /**
150
- * Detecta serviços em um projeto específico
150
+ * Detects services in a specific project
151
151
  */
152
152
  async detectServicesInProject(project) {
153
153
  try {
154
- // Usar o scanner para analisar o projeto
154
+ // Use the scanner to analyze the project
155
155
  const scanner = new WorkspaceScanner(project.path);
156
156
  const structure = await scanner.scan();
157
157
 
158
- // Detectar linguagem
158
+ // Detect the language
159
159
  const languageDetector = new LanguageDetector(structure.files);
160
160
  const languageInfo = languageDetector.detect();
161
161
 
162
- // Detectar framework
162
+ // Detect the framework
163
163
  const fileReader = new FileReader(project.path);
164
164
  const frameworkDetector = new FrameworkDetector(
165
165
  languageInfo.primary,
@@ -168,7 +168,7 @@ export class MultiProjectDetector {
168
168
  );
169
169
  const frameworkInfo = await frameworkDetector.detect();
170
170
 
171
- // Detectar serviços
171
+ // Detect the services
172
172
  const serviceDetector = new ServiceDetector(
173
173
  project.path,
174
174
  languageInfo,
@@ -177,7 +177,7 @@ export class MultiProjectDetector {
177
177
 
178
178
  const services = await serviceDetector.detect();
179
179
 
180
- // Adicionar nome do workspace a cada serviço
180
+ // Add the workspace name to each service
181
181
  return services.map(service => ({
182
182
  ...service,
183
183
  workspaceName: project.name,