@jstn-sdk/ma 0.1.11 → 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/.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 +287 -37
- package/bin/ma.js +232 -71
- 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 +7 -1
- package/docs/getting-started.md +108 -37
- package/docs/installed-sdk.md +23 -0
- package/docs/mcp-setup.md +65 -1
- 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.13.md +116 -0
- package/docs/qa/release-readiness-0.1.5.md +1 -1
- package/docs/reference/native-engineering-patterns.md +35 -0
- package/docs/reference/native-security-playbooks.md +27 -0
- package/docs/reference/native-source-selection.md +27 -0
- package/docs/reference/native-style-and-deslop.md +20 -0
- package/docs/release-spec.md +37 -10
- package/docs/skills-publishing.md +25 -1
- package/docs/skills.md +37 -12
- package/index.js +222 -2
- package/mcp/collections.json +23 -6
- package/mcp/local/code-intel.js +113 -0
- package/mcp/local/memory.js +46 -0
- package/mcp/local/playbooks.js +168 -0
- package/mcp/local/state.js +71 -0
- package/mcp/local/team-run.js +113 -0
- package/mcp/local/trace.js +60 -0
- package/mcp/local-capabilities.json +56 -0
- package/mcp/native-playbooks.json +117 -0
- package/mcp/servers.json +34 -0
- package/package.json +8 -2
- package/plugins/meta-architect/.app.json +1 -1
- package/plugins/meta-architect/.codex-plugin/plugin.json +4 -4
- package/plugins/meta-architect/.mcp.json +1 -1
- package/plugins/meta-architect/README.md +34 -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 +24 -0
- package/plugins/meta-architect/skills/align/agents/openai.yaml +4 -0
- package/plugins/meta-architect/skills/align/references/shared-language.md +24 -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/cleanup/SKILL.md +23 -0
- package/plugins/meta-architect/skills/cleanup/agents/openai.yaml +4 -0
- package/plugins/meta-architect/skills/cleanup/references/style-and-deslop.md +18 -0
- package/plugins/meta-architect/skills/diagnose/SKILL.md +24 -0
- package/plugins/meta-architect/skills/diagnose/agents/openai.yaml +4 -0
- package/plugins/meta-architect/skills/flow/SKILL.md +2 -0
- package/plugins/meta-architect/skills/maestro/SKILL.md +36 -3
- package/plugins/meta-architect/skills/maestro/agents/openai.yaml +2 -2
- package/plugins/meta-architect/skills/maestro/references/native-ingest-map.md +44 -0
- package/plugins/meta-architect/skills/sage/SKILL.md +5 -1
- package/plugins/meta-architect/skills/sage/references/source-selection.md +36 -0
- package/plugins/meta-architect/skills/tdd/SKILL.md +24 -0
- package/plugins/meta-architect/skills/tdd/agents/openai.yaml +4 -0
- package/plugins/meta-architect/skills/vet/SKILL.md +5 -1
- package/plugins/meta-architect/skills/vet/references/security-playbooks.md +30 -0
- package/plugins/meta-architect/skills/vibe/SKILL.md +2 -0
- package/scripts/active-autonomy-hook.mjs +96 -0
- package/scripts/build-linux-packages.mjs +328 -0
- package/scripts/doctor.js +36 -4
- package/scripts/install.sh +28 -0
- package/scripts/linux-package-lib.mjs +40 -0
- package/scripts/linux-package-smoke.mjs +103 -0
- package/scripts/ralph/prompt.md +35 -0
- package/scripts/release-sync.js +13 -6
- package/scripts/release-verify.js +166 -1
- package/skills/align/SKILL.md +24 -0
- package/skills/align/agents/openai.yaml +4 -0
- package/skills/align/references/shared-language.md +24 -0
- package/skills/arch/SKILL.md +2 -0
- package/skills/build/SKILL.md +3 -0
- package/skills/cleanup/SKILL.md +23 -0
- package/skills/cleanup/agents/openai.yaml +4 -0
- package/skills/cleanup/references/style-and-deslop.md +18 -0
- package/skills/diagnose/SKILL.md +24 -0
- package/skills/diagnose/agents/openai.yaml +4 -0
- package/skills/flow/SKILL.md +2 -0
- package/skills/index.json +21 -6
- package/skills/maestro/SKILL.md +36 -3
- package/skills/maestro/agents/openai.yaml +2 -2
- package/skills/maestro/references/native-ingest-map.md +44 -0
- package/skills/sage/SKILL.md +5 -1
- package/skills/sage/references/source-selection.md +36 -0
- package/skills/tdd/SKILL.md +24 -0
- package/skills/tdd/agents/openai.yaml +4 -0
- package/skills/vet/SKILL.md +5 -1
- package/skills/vet/references/security-playbooks.md +30 -0
- package/skills/vibe/SKILL.md +2 -0
- package/src/bootstrap.js +141 -24
- package/src/build-gate.js +2 -2
- package/src/decision-log.js +12 -9
- package/src/launcher.js +4 -0
- package/src/mcp-config.js +130 -8
- package/src/mcp-live-client.js +289 -3
- package/src/paths.js +37 -1
- package/src/policy.js +1 -1
- package/src/release-issue-gates.js +190 -0
- package/src/release-state.js +30 -1
- 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 +62 -0
- package/src/runtime/code-graph-rehearse.js +113 -0
- package/src/runtime/context-economy-core.js +276 -0
- package/src/runtime/continuity-notes.js +79 -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 +42 -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 +605 -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 +158 -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 +1223 -0
- package/src/runtime/semantic-recording-core.js +147 -0
- package/src/runtime/signal-hooks.js +67 -0
- package/src/runtime/skills-registry-export.js +670 -0
- package/src/runtime/startup-path.js +14 -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 +25 -0
- package/src/runtime-artifacts.js +407 -84
- package/src/skill-installer.js +53 -5
- package/src/skills.js +1454 -78
- package/src/state-sync.js +51 -8
- package/docs/qa/release-readiness-0.1.11.md +0 -79
- package/plugins/meta-architect/skills/meta-architect/SKILL.md +0 -32
- package/plugins/meta-architect/skills/meta-architect/agents/openai.yaml +0 -4
- package/skills/meta-architect/SKILL.md +0 -32
- package/skills/meta-architect/agents/openai.yaml +0 -4
- /package/plugins/meta-architect/skills/{meta-architect → maestro}/references/core-release-rules.md +0 -0
- /package/skills/{meta-architect → maestro}/references/core-release-rules.md +0 -0
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { ensureDir, readJson, writeFileIfMissing, writeJson } from "../fs-utils.js";
|
|
6
|
+
import { getRepoRoot, getRuntimeSubsystemPath, getRuntimeWritePath } from "../paths.js";
|
|
7
|
+
|
|
8
|
+
export const coreSourceIngestSchemaVersion = "0.1.0";
|
|
9
|
+
|
|
10
|
+
export const coreSourceDefinitions = [
|
|
11
|
+
{
|
|
12
|
+
id: "obsidian-api",
|
|
13
|
+
capability: "obsidian_integration_core",
|
|
14
|
+
source_type: "external_clone",
|
|
15
|
+
repo: "obsidianmd/obsidian-api",
|
|
16
|
+
source_url: "https://github.com/obsidianmd/obsidian-api.git",
|
|
17
|
+
local_dir: "obsidian-api",
|
|
18
|
+
semantic_role: "obsidian_api_contract",
|
|
19
|
+
records_as: "core_source_snapshot",
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: "obsidian-sample-plugin",
|
|
23
|
+
capability: "obsidian_integration_core",
|
|
24
|
+
source_type: "external_clone",
|
|
25
|
+
repo: "obsidianmd/obsidian-sample-plugin",
|
|
26
|
+
source_url: "https://github.com/obsidianmd/obsidian-sample-plugin.git",
|
|
27
|
+
local_dir: "obsidian-sample-plugin",
|
|
28
|
+
semantic_role: "obsidian_plugin_compatibility_reference",
|
|
29
|
+
records_as: "core_source_snapshot",
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: "caveman",
|
|
33
|
+
capability: "context_economy_core",
|
|
34
|
+
source_type: "external_clone",
|
|
35
|
+
repo: "JuliusBrussee/caveman",
|
|
36
|
+
source_url: "https://github.com/JuliusBrussee/caveman.git",
|
|
37
|
+
local_dir: "caveman",
|
|
38
|
+
semantic_role: "context_compression_contract",
|
|
39
|
+
records_as: "core_source_snapshot",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: "prompt-engineering",
|
|
43
|
+
capability: "prompt_strategy_core",
|
|
44
|
+
source_type: "external_clone",
|
|
45
|
+
repo: "NirDiamant/Prompt_Engineering",
|
|
46
|
+
source_url: "https://github.com/NirDiamant/Prompt_Engineering.git",
|
|
47
|
+
local_dir: "prompt-engineering",
|
|
48
|
+
semantic_role: "prompt_technique_catalog",
|
|
49
|
+
records_as: "core_source_snapshot",
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: "ralph-execution-core",
|
|
53
|
+
capability: "ralph_execution_core",
|
|
54
|
+
source_type: "ma_owned_core",
|
|
55
|
+
repo: "meta-architect/ralph-execution-core",
|
|
56
|
+
source_url: null,
|
|
57
|
+
local_dir: null,
|
|
58
|
+
semantic_role: "story_execution_contract",
|
|
59
|
+
records_as: "ma_core_contract",
|
|
60
|
+
local_paths: ["src/runtime/ralph-execution-core.js", "scripts/ralph/prompt.md"],
|
|
61
|
+
},
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
export function getCoreSourcesRootPath() {
|
|
65
|
+
return getRuntimeWritePath("core-sources");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function getCoreSourceIngestPath() {
|
|
69
|
+
return getRuntimeSubsystemPath("context", "core-source-ingest.json");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function createDefaultCoreSourceIngest() {
|
|
73
|
+
return {
|
|
74
|
+
schemaVersion: coreSourceIngestSchemaVersion,
|
|
75
|
+
record_type: "core_source_ingest",
|
|
76
|
+
product: "Meta-Architect",
|
|
77
|
+
purpose:
|
|
78
|
+
"Canonical MA core source snapshots. External projects are cloned/cached once, then MA reads local snapshots instead of fetching on every run.",
|
|
79
|
+
runtime_fetch_required: false,
|
|
80
|
+
refresh_policy: "manual_refresh_via_ma_core_ingest",
|
|
81
|
+
records_as: "core_source_snapshot",
|
|
82
|
+
sources: coreSourceDefinitions.map((definition) => ({
|
|
83
|
+
id: definition.id,
|
|
84
|
+
capability: definition.capability,
|
|
85
|
+
source_type: definition.source_type,
|
|
86
|
+
repo: definition.repo,
|
|
87
|
+
source_url: definition.source_url,
|
|
88
|
+
semantic_role: definition.semantic_role,
|
|
89
|
+
records_as: definition.records_as,
|
|
90
|
+
status: "NOT_INGESTED",
|
|
91
|
+
local_path: definition.local_dir
|
|
92
|
+
? path.join(".ma", "core-sources", definition.local_dir)
|
|
93
|
+
: null,
|
|
94
|
+
file_count: 0,
|
|
95
|
+
content_sha256: null,
|
|
96
|
+
git_commit: null,
|
|
97
|
+
sampled_files: [],
|
|
98
|
+
sample_text: null,
|
|
99
|
+
})),
|
|
100
|
+
hard_rules: [
|
|
101
|
+
"Obsidian, Caveman, Prompt Strategy, and Ralph are MA semantic cores, not MCP servers.",
|
|
102
|
+
"GitMCP or git remotes may refresh provenance, but core behavior reads local source snapshots.",
|
|
103
|
+
"Obsidian-derived claims remain vault_context unless promoted by an owning MA lane.",
|
|
104
|
+
"Ralph Execution Core is MA-owned and cannot mutate release or decision state directly.",
|
|
105
|
+
],
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function validateCoreSourceIngest(value) {
|
|
110
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
111
|
+
throw new Error("core source ingest must be an object");
|
|
112
|
+
}
|
|
113
|
+
if (value.schemaVersion !== coreSourceIngestSchemaVersion) {
|
|
114
|
+
throw new Error(`Unsupported core source ingest schemaVersion: ${value.schemaVersion}`);
|
|
115
|
+
}
|
|
116
|
+
if (value.record_type !== "core_source_ingest") {
|
|
117
|
+
throw new Error("core source ingest requires record_type=core_source_ingest");
|
|
118
|
+
}
|
|
119
|
+
if (value.runtime_fetch_required !== false) {
|
|
120
|
+
throw new Error("core source ingest must not require runtime fetches");
|
|
121
|
+
}
|
|
122
|
+
if (!Array.isArray(value.sources) || value.sources.length < coreSourceDefinitions.length) {
|
|
123
|
+
throw new Error("core source ingest requires all core source entries");
|
|
124
|
+
}
|
|
125
|
+
const byId = new Map(value.sources.map((source) => [source.id, source]));
|
|
126
|
+
for (const definition of coreSourceDefinitions) {
|
|
127
|
+
const source = byId.get(definition.id);
|
|
128
|
+
if (!source) {
|
|
129
|
+
throw new Error(`core source ingest missing ${definition.id}`);
|
|
130
|
+
}
|
|
131
|
+
if (source.capability !== definition.capability) {
|
|
132
|
+
throw new Error(`core source ${definition.id} capability mismatch`);
|
|
133
|
+
}
|
|
134
|
+
if (source.records_as !== definition.records_as) {
|
|
135
|
+
throw new Error(`core source ${definition.id} records_as mismatch`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return value;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export async function seedCoreSourceIngestArtifacts() {
|
|
142
|
+
await ensureDir(getRuntimeSubsystemPath("context"));
|
|
143
|
+
await writeFileIfMissing(
|
|
144
|
+
getCoreSourceIngestPath(),
|
|
145
|
+
`${JSON.stringify(createDefaultCoreSourceIngest(), null, 2)}\n`,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export async function loadCoreSourceIngest() {
|
|
150
|
+
return validateCoreSourceIngest(await readJson(getCoreSourceIngestPath()));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export async function ingestCoreSources({
|
|
154
|
+
definitions = coreSourceDefinitions,
|
|
155
|
+
refresh = false,
|
|
156
|
+
spawnImpl = spawn,
|
|
157
|
+
} = {}) {
|
|
158
|
+
await ensureDir(getCoreSourcesRootPath());
|
|
159
|
+
const sources = [];
|
|
160
|
+
const errors = [];
|
|
161
|
+
|
|
162
|
+
for (const definition of definitions) {
|
|
163
|
+
try {
|
|
164
|
+
if (definition.source_type === "ma_owned_core") {
|
|
165
|
+
sources.push(await inspectMaOwnedCore(definition));
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const localPath = path.join(getCoreSourcesRootPath(), definition.local_dir);
|
|
170
|
+
if (refresh) {
|
|
171
|
+
await fs.rm(localPath, { recursive: true, force: true });
|
|
172
|
+
}
|
|
173
|
+
if (!(await pathExists(localPath))) {
|
|
174
|
+
await cloneSource(definition.source_url, localPath, spawnImpl);
|
|
175
|
+
}
|
|
176
|
+
sources.push(await inspectExternalCore(definition, localPath));
|
|
177
|
+
} catch (error) {
|
|
178
|
+
errors.push({ id: definition.id, repo: definition.repo, error: error.message });
|
|
179
|
+
sources.push(createFailedSource(definition, error));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const manifest = validateCoreSourceIngest({
|
|
184
|
+
...createDefaultCoreSourceIngest(),
|
|
185
|
+
ingested_at: new Date().toISOString(),
|
|
186
|
+
status: errors.length === 0 ? "READY" : "PARTIAL",
|
|
187
|
+
sources,
|
|
188
|
+
errors,
|
|
189
|
+
});
|
|
190
|
+
await writeJson(getCoreSourceIngestPath(), manifest);
|
|
191
|
+
return manifest;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function findIngestedCoreSourceForRepo(manifest, repo) {
|
|
195
|
+
if (!manifest?.sources || !repo) return null;
|
|
196
|
+
return manifest.sources.find(
|
|
197
|
+
(source) => source.repo === repo && ["INGESTED", "LOCAL_CORE"].includes(source.status),
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function inspectExternalCore(definition, localPath) {
|
|
202
|
+
const files = await listSourceFiles(localPath);
|
|
203
|
+
const sampledFiles = chooseSampleFiles(files);
|
|
204
|
+
const sampleText = await readSampleText(localPath, sampledFiles);
|
|
205
|
+
const gitCommit = await readGitCommit(localPath);
|
|
206
|
+
return {
|
|
207
|
+
id: definition.id,
|
|
208
|
+
capability: definition.capability,
|
|
209
|
+
source_type: definition.source_type,
|
|
210
|
+
repo: definition.repo,
|
|
211
|
+
source_url: definition.source_url,
|
|
212
|
+
semantic_role: definition.semantic_role,
|
|
213
|
+
records_as: definition.records_as,
|
|
214
|
+
status: "INGESTED",
|
|
215
|
+
local_path: path.relative(getRepoRoot(), localPath),
|
|
216
|
+
file_count: files.length,
|
|
217
|
+
content_sha256: hashFileList(files),
|
|
218
|
+
git_commit: gitCommit,
|
|
219
|
+
sampled_files: sampledFiles,
|
|
220
|
+
sample_text: sampleText,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async function inspectMaOwnedCore(definition) {
|
|
225
|
+
const repoRoot = getRepoRoot();
|
|
226
|
+
const files = [];
|
|
227
|
+
for (const relativePath of definition.local_paths ?? []) {
|
|
228
|
+
const absolutePath = path.join(repoRoot, relativePath);
|
|
229
|
+
if (await pathExists(absolutePath)) {
|
|
230
|
+
const stat = await fs.stat(absolutePath);
|
|
231
|
+
files.push({
|
|
232
|
+
relative_path: relativePath,
|
|
233
|
+
size: stat.size,
|
|
234
|
+
mtimeMs: Math.floor(stat.mtimeMs),
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
const samplePath = files[0]?.relative_path;
|
|
239
|
+
const sampleText = samplePath
|
|
240
|
+
? (await fs.readFile(path.join(repoRoot, samplePath), "utf8")).slice(0, 600)
|
|
241
|
+
: null;
|
|
242
|
+
return {
|
|
243
|
+
id: definition.id,
|
|
244
|
+
capability: definition.capability,
|
|
245
|
+
source_type: definition.source_type,
|
|
246
|
+
repo: definition.repo,
|
|
247
|
+
source_url: null,
|
|
248
|
+
semantic_role: definition.semantic_role,
|
|
249
|
+
records_as: definition.records_as,
|
|
250
|
+
status: "LOCAL_CORE",
|
|
251
|
+
local_path: null,
|
|
252
|
+
file_count: files.length,
|
|
253
|
+
content_sha256: hashFileList(files),
|
|
254
|
+
git_commit: null,
|
|
255
|
+
sampled_files: files.map((file) => file.relative_path),
|
|
256
|
+
sample_text: sampleText,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function createFailedSource(definition, error) {
|
|
261
|
+
return {
|
|
262
|
+
id: definition.id,
|
|
263
|
+
capability: definition.capability,
|
|
264
|
+
source_type: definition.source_type,
|
|
265
|
+
repo: definition.repo,
|
|
266
|
+
source_url: definition.source_url,
|
|
267
|
+
semantic_role: definition.semantic_role,
|
|
268
|
+
records_as: definition.records_as,
|
|
269
|
+
status: "FAILED",
|
|
270
|
+
local_path: definition.local_dir
|
|
271
|
+
? path.join(".ma", "core-sources", definition.local_dir)
|
|
272
|
+
: null,
|
|
273
|
+
file_count: 0,
|
|
274
|
+
content_sha256: null,
|
|
275
|
+
git_commit: null,
|
|
276
|
+
sampled_files: [],
|
|
277
|
+
sample_text: null,
|
|
278
|
+
error: error.message,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
async function cloneSource(sourceUrl, localPath, spawnImpl) {
|
|
283
|
+
await ensureDir(path.dirname(localPath));
|
|
284
|
+
await new Promise((resolve, reject) => {
|
|
285
|
+
const child = spawnImpl("git", ["clone", "--depth", "1", sourceUrl, localPath], {
|
|
286
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
287
|
+
});
|
|
288
|
+
let stderr = "";
|
|
289
|
+
child.stderr.on("data", (chunk) => {
|
|
290
|
+
stderr = `${stderr}${chunk}`.slice(-2000);
|
|
291
|
+
});
|
|
292
|
+
child.on("error", reject);
|
|
293
|
+
child.on("exit", (code) => {
|
|
294
|
+
if (code === 0) {
|
|
295
|
+
resolve();
|
|
296
|
+
} else {
|
|
297
|
+
reject(new Error(`git clone failed for ${sourceUrl}: ${stderr.trim() || `exit ${code}`}`));
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async function listSourceFiles(rootPath) {
|
|
304
|
+
const files = [];
|
|
305
|
+
async function walk(current) {
|
|
306
|
+
const entries = await fs.readdir(current, { withFileTypes: true });
|
|
307
|
+
for (const entry of entries) {
|
|
308
|
+
if (entry.name === ".git" || entry.name === "node_modules") continue;
|
|
309
|
+
const absolutePath = path.join(current, entry.name);
|
|
310
|
+
if (entry.isDirectory()) {
|
|
311
|
+
await walk(absolutePath);
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
if (!entry.isFile()) continue;
|
|
315
|
+
const stat = await fs.stat(absolutePath);
|
|
316
|
+
files.push({
|
|
317
|
+
relative_path: path.relative(rootPath, absolutePath).split(path.sep).join("/"),
|
|
318
|
+
size: stat.size,
|
|
319
|
+
mtimeMs: Math.floor(stat.mtimeMs),
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
await walk(rootPath);
|
|
324
|
+
return files.sort((a, b) => a.relative_path.localeCompare(b.relative_path));
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function chooseSampleFiles(files) {
|
|
328
|
+
const preferred = [/^README\.md$/i, /^INSTALL\.md$/i, /^docs\//i, /SKILL\.md$/i, /prompt\.md$/i];
|
|
329
|
+
const selected = [];
|
|
330
|
+
for (const pattern of preferred) {
|
|
331
|
+
const match = files.find((file) => pattern.test(file.relative_path));
|
|
332
|
+
if (match && !selected.includes(match.relative_path)) {
|
|
333
|
+
selected.push(match.relative_path);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
for (const file of files) {
|
|
337
|
+
if (selected.length >= 4) break;
|
|
338
|
+
if (!selected.includes(file.relative_path) && /\.(md|ts|js|json)$/i.test(file.relative_path)) {
|
|
339
|
+
selected.push(file.relative_path);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return selected;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
async function readSampleText(rootPath, sampledFiles) {
|
|
346
|
+
for (const relativePath of sampledFiles) {
|
|
347
|
+
try {
|
|
348
|
+
return (await fs.readFile(path.join(rootPath, relativePath), "utf8")).slice(0, 600);
|
|
349
|
+
} catch {}
|
|
350
|
+
}
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
async function readGitCommit(localPath) {
|
|
355
|
+
try {
|
|
356
|
+
return (await fs.readFile(path.join(localPath, ".git", "shallow"), "utf8"))
|
|
357
|
+
.trim()
|
|
358
|
+
.split("\n")[0];
|
|
359
|
+
} catch {
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function hashFileList(files) {
|
|
365
|
+
const hash = createHash("sha256");
|
|
366
|
+
for (const file of files) {
|
|
367
|
+
hash.update(`${file.relative_path}:${file.size}:${file.mtimeMs}\n`);
|
|
368
|
+
}
|
|
369
|
+
return hash.digest("hex");
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
async function pathExists(filePath) {
|
|
373
|
+
try {
|
|
374
|
+
await fs.access(filePath);
|
|
375
|
+
return true;
|
|
376
|
+
} catch {
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { ensureDir } from "../fs-utils.js";
|
|
5
|
+
import { getRuntimeSubsystemPath } from "../paths.js";
|
|
6
|
+
|
|
7
|
+
export function getRuntimeLogsRoot() {
|
|
8
|
+
return getRuntimeSubsystemPath("logs");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function resolveDetachedProvider() {
|
|
12
|
+
const preferred = process.env.MA_DETACHED_PROVIDER ?? "";
|
|
13
|
+
if (preferred === "tmux" || process.env.MA_TMUX_PROVIDER === "1") {
|
|
14
|
+
const probe = spawnSync("tmux", ["ls"], { encoding: "utf8" });
|
|
15
|
+
if (!probe.error) {
|
|
16
|
+
return "tmux";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return "none";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function launchDetachedTrack({ trackId, title, command, args = [] }) {
|
|
24
|
+
const provider = resolveDetachedProvider();
|
|
25
|
+
await ensureDir(getRuntimeLogsRoot());
|
|
26
|
+
const logPath = path.join(getRuntimeLogsRoot(), `${trackId}.log`);
|
|
27
|
+
|
|
28
|
+
if (provider !== "tmux") {
|
|
29
|
+
await fs.writeFile(
|
|
30
|
+
logPath,
|
|
31
|
+
`provider=none\ntitle=${title}\ncommand=${[command, ...args].join(" ")}\n`,
|
|
32
|
+
"utf8",
|
|
33
|
+
);
|
|
34
|
+
return {
|
|
35
|
+
provider: "none",
|
|
36
|
+
executionPane: null,
|
|
37
|
+
pid: null,
|
|
38
|
+
logPath,
|
|
39
|
+
started: false,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const sessionName = `ma_${trackId.replace(/[^a-zA-Z0-9_-]/g, "_")}`;
|
|
44
|
+
const shellCommand = `${[command, ...args].join(" ")} > ${logPath} 2>&1`;
|
|
45
|
+
const started = spawnSync(
|
|
46
|
+
"tmux",
|
|
47
|
+
["new-session", "-d", "-s", sessionName, "/bin/sh", "-lc", shellCommand],
|
|
48
|
+
{ encoding: "utf8" },
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
if (started.status !== 0) {
|
|
52
|
+
await fs.writeFile(
|
|
53
|
+
logPath,
|
|
54
|
+
`provider=tmux\nstatus=failed\nstderr=${started.stderr ?? ""}\n`,
|
|
55
|
+
"utf8",
|
|
56
|
+
);
|
|
57
|
+
return {
|
|
58
|
+
provider: "tmux",
|
|
59
|
+
executionPane: sessionName,
|
|
60
|
+
pid: null,
|
|
61
|
+
logPath,
|
|
62
|
+
started: false,
|
|
63
|
+
error: started.stderr?.trim() || "tmux launch failed",
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
provider: "tmux",
|
|
69
|
+
executionPane: sessionName,
|
|
70
|
+
pid: null,
|
|
71
|
+
logPath,
|
|
72
|
+
started: true,
|
|
73
|
+
};
|
|
74
|
+
}
|