@jstn-sdk/ma 0.1.12 → 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 (198) hide show
  1. package/.codex/agents/Architect.toml +1 -2
  2. package/.codex/agents/Auditor.toml +1 -2
  3. package/.codex/agents/Builder.toml +1 -2
  4. package/.codex/agents/Flow.toml +1 -2
  5. package/.codex/agents/Sage.toml +1 -2
  6. package/.codex/agents/Vibe.toml +1 -2
  7. package/.codex/hooks.json +24 -13
  8. package/.codex/prompts/enforcement.md +4 -0
  9. package/.codex/prompts/onboarding.md +6 -1
  10. package/.codex/prompts/release-rules.md +0 -0
  11. package/.codex/prompts/skill-contract.md +4 -0
  12. package/COVERAGE.md +211 -0
  13. package/DEMO.md +274 -0
  14. package/LICENSE +0 -0
  15. package/README.md +132 -656
  16. package/bin/ma.js +748 -55
  17. package/data/clone-data.ledger.json +41 -0
  18. package/data/clone-data.proof.json +50 -0
  19. package/data/clone-data.rvf +37 -0
  20. package/docs/README.md +10 -1
  21. package/docs/autonomous-tasks.md +46 -0
  22. package/docs/codex-integration.md +16 -0
  23. package/docs/getting-started.md +62 -22
  24. package/docs/mcp-setup.md +64 -0
  25. package/docs/qa/release-issue-gates-0.14.0.json +644 -0
  26. package/docs/qa/release-readiness-0.1.5.md +1 -1
  27. package/docs/qa/{release-readiness-0.1.12.md → release-readiness-0.14.0.md} +25 -9
  28. package/docs/quality.md +24 -0
  29. package/docs/reference/native-security-playbooks.md +6 -0
  30. package/docs/reference/native-source-selection.md +6 -0
  31. package/docs/release-spec.md +39 -10
  32. package/docs/skills.md +14 -1
  33. package/index.js +295 -2
  34. package/mcp/collections.json +23 -6
  35. package/mcp/fallback.json +0 -0
  36. package/mcp/local/code-intel.js +179 -41
  37. package/mcp/local/context.js +180 -0
  38. package/mcp/local/memory.js +17 -6
  39. package/mcp/local/quality.js +39 -0
  40. package/mcp/local/state.js +7 -3
  41. package/mcp/local/team-run.js +56 -4
  42. package/mcp/local-capabilities.json +9 -0
  43. package/mcp/servers.json +24 -0
  44. package/package.json +29 -4
  45. package/plugins/meta-architect/.app.json +1 -1
  46. package/plugins/meta-architect/.codex-plugin/plugin.json +1 -1
  47. package/plugins/meta-architect/.mcp.json +1 -1
  48. package/plugins/meta-architect/README.md +8 -1
  49. package/plugins/meta-architect/obsidian/main.js +534 -0
  50. package/plugins/meta-architect/obsidian/manifest.json +9 -0
  51. package/plugins/meta-architect/obsidian/styles.css +7 -0
  52. package/plugins/meta-architect/skills/arch/SKILL.md +2 -0
  53. package/plugins/meta-architect/skills/build/SKILL.md +3 -0
  54. package/plugins/meta-architect/skills/flow/SKILL.md +2 -0
  55. package/plugins/meta-architect/skills/sage/SKILL.md +2 -0
  56. package/plugins/meta-architect/skills/sage/references/source-selection.md +8 -0
  57. package/plugins/meta-architect/skills/vet/SKILL.md +2 -0
  58. package/plugins/meta-architect/skills/vet/references/security-playbooks.md +7 -0
  59. package/plugins/meta-architect/skills/vibe/SKILL.md +2 -0
  60. package/schemas/autonomous-task-queue.schema.json +56 -0
  61. package/schemas/handoff-packet.schema.json +36 -0
  62. package/schemas/setup-receipt.schema.json +28 -0
  63. package/schemas/skill-frontmatter.schema.json +20 -0
  64. package/schemas/support-bundle.schema.json +37 -0
  65. package/schemas/task-contract.schema.json +31 -0
  66. package/scripts/active-autonomy-hook.mjs +226 -0
  67. package/scripts/build-linux-packages.mjs +7 -3
  68. package/scripts/cleanup-test-fixtures.sh +55 -0
  69. package/scripts/context-hydration-hook.mjs +89 -0
  70. package/scripts/demo-smoke.js +285 -0
  71. package/scripts/doctor.js +27 -12
  72. package/scripts/install.sh +57 -0
  73. package/scripts/install.sh.sha256 +1 -0
  74. package/scripts/linux-package-smoke.mjs +2 -2
  75. package/scripts/package-size-check.mjs +26 -0
  76. package/scripts/plugin-sync.js +83 -17
  77. package/scripts/postinstall.js +17 -10
  78. package/scripts/prepack.js +9 -0
  79. package/scripts/ralph/prompt.md +35 -0
  80. package/scripts/release-sync.js +72 -7
  81. package/scripts/release-verify.js +260 -2
  82. package/scripts/setup-npmrc.js +48 -23
  83. package/scripts/skills-install.js +9 -2
  84. package/scripts/skills-manifest.js +2 -21
  85. package/scripts/skills-pack.js +2 -2
  86. package/scripts/skills-validate.js +5 -55
  87. package/skills/arch/SKILL.md +2 -0
  88. package/skills/build/SKILL.md +3 -0
  89. package/skills/flow/SKILL.md +2 -0
  90. package/skills/index.json +0 -0
  91. package/skills/sage/SKILL.md +2 -0
  92. package/skills/sage/references/source-selection.md +8 -0
  93. package/skills/vet/SKILL.md +2 -0
  94. package/skills/vet/references/security-playbooks.md +7 -0
  95. package/skills/vibe/SKILL.md +2 -0
  96. package/sprint/00-idea.md +0 -0
  97. package/sprint/01-architecture.md +0 -0
  98. package/sprint/02-oss-evidence.md +0 -0
  99. package/sprint/03-logic.md +0 -0
  100. package/sprint/04-security.md +0 -0
  101. package/sprint/05-dx-ux.md +0 -0
  102. package/sprint/06-build-plan.md +0 -0
  103. package/sprint/07-release.md +0 -0
  104. package/src/agents.js +279 -0
  105. package/src/bootstrap.js +268 -62
  106. package/src/build-gate.js +2 -2
  107. package/src/codex-app-server.js +291 -0
  108. package/src/decision-log.js +0 -0
  109. package/src/fs-utils.js +175 -4
  110. package/src/launcher.js +28 -21
  111. package/src/mcp-config.js +9 -1
  112. package/src/mcp-live-client.js +422 -3
  113. package/src/paths.js +10 -1
  114. package/src/policy.js +3 -3
  115. package/src/prelaunch.js +189 -0
  116. package/src/quality/ai-quality-orchestrator.js +328 -0
  117. package/src/release-issue-gates.js +252 -0
  118. package/src/release-operations.js +62 -0
  119. package/src/release-state.js +0 -0
  120. package/src/runtime/active-autonomy-core.js +208 -0
  121. package/src/runtime/agent-compat.js +31 -0
  122. package/src/runtime/alignment-sentinel.js +165 -0
  123. package/src/runtime/architect-review.js +326 -0
  124. package/src/runtime/autonomous-tasks.js +408 -0
  125. package/src/runtime/build-readiness.js +28 -4
  126. package/src/runtime/code-graph-rehearse.js +113 -0
  127. package/src/runtime/codeburn-core.js +112 -0
  128. package/src/runtime/context-authority.js +40 -0
  129. package/src/runtime/context-economy-core.js +316 -0
  130. package/src/runtime/continuity-graph.js +201 -0
  131. package/src/runtime/continuity-notes.js +71 -26
  132. package/src/runtime/core-source-ingest.js +379 -0
  133. package/src/runtime/detached-provider.js +142 -0
  134. package/src/runtime/doctor-report.js +18 -0
  135. package/src/runtime/environment-awareness-core.js +460 -0
  136. package/src/runtime/exposure-catalog.js +276 -0
  137. package/src/runtime/graphify-core.js +167 -0
  138. package/src/runtime/guidance-stack.js +9 -2
  139. package/src/runtime/handoff-packets.js +92 -0
  140. package/src/runtime/headroom-core.js +104 -0
  141. package/src/runtime/helper-orchestration-core.js +307 -0
  142. package/src/runtime/hook-profiles.js +20 -0
  143. package/src/runtime/learning-loop-core.js +344 -0
  144. package/src/runtime/live-agent-verification.js +122 -0
  145. package/src/runtime/maestro-events.js +18 -0
  146. package/src/runtime/maestro-manager.js +19 -0
  147. package/src/runtime/maestro-output.js +58 -0
  148. package/src/runtime/maestro-state.js +125 -0
  149. package/src/runtime/managed-markdown.js +29 -0
  150. package/src/runtime/mcp-authority.js +147 -0
  151. package/src/runtime/mcp-policy.js +192 -0
  152. package/src/runtime/obsidian-integration-core.js +1089 -0
  153. package/src/runtime/obsidian-plugin-bridge.js +1045 -0
  154. package/src/runtime/pi-maestro-core.js +89 -0
  155. package/src/runtime/preferences.js +79 -0
  156. package/src/runtime/project-context.js +636 -0
  157. package/src/runtime/prompt-strategy-core.js +230 -0
  158. package/src/runtime/quorum-review.js +90 -0
  159. package/src/runtime/ralph-execution-core.js +217 -0
  160. package/src/runtime/redaction-gateway.js +373 -0
  161. package/src/runtime/runtime-state.js +935 -2
  162. package/src/runtime/schema-migrations.js +162 -0
  163. package/src/runtime/semantic-recording-core.js +147 -0
  164. package/src/runtime/signal-hooks.js +28 -0
  165. package/src/runtime/skills-registry-export.js +754 -0
  166. package/src/runtime/task-contracts.js +83 -0
  167. package/src/runtime/universal-plugin-broker-core.js +898 -0
  168. package/src/runtime/workspace-intelligence-runtime.js +674 -0
  169. package/src/runtime/workspace-virtualizer.js +102 -0
  170. package/src/runtime-artifacts.js +225 -62
  171. package/src/setup-lifecycle.js +359 -0
  172. package/src/skill-frontmatter.js +78 -0
  173. package/src/skill-installer.js +226 -71
  174. package/src/skills.js +1166 -96
  175. package/src/state-sync.js +0 -0
  176. package/src/test-fixtures.js +543 -0
  177. package/src/tui/grid.js +67 -0
  178. package/src/tui/status-grid.js +23 -0
  179. package/support-bundle.json +114 -0
  180. package/templates/AGENTS.md +6 -2
  181. package/templates/catalog-manifest.json +0 -0
  182. package/templates/model-instructions/core.md +0 -0
  183. package/templates/model-instructions/release.md +0 -0
  184. package/templates/model-instructions/security.md +0 -0
  185. package/templates/quality/ai-quality-rules.yml +37 -0
  186. package/docs/assets/image/Screenshot(1).png +0 -0
  187. package/docs/assets/image/Screenshot(2).png +0 -0
  188. package/docs/assets/image/Screenshot(3).png +0 -0
  189. package/docs/assets/image/Screenshot(4).png +0 -0
  190. package/docs/assets/image/Screenshot(5).png +0 -0
  191. package/docs/assets/image/Screenshot(6).png +0 -0
  192. package/docs/assets/image/Screenshot(7).png +0 -0
  193. package/docs/assets/image/Screenshot(8).png +0 -0
  194. package/docs/assets/image/Screenshot(9).png +0 -0
  195. package/docs/assets/meta-architect-logo.png +0 -0
  196. package/docs/assets/meta-architect-logo.svg +0 -8
  197. package/docs/onboarding.md +0 -65
  198. package/docs/skills-publishing.md +0 -255
@@ -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
+ }
@@ -0,0 +1,316 @@
1
+ import { ensureDir, readJson, writeFileIfMissing } from "../fs-utils.js";
2
+ import { getRuntimeSubsystemPath } from "../paths.js";
3
+
4
+ export const contextEconomySchemaVersion = "0.1.0";
5
+
6
+ const safetySignals = [
7
+ "security warning",
8
+ "irreversible",
9
+ "destructive",
10
+ "credential",
11
+ "verification failed",
12
+ "validation failed",
13
+ "user confused",
14
+ ];
15
+
16
+ const removableWords = new Set([
17
+ "a",
18
+ "an",
19
+ "the",
20
+ "just",
21
+ "really",
22
+ "basically",
23
+ "sure",
24
+ "happy",
25
+ "glad",
26
+ ]);
27
+
28
+ export function getContextEconomyCorePath() {
29
+ return getRuntimeSubsystemPath("context", "context-economy-core.json");
30
+ }
31
+
32
+ export function createDefaultContextEconomyCore() {
33
+ return {
34
+ schemaVersion: contextEconomySchemaVersion,
35
+ product: "Meta-Architect",
36
+ purpose:
37
+ "MA-owned default context compression policy for terse progress, summaries, MCP descriptors, and exported host payloads.",
38
+ style: "compact_technical",
39
+ evidence_source: {
40
+ repo: "JuliusBrussee/caveman",
41
+ endpoint: "https://gitmcp.io/JuliusBrussee/caveman",
42
+ applied_as: "MA Context Economy Core",
43
+ adoption_boundary:
44
+ "Use the compression and auto-clarity contract as core MA behavior without adopting third-party persona branding.",
45
+ },
46
+ applies_to: [
47
+ "$maestro",
48
+ "gated_lanes",
49
+ "helper_skills",
50
+ "executor_roles",
51
+ "reviewer_roles",
52
+ "verifier_roles",
53
+ "team_workers",
54
+ "mcp_descriptors",
55
+ "exported_host_payloads",
56
+ ],
57
+ preserve_exact: [
58
+ "code_blocks",
59
+ "commands",
60
+ "schemas",
61
+ "security_warnings",
62
+ "authority_fields",
63
+ "verification_failures",
64
+ "known_gaps",
65
+ "commit_messages",
66
+ ],
67
+ safety_valves: safetySignals,
68
+ compression_rules: [
69
+ "Remove filler and pleasantries from progress, summaries, and descriptors.",
70
+ "Fragments are allowed when meaning stays exact.",
71
+ "Keep technical nouns, commands, paths, code, schemas, evidence status, and blockers exact.",
72
+ "Use auto-clarity bypass for safety warnings, irreversible actions, failed checks, credentials, and confused users.",
73
+ ],
74
+ hard_rules: [
75
+ "Context Economy is default MA behavior, not a third-party persona.",
76
+ "Technical terms, code, schemas, commands, warnings, and evidence boundaries stay exact.",
77
+ "Disable compression when safety, authority, or user clarity would degrade.",
78
+ ],
79
+ };
80
+ }
81
+
82
+ function compactPlainText(text) {
83
+ return text
84
+ .split(/\s+/)
85
+ .filter((word) => !removableWords.has(word.toLowerCase().replace(/[^a-z]/g, "")))
86
+ .join(" ")
87
+ .replace(/\s+([,.;:!?])/g, "$1")
88
+ .trim();
89
+ }
90
+
91
+ export function shouldBypassContextEconomy({ text = "", tags = [] } = {}) {
92
+ const haystack = `${text} ${tags.join(" ")}`.toLowerCase();
93
+ return safetySignals.some((signal) => haystack.includes(signal));
94
+ }
95
+
96
+ export function createContextEconomyView({ text, tags = [], level = "core" }) {
97
+ if (typeof text !== "string") {
98
+ throw new Error("Context Economy view requires text");
99
+ }
100
+
101
+ const bypassed = shouldBypassContextEconomy({ text, tags });
102
+ const parts = text.split(/(```[\s\S]*?```)/g);
103
+ const output = bypassed
104
+ ? text
105
+ : parts
106
+ .map((part) => (part.startsWith("```") ? part : compactPlainText(part)))
107
+ .join("")
108
+ .trim();
109
+
110
+ return {
111
+ record_type: "context_economy_view",
112
+ level,
113
+ bypassed,
114
+ records_as: "context_budget",
115
+ original_chars: text.length,
116
+ output_chars: output.length,
117
+ output,
118
+ preserved_exact: ["code_blocks", "commands", "warnings", "schemas", "authority_fields"],
119
+ };
120
+ }
121
+
122
+ export function createContextEconomyPayload({
123
+ surface = "$maestro",
124
+ payload = {},
125
+ tags = [],
126
+ level = "core",
127
+ } = {}) {
128
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
129
+ throw new Error("Context Economy payload requires an object payload");
130
+ }
131
+
132
+ const transformed = {};
133
+ const fields = [];
134
+ for (const [key, value] of Object.entries(payload)) {
135
+ const result = transformContextEconomyValue({ key, value, tags, level });
136
+ transformed[key] = result.value;
137
+ fields.push({
138
+ key,
139
+ compressed: result.compressed,
140
+ bypassed: result.bypassed,
141
+ original_chars: result.originalChars,
142
+ output_chars: result.outputChars,
143
+ });
144
+ }
145
+
146
+ return {
147
+ record_type: "context_economy_payload",
148
+ surface,
149
+ level,
150
+ records_as: "context_budget",
151
+ build_evidence: false,
152
+ payload: transformed,
153
+ fields,
154
+ preserved_exact: [
155
+ "code",
156
+ "commands",
157
+ "paths",
158
+ "schemas",
159
+ "authority_fields",
160
+ "security_warnings",
161
+ "verification_evidence",
162
+ ],
163
+ };
164
+ }
165
+
166
+ export function createMcpDescriptorEconomy({ name, description, inputSchema = {}, tags = [] }) {
167
+ if (!name || typeof name !== "string") {
168
+ throw new Error("MCP descriptor economy requires a tool name");
169
+ }
170
+ const compacted = createContextEconomyView({
171
+ text: description ?? "",
172
+ tags,
173
+ level: "mcp_descriptor",
174
+ });
175
+ return {
176
+ record_type: "mcp_descriptor_economy",
177
+ records_as: "context_budget",
178
+ build_evidence: false,
179
+ name,
180
+ description: compacted.output,
181
+ inputSchema,
182
+ bypassed: compacted.bypassed,
183
+ preserved_exact: ["name", "inputSchema"],
184
+ };
185
+ }
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
+
227
+ export function validateContextEconomyCore(value) {
228
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
229
+ throw new Error("Context Economy core must be an object");
230
+ }
231
+ if (value.schemaVersion !== contextEconomySchemaVersion) {
232
+ throw new Error(`Unsupported Context Economy schemaVersion: ${value.schemaVersion}`);
233
+ }
234
+ if (value.evidence_source?.repo !== "JuliusBrussee/caveman") {
235
+ throw new Error("Context Economy core requires Caveman evidence source mapping");
236
+ }
237
+ for (const required of ["code_blocks", "security_warnings", "authority_fields"]) {
238
+ if (!value.preserve_exact?.includes(required)) {
239
+ throw new Error(`Context Economy core must preserve ${required}`);
240
+ }
241
+ }
242
+ if (!value.compression_rules?.some((rule) => rule.includes("auto-clarity bypass"))) {
243
+ throw new Error("Context Economy core requires auto-clarity compression rule");
244
+ }
245
+ for (const signal of ["security warning", "irreversible", "verification failed"]) {
246
+ if (!value.safety_valves?.includes(signal)) {
247
+ throw new Error(`Context Economy core requires ${signal} safety valve`);
248
+ }
249
+ }
250
+ if (JSON.stringify(value).includes(".omx")) {
251
+ throw new Error("Context Economy core must not expose OMX runtime paths");
252
+ }
253
+ return value;
254
+ }
255
+
256
+ function transformContextEconomyValue({ key, value, tags, level }) {
257
+ if (typeof value !== "string") {
258
+ return {
259
+ value,
260
+ compressed: false,
261
+ bypassed: false,
262
+ originalChars: 0,
263
+ outputChars: 0,
264
+ };
265
+ }
266
+ if (shouldPreserveField(key)) {
267
+ return {
268
+ value,
269
+ compressed: false,
270
+ bypassed: true,
271
+ originalChars: value.length,
272
+ outputChars: value.length,
273
+ };
274
+ }
275
+ const view = createContextEconomyView({ text: value, tags, level });
276
+ return {
277
+ value: view.output,
278
+ compressed: !view.bypassed && view.output !== value,
279
+ bypassed: view.bypassed,
280
+ originalChars: view.original_chars,
281
+ outputChars: view.output_chars,
282
+ };
283
+ }
284
+
285
+ function shouldPreserveField(key) {
286
+ const normalized = key.toLowerCase();
287
+ return [
288
+ "code",
289
+ "command",
290
+ "commands",
291
+ "schema",
292
+ "inputschema",
293
+ "path",
294
+ "paths",
295
+ "authority",
296
+ "authority_boundary",
297
+ "security_warning",
298
+ "warning",
299
+ "verification_evidence",
300
+ "evidence",
301
+ "known_gap",
302
+ "known_gaps",
303
+ ].includes(normalized);
304
+ }
305
+
306
+ export async function seedContextEconomyCoreArtifacts() {
307
+ await ensureDir(getRuntimeSubsystemPath("context"));
308
+ await writeFileIfMissing(
309
+ getContextEconomyCorePath(),
310
+ `${JSON.stringify(createDefaultContextEconomyCore(), null, 2)}\n`,
311
+ );
312
+ }
313
+
314
+ export async function loadContextEconomyCore() {
315
+ return validateContextEconomyCore(await readJson(getContextEconomyCorePath()));
316
+ }
@@ -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
  }