@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,359 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+
5
+ const RECEIPT_RELATIVE_PATH = path.join(".ma", "state", "setup-receipt.json");
6
+ const LOCK_RELATIVE_PATH = path.join(".ma", "state", ".setup.lock");
7
+ const MAX_RUNTIME_LOG_BYTES = 5 * 1024 * 1024;
8
+ const MAX_RUNTIME_LOG_FILES = 100;
9
+ const MANAGED_ROOTS = [".ma", ".codex", "scripts", "mcp", "docs", "sprint"];
10
+
11
+ async function exists(target) {
12
+ try {
13
+ await fs.access(target);
14
+ return true;
15
+ } catch {
16
+ return false;
17
+ }
18
+ }
19
+
20
+ function within(root, target) {
21
+ const relative = path.relative(root, target);
22
+ return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
23
+ }
24
+
25
+ function assertRepoPath(repoRoot, relative) {
26
+ const target = path.resolve(repoRoot, relative);
27
+ if (!within(path.resolve(repoRoot), target))
28
+ throw new Error(`Setup path escapes repository: ${relative}`);
29
+ return target;
30
+ }
31
+
32
+ export async function assertNoSymlinkComponents(root, target) {
33
+ const rootStat = await fs.lstat(path.resolve(root));
34
+ if (rootStat.isSymbolicLink()) {
35
+ throw new Error(`Setup root cannot be a symlink: ${root}`);
36
+ }
37
+ const relative = path.relative(path.resolve(root), path.resolve(target));
38
+ let current = path.resolve(root);
39
+ for (const component of relative.split(path.sep).filter(Boolean)) {
40
+ current = path.join(current, component);
41
+ const stat = await fs.lstat(current).catch((error) => {
42
+ if (error?.code === "ENOENT") return null;
43
+ throw error;
44
+ });
45
+ if (stat?.isSymbolicLink())
46
+ throw new Error(`Setup path cannot traverse a symlink: ${relative}`);
47
+ }
48
+ }
49
+
50
+ export async function writeJsonAtomically(target, value) {
51
+ const absolute = path.resolve(target);
52
+ const temporary = `${absolute}.tmp-${randomUUID()}`;
53
+ try {
54
+ await fs.mkdir(path.dirname(absolute), { recursive: true });
55
+ await fs.writeFile(temporary, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
56
+ await fs.rename(temporary, absolute);
57
+ } catch (error) {
58
+ await fs.rm(temporary, { force: true }).catch(() => {});
59
+ throw error;
60
+ }
61
+ }
62
+
63
+ export async function withSetupLock(repoRoot, action) {
64
+ const lockPath = assertRepoPath(repoRoot, LOCK_RELATIVE_PATH);
65
+ await assertNoSymlinkComponents(repoRoot, path.dirname(lockPath));
66
+ await fs.mkdir(path.dirname(lockPath), { recursive: true });
67
+ for (;;) {
68
+ try {
69
+ await fs.mkdir(lockPath);
70
+ await fs.writeFile(
71
+ path.join(lockPath, "owner.json"),
72
+ `${JSON.stringify({ pid: process.pid, createdAt: Date.now() })}\n`,
73
+ );
74
+ break;
75
+ } catch (error) {
76
+ if (error?.code !== "EEXIST") throw error;
77
+ const ownerText = await fs
78
+ .readFile(path.join(lockPath, "owner.json"), "utf8")
79
+ .catch(() => null);
80
+ const lockStat = await fs.stat(lockPath).catch(() => null);
81
+ let owner = null;
82
+ if (ownerText) {
83
+ try {
84
+ owner = JSON.parse(ownerText);
85
+ } catch {
86
+ throw new Error(
87
+ "Meta-Architect setup lock is unreadable; remove it only after confirming no setup is running",
88
+ );
89
+ }
90
+ }
91
+ const stale = owner
92
+ ? !Number.isInteger(owner.pid) || !isProcessAlive(owner.pid)
93
+ : Boolean(lockStat && Date.now() - lockStat.mtimeMs > 60 * 60 * 1000);
94
+ if (!stale) throw new Error("Meta-Architect setup is already running");
95
+ await fs.rm(lockPath, { recursive: true, force: true });
96
+ }
97
+ }
98
+ try {
99
+ return await action();
100
+ } finally {
101
+ await fs.rm(lockPath, { recursive: true, force: true });
102
+ }
103
+ }
104
+
105
+ function isProcessAlive(pid) {
106
+ try {
107
+ process.kill(pid, 0);
108
+ return true;
109
+ } catch {
110
+ return false;
111
+ }
112
+ }
113
+
114
+ export async function listManagedArtifacts(repoRoot) {
115
+ const root = path.resolve(repoRoot);
116
+ const artifacts = new Map();
117
+ async function walk(current) {
118
+ const entries = await fs.readdir(current, { withFileTypes: true }).catch((error) => {
119
+ if (error?.code === "ENOENT") return [];
120
+ throw error;
121
+ });
122
+ for (const entry of entries) {
123
+ if (entry.name === ".setup.lock" || entry.name.includes(".tmp-")) continue;
124
+ const target = path.join(current, entry.name);
125
+ const relative = path.relative(root, target);
126
+ if (entry.isSymbolicLink()) {
127
+ throw new Error(`Managed setup path cannot contain a symlink: ${relative}`);
128
+ }
129
+ artifacts.set(relative, entry.isDirectory() ? "directory" : "file");
130
+ if (entry.isDirectory()) await walk(target);
131
+ }
132
+ }
133
+ for (const relative of MANAGED_ROOTS) {
134
+ const target = path.join(root, relative);
135
+ await assertNoSymlinkComponents(root, target);
136
+ if (await exists(target)) {
137
+ artifacts.set(relative, "directory");
138
+ await walk(target);
139
+ }
140
+ }
141
+ return artifacts;
142
+ }
143
+
144
+ export async function writeSetupReceipt(repoRoot, report) {
145
+ const receiptPath = assertRepoPath(repoRoot, RECEIPT_RELATIVE_PATH);
146
+ await assertNoSymlinkComponents(repoRoot, path.dirname(receiptPath));
147
+ const artifacts = [...(report.directories ?? []), ...(report.files ?? [])].map((entry) => ({
148
+ path: entry.path,
149
+ kind: entry.kind,
150
+ status: entry.status,
151
+ created: entry.status === "created",
152
+ }));
153
+ const previous = await readSetupReceipt(repoRoot);
154
+ const previousCreated = new Set(
155
+ (previous.artifacts ?? [])
156
+ .filter((artifact) => artifact.created)
157
+ .map((artifact) => artifact.path),
158
+ );
159
+ const artifactsByPath = new Map(
160
+ (previous.artifacts ?? []).map((artifact) => [artifact.path, artifact]),
161
+ );
162
+ for (const artifact of artifacts) {
163
+ const old = artifactsByPath.get(artifact.path);
164
+ artifactsByPath.set(artifact.path, {
165
+ ...artifact,
166
+ created: artifact.created || old?.created === true,
167
+ });
168
+ }
169
+ const receipt = {
170
+ schemaVersion: "0.1.0",
171
+ record_type: "setup_receipt",
172
+ generatedAt: new Date().toISOString(),
173
+ managedBy: "@jstn-sdk/ma",
174
+ artifacts: [...artifactsByPath.values()].map((artifact) => ({
175
+ ...artifact,
176
+ created: artifact.created || previousCreated.has(artifact.path),
177
+ })),
178
+ };
179
+ await writeJsonAtomically(receiptPath, receipt);
180
+ return { path: path.relative(repoRoot, receiptPath), receipt };
181
+ }
182
+
183
+ async function readSetupReceipt(repoRoot) {
184
+ const receiptPath = assertRepoPath(repoRoot, RECEIPT_RELATIVE_PATH);
185
+ await assertNoSymlinkComponents(repoRoot, receiptPath);
186
+ try {
187
+ const value = JSON.parse(await fs.readFile(receiptPath, "utf8"));
188
+ if (
189
+ !value ||
190
+ typeof value !== "object" ||
191
+ value.schemaVersion !== "0.1.0" ||
192
+ value.record_type !== "setup_receipt" ||
193
+ value.managedBy !== "@jstn-sdk/ma" ||
194
+ typeof value.generatedAt !== "string" ||
195
+ !Array.isArray(value.artifacts) ||
196
+ value.artifacts.some(
197
+ (artifact) =>
198
+ !artifact ||
199
+ typeof artifact.path !== "string" ||
200
+ !["file", "directory"].includes(artifact.kind) ||
201
+ !["created", "existing", "refreshed", "skipped", "warning", "failed"].includes(
202
+ artifact.status,
203
+ ) ||
204
+ typeof artifact.created !== "boolean",
205
+ )
206
+ ) {
207
+ throw new Error("setup receipt schema invalid");
208
+ }
209
+ for (const artifact of value.artifacts) assertRepoPath(repoRoot, artifact.path);
210
+ return value;
211
+ } catch (error) {
212
+ if (error?.code === "ENOENT") return {};
213
+ throw new Error(`Setup receipt is corrupt; repair or remove ${RECEIPT_RELATIVE_PATH}`, {
214
+ cause: error,
215
+ });
216
+ }
217
+ }
218
+
219
+ export async function assertSetupReceiptHealthy(repoRoot) {
220
+ await readSetupReceipt(repoRoot);
221
+ }
222
+
223
+ export async function inspectSetupDrift(repoRoot) {
224
+ const receipt = await readSetupReceipt(repoRoot);
225
+ return (receipt.artifacts ?? [])
226
+ .filter((artifact) => ["skipped", "warning", "failed"].includes(artifact.status))
227
+ .map((artifact) => ({ path: artifact.path, status: artifact.status }));
228
+ }
229
+
230
+ async function rollbackSetupUnlocked(repoRoot, { dryRun = false } = {}) {
231
+ const receiptPath = assertRepoPath(repoRoot, RECEIPT_RELATIVE_PATH);
232
+ if (!(await exists(receiptPath))) return { status: "nothing-to-rollback", removed: [] };
233
+ const receipt = await readSetupReceipt(repoRoot);
234
+ const removed = [];
235
+ const artifacts = (receipt.artifacts ?? [])
236
+ .filter((artifact) => artifact.created)
237
+ .sort((left, right) => right.path.split(path.sep).length - left.path.split(path.sep).length);
238
+ for (const artifact of artifacts) {
239
+ const target = assertRepoPath(repoRoot, artifact.path);
240
+ await assertNoSymlinkComponents(repoRoot, target);
241
+ if (artifact.kind === "directory") {
242
+ const entries = await fs.readdir(target).catch(() => null);
243
+ if (entries?.length) continue;
244
+ }
245
+ if (await exists(target)) {
246
+ if (dryRun) {
247
+ removed.push(artifact.path);
248
+ continue;
249
+ }
250
+ await fs.rm(target, { recursive: artifact.kind === "directory", force: true });
251
+ removed.push(artifact.path);
252
+ }
253
+ }
254
+ if (dryRun) return { status: "dry-run", removed };
255
+ await fs.rm(receiptPath, { force: true });
256
+ return { status: "rolled-back", removed };
257
+ }
258
+
259
+ export function rollbackSetup(repoRoot, options = {}) {
260
+ return withSetupLock(repoRoot, () => rollbackSetupUnlocked(repoRoot, options));
261
+ }
262
+
263
+ export async function pruneRuntimeLogs(runtimeRoot, options = {}) {
264
+ const maxBytes = options.maxBytes ?? MAX_RUNTIME_LOG_BYTES;
265
+ const maxFiles = options.maxFiles ?? MAX_RUNTIME_LOG_FILES;
266
+ const logs = [];
267
+ async function collect(current) {
268
+ const entries = await fs.readdir(current, { withFileTypes: true }).catch((error) => {
269
+ if (error?.code === "ENOENT") return;
270
+ throw error;
271
+ });
272
+ for (const entry of entries ?? []) {
273
+ const target = path.join(current, entry.name);
274
+ if (entry.isDirectory()) await collect(target);
275
+ else if (entry.isFile() && /\.(log|jsonl)$/.test(entry.name)) {
276
+ const stat = await fs.stat(target);
277
+ logs.push({ target, size: stat.size, mtimeMs: stat.mtimeMs });
278
+ }
279
+ }
280
+ }
281
+ await collect(runtimeRoot);
282
+ logs.sort((a, b) => b.mtimeMs - a.mtimeMs);
283
+ let total = 0;
284
+ let retained = 0;
285
+ for (let index = 0; index < logs.length; index += 1) {
286
+ const log = logs[index];
287
+ if (index >= maxFiles || total + log.size > maxBytes) {
288
+ await fs.rm(log.target, { force: true });
289
+ continue;
290
+ }
291
+ total += log.size;
292
+ retained += 1;
293
+ }
294
+ return { retained, bytes: total };
295
+ }
296
+
297
+ export async function compactRuntimeState(repoRoot, options = {}) {
298
+ const root = path.resolve(repoRoot);
299
+ const maxAgeMs = options.maxAgeMs ?? 30 * 24 * 60 * 60 * 1000;
300
+ const maxBytes = options.maxBytes ?? MAX_RUNTIME_LOG_BYTES;
301
+ const maxFiles = options.maxFiles ?? MAX_RUNTIME_LOG_FILES;
302
+ const archivePath = path.join(root, ".ma", "archive", "runtime-summary.jsonl");
303
+ const roots = [
304
+ path.join(root, ".ma", "runtime"),
305
+ path.join(root, ".ma", "hooks", "receipts"),
306
+ path.join(root, ".ma", "learning"),
307
+ path.join(root, ".ma", "state", "recovery-receipts"),
308
+ ];
309
+ const entries = [];
310
+ async function collect(current) {
311
+ const children = await fs.readdir(current, { withFileTypes: true }).catch(() => []);
312
+ for (const child of children) {
313
+ const target = path.join(current, child.name);
314
+ if (child.isDirectory()) await collect(target);
315
+ else if (child.isFile() && /\.(log|jsonl|json)$/.test(child.name)) {
316
+ const stat = await fs.stat(target);
317
+ entries.push({ target, size: stat.size, mtimeMs: stat.mtimeMs });
318
+ }
319
+ }
320
+ }
321
+ await Promise.all(roots.map(collect));
322
+ entries.sort((left, right) => right.mtimeMs - left.mtimeMs);
323
+ const cutoff = Date.now() - maxAgeMs;
324
+ const archive = [];
325
+ let activeBytes = 0;
326
+ let activeFiles = 0;
327
+ for (const entry of entries) {
328
+ const stale = entry.mtimeMs < cutoff;
329
+ const overBudget = activeFiles >= maxFiles || activeBytes + entry.size > maxBytes;
330
+ if (!stale && !overBudget) {
331
+ activeBytes += entry.size;
332
+ activeFiles += 1;
333
+ continue;
334
+ }
335
+ archive.push({
336
+ path: path.relative(root, entry.target),
337
+ size: entry.size,
338
+ modifiedAt: new Date(entry.mtimeMs).toISOString(),
339
+ reason: stale ? "retention" : "budget",
340
+ });
341
+ await fs.rm(entry.target, { force: true });
342
+ }
343
+ if (archive.length) {
344
+ await fs.mkdir(path.dirname(archivePath), { recursive: true });
345
+ await fs.appendFile(
346
+ archivePath,
347
+ archive.map((entry) => `${JSON.stringify(entry)}\n`).join(""),
348
+ { mode: 0o600 },
349
+ );
350
+ }
351
+ return {
352
+ retained: activeFiles,
353
+ archived: archive.length,
354
+ bytes: activeBytes,
355
+ archivePath: archive.length ? path.relative(root, archivePath) : null,
356
+ };
357
+ }
358
+
359
+ export const SETUP_RECEIPT_PATH = RECEIPT_RELATIVE_PATH;
@@ -0,0 +1,78 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ const schemaPath = path.join(
6
+ path.dirname(fileURLToPath(import.meta.url)),
7
+ "..",
8
+ "schemas",
9
+ "skill-frontmatter.schema.json",
10
+ );
11
+ const schema = JSON.parse(fs.readFileSync(schemaPath, "utf8"));
12
+ export const SKILL_FRONTMATTER_SCHEMA_VERSION = "1.0.0";
13
+
14
+ function parseScalar(value, lineNumber) {
15
+ if (!value) return "";
16
+ if (value.startsWith('"')) {
17
+ try {
18
+ const parsed = JSON.parse(value);
19
+ if (typeof parsed !== "string") throw new Error("must be a string");
20
+ return parsed;
21
+ } catch {
22
+ throw new Error(`frontmatter line ${lineNumber}: invalid double-quoted scalar`);
23
+ }
24
+ }
25
+ if (value.startsWith("'")) {
26
+ if (!value.endsWith("'") || value.length < 2) {
27
+ throw new Error(`frontmatter line ${lineNumber}: invalid single-quoted scalar`);
28
+ }
29
+ return value.slice(1, -1).replace(/''/g, "'");
30
+ }
31
+ if (/[[\]{},]/.test(value)) {
32
+ throw new Error(`frontmatter line ${lineNumber}: only scalar values are supported`);
33
+ }
34
+ return value;
35
+ }
36
+
37
+ export function parseSkillFrontmatter(content) {
38
+ const match = String(content).match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
39
+ if (!match) throw new Error("SKILL.md missing frontmatter");
40
+
41
+ const result = {};
42
+ for (const [index, rawLine] of match[1].split(/\r?\n/).entries()) {
43
+ const lineNumber = index + 2;
44
+ const line = rawLine.trim();
45
+ if (!line || line.startsWith("#")) continue;
46
+ const field = line.match(/^([A-Za-z][A-Za-z0-9_-]*):(?:[ \t]*(.*))$/);
47
+ if (!field) throw new Error(`frontmatter line ${lineNumber}: expected key: value`);
48
+ const [, key, rawValue] = field;
49
+ if (Object.hasOwn(result, key)) throw new Error(`frontmatter: duplicate ${key}`);
50
+ result[key] = parseScalar(rawValue.trim(), lineNumber);
51
+ }
52
+ return result;
53
+ }
54
+
55
+ export function validateSkillFrontmatter(frontmatter, skillName = null) {
56
+ for (const key of Object.keys(frontmatter)) {
57
+ if (!Object.hasOwn(schema.properties, key)) {
58
+ throw new Error(`frontmatter has unknown field ${key}`);
59
+ }
60
+ }
61
+ for (const key of schema.required) {
62
+ if (typeof frontmatter[key] !== "string" || frontmatter[key].length === 0) {
63
+ throw new Error(`frontmatter missing ${key}`);
64
+ }
65
+ }
66
+ if (!new RegExp(schema.properties.name.pattern).test(frontmatter.name)) {
67
+ throw new Error("frontmatter name must be lowercase kebab-case");
68
+ }
69
+ if (frontmatter.description.length < schema.properties.description.minLength) {
70
+ throw new Error("frontmatter description is missing or too short");
71
+ }
72
+ if (skillName && frontmatter.name !== skillName) {
73
+ throw new Error("frontmatter name must match directory name");
74
+ }
75
+ return frontmatter;
76
+ }
77
+
78
+ export const skillFrontmatterSchema = schema;