@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
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { agentRegistry as executableAgents, resolveAgentCommand } from "../agents.js";
|
|
6
|
+
import {
|
|
7
|
+
agentRegistry,
|
|
8
|
+
createSkillCompatibilityPayload,
|
|
9
|
+
verifyCrossAgentInstallMatrix,
|
|
10
|
+
} from "./skills-registry-export.js";
|
|
11
|
+
|
|
12
|
+
const defaultTimeoutMs = 5000;
|
|
13
|
+
|
|
14
|
+
function runVersionProbe(command, timeoutMs, args = ["--version"]) {
|
|
15
|
+
return new Promise((resolve) => {
|
|
16
|
+
const child = spawn(command, args, {
|
|
17
|
+
shell: false,
|
|
18
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
19
|
+
});
|
|
20
|
+
let stdout = "";
|
|
21
|
+
let stderr = "";
|
|
22
|
+
let settled = false;
|
|
23
|
+
const finish = (result) => {
|
|
24
|
+
if (settled) return;
|
|
25
|
+
settled = true;
|
|
26
|
+
clearTimeout(timer);
|
|
27
|
+
resolve(result);
|
|
28
|
+
};
|
|
29
|
+
const timer = setTimeout(() => {
|
|
30
|
+
child.kill("SIGTERM");
|
|
31
|
+
finish({ ok: false, reason: "version probe timed out" });
|
|
32
|
+
}, timeoutMs);
|
|
33
|
+
child.stdout.on("data", (chunk) => {
|
|
34
|
+
stdout += chunk.toString();
|
|
35
|
+
});
|
|
36
|
+
child.stderr.on("data", (chunk) => {
|
|
37
|
+
stderr += chunk.toString();
|
|
38
|
+
});
|
|
39
|
+
child.once("error", (error) => finish({ ok: false, reason: error.message }));
|
|
40
|
+
child.once("close", (code) =>
|
|
41
|
+
finish({
|
|
42
|
+
ok: code === 0,
|
|
43
|
+
version: stdout.trim(),
|
|
44
|
+
reason: code === 0 ? null : stderr.trim() || `exited with code ${code}`,
|
|
45
|
+
}),
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function normalizeProbeCommand(command) {
|
|
51
|
+
return [".js", ".mjs", ".cjs"].includes(path.extname(command))
|
|
52
|
+
? { command: process.execPath, args: [command, "--version"] }
|
|
53
|
+
: { command, args: ["--version"] };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function verifyLiveAgentMatrix({
|
|
57
|
+
cwd = process.cwd(),
|
|
58
|
+
targets = Object.keys(agentRegistry),
|
|
59
|
+
timeoutMs = defaultTimeoutMs,
|
|
60
|
+
} = {}) {
|
|
61
|
+
const root = path.resolve(cwd);
|
|
62
|
+
const tempRoot = await fsTempDirectory();
|
|
63
|
+
try {
|
|
64
|
+
const payload = createSkillCompatibilityPayload({
|
|
65
|
+
name: "live-agent-verification",
|
|
66
|
+
capabilities: ["maestro", "agent-compatibility"],
|
|
67
|
+
});
|
|
68
|
+
const distribution = await verifyCrossAgentInstallMatrix({
|
|
69
|
+
payload,
|
|
70
|
+
cwd: tempRoot,
|
|
71
|
+
targets,
|
|
72
|
+
existingAgentRoots: targets,
|
|
73
|
+
});
|
|
74
|
+
const results = [];
|
|
75
|
+
for (const [index, target] of targets.entries()) {
|
|
76
|
+
const distributionResult = distribution.results[index];
|
|
77
|
+
const command = executableAgents[target]?.probeable ? resolveAgentCommand(target) : null;
|
|
78
|
+
if (!command) {
|
|
79
|
+
results.push({
|
|
80
|
+
target,
|
|
81
|
+
status: distributionResult?.ok ? "distribution-only" : "blocked",
|
|
82
|
+
version: null,
|
|
83
|
+
command: null,
|
|
84
|
+
distribution: distributionResult?.ok === true,
|
|
85
|
+
reason: "No safe non-interactive host command is registered for this target.",
|
|
86
|
+
});
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const normalized = normalizeProbeCommand(command);
|
|
90
|
+
const probe = await runVersionProbe(normalized.command, timeoutMs, normalized.args);
|
|
91
|
+
results.push({
|
|
92
|
+
target,
|
|
93
|
+
status: probe.ok && distributionResult?.ok ? "runtime-verified" : "blocked",
|
|
94
|
+
version: probe.version ?? null,
|
|
95
|
+
command,
|
|
96
|
+
distribution: distributionResult?.ok === true,
|
|
97
|
+
reason: probe.ok ? null : probe.reason,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
schemaVersion: "0.1.0",
|
|
102
|
+
record_type: "live_agent_verification",
|
|
103
|
+
cwd: root,
|
|
104
|
+
target_count: results.length,
|
|
105
|
+
runtime_verified: results.filter((result) => result.status === "runtime-verified").length,
|
|
106
|
+
distribution_only: results.filter((result) => result.status === "distribution-only").length,
|
|
107
|
+
blocked: results.filter((result) => result.status === "blocked").length,
|
|
108
|
+
results,
|
|
109
|
+
production_evidence: false,
|
|
110
|
+
};
|
|
111
|
+
} finally {
|
|
112
|
+
await fsTempRemove(tempRoot);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function fsTempDirectory() {
|
|
117
|
+
return fs.mkdtemp(path.join(os.tmpdir(), "ma-live-agents-"));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function fsTempRemove(directory) {
|
|
121
|
+
await fs.rm(directory, { recursive: true, force: true });
|
|
122
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import { getRuntimeSubsystemPath } from "../paths.js";
|
|
3
|
+
|
|
4
|
+
export function getMaestroEventsPath() {
|
|
5
|
+
return getRuntimeSubsystemPath("logs", "maestro-events.ndjson");
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export async function appendMaestroEvent(record) {
|
|
9
|
+
const event = {
|
|
10
|
+
schemaVersion: "0.1.0",
|
|
11
|
+
timestamp: new Date().toISOString(),
|
|
12
|
+
...record,
|
|
13
|
+
};
|
|
14
|
+
const target = getMaestroEventsPath();
|
|
15
|
+
await fs.mkdir(getRuntimeSubsystemPath("logs"), { recursive: true });
|
|
16
|
+
await fs.appendFile(target, `${JSON.stringify(event)}\n`, "utf8");
|
|
17
|
+
return event;
|
|
18
|
+
}
|
|
@@ -524,6 +524,25 @@ export function chooseMaestroManagerAction({
|
|
|
524
524
|
};
|
|
525
525
|
}
|
|
526
526
|
|
|
527
|
+
if (["READY", "RUNNING"].includes(releaseState.build_status)) {
|
|
528
|
+
return {
|
|
529
|
+
mode: "helper+gated",
|
|
530
|
+
nextAction: "dispatch-gated",
|
|
531
|
+
dispatchPlan: {
|
|
532
|
+
helpers: [],
|
|
533
|
+
gated: [
|
|
534
|
+
createGatedPlan(
|
|
535
|
+
"$build",
|
|
536
|
+
"Advance the bounded build execution substep and persist lane-owned build evidence.",
|
|
537
|
+
{ prerequisites: ["build_status=READY|RUNNING"] },
|
|
538
|
+
),
|
|
539
|
+
],
|
|
540
|
+
team: null,
|
|
541
|
+
},
|
|
542
|
+
reason: recommendation?.why ?? "The build lane still owns the next bounded execution step.",
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
|
|
527
546
|
if (releaseState.merge_status !== "MERGED_TO_DEVELOPMENT") {
|
|
528
547
|
return {
|
|
529
548
|
mode: "team",
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export function createMaestroView(maestroState) {
|
|
2
|
+
const tracks = Object.entries(maestroState?.runtime_tracks ?? {}).map(([id, track]) => ({
|
|
3
|
+
id,
|
|
4
|
+
gate: track.active_gate ?? "none",
|
|
5
|
+
status: track.status ?? "unknown",
|
|
6
|
+
blockers: Array.isArray(track.blockers) ? track.blockers : [],
|
|
7
|
+
}));
|
|
8
|
+
const locks = Object.entries(maestroState?.downstream_lock_table ?? {}).map(([gate, lock]) => ({
|
|
9
|
+
gate,
|
|
10
|
+
status: lock.is_locked ? "locked" : "unlocked",
|
|
11
|
+
lockedBy: Array.isArray(lock.locked_by) ? lock.locked_by : [],
|
|
12
|
+
unlockCriteria: lock.unlock_criteria ?? null,
|
|
13
|
+
}));
|
|
14
|
+
const blockers = tracks.flatMap((track) =>
|
|
15
|
+
track.blockers.map((message) => `${track.id}: ${message}`),
|
|
16
|
+
);
|
|
17
|
+
return {
|
|
18
|
+
schemaVersion: "0.1.0",
|
|
19
|
+
scope: "maestro",
|
|
20
|
+
globalStatus: maestroState?.global_status ?? "unknown",
|
|
21
|
+
orchestrationId: maestroState?.orchestration_id ?? null,
|
|
22
|
+
tracks,
|
|
23
|
+
locks,
|
|
24
|
+
blockers,
|
|
25
|
+
nextAction:
|
|
26
|
+
blockers[0] ??
|
|
27
|
+
(locks.some((lock) => lock.status === "locked")
|
|
28
|
+
? "Resolve downstream locks"
|
|
29
|
+
: "Continue active lane"),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function formatMaestroView(view) {
|
|
34
|
+
const lines = [
|
|
35
|
+
"Maestro View",
|
|
36
|
+
"Meta-Architect / Maestro",
|
|
37
|
+
"========================",
|
|
38
|
+
`Global status: ${view.globalStatus}`,
|
|
39
|
+
`Orchestration id: ${view.orchestrationId ?? "not started"}`,
|
|
40
|
+
`Status: ${view.globalStatus}`,
|
|
41
|
+
`Run: ${view.orchestrationId ?? "not started"}`,
|
|
42
|
+
"",
|
|
43
|
+
"Lane Gate Status",
|
|
44
|
+
"---------- -------------------- ----------",
|
|
45
|
+
];
|
|
46
|
+
if (view.tracks.length === 0) lines.push("none");
|
|
47
|
+
for (const track of view.tracks)
|
|
48
|
+
lines.push(`${track.id.padEnd(10)} ${track.gate.padEnd(20)} ${track.status}`);
|
|
49
|
+
lines.push("", "Blockers");
|
|
50
|
+
lines.push(...(view.blockers.length ? view.blockers.map((item) => `- ${item}`) : ["- none"]));
|
|
51
|
+
lines.push("", `Next: ${view.nextAction}`, "", "Downstream locks");
|
|
52
|
+
lines.push(
|
|
53
|
+
...(view.locks.length
|
|
54
|
+
? view.locks.map((lock) => `- ${lock.gate}: ${lock.status}`)
|
|
55
|
+
: ["- none"]),
|
|
56
|
+
);
|
|
57
|
+
return `${lines.join("\n")}\n`;
|
|
58
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { readJson, writeFileIfMissing, writeJson } from "../fs-utils.js";
|
|
3
|
+
import { getRuntimeStatePath } from "../paths.js";
|
|
4
|
+
|
|
5
|
+
const defaultDependencyGraph = {
|
|
6
|
+
description: "Topological map of gate execution dependencies.",
|
|
7
|
+
nodes: {
|
|
8
|
+
$arch: { dependencies: [] },
|
|
9
|
+
$sage: { dependencies: ["$arch"] },
|
|
10
|
+
$flow: { dependencies: ["$sage"] },
|
|
11
|
+
$vet: { dependencies: ["$sage"] },
|
|
12
|
+
$vibe: { dependencies: ["$sage"] },
|
|
13
|
+
$build: { dependencies: ["$flow", "$vet", "$vibe"] },
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const validGlobalStatuses = new Set([
|
|
18
|
+
"IDLE",
|
|
19
|
+
"READY",
|
|
20
|
+
"RUNNING",
|
|
21
|
+
"COMPLETED",
|
|
22
|
+
"DEGRADED_HEALING",
|
|
23
|
+
"LOCKED",
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
export function getMaestroStatePath() {
|
|
27
|
+
return getRuntimeStatePath("maestro-state.json");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function createDefaultMaestroState() {
|
|
31
|
+
return {
|
|
32
|
+
schemaVersion: "0.1.0",
|
|
33
|
+
orchestration_id: null,
|
|
34
|
+
timestamp: null,
|
|
35
|
+
global_status: "IDLE",
|
|
36
|
+
configuration: {
|
|
37
|
+
auto_heal: false,
|
|
38
|
+
max_healing_attempts: 2,
|
|
39
|
+
concurrency_limit: 4,
|
|
40
|
+
},
|
|
41
|
+
dependency_graph: defaultDependencyGraph,
|
|
42
|
+
runtime_tracks: {},
|
|
43
|
+
downstream_lock_table: {},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function validateMaestroState(state) {
|
|
48
|
+
if (!state || typeof state !== "object" || Array.isArray(state)) {
|
|
49
|
+
throw new Error("Maestro state must be an object");
|
|
50
|
+
}
|
|
51
|
+
if (state.schemaVersion !== "0.1.0") {
|
|
52
|
+
throw new Error(`Unsupported maestro state schemaVersion: ${state.schemaVersion}`);
|
|
53
|
+
}
|
|
54
|
+
if (!(state.orchestration_id === null || typeof state.orchestration_id === "string")) {
|
|
55
|
+
throw new Error("maestro_state.orchestration_id must be null or a string");
|
|
56
|
+
}
|
|
57
|
+
if (!(state.timestamp === null || typeof state.timestamp === "string")) {
|
|
58
|
+
throw new Error("maestro_state.timestamp must be null or a string");
|
|
59
|
+
}
|
|
60
|
+
if (!validGlobalStatuses.has(state.global_status)) {
|
|
61
|
+
throw new Error(`Unsupported maestro_state.global_status: ${state.global_status}`);
|
|
62
|
+
}
|
|
63
|
+
if (!state.configuration || typeof state.configuration !== "object") {
|
|
64
|
+
throw new Error("maestro_state.configuration must be an object");
|
|
65
|
+
}
|
|
66
|
+
if (!state.dependency_graph || typeof state.dependency_graph !== "object") {
|
|
67
|
+
throw new Error("maestro_state.dependency_graph must be an object");
|
|
68
|
+
}
|
|
69
|
+
if (!state.runtime_tracks || typeof state.runtime_tracks !== "object") {
|
|
70
|
+
throw new Error("maestro_state.runtime_tracks must be an object");
|
|
71
|
+
}
|
|
72
|
+
if (!state.downstream_lock_table || typeof state.downstream_lock_table !== "object") {
|
|
73
|
+
throw new Error("maestro_state.downstream_lock_table must be an object");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return state;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export async function seedMaestroStateArtifacts() {
|
|
80
|
+
await writeFileIfMissing(
|
|
81
|
+
getMaestroStatePath(),
|
|
82
|
+
`${JSON.stringify(createDefaultMaestroState(), null, 2)}\n`,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function loadMaestroState() {
|
|
87
|
+
return validateMaestroState(await readJson(getMaestroStatePath()));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export async function loadMaestroStateOrDefault() {
|
|
91
|
+
try {
|
|
92
|
+
return await loadMaestroState();
|
|
93
|
+
} catch (error) {
|
|
94
|
+
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
|
|
95
|
+
return createDefaultMaestroState();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export async function saveMaestroState(state) {
|
|
103
|
+
validateMaestroState(state);
|
|
104
|
+
await writeJson(getMaestroStatePath(), state);
|
|
105
|
+
return state;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function beginMaestroOrchestration(state, { globalStatus, configuration = null } = {}) {
|
|
109
|
+
const next = structuredClone(state);
|
|
110
|
+
if (
|
|
111
|
+
next.orchestration_id === null ||
|
|
112
|
+
["IDLE", "COMPLETED", "LOCKED"].includes(next.global_status)
|
|
113
|
+
) {
|
|
114
|
+
next.orchestration_id = `ma_run_${Date.now()}_${randomUUID().slice(0, 8)}`;
|
|
115
|
+
}
|
|
116
|
+
next.timestamp = new Date().toISOString();
|
|
117
|
+
next.global_status = globalStatus;
|
|
118
|
+
if (configuration) {
|
|
119
|
+
next.configuration = {
|
|
120
|
+
...next.configuration,
|
|
121
|
+
...configuration,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
return next;
|
|
125
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const START = "<!-- MA:MANAGED:START";
|
|
2
|
+
const END = "<!-- MA:MANAGED:END -->";
|
|
3
|
+
|
|
4
|
+
export function createManagedMarkdownBlock({ id, source, body }) {
|
|
5
|
+
return `${START} id=${id} source=${source} -->\n${body.trim()}\n${END}\n`;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function replaceManagedMarkdownBlock(existing, generated, id) {
|
|
9
|
+
const marker = new RegExp(
|
|
10
|
+
`<!-- MA:MANAGED:START id=${escapeRegExp(id)}[^>]*-->[\\s\\S]*?${END}`,
|
|
11
|
+
"g",
|
|
12
|
+
);
|
|
13
|
+
const starts = existing.match(/<!-- MA:MANAGED:START\b/g)?.length ?? 0;
|
|
14
|
+
const ends = existing.match(new RegExp(escapeRegExp(END), "g"))?.length ?? 0;
|
|
15
|
+
if (starts === 1 && ends === 1 && marker.test(existing))
|
|
16
|
+
return existing.replace(marker, generated.trim());
|
|
17
|
+
return `${existing.trimEnd()}\n\n${generated}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function removeManagedMarkdownBlock(existing, id) {
|
|
21
|
+
const marker = new RegExp(
|
|
22
|
+
`\\n?<!-- MA:MANAGED:START id=${escapeRegExp(id)}[^>]*-->[\\s\\S]*?${END}\\n?`,
|
|
23
|
+
);
|
|
24
|
+
return existing.replace(marker, "");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function escapeRegExp(value) {
|
|
28
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
29
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { writeJson } from "../fs-utils.js";
|
|
5
|
+
import { getRuntimeWritePath } from "../paths.js";
|
|
6
|
+
import { resolveHookProfile } from "./hook-profiles.js";
|
|
7
|
+
import { maskSensitiveText } from "./redaction-gateway.js";
|
|
8
|
+
import { queueMailboxProposal } from "./runtime-state.js";
|
|
9
|
+
|
|
10
|
+
const writePolicies = {
|
|
11
|
+
"state.sync_release_status": "state-write",
|
|
12
|
+
"state.append_decision": "state-write",
|
|
13
|
+
"memory.store_note": "memory-write",
|
|
14
|
+
"team_run.submit_task": "task-control",
|
|
15
|
+
"team_run.control_task": "task-control",
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function getField(options, camel, snake) {
|
|
19
|
+
return options?.[camel] ?? options?.[snake];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getProfile(options) {
|
|
23
|
+
const requested = getField(options, "profile", "profile") ?? process.env.MA_MCP_PROFILE;
|
|
24
|
+
return requested ? resolveHookProfile(requested) : resolveHookProfile();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function validateMetadata(options) {
|
|
28
|
+
const metadata = {
|
|
29
|
+
actor: getField(options, "actor", "actor"),
|
|
30
|
+
authority: options?.authority ?? getField(options, "authorityBoundary", "authority_boundary"),
|
|
31
|
+
reason: getField(options, "reason", "reason"),
|
|
32
|
+
evidence: getField(options, "evidence", "evidence"),
|
|
33
|
+
};
|
|
34
|
+
const missing = Object.entries(metadata)
|
|
35
|
+
.filter(([, value]) => value == null || (typeof value === "string" && value.trim() === ""))
|
|
36
|
+
.map(([key]) => key);
|
|
37
|
+
return { metadata, missing };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function receiptPath() {
|
|
41
|
+
return path.join(getRuntimeWritePath("mcp", "receipts"), `${Date.now()}-${randomUUID()}.json`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function writeAuditReceipt({ tool, classification, profile, metadata, outcome, reason }) {
|
|
45
|
+
const safe = maskSensitiveText(
|
|
46
|
+
JSON.stringify({
|
|
47
|
+
actor: metadata.actor,
|
|
48
|
+
authority: metadata.authority,
|
|
49
|
+
reason: metadata.reason,
|
|
50
|
+
evidence: metadata.evidence,
|
|
51
|
+
}),
|
|
52
|
+
).sanitizedText;
|
|
53
|
+
const receipt = {
|
|
54
|
+
schemaVersion: "0.1.0",
|
|
55
|
+
record_type: "mcp_write_audit_receipt",
|
|
56
|
+
tool,
|
|
57
|
+
classification,
|
|
58
|
+
profile: profile.id,
|
|
59
|
+
outcome,
|
|
60
|
+
denial_reason: reason ?? null,
|
|
61
|
+
metadata: JSON.parse(safe),
|
|
62
|
+
recordedAt: new Date().toISOString(),
|
|
63
|
+
};
|
|
64
|
+
const target = receiptPath();
|
|
65
|
+
await writeJson(target, receipt);
|
|
66
|
+
await fs.chmod(target, 0o600).catch(() => {});
|
|
67
|
+
return target;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function listMcpWritePolicies() {
|
|
71
|
+
return Object.entries(writePolicies).map(([tool, classification]) => ({ tool, classification }));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function mcpWriteCapabilityStatus() {
|
|
75
|
+
const profile = resolveHookProfile(
|
|
76
|
+
process.env.MA_MCP_PROFILE ?? process.env.MA_PROFILE ?? "local",
|
|
77
|
+
);
|
|
78
|
+
return {
|
|
79
|
+
profile: profile.id,
|
|
80
|
+
readOnly: profile.readOnly === true,
|
|
81
|
+
tools: listMcpWritePolicies(),
|
|
82
|
+
detail: profile.readOnly
|
|
83
|
+
? `MCP writes disabled by ${profile.id} profile`
|
|
84
|
+
: `MCP writes require actor, authority, reason, and evidence in ${profile.id} profile`,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export async function loadMcpAuditReceipts() {
|
|
89
|
+
const root = getRuntimeWritePath("mcp", "receipts");
|
|
90
|
+
const entries = await fs.readdir(root, { withFileTypes: true }).catch(() => []);
|
|
91
|
+
const receipts = [];
|
|
92
|
+
for (const entry of entries.filter((item) => item.isFile() && item.name.endsWith(".json"))) {
|
|
93
|
+
try {
|
|
94
|
+
receipts.push(JSON.parse(await fs.readFile(path.join(root, entry.name), "utf8")));
|
|
95
|
+
} catch {
|
|
96
|
+
// Ignore incomplete receipts; doctor can still report the directory.
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return receipts.sort((a, b) => `${a.recordedAt}`.localeCompare(`${b.recordedAt}`));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export async function withMcpWriteGate({ tool, options = {}, payload = {} }, operation) {
|
|
103
|
+
const classification = writePolicies[tool];
|
|
104
|
+
if (!classification) throw new Error(`Unknown MCP write tool: ${tool}`);
|
|
105
|
+
const profile = getProfile(options);
|
|
106
|
+
const { metadata, missing } = validateMetadata(options);
|
|
107
|
+
|
|
108
|
+
if (profile.readOnly === true) {
|
|
109
|
+
const auditReceipt = await writeAuditReceipt({
|
|
110
|
+
tool,
|
|
111
|
+
classification,
|
|
112
|
+
profile,
|
|
113
|
+
metadata,
|
|
114
|
+
outcome: "denied",
|
|
115
|
+
reason: "profile_read_only",
|
|
116
|
+
});
|
|
117
|
+
return { proposed: false, denied: true, reason: "profile_read_only", auditReceipt };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (missing.length > 0) {
|
|
121
|
+
const proposalPath = await queueMailboxProposal({
|
|
122
|
+
actor: metadata.actor ?? "mcp-unknown",
|
|
123
|
+
kind: `mcp-${classification}`,
|
|
124
|
+
payload: { tool, payload, missingAuthority: missing },
|
|
125
|
+
});
|
|
126
|
+
const auditReceipt = await writeAuditReceipt({
|
|
127
|
+
tool,
|
|
128
|
+
classification,
|
|
129
|
+
profile,
|
|
130
|
+
metadata,
|
|
131
|
+
outcome: "proposed",
|
|
132
|
+
reason: `missing_authority:${missing.join(",")}`,
|
|
133
|
+
});
|
|
134
|
+
return { proposed: true, proposalPath, auditReceipt, reason: "missing_authority" };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const result = await operation(metadata);
|
|
138
|
+
const auditReceipt = await writeAuditReceipt({
|
|
139
|
+
tool,
|
|
140
|
+
classification,
|
|
141
|
+
profile,
|
|
142
|
+
metadata,
|
|
143
|
+
outcome: result?.proposed ? "proposed" : "allowed",
|
|
144
|
+
reason: result?.proposed ? "leader_authority_required" : null,
|
|
145
|
+
});
|
|
146
|
+
return { ...result, auditReceipt };
|
|
147
|
+
}
|