@jstn-sdk/ma 0.1.12 → 0.14.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/.codex/agents/Architect.toml +1 -2
- package/.codex/agents/Auditor.toml +1 -2
- package/.codex/agents/Builder.toml +1 -2
- package/.codex/agents/Flow.toml +1 -2
- package/.codex/agents/Sage.toml +1 -2
- package/.codex/agents/Vibe.toml +1 -2
- package/.codex/hooks.json +24 -13
- package/.codex/prompts/enforcement.md +4 -0
- package/.codex/prompts/onboarding.md +6 -1
- package/.codex/prompts/release-rules.md +0 -0
- package/.codex/prompts/skill-contract.md +4 -0
- package/COVERAGE.md +211 -0
- package/DEMO.md +274 -0
- package/LICENSE +0 -0
- package/README.md +132 -656
- package/bin/ma.js +748 -55
- package/data/clone-data.ledger.json +41 -0
- package/data/clone-data.proof.json +50 -0
- package/data/clone-data.rvf +37 -0
- package/docs/README.md +10 -1
- package/docs/autonomous-tasks.md +46 -0
- package/docs/codex-integration.md +16 -0
- package/docs/getting-started.md +62 -22
- package/docs/mcp-setup.md +64 -0
- package/docs/qa/release-issue-gates-0.14.0.json +644 -0
- package/docs/qa/release-readiness-0.1.5.md +1 -1
- package/docs/qa/{release-readiness-0.1.12.md → release-readiness-0.14.0.md} +25 -9
- package/docs/quality.md +24 -0
- package/docs/reference/native-security-playbooks.md +6 -0
- package/docs/reference/native-source-selection.md +6 -0
- package/docs/release-spec.md +39 -10
- package/docs/skills.md +14 -1
- package/index.js +295 -2
- package/mcp/collections.json +23 -6
- package/mcp/fallback.json +0 -0
- package/mcp/local/code-intel.js +179 -41
- package/mcp/local/context.js +180 -0
- package/mcp/local/memory.js +17 -6
- package/mcp/local/quality.js +39 -0
- package/mcp/local/state.js +7 -3
- package/mcp/local/team-run.js +56 -4
- package/mcp/local-capabilities.json +9 -0
- package/mcp/servers.json +24 -0
- package/package.json +29 -4
- package/plugins/meta-architect/.app.json +1 -1
- package/plugins/meta-architect/.codex-plugin/plugin.json +1 -1
- package/plugins/meta-architect/.mcp.json +1 -1
- package/plugins/meta-architect/README.md +8 -1
- package/plugins/meta-architect/obsidian/main.js +534 -0
- package/plugins/meta-architect/obsidian/manifest.json +9 -0
- package/plugins/meta-architect/obsidian/styles.css +7 -0
- package/plugins/meta-architect/skills/arch/SKILL.md +2 -0
- package/plugins/meta-architect/skills/build/SKILL.md +3 -0
- package/plugins/meta-architect/skills/flow/SKILL.md +2 -0
- package/plugins/meta-architect/skills/sage/SKILL.md +2 -0
- package/plugins/meta-architect/skills/sage/references/source-selection.md +8 -0
- package/plugins/meta-architect/skills/vet/SKILL.md +2 -0
- package/plugins/meta-architect/skills/vet/references/security-playbooks.md +7 -0
- package/plugins/meta-architect/skills/vibe/SKILL.md +2 -0
- package/schemas/autonomous-task-queue.schema.json +56 -0
- package/schemas/handoff-packet.schema.json +36 -0
- package/schemas/setup-receipt.schema.json +28 -0
- package/schemas/skill-frontmatter.schema.json +20 -0
- package/schemas/support-bundle.schema.json +37 -0
- package/schemas/task-contract.schema.json +31 -0
- package/scripts/active-autonomy-hook.mjs +226 -0
- package/scripts/build-linux-packages.mjs +7 -3
- package/scripts/cleanup-test-fixtures.sh +55 -0
- package/scripts/context-hydration-hook.mjs +89 -0
- package/scripts/demo-smoke.js +285 -0
- package/scripts/doctor.js +27 -12
- package/scripts/install.sh +57 -0
- package/scripts/install.sh.sha256 +1 -0
- package/scripts/linux-package-smoke.mjs +2 -2
- package/scripts/package-size-check.mjs +26 -0
- package/scripts/plugin-sync.js +83 -17
- package/scripts/postinstall.js +17 -10
- package/scripts/prepack.js +9 -0
- package/scripts/ralph/prompt.md +35 -0
- package/scripts/release-sync.js +72 -7
- package/scripts/release-verify.js +260 -2
- package/scripts/setup-npmrc.js +48 -23
- package/scripts/skills-install.js +9 -2
- package/scripts/skills-manifest.js +2 -21
- package/scripts/skills-pack.js +2 -2
- package/scripts/skills-validate.js +5 -55
- package/skills/arch/SKILL.md +2 -0
- package/skills/build/SKILL.md +3 -0
- package/skills/flow/SKILL.md +2 -0
- package/skills/index.json +0 -0
- package/skills/sage/SKILL.md +2 -0
- package/skills/sage/references/source-selection.md +8 -0
- package/skills/vet/SKILL.md +2 -0
- package/skills/vet/references/security-playbooks.md +7 -0
- package/skills/vibe/SKILL.md +2 -0
- package/sprint/00-idea.md +0 -0
- package/sprint/01-architecture.md +0 -0
- package/sprint/02-oss-evidence.md +0 -0
- package/sprint/03-logic.md +0 -0
- package/sprint/04-security.md +0 -0
- package/sprint/05-dx-ux.md +0 -0
- package/sprint/06-build-plan.md +0 -0
- package/sprint/07-release.md +0 -0
- package/src/agents.js +279 -0
- package/src/bootstrap.js +268 -62
- package/src/build-gate.js +2 -2
- package/src/codex-app-server.js +291 -0
- package/src/decision-log.js +0 -0
- package/src/fs-utils.js +175 -4
- package/src/launcher.js +28 -21
- package/src/mcp-config.js +9 -1
- package/src/mcp-live-client.js +422 -3
- package/src/paths.js +10 -1
- package/src/policy.js +3 -3
- package/src/prelaunch.js +189 -0
- package/src/quality/ai-quality-orchestrator.js +328 -0
- package/src/release-issue-gates.js +252 -0
- package/src/release-operations.js +62 -0
- package/src/release-state.js +0 -0
- package/src/runtime/active-autonomy-core.js +208 -0
- package/src/runtime/agent-compat.js +31 -0
- package/src/runtime/alignment-sentinel.js +165 -0
- package/src/runtime/architect-review.js +326 -0
- package/src/runtime/autonomous-tasks.js +408 -0
- package/src/runtime/build-readiness.js +28 -4
- package/src/runtime/code-graph-rehearse.js +113 -0
- package/src/runtime/codeburn-core.js +112 -0
- package/src/runtime/context-authority.js +40 -0
- package/src/runtime/context-economy-core.js +316 -0
- package/src/runtime/continuity-graph.js +201 -0
- package/src/runtime/continuity-notes.js +71 -26
- package/src/runtime/core-source-ingest.js +379 -0
- package/src/runtime/detached-provider.js +142 -0
- package/src/runtime/doctor-report.js +18 -0
- package/src/runtime/environment-awareness-core.js +460 -0
- package/src/runtime/exposure-catalog.js +276 -0
- package/src/runtime/graphify-core.js +167 -0
- package/src/runtime/guidance-stack.js +9 -2
- package/src/runtime/handoff-packets.js +92 -0
- package/src/runtime/headroom-core.js +104 -0
- package/src/runtime/helper-orchestration-core.js +307 -0
- package/src/runtime/hook-profiles.js +20 -0
- package/src/runtime/learning-loop-core.js +344 -0
- package/src/runtime/live-agent-verification.js +122 -0
- package/src/runtime/maestro-events.js +18 -0
- package/src/runtime/maestro-manager.js +19 -0
- package/src/runtime/maestro-output.js +58 -0
- package/src/runtime/maestro-state.js +125 -0
- package/src/runtime/managed-markdown.js +29 -0
- package/src/runtime/mcp-authority.js +147 -0
- package/src/runtime/mcp-policy.js +192 -0
- package/src/runtime/obsidian-integration-core.js +1089 -0
- package/src/runtime/obsidian-plugin-bridge.js +1045 -0
- package/src/runtime/pi-maestro-core.js +89 -0
- package/src/runtime/preferences.js +79 -0
- package/src/runtime/project-context.js +636 -0
- package/src/runtime/prompt-strategy-core.js +230 -0
- package/src/runtime/quorum-review.js +90 -0
- package/src/runtime/ralph-execution-core.js +217 -0
- package/src/runtime/redaction-gateway.js +373 -0
- package/src/runtime/runtime-state.js +935 -2
- package/src/runtime/schema-migrations.js +162 -0
- package/src/runtime/semantic-recording-core.js +147 -0
- package/src/runtime/signal-hooks.js +28 -0
- package/src/runtime/skills-registry-export.js +754 -0
- package/src/runtime/task-contracts.js +83 -0
- package/src/runtime/universal-plugin-broker-core.js +898 -0
- package/src/runtime/workspace-intelligence-runtime.js +674 -0
- package/src/runtime/workspace-virtualizer.js +102 -0
- package/src/runtime-artifacts.js +225 -62
- package/src/setup-lifecycle.js +359 -0
- package/src/skill-frontmatter.js +78 -0
- package/src/skill-installer.js +226 -71
- package/src/skills.js +1166 -96
- package/src/state-sync.js +0 -0
- package/src/test-fixtures.js +543 -0
- package/src/tui/grid.js +67 -0
- package/src/tui/status-grid.js +23 -0
- package/support-bundle.json +114 -0
- package/templates/AGENTS.md +6 -2
- package/templates/catalog-manifest.json +0 -0
- package/templates/model-instructions/core.md +0 -0
- package/templates/model-instructions/release.md +0 -0
- package/templates/model-instructions/security.md +0 -0
- package/templates/quality/ai-quality-rules.yml +37 -0
- package/docs/assets/image/Screenshot(1).png +0 -0
- package/docs/assets/image/Screenshot(2).png +0 -0
- package/docs/assets/image/Screenshot(3).png +0 -0
- package/docs/assets/image/Screenshot(4).png +0 -0
- package/docs/assets/image/Screenshot(5).png +0 -0
- package/docs/assets/image/Screenshot(6).png +0 -0
- package/docs/assets/image/Screenshot(7).png +0 -0
- package/docs/assets/image/Screenshot(8).png +0 -0
- package/docs/assets/image/Screenshot(9).png +0 -0
- package/docs/assets/meta-architect-logo.png +0 -0
- package/docs/assets/meta-architect-logo.svg +0 -8
- package/docs/onboarding.md +0 -65
- package/docs/skills-publishing.md +0 -255
package/src/mcp-live-client.js
CHANGED
|
@@ -1,3 +1,105 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { maskSensitiveText } from "./runtime/redaction-gateway.js";
|
|
6
|
+
|
|
7
|
+
const packageMetadata = JSON.parse(
|
|
8
|
+
readFileSync(new URL("../package.json", import.meta.url), "utf8"),
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
export function resolveMcpClientVersion(metadata) {
|
|
12
|
+
return typeof metadata?.version === "string" && metadata.version.trim()
|
|
13
|
+
? metadata.version
|
|
14
|
+
: "0.0.0-dev";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const mcpClientVersion = resolveMcpClientVersion(packageMetadata);
|
|
18
|
+
|
|
19
|
+
const GITMCP_ENDPOINT_PATTERN = /^https:\/\/gitmcp\.io\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
|
|
20
|
+
const BRIDGE_COMMAND_ENV = "MA_MCP_REMOTE_BRIDGE_CMD";
|
|
21
|
+
const REQUEST_TIMEOUT_ENV = "MA_MCP_REQUEST_TIMEOUT_MS";
|
|
22
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = 15000;
|
|
23
|
+
const BRIDGE_POLICY_PATH = path.join("mcp", "bridge.json");
|
|
24
|
+
|
|
25
|
+
function readRequestTimeoutMs() {
|
|
26
|
+
const raw = process.env[REQUEST_TIMEOUT_ENV]?.trim();
|
|
27
|
+
if (!raw) return DEFAULT_REQUEST_TIMEOUT_MS;
|
|
28
|
+
|
|
29
|
+
const parsed = Number(raw);
|
|
30
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
31
|
+
return DEFAULT_REQUEST_TIMEOUT_MS;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return Math.max(50, Math.floor(parsed));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export class McpRemoteTransportRequiredError extends Error {
|
|
38
|
+
constructor(endpoint, status, bodyPreview = "") {
|
|
39
|
+
const bridgeHint =
|
|
40
|
+
`Set ${BRIDGE_COMMAND_ENV} to a trusted stdio bridge command with {url}, ` +
|
|
41
|
+
"for example a preinstalled mcp-remote wrapper, then rerun $sage.";
|
|
42
|
+
const bodyHint = bodyPreview ? ` Response preview: ${bodyPreview}` : "";
|
|
43
|
+
super(
|
|
44
|
+
`Remote MCP endpoint requires a bridge transport: ${endpoint} returned HTTP ${status}. ${bridgeHint}${bodyHint}`,
|
|
45
|
+
);
|
|
46
|
+
this.name = "McpRemoteTransportRequiredError";
|
|
47
|
+
this.endpoint = endpoint;
|
|
48
|
+
this.status = status;
|
|
49
|
+
this.bridgeEnv = BRIDGE_COMMAND_ENV;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function isGitMcpEndpoint(endpoint) {
|
|
54
|
+
return GITMCP_ENDPOINT_PATTERN.test(endpoint);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function hasConfiguredMcpRemoteBridge() {
|
|
58
|
+
return Boolean(process.env[BRIDGE_COMMAND_ENV]?.trim());
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function createMcpLiveClient(endpoint) {
|
|
62
|
+
if (hasConfiguredMcpRemoteBridge()) {
|
|
63
|
+
return new McpStdioBridgeClient(
|
|
64
|
+
endpoint,
|
|
65
|
+
process.env[BRIDGE_COMMAND_ENV],
|
|
66
|
+
spawn,
|
|
67
|
+
getMcpBridgeConfiguration(),
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return new McpSseClient(endpoint);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function getMcpBridgeConfiguration(root = process.env.MA_ROOT ?? process.cwd()) {
|
|
75
|
+
const allowedCommands = new Set(
|
|
76
|
+
(process.env.MA_MCP_REMOTE_BRIDGE_ALLOWLIST ?? "")
|
|
77
|
+
.split(",")
|
|
78
|
+
.map((value) => value.trim())
|
|
79
|
+
.filter(Boolean),
|
|
80
|
+
);
|
|
81
|
+
const configPath = path.join(root, BRIDGE_POLICY_PATH);
|
|
82
|
+
try {
|
|
83
|
+
const config = JSON.parse(readFileSync(configPath, "utf8"));
|
|
84
|
+
for (const command of config.allowedCommands ?? []) {
|
|
85
|
+
if (typeof command === "string" && command.trim()) allowedCommands.add(command.trim());
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
configured: true,
|
|
89
|
+
configPath,
|
|
90
|
+
allowedCommands: [...allowedCommands],
|
|
91
|
+
source: "environment+project",
|
|
92
|
+
};
|
|
93
|
+
} catch {
|
|
94
|
+
return {
|
|
95
|
+
configured: true,
|
|
96
|
+
configPath,
|
|
97
|
+
allowedCommands: [...allowedCommands],
|
|
98
|
+
source: allowedCommands.size > 0 ? "environment" : "none",
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
1
103
|
export class McpSseClient {
|
|
2
104
|
constructor(baseUrl) {
|
|
3
105
|
this.baseUrl = baseUrl;
|
|
@@ -17,6 +119,10 @@ export class McpSseClient {
|
|
|
17
119
|
});
|
|
18
120
|
|
|
19
121
|
if (!response.ok || !response.body) {
|
|
122
|
+
const bodyPreview = await readResponsePreview(response);
|
|
123
|
+
if (isGitMcpEndpoint(this.baseUrl) && response.status === 405) {
|
|
124
|
+
throw new McpRemoteTransportRequiredError(this.baseUrl, response.status, bodyPreview);
|
|
125
|
+
}
|
|
20
126
|
throw new Error(`Failed to open MCP SSE stream: ${response.status}`);
|
|
21
127
|
}
|
|
22
128
|
|
|
@@ -26,7 +132,7 @@ export class McpSseClient {
|
|
|
26
132
|
5000,
|
|
27
133
|
"Timed out waiting for MCP endpoint event",
|
|
28
134
|
);
|
|
29
|
-
if (
|
|
135
|
+
if (endpointEvent?.event !== "endpoint") {
|
|
30
136
|
throw new Error("MCP server did not provide an endpoint event");
|
|
31
137
|
}
|
|
32
138
|
|
|
@@ -38,7 +144,7 @@ export class McpSseClient {
|
|
|
38
144
|
capabilities: {},
|
|
39
145
|
clientInfo: {
|
|
40
146
|
name: "meta-architect",
|
|
41
|
-
version:
|
|
147
|
+
version: mcpClientVersion,
|
|
42
148
|
},
|
|
43
149
|
});
|
|
44
150
|
|
|
@@ -52,7 +158,7 @@ export class McpSseClient {
|
|
|
52
158
|
const timeout = setTimeout(() => {
|
|
53
159
|
this.pending.delete(id);
|
|
54
160
|
reject(new Error(`Timed out waiting for MCP response to ${method}`));
|
|
55
|
-
},
|
|
161
|
+
}, readRequestTimeoutMs());
|
|
56
162
|
|
|
57
163
|
this.pending.set(id, {
|
|
58
164
|
resolve: (payload) => {
|
|
@@ -171,6 +277,319 @@ export class McpSseClient {
|
|
|
171
277
|
}
|
|
172
278
|
}
|
|
173
279
|
|
|
280
|
+
export class McpStdioBridgeClient {
|
|
281
|
+
constructor(endpoint, commandTemplate, spawnImpl = spawn, bridgeConfiguration = {}) {
|
|
282
|
+
this.endpoint = endpoint;
|
|
283
|
+
this.commandTemplate = commandTemplate;
|
|
284
|
+
this.spawnImpl = spawnImpl;
|
|
285
|
+
this.bridgeConfiguration = bridgeConfiguration;
|
|
286
|
+
this.child = null;
|
|
287
|
+
this.buffer = "";
|
|
288
|
+
this.pending = new Map();
|
|
289
|
+
this.nextId = 1;
|
|
290
|
+
this.stderr = "";
|
|
291
|
+
this.closed = false;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async connect() {
|
|
295
|
+
const { command, args } = buildBridgeCommand(
|
|
296
|
+
this.commandTemplate,
|
|
297
|
+
this.endpoint,
|
|
298
|
+
this.bridgeConfiguration,
|
|
299
|
+
);
|
|
300
|
+
this.child = this.spawnImpl(command, args, {
|
|
301
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
302
|
+
env: buildBridgeEnv(),
|
|
303
|
+
});
|
|
304
|
+
await writeBridgeReceipt(this.endpoint, command, args, {
|
|
305
|
+
status: "started",
|
|
306
|
+
pid: this.child.pid ?? null,
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
this.child.stdout.setEncoding("utf8");
|
|
310
|
+
this.child.stderr.setEncoding("utf8");
|
|
311
|
+
this.child.stdout.on("data", (chunk) => this.#handleStdout(chunk));
|
|
312
|
+
this.child.stderr.on("data", (chunk) => {
|
|
313
|
+
this.stderr = `${this.stderr}${chunk}`.slice(-2000);
|
|
314
|
+
});
|
|
315
|
+
this.child.on("error", (error) => {
|
|
316
|
+
this.closed = true;
|
|
317
|
+
void writeBridgeReceipt(this.endpoint, command, args, {
|
|
318
|
+
status: "failed",
|
|
319
|
+
error: error.message,
|
|
320
|
+
});
|
|
321
|
+
for (const [, handlers] of this.pending) {
|
|
322
|
+
handlers.reject(new Error(`MCP bridge failed to start: ${error.message}`));
|
|
323
|
+
}
|
|
324
|
+
this.pending.clear();
|
|
325
|
+
});
|
|
326
|
+
this.child.on("exit", (code, signal) => {
|
|
327
|
+
this.closed = true;
|
|
328
|
+
void writeBridgeReceipt(this.endpoint, command, args, {
|
|
329
|
+
status: "exited",
|
|
330
|
+
code,
|
|
331
|
+
signal,
|
|
332
|
+
stderr: this.stderr,
|
|
333
|
+
});
|
|
334
|
+
const reason = signal
|
|
335
|
+
? `MCP bridge exited with signal ${signal}`
|
|
336
|
+
: `MCP bridge exited with code ${code}`;
|
|
337
|
+
for (const [, handlers] of this.pending) {
|
|
338
|
+
handlers.reject(new Error(`${reason}${this.stderr ? `: ${this.stderr.trim()}` : ""}`));
|
|
339
|
+
}
|
|
340
|
+
this.pending.clear();
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
const initResult = await this.request("initialize", {
|
|
344
|
+
protocolVersion: "2025-03-26",
|
|
345
|
+
capabilities: {},
|
|
346
|
+
clientInfo: {
|
|
347
|
+
name: "meta-architect",
|
|
348
|
+
version: mcpClientVersion,
|
|
349
|
+
},
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
await this.notify("notifications/initialized");
|
|
353
|
+
return initResult;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
async request(method, params = {}) {
|
|
357
|
+
const id = this.nextId++;
|
|
358
|
+
const responsePromise = new Promise((resolve, reject) => {
|
|
359
|
+
const timeout = setTimeout(() => {
|
|
360
|
+
this.pending.delete(id);
|
|
361
|
+
reject(new Error(`Timed out waiting for MCP bridge response to ${method}`));
|
|
362
|
+
}, readRequestTimeoutMs());
|
|
363
|
+
|
|
364
|
+
this.pending.set(id, {
|
|
365
|
+
resolve: (payload) => {
|
|
366
|
+
clearTimeout(timeout);
|
|
367
|
+
resolve(payload);
|
|
368
|
+
},
|
|
369
|
+
reject: (error) => {
|
|
370
|
+
clearTimeout(timeout);
|
|
371
|
+
reject(error);
|
|
372
|
+
},
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
this.#send({
|
|
377
|
+
jsonrpc: "2.0",
|
|
378
|
+
id,
|
|
379
|
+
method,
|
|
380
|
+
params,
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
return responsePromise;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
async notify(method, params = {}) {
|
|
387
|
+
this.#send({
|
|
388
|
+
jsonrpc: "2.0",
|
|
389
|
+
method,
|
|
390
|
+
params,
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
async close() {
|
|
395
|
+
if (!this.child || this.closed) return;
|
|
396
|
+
this.child.stdin.end();
|
|
397
|
+
this.child.kill();
|
|
398
|
+
await new Promise((resolve) => {
|
|
399
|
+
const timeout = setTimeout(resolve, 1000);
|
|
400
|
+
this.child.once("exit", () => {
|
|
401
|
+
clearTimeout(timeout);
|
|
402
|
+
resolve();
|
|
403
|
+
});
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
#send(message) {
|
|
408
|
+
if (!this.child?.stdin.writable) {
|
|
409
|
+
throw new Error("MCP bridge is not writable");
|
|
410
|
+
}
|
|
411
|
+
this.child.stdin.write(`${JSON.stringify(message)}\n`);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
#handleStdout(chunk) {
|
|
415
|
+
this.buffer += chunk;
|
|
416
|
+
while (true) {
|
|
417
|
+
const newlineIndex = this.buffer.indexOf("\n");
|
|
418
|
+
if (newlineIndex === -1) return;
|
|
419
|
+
|
|
420
|
+
const line = this.buffer.slice(0, newlineIndex).trim();
|
|
421
|
+
this.buffer = this.buffer.slice(newlineIndex + 1);
|
|
422
|
+
if (!line?.startsWith("{")) continue;
|
|
423
|
+
|
|
424
|
+
let payload;
|
|
425
|
+
try {
|
|
426
|
+
payload = JSON.parse(line);
|
|
427
|
+
} catch {
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
if (typeof payload.id !== "number" || !this.pending.has(payload.id)) {
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const handlers = this.pending.get(payload.id);
|
|
436
|
+
this.pending.delete(payload.id);
|
|
437
|
+
if (payload.error) {
|
|
438
|
+
handlers.reject(new Error(payload.error.message ?? "Unknown MCP bridge error"));
|
|
439
|
+
} else {
|
|
440
|
+
handlers.resolve(payload.result);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function buildBridgeCommand(commandTemplate, endpoint, bridgeConfiguration = {}) {
|
|
447
|
+
const template = commandTemplate?.trim();
|
|
448
|
+
if (!template) {
|
|
449
|
+
throw new Error(`${BRIDGE_COMMAND_ENV} is empty`);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const parts = splitCommand(template);
|
|
453
|
+
if (parts.length === 0) {
|
|
454
|
+
throw new Error(`${BRIDGE_COMMAND_ENV} is empty`);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const substituted = parts.map((part) => part.replaceAll("{url}", endpoint));
|
|
458
|
+
if (!template.includes("{url}")) {
|
|
459
|
+
substituted.push(endpoint);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const [command, ...args] = substituted;
|
|
463
|
+
const allowedCommands = new Set(bridgeConfiguration.allowedCommands ?? []);
|
|
464
|
+
const commandName = path.basename(command);
|
|
465
|
+
if (!allowedCommands.has(command) && !allowedCommands.has(commandName)) {
|
|
466
|
+
throw new Error(
|
|
467
|
+
`${BRIDGE_COMMAND_ENV} command is not allowlisted: ${command}. ` +
|
|
468
|
+
"Add its exact command or basename to MA_MCP_REMOTE_BRIDGE_ALLOWLIST or mcp/bridge.json.",
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
return { command, args };
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
async function writeBridgeReceipt(endpoint, command, args, details) {
|
|
475
|
+
const root = process.env.MA_ROOT ?? process.cwd();
|
|
476
|
+
const receiptDir = path.join(root, ".ma", "evidence", "mcp-bridge-receipts");
|
|
477
|
+
const safeError = details.error
|
|
478
|
+
? maskSensitiveText(String(details.error)).sanitizedText
|
|
479
|
+
: undefined;
|
|
480
|
+
const safeStderr = details.stderr
|
|
481
|
+
? maskSensitiveText(String(details.stderr)).sanitizedText
|
|
482
|
+
: undefined;
|
|
483
|
+
await fs.mkdir(receiptDir, { recursive: true }).catch(() => {});
|
|
484
|
+
await fs
|
|
485
|
+
.writeFile(
|
|
486
|
+
path.join(receiptDir, `bridge-${Date.now()}-${process.pid}.json`),
|
|
487
|
+
`${JSON.stringify(
|
|
488
|
+
{
|
|
489
|
+
schemaVersion: "0.1.0",
|
|
490
|
+
record_type: "mcp_bridge_receipt",
|
|
491
|
+
generatedAt: new Date().toISOString(),
|
|
492
|
+
endpoint,
|
|
493
|
+
command,
|
|
494
|
+
args,
|
|
495
|
+
...details,
|
|
496
|
+
...(safeError ? { error: safeError } : {}),
|
|
497
|
+
...(safeStderr ? { stderr: safeStderr } : {}),
|
|
498
|
+
},
|
|
499
|
+
null,
|
|
500
|
+
)}\n`,
|
|
501
|
+
{ mode: 0o600 },
|
|
502
|
+
)
|
|
503
|
+
.catch(() => {});
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
async function readResponsePreview(response) {
|
|
507
|
+
try {
|
|
508
|
+
return (await response.text()).replace(/\s+/g, " ").trim().slice(0, 240);
|
|
509
|
+
} catch {
|
|
510
|
+
return "";
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function splitCommand(command) {
|
|
515
|
+
const parts = [];
|
|
516
|
+
let current = "";
|
|
517
|
+
let quote = null;
|
|
518
|
+
let escaping = false;
|
|
519
|
+
|
|
520
|
+
for (const char of command) {
|
|
521
|
+
if (escaping) {
|
|
522
|
+
current += char;
|
|
523
|
+
escaping = false;
|
|
524
|
+
continue;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (char === "\\") {
|
|
528
|
+
escaping = true;
|
|
529
|
+
continue;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
if (quote) {
|
|
533
|
+
if (char === quote) {
|
|
534
|
+
quote = null;
|
|
535
|
+
} else {
|
|
536
|
+
current += char;
|
|
537
|
+
}
|
|
538
|
+
continue;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
if (char === "'" || char === '"') {
|
|
542
|
+
quote = char;
|
|
543
|
+
continue;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
if (/\s/.test(char)) {
|
|
547
|
+
if (current) {
|
|
548
|
+
parts.push(current);
|
|
549
|
+
current = "";
|
|
550
|
+
}
|
|
551
|
+
continue;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
current += char;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
if (escaping) {
|
|
558
|
+
current += "\\";
|
|
559
|
+
}
|
|
560
|
+
if (quote) {
|
|
561
|
+
throw new Error(`${BRIDGE_COMMAND_ENV} has an unterminated quote`);
|
|
562
|
+
}
|
|
563
|
+
if (current) {
|
|
564
|
+
parts.push(current);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
return parts;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
function buildBridgeEnv() {
|
|
571
|
+
const passthroughKeys = new Set([
|
|
572
|
+
"PATH",
|
|
573
|
+
"HOME",
|
|
574
|
+
"USERPROFILE",
|
|
575
|
+
"TMPDIR",
|
|
576
|
+
"TMP",
|
|
577
|
+
"TEMP",
|
|
578
|
+
"LANG",
|
|
579
|
+
"LC_ALL",
|
|
580
|
+
"SYSTEMROOT",
|
|
581
|
+
"COMSPEC",
|
|
582
|
+
"MA_ROOT",
|
|
583
|
+
]);
|
|
584
|
+
const env = {};
|
|
585
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
586
|
+
if (passthroughKeys.has(key)) {
|
|
587
|
+
env[key] = value;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
return env;
|
|
591
|
+
}
|
|
592
|
+
|
|
174
593
|
export function parseSseEvent(rawEvent) {
|
|
175
594
|
const event = { event: "message", data: "" };
|
|
176
595
|
for (const line of rawEvent.split("\n")) {
|
package/src/paths.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
1
2
|
import path from "node:path";
|
|
2
3
|
import { fileURLToPath } from "node:url";
|
|
3
4
|
|
|
@@ -6,7 +7,11 @@ const __dirname = path.dirname(__filename);
|
|
|
6
7
|
|
|
7
8
|
export const packageRoot = path.resolve(__dirname, "..");
|
|
8
9
|
export function getRepoRoot() {
|
|
9
|
-
|
|
10
|
+
const root = process.env.MA_ROOT ? path.resolve(process.env.MA_ROOT) : process.cwd();
|
|
11
|
+
if (fs.realpathSync(root) !== root) {
|
|
12
|
+
throw new Error(`Repository root cannot traverse symlinks: ${root}`);
|
|
13
|
+
}
|
|
14
|
+
return root;
|
|
10
15
|
}
|
|
11
16
|
|
|
12
17
|
export function getRuntimeRoot() {
|
|
@@ -45,6 +50,10 @@ export function getMcpLocalCapabilitiesPath() {
|
|
|
45
50
|
return path.join(getMcpRootPath(), "local-capabilities.json");
|
|
46
51
|
}
|
|
47
52
|
|
|
53
|
+
export function getRuntimeMcpPolicyPath() {
|
|
54
|
+
return getRuntimeWritePath(".mcp.json");
|
|
55
|
+
}
|
|
56
|
+
|
|
48
57
|
export function getBundledNativePlaybooksPath() {
|
|
49
58
|
return getBundledMcpPath("native-playbooks.json");
|
|
50
59
|
}
|
package/src/policy.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export function validateReleaseOrigin(originBranch) {
|
|
2
|
-
if (originBranch === "
|
|
2
|
+
if (originBranch === "dev") {
|
|
3
3
|
return true;
|
|
4
4
|
}
|
|
5
5
|
|
|
@@ -15,9 +15,9 @@ export function rejectsDirectProdPromotion(originBranch) {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export function validateMergeTarget(sourceBranch, targetBranch) {
|
|
18
|
-
return sourceBranch.startsWith("feature/") && targetBranch === "
|
|
18
|
+
return sourceBranch.startsWith("feature/") && targetBranch === "dev";
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export function canMarkBuildDone(releaseState) {
|
|
22
|
-
return
|
|
22
|
+
return releaseState.build_status === "DONE";
|
|
23
23
|
}
|
package/src/prelaunch.js
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
5
|
+
import readline from "node:readline/promises";
|
|
6
|
+
import { agentRegistry, detectInstalled, listAgents } from "./agents.js";
|
|
7
|
+
import {
|
|
8
|
+
createSkillCompatibilityPayload,
|
|
9
|
+
writeSkillCompatibilityExport,
|
|
10
|
+
} from "./runtime/skills-registry-export.js";
|
|
11
|
+
import { ensureSkillsInstalled, ensureSupportBundleInstalled } from "./skill-installer.js";
|
|
12
|
+
|
|
13
|
+
const selectionFile = ".ma/prelaunch.json";
|
|
14
|
+
const projectSignals = {
|
|
15
|
+
codex: [".codex", "AGENTS.md"],
|
|
16
|
+
opencode: [".opencode"],
|
|
17
|
+
"gemini-cli": [".gemini", "GEMINI.md"],
|
|
18
|
+
amp: [".amp"],
|
|
19
|
+
cursor: [".cursor"],
|
|
20
|
+
windsurf: [".windsurf"],
|
|
21
|
+
cline: [".cline"],
|
|
22
|
+
continue: [".continue"],
|
|
23
|
+
roo: [".roo"],
|
|
24
|
+
"kiro-cli": [".kiro"],
|
|
25
|
+
junie: [".junie"],
|
|
26
|
+
};
|
|
27
|
+
const globalSignals = {
|
|
28
|
+
codex: ["~/.codex"],
|
|
29
|
+
opencode: ["~/.config/opencode"],
|
|
30
|
+
"gemini-cli": ["~/.gemini"],
|
|
31
|
+
amp: ["~/.config/agents"],
|
|
32
|
+
cursor: ["~/.cursor"],
|
|
33
|
+
windsurf: ["~/.codeium/windsurf"],
|
|
34
|
+
cline: ["~/.cline"],
|
|
35
|
+
continue: ["~/.continue"],
|
|
36
|
+
roo: ["~/.roo"],
|
|
37
|
+
"kiro-cli": ["~/.kiro"],
|
|
38
|
+
junie: ["~/.junie"],
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
function expandHome(value) {
|
|
42
|
+
return value.startsWith("~/") ? path.join(os.homedir(), value.slice(2)) : value;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function exists(target) {
|
|
46
|
+
try {
|
|
47
|
+
await fs.access(target);
|
|
48
|
+
return true;
|
|
49
|
+
} catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function detectPrelaunchTargets(cwd = process.cwd()) {
|
|
55
|
+
const targets = listAgents().map((agent) => {
|
|
56
|
+
const definition = agentRegistry[agent.id];
|
|
57
|
+
const projectPath =
|
|
58
|
+
definition?.isUniversal && agent.id === "universal"
|
|
59
|
+
? path.join(cwd, ".agents")
|
|
60
|
+
: path.join(cwd, agent.skillsDir);
|
|
61
|
+
const globalPath = expandHome(agent.globalSkillsDir);
|
|
62
|
+
return {
|
|
63
|
+
id: agent.id,
|
|
64
|
+
displayName: agent.displayName,
|
|
65
|
+
surface: agent.surface,
|
|
66
|
+
project: projectPath,
|
|
67
|
+
global: globalPath,
|
|
68
|
+
projectSignals: (projectSignals[agent.id] ?? []).map((signal) => path.join(cwd, signal)),
|
|
69
|
+
globalSignals: (globalSignals[agent.id] ?? []).map(expandHome),
|
|
70
|
+
projectDetected: false,
|
|
71
|
+
globalDetected: false,
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
for (const target of targets) {
|
|
76
|
+
const installed = target.surface === "cli" && detectInstalled(target.id).installed;
|
|
77
|
+
target.projectDetected =
|
|
78
|
+
(target.id === "universal" && (await exists(target.project))) ||
|
|
79
|
+
(await Promise.all(target.projectSignals.map(exists))).some(Boolean);
|
|
80
|
+
target.globalDetected =
|
|
81
|
+
installed ||
|
|
82
|
+
(target.id === "universal" && (await exists(target.global))) ||
|
|
83
|
+
(await Promise.all(target.globalSignals.map(exists))).some(Boolean);
|
|
84
|
+
delete target.projectSignals;
|
|
85
|
+
delete target.globalSignals;
|
|
86
|
+
}
|
|
87
|
+
return targets;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function readSelection(cwd) {
|
|
91
|
+
try {
|
|
92
|
+
const value = JSON.parse(await fs.readFile(path.join(cwd, selectionFile), "utf8"));
|
|
93
|
+
if (
|
|
94
|
+
(value.scope === "project" || value.scope === "global") &&
|
|
95
|
+
Array.isArray(value.targets) &&
|
|
96
|
+
value.targets.every((target) => typeof target === "string" && agentRegistry[target])
|
|
97
|
+
) {
|
|
98
|
+
return value;
|
|
99
|
+
}
|
|
100
|
+
} catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export async function choosePrelaunchInstall({
|
|
107
|
+
cwd = process.cwd(),
|
|
108
|
+
interactive = Boolean(input.isTTY && output.isTTY),
|
|
109
|
+
} = {}) {
|
|
110
|
+
if (!interactive || process.env.MA_SKIP_PRELAUNCH === "1") return null;
|
|
111
|
+
const previous = await readSelection(cwd);
|
|
112
|
+
if (previous) return previous;
|
|
113
|
+
|
|
114
|
+
const detected = await detectPrelaunchTargets(cwd);
|
|
115
|
+
const detectedIds = detected
|
|
116
|
+
.filter((target) => target.projectDetected || target.globalDetected)
|
|
117
|
+
.map((target) => target.id);
|
|
118
|
+
const suggested = detectedIds.length > 0 ? detectedIds : ["codex"];
|
|
119
|
+
console.log("Meta-Architect pre-launch setup");
|
|
120
|
+
console.log("Detected targets:");
|
|
121
|
+
for (const target of detected) {
|
|
122
|
+
if (target.projectDetected || target.globalDetected) {
|
|
123
|
+
console.log(`- ${target.id}: ${target.projectDetected ? "project" : "user"}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const rl = readline.createInterface({ input, output });
|
|
127
|
+
try {
|
|
128
|
+
const answer = await rl.question(
|
|
129
|
+
`Install scope and targets [project:${suggested.join(",")}]: `,
|
|
130
|
+
);
|
|
131
|
+
const value = answer.trim() || `project:${suggested.join(",")}`;
|
|
132
|
+
const [scope, targetValue] = value.split(":", 2);
|
|
133
|
+
const targets = (targetValue || "codex")
|
|
134
|
+
.split(",")
|
|
135
|
+
.map((target) => target.trim())
|
|
136
|
+
.filter((target, index, values) => agentRegistry[target] && values.indexOf(target) === index);
|
|
137
|
+
if (!(scope === "project" || scope === "global") || targets.length === 0) {
|
|
138
|
+
throw new Error("Use project:codex,cursor or global:codex,cursor");
|
|
139
|
+
}
|
|
140
|
+
return { schemaVersion: "0.1.0", scope, targets };
|
|
141
|
+
} finally {
|
|
142
|
+
rl.close();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export async function installPrelaunchSelection(selection, cwd = process.cwd()) {
|
|
147
|
+
if (
|
|
148
|
+
!selection ||
|
|
149
|
+
!["project", "global"].includes(selection.scope) ||
|
|
150
|
+
!Array.isArray(selection.targets) ||
|
|
151
|
+
selection.targets.length === 0 ||
|
|
152
|
+
selection.targets.some((target) => typeof target !== "string" || !agentRegistry[target])
|
|
153
|
+
) {
|
|
154
|
+
throw new Error("Invalid pre-launch selection");
|
|
155
|
+
}
|
|
156
|
+
const payload = createSkillCompatibilityPayload({
|
|
157
|
+
name: "meta-architect",
|
|
158
|
+
description: "Meta-Architect host compatibility entrypoint.",
|
|
159
|
+
capabilities: ["maestro", "agent-compatibility"],
|
|
160
|
+
});
|
|
161
|
+
const results = [];
|
|
162
|
+
if (selection.targets.includes("codex")) {
|
|
163
|
+
const skillsRoot =
|
|
164
|
+
selection.scope === "global" ? undefined : path.join(cwd, ".agents", "skills");
|
|
165
|
+
const supportRoot =
|
|
166
|
+
selection.scope === "global" ? undefined : path.join(cwd, ".ma", "support-bundle");
|
|
167
|
+
results.push(
|
|
168
|
+
await ensureSkillsInstalled({ targetRoot: skillsRoot }),
|
|
169
|
+
await ensureSupportBundleInstalled({ targetRoot: supportRoot }),
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
for (const target of selection.targets) {
|
|
173
|
+
if (selection.scope === "global" && target === "codex") continue;
|
|
174
|
+
results.push(
|
|
175
|
+
await writeSkillCompatibilityExport({
|
|
176
|
+
payload,
|
|
177
|
+
agentType: target,
|
|
178
|
+
cwd,
|
|
179
|
+
global: selection.scope === "global",
|
|
180
|
+
agentRootExists: true,
|
|
181
|
+
}),
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
await fs.mkdir(path.join(cwd, ".ma"), { recursive: true });
|
|
185
|
+
await fs.writeFile(path.join(cwd, selectionFile), `${JSON.stringify(selection, null, 2)}\n`, {
|
|
186
|
+
mode: 0o600,
|
|
187
|
+
});
|
|
188
|
+
return { selection, results };
|
|
189
|
+
}
|