@jstn-sdk/ma 0.1.12 → 0.1.13
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/.agents/plugins/marketplace.json +0 -0
- package/.codex/agents/Architect.toml +0 -2
- package/.codex/agents/Auditor.toml +0 -2
- package/.codex/agents/Builder.toml +0 -2
- package/.codex/agents/Flow.toml +0 -2
- package/.codex/agents/Sage.toml +0 -2
- package/.codex/agents/Vibe.toml +0 -2
- package/.codex/hooks.json +14 -13
- package/.codex/prompts/enforcement.md +4 -0
- package/.codex/prompts/skill-contract.md +4 -0
- package/COVERAGE.md +211 -0
- package/DEMO.md +265 -0
- package/README.md +159 -16
- package/bin/ma.js +219 -14
- 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 +4 -1
- 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 -0
- package/docs/getting-started.md +38 -22
- package/docs/installed-sdk.md +0 -0
- package/docs/mcp-setup.md +35 -0
- package/docs/onboarding.md +18 -2
- package/docs/prompt-guidance-contract.md +17 -0
- package/docs/prompt-guidance-fragments/active-autonomy-core.md +7 -0
- package/docs/qa/release-issue-gates-0.1.13.json +644 -0
- package/docs/qa/{release-readiness-0.1.12.md → release-readiness-0.1.13.md} +25 -9
- package/docs/qa/release-readiness-0.1.5.md +1 -1
- package/docs/reference/native-engineering-patterns.md +0 -0
- package/docs/reference/native-security-playbooks.md +6 -0
- package/docs/reference/native-source-selection.md +6 -0
- package/docs/reference/native-style-and-deslop.md +0 -0
- package/docs/release-spec.md +25 -9
- package/docs/skills.md +14 -1
- package/index.js +222 -2
- package/mcp/collections.json +23 -6
- package/mcp/local/code-intel.js +0 -0
- package/mcp/local/memory.js +0 -0
- package/mcp/local/playbooks.js +0 -0
- package/mcp/local/state.js +0 -0
- package/mcp/local/team-run.js +48 -1
- package/mcp/local/trace.js +0 -0
- package/mcp/local-capabilities.json +0 -0
- package/mcp/native-playbooks.json +0 -0
- package/mcp/servers.json +24 -0
- package/package.json +5 -2
- 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 +401 -0
- package/plugins/meta-architect/obsidian/manifest.json +9 -0
- package/plugins/meta-architect/obsidian/styles.css +7 -0
- package/plugins/meta-architect/skills/align/SKILL.md +0 -0
- package/plugins/meta-architect/skills/align/agents/openai.yaml +0 -0
- package/plugins/meta-architect/skills/align/references/shared-language.md +0 -0
- package/plugins/meta-architect/skills/arch/SKILL.md +2 -0
- package/plugins/meta-architect/skills/arch/agents/openai.yaml +0 -0
- package/plugins/meta-architect/skills/build/SKILL.md +3 -0
- package/plugins/meta-architect/skills/build/agents/openai.yaml +0 -0
- package/plugins/meta-architect/skills/cleanup/SKILL.md +0 -0
- package/plugins/meta-architect/skills/cleanup/agents/openai.yaml +0 -0
- package/plugins/meta-architect/skills/cleanup/references/style-and-deslop.md +0 -0
- package/plugins/meta-architect/skills/diagnose/SKILL.md +0 -0
- package/plugins/meta-architect/skills/diagnose/agents/openai.yaml +0 -0
- package/plugins/meta-architect/skills/flow/SKILL.md +2 -0
- package/plugins/meta-architect/skills/flow/agents/openai.yaml +0 -0
- package/plugins/meta-architect/skills/maestro/SKILL.md +0 -0
- package/plugins/meta-architect/skills/maestro/agents/openai.yaml +0 -0
- package/plugins/meta-architect/skills/maestro/references/core-release-rules.md +0 -0
- package/plugins/meta-architect/skills/maestro/references/native-ingest-map.md +0 -0
- package/plugins/meta-architect/skills/sage/SKILL.md +2 -0
- package/plugins/meta-architect/skills/sage/agents/openai.yaml +0 -0
- package/plugins/meta-architect/skills/sage/references/source-selection.md +8 -0
- package/plugins/meta-architect/skills/tdd/SKILL.md +0 -0
- package/plugins/meta-architect/skills/tdd/agents/openai.yaml +0 -0
- package/plugins/meta-architect/skills/vet/SKILL.md +2 -0
- package/plugins/meta-architect/skills/vet/agents/openai.yaml +0 -0
- package/plugins/meta-architect/skills/vet/references/security-playbooks.md +7 -0
- package/plugins/meta-architect/skills/vibe/SKILL.md +2 -0
- package/plugins/meta-architect/skills/vibe/agents/openai.yaml +0 -0
- package/scripts/active-autonomy-hook.mjs +96 -0
- package/scripts/biome-staged.js +0 -0
- package/scripts/build-linux-packages.mjs +0 -0
- package/scripts/doctor.js +17 -4
- package/scripts/install.sh +28 -0
- package/scripts/linux-package-lib.mjs +0 -0
- package/scripts/linux-package-smoke.mjs +0 -0
- package/scripts/plugin-sync.js +0 -0
- package/scripts/postinstall.js +0 -0
- package/scripts/ralph/prompt.md +35 -0
- package/scripts/release-metadata.js +0 -0
- package/scripts/release-sync.js +0 -0
- package/scripts/release-verify.js +166 -1
- package/scripts/setup-npmrc.js +0 -0
- package/skills/align/SKILL.md +0 -0
- package/skills/align/agents/openai.yaml +0 -0
- package/skills/align/references/shared-language.md +0 -0
- package/skills/arch/SKILL.md +2 -0
- package/skills/arch/agents/openai.yaml +0 -0
- package/skills/build/SKILL.md +3 -0
- package/skills/build/agents/openai.yaml +0 -0
- package/skills/cleanup/SKILL.md +0 -0
- package/skills/cleanup/agents/openai.yaml +0 -0
- package/skills/cleanup/references/style-and-deslop.md +0 -0
- package/skills/diagnose/SKILL.md +0 -0
- package/skills/diagnose/agents/openai.yaml +0 -0
- package/skills/flow/SKILL.md +2 -0
- package/skills/flow/agents/openai.yaml +0 -0
- package/skills/maestro/SKILL.md +0 -0
- package/skills/maestro/agents/openai.yaml +0 -0
- package/skills/maestro/references/core-release-rules.md +0 -0
- package/skills/maestro/references/native-ingest-map.md +0 -0
- package/skills/sage/SKILL.md +2 -0
- package/skills/sage/agents/openai.yaml +0 -0
- package/skills/sage/references/source-selection.md +8 -0
- package/skills/tdd/SKILL.md +0 -0
- package/skills/tdd/agents/openai.yaml +0 -0
- package/skills/vet/SKILL.md +2 -0
- package/skills/vet/agents/openai.yaml +0 -0
- package/skills/vet/references/security-playbooks.md +7 -0
- package/skills/vibe/SKILL.md +2 -0
- package/skills/vibe/agents/openai.yaml +0 -0
- package/src/bootstrap.js +17 -0
- package/src/build-gate.js +2 -2
- package/src/launcher.js +4 -0
- package/src/mcp-live-client.js +289 -3
- package/src/paths.js +4 -0
- package/src/policy.js +1 -1
- package/src/release-issue-gates.js +190 -0
- package/src/runtime/active-autonomy-core.js +208 -0
- package/src/runtime/alignment-sentinel.js +165 -0
- package/src/runtime/architect-review.js +88 -0
- package/src/runtime/build-readiness.js +28 -4
- package/src/runtime/code-graph-rehearse.js +113 -0
- package/src/runtime/context-economy-core.js +276 -0
- package/src/runtime/continuity-notes.js +0 -0
- package/src/runtime/core-source-ingest.js +379 -0
- package/src/runtime/detached-provider.js +74 -0
- package/src/runtime/environment-awareness-core.js +460 -0
- package/src/runtime/exposure-catalog.js +276 -0
- package/src/runtime/guidance-stack.js +0 -0
- package/src/runtime/helper-orchestration-core.js +307 -0
- package/src/runtime/learning-loop-core.js +238 -0
- package/src/runtime/maestro-events.js +18 -0
- package/src/runtime/maestro-manager.js +19 -0
- package/src/runtime/maestro-state.js +125 -0
- package/src/runtime/mcp-policy.js +192 -0
- package/src/runtime/obsidian-integration-core.js +851 -0
- package/src/runtime/obsidian-plugin-bridge.js +739 -0
- package/src/runtime/orchestrator.js +0 -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 +174 -0
- package/src/runtime/runtime-state.js +843 -2
- package/src/runtime/semantic-recording-core.js +147 -0
- package/src/runtime/signal-hooks.js +15 -0
- package/src/runtime/skills-registry-export.js +670 -0
- package/src/runtime/startup-path.js +0 -0
- package/src/runtime/universal-plugin-broker-core.js +575 -0
- package/src/runtime/workspace-intelligence-runtime.js +674 -0
- package/src/runtime/workspace-virtualizer.js +102 -0
- package/src/runtime/workspaces.js +0 -0
- package/src/runtime-artifacts.js +225 -62
- package/src/skill-installer.js +0 -0
- package/src/skills.js +876 -84
package/src/mcp-live-client.js
CHANGED
|
@@ -1,3 +1,54 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
const GITMCP_ENDPOINT_PATTERN = /^https:\/\/gitmcp\.io\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
|
|
4
|
+
const BRIDGE_COMMAND_ENV = "MA_MCP_REMOTE_BRIDGE_CMD";
|
|
5
|
+
const REQUEST_TIMEOUT_ENV = "MA_MCP_REQUEST_TIMEOUT_MS";
|
|
6
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = 15000;
|
|
7
|
+
|
|
8
|
+
function readRequestTimeoutMs() {
|
|
9
|
+
const raw = process.env[REQUEST_TIMEOUT_ENV]?.trim();
|
|
10
|
+
if (!raw) return DEFAULT_REQUEST_TIMEOUT_MS;
|
|
11
|
+
|
|
12
|
+
const parsed = Number(raw);
|
|
13
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
14
|
+
return DEFAULT_REQUEST_TIMEOUT_MS;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return Math.max(50, Math.floor(parsed));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class McpRemoteTransportRequiredError extends Error {
|
|
21
|
+
constructor(endpoint, status, bodyPreview = "") {
|
|
22
|
+
const bridgeHint =
|
|
23
|
+
`Set ${BRIDGE_COMMAND_ENV} to a trusted stdio bridge command with {url}, ` +
|
|
24
|
+
"for example a preinstalled mcp-remote wrapper, then rerun $sage.";
|
|
25
|
+
const bodyHint = bodyPreview ? ` Response preview: ${bodyPreview}` : "";
|
|
26
|
+
super(
|
|
27
|
+
`Remote MCP endpoint requires a bridge transport: ${endpoint} returned HTTP ${status}. ${bridgeHint}${bodyHint}`,
|
|
28
|
+
);
|
|
29
|
+
this.name = "McpRemoteTransportRequiredError";
|
|
30
|
+
this.endpoint = endpoint;
|
|
31
|
+
this.status = status;
|
|
32
|
+
this.bridgeEnv = BRIDGE_COMMAND_ENV;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function isGitMcpEndpoint(endpoint) {
|
|
37
|
+
return GITMCP_ENDPOINT_PATTERN.test(endpoint);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function hasConfiguredMcpRemoteBridge() {
|
|
41
|
+
return Boolean(process.env[BRIDGE_COMMAND_ENV]?.trim());
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function createMcpLiveClient(endpoint) {
|
|
45
|
+
if (hasConfiguredMcpRemoteBridge()) {
|
|
46
|
+
return new McpStdioBridgeClient(endpoint, process.env[BRIDGE_COMMAND_ENV]);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return new McpSseClient(endpoint);
|
|
50
|
+
}
|
|
51
|
+
|
|
1
52
|
export class McpSseClient {
|
|
2
53
|
constructor(baseUrl) {
|
|
3
54
|
this.baseUrl = baseUrl;
|
|
@@ -17,6 +68,10 @@ export class McpSseClient {
|
|
|
17
68
|
});
|
|
18
69
|
|
|
19
70
|
if (!response.ok || !response.body) {
|
|
71
|
+
const bodyPreview = await readResponsePreview(response);
|
|
72
|
+
if (isGitMcpEndpoint(this.baseUrl) && response.status === 405) {
|
|
73
|
+
throw new McpRemoteTransportRequiredError(this.baseUrl, response.status, bodyPreview);
|
|
74
|
+
}
|
|
20
75
|
throw new Error(`Failed to open MCP SSE stream: ${response.status}`);
|
|
21
76
|
}
|
|
22
77
|
|
|
@@ -26,7 +81,7 @@ export class McpSseClient {
|
|
|
26
81
|
5000,
|
|
27
82
|
"Timed out waiting for MCP endpoint event",
|
|
28
83
|
);
|
|
29
|
-
if (
|
|
84
|
+
if (endpointEvent?.event !== "endpoint") {
|
|
30
85
|
throw new Error("MCP server did not provide an endpoint event");
|
|
31
86
|
}
|
|
32
87
|
|
|
@@ -38,7 +93,7 @@ export class McpSseClient {
|
|
|
38
93
|
capabilities: {},
|
|
39
94
|
clientInfo: {
|
|
40
95
|
name: "meta-architect",
|
|
41
|
-
version: "0.1.
|
|
96
|
+
version: "0.1.13-dev",
|
|
42
97
|
},
|
|
43
98
|
});
|
|
44
99
|
|
|
@@ -52,7 +107,7 @@ export class McpSseClient {
|
|
|
52
107
|
const timeout = setTimeout(() => {
|
|
53
108
|
this.pending.delete(id);
|
|
54
109
|
reject(new Error(`Timed out waiting for MCP response to ${method}`));
|
|
55
|
-
},
|
|
110
|
+
}, readRequestTimeoutMs());
|
|
56
111
|
|
|
57
112
|
this.pending.set(id, {
|
|
58
113
|
resolve: (payload) => {
|
|
@@ -171,6 +226,237 @@ export class McpSseClient {
|
|
|
171
226
|
}
|
|
172
227
|
}
|
|
173
228
|
|
|
229
|
+
export class McpStdioBridgeClient {
|
|
230
|
+
constructor(endpoint, commandTemplate, spawnImpl = spawn) {
|
|
231
|
+
this.endpoint = endpoint;
|
|
232
|
+
this.commandTemplate = commandTemplate;
|
|
233
|
+
this.spawnImpl = spawnImpl;
|
|
234
|
+
this.child = null;
|
|
235
|
+
this.buffer = "";
|
|
236
|
+
this.pending = new Map();
|
|
237
|
+
this.nextId = 1;
|
|
238
|
+
this.stderr = "";
|
|
239
|
+
this.closed = false;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async connect() {
|
|
243
|
+
const { command, args } = buildBridgeCommand(this.commandTemplate, this.endpoint);
|
|
244
|
+
this.child = this.spawnImpl(command, args, {
|
|
245
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
246
|
+
env: process.env,
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
this.child.stdout.setEncoding("utf8");
|
|
250
|
+
this.child.stderr.setEncoding("utf8");
|
|
251
|
+
this.child.stdout.on("data", (chunk) => this.#handleStdout(chunk));
|
|
252
|
+
this.child.stderr.on("data", (chunk) => {
|
|
253
|
+
this.stderr = `${this.stderr}${chunk}`.slice(-2000);
|
|
254
|
+
});
|
|
255
|
+
this.child.on("error", (error) => {
|
|
256
|
+
this.closed = true;
|
|
257
|
+
for (const [, handlers] of this.pending) {
|
|
258
|
+
handlers.reject(new Error(`MCP bridge failed to start: ${error.message}`));
|
|
259
|
+
}
|
|
260
|
+
this.pending.clear();
|
|
261
|
+
});
|
|
262
|
+
this.child.on("exit", (code, signal) => {
|
|
263
|
+
this.closed = true;
|
|
264
|
+
const reason = signal
|
|
265
|
+
? `MCP bridge exited with signal ${signal}`
|
|
266
|
+
: `MCP bridge exited with code ${code}`;
|
|
267
|
+
for (const [, handlers] of this.pending) {
|
|
268
|
+
handlers.reject(new Error(`${reason}${this.stderr ? `: ${this.stderr.trim()}` : ""}`));
|
|
269
|
+
}
|
|
270
|
+
this.pending.clear();
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
const initResult = await this.request("initialize", {
|
|
274
|
+
protocolVersion: "2025-03-26",
|
|
275
|
+
capabilities: {},
|
|
276
|
+
clientInfo: {
|
|
277
|
+
name: "meta-architect",
|
|
278
|
+
version: "0.1.13-dev",
|
|
279
|
+
},
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
await this.notify("notifications/initialized");
|
|
283
|
+
return initResult;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
async request(method, params = {}) {
|
|
287
|
+
const id = this.nextId++;
|
|
288
|
+
const responsePromise = new Promise((resolve, reject) => {
|
|
289
|
+
const timeout = setTimeout(() => {
|
|
290
|
+
this.pending.delete(id);
|
|
291
|
+
reject(new Error(`Timed out waiting for MCP bridge response to ${method}`));
|
|
292
|
+
}, readRequestTimeoutMs());
|
|
293
|
+
|
|
294
|
+
this.pending.set(id, {
|
|
295
|
+
resolve: (payload) => {
|
|
296
|
+
clearTimeout(timeout);
|
|
297
|
+
resolve(payload);
|
|
298
|
+
},
|
|
299
|
+
reject: (error) => {
|
|
300
|
+
clearTimeout(timeout);
|
|
301
|
+
reject(error);
|
|
302
|
+
},
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
this.#send({
|
|
307
|
+
jsonrpc: "2.0",
|
|
308
|
+
id,
|
|
309
|
+
method,
|
|
310
|
+
params,
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
return responsePromise;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
async notify(method, params = {}) {
|
|
317
|
+
this.#send({
|
|
318
|
+
jsonrpc: "2.0",
|
|
319
|
+
method,
|
|
320
|
+
params,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
async close() {
|
|
325
|
+
if (!this.child || this.closed) return;
|
|
326
|
+
this.child.stdin.end();
|
|
327
|
+
this.child.kill();
|
|
328
|
+
await new Promise((resolve) => {
|
|
329
|
+
const timeout = setTimeout(resolve, 1000);
|
|
330
|
+
this.child.once("exit", () => {
|
|
331
|
+
clearTimeout(timeout);
|
|
332
|
+
resolve();
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
#send(message) {
|
|
338
|
+
if (!this.child?.stdin.writable) {
|
|
339
|
+
throw new Error("MCP bridge is not writable");
|
|
340
|
+
}
|
|
341
|
+
this.child.stdin.write(`${JSON.stringify(message)}\n`);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
#handleStdout(chunk) {
|
|
345
|
+
this.buffer += chunk;
|
|
346
|
+
while (true) {
|
|
347
|
+
const newlineIndex = this.buffer.indexOf("\n");
|
|
348
|
+
if (newlineIndex === -1) return;
|
|
349
|
+
|
|
350
|
+
const line = this.buffer.slice(0, newlineIndex).trim();
|
|
351
|
+
this.buffer = this.buffer.slice(newlineIndex + 1);
|
|
352
|
+
if (!line?.startsWith("{")) continue;
|
|
353
|
+
|
|
354
|
+
let payload;
|
|
355
|
+
try {
|
|
356
|
+
payload = JSON.parse(line);
|
|
357
|
+
} catch {
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (typeof payload.id !== "number" || !this.pending.has(payload.id)) {
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const handlers = this.pending.get(payload.id);
|
|
366
|
+
this.pending.delete(payload.id);
|
|
367
|
+
if (payload.error) {
|
|
368
|
+
handlers.reject(new Error(payload.error.message ?? "Unknown MCP bridge error"));
|
|
369
|
+
} else {
|
|
370
|
+
handlers.resolve(payload.result);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function buildBridgeCommand(commandTemplate, endpoint) {
|
|
377
|
+
const template = commandTemplate?.trim();
|
|
378
|
+
if (!template) {
|
|
379
|
+
throw new Error(`${BRIDGE_COMMAND_ENV} is empty`);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const parts = splitCommand(template);
|
|
383
|
+
if (parts.length === 0) {
|
|
384
|
+
throw new Error(`${BRIDGE_COMMAND_ENV} is empty`);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const substituted = parts.map((part) => part.replaceAll("{url}", endpoint));
|
|
388
|
+
if (!template.includes("{url}")) {
|
|
389
|
+
substituted.push(endpoint);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const [command, ...args] = substituted;
|
|
393
|
+
return { command, args };
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
async function readResponsePreview(response) {
|
|
397
|
+
try {
|
|
398
|
+
return (await response.text()).replace(/\s+/g, " ").trim().slice(0, 240);
|
|
399
|
+
} catch {
|
|
400
|
+
return "";
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function splitCommand(command) {
|
|
405
|
+
const parts = [];
|
|
406
|
+
let current = "";
|
|
407
|
+
let quote = null;
|
|
408
|
+
let escaping = false;
|
|
409
|
+
|
|
410
|
+
for (const char of command) {
|
|
411
|
+
if (escaping) {
|
|
412
|
+
current += char;
|
|
413
|
+
escaping = false;
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (char === "\\") {
|
|
418
|
+
escaping = true;
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (quote) {
|
|
423
|
+
if (char === quote) {
|
|
424
|
+
quote = null;
|
|
425
|
+
} else {
|
|
426
|
+
current += char;
|
|
427
|
+
}
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
if (char === "'" || char === '"') {
|
|
432
|
+
quote = char;
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (/\s/.test(char)) {
|
|
437
|
+
if (current) {
|
|
438
|
+
parts.push(current);
|
|
439
|
+
current = "";
|
|
440
|
+
}
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
current += char;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if (escaping) {
|
|
448
|
+
current += "\\";
|
|
449
|
+
}
|
|
450
|
+
if (quote) {
|
|
451
|
+
throw new Error(`${BRIDGE_COMMAND_ENV} has an unterminated quote`);
|
|
452
|
+
}
|
|
453
|
+
if (current) {
|
|
454
|
+
parts.push(current);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
return parts;
|
|
458
|
+
}
|
|
459
|
+
|
|
174
460
|
export function parseSseEvent(rawEvent) {
|
|
175
461
|
const event = { event: "message", data: "" };
|
|
176
462
|
for (const line of rawEvent.split("\n")) {
|
package/src/paths.js
CHANGED
|
@@ -45,6 +45,10 @@ export function getMcpLocalCapabilitiesPath() {
|
|
|
45
45
|
return path.join(getMcpRootPath(), "local-capabilities.json");
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
export function getRuntimeMcpPolicyPath() {
|
|
49
|
+
return getRuntimeWritePath(".mcp.json");
|
|
50
|
+
}
|
|
51
|
+
|
|
48
52
|
export function getBundledNativePlaybooksPath() {
|
|
49
53
|
return getBundledMcpPath("native-playbooks.json");
|
|
50
54
|
}
|
package/src/policy.js
CHANGED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
const ALLOWED_STATUSES = new Set(["pending", "in_progress", "blocked", "failed", "passed"]);
|
|
2
|
+
const REQUIRED_PROOF_FIELDS = [
|
|
3
|
+
"implementationEvidence",
|
|
4
|
+
"verificationEvidence",
|
|
5
|
+
"productionEvidence",
|
|
6
|
+
];
|
|
7
|
+
|
|
8
|
+
function isNonEmptyString(value) {
|
|
9
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function hasStringItems(value) {
|
|
13
|
+
return Array.isArray(value) && value.length > 0 && value.every(isNonEmptyString);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function validateReleaseIssueGates(document, options = {}) {
|
|
17
|
+
const version = options.version;
|
|
18
|
+
const requirePassed = options.requirePassed ?? true;
|
|
19
|
+
const errors = [];
|
|
20
|
+
|
|
21
|
+
if (!document || typeof document !== "object" || Array.isArray(document)) {
|
|
22
|
+
return {
|
|
23
|
+
valid: false,
|
|
24
|
+
errors: ["release issue gate document must be an object"],
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const expectedTag = version ? `v${version}` : document.releaseTag;
|
|
29
|
+
|
|
30
|
+
if (document.schemaVersion !== "1.0.0") {
|
|
31
|
+
errors.push("schemaVersion must be 1.0.0");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (version && document.releaseVersion !== version) {
|
|
35
|
+
errors.push(`releaseVersion must match package version ${version}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!isNonEmptyString(document.releaseTag)) {
|
|
39
|
+
errors.push("releaseTag is required");
|
|
40
|
+
} else if (expectedTag && document.releaseTag !== expectedTag) {
|
|
41
|
+
errors.push(`releaseTag must be ${expectedTag}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (document.passContract?.allIssuesMustPassProduction !== true) {
|
|
45
|
+
errors.push("passContract.allIssuesMustPassProduction must be true");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (document.passContract?.allIssuesMustHaveLabels !== true) {
|
|
49
|
+
errors.push("passContract.allIssuesMustHaveLabels must be true");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!Array.isArray(document.issues) || document.issues.length === 0) {
|
|
53
|
+
errors.push("issues must be a non-empty array");
|
|
54
|
+
return { valid: errors.length === 0, errors };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const seen = new Set();
|
|
58
|
+
for (const issue of document.issues) {
|
|
59
|
+
const issueLabel = Number.isInteger(issue?.number) ? `#${issue.number}` : "<unknown issue>";
|
|
60
|
+
|
|
61
|
+
if (!Number.isInteger(issue?.number) || issue.number <= 0) {
|
|
62
|
+
errors.push(`${issueLabel}: number must be a positive integer`);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (seen.has(issue.number)) {
|
|
67
|
+
errors.push(`${issueLabel}: duplicate issue number`);
|
|
68
|
+
}
|
|
69
|
+
seen.add(issue.number);
|
|
70
|
+
|
|
71
|
+
if (!isNonEmptyString(issue.title)) {
|
|
72
|
+
errors.push(`${issueLabel}: title is required`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!isNonEmptyString(issue.url) || !issue.url.includes(`/issues/${issue.number}`)) {
|
|
76
|
+
errors.push(`${issueLabel}: url must reference the GitHub issue`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (issue.releaseVersion !== document.releaseVersion) {
|
|
80
|
+
errors.push(`${issueLabel}: releaseVersion must match document releaseVersion`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (issue.releaseTag !== document.releaseTag) {
|
|
84
|
+
errors.push(`${issueLabel}: releaseTag must match document releaseTag`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (issue.milestone !== document.releaseTag) {
|
|
88
|
+
errors.push(`${issueLabel}: milestone must match document releaseTag`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!ALLOWED_STATUSES.has(issue.status)) {
|
|
92
|
+
errors.push(`${issueLabel}: invalid status ${issue.status}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (document.passContract?.allIssuesMustHaveLabels === true && !hasStringItems(issue.labels)) {
|
|
96
|
+
errors.push(`${issueLabel}: labels must contain at least one issue label`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!hasStringItems(issue.requiredProof)) {
|
|
100
|
+
errors.push(`${issueLabel}: requiredProof must contain at least one proof requirement`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (!isNonEmptyString(issue.loopAction)) {
|
|
104
|
+
errors.push(`${issueLabel}: loopAction is required until production proof passes`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const proof = issue.proof;
|
|
108
|
+
if (!proof || typeof proof !== "object" || Array.isArray(proof)) {
|
|
109
|
+
errors.push(`${issueLabel}: proof object is required`);
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
for (const field of REQUIRED_PROOF_FIELDS) {
|
|
114
|
+
if (!Array.isArray(proof[field])) {
|
|
115
|
+
errors.push(`${issueLabel}: proof.${field} must be an array`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (issue.status === "passed") {
|
|
120
|
+
for (const field of REQUIRED_PROOF_FIELDS) {
|
|
121
|
+
if (!hasStringItems(proof[field])) {
|
|
122
|
+
errors.push(`${issueLabel}: status passed requires non-empty proof.${field}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (requirePassed && issue.status !== "passed") {
|
|
128
|
+
errors.push(`${issueLabel}: status is ${issue.status}; continue loopAction before release`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
valid: errors.length === 0,
|
|
134
|
+
errors,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function summarizeReleaseIssueGateStatus(document, options = {}) {
|
|
139
|
+
const structural = validateReleaseIssueGates(document, {
|
|
140
|
+
...options,
|
|
141
|
+
requirePassed: false,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
if (!structural.valid) {
|
|
145
|
+
return {
|
|
146
|
+
valid: false,
|
|
147
|
+
total: 0,
|
|
148
|
+
passed: 0,
|
|
149
|
+
pending: 0,
|
|
150
|
+
inProgress: 0,
|
|
151
|
+
blocked: 0,
|
|
152
|
+
failed: 0,
|
|
153
|
+
blockers: structural.errors,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const summary = {
|
|
158
|
+
valid: true,
|
|
159
|
+
total: document.issues.length,
|
|
160
|
+
passed: 0,
|
|
161
|
+
pending: 0,
|
|
162
|
+
inProgress: 0,
|
|
163
|
+
blocked: 0,
|
|
164
|
+
failed: 0,
|
|
165
|
+
blockers: [],
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
for (const issue of document.issues) {
|
|
169
|
+
if (issue.status === "passed") {
|
|
170
|
+
summary.passed += 1;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (issue.status === "pending") {
|
|
175
|
+
summary.pending += 1;
|
|
176
|
+
} else if (issue.status === "in_progress") {
|
|
177
|
+
summary.inProgress += 1;
|
|
178
|
+
} else if (issue.status === "blocked") {
|
|
179
|
+
summary.blocked += 1;
|
|
180
|
+
} else if (issue.status === "failed") {
|
|
181
|
+
summary.failed += 1;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
summary.blockers.push(
|
|
185
|
+
`#${issue.number}: ${issue.status}; ${issue.loopAction || "continue implementation loop"}`,
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return summary;
|
|
190
|
+
}
|