@jstn-sdk/ma 0.14.0 → 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/COVERAGE.md +9 -9
- package/DEMO.md +4 -4
- package/README.md +80 -70
- package/bin/ma.js +6 -8
- package/docs/README.md +2 -2
- package/docs/assets/banner.png +0 -0
- package/docs/getting-started.md +9 -9
- package/docs/qa/{release-issue-gates-0.14.0.json → release-issue-gates-0.14.1.json} +77 -77
- package/docs/qa/{release-readiness-0.14.0.md → release-readiness-0.14.1.md} +12 -12
- package/docs/release-spec.md +12 -12
- package/docs/vendor-plugin-publishing.md +49 -0
- package/mcp/local/code-intel.js +9 -3
- package/package.json +12 -3
- 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 +13 -1
- package/plugins/meta-architect/obsidian/manifest.json +1 -1
- package/plugins/meta-architect/skills/maestro/SKILL.md +17 -1
- package/plugins/meta-architect/skills/maestro/references/core-release-rules.md +2 -2
- package/schemas/autonomous-task-queue.schema.json +2 -0
- package/scripts/install.sh +1 -1
- package/scripts/install.sh.sha256 +1 -1
- package/scripts/plugin-build.js +314 -0
- package/scripts/plugin-publish.js +77 -0
- package/scripts/release-sync.js +11 -0
- package/scripts/release-verify.js +35 -4
- package/skills/maestro/SKILL.md +17 -1
- package/skills/maestro/references/core-release-rules.md +2 -2
- package/sprint/07-release.md +2 -2
- package/src/agents.js +4 -2
- package/src/bootstrap.js +20 -2
- package/src/codex-app-server.js +4 -4
- package/src/launcher.js +2 -2
- package/src/mcp-live-client.js +2 -2
- package/src/prelaunch.js +34 -0
- package/src/process-utils.js +62 -0
- package/src/quality/ai-quality-orchestrator.js +2 -2
- package/src/release-operations.js +14 -4
- package/src/runtime/architect-review.js +2 -2
- package/src/runtime/autonomous-tasks.js +220 -43
- package/src/runtime/codeburn-core.js +9 -3
- package/src/runtime/core-source-ingest.js +2 -2
- package/src/runtime/detached-provider.js +17 -8
- package/src/runtime/live-agent-verification.js +2 -2
- package/src/runtime/project-context.js +2 -2
- package/src/runtime/skills-registry-export.js +7 -3
- package/src/skills.js +12 -12
- package/src/test-fixtures.js +4 -4
- package/support-bundle.json +1 -1
- package/scripts/postinstall.js +0 -30
package/skills/maestro/SKILL.md
CHANGED
|
@@ -7,7 +7,7 @@ description: "Use when the user wants the singular Meta-Architect in-session aut
|
|
|
7
7
|
|
|
8
8
|
## Overview
|
|
9
9
|
|
|
10
|
-
Use this skill inside Codex as the singular Meta-Architect umbrella surface and
|
|
10
|
+
Use this skill inside Codex as the singular Meta-Architect umbrella surface and persistent autonomous manager. It accepts one task or a durable batch, inspects workflow state, chooses the smallest safe next step, manages the fixed gated design-and-review sequence, and keeps resuming eligible work until each task is completed, blocked, cancelled, or failed. It never invents a second umbrella command. There is no separate shipped `$meta-architect` skill.
|
|
11
11
|
|
|
12
12
|
## Workflow
|
|
13
13
|
|
|
@@ -30,6 +30,19 @@ Use this skill inside Codex as the singular Meta-Architect umbrella surface and
|
|
|
30
30
|
- `$build`
|
|
31
31
|
5. End with a clear result shape: decision, evidence, blockers, the lane assignment if any, and the exact next trigger.
|
|
32
32
|
|
|
33
|
+
## Autonomous task loop
|
|
34
|
+
|
|
35
|
+
For a task or batch, keep the manager loop active across turns:
|
|
36
|
+
|
|
37
|
+
1. Normalize the request into the durable `.ma` task contract before dispatch.
|
|
38
|
+
2. Discover the selected host, available skills, and required lane syntax without taking ownership of user-installed assets.
|
|
39
|
+
3. Dispatch independent tasks with bounded concurrency and preserve dependency order for dependent tasks.
|
|
40
|
+
4. Inspect, implement, test, review, repair, and verify. Route a failure back to its owning lane with the failure evidence attached.
|
|
41
|
+
5. Persist checkpoints, receipts, and the next trigger after every transition so an interrupted process can resume safely.
|
|
42
|
+
6. Continue unaffected batch tasks when one task is blocked, then return a batch summary with terminal states and unresolved blockers.
|
|
43
|
+
|
|
44
|
+
Routine local work continues without asking for another prompt. Stop only for credentials, destructive actions, production or external mutations, explicit approval gates, unsafe commands, unavailable required providers, or missing verification evidence.
|
|
45
|
+
|
|
33
46
|
## Output
|
|
34
47
|
|
|
35
48
|
Produce:
|
|
@@ -43,6 +56,9 @@ Produce:
|
|
|
43
56
|
## Rules
|
|
44
57
|
|
|
45
58
|
- Prefer the smallest next step that moves the workflow forward safely.
|
|
59
|
+
- Continue eligible work until the current task or batch reaches a terminal state; do not stop after a single successful lane.
|
|
60
|
+
- Treat persisted `.ma` state as the source for resume, deduplication, dependency ordering, cancellation, and bounded retries.
|
|
61
|
+
- Keep autonomous execution bounded by task dependencies, concurrency limits, deadlines, retry limits, and explicit safety gates.
|
|
46
62
|
- Respect current gate state before recommending implementation or release work.
|
|
47
63
|
- Be explicit when more evidence, planning, or validation is still needed.
|
|
48
64
|
- Treat the in-session skill flow as primary. Use `ma ...` terminal helpers only when repo-local setup, inspection, or scripted state automation is explicitly the better support path.
|
|
@@ -8,6 +8,6 @@
|
|
|
8
8
|
- `logic_status = GREEN`
|
|
9
9
|
- `security_status = GREEN`
|
|
10
10
|
- `experience_status = GREEN` or `WAIVED`
|
|
11
|
-
- Feature work merges into `
|
|
12
|
-
- Release promotion is allowed only from `
|
|
11
|
+
- Feature work merges into `dev`, never directly into `main`.
|
|
12
|
+
- Release promotion is allowed only from `dev` or approved `release/*`.
|
|
13
13
|
- Use the helper command path only when repo-local state automation is explicitly needed; otherwise stay inside Codex and carry the gate decisions in the session.
|
package/sprint/07-release.md
CHANGED
|
@@ -16,8 +16,8 @@ Move from completed bounded implementation to a controlled merge and release pat
|
|
|
16
16
|
|
|
17
17
|
## Exit criteria
|
|
18
18
|
|
|
19
|
-
- merge follows `feature/* ->
|
|
20
|
-
- release follows `
|
|
19
|
+
- merge follows `feature/* -> dev`
|
|
20
|
+
- release follows `dev|release/* -> main`
|
|
21
21
|
|
|
22
22
|
## Failure conditions
|
|
23
23
|
|
package/src/agents.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
2
1
|
import path from "node:path";
|
|
2
|
+
import { safeSpawnSync } from "./process-utils.js";
|
|
3
3
|
|
|
4
4
|
const definitions = {
|
|
5
5
|
codex: {
|
|
@@ -240,11 +240,13 @@ export function detectInstalled(type = process.env.MA_AGENT || "codex") {
|
|
|
240
240
|
};
|
|
241
241
|
}
|
|
242
242
|
const script = [".js", ".mjs", ".cjs"].includes(path.extname(command));
|
|
243
|
-
const result =
|
|
243
|
+
const result = safeSpawnSync(
|
|
244
244
|
script ? process.execPath : command,
|
|
245
245
|
script ? [command, "--version"] : ["--version"],
|
|
246
246
|
{
|
|
247
247
|
encoding: "utf8",
|
|
248
|
+
shell: false,
|
|
249
|
+
timeout: 5_000,
|
|
248
250
|
},
|
|
249
251
|
);
|
|
250
252
|
return {
|
package/src/bootstrap.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
2
1
|
import fs from "node:fs/promises";
|
|
3
2
|
import os from "node:os";
|
|
4
3
|
import path from "node:path";
|
|
@@ -12,8 +11,10 @@ import {
|
|
|
12
11
|
} from "./mcp-config.js";
|
|
13
12
|
import { getMcpBridgeConfiguration, mcpClientVersion } from "./mcp-live-client.js";
|
|
14
13
|
import { getMcpRootPath, getRepoRoot, getRuntimeWritePath, packageRoot } from "./paths.js";
|
|
14
|
+
import { safeSpawnSync } from "./process-utils.js";
|
|
15
15
|
import { resolveReleaseIssueGates } from "./release-issue-gates.js";
|
|
16
16
|
import { loadReleaseState } from "./release-state.js";
|
|
17
|
+
import { validateAgentIntegrations } from "./runtime/agent-compat.js";
|
|
17
18
|
import { getArchitectReviewConfiguration } from "./runtime/architect-review.js";
|
|
18
19
|
import { printDoctorStatuses, summarizeDoctorStatuses } from "./runtime/doctor-report.js";
|
|
19
20
|
import { mcpWriteCapabilityStatus } from "./runtime/mcp-authority.js";
|
|
@@ -238,7 +239,7 @@ function inspectAgent(type = process.env.MA_AGENT || "codex") {
|
|
|
238
239
|
}
|
|
239
240
|
|
|
240
241
|
function listCodexPaths() {
|
|
241
|
-
const result =
|
|
242
|
+
const result = safeSpawnSync("which", ["-a", "codex"], { encoding: "utf8" });
|
|
242
243
|
if (result.error || result.status !== 0) {
|
|
243
244
|
return [];
|
|
244
245
|
}
|
|
@@ -599,6 +600,23 @@ async function runEnvironmentFlow({ fix, initMcp }) {
|
|
|
599
600
|
statuses.push(await inspectLocalCapabilityState({ fix }));
|
|
600
601
|
statuses.push(...(await inspectContextHealth()));
|
|
601
602
|
statuses.push(await inspectPluginInjectionState());
|
|
603
|
+
if (agent.id === "cursor") {
|
|
604
|
+
try {
|
|
605
|
+
const validation = await validateAgentIntegrations(getRepoRoot(), { targets: ["cursor"] });
|
|
606
|
+
const cursorResult = validation.results.cursor;
|
|
607
|
+
statuses.push(
|
|
608
|
+
makeStatus(
|
|
609
|
+
cursorResult?.status === "valid" ? "OK" : "WARN",
|
|
610
|
+
"Cursor native project rules",
|
|
611
|
+
cursorResult?.status === "valid"
|
|
612
|
+
? ".cursor/rules/agents.mdc validated"
|
|
613
|
+
: cursorResult?.issues?.map((issue) => issue.message).join(", ") || "run `ma setup`",
|
|
614
|
+
),
|
|
615
|
+
);
|
|
616
|
+
} catch (error) {
|
|
617
|
+
statuses.push(makeStatus("WARN", "Cursor native project rules", error.message));
|
|
618
|
+
}
|
|
619
|
+
}
|
|
602
620
|
return statuses;
|
|
603
621
|
}
|
|
604
622
|
|
package/src/codex-app-server.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
1
|
import fs from "node:fs";
|
|
2
|
+
import { safeSpawn } from "./process-utils.js";
|
|
3
3
|
|
|
4
4
|
const PACKAGE_VERSION = JSON.parse(
|
|
5
5
|
fs.readFileSync(new URL("../package.json", import.meta.url), "utf8"),
|
|
@@ -144,7 +144,7 @@ export class CodexAppServerClient {
|
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
export function createStdioTransport(command = "codex") {
|
|
147
|
-
const child =
|
|
147
|
+
const child = safeSpawn(command, ["app-server", "--stdio"], {
|
|
148
148
|
stdio: ["pipe", "pipe", "inherit"],
|
|
149
149
|
shell: false,
|
|
150
150
|
});
|
|
@@ -205,7 +205,7 @@ export function runCodexExec({
|
|
|
205
205
|
} = {}) {
|
|
206
206
|
if (typeof prompt !== "string" || !prompt.trim()) throw new Error("Codex exec requires a prompt");
|
|
207
207
|
return new Promise((resolve, reject) => {
|
|
208
|
-
const child =
|
|
208
|
+
const child = safeSpawn(command, ["exec", "--json", ...args, prompt], {
|
|
209
209
|
cwd,
|
|
210
210
|
stdio: ["ignore", "pipe", "pipe"],
|
|
211
211
|
shell: false,
|
|
@@ -250,7 +250,7 @@ export function generateCodexBindings({
|
|
|
250
250
|
if (!outDir) throw new Error("Codex binding generation requires an output directory");
|
|
251
251
|
const generator = kind === "json-schema" ? "generate-json-schema" : "generate-ts";
|
|
252
252
|
return new Promise((resolve, reject) => {
|
|
253
|
-
const child =
|
|
253
|
+
const child = safeSpawn(command, ["app-server", generator, "--out", outDir], {
|
|
254
254
|
cwd,
|
|
255
255
|
stdio: ["ignore", "pipe", "pipe"],
|
|
256
256
|
shell: false,
|
package/src/launcher.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
2
1
|
import path from "node:path";
|
|
3
2
|
import { getAgent, resolveAgentCommand } from "./agents.js";
|
|
3
|
+
import { safeSpawnSync } from "./process-utils.js";
|
|
4
4
|
|
|
5
5
|
const nativeCommands = new Set([
|
|
6
6
|
"bootstrap",
|
|
@@ -46,7 +46,7 @@ export function runAgent(args, agentType = process.env.MA_AGENT || "codex") {
|
|
|
46
46
|
: agentArgs;
|
|
47
47
|
const command = commandArgs[0] === agentCommand ? process.execPath : agentCommand;
|
|
48
48
|
const finalArgs = command === process.execPath ? commandArgs : agentArgs;
|
|
49
|
-
const result =
|
|
49
|
+
const result = safeSpawnSync(command, finalArgs, { stdio: "inherit" });
|
|
50
50
|
|
|
51
51
|
if (result.error) {
|
|
52
52
|
if (result.error.code === "ENOENT") {
|
package/src/mcp-live-client.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
1
|
import { readFileSync } from "node:fs";
|
|
3
2
|
import fs from "node:fs/promises";
|
|
4
3
|
import path from "node:path";
|
|
4
|
+
import { safeSpawn } from "./process-utils.js";
|
|
5
5
|
import { maskSensitiveText } from "./runtime/redaction-gateway.js";
|
|
6
6
|
|
|
7
7
|
const packageMetadata = JSON.parse(
|
|
@@ -63,7 +63,7 @@ export function createMcpLiveClient(endpoint) {
|
|
|
63
63
|
return new McpStdioBridgeClient(
|
|
64
64
|
endpoint,
|
|
65
65
|
process.env[BRIDGE_COMMAND_ENV],
|
|
66
|
-
|
|
66
|
+
safeSpawn,
|
|
67
67
|
getMcpBridgeConfiguration(),
|
|
68
68
|
);
|
|
69
69
|
}
|
package/src/prelaunch.js
CHANGED
|
@@ -3,6 +3,7 @@ import os from "node:os";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { stdin as input, stdout as output } from "node:process";
|
|
5
5
|
import readline from "node:readline/promises";
|
|
6
|
+
import { Agents } from "@jstn-sdk/agents";
|
|
6
7
|
import { agentRegistry, detectInstalled, listAgents } from "./agents.js";
|
|
7
8
|
import {
|
|
8
9
|
createSkillCompatibilityPayload,
|
|
@@ -159,6 +160,38 @@ export async function installPrelaunchSelection(selection, cwd = process.cwd())
|
|
|
159
160
|
capabilities: ["maestro", "agent-compatibility"],
|
|
160
161
|
});
|
|
161
162
|
const results = [];
|
|
163
|
+
if (selection.scope === "project" && selection.targets.includes("cursor")) {
|
|
164
|
+
results.push(await ensureSkillsInstalled({ targetRoot: path.join(cwd, ".agents", "skills") }));
|
|
165
|
+
results.push(
|
|
166
|
+
await Agents.compile(
|
|
167
|
+
{
|
|
168
|
+
version: 1,
|
|
169
|
+
project: { name: path.basename(path.resolve(cwd)) },
|
|
170
|
+
instructions: [
|
|
171
|
+
"Use Meta-Architect lanes for architecture, evidence, review, and gated build work.",
|
|
172
|
+
"Run `ma run '$maestro'` when the host supports shell execution, or invoke /maestro in Cursor Agent.",
|
|
173
|
+
],
|
|
174
|
+
},
|
|
175
|
+
{ targets: ["cursor"], output: cwd },
|
|
176
|
+
),
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
if (selection.scope === "project" && selection.targets.includes("claude-code")) {
|
|
180
|
+
results.push(await ensureSkillsInstalled({ targetRoot: path.join(cwd, ".claude", "skills") }));
|
|
181
|
+
results.push(
|
|
182
|
+
await Agents.compile(
|
|
183
|
+
{
|
|
184
|
+
version: 1,
|
|
185
|
+
project: { name: path.basename(path.resolve(cwd)) },
|
|
186
|
+
instructions: [
|
|
187
|
+
"Use Meta-Architect lanes for architecture, evidence, review, and gated build work.",
|
|
188
|
+
"Invoke /maestro to run the autonomous Meta-Architect manager in Claude Code.",
|
|
189
|
+
],
|
|
190
|
+
},
|
|
191
|
+
{ targets: ["claude-code"], output: cwd },
|
|
192
|
+
),
|
|
193
|
+
);
|
|
194
|
+
}
|
|
162
195
|
if (selection.targets.includes("codex")) {
|
|
163
196
|
const skillsRoot =
|
|
164
197
|
selection.scope === "global" ? undefined : path.join(cwd, ".agents", "skills");
|
|
@@ -175,6 +208,7 @@ export async function installPrelaunchSelection(selection, cwd = process.cwd())
|
|
|
175
208
|
await writeSkillCompatibilityExport({
|
|
176
209
|
payload,
|
|
177
210
|
agentType: target,
|
|
211
|
+
renderAgentType: selection.targets.length > 1 ? "generic" : target,
|
|
178
212
|
cwd,
|
|
179
213
|
global: selection.scope === "global",
|
|
180
214
|
agentRootExists: true,
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { execFile, execFileSync, spawn, spawnSync } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
|
|
4
|
+
const unsafeExecutable = /[;&|<>`$]/;
|
|
5
|
+
|
|
6
|
+
export function assertSafeExecutable(command, label = "Executable") {
|
|
7
|
+
if (
|
|
8
|
+
typeof command !== "string" ||
|
|
9
|
+
command.trim() === "" ||
|
|
10
|
+
command !== command.trim() ||
|
|
11
|
+
command.includes("\0") ||
|
|
12
|
+
command.includes("\r") ||
|
|
13
|
+
command.includes("\n") ||
|
|
14
|
+
unsafeExecutable.test(command)
|
|
15
|
+
) {
|
|
16
|
+
throw new Error(`${label} contains an unsafe executable value`);
|
|
17
|
+
}
|
|
18
|
+
return command;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function assertArgs(args) {
|
|
22
|
+
if (!Array.isArray(args) || args.some((arg) => typeof arg !== "string" || arg.includes("\0")))
|
|
23
|
+
throw new Error("Process arguments must be an array of strings without NUL bytes");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function safeOptions(options = {}) {
|
|
27
|
+
return { ...options, shell: false };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function safeSpawn(command, args = [], options = {}) {
|
|
31
|
+
assertSafeExecutable(command);
|
|
32
|
+
assertArgs(args);
|
|
33
|
+
return spawn(command, args, safeOptions(options));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function safeSpawnSync(command, args = [], options = {}) {
|
|
37
|
+
assertSafeExecutable(command);
|
|
38
|
+
assertArgs(args);
|
|
39
|
+
return spawnSync(command, args, safeOptions(options));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function safeExecFile(command, args = [], options = {}, callback) {
|
|
43
|
+
assertSafeExecutable(command);
|
|
44
|
+
assertArgs(args);
|
|
45
|
+
if (typeof options === "function") {
|
|
46
|
+
callback = options;
|
|
47
|
+
options = {};
|
|
48
|
+
}
|
|
49
|
+
return execFile(command, args, safeOptions(options), callback);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function safeExecFileSync(command, args = [], options = {}) {
|
|
53
|
+
assertSafeExecutable(command);
|
|
54
|
+
assertArgs(args);
|
|
55
|
+
return execFileSync(command, args, safeOptions(options));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
safeExecFile[promisify.custom] = (command, args = [], options = {}) => {
|
|
59
|
+
assertSafeExecutable(command);
|
|
60
|
+
assertArgs(args);
|
|
61
|
+
return promisify(execFile)(command, args, safeOptions(options));
|
|
62
|
+
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
1
|
import { randomUUID } from "node:crypto";
|
|
3
2
|
import fs from "node:fs/promises";
|
|
4
3
|
import path from "node:path";
|
|
5
4
|
import { writeJson } from "../fs-utils.js";
|
|
6
5
|
import { packageRoot } from "../paths.js";
|
|
6
|
+
import { safeSpawn } from "../process-utils.js";
|
|
7
7
|
|
|
8
8
|
const QUALITY_ROOT = path.join(".ma", "quality");
|
|
9
9
|
let appendQueue = Promise.resolve();
|
|
@@ -94,7 +94,7 @@ function calculateQualityScore(kpis) {
|
|
|
94
94
|
|
|
95
95
|
function runCommand(command, args, options = {}) {
|
|
96
96
|
return new Promise((resolve, reject) => {
|
|
97
|
-
const child =
|
|
97
|
+
const child = safeSpawn(command, args, {
|
|
98
98
|
cwd: options.cwd,
|
|
99
99
|
shell: false,
|
|
100
100
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { execFile } from "node:child_process";
|
|
2
1
|
import { promisify } from "node:util";
|
|
2
|
+
import { safeExecFile } from "./process-utils.js";
|
|
3
3
|
|
|
4
|
-
const execFileAsync = promisify(
|
|
4
|
+
const execFileAsync = promisify(safeExecFile);
|
|
5
5
|
const branchPattern = /^[A-Za-z0-9._/-]+$/;
|
|
6
6
|
|
|
7
7
|
function assertBranch(value, label) {
|
|
@@ -26,11 +26,20 @@ export async function inspectGitOperation(root, sourceBranch, targetBranch) {
|
|
|
26
26
|
const operation = getGitOperation(sourceBranch, targetBranch);
|
|
27
27
|
try {
|
|
28
28
|
const [branch, status] = await Promise.all([
|
|
29
|
-
execFileAsync("git", ["branch", "--show-current"], {
|
|
30
|
-
|
|
29
|
+
execFileAsync("git", ["branch", "--show-current"], {
|
|
30
|
+
cwd: root,
|
|
31
|
+
encoding: "utf8",
|
|
32
|
+
timeout: 10_000,
|
|
33
|
+
}),
|
|
34
|
+
execFileAsync("git", ["status", "--porcelain"], {
|
|
35
|
+
cwd: root,
|
|
36
|
+
encoding: "utf8",
|
|
37
|
+
timeout: 10_000,
|
|
38
|
+
}),
|
|
31
39
|
execFileAsync("git", ["rev-parse", "--verify", `refs/heads/${sourceBranch}`], {
|
|
32
40
|
cwd: root,
|
|
33
41
|
encoding: "utf8",
|
|
42
|
+
timeout: 10_000,
|
|
34
43
|
}),
|
|
35
44
|
]);
|
|
36
45
|
return {
|
|
@@ -57,6 +66,7 @@ export async function executeGitOperation(root, operation) {
|
|
|
57
66
|
const result = await execFileAsync(operation.command, operation.args, {
|
|
58
67
|
cwd: root,
|
|
59
68
|
encoding: "utf8",
|
|
69
|
+
timeout: 30_000,
|
|
60
70
|
});
|
|
61
71
|
return { ...operation, stdout: result.stdout.trim(), stderr: result.stderr.trim() };
|
|
62
72
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
2
1
|
import { readFileSync } from "node:fs";
|
|
3
2
|
import fs from "node:fs/promises";
|
|
4
3
|
import path from "node:path";
|
|
5
4
|
import { readJson, writeFileIfMissing, writeJson } from "../fs-utils.js";
|
|
6
5
|
import { getRuntimeStatePath } from "../paths.js";
|
|
6
|
+
import { safeSpawnSync } from "../process-utils.js";
|
|
7
7
|
import {
|
|
8
8
|
maskSensitiveText,
|
|
9
9
|
redactProviderBoundPayload,
|
|
@@ -74,7 +74,7 @@ export async function runExternalArchitectReview({ prompt = "" } = {}) {
|
|
|
74
74
|
getArchitectReviewConfiguration(),
|
|
75
75
|
);
|
|
76
76
|
const timeout = readReviewTimeoutMs();
|
|
77
|
-
const result =
|
|
77
|
+
const result = safeSpawnSync(binary, args, {
|
|
78
78
|
cwd: process.cwd(),
|
|
79
79
|
env: buildArchitectReviewEnv({
|
|
80
80
|
MA_ARCHITECT_REVIEW_PROMPT: redactedPrompt.sanitizedText,
|