@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,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
  }
@@ -1,9 +1,9 @@
1
- import { spawn } from "node:child_process";
2
1
  import { createHash } from "node:crypto";
3
2
  import fs from "node:fs/promises";
4
3
  import path from "node:path";
5
4
  import { ensureDir, readJson, writeFileIfMissing, writeJson } from "../fs-utils.js";
6
5
  import { getRepoRoot, getRuntimeSubsystemPath, getRuntimeWritePath } from "../paths.js";
6
+ import { safeSpawn } from "../process-utils.js";
7
7
 
8
8
  export const coreSourceIngestSchemaVersion = "0.1.0";
9
9
 
@@ -153,7 +153,7 @@ export async function loadCoreSourceIngest() {
153
153
  export async function ingestCoreSources({
154
154
  definitions = coreSourceDefinitions,
155
155
  refresh = false,
156
- spawnImpl = spawn,
156
+ spawnImpl = safeSpawn,
157
157
  } = {}) {
158
158
  await ensureDir(getCoreSourcesRootPath());
159
159
  const sources = [];
@@ -1,8 +1,8 @@
1
- import { spawnSync } from "node:child_process";
2
1
  import fs from "node:fs/promises";
3
2
  import path from "node:path";
4
3
  import { ensureDir } from "../fs-utils.js";
5
4
  import { getRuntimeSubsystemPath } from "../paths.js";
5
+ import { assertSafeExecutable, safeSpawnSync } from "../process-utils.js";
6
6
 
7
7
  export function getRuntimeLogsRoot() {
8
8
  return getRuntimeSubsystemPath("logs");
@@ -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 = safeSpawnSync("tmux", ["ls"], {
15
+ encoding: "utf8",
16
+ env: buildDetachedEnv(),
17
+ });
15
18
  if (!probe.error) {
16
19
  return "tmux";
17
20
  }
@@ -21,16 +24,18 @@ export function resolveDetachedProvider() {
21
24
  }
22
25
 
23
26
  export async function launchDetachedTrack({ trackId, title, command, args = [] }) {
27
+ assertSafeExecutable(command, "Detached provider command");
28
+ if (!Array.isArray(args) || args.some((arg) => typeof arg !== "string" || arg.includes("\0"))) {
29
+ throw new Error("Detached provider arguments must be strings without NUL bytes");
30
+ }
24
31
  const provider = resolveDetachedProvider();
25
32
  await ensureDir(getRuntimeLogsRoot());
26
- const logPath = path.join(getRuntimeLogsRoot(), `${trackId}.log`);
33
+ const safeTrackId = sanitizeTrackId(trackId);
34
+ const logPath = path.join(getRuntimeLogsRoot(), `${safeTrackId}.log`);
35
+ const metadata = `title_present=${Boolean(title)}\ncommand=${commandBasename(command)}\narg_count=${args.length}\n`;
27
36
 
28
37
  if (provider !== "tmux") {
29
- await fs.writeFile(
30
- logPath,
31
- `provider=none\ntitle=${title}\ncommand=${[command, ...args].join(" ")}\n`,
32
- "utf8",
33
- );
38
+ await fs.writeFile(logPath, `provider=none\n${metadata}`, "utf8");
34
39
  return {
35
40
  provider: "none",
36
41
  executionPane: null,
@@ -40,12 +45,16 @@ export async function launchDetachedTrack({ trackId, title, command, args = [] }
40
45
  };
41
46
  }
42
47
 
43
- const sessionName = `ma_${trackId.replace(/[^a-zA-Z0-9_-]/g, "_")}`;
44
- const shellCommand = `${[command, ...args].join(" ")} > ${logPath} 2>&1`;
45
- const started = spawnSync(
48
+ const sessionName = `ma_${safeTrackId}`;
49
+ await fs.writeFile(logPath, `provider=tmux\n${metadata}`, "utf8");
50
+ const tmuxCommand = [command, ...args].map(quoteForShell).join(" ");
51
+ const started = safeSpawnSync(
46
52
  "tmux",
47
- ["new-session", "-d", "-s", sessionName, "/bin/sh", "-lc", shellCommand],
48
- { encoding: "utf8" },
53
+ ["new-session", "-d", "-s", sessionName, "--", tmuxCommand],
54
+ {
55
+ encoding: "utf8",
56
+ env: buildDetachedEnv(),
57
+ },
49
58
  );
50
59
 
51
60
  if (started.status !== 0) {
@@ -64,6 +73,31 @@ export async function launchDetachedTrack({ trackId, title, command, args = [] }
64
73
  };
65
74
  }
66
75
 
76
+ const piped = safeSpawnSync(
77
+ "tmux",
78
+ ["pipe-pane", "-t", sessionName, "-o", `cat >> ${quoteForShell(logPath)}`],
79
+ { encoding: "utf8", env: buildDetachedEnv() },
80
+ );
81
+ if (piped.status !== 0) {
82
+ safeSpawnSync("tmux", ["kill-session", "-t", sessionName], {
83
+ encoding: "utf8",
84
+ env: buildDetachedEnv(),
85
+ });
86
+ await fs.writeFile(
87
+ logPath,
88
+ `provider=tmux\nstatus=failed\nstderr=${piped.stderr ?? ""}\n`,
89
+ "utf8",
90
+ );
91
+ return {
92
+ provider: "tmux",
93
+ executionPane: sessionName,
94
+ pid: null,
95
+ logPath,
96
+ started: false,
97
+ error: piped.stderr?.trim() || "tmux log pipe failed",
98
+ };
99
+ }
100
+
67
101
  return {
68
102
  provider: "tmux",
69
103
  executionPane: sessionName,
@@ -72,3 +106,46 @@ export async function launchDetachedTrack({ trackId, title, command, args = [] }
72
106
  started: true,
73
107
  };
74
108
  }
109
+
110
+ const detachedEnvKeys = [
111
+ "PATH",
112
+ "HOME",
113
+ "USERPROFILE",
114
+ "TMPDIR",
115
+ "TMP",
116
+ "TEMP",
117
+ "LANG",
118
+ "LC_ALL",
119
+ "SYSTEMROOT",
120
+ "COMSPEC",
121
+ "MA_ROOT",
122
+ ];
123
+
124
+ function buildDetachedEnv() {
125
+ return Object.fromEntries(
126
+ detachedEnvKeys
127
+ .filter((key) => process.env[key] !== undefined)
128
+ .map((key) => [key, process.env[key]]),
129
+ );
130
+ }
131
+
132
+ function sanitizeTrackId(trackId) {
133
+ const safe = String(trackId ?? "")
134
+ .replace(/[^a-zA-Z0-9_-]/g, "_")
135
+ .replace(/^_+|_+$/g, "");
136
+ return safe || "track";
137
+ }
138
+
139
+ function commandBasename(command) {
140
+ return (
141
+ String(command ?? "")
142
+ .split(/[\\/]/)
143
+ .pop() || "unknown"
144
+ );
145
+ }
146
+
147
+ function quoteForShell(value) {
148
+ // The provider command never enters a shell; this quote is only for tmux's
149
+ // fixed `cat >> path` pipe command.
150
+ return `'${String(value).replaceAll("'", `'\\''`)}'`;
151
+ }
@@ -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