@jstn-sdk/ma 0.1.13 → 0.14.0

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 (253) 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 +7 -7
  14. package/DEMO.md +15 -6
  15. package/LICENSE +0 -0
  16. package/README.md +126 -793
  17. package/bin/ma.js +585 -97
  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/autonomous-tasks.md +46 -0
  23. package/docs/codex-integration.md +16 -0
  24. package/docs/getting-started.md +28 -4
  25. package/docs/installed-sdk.md +0 -0
  26. package/docs/mcp-setup.md +30 -1
  27. package/docs/qa/{release-issue-gates-0.1.13.json → release-issue-gates-0.14.0.json} +77 -77
  28. package/docs/qa/release-readiness-0.1.5.md +0 -0
  29. package/docs/qa/{release-readiness-0.1.13.md → release-readiness-0.14.0.md} +12 -12
  30. package/docs/quality.md +24 -0
  31. package/docs/reference/native-engineering-patterns.md +0 -0
  32. package/docs/reference/native-security-playbooks.md +0 -0
  33. package/docs/reference/native-source-selection.md +0 -0
  34. package/docs/reference/native-style-and-deslop.md +0 -0
  35. package/docs/release-spec.md +27 -14
  36. package/docs/skills.md +1 -1
  37. package/index.js +73 -0
  38. package/mcp/collections.json +0 -0
  39. package/mcp/fallback.json +0 -0
  40. package/mcp/local/code-intel.js +179 -41
  41. package/mcp/local/context.js +180 -0
  42. package/mcp/local/memory.js +17 -6
  43. package/mcp/local/playbooks.js +0 -0
  44. package/mcp/local/quality.js +39 -0
  45. package/mcp/local/state.js +7 -3
  46. package/mcp/local/team-run.js +8 -3
  47. package/mcp/local/trace.js +0 -0
  48. package/mcp/local-capabilities.json +9 -0
  49. package/mcp/native-playbooks.json +0 -0
  50. package/mcp/servers.json +0 -0
  51. package/package.json +26 -4
  52. package/plugins/meta-architect/.app.json +1 -1
  53. package/plugins/meta-architect/.codex-plugin/plugin.json +1 -1
  54. package/plugins/meta-architect/.mcp.json +1 -1
  55. package/plugins/meta-architect/README.md +2 -2
  56. package/plugins/meta-architect/obsidian/main.js +137 -4
  57. package/plugins/meta-architect/obsidian/manifest.json +1 -1
  58. package/plugins/meta-architect/obsidian/styles.css +0 -0
  59. package/plugins/meta-architect/skills/align/SKILL.md +0 -0
  60. package/plugins/meta-architect/skills/align/agents/openai.yaml +0 -0
  61. package/plugins/meta-architect/skills/align/references/shared-language.md +0 -0
  62. package/plugins/meta-architect/skills/arch/SKILL.md +0 -0
  63. package/plugins/meta-architect/skills/arch/agents/openai.yaml +0 -0
  64. package/plugins/meta-architect/skills/build/SKILL.md +0 -0
  65. package/plugins/meta-architect/skills/build/agents/openai.yaml +0 -0
  66. package/plugins/meta-architect/skills/cleanup/SKILL.md +0 -0
  67. package/plugins/meta-architect/skills/cleanup/agents/openai.yaml +0 -0
  68. package/plugins/meta-architect/skills/cleanup/references/style-and-deslop.md +0 -0
  69. package/plugins/meta-architect/skills/diagnose/SKILL.md +0 -0
  70. package/plugins/meta-architect/skills/diagnose/agents/openai.yaml +0 -0
  71. package/plugins/meta-architect/skills/flow/SKILL.md +0 -0
  72. package/plugins/meta-architect/skills/flow/agents/openai.yaml +0 -0
  73. package/plugins/meta-architect/skills/maestro/SKILL.md +0 -0
  74. package/plugins/meta-architect/skills/maestro/agents/openai.yaml +0 -0
  75. package/plugins/meta-architect/skills/maestro/references/core-release-rules.md +0 -0
  76. package/plugins/meta-architect/skills/maestro/references/native-ingest-map.md +0 -0
  77. package/plugins/meta-architect/skills/sage/SKILL.md +0 -0
  78. package/plugins/meta-architect/skills/sage/agents/openai.yaml +0 -0
  79. package/plugins/meta-architect/skills/sage/references/source-selection.md +0 -0
  80. package/plugins/meta-architect/skills/tdd/SKILL.md +0 -0
  81. package/plugins/meta-architect/skills/tdd/agents/openai.yaml +0 -0
  82. package/plugins/meta-architect/skills/vet/SKILL.md +0 -0
  83. package/plugins/meta-architect/skills/vet/agents/openai.yaml +0 -0
  84. package/plugins/meta-architect/skills/vet/references/security-playbooks.md +0 -0
  85. package/plugins/meta-architect/skills/vibe/SKILL.md +0 -0
  86. package/plugins/meta-architect/skills/vibe/agents/openai.yaml +0 -0
  87. package/schemas/autonomous-task-queue.schema.json +56 -0
  88. package/schemas/handoff-packet.schema.json +36 -0
  89. package/schemas/setup-receipt.schema.json +28 -0
  90. package/schemas/skill-frontmatter.schema.json +20 -0
  91. package/schemas/support-bundle.schema.json +37 -0
  92. package/schemas/task-contract.schema.json +31 -0
  93. package/scripts/active-autonomy-hook.mjs +141 -11
  94. package/scripts/biome-staged.js +0 -0
  95. package/scripts/build-linux-packages.mjs +7 -3
  96. package/scripts/cleanup-test-fixtures.sh +55 -0
  97. package/scripts/context-hydration-hook.mjs +89 -0
  98. package/scripts/demo-smoke.js +285 -0
  99. package/scripts/doctor.js +10 -8
  100. package/scripts/install.sh +33 -4
  101. package/scripts/install.sh.sha256 +1 -0
  102. package/scripts/linux-package-lib.mjs +0 -0
  103. package/scripts/linux-package-smoke.mjs +2 -2
  104. package/scripts/package-size-check.mjs +26 -0
  105. package/scripts/plugin-sync.js +83 -17
  106. package/scripts/postinstall.js +17 -10
  107. package/scripts/prepack.js +9 -0
  108. package/scripts/ralph/prompt.md +0 -0
  109. package/scripts/release-metadata.js +0 -0
  110. package/scripts/release-sync.js +72 -7
  111. package/scripts/release-verify.js +100 -7
  112. package/scripts/setup-npmrc.js +48 -23
  113. package/scripts/skills-install.js +9 -2
  114. package/scripts/skills-manifest.js +2 -21
  115. package/scripts/skills-pack.js +2 -2
  116. package/scripts/skills-validate.js +5 -55
  117. package/skills/align/SKILL.md +0 -0
  118. package/skills/align/agents/openai.yaml +0 -0
  119. package/skills/align/references/shared-language.md +0 -0
  120. package/skills/arch/SKILL.md +0 -0
  121. package/skills/arch/agents/openai.yaml +0 -0
  122. package/skills/build/SKILL.md +0 -0
  123. package/skills/build/agents/openai.yaml +0 -0
  124. package/skills/cleanup/SKILL.md +0 -0
  125. package/skills/cleanup/agents/openai.yaml +0 -0
  126. package/skills/cleanup/references/style-and-deslop.md +0 -0
  127. package/skills/diagnose/SKILL.md +0 -0
  128. package/skills/diagnose/agents/openai.yaml +0 -0
  129. package/skills/flow/SKILL.md +0 -0
  130. package/skills/flow/agents/openai.yaml +0 -0
  131. package/skills/index.json +0 -0
  132. package/skills/maestro/SKILL.md +0 -0
  133. package/skills/maestro/agents/openai.yaml +0 -0
  134. package/skills/maestro/references/core-release-rules.md +0 -0
  135. package/skills/maestro/references/native-ingest-map.md +0 -0
  136. package/skills/sage/SKILL.md +0 -0
  137. package/skills/sage/agents/openai.yaml +0 -0
  138. package/skills/sage/references/source-selection.md +0 -0
  139. package/skills/tdd/SKILL.md +0 -0
  140. package/skills/tdd/agents/openai.yaml +0 -0
  141. package/skills/vet/SKILL.md +0 -0
  142. package/skills/vet/agents/openai.yaml +0 -0
  143. package/skills/vet/references/security-playbooks.md +0 -0
  144. package/skills/vibe/SKILL.md +0 -0
  145. package/skills/vibe/agents/openai.yaml +0 -0
  146. package/sprint/00-idea.md +0 -0
  147. package/sprint/01-architecture.md +0 -0
  148. package/sprint/02-oss-evidence.md +0 -0
  149. package/sprint/03-logic.md +0 -0
  150. package/sprint/04-security.md +0 -0
  151. package/sprint/05-dx-ux.md +0 -0
  152. package/sprint/06-build-plan.md +0 -0
  153. package/sprint/07-release.md +0 -0
  154. package/src/agents.js +279 -0
  155. package/src/bootstrap.js +252 -63
  156. package/src/build-gate.js +0 -0
  157. package/src/codex-app-server.js +291 -0
  158. package/src/decision-log.js +0 -0
  159. package/src/fs-utils.js +175 -4
  160. package/src/launcher.js +24 -21
  161. package/src/mcp-config.js +9 -1
  162. package/src/mcp-live-client.js +140 -7
  163. package/src/paths.js +6 -1
  164. package/src/policy.js +2 -2
  165. package/src/prelaunch.js +189 -0
  166. package/src/quality/ai-quality-orchestrator.js +328 -0
  167. package/src/release-issue-gates.js +62 -0
  168. package/src/release-operations.js +62 -0
  169. package/src/release-state.js +0 -0
  170. package/src/runtime/active-autonomy-core.js +0 -0
  171. package/src/runtime/agent-compat.js +31 -0
  172. package/src/runtime/alignment-sentinel.js +0 -0
  173. package/src/runtime/architect-review.js +248 -10
  174. package/src/runtime/autonomous-tasks.js +408 -0
  175. package/src/runtime/build-readiness.js +0 -0
  176. package/src/runtime/code-graph-rehearse.js +0 -0
  177. package/src/runtime/codeburn-core.js +112 -0
  178. package/src/runtime/context-authority.js +40 -0
  179. package/src/runtime/context-economy-core.js +40 -0
  180. package/src/runtime/continuity-graph.js +201 -0
  181. package/src/runtime/continuity-notes.js +71 -26
  182. package/src/runtime/core-source-ingest.js +0 -0
  183. package/src/runtime/detached-provider.js +82 -14
  184. package/src/runtime/doctor-report.js +18 -0
  185. package/src/runtime/environment-awareness-core.js +0 -0
  186. package/src/runtime/exposure-catalog.js +0 -0
  187. package/src/runtime/graphify-core.js +167 -0
  188. package/src/runtime/guidance-stack.js +9 -2
  189. package/src/runtime/handoff-packets.js +92 -0
  190. package/src/runtime/headroom-core.js +104 -0
  191. package/src/runtime/helper-orchestration-core.js +0 -0
  192. package/src/runtime/hook-profiles.js +20 -0
  193. package/src/runtime/learning-loop-core.js +110 -4
  194. package/src/runtime/live-agent-verification.js +122 -0
  195. package/src/runtime/maestro-events.js +0 -0
  196. package/src/runtime/maestro-manager.js +0 -0
  197. package/src/runtime/maestro-output.js +58 -0
  198. package/src/runtime/maestro-state.js +0 -0
  199. package/src/runtime/managed-markdown.js +29 -0
  200. package/src/runtime/mcp-authority.js +147 -0
  201. package/src/runtime/mcp-policy.js +0 -0
  202. package/src/runtime/obsidian-integration-core.js +255 -17
  203. package/src/runtime/obsidian-plugin-bridge.js +344 -38
  204. package/src/runtime/orchestrator.js +0 -0
  205. package/src/runtime/pi-maestro-core.js +89 -0
  206. package/src/runtime/preferences.js +79 -0
  207. package/src/runtime/project-context.js +636 -0
  208. package/src/runtime/prompt-strategy-core.js +0 -0
  209. package/src/runtime/quorum-review.js +0 -0
  210. package/src/runtime/ralph-execution-core.js +0 -0
  211. package/src/runtime/redaction-gateway.js +218 -19
  212. package/src/runtime/runtime-state.js +94 -2
  213. package/src/runtime/schema-migrations.js +162 -0
  214. package/src/runtime/semantic-recording-core.js +0 -0
  215. package/src/runtime/signal-hooks.js +13 -0
  216. package/src/runtime/skills-registry-export.js +92 -8
  217. package/src/runtime/startup-path.js +0 -0
  218. package/src/runtime/task-contracts.js +83 -0
  219. package/src/runtime/universal-plugin-broker-core.js +373 -50
  220. package/src/runtime/workspace-intelligence-runtime.js +0 -0
  221. package/src/runtime/workspace-virtualizer.js +0 -0
  222. package/src/runtime/workspaces.js +0 -0
  223. package/src/runtime-artifacts.js +0 -0
  224. package/src/setup-lifecycle.js +359 -0
  225. package/src/skill-frontmatter.js +78 -0
  226. package/src/skill-installer.js +226 -71
  227. package/src/skills.js +297 -19
  228. package/src/state-sync.js +0 -0
  229. package/src/test-fixtures.js +543 -0
  230. package/src/tui/grid.js +67 -0
  231. package/src/tui/status-grid.js +23 -0
  232. package/support-bundle.json +114 -0
  233. package/templates/AGENTS.md +6 -2
  234. package/templates/catalog-manifest.json +0 -0
  235. package/templates/model-instructions/core.md +0 -0
  236. package/templates/model-instructions/release.md +0 -0
  237. package/templates/model-instructions/security.md +0 -0
  238. package/templates/quality/ai-quality-rules.yml +37 -0
  239. package/docs/assets/image/Screenshot(1).png +0 -0
  240. package/docs/assets/image/Screenshot(2).png +0 -0
  241. package/docs/assets/image/Screenshot(3).png +0 -0
  242. package/docs/assets/image/Screenshot(4).png +0 -0
  243. package/docs/assets/image/Screenshot(5).png +0 -0
  244. package/docs/assets/image/Screenshot(6).png +0 -0
  245. package/docs/assets/image/Screenshot(7).png +0 -0
  246. package/docs/assets/image/Screenshot(8).png +0 -0
  247. package/docs/assets/image/Screenshot(9).png +0 -0
  248. package/docs/assets/meta-architect-logo.png +0 -0
  249. package/docs/assets/meta-architect-logo.svg +0 -8
  250. package/docs/onboarding.md +0 -81
  251. package/docs/prompt-guidance-contract.md +0 -17
  252. package/docs/prompt-guidance-fragments/active-autonomy-core.md +0 -7
  253. package/docs/skills-publishing.md +0 -255
@@ -0,0 +1,112 @@
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import { ensureDir, readJson, writeFileIfMissing, writeJson } from "../fs-utils.js";
4
+ import { getRuntimeSubsystemPath } from "../paths.js";
5
+
6
+ const execFileAsync = promisify(execFile);
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
+ try {
91
+ const result = await exec(executable, commandArgs, { cwd, encoding: "utf8", timeout: 15_000 });
92
+ const entries = parseCodeburnOutput(result.stdout ?? result);
93
+ const normalized = entries
94
+ .filter((entry) => entry && typeof entry === "object")
95
+ .map(normalizeEntry);
96
+ const usage = {
97
+ schemaVersion: codeburnSchemaVersion,
98
+ entries: normalized,
99
+ totalTokens: normalized.reduce((sum, entry) => sum + entry.tokens, 0),
100
+ totalCost: normalized.reduce((sum, entry) => sum + entry.cost, 0),
101
+ syncedAt: new Date().toISOString(),
102
+ };
103
+ await writeJson(getCodeburnLogPath(), usage);
104
+ return { available: true, usage };
105
+ } catch (error) {
106
+ return {
107
+ available: false,
108
+ usage: existing,
109
+ reason: error instanceof Error ? error.message : "codeburn unavailable",
110
+ };
111
+ }
112
+ }
@@ -0,0 +1,40 @@
1
+ export const contextAuthorityOrder = [
2
+ "source_truth",
3
+ "verified_evidence",
4
+ "hook_evidence",
5
+ "generated_context",
6
+ "vault_note",
7
+ "learning_memory",
8
+ "external_reference",
9
+ "stale",
10
+ ];
11
+
12
+ export function createFreshness({
13
+ sourceHash = null,
14
+ sourceFiles = [],
15
+ checkedAt = new Date().toISOString(),
16
+ status = "fresh",
17
+ } = {}) {
18
+ return {
19
+ status,
20
+ stale: status === "stale",
21
+ checkedAt,
22
+ sourceHash,
23
+ sourceFiles: [...sourceFiles],
24
+ changedFiles: [...sourceFiles],
25
+ };
26
+ }
27
+
28
+ export function resolveContextConflict(candidates = []) {
29
+ return (
30
+ [...candidates]
31
+ .filter((candidate) => candidate && typeof candidate === "object")
32
+ .sort((left, right) => {
33
+ const authority =
34
+ contextAuthorityOrder.indexOf(left.authority) -
35
+ contextAuthorityOrder.indexOf(right.authority);
36
+ if (authority !== 0) return authority;
37
+ return Number(Boolean(left.freshness?.stale)) - Number(Boolean(right.freshness?.stale));
38
+ })[0] ?? null
39
+ );
40
+ }
@@ -184,6 +184,46 @@ export function createMcpDescriptorEconomy({ name, description, inputSchema = {}
184
184
  };
185
185
  }
186
186
 
187
+ export function createBudgetedContext({ items = [], budgetChars = 12000, topic } = {}) {
188
+ const candidates = items
189
+ .filter((item) => item && typeof item.id === "string" && typeof item.text === "string")
190
+ .map((item, index) => ({ ...item, tier: item.tier ?? index }));
191
+ const topical = topic
192
+ ? candidates.filter((item) =>
193
+ `${item.topic ?? ""} ${item.id}`.toLowerCase().includes(topic.toLowerCase()),
194
+ )
195
+ : candidates;
196
+ const pool = topical.length > 0 ? topical : candidates;
197
+ let remaining = Math.max(0, budgetChars);
198
+ const loaded = [];
199
+ const skipped = candidates
200
+ .filter((item) => !pool.includes(item))
201
+ .map((item) => ({ id: item.id, reason: "topic_mismatch" }));
202
+ const context = {};
203
+
204
+ for (const item of [...pool].sort((left, right) => left.tier - right.tier)) {
205
+ const view = createContextEconomyView({ text: item.text, level: "budgeted_context" });
206
+ if (view.output_chars <= remaining) {
207
+ context[item.id] = view.output;
208
+ remaining -= view.output_chars;
209
+ loaded.push(item.id);
210
+ } else {
211
+ skipped.push({ id: item.id, reason: "budget_exhausted" });
212
+ }
213
+ }
214
+
215
+ return {
216
+ record_type: "budgeted_context",
217
+ budget_chars: budgetChars,
218
+ used_chars: budgetChars - remaining,
219
+ topic: topic ?? null,
220
+ loaded,
221
+ skipped,
222
+ fallback_expanded: Boolean(topic && topical.length === 0),
223
+ context,
224
+ };
225
+ }
226
+
187
227
  export function validateContextEconomyCore(value) {
188
228
  if (!value || typeof value !== "object" || Array.isArray(value)) {
189
229
  throw new Error("Context Economy core must be an object");
@@ -0,0 +1,201 @@
1
+ import { createHash } from "node:crypto";
2
+ import { ensureDir, readJson, writeFileIfMissing, writeJson } from "../fs-utils.js";
3
+ import { getRuntimeSubsystemPath } from "../paths.js";
4
+
5
+ export const continuityGraphSchemaVersion = "0.1.0";
6
+
7
+ export function getContinuityGraphPath() {
8
+ return getRuntimeSubsystemPath("memory", "graph.json");
9
+ }
10
+
11
+ export function createDefaultContinuityGraph() {
12
+ return {
13
+ schemaVersion: continuityGraphSchemaVersion,
14
+ record_type: "continuity_knowledge_graph",
15
+ humanReadableMirror: ".ma/memory/notes.md",
16
+ nodes: [],
17
+ edges: [],
18
+ lastUpdatedAt: null,
19
+ };
20
+ }
21
+
22
+ export function validateContinuityGraph(value) {
23
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
24
+ throw new Error("continuity graph must be an object");
25
+ }
26
+ if (value.schemaVersion !== continuityGraphSchemaVersion) {
27
+ throw new Error(`Unsupported continuity graph schemaVersion: ${value.schemaVersion}`);
28
+ }
29
+ if (value.record_type !== "continuity_knowledge_graph") {
30
+ throw new Error("continuity graph has an invalid record_type");
31
+ }
32
+ if (!Array.isArray(value.nodes) || !Array.isArray(value.edges)) {
33
+ throw new Error("continuity graph requires nodes and edges arrays");
34
+ }
35
+ for (const node of value.nodes) {
36
+ if (
37
+ !node ||
38
+ typeof node !== "object" ||
39
+ typeof node.id !== "string" ||
40
+ typeof node.type !== "string" ||
41
+ typeof node.label !== "string" ||
42
+ typeof node.scope !== "string" ||
43
+ !Number.isInteger(node.mentions) ||
44
+ node.mentions < 0
45
+ ) {
46
+ throw new Error("continuity graph contains an invalid node");
47
+ }
48
+ }
49
+ const nodeIds = new Set(value.nodes.map((node) => node.id));
50
+ for (const edge of value.edges) {
51
+ if (
52
+ !edge ||
53
+ typeof edge !== "object" ||
54
+ typeof edge.id !== "string" ||
55
+ typeof edge.source !== "string" ||
56
+ typeof edge.target !== "string" ||
57
+ typeof edge.relation !== "string" ||
58
+ !nodeIds.has(edge.source) ||
59
+ !nodeIds.has(edge.target)
60
+ ) {
61
+ throw new Error("continuity graph contains an invalid edge");
62
+ }
63
+ }
64
+ return value;
65
+ }
66
+
67
+ export async function seedContinuityGraphArtifacts() {
68
+ await ensureDir(getRuntimeSubsystemPath("memory"));
69
+ await writeFileIfMissing(
70
+ getContinuityGraphPath(),
71
+ `${JSON.stringify(createDefaultContinuityGraph(), null, 2)}\n`,
72
+ );
73
+ }
74
+
75
+ export async function loadContinuityGraph() {
76
+ return validateContinuityGraph(await readJson(getContinuityGraphPath()));
77
+ }
78
+
79
+ function stableId(prefix, value) {
80
+ return `${prefix}:${createHash("sha256").update(value).digest("hex").slice(0, 16)}`;
81
+ }
82
+
83
+ function deriveEntities(content, explicit = []) {
84
+ const values = [
85
+ ...explicit,
86
+ ...(content.match(/`([^`]+)`|#([a-z][\w-]*)|\b[A-Z][A-Za-z0-9_-]{2,}\b/g) ?? []),
87
+ ]
88
+ .map((value) => value.replaceAll("`", "").replace(/^#/, "").trim().toLowerCase())
89
+ .filter((value) => value.length >= 3 && value.length <= 80);
90
+ return [...new Set(values)].slice(0, 32);
91
+ }
92
+
93
+ export function mergeContinuityGraph(
94
+ graph,
95
+ { content, scope, timestamp, entities = [], relationships = [] },
96
+ ) {
97
+ const next = validateContinuityGraph(graph);
98
+ const noteId = stableId("note", `${timestamp}\0${scope}\0${content}`);
99
+ const entityNames = deriveEntities(content, entities);
100
+ const nodes = [...next.nodes];
101
+ const edges = [...next.edges];
102
+ const nodeById = new Map(nodes.map((node) => [node.id, node]));
103
+ const edgeKeys = new Set(edges.map((edge) => `${edge.source}\0${edge.target}\0${edge.relation}`));
104
+ const addNode = (node) => {
105
+ const existing = nodeById.get(node.id);
106
+ if (existing) {
107
+ existing.updatedAt = timestamp;
108
+ existing.mentions = (existing.mentions ?? 0) + (node.mentions ?? 0);
109
+ return;
110
+ }
111
+ nodeById.set(node.id, node);
112
+ nodes.push(node);
113
+ };
114
+ const addEdge = (source, target, relation) => {
115
+ const key = `${source}\0${target}\0${relation}`;
116
+ if (source === target || edgeKeys.has(key)) return;
117
+ edgeKeys.add(key);
118
+ edges.push({ id: stableId("edge", key), source, target, relation, updatedAt: timestamp });
119
+ };
120
+
121
+ addNode({
122
+ id: noteId,
123
+ type: "session",
124
+ label: `${scope} note`,
125
+ scope,
126
+ mentions: 1,
127
+ createdAt: timestamp,
128
+ updatedAt: timestamp,
129
+ });
130
+ const entityIds = entityNames.map((name) => {
131
+ const id = stableId("entity", name);
132
+ addNode({
133
+ id,
134
+ type: "entity",
135
+ label: name,
136
+ scope,
137
+ mentions: 1,
138
+ createdAt: timestamp,
139
+ updatedAt: timestamp,
140
+ });
141
+ addEdge(noteId, id, "mentions");
142
+ return id;
143
+ });
144
+ for (let index = 0; index < entityIds.length; index += 1) {
145
+ for (let other = index + 1; other < entityIds.length; other += 1)
146
+ addEdge(entityIds[index], entityIds[other], "co_occurs");
147
+ }
148
+ for (const relationship of relationships) {
149
+ if (!relationship || typeof relationship !== "object") continue;
150
+ const from = stableId("entity", String(relationship.from ?? "").toLowerCase());
151
+ const to = stableId("entity", String(relationship.to ?? "").toLowerCase());
152
+ if (nodeById.has(from) && nodeById.has(to) && relationship.relation)
153
+ addEdge(from, to, String(relationship.relation));
154
+ }
155
+ return {
156
+ ...next,
157
+ nodes: nodes.slice(-1000),
158
+ edges: edges.slice(-4000),
159
+ lastUpdatedAt: timestamp,
160
+ };
161
+ }
162
+
163
+ export function queryContinuityGraph(graph, { nodeId, label, depth = 2, limit = 100 } = {}) {
164
+ const value = validateContinuityGraph(graph);
165
+ const normalizedLabel = label?.trim().toLowerCase();
166
+ const boundedLimit = Math.max(1, Math.min(Number(limit) || 100, 1000));
167
+ const starts = value.nodes.filter(
168
+ (node) =>
169
+ node.id === nodeId || (normalizedLabel && node.label.toLowerCase() === normalizedLabel),
170
+ );
171
+ if (starts.length === 0) return { nodes: [], edges: [], depth: 0 };
172
+ const included = new Set(starts.map((node) => node.id));
173
+ let frontier = [...included];
174
+ const maxDepth = Math.max(0, Math.min(Number(depth) || 0, 5));
175
+ for (let level = 0; level < maxDepth; level += 1) {
176
+ const next = [];
177
+ for (const edge of value.edges) {
178
+ if (frontier.includes(edge.source) && !included.has(edge.target)) next.push(edge.target);
179
+ if (frontier.includes(edge.target) && !included.has(edge.source)) next.push(edge.source);
180
+ }
181
+ frontier = [...new Set(next)].slice(0, boundedLimit);
182
+ frontier.forEach((id) => {
183
+ included.add(id);
184
+ });
185
+ if (frontier.length === 0) break;
186
+ }
187
+ const nodes = value.nodes.filter((node) => included.has(node.id)).slice(0, boundedLimit);
188
+ const ids = new Set(nodes.map((node) => node.id));
189
+ return {
190
+ nodes,
191
+ edges: value.edges.filter((edge) => ids.has(edge.source) && ids.has(edge.target)),
192
+ depth: maxDepth,
193
+ };
194
+ }
195
+
196
+ export async function mergeContinuityGraphEntry(entry) {
197
+ const graph = await loadContinuityGraph();
198
+ const next = mergeContinuityGraph(graph, entry);
199
+ await writeJson(getContinuityGraphPath(), next);
200
+ return next;
201
+ }
@@ -1,8 +1,25 @@
1
1
  import fs from "node:fs/promises";
2
- import { ensureDir, readJson, writeFileIfMissing, writeJson } from "../fs-utils.js";
2
+ import path from "node:path";
3
+ import {
4
+ ensureDir,
5
+ readJson,
6
+ writeFileIfMissing,
7
+ writeJson,
8
+ writeTextAtomically,
9
+ } from "../fs-utils.js";
3
10
  import { getRuntimeSubsystemPath } from "../paths.js";
11
+ import { mergeContinuityGraphEntry, seedContinuityGraphArtifacts } from "./continuity-graph.js";
4
12
  import { guardLeaderMutation } from "./runtime-state.js";
5
13
 
14
+ export const knowledgeScopes = ["personal/local", "project-shared", "team/process", "session-only"];
15
+ let continuityWriteQueue = Promise.resolve();
16
+ const scopeFiles = {
17
+ "personal/local": "personal.md",
18
+ "project-shared": "notes.md",
19
+ "team/process": "team.md",
20
+ "session-only": "session.md",
21
+ };
22
+
6
23
  export function getContinuityRoot() {
7
24
  return getRuntimeSubsystemPath("memory");
8
25
  }
@@ -21,6 +38,13 @@ export async function seedContinuityArtifacts() {
21
38
  getContinuityNotesPath(),
22
39
  "# Continuity Notes\n\nNo continuity notes captured yet.\n",
23
40
  );
41
+ await seedContinuityGraphArtifacts();
42
+ for (const file of ["personal.md", "team.md", "session.md"]) {
43
+ await writeFileIfMissing(
44
+ path.join(getContinuityRoot(), file),
45
+ `# ${file.replace(".md", "")} knowledge\n\nNo notes captured yet.\n`,
46
+ );
47
+ }
24
48
  await writeFileIfMissing(
25
49
  getContinuityIndexPath(),
26
50
  `${JSON.stringify(
@@ -28,6 +52,7 @@ export async function seedContinuityArtifacts() {
28
52
  schemaVersion: "0.1.0",
29
53
  sessionCount: 0,
30
54
  lastUpdatedAt: null,
55
+ knowledgeScopes,
31
56
  },
32
57
  null,
33
58
  2,
@@ -36,7 +61,17 @@ export async function seedContinuityArtifacts() {
36
61
  }
37
62
 
38
63
  export async function loadContinuityNotes() {
39
- return fs.readFile(getContinuityNotesPath(), "utf8");
64
+ return loadScopedKnowledge({ scopes: ["project-shared", "team/process"] });
65
+ }
66
+
67
+ export async function loadScopedKnowledge({ scopes = ["project-shared", "team/process"] } = {}) {
68
+ const selected = scopes.filter((scope) => knowledgeScopes.includes(scope));
69
+ const notes = await Promise.all(
70
+ selected.map((scope) =>
71
+ fs.readFile(path.join(getContinuityRoot(), scopeFiles[scope]), "utf8").catch(() => ""),
72
+ ),
73
+ );
74
+ return notes.filter(Boolean).join("\n\n");
40
75
  }
41
76
 
42
77
  export async function loadContinuityIndex() {
@@ -47,33 +82,43 @@ export async function storeContinuityNote(content, options = {}) {
47
82
  const guard = await guardLeaderMutation({
48
83
  actor: options.actor,
49
84
  kind: "memory-note-write",
50
- payload: { content },
85
+ payload: { content, scope: options.scope ?? "session-only" },
51
86
  });
52
87
  if (!guard.allowed) {
53
88
  return { proposed: true, proposalPath: guard.proposalPath };
54
89
  }
55
90
 
56
- const note = content.trim();
57
- if (!note) {
58
- throw new Error("memory.store_note requires non-empty content");
59
- }
60
-
61
- const [existingNotes, existingIndex] = await Promise.all([
62
- loadContinuityNotes(),
63
- loadContinuityIndex(),
64
- ]);
65
- const timestamp = new Date().toISOString();
66
- const nextNotes = `${existingNotes.trimEnd()}\n\n## ${timestamp}\n\n${note}\n`;
67
- const nextIndex = {
68
- ...existingIndex,
69
- sessionCount: existingIndex.sessionCount + 1,
70
- lastUpdatedAt: timestamp,
71
- };
72
-
73
- await Promise.all([
74
- fs.writeFile(getContinuityNotesPath(), nextNotes),
75
- writeJson(getContinuityIndexPath(), nextIndex),
76
- ]);
77
-
78
- return { proposed: false, proposalPath: null, index: nextIndex };
91
+ const operation = continuityWriteQueue.then(async () => {
92
+ const note = content.trim();
93
+ if (!note) throw new Error("memory.store_note requires non-empty content");
94
+ const scope = options.scope ?? "project-shared";
95
+ if (!knowledgeScopes.includes(scope)) throw new Error(`Unsupported knowledge scope: ${scope}`);
96
+ const [existingNotes, existingIndex] = await Promise.all([
97
+ fs.readFile(path.join(getContinuityRoot(), scopeFiles[scope]), "utf8").catch(() => ""),
98
+ loadContinuityIndex(),
99
+ ]);
100
+ const timestamp = new Date().toISOString();
101
+ const nextNotes = `${existingNotes.trimEnd()}\n\n## ${timestamp}\n\n${note}\n`;
102
+ const nextIndex = {
103
+ ...existingIndex,
104
+ sessionCount: existingIndex.sessionCount + 1,
105
+ lastUpdatedAt: timestamp,
106
+ scopeCounts: {
107
+ ...(existingIndex.scopeCounts ?? {}),
108
+ [scope]: (existingIndex.scopeCounts?.[scope] ?? 0) + 1,
109
+ },
110
+ };
111
+ await writeTextAtomically(path.join(getContinuityRoot(), scopeFiles[scope]), nextNotes);
112
+ await writeJson(getContinuityIndexPath(), nextIndex);
113
+ await mergeContinuityGraphEntry({
114
+ content: note,
115
+ scope,
116
+ timestamp,
117
+ entities: options.entities,
118
+ relationships: options.relationships,
119
+ });
120
+ return { proposed: false, proposalPath: null, index: nextIndex };
121
+ });
122
+ continuityWriteQueue = operation.catch(() => {});
123
+ return operation;
79
124
  }
File without changes
@@ -11,7 +11,10 @@ export function getRuntimeLogsRoot() {
11
11
  export function resolveDetachedProvider() {
12
12
  const preferred = process.env.MA_DETACHED_PROVIDER ?? "";
13
13
  if (preferred === "tmux" || process.env.MA_TMUX_PROVIDER === "1") {
14
- const probe = spawnSync("tmux", ["ls"], { encoding: "utf8" });
14
+ const probe = spawnSync("tmux", ["ls"], {
15
+ encoding: "utf8",
16
+ env: buildDetachedEnv(),
17
+ });
15
18
  if (!probe.error) {
16
19
  return "tmux";
17
20
  }
@@ -23,14 +26,12 @@ export function resolveDetachedProvider() {
23
26
  export async function launchDetachedTrack({ trackId, title, command, args = [] }) {
24
27
  const provider = resolveDetachedProvider();
25
28
  await ensureDir(getRuntimeLogsRoot());
26
- const logPath = path.join(getRuntimeLogsRoot(), `${trackId}.log`);
29
+ const safeTrackId = sanitizeTrackId(trackId);
30
+ const logPath = path.join(getRuntimeLogsRoot(), `${safeTrackId}.log`);
31
+ const metadata = `title_present=${Boolean(title)}\ncommand=${commandBasename(command)}\narg_count=${args.length}\n`;
27
32
 
28
33
  if (provider !== "tmux") {
29
- await fs.writeFile(
30
- logPath,
31
- `provider=none\ntitle=${title}\ncommand=${[command, ...args].join(" ")}\n`,
32
- "utf8",
33
- );
34
+ await fs.writeFile(logPath, `provider=none\n${metadata}`, "utf8");
34
35
  return {
35
36
  provider: "none",
36
37
  executionPane: null,
@@ -40,13 +41,12 @@ export async function launchDetachedTrack({ trackId, title, command, args = [] }
40
41
  };
41
42
  }
42
43
 
43
- const sessionName = `ma_${trackId.replace(/[^a-zA-Z0-9_-]/g, "_")}`;
44
- const shellCommand = `${[command, ...args].join(" ")} > ${logPath} 2>&1`;
45
- const started = spawnSync(
46
- "tmux",
47
- ["new-session", "-d", "-s", sessionName, "/bin/sh", "-lc", shellCommand],
48
- { encoding: "utf8" },
49
- );
44
+ const sessionName = `ma_${safeTrackId}`;
45
+ await fs.writeFile(logPath, `provider=tmux\n${metadata}`, "utf8");
46
+ const started = spawnSync("tmux", ["new-session", "-d", "-s", sessionName, command, ...args], {
47
+ encoding: "utf8",
48
+ env: buildDetachedEnv(),
49
+ });
50
50
 
51
51
  if (started.status !== 0) {
52
52
  await fs.writeFile(
@@ -64,6 +64,31 @@ export async function launchDetachedTrack({ trackId, title, command, args = [] }
64
64
  };
65
65
  }
66
66
 
67
+ const piped = spawnSync(
68
+ "tmux",
69
+ ["pipe-pane", "-t", sessionName, "-o", `cat >> ${quoteForShell(logPath)}`],
70
+ { encoding: "utf8", env: buildDetachedEnv() },
71
+ );
72
+ if (piped.status !== 0) {
73
+ spawnSync("tmux", ["kill-session", "-t", sessionName], {
74
+ encoding: "utf8",
75
+ env: buildDetachedEnv(),
76
+ });
77
+ await fs.writeFile(
78
+ logPath,
79
+ `provider=tmux\nstatus=failed\nstderr=${piped.stderr ?? ""}\n`,
80
+ "utf8",
81
+ );
82
+ return {
83
+ provider: "tmux",
84
+ executionPane: sessionName,
85
+ pid: null,
86
+ logPath,
87
+ started: false,
88
+ error: piped.stderr?.trim() || "tmux log pipe failed",
89
+ };
90
+ }
91
+
67
92
  return {
68
93
  provider: "tmux",
69
94
  executionPane: sessionName,
@@ -72,3 +97,46 @@ export async function launchDetachedTrack({ trackId, title, command, args = [] }
72
97
  started: true,
73
98
  };
74
99
  }
100
+
101
+ const detachedEnvKeys = [
102
+ "PATH",
103
+ "HOME",
104
+ "USERPROFILE",
105
+ "TMPDIR",
106
+ "TMP",
107
+ "TEMP",
108
+ "LANG",
109
+ "LC_ALL",
110
+ "SYSTEMROOT",
111
+ "COMSPEC",
112
+ "MA_ROOT",
113
+ ];
114
+
115
+ function buildDetachedEnv() {
116
+ return Object.fromEntries(
117
+ detachedEnvKeys
118
+ .filter((key) => process.env[key] !== undefined)
119
+ .map((key) => [key, process.env[key]]),
120
+ );
121
+ }
122
+
123
+ function sanitizeTrackId(trackId) {
124
+ const safe = String(trackId ?? "")
125
+ .replace(/[^a-zA-Z0-9_-]/g, "_")
126
+ .replace(/^_+|_+$/g, "");
127
+ return safe || "track";
128
+ }
129
+
130
+ function commandBasename(command) {
131
+ return (
132
+ String(command ?? "")
133
+ .split(/[\\/]/)
134
+ .pop() || "unknown"
135
+ );
136
+ }
137
+
138
+ function quoteForShell(value) {
139
+ // The provider command never enters a shell; this quote is only for tmux's
140
+ // fixed `cat >> path` pipe command.
141
+ return `'${String(value).replaceAll("'", `'\\''`)}'`;
142
+ }
@@ -0,0 +1,18 @@
1
+ export function summarizeDoctorStatuses(statuses) {
2
+ if (statuses.some((status) => status.kind === "BLOCKED")) return "BLOCKED";
3
+ if (statuses.some((status) => status.kind === "WARN")) return "READY_WITH_WARNINGS";
4
+ return "READY";
5
+ }
6
+
7
+ export function printDoctorStatuses(title, statuses, write = console.log) {
8
+ write(title);
9
+ write("=".repeat(title.length));
10
+ for (const status of statuses) {
11
+ const label = status.kind.padEnd(7, " ");
12
+ write(`${label} ${status.label}${status.detail ? `: ${status.detail}` : ""}`);
13
+ }
14
+ const result = summarizeDoctorStatuses(statuses);
15
+ write("");
16
+ write(`Result: ${result}`);
17
+ return result;
18
+ }
File without changes
File without changes