@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.
Files changed (52) hide show
  1. package/COVERAGE.md +9 -9
  2. package/DEMO.md +4 -4
  3. package/README.md +80 -70
  4. package/bin/ma.js +6 -8
  5. package/docs/README.md +2 -2
  6. package/docs/assets/banner.png +0 -0
  7. package/docs/getting-started.md +9 -9
  8. package/docs/qa/{release-issue-gates-0.14.0.json → release-issue-gates-0.14.1.json} +77 -77
  9. package/docs/qa/{release-readiness-0.14.0.md → release-readiness-0.14.1.md} +12 -12
  10. package/docs/release-spec.md +12 -12
  11. package/docs/vendor-plugin-publishing.md +49 -0
  12. package/mcp/local/code-intel.js +9 -3
  13. package/package.json +12 -3
  14. package/plugins/meta-architect/.app.json +1 -1
  15. package/plugins/meta-architect/.claude-plugin/plugin.json +12 -0
  16. package/plugins/meta-architect/.codex-plugin/plugin.json +1 -1
  17. package/plugins/meta-architect/.mcp.json +1 -1
  18. package/plugins/meta-architect/README.md +13 -1
  19. package/plugins/meta-architect/obsidian/manifest.json +1 -1
  20. package/plugins/meta-architect/skills/maestro/SKILL.md +17 -1
  21. package/plugins/meta-architect/skills/maestro/references/core-release-rules.md +2 -2
  22. package/schemas/autonomous-task-queue.schema.json +2 -0
  23. package/scripts/install.sh +1 -1
  24. package/scripts/install.sh.sha256 +1 -1
  25. package/scripts/plugin-build.js +314 -0
  26. package/scripts/plugin-publish.js +77 -0
  27. package/scripts/release-sync.js +11 -0
  28. package/scripts/release-verify.js +35 -4
  29. package/skills/maestro/SKILL.md +17 -1
  30. package/skills/maestro/references/core-release-rules.md +2 -2
  31. package/sprint/07-release.md +2 -2
  32. package/src/agents.js +4 -2
  33. package/src/bootstrap.js +20 -2
  34. package/src/codex-app-server.js +4 -4
  35. package/src/launcher.js +2 -2
  36. package/src/mcp-live-client.js +2 -2
  37. package/src/prelaunch.js +34 -0
  38. package/src/process-utils.js +62 -0
  39. package/src/quality/ai-quality-orchestrator.js +2 -2
  40. package/src/release-operations.js +14 -4
  41. package/src/runtime/architect-review.js +2 -2
  42. package/src/runtime/autonomous-tasks.js +220 -43
  43. package/src/runtime/codeburn-core.js +9 -3
  44. package/src/runtime/core-source-ingest.js +2 -2
  45. package/src/runtime/detached-provider.js +17 -8
  46. package/src/runtime/live-agent-verification.js +2 -2
  47. package/src/runtime/project-context.js +2 -2
  48. package/src/runtime/skills-registry-export.js +7 -3
  49. package/src/skills.js +12 -12
  50. package/src/test-fixtures.js +4 -4
  51. package/support-bundle.json +1 -1
  52. package/scripts/postinstall.js +0 -30
@@ -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 bounded autonomous manager. It inspects workflow state, chooses the smallest safe next step, manages the fixed gated design-and-review sequence one lane at a time, and routes helper handoffs when they are enough. There is no separate shipped `$meta-architect` skill.
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 `development`, never directly into `prod`.
12
- - Release promotion is allowed only from `development` or approved `release/*`.
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.
@@ -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/* -> development`
20
- - release follows `development|release/* -> prod`
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 = spawnSync(
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 = spawnSync("which", ["-a", "codex"], { encoding: "utf8" });
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
 
@@ -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 = spawn(command, ["app-server", "--stdio"], {
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 = spawn(command, ["exec", "--json", ...args, prompt], {
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 = spawn(command, ["app-server", generator, "--out", outDir], {
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 = spawnSync(command, finalArgs, { stdio: "inherit" });
49
+ const result = safeSpawnSync(command, finalArgs, { stdio: "inherit" });
50
50
 
51
51
  if (result.error) {
52
52
  if (result.error.code === "ENOENT") {
@@ -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
- spawn,
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 = spawn(command, args, {
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(execFile);
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"], { cwd: root, encoding: "utf8" }),
30
- execFileAsync("git", ["status", "--porcelain"], { cwd: root, encoding: "utf8" }),
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 = spawnSync(binary, args, {
77
+ const result = safeSpawnSync(binary, args, {
78
78
  cwd: process.cwd(),
79
79
  env: buildArchitectReviewEnv({
80
80
  MA_ARCHITECT_REVIEW_PROMPT: redactedPrompt.sanitizedText,