@jstn-sdk/ma 0.1.13 → 0.14.1
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 +1 -0
- package/.codex/agents/Auditor.toml +1 -0
- package/.codex/agents/Builder.toml +1 -0
- package/.codex/agents/Flow.toml +1 -0
- package/.codex/agents/Sage.toml +1 -0
- package/.codex/agents/Vibe.toml +1 -0
- package/.codex/hooks.json +12 -2
- package/.codex/prompts/enforcement.md +0 -0
- package/.codex/prompts/onboarding.md +6 -1
- package/.codex/prompts/release-rules.md +0 -0
- package/.codex/prompts/skill-contract.md +0 -0
- package/COVERAGE.md +9 -9
- package/DEMO.md +15 -6
- package/LICENSE +0 -0
- package/README.md +127 -784
- package/bin/ma.js +591 -105
- package/data/clone-data.ledger.json +0 -0
- package/data/clone-data.proof.json +0 -0
- package/data/clone-data.rvf +0 -0
- package/docs/README.md +8 -2
- package/docs/assets/banner.png +0 -0
- package/docs/autonomous-tasks.md +46 -0
- package/docs/codex-integration.md +16 -0
- package/docs/getting-started.md +32 -8
- package/docs/installed-sdk.md +0 -0
- package/docs/mcp-setup.md +30 -1
- package/docs/qa/{release-issue-gates-0.1.13.json → release-issue-gates-0.14.1.json} +77 -77
- package/docs/qa/release-readiness-0.1.5.md +0 -0
- package/docs/qa/{release-readiness-0.1.13.md → release-readiness-0.14.1.md} +12 -12
- package/docs/quality.md +24 -0
- package/docs/reference/native-engineering-patterns.md +0 -0
- package/docs/reference/native-security-playbooks.md +0 -0
- package/docs/reference/native-source-selection.md +0 -0
- package/docs/reference/native-style-and-deslop.md +0 -0
- package/docs/release-spec.md +27 -14
- package/docs/skills.md +1 -1
- package/docs/vendor-plugin-publishing.md +49 -0
- package/index.js +73 -0
- package/mcp/collections.json +0 -0
- package/mcp/fallback.json +0 -0
- package/mcp/local/code-intel.js +185 -41
- package/mcp/local/context.js +180 -0
- package/mcp/local/memory.js +17 -6
- package/mcp/local/playbooks.js +0 -0
- package/mcp/local/quality.js +39 -0
- package/mcp/local/state.js +7 -3
- package/mcp/local/team-run.js +8 -3
- package/mcp/local/trace.js +0 -0
- package/mcp/local-capabilities.json +9 -0
- package/mcp/native-playbooks.json +0 -0
- package/mcp/servers.json +0 -0
- package/package.json +37 -6
- package/plugins/meta-architect/.app.json +1 -1
- package/plugins/meta-architect/.claude-plugin/plugin.json +12 -0
- package/plugins/meta-architect/.codex-plugin/plugin.json +1 -1
- package/plugins/meta-architect/.mcp.json +1 -1
- package/plugins/meta-architect/README.md +14 -2
- package/plugins/meta-architect/obsidian/main.js +137 -4
- package/plugins/meta-architect/obsidian/manifest.json +1 -1
- package/plugins/meta-architect/obsidian/styles.css +0 -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 +0 -0
- package/plugins/meta-architect/skills/arch/agents/openai.yaml +0 -0
- package/plugins/meta-architect/skills/build/SKILL.md +0 -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 +0 -0
- package/plugins/meta-architect/skills/flow/agents/openai.yaml +0 -0
- package/plugins/meta-architect/skills/maestro/SKILL.md +17 -1
- package/plugins/meta-architect/skills/maestro/agents/openai.yaml +0 -0
- package/plugins/meta-architect/skills/maestro/references/core-release-rules.md +2 -2
- package/plugins/meta-architect/skills/maestro/references/native-ingest-map.md +0 -0
- package/plugins/meta-architect/skills/sage/SKILL.md +0 -0
- package/plugins/meta-architect/skills/sage/agents/openai.yaml +0 -0
- package/plugins/meta-architect/skills/sage/references/source-selection.md +0 -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 +0 -0
- package/plugins/meta-architect/skills/vet/agents/openai.yaml +0 -0
- package/plugins/meta-architect/skills/vet/references/security-playbooks.md +0 -0
- package/plugins/meta-architect/skills/vibe/SKILL.md +0 -0
- package/plugins/meta-architect/skills/vibe/agents/openai.yaml +0 -0
- package/schemas/autonomous-task-queue.schema.json +58 -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 +141 -11
- package/scripts/biome-staged.js +0 -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 +10 -8
- package/scripts/install.sh +33 -4
- package/scripts/install.sh.sha256 +1 -0
- package/scripts/linux-package-lib.mjs +0 -0
- package/scripts/linux-package-smoke.mjs +2 -2
- package/scripts/package-size-check.mjs +26 -0
- package/scripts/plugin-build.js +314 -0
- package/scripts/plugin-publish.js +77 -0
- package/scripts/plugin-sync.js +83 -17
- package/scripts/prepack.js +9 -0
- package/scripts/ralph/prompt.md +0 -0
- package/scripts/release-metadata.js +0 -0
- package/scripts/release-sync.js +83 -7
- package/scripts/release-verify.js +131 -7
- 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/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 +0 -0
- package/skills/arch/agents/openai.yaml +0 -0
- package/skills/build/SKILL.md +0 -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 +0 -0
- package/skills/flow/agents/openai.yaml +0 -0
- package/skills/index.json +0 -0
- package/skills/maestro/SKILL.md +17 -1
- package/skills/maestro/agents/openai.yaml +0 -0
- package/skills/maestro/references/core-release-rules.md +2 -2
- package/skills/maestro/references/native-ingest-map.md +0 -0
- package/skills/sage/SKILL.md +0 -0
- package/skills/sage/agents/openai.yaml +0 -0
- package/skills/sage/references/source-selection.md +0 -0
- package/skills/tdd/SKILL.md +0 -0
- package/skills/tdd/agents/openai.yaml +0 -0
- package/skills/vet/SKILL.md +0 -0
- package/skills/vet/agents/openai.yaml +0 -0
- package/skills/vet/references/security-playbooks.md +0 -0
- package/skills/vibe/SKILL.md +0 -0
- package/skills/vibe/agents/openai.yaml +0 -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 +2 -2
- package/src/agents.js +281 -0
- package/src/bootstrap.js +272 -65
- package/src/build-gate.js +0 -0
- 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 +26 -23
- package/src/mcp-config.js +9 -1
- package/src/mcp-live-client.js +141 -8
- package/src/paths.js +6 -1
- package/src/policy.js +2 -2
- package/src/prelaunch.js +223 -0
- package/src/process-utils.js +62 -0
- package/src/quality/ai-quality-orchestrator.js +328 -0
- package/src/release-issue-gates.js +62 -0
- package/src/release-operations.js +72 -0
- package/src/release-state.js +0 -0
- package/src/runtime/active-autonomy-core.js +0 -0
- package/src/runtime/agent-compat.js +31 -0
- package/src/runtime/alignment-sentinel.js +0 -0
- package/src/runtime/architect-review.js +249 -11
- package/src/runtime/autonomous-tasks.js +585 -0
- package/src/runtime/build-readiness.js +0 -0
- package/src/runtime/code-graph-rehearse.js +0 -0
- package/src/runtime/codeburn-core.js +118 -0
- package/src/runtime/context-authority.js +40 -0
- package/src/runtime/context-economy-core.js +40 -0
- package/src/runtime/continuity-graph.js +201 -0
- package/src/runtime/continuity-notes.js +71 -26
- package/src/runtime/core-source-ingest.js +2 -2
- package/src/runtime/detached-provider.js +90 -13
- package/src/runtime/doctor-report.js +18 -0
- package/src/runtime/environment-awareness-core.js +0 -0
- package/src/runtime/exposure-catalog.js +0 -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 +0 -0
- package/src/runtime/hook-profiles.js +20 -0
- package/src/runtime/learning-loop-core.js +110 -4
- package/src/runtime/live-agent-verification.js +122 -0
- package/src/runtime/maestro-events.js +0 -0
- package/src/runtime/maestro-manager.js +0 -0
- package/src/runtime/maestro-output.js +58 -0
- package/src/runtime/maestro-state.js +0 -0
- package/src/runtime/managed-markdown.js +29 -0
- package/src/runtime/mcp-authority.js +147 -0
- package/src/runtime/mcp-policy.js +0 -0
- package/src/runtime/obsidian-integration-core.js +255 -17
- package/src/runtime/obsidian-plugin-bridge.js +344 -38
- package/src/runtime/orchestrator.js +0 -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 +0 -0
- package/src/runtime/quorum-review.js +0 -0
- package/src/runtime/ralph-execution-core.js +0 -0
- package/src/runtime/redaction-gateway.js +218 -19
- package/src/runtime/runtime-state.js +94 -2
- package/src/runtime/schema-migrations.js +162 -0
- package/src/runtime/semantic-recording-core.js +0 -0
- package/src/runtime/signal-hooks.js +13 -0
- package/src/runtime/skills-registry-export.js +96 -8
- package/src/runtime/startup-path.js +0 -0
- package/src/runtime/task-contracts.js +83 -0
- package/src/runtime/universal-plugin-broker-core.js +373 -50
- package/src/runtime/workspace-intelligence-runtime.js +0 -0
- package/src/runtime/workspace-virtualizer.js +0 -0
- package/src/runtime/workspaces.js +0 -0
- package/src/runtime-artifacts.js +0 -0
- 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 +309 -31
- 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 -81
- package/docs/prompt-guidance-contract.md +0 -17
- package/docs/prompt-guidance-fragments/active-autonomy-core.md +0 -7
- package/docs/skills-publishing.md +0 -255
- package/scripts/postinstall.js +0 -23
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
1
2
|
import fs from "node:fs/promises";
|
|
2
3
|
import os from "node:os";
|
|
3
4
|
import path from "node:path";
|
|
@@ -18,6 +19,14 @@ function quoteTomlString(value) {
|
|
|
18
19
|
return JSON.stringify(`${value}`);
|
|
19
20
|
}
|
|
20
21
|
|
|
22
|
+
function quotePosixShell(value) {
|
|
23
|
+
const text = `${value}`;
|
|
24
|
+
if (text.includes("\0")) {
|
|
25
|
+
throw new Error("plugin command and args must not contain NUL bytes");
|
|
26
|
+
}
|
|
27
|
+
return `'${text.replaceAll("'", "'\\''")}'`;
|
|
28
|
+
}
|
|
29
|
+
|
|
21
30
|
function dedupe(values) {
|
|
22
31
|
return [...new Set(values)];
|
|
23
32
|
}
|
|
@@ -48,8 +57,18 @@ async function directoryExists(targetPath) {
|
|
|
48
57
|
async function readJsonOrDefault(filePath, fallback) {
|
|
49
58
|
try {
|
|
50
59
|
return JSON.parse(await fs.readFile(filePath, "utf8"));
|
|
51
|
-
} catch {
|
|
52
|
-
return structuredClone(fallback);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if (error?.code === "ENOENT") return structuredClone(fallback);
|
|
62
|
+
throw new Error(`Invalid JSON configuration: ${filePath}`, { cause: error });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function readTextOrEmpty(filePath) {
|
|
67
|
+
try {
|
|
68
|
+
return await fs.readFile(filePath, "utf8");
|
|
69
|
+
} catch (error) {
|
|
70
|
+
if (error?.code === "ENOENT") return "";
|
|
71
|
+
throw new Error(`Unable to read vendor configuration: ${filePath}`, { cause: error });
|
|
53
72
|
}
|
|
54
73
|
}
|
|
55
74
|
|
|
@@ -58,6 +77,79 @@ async function writeJsonConfig(filePath, config) {
|
|
|
58
77
|
await fs.writeFile(filePath, `${JSON.stringify(config, null, 2)}\n`);
|
|
59
78
|
}
|
|
60
79
|
|
|
80
|
+
async function fileHash(filePath) {
|
|
81
|
+
try {
|
|
82
|
+
if ((await fs.lstat(filePath)).isSymbolicLink()) {
|
|
83
|
+
throw new Error(`Refusing to hash symlinked vendor path: ${filePath}`);
|
|
84
|
+
}
|
|
85
|
+
return crypto
|
|
86
|
+
.createHash("sha256")
|
|
87
|
+
.update(await fs.readFile(filePath))
|
|
88
|
+
.digest("hex");
|
|
89
|
+
} catch (error) {
|
|
90
|
+
if (error?.code === "ENOENT") return null;
|
|
91
|
+
throw new Error(`Unable to read vendor configuration: ${filePath}`, { cause: error });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function pathHash(target) {
|
|
96
|
+
try {
|
|
97
|
+
const stat = await fs.lstat(target);
|
|
98
|
+
if (stat.isSymbolicLink()) throw new Error(`Refusing to hash symlinked path: ${target}`);
|
|
99
|
+
if (stat.isDirectory()) {
|
|
100
|
+
const entries = (await fs.readdir(target)).sort();
|
|
101
|
+
const parts = [];
|
|
102
|
+
for (const entry of entries)
|
|
103
|
+
parts.push(`${entry}\0${await pathHash(path.join(target, entry))}`);
|
|
104
|
+
return crypto
|
|
105
|
+
.createHash("sha256")
|
|
106
|
+
.update(`dir\0${parts.join("\0")}`)
|
|
107
|
+
.digest("hex");
|
|
108
|
+
}
|
|
109
|
+
return crypto
|
|
110
|
+
.createHash("sha256")
|
|
111
|
+
.update(await fs.readFile(target))
|
|
112
|
+
.digest("hex");
|
|
113
|
+
} catch (error) {
|
|
114
|
+
if (error?.code === "ENOENT") return null;
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function assertNoSymlinkPath(target) {
|
|
120
|
+
if (!target) return;
|
|
121
|
+
let current = path.parse(path.resolve(target)).root;
|
|
122
|
+
for (const part of path.relative(current, path.resolve(target)).split(path.sep).filter(Boolean)) {
|
|
123
|
+
current = path.join(current, part);
|
|
124
|
+
const stat = await fs.lstat(current).catch((error) => {
|
|
125
|
+
if (error?.code === "ENOENT") return null;
|
|
126
|
+
throw error;
|
|
127
|
+
});
|
|
128
|
+
if (stat?.isSymbolicLink()) throw new Error(`Refusing symlinked vendor path: ${current}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function hostConfigPath({ host, home, platform, env }) {
|
|
133
|
+
if (host === "claude-code") return path.join(home, ".claude.json");
|
|
134
|
+
if (host === "antigravity") return path.join(home, ".antigravity", "config.toml");
|
|
135
|
+
if (host === "cursor") return cursorStoragePath({ home, platform, env });
|
|
136
|
+
if (host === "codex") return path.join(home, ".codex", "config.toml");
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function snapshotHostConfig({ host, home, platform, env, backupRoot }) {
|
|
141
|
+
const configPath = hostConfigPath({ host, home, platform, env });
|
|
142
|
+
if (!configPath) return { host, path: null, backupPath: null, beforeHash: null };
|
|
143
|
+
await assertNoSymlinkPath(configPath);
|
|
144
|
+
const beforeHash = await fileHash(configPath);
|
|
145
|
+
const backupPath = path.join(backupRoot, `${host}.bak`);
|
|
146
|
+
if (beforeHash) {
|
|
147
|
+
await fs.mkdir(backupRoot, { recursive: true });
|
|
148
|
+
await fs.copyFile(configPath, backupPath);
|
|
149
|
+
}
|
|
150
|
+
return { host, path: configPath, backupPath: beforeHash ? backupPath : null, beforeHash };
|
|
151
|
+
}
|
|
152
|
+
|
|
61
153
|
function cursorStoragePath({
|
|
62
154
|
home = os.homedir(),
|
|
63
155
|
platform = process.platform,
|
|
@@ -102,17 +194,17 @@ function createMcpDefinition({ manifest, pluginDir, wrapperPath, useWrapper = tr
|
|
|
102
194
|
};
|
|
103
195
|
}
|
|
104
196
|
|
|
105
|
-
function renderWrapperScript({ manifest, pluginDir }) {
|
|
197
|
+
export function renderWrapperScript({ manifest, pluginDir }) {
|
|
106
198
|
const args = asArray(manifest.mcp?.args).map((arg) =>
|
|
107
199
|
`${arg}`.replaceAll("{{PLUGIN_DIR}}", pluginDir),
|
|
108
200
|
);
|
|
109
201
|
if (manifest.mcp.command === "node" && args.length > 0) {
|
|
110
202
|
const [entrypoint, ...rest] = args;
|
|
111
|
-
const quotedRest = rest.map(
|
|
112
|
-
return `#!/usr/bin/env sh\nexec
|
|
203
|
+
const quotedRest = rest.map(quotePosixShell).join(" ");
|
|
204
|
+
return `#!/usr/bin/env sh\nexec ${quotePosixShell("node")} ${quotePosixShell(entrypoint)}${quotedRest ? ` ${quotedRest}` : ""} "$@"\n`;
|
|
113
205
|
}
|
|
114
|
-
const quotedArgs = args.map(
|
|
115
|
-
return `#!/usr/bin/env sh\nexec
|
|
206
|
+
const quotedArgs = args.map(quotePosixShell).join(" ");
|
|
207
|
+
return `#!/usr/bin/env sh\nexec ${quotePosixShell(manifest.mcp.command)}${quotedArgs ? ` ${quotedArgs}` : ""} "$@"\n`;
|
|
116
208
|
}
|
|
117
209
|
|
|
118
210
|
export function getUniversalPluginBrokerCorePath() {
|
|
@@ -341,6 +433,10 @@ export async function writePluginContextSkill({ manifest, home = os.homedir() })
|
|
|
341
433
|
const plugin = validateUniversalPluginManifest(manifest);
|
|
342
434
|
const serverName = mcpServerNameForPlugin(plugin.name);
|
|
343
435
|
const skillDir = path.join(home, ".agents", "skills", serverName);
|
|
436
|
+
await assertNoSymlinkPath(skillDir);
|
|
437
|
+
for (const fileName of ["SKILL.md", "ma-plugin-manifest.json", "ma-plugin-broker-receipt.json"]) {
|
|
438
|
+
await assertNoSymlinkPath(path.join(skillDir, fileName));
|
|
439
|
+
}
|
|
344
440
|
await fs.mkdir(skillDir, { recursive: true });
|
|
345
441
|
await fs.writeFile(path.join(skillDir, "SKILL.md"), renderPluginContextSkillMd(plugin));
|
|
346
442
|
await writeJson(path.join(skillDir, "ma-plugin-manifest.json"), plugin);
|
|
@@ -391,6 +487,7 @@ export async function injectClaudeCodeMcpServer({
|
|
|
391
487
|
args,
|
|
392
488
|
}) {
|
|
393
489
|
const claudeJsonPath = path.join(home, ".claude.json");
|
|
490
|
+
await assertNoSymlinkPath(claudeJsonPath);
|
|
394
491
|
if (!(await pathExists(claudeJsonPath)) && !(await directoryExists(path.join(home, ".claude")))) {
|
|
395
492
|
return { host: "claude-code", status: "skipped", reason: "Claude Code config not detected" };
|
|
396
493
|
}
|
|
@@ -416,7 +513,8 @@ export async function injectAntigravityMcpServer({
|
|
|
416
513
|
};
|
|
417
514
|
}
|
|
418
515
|
const configPath = path.join(antigravityRoot, "config.toml");
|
|
419
|
-
|
|
516
|
+
await assertNoSymlinkPath(configPath);
|
|
517
|
+
const existing = (await readTextOrEmpty(configPath)).trimEnd();
|
|
420
518
|
const marker = `name = ${quoteTomlString(serverName)}`;
|
|
421
519
|
if (existing.includes(marker)) {
|
|
422
520
|
return { host: "antigravity", status: "configured", path: configPath, idempotent: true };
|
|
@@ -445,6 +543,7 @@ export async function injectCursorMcpServer({
|
|
|
445
543
|
args,
|
|
446
544
|
}) {
|
|
447
545
|
const settingsPath = cursorStoragePath({ home, platform, env });
|
|
546
|
+
await assertNoSymlinkPath(settingsPath);
|
|
448
547
|
if (!(await pathExists(settingsPath)) && !(await directoryExists(path.dirname(settingsPath)))) {
|
|
449
548
|
return { host: "cursor", status: "skipped", reason: "Cursor storage not detected" };
|
|
450
549
|
}
|
|
@@ -466,7 +565,8 @@ export async function injectCodexMcpServer({ home = os.homedir(), serverName, co
|
|
|
466
565
|
return { host: "codex", status: "skipped", reason: "Codex config root not detected" };
|
|
467
566
|
}
|
|
468
567
|
const configPath = path.join(codexRoot, "config.toml");
|
|
469
|
-
|
|
568
|
+
await assertNoSymlinkPath(configPath);
|
|
569
|
+
const existing = (await readTextOrEmpty(configPath)).trimEnd();
|
|
470
570
|
const header = `[mcp_servers.${quoteTomlString(serverName)}]`;
|
|
471
571
|
if (existing.includes(header)) {
|
|
472
572
|
return { host: "codex", status: "configured", path: configPath, idempotent: true };
|
|
@@ -489,25 +589,55 @@ export async function injectPluginToVendors({
|
|
|
489
589
|
platform = process.platform,
|
|
490
590
|
env = process.env,
|
|
491
591
|
useWrapper = true,
|
|
592
|
+
backupRoot = null,
|
|
492
593
|
} = {}) {
|
|
493
594
|
const plugin = validateUniversalPluginManifest(manifest);
|
|
494
595
|
const selectedTargets = targets ?? (await detectInstalledPluginHosts({ home, platform, env }));
|
|
495
596
|
const serverName = mcpServerNameForPlugin(plugin.name);
|
|
496
597
|
const definition = createMcpDefinition({ manifest: plugin, pluginDir, wrapperPath, useWrapper });
|
|
497
598
|
const receipts = [];
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
599
|
+
const snapshots = new Map();
|
|
600
|
+
try {
|
|
601
|
+
for (const target of selectedTargets) {
|
|
602
|
+
const snapshot = backupRoot
|
|
603
|
+
? await snapshotHostConfig({ host: target, home, platform, env, backupRoot })
|
|
604
|
+
: null;
|
|
605
|
+
if (snapshot) snapshots.set(target, snapshot);
|
|
606
|
+
if (target === "claude-code") {
|
|
607
|
+
receipts.push(await injectClaudeCodeMcpServer({ home, serverName, ...definition }));
|
|
608
|
+
} else if (target === "antigravity") {
|
|
609
|
+
receipts.push(await injectAntigravityMcpServer({ home, serverName, ...definition }));
|
|
610
|
+
} else if (target === "cursor") {
|
|
611
|
+
receipts.push(
|
|
612
|
+
await injectCursorMcpServer({ home, platform, env, serverName, ...definition }),
|
|
613
|
+
);
|
|
614
|
+
} else if (target === "codex") {
|
|
615
|
+
receipts.push(await injectCodexMcpServer({ home, serverName, ...definition }));
|
|
616
|
+
} else {
|
|
617
|
+
receipts.push({ host: target, status: "skipped", reason: "No MA MCP injector for host" });
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
} catch (error) {
|
|
621
|
+
const rollbackErrors = [];
|
|
622
|
+
for (const snapshot of snapshots.values()) {
|
|
623
|
+
try {
|
|
624
|
+
if (snapshot.backupPath) await fs.copyFile(snapshot.backupPath, snapshot.path);
|
|
625
|
+
else await fs.rm(snapshot.path, { force: true });
|
|
626
|
+
} catch (rollbackError) {
|
|
627
|
+
rollbackErrors.push(rollbackError);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
if (rollbackErrors.length > 0) {
|
|
631
|
+
throw new AggregateError([error, ...rollbackErrors], "Plugin vendor rollback failed");
|
|
632
|
+
}
|
|
633
|
+
throw error;
|
|
634
|
+
}
|
|
635
|
+
for (const receipt of receipts) {
|
|
636
|
+
const snapshot = backupRoot ? snapshots.get(receipt.host) : null;
|
|
637
|
+
if (snapshot) {
|
|
638
|
+
receipt.backupPath = snapshot.backupPath;
|
|
639
|
+
receipt.beforeHash = snapshot.beforeHash;
|
|
640
|
+
receipt.afterHash = await fileHash(snapshot.path);
|
|
511
641
|
}
|
|
512
642
|
}
|
|
513
643
|
return {
|
|
@@ -531,6 +661,7 @@ export async function installUniversalPlugin({
|
|
|
531
661
|
platform = process.platform,
|
|
532
662
|
env = process.env,
|
|
533
663
|
useWrapper = true,
|
|
664
|
+
dryRun = false,
|
|
534
665
|
} = {}) {
|
|
535
666
|
if (!sourceDir) {
|
|
536
667
|
throw new Error("installUniversalPlugin requires sourceDir");
|
|
@@ -540,36 +671,228 @@ export async function installUniversalPlugin({
|
|
|
540
671
|
if (!(await pathExists(sourceEntrypoint))) {
|
|
541
672
|
throw new Error(`Plugin entrypoint missing: ${manifest.entrypoint}`);
|
|
542
673
|
}
|
|
674
|
+
const selectedTargets = targets ?? (await detectInstalledPluginHosts({ home, platform, env }));
|
|
675
|
+
if (dryRun) {
|
|
676
|
+
return {
|
|
677
|
+
schemaVersion: universalPluginBrokerSchemaVersion,
|
|
678
|
+
record_type: "universal_plugin_broker_dry_run",
|
|
679
|
+
plugin: manifest.name,
|
|
680
|
+
targets: selectedTargets,
|
|
681
|
+
files: selectedTargets
|
|
682
|
+
.map((host) => hostConfigPath({ host, home, platform, env }))
|
|
683
|
+
.filter(Boolean),
|
|
684
|
+
records_as: "plugin_compatibility_configuration",
|
|
685
|
+
build_evidence: false,
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
await preflightVendorConfigs({ selectedTargets, home, platform, env });
|
|
543
689
|
const pluginDir = path.join(home, ".ma", "plugins", manifest.name);
|
|
544
690
|
const wrapperPath = path.join(home, ".ma", "bin", mcpServerNameForPlugin(manifest.name));
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
691
|
+
const backupRoot = path.join(home, ".ma", "backups", "plugins", manifest.name, `${Date.now()}`);
|
|
692
|
+
let previousPaths;
|
|
693
|
+
let contextSkillReceipt;
|
|
694
|
+
let vendorInjection;
|
|
695
|
+
let installedPaths;
|
|
696
|
+
try {
|
|
697
|
+
const contextSkillPath = path.join(
|
|
698
|
+
home,
|
|
699
|
+
".agents",
|
|
700
|
+
"skills",
|
|
701
|
+
mcpServerNameForPlugin(manifest.name),
|
|
702
|
+
);
|
|
703
|
+
await assertNoSymlinkPath(backupRoot);
|
|
704
|
+
await assertNoSymlinkPath(pluginDir);
|
|
705
|
+
await assertNoSymlinkPath(wrapperPath);
|
|
706
|
+
await assertNoSymlinkPath(contextSkillPath);
|
|
707
|
+
await fs.mkdir(backupRoot, { recursive: true });
|
|
708
|
+
previousPaths = {
|
|
709
|
+
pluginDir: await backupPathIfPresent(pluginDir, path.join(backupRoot, "previous-plugin")),
|
|
710
|
+
wrapperPath: await backupPathIfPresent(
|
|
711
|
+
wrapperPath,
|
|
712
|
+
path.join(backupRoot, "previous-wrapper"),
|
|
713
|
+
),
|
|
714
|
+
contextSkill: await backupPathIfPresent(
|
|
715
|
+
contextSkillPath,
|
|
716
|
+
path.join(backupRoot, "previous-skill"),
|
|
717
|
+
),
|
|
718
|
+
};
|
|
719
|
+
await fs.mkdir(path.dirname(pluginDir), { recursive: true });
|
|
720
|
+
await fs.cp(sourceDir, pluginDir, { recursive: true });
|
|
721
|
+
await fs.mkdir(path.dirname(wrapperPath), { recursive: true });
|
|
722
|
+
await fs.writeFile(wrapperPath, renderWrapperScript({ manifest, pluginDir }));
|
|
723
|
+
await fs.chmod(wrapperPath, 0o755);
|
|
724
|
+
contextSkillReceipt = await writePluginContextSkill({ manifest, home });
|
|
725
|
+
vendorInjection = await injectPluginToVendors({
|
|
726
|
+
manifest,
|
|
727
|
+
pluginDir,
|
|
728
|
+
wrapperPath,
|
|
729
|
+
home,
|
|
730
|
+
targets: selectedTargets,
|
|
731
|
+
platform,
|
|
732
|
+
env,
|
|
733
|
+
useWrapper,
|
|
734
|
+
backupRoot,
|
|
735
|
+
});
|
|
736
|
+
installedPaths = {
|
|
737
|
+
pluginDir: await pathHash(pluginDir),
|
|
738
|
+
wrapperPath: await pathHash(wrapperPath),
|
|
739
|
+
contextSkill: await pathHash(contextSkillReceipt.path),
|
|
740
|
+
};
|
|
741
|
+
const receipt = {
|
|
742
|
+
schemaVersion: universalPluginBrokerSchemaVersion,
|
|
743
|
+
record_type: "universal_plugin_broker_install_receipt",
|
|
744
|
+
plugin: manifest.name,
|
|
745
|
+
pluginDir,
|
|
746
|
+
wrapperPath,
|
|
747
|
+
contextSkillReceipt,
|
|
748
|
+
previousPaths,
|
|
749
|
+
installedPaths,
|
|
750
|
+
vendorInjection,
|
|
751
|
+
backupRoot,
|
|
752
|
+
activation_notice:
|
|
753
|
+
"Plugin configured. Run /reload-mcp where supported or restart active agent sessions to activate newly injected MCP servers.",
|
|
754
|
+
records_as: "plugin_compatibility_configuration",
|
|
755
|
+
build_evidence: false,
|
|
756
|
+
};
|
|
757
|
+
await writeJson(path.join(backupRoot, "receipt.json"), receipt);
|
|
758
|
+
return receipt;
|
|
759
|
+
} catch (error) {
|
|
760
|
+
if (vendorInjection && previousPaths) {
|
|
761
|
+
try {
|
|
762
|
+
await rollbackUniversalPluginInstall({
|
|
763
|
+
pluginDir,
|
|
764
|
+
wrapperPath,
|
|
765
|
+
contextSkillReceipt,
|
|
766
|
+
previousPaths,
|
|
767
|
+
installedPaths,
|
|
768
|
+
vendorInjection,
|
|
769
|
+
backupRoot,
|
|
770
|
+
});
|
|
771
|
+
} catch (rollbackError) {
|
|
772
|
+
const failure = error instanceof Error ? error : new Error(String(error));
|
|
773
|
+
failure.cause = rollbackError;
|
|
774
|
+
throw failure;
|
|
775
|
+
}
|
|
776
|
+
} else {
|
|
777
|
+
if (previousPaths) {
|
|
778
|
+
await restorePath(pluginDir, previousPaths.pluginDir);
|
|
779
|
+
await restorePath(wrapperPath, previousPaths.wrapperPath);
|
|
780
|
+
await restorePath(
|
|
781
|
+
path.join(home, ".agents", "skills", mcpServerNameForPlugin(manifest.name)),
|
|
782
|
+
previousPaths.contextSkill,
|
|
783
|
+
);
|
|
784
|
+
}
|
|
785
|
+
await fs.rm(backupRoot, { recursive: true, force: true });
|
|
786
|
+
}
|
|
787
|
+
throw error;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
async function preflightVendorConfigs({ selectedTargets, home, platform, env }) {
|
|
792
|
+
for (const target of selectedTargets) {
|
|
793
|
+
if (target === "claude-code") {
|
|
794
|
+
const configPath = path.join(home, ".claude.json");
|
|
795
|
+
if (await pathExists(configPath)) await readJsonOrDefault(configPath, {});
|
|
796
|
+
}
|
|
797
|
+
if (target === "cursor") {
|
|
798
|
+
const configPath = cursorStoragePath({ home, platform, env });
|
|
799
|
+
if (await pathExists(configPath)) await readJsonOrDefault(configPath, {});
|
|
800
|
+
}
|
|
801
|
+
if (target === "antigravity" || target === "codex") {
|
|
802
|
+
const configPath = hostConfigPath({ host: target, home, platform, env });
|
|
803
|
+
if (configPath) await readTextOrEmpty(configPath);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
async function backupPathIfPresent(source, backup) {
|
|
809
|
+
if (!(await pathExists(source))) return null;
|
|
810
|
+
await fs.cp(source, backup, { recursive: true, force: true });
|
|
811
|
+
return backup;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
async function restorePath(target, backup) {
|
|
815
|
+
if (!target) return;
|
|
816
|
+
await fs.rm(target, { recursive: true, force: true });
|
|
817
|
+
if (backup) await fs.cp(backup, target, { recursive: true, force: true });
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
export async function rollbackUniversalPluginInstall(receipt) {
|
|
821
|
+
if (receipt?.installedPaths) {
|
|
822
|
+
for (const [label, expectedHash] of Object.entries(receipt.installedPaths)) {
|
|
823
|
+
const target =
|
|
824
|
+
label === "pluginDir"
|
|
825
|
+
? receipt.pluginDir
|
|
826
|
+
: label === "wrapperPath"
|
|
827
|
+
? receipt.wrapperPath
|
|
828
|
+
: receipt.contextSkillReceipt?.path;
|
|
829
|
+
if ((await pathHash(target)) !== expectedHash) {
|
|
830
|
+
throw new Error(`Refusing rollback: installed ${label} changed after installation`);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
const entries = receipt?.vendorInjection?.receipts ?? [];
|
|
835
|
+
const validated = [];
|
|
836
|
+
for (const entry of entries) {
|
|
837
|
+
if (!entry.path) continue;
|
|
838
|
+
const currentHash = await fileHash(entry.path);
|
|
839
|
+
const expectedHash = entry.afterHash ?? null;
|
|
840
|
+
if (currentHash !== expectedHash) {
|
|
841
|
+
throw new Error(`Refusing rollback for ${entry.host}: vendor config changed after injection`);
|
|
842
|
+
}
|
|
843
|
+
const backupExists = entry.backupPath && (await pathExists(entry.backupPath));
|
|
844
|
+
if (entry.status === "configured" && entry.beforeHash && !backupExists) {
|
|
845
|
+
throw new Error(`Refusing rollback for ${entry.host}: rollback source is missing`);
|
|
846
|
+
}
|
|
847
|
+
validated.push({
|
|
848
|
+
entry,
|
|
849
|
+
current: currentHash === null ? null : await fs.readFile(entry.path),
|
|
850
|
+
desired: backupExists ? await fs.readFile(entry.backupPath) : null,
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
try {
|
|
854
|
+
for (const { entry, desired } of validated) {
|
|
855
|
+
if (desired) await fs.writeFile(entry.path, desired);
|
|
856
|
+
else if (entry.status === "configured") await fs.rm(entry.path, { force: true });
|
|
857
|
+
}
|
|
858
|
+
} catch (error) {
|
|
859
|
+
const restoreErrors = [];
|
|
860
|
+
for (const { entry, current } of validated) {
|
|
861
|
+
try {
|
|
862
|
+
await assertNoSymlinkPath(entry.path);
|
|
863
|
+
if (current) await fs.writeFile(entry.path, current);
|
|
864
|
+
else await fs.rm(entry.path, { force: true });
|
|
865
|
+
} catch (restoreError) {
|
|
866
|
+
restoreErrors.push(restoreError);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
if (restoreErrors.length > 0) {
|
|
870
|
+
throw new AggregateError(restoreErrors, "Plugin vendor rollback recovery failed");
|
|
871
|
+
}
|
|
872
|
+
throw new Error("Plugin rollback failed; prior vendor state was restored where possible", {
|
|
873
|
+
cause: error,
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
const pathRestoreErrors = [];
|
|
877
|
+
for (const [target, backup] of [
|
|
878
|
+
[receipt.pluginDir, receipt.previousPaths?.pluginDir],
|
|
879
|
+
[receipt.wrapperPath, receipt.previousPaths?.wrapperPath],
|
|
880
|
+
[receipt.contextSkillReceipt?.path, receipt.previousPaths?.contextSkill],
|
|
881
|
+
]) {
|
|
882
|
+
try {
|
|
883
|
+
await assertNoSymlinkPath(target);
|
|
884
|
+
await restorePath(target, backup);
|
|
885
|
+
} catch (restoreError) {
|
|
886
|
+
pathRestoreErrors.push(restoreError);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
if (pathRestoreErrors.length > 0) {
|
|
890
|
+
throw new AggregateError(pathRestoreErrors, "Plugin rollback recovery failed");
|
|
891
|
+
}
|
|
892
|
+
if (receipt.backupRoot) await fs.rm(receipt.backupRoot, { recursive: true, force: true });
|
|
562
893
|
return {
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
pluginDir,
|
|
567
|
-
wrapperPath,
|
|
568
|
-
contextSkillReceipt,
|
|
569
|
-
vendorInjection,
|
|
570
|
-
activation_notice:
|
|
571
|
-
"Plugin configured. Run /reload-mcp where supported or restart active agent sessions to activate newly injected MCP servers.",
|
|
572
|
-
records_as: "plugin_compatibility_configuration",
|
|
573
|
-
build_evidence: false,
|
|
894
|
+
rolledBack: validated
|
|
895
|
+
.filter(({ entry }) => entry.status === "configured")
|
|
896
|
+
.map(({ entry }) => entry.host),
|
|
574
897
|
};
|
|
575
898
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/runtime-artifacts.js
CHANGED
|
File without changes
|