@jstn-sdk/ma 0.1.13 → 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (259) hide show
  1. package/.agents/plugins/marketplace.json +0 -0
  2. package/.codex/agents/Architect.toml +1 -0
  3. package/.codex/agents/Auditor.toml +1 -0
  4. package/.codex/agents/Builder.toml +1 -0
  5. package/.codex/agents/Flow.toml +1 -0
  6. package/.codex/agents/Sage.toml +1 -0
  7. package/.codex/agents/Vibe.toml +1 -0
  8. package/.codex/hooks.json +12 -2
  9. package/.codex/prompts/enforcement.md +0 -0
  10. package/.codex/prompts/onboarding.md +6 -1
  11. package/.codex/prompts/release-rules.md +0 -0
  12. package/.codex/prompts/skill-contract.md +0 -0
  13. package/COVERAGE.md +9 -9
  14. package/DEMO.md +15 -6
  15. package/LICENSE +0 -0
  16. package/README.md +127 -784
  17. package/bin/ma.js +591 -105
  18. package/data/clone-data.ledger.json +0 -0
  19. package/data/clone-data.proof.json +0 -0
  20. package/data/clone-data.rvf +0 -0
  21. package/docs/README.md +8 -2
  22. package/docs/assets/banner.png +0 -0
  23. package/docs/autonomous-tasks.md +46 -0
  24. package/docs/codex-integration.md +16 -0
  25. package/docs/getting-started.md +32 -8
  26. package/docs/installed-sdk.md +0 -0
  27. package/docs/mcp-setup.md +30 -1
  28. package/docs/qa/{release-issue-gates-0.1.13.json → release-issue-gates-0.14.1.json} +77 -77
  29. package/docs/qa/release-readiness-0.1.5.md +0 -0
  30. package/docs/qa/{release-readiness-0.1.13.md → release-readiness-0.14.1.md} +12 -12
  31. package/docs/quality.md +24 -0
  32. package/docs/reference/native-engineering-patterns.md +0 -0
  33. package/docs/reference/native-security-playbooks.md +0 -0
  34. package/docs/reference/native-source-selection.md +0 -0
  35. package/docs/reference/native-style-and-deslop.md +0 -0
  36. package/docs/release-spec.md +27 -14
  37. package/docs/skills.md +1 -1
  38. package/docs/vendor-plugin-publishing.md +49 -0
  39. package/index.js +73 -0
  40. package/mcp/collections.json +0 -0
  41. package/mcp/fallback.json +0 -0
  42. package/mcp/local/code-intel.js +185 -41
  43. package/mcp/local/context.js +180 -0
  44. package/mcp/local/memory.js +17 -6
  45. package/mcp/local/playbooks.js +0 -0
  46. package/mcp/local/quality.js +39 -0
  47. package/mcp/local/state.js +7 -3
  48. package/mcp/local/team-run.js +8 -3
  49. package/mcp/local/trace.js +0 -0
  50. package/mcp/local-capabilities.json +9 -0
  51. package/mcp/native-playbooks.json +0 -0
  52. package/mcp/servers.json +0 -0
  53. package/package.json +37 -6
  54. package/plugins/meta-architect/.app.json +1 -1
  55. package/plugins/meta-architect/.claude-plugin/plugin.json +12 -0
  56. package/plugins/meta-architect/.codex-plugin/plugin.json +1 -1
  57. package/plugins/meta-architect/.mcp.json +1 -1
  58. package/plugins/meta-architect/README.md +14 -2
  59. package/plugins/meta-architect/obsidian/main.js +137 -4
  60. package/plugins/meta-architect/obsidian/manifest.json +1 -1
  61. package/plugins/meta-architect/obsidian/styles.css +0 -0
  62. package/plugins/meta-architect/skills/align/SKILL.md +0 -0
  63. package/plugins/meta-architect/skills/align/agents/openai.yaml +0 -0
  64. package/plugins/meta-architect/skills/align/references/shared-language.md +0 -0
  65. package/plugins/meta-architect/skills/arch/SKILL.md +0 -0
  66. package/plugins/meta-architect/skills/arch/agents/openai.yaml +0 -0
  67. package/plugins/meta-architect/skills/build/SKILL.md +0 -0
  68. package/plugins/meta-architect/skills/build/agents/openai.yaml +0 -0
  69. package/plugins/meta-architect/skills/cleanup/SKILL.md +0 -0
  70. package/plugins/meta-architect/skills/cleanup/agents/openai.yaml +0 -0
  71. package/plugins/meta-architect/skills/cleanup/references/style-and-deslop.md +0 -0
  72. package/plugins/meta-architect/skills/diagnose/SKILL.md +0 -0
  73. package/plugins/meta-architect/skills/diagnose/agents/openai.yaml +0 -0
  74. package/plugins/meta-architect/skills/flow/SKILL.md +0 -0
  75. package/plugins/meta-architect/skills/flow/agents/openai.yaml +0 -0
  76. package/plugins/meta-architect/skills/maestro/SKILL.md +17 -1
  77. package/plugins/meta-architect/skills/maestro/agents/openai.yaml +0 -0
  78. package/plugins/meta-architect/skills/maestro/references/core-release-rules.md +2 -2
  79. package/plugins/meta-architect/skills/maestro/references/native-ingest-map.md +0 -0
  80. package/plugins/meta-architect/skills/sage/SKILL.md +0 -0
  81. package/plugins/meta-architect/skills/sage/agents/openai.yaml +0 -0
  82. package/plugins/meta-architect/skills/sage/references/source-selection.md +0 -0
  83. package/plugins/meta-architect/skills/tdd/SKILL.md +0 -0
  84. package/plugins/meta-architect/skills/tdd/agents/openai.yaml +0 -0
  85. package/plugins/meta-architect/skills/vet/SKILL.md +0 -0
  86. package/plugins/meta-architect/skills/vet/agents/openai.yaml +0 -0
  87. package/plugins/meta-architect/skills/vet/references/security-playbooks.md +0 -0
  88. package/plugins/meta-architect/skills/vibe/SKILL.md +0 -0
  89. package/plugins/meta-architect/skills/vibe/agents/openai.yaml +0 -0
  90. package/schemas/autonomous-task-queue.schema.json +58 -0
  91. package/schemas/handoff-packet.schema.json +36 -0
  92. package/schemas/setup-receipt.schema.json +28 -0
  93. package/schemas/skill-frontmatter.schema.json +20 -0
  94. package/schemas/support-bundle.schema.json +37 -0
  95. package/schemas/task-contract.schema.json +31 -0
  96. package/scripts/active-autonomy-hook.mjs +141 -11
  97. package/scripts/biome-staged.js +0 -0
  98. package/scripts/build-linux-packages.mjs +7 -3
  99. package/scripts/cleanup-test-fixtures.sh +55 -0
  100. package/scripts/context-hydration-hook.mjs +89 -0
  101. package/scripts/demo-smoke.js +285 -0
  102. package/scripts/doctor.js +10 -8
  103. package/scripts/install.sh +33 -4
  104. package/scripts/install.sh.sha256 +1 -0
  105. package/scripts/linux-package-lib.mjs +0 -0
  106. package/scripts/linux-package-smoke.mjs +2 -2
  107. package/scripts/package-size-check.mjs +26 -0
  108. package/scripts/plugin-build.js +314 -0
  109. package/scripts/plugin-publish.js +77 -0
  110. package/scripts/plugin-sync.js +83 -17
  111. package/scripts/prepack.js +9 -0
  112. package/scripts/ralph/prompt.md +0 -0
  113. package/scripts/release-metadata.js +0 -0
  114. package/scripts/release-sync.js +83 -7
  115. package/scripts/release-verify.js +131 -7
  116. package/scripts/setup-npmrc.js +48 -23
  117. package/scripts/skills-install.js +9 -2
  118. package/scripts/skills-manifest.js +2 -21
  119. package/scripts/skills-pack.js +2 -2
  120. package/scripts/skills-validate.js +5 -55
  121. package/skills/align/SKILL.md +0 -0
  122. package/skills/align/agents/openai.yaml +0 -0
  123. package/skills/align/references/shared-language.md +0 -0
  124. package/skills/arch/SKILL.md +0 -0
  125. package/skills/arch/agents/openai.yaml +0 -0
  126. package/skills/build/SKILL.md +0 -0
  127. package/skills/build/agents/openai.yaml +0 -0
  128. package/skills/cleanup/SKILL.md +0 -0
  129. package/skills/cleanup/agents/openai.yaml +0 -0
  130. package/skills/cleanup/references/style-and-deslop.md +0 -0
  131. package/skills/diagnose/SKILL.md +0 -0
  132. package/skills/diagnose/agents/openai.yaml +0 -0
  133. package/skills/flow/SKILL.md +0 -0
  134. package/skills/flow/agents/openai.yaml +0 -0
  135. package/skills/index.json +0 -0
  136. package/skills/maestro/SKILL.md +17 -1
  137. package/skills/maestro/agents/openai.yaml +0 -0
  138. package/skills/maestro/references/core-release-rules.md +2 -2
  139. package/skills/maestro/references/native-ingest-map.md +0 -0
  140. package/skills/sage/SKILL.md +0 -0
  141. package/skills/sage/agents/openai.yaml +0 -0
  142. package/skills/sage/references/source-selection.md +0 -0
  143. package/skills/tdd/SKILL.md +0 -0
  144. package/skills/tdd/agents/openai.yaml +0 -0
  145. package/skills/vet/SKILL.md +0 -0
  146. package/skills/vet/agents/openai.yaml +0 -0
  147. package/skills/vet/references/security-playbooks.md +0 -0
  148. package/skills/vibe/SKILL.md +0 -0
  149. package/skills/vibe/agents/openai.yaml +0 -0
  150. package/sprint/00-idea.md +0 -0
  151. package/sprint/01-architecture.md +0 -0
  152. package/sprint/02-oss-evidence.md +0 -0
  153. package/sprint/03-logic.md +0 -0
  154. package/sprint/04-security.md +0 -0
  155. package/sprint/05-dx-ux.md +0 -0
  156. package/sprint/06-build-plan.md +0 -0
  157. package/sprint/07-release.md +2 -2
  158. package/src/agents.js +281 -0
  159. package/src/bootstrap.js +272 -65
  160. package/src/build-gate.js +0 -0
  161. package/src/codex-app-server.js +291 -0
  162. package/src/decision-log.js +0 -0
  163. package/src/fs-utils.js +175 -4
  164. package/src/launcher.js +26 -23
  165. package/src/mcp-config.js +9 -1
  166. package/src/mcp-live-client.js +141 -8
  167. package/src/paths.js +6 -1
  168. package/src/policy.js +2 -2
  169. package/src/prelaunch.js +223 -0
  170. package/src/process-utils.js +62 -0
  171. package/src/quality/ai-quality-orchestrator.js +328 -0
  172. package/src/release-issue-gates.js +62 -0
  173. package/src/release-operations.js +72 -0
  174. package/src/release-state.js +0 -0
  175. package/src/runtime/active-autonomy-core.js +0 -0
  176. package/src/runtime/agent-compat.js +31 -0
  177. package/src/runtime/alignment-sentinel.js +0 -0
  178. package/src/runtime/architect-review.js +249 -11
  179. package/src/runtime/autonomous-tasks.js +585 -0
  180. package/src/runtime/build-readiness.js +0 -0
  181. package/src/runtime/code-graph-rehearse.js +0 -0
  182. package/src/runtime/codeburn-core.js +118 -0
  183. package/src/runtime/context-authority.js +40 -0
  184. package/src/runtime/context-economy-core.js +40 -0
  185. package/src/runtime/continuity-graph.js +201 -0
  186. package/src/runtime/continuity-notes.js +71 -26
  187. package/src/runtime/core-source-ingest.js +2 -2
  188. package/src/runtime/detached-provider.js +90 -13
  189. package/src/runtime/doctor-report.js +18 -0
  190. package/src/runtime/environment-awareness-core.js +0 -0
  191. package/src/runtime/exposure-catalog.js +0 -0
  192. package/src/runtime/graphify-core.js +167 -0
  193. package/src/runtime/guidance-stack.js +9 -2
  194. package/src/runtime/handoff-packets.js +92 -0
  195. package/src/runtime/headroom-core.js +104 -0
  196. package/src/runtime/helper-orchestration-core.js +0 -0
  197. package/src/runtime/hook-profiles.js +20 -0
  198. package/src/runtime/learning-loop-core.js +110 -4
  199. package/src/runtime/live-agent-verification.js +122 -0
  200. package/src/runtime/maestro-events.js +0 -0
  201. package/src/runtime/maestro-manager.js +0 -0
  202. package/src/runtime/maestro-output.js +58 -0
  203. package/src/runtime/maestro-state.js +0 -0
  204. package/src/runtime/managed-markdown.js +29 -0
  205. package/src/runtime/mcp-authority.js +147 -0
  206. package/src/runtime/mcp-policy.js +0 -0
  207. package/src/runtime/obsidian-integration-core.js +255 -17
  208. package/src/runtime/obsidian-plugin-bridge.js +344 -38
  209. package/src/runtime/orchestrator.js +0 -0
  210. package/src/runtime/pi-maestro-core.js +89 -0
  211. package/src/runtime/preferences.js +79 -0
  212. package/src/runtime/project-context.js +636 -0
  213. package/src/runtime/prompt-strategy-core.js +0 -0
  214. package/src/runtime/quorum-review.js +0 -0
  215. package/src/runtime/ralph-execution-core.js +0 -0
  216. package/src/runtime/redaction-gateway.js +218 -19
  217. package/src/runtime/runtime-state.js +94 -2
  218. package/src/runtime/schema-migrations.js +162 -0
  219. package/src/runtime/semantic-recording-core.js +0 -0
  220. package/src/runtime/signal-hooks.js +13 -0
  221. package/src/runtime/skills-registry-export.js +96 -8
  222. package/src/runtime/startup-path.js +0 -0
  223. package/src/runtime/task-contracts.js +83 -0
  224. package/src/runtime/universal-plugin-broker-core.js +373 -50
  225. package/src/runtime/workspace-intelligence-runtime.js +0 -0
  226. package/src/runtime/workspace-virtualizer.js +0 -0
  227. package/src/runtime/workspaces.js +0 -0
  228. package/src/runtime-artifacts.js +0 -0
  229. package/src/setup-lifecycle.js +359 -0
  230. package/src/skill-frontmatter.js +78 -0
  231. package/src/skill-installer.js +226 -71
  232. package/src/skills.js +309 -31
  233. package/src/state-sync.js +0 -0
  234. package/src/test-fixtures.js +543 -0
  235. package/src/tui/grid.js +67 -0
  236. package/src/tui/status-grid.js +23 -0
  237. package/support-bundle.json +114 -0
  238. package/templates/AGENTS.md +6 -2
  239. package/templates/catalog-manifest.json +0 -0
  240. package/templates/model-instructions/core.md +0 -0
  241. package/templates/model-instructions/release.md +0 -0
  242. package/templates/model-instructions/security.md +0 -0
  243. package/templates/quality/ai-quality-rules.yml +37 -0
  244. package/docs/assets/image/Screenshot(1).png +0 -0
  245. package/docs/assets/image/Screenshot(2).png +0 -0
  246. package/docs/assets/image/Screenshot(3).png +0 -0
  247. package/docs/assets/image/Screenshot(4).png +0 -0
  248. package/docs/assets/image/Screenshot(5).png +0 -0
  249. package/docs/assets/image/Screenshot(6).png +0 -0
  250. package/docs/assets/image/Screenshot(7).png +0 -0
  251. package/docs/assets/image/Screenshot(8).png +0 -0
  252. package/docs/assets/image/Screenshot(9).png +0 -0
  253. package/docs/assets/meta-architect-logo.png +0 -0
  254. package/docs/assets/meta-architect-logo.svg +0 -8
  255. package/docs/onboarding.md +0 -81
  256. package/docs/prompt-guidance-contract.md +0 -17
  257. package/docs/prompt-guidance-fragments/active-autonomy-core.md +0 -7
  258. package/docs/skills-publishing.md +0 -255
  259. package/scripts/postinstall.js +0 -23
@@ -0,0 +1,585 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { Worker } from "node:worker_threads";
5
+ import { getAgentInvocation } from "../agents.js";
6
+ import { readJson, withRuntimeStateLock, writeFileIfMissing, writeJson } from "../fs-utils.js";
7
+ import { getRuntimeSubsystemPath } from "../paths.js";
8
+ import { writeJsonAtomically } from "../setup-lifecycle.js";
9
+ import {
10
+ createDefaultEnvironmentAwarenessCore,
11
+ discoverEnvironmentCapabilities,
12
+ selectEnvironmentCapabilitiesForTask,
13
+ } from "./environment-awareness-core.js";
14
+ import { appendMaestroEvent } from "./maestro-events.js";
15
+ import { createTaskContract, validateTaskContract } from "./task-contracts.js";
16
+
17
+ export const autonomousTaskSchemaVersion = "0.1.0";
18
+ const statuses = new Set(["queued", "running", "completed", "failed", "blocked", "cancelled"]);
19
+ const priorities = new Set(["low", "normal", "high", "critical"]);
20
+ const runnerLeaseMaxAgeMs = 30_000;
21
+
22
+ export function getAutonomousTaskQueuePath() {
23
+ return getRuntimeSubsystemPath("tasks", "autonomous-queue.json");
24
+ }
25
+
26
+ export function getRunnerLeasePath(runnerId) {
27
+ return getRuntimeSubsystemPath("tasks", "leases", `${encodeURIComponent(runnerId)}.json`);
28
+ }
29
+
30
+ export function createTaskQueue() {
31
+ return {
32
+ schemaVersion: autonomousTaskSchemaVersion,
33
+ record_type: "autonomous_task_queue",
34
+ tasks: [],
35
+ };
36
+ }
37
+
38
+ export async function seedAutonomousTaskArtifacts() {
39
+ const queuePath = getAutonomousTaskQueuePath();
40
+ await fs.mkdir(path.dirname(queuePath), { recursive: true });
41
+ await writeFileIfMissing(queuePath, `${JSON.stringify(createTaskQueue(), null, 2)}\n`);
42
+ }
43
+
44
+ function normalizeTaskId(value) {
45
+ const id = String(value ?? "")
46
+ .trim()
47
+ .replace(/[^a-zA-Z0-9._-]/g, "-");
48
+ if (!id || id === "." || id === "..") throw new Error("task id is required");
49
+ return id;
50
+ }
51
+
52
+ function normalizeTask(input) {
53
+ const goal = String(input?.goal ?? input?.title ?? "").trim();
54
+ if (!goal) throw new Error("autonomous task requires goal");
55
+ const id = normalizeTaskId(input?.id ?? `task-${randomUUID().slice(0, 8)}`);
56
+ const priority = input?.priority ?? "normal";
57
+ if (!priorities.has(priority)) throw new Error(`unsupported task priority: ${priority}`);
58
+ const dependencies = Array.isArray(input?.dependencies ?? input?.dependsOn)
59
+ ? [...(input.dependencies ?? input.dependsOn)].map(normalizeTaskId)
60
+ : [];
61
+ const labels = Array.isArray(input?.labels) ? [...new Set(input.labels.map(String))] : [];
62
+ const deadline = input?.deadline ?? null;
63
+ if (deadline && Number.isNaN(Date.parse(deadline)))
64
+ throw new Error("task deadline must be an ISO date");
65
+ const contract = input?.contract
66
+ ? validateTaskContract(input.contract)
67
+ : createTaskContract({
68
+ goal,
69
+ contextUsed: [
70
+ ".ma/context/project-index.json",
71
+ ".ma/context/environment-awareness-core.json",
72
+ ],
73
+ constraints: ["Stop for destructive, credential-gated, or external production actions"],
74
+ verification: ["ma doctor", "Maestro lane receipts"],
75
+ stopCondition: "Complete with verification evidence or record a blocker",
76
+ risk: input?.risk ?? "medium",
77
+ });
78
+ return {
79
+ id,
80
+ goal,
81
+ priority,
82
+ dependencies,
83
+ labels,
84
+ deadline,
85
+ status: input?.status ?? "queued",
86
+ attempts: Number.isInteger(input?.attempts) ? input.attempts : 0,
87
+ maxAttempts: Number.isInteger(input?.maxAttempts) ? Math.max(1, input.maxAttempts) : 3,
88
+ vendor: input?.vendor ?? null,
89
+ contract,
90
+ createdAt: input?.createdAt ?? new Date().toISOString(),
91
+ updatedAt: new Date().toISOString(),
92
+ startedAt: input?.startedAt ?? null,
93
+ completedAt: input?.completedAt ?? null,
94
+ error: input?.error ?? null,
95
+ blocker: input?.blocker ?? null,
96
+ runnerPid: Number.isInteger(input?.runnerPid) ? input.runnerPid : null,
97
+ runnerId: typeof input?.runnerId === "string" ? input.runnerId : null,
98
+ evidence: Array.isArray(input?.evidence) ? [...input.evidence] : [],
99
+ selectedCapabilities: Array.isArray(input?.selectedCapabilities)
100
+ ? input.selectedCapabilities
101
+ : [],
102
+ };
103
+ }
104
+
105
+ export function validateTaskQueue(queue) {
106
+ if (
107
+ !queue ||
108
+ queue.schemaVersion !== autonomousTaskSchemaVersion ||
109
+ queue.record_type !== "autonomous_task_queue"
110
+ )
111
+ throw new Error("invalid autonomous task queue schema");
112
+ if (!Array.isArray(queue.tasks)) throw new Error("autonomous task queue requires tasks");
113
+ const ids = new Set();
114
+ for (const task of queue.tasks) {
115
+ if (ids.has(task.id)) throw new Error(`duplicate autonomous task id: ${task.id}`);
116
+ ids.add(task.id);
117
+ if (
118
+ !statuses.has(task.status) ||
119
+ !priorities.has(task.priority) ||
120
+ !Array.isArray(task.dependencies)
121
+ )
122
+ throw new Error(`invalid task state: ${task.id}`);
123
+ validateTaskContract(task.contract);
124
+ if (task.dependencies.some((dependency) => dependency === task.id))
125
+ throw new Error(`task cannot depend on itself: ${task.id}`);
126
+ }
127
+ for (const task of queue.tasks)
128
+ for (const dependency of task.dependencies)
129
+ if (!ids.has(dependency)) throw new Error(`unknown task dependency: ${dependency}`);
130
+ for (const task of queue.tasks) {
131
+ const visiting = new Set();
132
+ const visit = (id) => {
133
+ if (visiting.has(id)) throw new Error(`cyclic task dependency: ${id}`);
134
+ visiting.add(id);
135
+ for (const dependency of byId(queue.tasks).get(id)?.dependencies ?? []) visit(dependency);
136
+ visiting.delete(id);
137
+ };
138
+ visit(task.id);
139
+ }
140
+ return queue;
141
+ }
142
+
143
+ function byId(tasks) {
144
+ return new Map(tasks.map((task) => [task.id, task]));
145
+ }
146
+
147
+ async function runnerIsAlive(task) {
148
+ if (!Number.isInteger(task.runnerPid) || task.runnerPid < 1 || !task.runnerId) return false;
149
+ try {
150
+ const lease = await readJson(getRunnerLeasePath(task.runnerId));
151
+ const heartbeatAt = Date.parse(lease.heartbeatAt);
152
+ if (
153
+ lease.runnerId !== task.runnerId ||
154
+ lease.runnerPid !== task.runnerPid ||
155
+ !Number.isFinite(heartbeatAt) ||
156
+ Date.now() - heartbeatAt > runnerLeaseMaxAgeMs
157
+ ) {
158
+ return false;
159
+ }
160
+ process.kill(task.runnerPid, 0);
161
+ return true;
162
+ } catch {
163
+ return false;
164
+ }
165
+ }
166
+
167
+ async function loadTaskQueueUnlocked() {
168
+ try {
169
+ const queue = validateTaskQueue(await readJson(getAutonomousTaskQueuePath()));
170
+ const interrupted = [];
171
+ for (const task of queue.tasks) {
172
+ if (task.status === "running" && !(await runnerIsAlive(task))) interrupted.push(task);
173
+ }
174
+ if (interrupted.length === 0) return queue;
175
+ const recoveredAt = new Date().toISOString();
176
+ for (const task of interrupted) {
177
+ task.status = "queued";
178
+ task.error = "Recovered after the previous process exited while the task was running";
179
+ task.runnerPid = null;
180
+ task.runnerId = null;
181
+ task.updatedAt = recoveredAt;
182
+ }
183
+ await writeJsonAtomically(getAutonomousTaskQueuePath(), queue);
184
+ return queue;
185
+ } catch (error) {
186
+ if (error?.code === "ENOENT") return createTaskQueue();
187
+ throw error;
188
+ }
189
+ }
190
+
191
+ export async function loadTaskQueue() {
192
+ return withRuntimeStateLock(getAutonomousTaskQueuePath(), loadTaskQueueUnlocked, { wait: true });
193
+ }
194
+
195
+ export async function saveTaskQueue(queue) {
196
+ await fs.mkdir(path.dirname(getAutonomousTaskQueuePath()), { recursive: true });
197
+ return writeJson(getAutonomousTaskQueuePath(), validateTaskQueue(queue));
198
+ }
199
+
200
+ async function updateTaskQueueTask(taskId, update) {
201
+ return withRuntimeStateLock(
202
+ getAutonomousTaskQueuePath(),
203
+ async () => {
204
+ const queue = await loadTaskQueueUnlocked();
205
+ const task = queue.tasks.find((entry) => entry.id === taskId);
206
+ if (!task) return null;
207
+ const changed = (await update(task, queue)) !== false;
208
+ if (changed) {
209
+ task.updatedAt = new Date().toISOString();
210
+ await writeJsonAtomically(getAutonomousTaskQueuePath(), validateTaskQueue(queue));
211
+ }
212
+ return { task, changed };
213
+ },
214
+ { wait: true },
215
+ );
216
+ }
217
+
218
+ async function createRunnerLease(runnerId) {
219
+ await writeJsonAtomically(getRunnerLeasePath(runnerId), {
220
+ schemaVersion: autonomousTaskSchemaVersion,
221
+ runnerId,
222
+ runnerPid: process.pid,
223
+ heartbeatAt: new Date().toISOString(),
224
+ });
225
+ }
226
+
227
+ function startRunnerLeaseHeartbeat(runnerId) {
228
+ const leasePath = getRunnerLeasePath(runnerId);
229
+ const worker = new Worker(
230
+ `const { parentPort, workerData } = require("node:worker_threads");
231
+ const fs = require("node:fs");
232
+ const path = require("node:path");
233
+ let active = true;
234
+ const beat = () => {
235
+ if (!active) return;
236
+ const temporary = workerData.leasePath + ".heartbeat-" + process.pid;
237
+ fs.writeFile(temporary, JSON.stringify({
238
+ schemaVersion: workerData.schemaVersion,
239
+ runnerId: workerData.runnerId,
240
+ runnerPid: workerData.runnerPid,
241
+ heartbeatAt: new Date().toISOString()
242
+ }) + "\\n", { mode: 0o600 }, (writeError) => {
243
+ if (writeError) return;
244
+ fs.rename(temporary, workerData.leasePath, () => {});
245
+ });
246
+ };
247
+ const timer = setInterval(beat, workerData.intervalMs);
248
+ timer.unref();
249
+ parentPort.on("message", (message) => {
250
+ if (message === "stop") {
251
+ active = false;
252
+ clearInterval(timer);
253
+ process.exit(0);
254
+ }
255
+ });`,
256
+ {
257
+ eval: true,
258
+ workerData: {
259
+ leasePath,
260
+ runnerId,
261
+ runnerPid: process.pid,
262
+ schemaVersion: autonomousTaskSchemaVersion,
263
+ intervalMs: runnerLeaseMaxAgeMs / 3,
264
+ },
265
+ },
266
+ );
267
+ worker.on("error", () => {});
268
+ return async () => {
269
+ worker.postMessage("stop");
270
+ await worker.terminate();
271
+ };
272
+ }
273
+
274
+ export async function enqueueAutonomousTasks(inputs) {
275
+ return withRuntimeStateLock(
276
+ getAutonomousTaskQueuePath(),
277
+ async () => {
278
+ const queue = await loadTaskQueueUnlocked();
279
+ const tasks = Array.isArray(inputs) ? inputs : [inputs];
280
+ const added = tasks.map(normalizeTask);
281
+ const existing = new Set(queue.tasks.map((task) => task.id));
282
+ for (const task of added) {
283
+ if (existing.has(task.id)) throw new Error(`task already exists: ${task.id}`);
284
+ existing.add(task.id);
285
+ queue.tasks.push(task);
286
+ }
287
+ await writeJsonAtomically(getAutonomousTaskQueuePath(), validateTaskQueue(queue));
288
+ return added;
289
+ },
290
+ { wait: true },
291
+ );
292
+ }
293
+
294
+ export async function cancelAutonomousTask(id, reason = "Cancelled by operator") {
295
+ return withRuntimeStateLock(
296
+ getAutonomousTaskQueuePath(),
297
+ async () => {
298
+ const queue = await loadTaskQueueUnlocked();
299
+ const task = queue.tasks.find((entry) => entry.id === normalizeTaskId(id));
300
+ if (!task) throw new Error(`unknown autonomous task: ${id}`);
301
+ if (["completed", "cancelled"].includes(task.status)) return task;
302
+ task.status = "cancelled";
303
+ task.blocker = reason;
304
+ task.updatedAt = new Date().toISOString();
305
+ await writeJsonAtomically(getAutonomousTaskQueuePath(), validateTaskQueue(queue));
306
+ return task;
307
+ },
308
+ { wait: true },
309
+ );
310
+ }
311
+
312
+ function hasUnsafeAction(task) {
313
+ return /\b(deploy|production|publish|delete\s+all|drop\s+(database|table)|rotate\s+credentials|send\s+money)\b/i.test(
314
+ task.goal,
315
+ );
316
+ }
317
+
318
+ function dependenciesReady(task, byId) {
319
+ return task.dependencies.every((id) => byId.get(id)?.status === "completed");
320
+ }
321
+
322
+ function isExpired(task, now) {
323
+ return task.deadline && Date.parse(task.deadline) < now;
324
+ }
325
+
326
+ export async function runAutonomousTasks({
327
+ concurrency = 1,
328
+ maxTasks = Infinity,
329
+ includeGlobal = true,
330
+ execute = null,
331
+ cwd = process.cwd(),
332
+ onEvent = null,
333
+ } = {}) {
334
+ if (!Number.isInteger(concurrency) || concurrency < 1)
335
+ throw new Error("concurrency must be a positive integer");
336
+ const queue = await loadTaskQueue();
337
+ const taskById = new Map(queue.tasks.map((task) => [task.id, task]));
338
+ const capabilities = createDefaultEnvironmentAwarenessCore({
339
+ capabilities: await discoverEnvironmentCapabilities({ cwd, includeGlobal }),
340
+ });
341
+ const runner =
342
+ execute ??
343
+ (async (task) => {
344
+ const { runMaestro } = await import("../skills.js");
345
+ await runMaestro({ taskContract: task.contract, handoff: { nextAction: task.goal } });
346
+ const [{ loadManagerRunRegistryOrDefault }, { loadAlignmentSentinelReport }] =
347
+ await Promise.all([import("./maestro-manager.js"), import("./alignment-sentinel.js")]);
348
+ const [managerRegistry, alignment] = await Promise.all([
349
+ loadManagerRunRegistryOrDefault(),
350
+ loadAlignmentSentinelReport(),
351
+ ]);
352
+ const latestRun = managerRegistry.runs.at(-1);
353
+ if (alignment?.driftStatus === "DRIFTED") {
354
+ return {
355
+ status: "blocked",
356
+ reason: "Maestro blocked dispatch because alignment drift must be resolved",
357
+ };
358
+ }
359
+ if (latestRun && ["waiting-review", "blocked"].includes(latestRun.state)) {
360
+ return {
361
+ status: "blocked",
362
+ reason: `Maestro ${latestRun.state}: ${latestRun.retry?.lastReason ?? "review or blocker remains"}`,
363
+ };
364
+ }
365
+ return { status: "completed", evidence: ["Maestro manager run completed"] };
366
+ });
367
+ const events = [];
368
+ const emit = async (event) => {
369
+ events.push(event);
370
+ await appendMaestroEvent({ record_type: "autonomous_task", ...event });
371
+ await onEvent?.(event);
372
+ };
373
+ const running = new Set();
374
+ let processed = 0;
375
+ while (processed + running.size < maxTasks) {
376
+ const now = Date.now();
377
+ const eligible = queue.tasks
378
+ .filter((task) => task.status === "queued" && !running.has(task.id))
379
+ .filter((task) => !isExpired(task, now) && dependenciesReady(task, taskById))
380
+ .sort(
381
+ (a, b) =>
382
+ (priorities.has(b.priority)
383
+ ? ["low", "normal", "high", "critical"].indexOf(b.priority)
384
+ : 0) - ["low", "normal", "high", "critical"].indexOf(a.priority),
385
+ );
386
+ if (eligible.length === 0 || running.size >= concurrency) {
387
+ if (running.size === 0) break;
388
+ await Promise.race(running);
389
+ continue;
390
+ }
391
+ const remaining = maxTasks - processed - running.size;
392
+ for (const task of eligible.slice(0, Math.min(concurrency - running.size, remaining))) {
393
+ task.selectedCapabilities = selectEnvironmentCapabilitiesForTask(
394
+ capabilities,
395
+ task.goal,
396
+ ).selected;
397
+ task.invocation = task.vendor ? getAgentInvocation(task.vendor, "maestro") : null;
398
+ if (hasUnsafeAction(task)) {
399
+ const transition = await updateTaskQueueTask(task.id, (current) => {
400
+ if (current.status !== "queued") return false;
401
+ current.status = "blocked";
402
+ current.blocker =
403
+ "Explicit approval required for destructive, production, credential, or external mutation task";
404
+ return true;
405
+ });
406
+ if (transition?.changed) {
407
+ Object.assign(task, transition.task);
408
+ await emit({ taskId: task.id, status: task.status, blocker: task.blocker });
409
+ }
410
+ processed++;
411
+ continue;
412
+ }
413
+ const runnerId = randomUUID();
414
+ try {
415
+ await createRunnerLease(runnerId);
416
+ } catch (error) {
417
+ await emit({
418
+ taskId: task.id,
419
+ status: "queued",
420
+ error: `Unable to establish task runner lease: ${error.message}`,
421
+ });
422
+ continue;
423
+ }
424
+ const claim = await updateTaskQueueTask(task.id, (current) => {
425
+ if (current.status !== "queued") return false;
426
+ current.status = "running";
427
+ current.attempts++;
428
+ current.startedAt ??= new Date().toISOString();
429
+ current.runnerPid = process.pid;
430
+ current.runnerId = runnerId;
431
+ current.selectedCapabilities = task.selectedCapabilities;
432
+ current.invocation = task.invocation;
433
+ return true;
434
+ });
435
+ if (!claim?.changed) {
436
+ await fs.rm(getRunnerLeasePath(runnerId), { force: true });
437
+ continue;
438
+ }
439
+ Object.assign(task, claim.task);
440
+ const promise = (async () => {
441
+ const stopHeartbeat = startRunnerLeaseHeartbeat(runnerId);
442
+ try {
443
+ const result = await runner(task);
444
+ const patch = { runnerPid: null, runnerId: null };
445
+ if (result?.status === "blocked") {
446
+ Object.assign(patch, {
447
+ status: "blocked",
448
+ blocker: result.reason ?? "Runner reported a blocker",
449
+ });
450
+ } else if (result?.status === "failed" && task.attempts < task.maxAttempts) {
451
+ Object.assign(patch, {
452
+ status: "queued",
453
+ error: result.reason ?? "Runner failed; task queued for retry",
454
+ });
455
+ } else if (result?.status === "failed") {
456
+ Object.assign(patch, {
457
+ status: "failed",
458
+ error: result.reason ?? "Runner failed after maximum attempts",
459
+ });
460
+ } else {
461
+ Object.assign(patch, {
462
+ status: "completed",
463
+ completedAt: new Date().toISOString(),
464
+ evidence: [
465
+ ...task.evidence,
466
+ ...(Array.isArray(result?.evidence) ? result.evidence : []),
467
+ ],
468
+ });
469
+ }
470
+ const transition = await updateTaskQueueTask(task.id, (current) => {
471
+ if (current.status === "cancelled") return false;
472
+ Object.assign(current, patch);
473
+ return true;
474
+ });
475
+ if (transition) Object.assign(task, transition.task);
476
+ } catch (error) {
477
+ task.error = error instanceof Error ? error.message : String(error);
478
+ task.status = task.attempts < task.maxAttempts ? "queued" : "failed";
479
+ task.runnerPid = null;
480
+ task.runnerId = null;
481
+ const transition = await updateTaskQueueTask(task.id, (current) => {
482
+ if (current.status === "cancelled") return false;
483
+ Object.assign(current, {
484
+ status: task.status,
485
+ error: task.error,
486
+ runnerPid: null,
487
+ runnerId: null,
488
+ });
489
+ return true;
490
+ });
491
+ if (transition) Object.assign(task, transition.task);
492
+ } finally {
493
+ await stopHeartbeat();
494
+ await fs.rm(getRunnerLeasePath(runnerId), { force: true });
495
+ }
496
+ await emit({
497
+ taskId: task.id,
498
+ status: task.status,
499
+ attempts: task.attempts,
500
+ error: task.error,
501
+ blocker: task.blocker,
502
+ });
503
+ running.delete(promise);
504
+ processed++;
505
+ })();
506
+ running.add(promise);
507
+ }
508
+ }
509
+ await Promise.all(running);
510
+ await withRuntimeStateLock(
511
+ getAutonomousTaskQueuePath(),
512
+ async () => {
513
+ const latestQueue = await loadTaskQueueUnlocked();
514
+ const latestById = byId(latestQueue.tasks);
515
+ for (const task of latestQueue.tasks) {
516
+ if (task.status !== "queued") continue;
517
+ const failedDependency = task.dependencies.find((id) =>
518
+ ["failed", "blocked", "cancelled"].includes(latestById.get(id)?.status),
519
+ );
520
+ if (failedDependency) {
521
+ task.status = "blocked";
522
+ task.blocker = `Dependency ${failedDependency} is ${latestById.get(failedDependency).status}`;
523
+ } else if (isExpired(task, Date.now())) {
524
+ task.status = "blocked";
525
+ task.blocker = "Task deadline expired";
526
+ } else if (!dependenciesReady(task, latestById)) {
527
+ task.blocker = "Waiting for dependencies";
528
+ }
529
+ task.updatedAt = new Date().toISOString();
530
+ }
531
+ await writeJsonAtomically(getAutonomousTaskQueuePath(), validateTaskQueue(latestQueue));
532
+ },
533
+ { wait: true },
534
+ );
535
+ const finalQueue = await loadTaskQueue();
536
+ return {
537
+ schemaVersion: autonomousTaskSchemaVersion,
538
+ tasks: finalQueue.tasks,
539
+ events,
540
+ summary: summarizeTasks(finalQueue.tasks),
541
+ };
542
+ }
543
+
544
+ export function summarizeTasks(tasks) {
545
+ return Object.fromEntries(
546
+ [...statuses].map((status) => [status, tasks.filter((task) => task.status === status).length]),
547
+ );
548
+ }
549
+
550
+ export async function parseTaskInput(raw, format = "json") {
551
+ if (format === "json" || format === "jsonl") return JSON.parse(raw);
552
+ if (format !== "yaml" && format !== "yml")
553
+ throw new Error(`unsupported task input format: ${format}`);
554
+ const result = [];
555
+ let current = null;
556
+ for (const line of raw.split(/\r?\n/)) {
557
+ const trimmed = line.trim();
558
+ if (!trimmed || trimmed.startsWith("#")) continue;
559
+ if (trimmed === "tasks:") continue;
560
+ if (trimmed.startsWith("- ")) {
561
+ if (current) result.push(current);
562
+ current = {};
563
+ parseYamlField(current, trimmed.slice(2));
564
+ continue;
565
+ }
566
+ if (!current) current = {};
567
+ parseYamlField(current, trimmed);
568
+ }
569
+ if (current) result.push(current);
570
+ return result.length === 1 ? result[0] : result;
571
+ }
572
+
573
+ function parseYamlField(target, line) {
574
+ const index = line.indexOf(":");
575
+ if (index < 1) throw new Error(`invalid task YAML line: ${line}`);
576
+ const key = line.slice(0, index).trim();
577
+ const value = line.slice(index + 1).trim();
578
+ if (value.startsWith("[") && value.endsWith("]"))
579
+ target[key] = value
580
+ .slice(1, -1)
581
+ .split(",")
582
+ .map((item) => item.trim())
583
+ .filter(Boolean);
584
+ else target[key] = value.replace(/^['"]|['"]$/g, "");
585
+ }
File without changes
File without changes
@@ -0,0 +1,118 @@
1
+ import { promisify } from "node:util";
2
+ import { ensureDir, readJson, writeFileIfMissing, writeJson } from "../fs-utils.js";
3
+ import { getRuntimeSubsystemPath } from "../paths.js";
4
+ import { assertSafeExecutable, safeExecFile } from "../process-utils.js";
5
+
6
+ const execFileAsync = promisify(safeExecFile);
7
+ export const codeburnSchemaVersion = "0.1.0";
8
+
9
+ export function getCodeburnRoot() {
10
+ return getRuntimeSubsystemPath("context");
11
+ }
12
+
13
+ export function getCodeburnLogPath() {
14
+ return getRuntimeSubsystemPath("context", "codeburn-usage.json");
15
+ }
16
+
17
+ export function createDefaultCodeburnUsage() {
18
+ return { schemaVersion: codeburnSchemaVersion, entries: [], totalTokens: 0, totalCost: 0 };
19
+ }
20
+
21
+ function validateUsage(value) {
22
+ if (
23
+ !value ||
24
+ typeof value !== "object" ||
25
+ value.schemaVersion !== codeburnSchemaVersion ||
26
+ !Array.isArray(value.entries) ||
27
+ !Number.isFinite(value.totalTokens) ||
28
+ !Number.isFinite(value.totalCost)
29
+ ) {
30
+ throw new Error("codeburn usage has an invalid schema");
31
+ }
32
+ return value;
33
+ }
34
+
35
+ export async function seedCodeburnArtifacts() {
36
+ await ensureDir(getCodeburnRoot());
37
+ await writeFileIfMissing(
38
+ getCodeburnLogPath(),
39
+ `${JSON.stringify(createDefaultCodeburnUsage(), null, 2)}\n`,
40
+ );
41
+ }
42
+
43
+ export async function loadCodeburnUsage() {
44
+ return validateUsage(await readJson(getCodeburnLogPath()));
45
+ }
46
+
47
+ function parseCodeburnOutput(output) {
48
+ const text = String(output ?? "").trim();
49
+ if (!text) return [];
50
+ try {
51
+ const parsed = JSON.parse(text);
52
+ return Array.isArray(parsed) ? parsed : (parsed.entries ?? [parsed]);
53
+ } catch {
54
+ const jsonLine = text
55
+ .split("\n")
56
+ .map((line) => line.trim())
57
+ .reverse()
58
+ .find((line) => line.startsWith("{") || line.startsWith("["));
59
+ if (!jsonLine) throw new Error("codeburn returned no JSON usage data");
60
+ const parsed = JSON.parse(jsonLine);
61
+ return Array.isArray(parsed) ? parsed : (parsed.entries ?? [parsed]);
62
+ }
63
+ }
64
+
65
+ function normalizeEntry(entry) {
66
+ const inputTokens = Number(entry.inputTokens ?? entry.input_tokens ?? 0);
67
+ const outputTokens = Number(entry.outputTokens ?? entry.output_tokens ?? 0);
68
+ const tokens = Number(
69
+ entry.totalTokens ?? entry.total_tokens ?? entry.tokens ?? inputTokens + outputTokens,
70
+ );
71
+ const cost = Number(entry.totalCost ?? entry.total_cost ?? entry.cost ?? 0);
72
+ return {
73
+ provider: entry.provider ?? null,
74
+ model: entry.model ?? entry.modelName ?? null,
75
+ sessionId: entry.sessionId ?? entry.session_id ?? null,
76
+ tokens: Number.isFinite(tokens) && tokens >= 0 ? tokens : 0,
77
+ cost: Number.isFinite(cost) && cost >= 0 ? cost : 0,
78
+ };
79
+ }
80
+
81
+ export async function syncCodeburnUsage({
82
+ cwd = process.cwd(),
83
+ command,
84
+ args,
85
+ exec = execFileAsync,
86
+ } = {}) {
87
+ const existing = await loadCodeburnUsage();
88
+ const executable = command ?? process.env.MA_CODEBURN_BIN ?? "npx";
89
+ const commandArgs = args ?? ["--yes", "codeburn", "usage", "--json"];
90
+ assertSafeExecutable(executable, "Codeburn executable");
91
+ try {
92
+ const result = await exec(executable, commandArgs, {
93
+ cwd,
94
+ encoding: "utf8",
95
+ timeout: 15_000,
96
+ shell: false,
97
+ });
98
+ const entries = parseCodeburnOutput(result.stdout ?? result);
99
+ const normalized = entries
100
+ .filter((entry) => entry && typeof entry === "object")
101
+ .map(normalizeEntry);
102
+ const usage = {
103
+ schemaVersion: codeburnSchemaVersion,
104
+ entries: normalized,
105
+ totalTokens: normalized.reduce((sum, entry) => sum + entry.tokens, 0),
106
+ totalCost: normalized.reduce((sum, entry) => sum + entry.cost, 0),
107
+ syncedAt: new Date().toISOString(),
108
+ };
109
+ await writeJson(getCodeburnLogPath(), usage);
110
+ return { available: true, usage };
111
+ } catch (error) {
112
+ return {
113
+ available: false,
114
+ usage: existing,
115
+ reason: error instanceof Error ? error.message : "codeburn unavailable",
116
+ };
117
+ }
118
+ }