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.
- package/.claude/context.md +76 -0
- package/.claude/skills/README.md +25 -0
- package/.claude/skills/tdd-debugging/SKILL.md +53 -0
- package/.claude/skills/tdd-debugging/references/anti-patterns.md +43 -0
- package/.claude/skills/tdd-debugging/references/command-reference.md +46 -0
- package/.claude/standards/README.md +51 -0
- package/.claude/standards/clean-code.md +75 -0
- package/.claude/standards/code-review-checklist.md +61 -0
- package/.claude/standards/layered-architecture.md +163 -0
- package/.claude/standards/node.md +45 -0
- package/.claude/standards/security-owasp.md +114 -0
- package/.claude/standards/simplicity.md +42 -0
- package/.claude/standards/solid.md +64 -0
- package/.claude/standards/testing.md +59 -0
- package/.cursor/rules/standards.mdc +17 -0
- package/.github/workflows/build-byoc-image.yml +315 -0
- package/.github/workflows/deploy-gcp.yml +102 -0
- package/.github/workflows/npm-publish.yml +69 -8
- package/AGENTS.md +21 -0
- package/CHANGELOG.md +69 -0
- package/CLAUDE.md +13 -0
- package/CLAUDE_CODE_AGENT_INFINITE.md +5 -3
- package/Dockerfile +4 -0
- package/Dockerfile.full +55 -3
- package/README.md +81 -0
- package/bin/install.js +16 -5
- package/cloudbuild-full.yaml +57 -0
- package/docker-compose.yml +5 -3
- package/docs/OBSERVABILITY.md +164 -0
- package/docs/byoc-agent-image.md +70 -0
- package/docs/graphify-packaging-plan.md +133 -0
- package/docs/graphify-validation-run-2026-10-28.md +243 -0
- package/docs/legacy-connectors.md +268 -0
- package/docs/releasing.md +43 -0
- package/docs/signed-commands.md +163 -0
- package/env.example +30 -0
- package/helm/Chart.yaml +2 -2
- package/helm/templates/deployment.yaml +13 -3
- package/helm/templates/networkpolicy.yaml +22 -0
- package/helm/templates/secret.yaml +1 -0
- package/helm/values.yaml +13 -14
- package/mcp-server.js +4 -1
- package/package.json +12 -4
- package/scripts/sync-fixtures.mjs +246 -0
- package/scripts/upgrade-observability-push.sh +227 -0
- package/src/adapters/as400/as400-parser.js +12 -0
- package/src/adapters/as400/index.js +134 -0
- package/src/adapters/cobol/cobol-parser.js +37 -0
- package/src/adapters/cobol/index.js +151 -0
- package/src/adapters/ibmmq/ibmmq-parser.js +23 -0
- package/src/adapters/ibmmq/index.js +137 -0
- package/src/adapters/index.js +79 -0
- package/src/adapters/oracle-forms/index.js +145 -0
- package/src/adapters/oracle-forms/oracle-forms-parser.js +11 -0
- package/src/adapters/sap/index.js +146 -0
- package/src/adapters/sap/sap-parser.js +41 -0
- package/src/ai-engine.js +22 -22
- package/src/config/trusted-signing-keys.json +3 -0
- package/src/detectors/build-tool-detector.js +50 -0
- package/src/detectors/multi-project-detector.js +22 -22
- package/src/detectors/service-detector.js +24 -24
- package/src/exec-utils.js +26 -4
- package/src/git/base-git-provider.js +117 -117
- package/src/git/bitbucket-provider.js +2 -2
- package/src/git/git-provider-registry.js +18 -18
- package/src/git/github-provider.js +13 -13
- package/src/git/multi-repo-pr.js +161 -0
- package/src/mcp-http-server.js +32 -16
- package/src/observability/bundle-files.js +418 -0
- package/src/observability/detect.js +151 -0
- package/src/observability/provision.js +212 -0
- package/src/observability/render-cli.js +59 -0
- package/src/observability/routes.js +88 -0
- package/src/patch/patch-engine.js +28 -28
- package/src/patch-manager.js +85 -85
- package/src/patch_bkp.js +14 -14
- package/src/ports.js +4 -4
- package/src/routes/workspace.route.js +47 -16
- package/src/runtimes/base-runtime.js +75 -75
- package/src/runtimes/java/gradle-runtime.js +17 -17
- package/src/runtimes/java/java-integrations.js +25 -25
- package/src/runtimes/java/maven-runtime.js +34 -34
- package/src/runtimes/node/node-integrations.js +1 -1
- package/src/runtimes/node/npm-runtime.js +14 -14
- package/src/runtimes/node/yarn-runtime.js +4 -4
- package/src/runtimes/runtime-registry.js +25 -25
- package/src/server-local.js +4 -4
- package/src/server.js +1412 -335
- package/src/siblings/burst-suppressor.js +89 -0
- package/src/siblings/capabilities.js +273 -0
- package/src/siblings/env-compat.cjs +58 -0
- package/src/siblings/incident-forwarder.js +320 -0
- package/src/siblings/logger.js +15 -0
- package/src/siblings/metrics.js +94 -0
- package/src/utils/agent-auth.js +184 -0
- package/src/utils/approval-token.js +56 -0
- package/src/utils/bind-host.js +29 -0
- package/src/utils/branch-refspec.js +98 -0
- package/src/utils/child-env.js +34 -0
- package/src/utils/environment-diagnostics.js +55 -55
- package/src/utils/exec-policy.js +962 -0
- package/src/utils/exec-utils.js +15 -15
- package/src/utils/file-count.js +46 -0
- package/src/utils/fs-utils.js +10 -10
- package/src/utils/multi-repo-open.js +132 -0
- package/src/utils/open-gate.js +183 -0
- package/src/utils/ranged-read.js +65 -0
- package/src/utils/request-guards.js +77 -0
- package/src/utils/request-signing.js +342 -0
- package/src/utils/root-recovery.js +244 -0
- package/src/utils/safe-exec-args.js +58 -0
- package/src/utils/sandbox.js +182 -0
- package/src/utils/search-outcome.js +56 -0
- package/src/utils/workspace-root.js +80 -0
- package/src/validators/syntax-validator.js +149 -0
- package/src/workspace/detect-port.js +16 -16
- package/src/workspace/file-reader.js +1 -1
- package/src/workspace/process-manager.js +46 -46
- package/src/workspace-manager.js +86 -24
- package/test/adapters/as400/as400.test.js +121 -0
- package/test/adapters/cobol/cobol.test.js +217 -0
- package/test/adapters/ibmmq/ibmmq.test.js +122 -0
- package/test/adapters/oracle-forms/oracle-forms.test.js +180 -0
- package/test/adapters/registry.test.js +108 -0
- package/test/adapters/sap/sap.test.js +142 -0
- package/test/agent-auth.test.js +194 -0
- package/test/bind-host.test.js +26 -0
- package/test/branch-refspec.test.js +224 -0
- package/test/burst-suppressor.test.js +60 -0
- package/test/capabilities-access.test.js +47 -0
- package/test/capabilities.test.js +87 -0
- package/test/child-env.test.js +23 -0
- package/test/env-compat.test.js +71 -0
- package/test/exec-policy.test.js +463 -0
- package/test/file-count.test.js +64 -0
- package/test/incident-forwarder.test.js +231 -0
- package/test/multi-repo-open.test.js +180 -0
- package/test/multi-repo-pr.test.js +129 -0
- package/test/observability-detect.test.js +199 -0
- package/test/open-gate.test.js +341 -0
- package/test/ranged-read.test.js +60 -0
- package/test/request-guards.test.js +91 -0
- package/test/request-signing.test.js +284 -0
- package/test/root-recovery-sidecar.test.js +171 -0
- package/test/root-recovery.test.js +274 -0
- package/test/safe-exec-args.test.js +58 -0
- package/test/sandbox.test.js +98 -0
- package/test/search-outcome.test.js +50 -0
- package/test/workspace-root.test.js +64 -0
- package/tools/graphify-validation/README.md +92 -0
- package/tools/graphify-validation/config/questions.json +31 -0
- package/tools/graphify-validation/config/repos.json +9 -0
- package/tools/graphify-validation/config/symbols.json +6 -0
- package/tools/graphify-validation/fixtures/spring/expected.json +34 -0
- package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/AppConfig.java +29 -0
- package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/AuditLogger.java +15 -0
- package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/Cache.java +7 -0
- package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/CheckoutService.java +25 -0
- package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/InMemoryCache.java +18 -0
- package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/OrderController.java +22 -0
- package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/PaymentGateway.java +5 -0
- package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/PaypalGateway.java +11 -0
- package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/RefundService.java +14 -0
- package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/StripeGateway.java +11 -0
- package/tools/graphify-validation/lib/affected_vs_rg.py +81 -0
- package/tools/graphify-validation/lib/audit_hook/sitecustomize.py +22 -0
- package/tools/graphify-validation/lib/graph_metrics.py +96 -0
- package/tools/graphify-validation/lib/gv_common.py +130 -0
- package/tools/graphify-validation/lib/licenses.py +47 -0
- package/tools/graphify-validation/lib/pip_audit_summary.py +18 -0
- package/tools/graphify-validation/lib/recall.py +115 -0
- package/tools/graphify-validation/lib/report.py +226 -0
- package/tools/graphify-validation/lib/run_all.py +237 -0
- package/tools/graphify-validation/lib/run_graphify.py +144 -0
- package/tools/graphify-validation/lib/rusage_helper.py +17 -0
- package/tools/graphify-validation/lib/sample_edges.py +246 -0
- package/tools/graphify-validation/lib/spring_fixture.py +81 -0
- package/tools/graphify-validation/requirements-py311.lock +508 -0
- package/tools/graphify-validation/requirements-tools.in +1 -0
- package/tools/graphify-validation/requirements-tools.lock +371 -0
- package/tools/graphify-validation/requirements.in +1 -0
- package/tools/graphify-validation/requirements.lock +502 -0
- package/tools/graphify-validation/run.sh +43 -0
- package/tools/graphify-validation/tests/test_harness.py +151 -0
- package/cloudbuild-agent-qa.yaml +0 -43
- package/cloudbuild-agent-staging.yaml +0 -40
- package/cloudbuild.yaml.deprecated +0 -42
- package/src/local-agent-source.tar.gz +0 -0
|
@@ -4,7 +4,7 @@ import { run } from '../utils/exec-utils.js';
|
|
|
4
4
|
/**
|
|
5
5
|
* GitHubProvider
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* Git provider implementation for GitHub.
|
|
8
8
|
*/
|
|
9
9
|
export class GitHubProvider extends BaseGitProvider {
|
|
10
10
|
|
|
@@ -170,7 +170,7 @@ export class GitHubProvider extends BaseGitProvider {
|
|
|
170
170
|
|
|
171
171
|
const cloneUrl = this.getCloneUrl(owner, repo, true);
|
|
172
172
|
|
|
173
|
-
//
|
|
173
|
+
// Insert the token into the URL for authentication
|
|
174
174
|
const authedUrl = cloneUrl.replace(
|
|
175
175
|
'https://',
|
|
176
176
|
`https://${this.accessToken}@`
|
|
@@ -212,16 +212,16 @@ export class GitHubProvider extends BaseGitProvider {
|
|
|
212
212
|
}
|
|
213
213
|
|
|
214
214
|
async createBranch(owner, repo, branchName, fromRef) {
|
|
215
|
-
//
|
|
215
|
+
// Get the SHA of the source ref
|
|
216
216
|
let sha = fromRef;
|
|
217
217
|
|
|
218
218
|
if (!fromRef.match(/^[a-f0-9]{40}$/)) {
|
|
219
|
-
//
|
|
219
|
+
// It is a branch name, we need the SHA
|
|
220
220
|
const branch = await this.getBranch(owner, repo, fromRef);
|
|
221
221
|
sha = branch.sha;
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
-
//
|
|
224
|
+
// Create the reference
|
|
225
225
|
const data = await this.apiRequest(`/repos/${owner}/${repo}/git/refs`, {
|
|
226
226
|
method: 'POST',
|
|
227
227
|
body: JSON.stringify({
|
|
@@ -268,17 +268,17 @@ export class GitHubProvider extends BaseGitProvider {
|
|
|
268
268
|
}
|
|
269
269
|
|
|
270
270
|
async createCommit(owner, repo, branch, message, changes) {
|
|
271
|
-
// 1.
|
|
271
|
+
// 1. Get the current reference
|
|
272
272
|
const branchData = await this.getBranch(owner, repo, branch);
|
|
273
273
|
const baseSha = branchData.sha;
|
|
274
274
|
|
|
275
|
-
// 2.
|
|
275
|
+
// 2. Get the current tree
|
|
276
276
|
const baseCommit = await this.apiRequest(
|
|
277
277
|
`/repos/${owner}/${repo}/git/commits/${baseSha}`
|
|
278
278
|
);
|
|
279
279
|
const baseTreeSha = baseCommit.tree.sha;
|
|
280
280
|
|
|
281
|
-
// 3.
|
|
281
|
+
// 3. Create blobs for each modified file
|
|
282
282
|
const treeItems = [];
|
|
283
283
|
|
|
284
284
|
for (const change of changes) {
|
|
@@ -290,7 +290,7 @@ export class GitHubProvider extends BaseGitProvider {
|
|
|
290
290
|
sha: null
|
|
291
291
|
});
|
|
292
292
|
} else {
|
|
293
|
-
//
|
|
293
|
+
// Create a blob
|
|
294
294
|
const blob = await this.apiRequest(`/repos/${owner}/${repo}/git/blobs`, {
|
|
295
295
|
method: 'POST',
|
|
296
296
|
body: JSON.stringify({
|
|
@@ -308,7 +308,7 @@ export class GitHubProvider extends BaseGitProvider {
|
|
|
308
308
|
}
|
|
309
309
|
}
|
|
310
310
|
|
|
311
|
-
// 4.
|
|
311
|
+
// 4. Create a new tree
|
|
312
312
|
const newTree = await this.apiRequest(`/repos/${owner}/${repo}/git/trees`, {
|
|
313
313
|
method: 'POST',
|
|
314
314
|
body: JSON.stringify({
|
|
@@ -317,7 +317,7 @@ export class GitHubProvider extends BaseGitProvider {
|
|
|
317
317
|
})
|
|
318
318
|
});
|
|
319
319
|
|
|
320
|
-
// 5.
|
|
320
|
+
// 5. Create the commit
|
|
321
321
|
const newCommit = await this.apiRequest(`/repos/${owner}/${repo}/git/commits`, {
|
|
322
322
|
method: 'POST',
|
|
323
323
|
body: JSON.stringify({
|
|
@@ -327,7 +327,7 @@ export class GitHubProvider extends BaseGitProvider {
|
|
|
327
327
|
})
|
|
328
328
|
});
|
|
329
329
|
|
|
330
|
-
// 6.
|
|
330
|
+
// 6. Update the branch reference
|
|
331
331
|
await this.apiRequest(`/repos/${owner}/${repo}/git/refs/heads/${branch}`, {
|
|
332
332
|
method: 'PATCH',
|
|
333
333
|
body: JSON.stringify({
|
|
@@ -676,7 +676,7 @@ export class GitHubProvider extends BaseGitProvider {
|
|
|
676
676
|
} catch {
|
|
677
677
|
// Fallback: add a reply indicating resolution
|
|
678
678
|
await this.replyToPRComment(owner, repo, prNumber, commentId,
|
|
679
|
-
'✅ Resolved by
|
|
679
|
+
'✅ Resolved by VisionHammer — fix has been applied.');
|
|
680
680
|
return { resolved: true };
|
|
681
681
|
}
|
|
682
682
|
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// Multi-repo coordinated PR helpers.
|
|
2
|
+
//
|
|
3
|
+
// A Workbench multi-repo session clones N repos UNDER ONE per-workspace parent
|
|
4
|
+
// dir (see POST /workspace/open cloud-multi-clone). The parent dir is NOT itself
|
|
5
|
+
// a git repo; each immediate sub-dir is. This module holds the PURE, unit-tested
|
|
6
|
+
// helpers used by /workspace/git/create-fix-branch to open ONE PR per CHANGED
|
|
7
|
+
// repo on a SHARED branch name, and cross-link the sibling PRs in each body.
|
|
8
|
+
//
|
|
9
|
+
// Single-repo behavior is untouched: server.js only routes here when the
|
|
10
|
+
// workspace root is a multi-repo parent AND the feature flag is enabled.
|
|
11
|
+
|
|
12
|
+
import fs from "fs";
|
|
13
|
+
import path from "path";
|
|
14
|
+
|
|
15
|
+
/** Feature flag. Default ON; set MULTI_REPO_PR_ENABLED=false to disable. */
|
|
16
|
+
export function multiRepoPrEnabled(env = process.env) {
|
|
17
|
+
return String(env.MULTI_REPO_PR_ENABLED ?? "true").toLowerCase() !== "false";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Immediate sub-directories of `root` that are themselves git repos
|
|
22
|
+
* (contain a `.git` entry). Returns [{ dir, path }] sorted by dir name for
|
|
23
|
+
* stable ordering. Never throws — an unreadable root yields [].
|
|
24
|
+
*/
|
|
25
|
+
export function listRepoSubdirs(root, fsImpl = fs) {
|
|
26
|
+
if (!root) return [];
|
|
27
|
+
let entries;
|
|
28
|
+
try {
|
|
29
|
+
entries = fsImpl.readdirSync(root, { withFileTypes: true });
|
|
30
|
+
} catch {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
const repos = [];
|
|
34
|
+
for (const e of entries) {
|
|
35
|
+
if (!e.isDirectory()) continue;
|
|
36
|
+
const repoPath = path.join(root, e.name);
|
|
37
|
+
let hasGit = false;
|
|
38
|
+
try {
|
|
39
|
+
hasGit = fsImpl.existsSync(path.join(repoPath, ".git"));
|
|
40
|
+
} catch {
|
|
41
|
+
hasGit = false;
|
|
42
|
+
}
|
|
43
|
+
if (hasGit) repos.push({ dir: e.name, path: repoPath });
|
|
44
|
+
}
|
|
45
|
+
repos.sort((a, b) => a.dir.localeCompare(b.dir));
|
|
46
|
+
return repos;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* A "multi-repo root" is a dir that is NOT a git repo itself but has >= 1
|
|
51
|
+
* git sub-repo directly under it. When the root IS a git repo (single-repo
|
|
52
|
+
* clone) this returns false and the caller keeps the legacy single-repo path.
|
|
53
|
+
*/
|
|
54
|
+
export function isMultiRepoRoot(root, fsImpl = fs) {
|
|
55
|
+
if (!root) return false;
|
|
56
|
+
let rootIsRepo = false;
|
|
57
|
+
try {
|
|
58
|
+
rootIsRepo = fsImpl.existsSync(path.join(root, ".git"));
|
|
59
|
+
} catch {
|
|
60
|
+
rootIsRepo = false;
|
|
61
|
+
}
|
|
62
|
+
if (rootIsRepo) return false;
|
|
63
|
+
return listRepoSubdirs(root, fsImpl).length > 0;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Parse an authenticated git remote URL into its parts.
|
|
68
|
+
* Handles https URLs with embedded userinfo, e.g.
|
|
69
|
+
* https://x-access-token:TOKEN@github.com/owner/repo.git
|
|
70
|
+
* https://oauth2:TOKEN@gitlab.com/group/repo.git
|
|
71
|
+
* https://user:appPassword@bitbucket.org/ws/repo.git
|
|
72
|
+
* https://github.com/owner/repo (no auth)
|
|
73
|
+
* Returns { host, provider, ownerRepo, userinfo, apiToken } or null if unparseable.
|
|
74
|
+
* apiToken = the token component for REST auth (part after the first ':' in
|
|
75
|
+
* userinfo when present, else the whole userinfo).
|
|
76
|
+
*/
|
|
77
|
+
export function parseAuthedRemote(remoteUrl) {
|
|
78
|
+
if (!remoteUrl || typeof remoteUrl !== "string") return null;
|
|
79
|
+
let u = remoteUrl.trim();
|
|
80
|
+
// Normalize scp-like SSH form git@host:owner/repo.git -> https://host/owner/repo.git
|
|
81
|
+
const sshMatch = u.match(/^([^@]+)@([^:]+):(.+)$/);
|
|
82
|
+
if (sshMatch && !u.includes("://")) {
|
|
83
|
+
u = `https://${sshMatch[2]}/${sshMatch[3]}`;
|
|
84
|
+
}
|
|
85
|
+
const m = u.match(/^https?:\/\/(?:([^@/]+)@)?([^/]+)\/(.+?)(?:\.git)?\/?$/i);
|
|
86
|
+
if (!m) return null;
|
|
87
|
+
const userinfo = m[1] || "";
|
|
88
|
+
const host = m[2];
|
|
89
|
+
const ownerRepo = m[3];
|
|
90
|
+
let provider = "generic";
|
|
91
|
+
if (/github\./i.test(host)) provider = "github";
|
|
92
|
+
else if (/gitlab\./i.test(host)) provider = "gitlab";
|
|
93
|
+
else if (/bitbucket\./i.test(host)) provider = "bitbucket";
|
|
94
|
+
let apiToken = "";
|
|
95
|
+
if (userinfo) {
|
|
96
|
+
const ci = userinfo.indexOf(":");
|
|
97
|
+
apiToken = ci >= 0 ? userinfo.slice(ci + 1) : userinfo;
|
|
98
|
+
}
|
|
99
|
+
return { host, provider, ownerRepo, userinfo, apiToken };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Strip any embedded userinfo (credentials) from an https git URL for display. */
|
|
103
|
+
export function stripRemoteCreds(remoteUrl) {
|
|
104
|
+
if (!remoteUrl) return "";
|
|
105
|
+
return remoteUrl.replace(/^(https?:\/\/)[^@/]+@/i, "$1").replace(/\.git$/i, "");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Build a "compare"/"new MR" web URL for a pushed branch when no API PR link
|
|
110
|
+
* is available (non-GitHub providers, or API failure). cleanBase = the repo web
|
|
111
|
+
* URL without creds or .git.
|
|
112
|
+
*/
|
|
113
|
+
export function buildCompareUrl(cleanBase, provider, branch, targetBranch) {
|
|
114
|
+
if (!cleanBase) return "";
|
|
115
|
+
switch (provider) {
|
|
116
|
+
case "gitlab":
|
|
117
|
+
return `${cleanBase}/-/merge_requests/new?merge_request[source_branch]=${branch}` +
|
|
118
|
+
`&merge_request[target_branch]=${targetBranch}`;
|
|
119
|
+
case "bitbucket":
|
|
120
|
+
return `${cleanBase}/pull-requests/new?source=${branch}&dest=${targetBranch}`;
|
|
121
|
+
case "github":
|
|
122
|
+
default:
|
|
123
|
+
return `${cleanBase}/compare/${targetBranch}...${branch}?expand=1`;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Cross-link note appended to each PR body: "part of a multi-repo change" plus
|
|
129
|
+
* links to the SIBLING PRs (all but the one identified by selfDir). Returns ""
|
|
130
|
+
* when there are no siblings with a usable url.
|
|
131
|
+
*/
|
|
132
|
+
export function buildCrossLinkNote(selfDir, prs) {
|
|
133
|
+
if (!Array.isArray(prs)) return "";
|
|
134
|
+
const siblings = prs.filter((p) => p && p.dir !== selfDir && p.prUrl);
|
|
135
|
+
if (siblings.length === 0) return "";
|
|
136
|
+
const links = siblings.map((p) => `- ${p.dir}: ${p.prUrl}`).join("\n");
|
|
137
|
+
return `\n\n---\nPart of a multi-repo change. Sibling pull requests:\n${links}\n`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const VH_TRAILER = "\nGenerated by VisionHammer Agent\n";
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Base PR body for one repo of a coordinated multi-repo change (provider-neutral,
|
|
144
|
+
* ASCII-only). Sibling links are appended later via buildCrossLinkNote once every
|
|
145
|
+
* PR exists (two-pass), so this body is self-consistent even if a sibling fails.
|
|
146
|
+
*/
|
|
147
|
+
export function buildMultiRepoPrBody({ dir, branch, commitMessage, filesChanged, commitSha }) {
|
|
148
|
+
const lines = [
|
|
149
|
+
"## Coordinated multi-repo change",
|
|
150
|
+
"",
|
|
151
|
+
`**Repo dir:** \`${dir}\``,
|
|
152
|
+
`**Branch:** \`${branch}\``,
|
|
153
|
+
`**Files changed:** ${filesChanged}`,
|
|
154
|
+
];
|
|
155
|
+
if (commitSha) lines.push(`**Commit:** ${String(commitSha).substring(0, 8)}`);
|
|
156
|
+
if (commitMessage) {
|
|
157
|
+
lines.push("", "### Summary", commitMessage);
|
|
158
|
+
}
|
|
159
|
+
lines.push("", "---", VH_TRAILER.trim());
|
|
160
|
+
return lines.join("\n");
|
|
161
|
+
}
|
package/src/mcp-http-server.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import express from "express";
|
|
2
|
+
import { sanitizedChildEnv } from "./utils/child-env.js";
|
|
3
|
+
import { createAuthMiddleware } from "./utils/agent-auth.js";
|
|
4
|
+
import { getSigningConfig, createSignatureMiddleware, captureRawBody } from "./utils/request-signing.js";
|
|
5
|
+
import { decideExec, rejectionBody, resolveExecPolicyConfig } from "./utils/exec-policy.js";
|
|
2
6
|
import path from "path";
|
|
3
7
|
import { spawn } from "child_process";
|
|
4
8
|
import { readFile, writeFile, listRecursive, exists } from "./fs-utils.js";
|
|
@@ -6,22 +10,30 @@ import { detectProject } from "./detectors.js";
|
|
|
6
10
|
import { applyUnifiedDiff } from "./patch.js";
|
|
7
11
|
|
|
8
12
|
/**
|
|
9
|
-
* MCP HTTP Bridge —
|
|
13
|
+
* MCP HTTP Bridge — Exposes the MCP tools as a REST API.
|
|
10
14
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
15
|
+
* The Java Gateway calls these endpoints to get code context
|
|
16
|
+
* BEFORE sending the prompt to Claude.
|
|
13
17
|
*
|
|
14
|
-
* Port: 5056 (
|
|
18
|
+
* Port: 5056 (separate from the main Express server on 5055)
|
|
15
19
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
20
|
+
* This is more practical than stdio MCP for Gateway ↔ Agent communication,
|
|
21
|
+
* and keeps compatibility with MCP tools for future use with Claude Code.
|
|
18
22
|
*/
|
|
19
23
|
|
|
20
24
|
const IGNORE_DIRS = ["node_modules", ".git", "target", "build", "dist", ".idea", "__pycache__", "vendor", ".gradle"];
|
|
21
25
|
|
|
22
26
|
export function startMCPHttpServer(workspaceManager, port = 5056) {
|
|
23
27
|
const app = express();
|
|
24
|
-
|
|
28
|
+
// SECURITY: this bridge runs shell commands (/mcp/execute-command) and
|
|
29
|
+
// writes files. Its only consumer is the in-process WS tunnel, so it binds
|
|
30
|
+
// to loopback and sits behind the same auth perimeter as the main API.
|
|
31
|
+
app.use(createAuthMiddleware());
|
|
32
|
+
app.use(express.json({ limit: "50mb", verify: captureRawBody }));
|
|
33
|
+
// Same signed-command verification as the main API (shared nonce cache).
|
|
34
|
+
const signing = getSigningConfig();
|
|
35
|
+
const execPolicy = resolveExecPolicyConfig();
|
|
36
|
+
app.use(createSignatureMiddleware(signing));
|
|
25
37
|
|
|
26
38
|
// ========================================
|
|
27
39
|
// Health
|
|
@@ -175,14 +187,16 @@ export function startMCPHttpServer(workspaceManager, port = 5056) {
|
|
|
175
187
|
const { workspaceId, command, timeout = 120 } = req.body;
|
|
176
188
|
if (!command) return res.status(400).json({ error: "command required" });
|
|
177
189
|
|
|
178
|
-
// Security
|
|
179
|
-
const BLOCKED = ["rm -rf /", "mkfs", "dd if=/dev", ":(){ :|:"];
|
|
180
|
-
if (BLOCKED.some(b => command.includes(b))) {
|
|
181
|
-
return res.status(403).json({ error: "Command blocked" });
|
|
182
|
-
}
|
|
183
|
-
|
|
184
190
|
try {
|
|
185
191
|
const root = workspaceManager.resolveRoot(workspaceId);
|
|
192
|
+
// Security: allowlist (utils/exec-policy.js) instead of the old blocklist.
|
|
193
|
+
const decision = decideExec({
|
|
194
|
+
command, root, headers: req.headers, body: req.body || {},
|
|
195
|
+
vhSignature: req.vhSignature, signingCfg: signing,
|
|
196
|
+
}, execPolicy);
|
|
197
|
+
if (!decision.allow) {
|
|
198
|
+
return res.status(decision.status || 403).json(rejectionBody(decision));
|
|
199
|
+
}
|
|
186
200
|
const result = await execInWorkspace(root, command, timeout);
|
|
187
201
|
res.json({ ok: result.exitCode === 0, ...result });
|
|
188
202
|
} catch (e) {
|
|
@@ -243,8 +257,9 @@ export function startMCPHttpServer(workspaceManager, port = 5056) {
|
|
|
243
257
|
// ========================================
|
|
244
258
|
// START SERVER
|
|
245
259
|
// ========================================
|
|
246
|
-
|
|
247
|
-
|
|
260
|
+
const host = process.env.MCP_BIND_HOST || "127.0.0.1";
|
|
261
|
+
app.listen(port, host, () => {
|
|
262
|
+
console.log(`🔌 MCP HTTP Bridge listening on http://${host}:${port}`);
|
|
248
263
|
});
|
|
249
264
|
|
|
250
265
|
return app;
|
|
@@ -288,7 +303,8 @@ function grepWorkspace(root, query, filePattern, maxResults) {
|
|
|
288
303
|
|
|
289
304
|
function execInWorkspace(root, command, timeoutSec) {
|
|
290
305
|
return new Promise((resolve) => {
|
|
291
|
-
|
|
306
|
+
// SECURITY (2026-10-11): model-authored command; strip the agent's secrets.
|
|
307
|
+
const child = spawn("sh", ["-c", command], { cwd: root, env: sanitizedChildEnv(process.env) });
|
|
292
308
|
let stdout = "";
|
|
293
309
|
let stderr = "";
|
|
294
310
|
|