@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
package/mcp/servers.json CHANGED
@@ -60,6 +60,30 @@
60
60
  "category": "system-design",
61
61
  "repo": "checkcheckzz/system-design-interview",
62
62
  "endpoint": "https://gitmcp.io/checkcheckzz/system-design-interview"
63
+ },
64
+ {
65
+ "kind": "gitmcp-evidence",
66
+ "category": "obsidian-api-docs",
67
+ "repo": "obsidianmd/obsidian-api",
68
+ "endpoint": "https://gitmcp.io/obsidianmd/obsidian-api"
69
+ },
70
+ {
71
+ "kind": "gitmcp-evidence",
72
+ "category": "obsidian-plugin-scaffold",
73
+ "repo": "obsidianmd/obsidian-sample-plugin",
74
+ "endpoint": "https://gitmcp.io/obsidianmd/obsidian-sample-plugin"
75
+ },
76
+ {
77
+ "kind": "gitmcp-evidence",
78
+ "category": "context-economy",
79
+ "repo": "JuliusBrussee/caveman",
80
+ "endpoint": "https://gitmcp.io/JuliusBrussee/caveman"
81
+ },
82
+ {
83
+ "kind": "gitmcp-evidence",
84
+ "category": "prompt-techniques",
85
+ "repo": "NirDiamant/Prompt_Engineering",
86
+ "endpoint": "https://gitmcp.io/NirDiamant/Prompt_Engineering"
63
87
  }
64
88
  ]
65
89
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jstn-sdk/ma",
3
- "version": "0.1.12",
3
+ "version": "0.14.0",
4
4
  "description": "Codex-native skills system for architecture, evidence, review, and gated build guidance.",
5
5
  "license": "MIT",
6
6
  "author": "JustineDevs",
@@ -40,10 +40,25 @@
40
40
  "templates/",
41
41
  "sprint/",
42
42
  "skills/",
43
- "docs/",
43
+ "docs/README.md",
44
+ "docs/getting-started.md",
45
+ "docs/skills.md",
46
+ "docs/mcp-setup.md",
47
+ "docs/installed-sdk.md",
48
+ "docs/release-spec.md",
49
+ "docs/quality.md",
50
+ "docs/codex-integration.md",
51
+ "docs/autonomous-tasks.md",
52
+ "docs/reference/",
53
+ "docs/qa/",
54
+ "data/",
44
55
  "scripts/",
56
+ "schemas/",
57
+ "support-bundle.json",
45
58
  "index.js",
46
59
  "README.md",
60
+ "DEMO.md",
61
+ "COVERAGE.md",
47
62
  "LICENSE"
48
63
  ],
49
64
  "exports": {
@@ -55,8 +70,12 @@
55
70
  },
56
71
  "scripts": {
57
72
  "prepare": "husky",
73
+ "prepack": "node ./scripts/prepack.js",
58
74
  "postinstall": "node ./scripts/postinstall.js",
59
- "test": "node --test",
75
+ "test": "node --test --test-concurrency=1 test/*.test.js",
76
+ "test:disk": "node --test --test-concurrency=1 test/test-fixtures.test.js",
77
+ "fixtures:cleanup": "bash ./scripts/cleanup-test-fixtures.sh",
78
+ "demo:smoke": "node ./scripts/demo-smoke.js",
60
79
  "status": "node ./bin/ma.js status",
61
80
  "linux:packages:build": "node ./scripts/build-linux-packages.mjs",
62
81
  "linux:packages:smoke": "node ./scripts/linux-package-smoke.mjs",
@@ -66,6 +85,7 @@
66
85
  "release:advance": "node ./scripts/release-sync.js --force --bump patch",
67
86
  "release:verify": "node ./scripts/release-verify.js",
68
87
  "release:assets": "node ./.github/scripts/validate-release-assets.js",
88
+ "release:assets:generate": "node ./.github/scripts/validate-release-assets.js --generate",
69
89
  "release:check": "npm run release:verify && npm run skills:manifest && npm run plugin:sync && npm run skills:validate && npm run plugin:verify && npm run skills:pack && npm run check && npm test && npm run pack:inspect",
70
90
  "skills:manifest": "node ./scripts/skills-manifest.js",
71
91
  "skills:validate": "node ./scripts/skills-validate.js",
@@ -74,7 +94,9 @@
74
94
  "plugin:sync": "node ./scripts/plugin-sync.js",
75
95
  "plugin:verify": "node ./scripts/plugin-sync.js --check",
76
96
  "pack:inspect": "npm pack --dry-run --ignore-scripts --cache ./.npm-cache",
77
- "doctor": "node ./scripts/doctor.js",
97
+ "package:size": "node ./scripts/package-size-check.mjs",
98
+ "package:doctor": "node ./scripts/doctor.js",
99
+ "release:doctor": "npm run package:doctor",
78
100
  "lint": "biome lint .",
79
101
  "format": "biome format .",
80
102
  "check": "biome check ."
@@ -86,5 +108,8 @@
86
108
  "devDependencies": {
87
109
  "@biomejs/biome": "^2.4.13",
88
110
  "husky": "^9.1.7"
111
+ },
112
+ "dependencies": {
113
+ "@jstn-sdk/agents": "0.1.0"
89
114
  }
90
115
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "meta-architect",
3
3
  "displayName": "Meta-Architect",
4
- "version": "0.1.12",
4
+ "version": "0.14.0",
5
5
  "description": "Programmatic architecture and verified engineering skills for Codex-native workflows.",
6
6
  "entry": "./README.md",
7
7
  "skillsDir": "./skills"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "meta-architect",
3
- "version": "0.1.12",
3
+ "version": "0.14.0",
4
4
  "description": "Skills-first workflow layer for Codex with a bounded autonomous manager, architecture, evidence, review, and gated build guidance.",
5
5
  "author": {
6
6
  "name": "JustineDevs",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "meta-architect",
3
- "version": "0.1.12",
3
+ "version": "0.14.0",
4
4
  "collections": [
5
5
  "sindresorhus/awesome",
6
6
  "dzharii/awesome-typescript",
@@ -48,6 +48,13 @@ Use the core repo when you want to develop Meta-Architect itself. Use the plugin
48
48
 
49
49
  The plugin-facing bundle should be consumed alongside the repository’s documented packaging/install flow.
50
50
 
51
+ Recommended CLI install for macOS, Linux, WSL, and Git-Bash:
52
+
53
+ ```bash
54
+ # One-line install (POSIX shells only; use WSL/Git-Bash on Windows)
55
+ curl -fsSLo install.sh https://cdn.jsdelivr.net/gh/JustineDevs/meta-architect@latest/scripts/install.sh && curl -fsSLo install.sh.sha256 https://cdn.jsdelivr.net/gh/JustineDevs/meta-architect@latest/scripts/install.sh.sha256 && sed 's#scripts/install.sh#install.sh#' install.sh.sha256 | sha256sum -c - && sh install.sh
56
+ ```
57
+
51
58
  Canonical package/runtime path:
52
59
 
53
60
  Debian-family install:
@@ -131,7 +138,7 @@ When consuming the plugin:
131
138
  The plugin version should track the release scope of the core repo.
132
139
 
133
140
  For this repository:
134
- - plugin scope is aligned to Meta-Architect `v0.1.12`
141
+ - plugin scope is aligned to Meta-Architect `v0.14.0`
135
142
  - any breaking contract change should be versioned intentionally
136
143
 
137
144
  The plugin is one distribution surface of the same product, not a separate product line.
@@ -0,0 +1,534 @@
1
+ const { MarkdownView, Modal, Notice, Plugin, PluginSettingTab, Setting } = require("obsidian");
2
+ const { createHash, randomUUID } = require("node:crypto");
3
+
4
+ const DEFAULT_SETTINGS = {
5
+ projectName: "Meta-Architect",
6
+ activeContextPath: "Meta-Architect/Plugin Context/Active Note Context.md",
7
+ queuePath: "Meta-Architect/Plugin Requests/request-queue.json",
8
+ attachmentDir: "Meta-Architect/Attachments",
9
+ sourceWorkspace: "",
10
+ protocolToken: "",
11
+ };
12
+
13
+ const FORBIDDEN_TARGETS = [".ma/release.json", ".ma/decisions.json", ".ma/plans/", ".ma/specs/"];
14
+ const MAX_ATTACHMENT_BYTES = 1_048_576;
15
+ const ALLOWED_ATTACHMENT_TYPES = new Set([
16
+ "application/octet-stream",
17
+ "application/json",
18
+ "text/plain",
19
+ "text/markdown",
20
+ "image/png",
21
+ "image/jpeg",
22
+ "image/webp",
23
+ ]);
24
+
25
+ module.exports = class MetaArchitectPlugin extends Plugin {
26
+ async onload() {
27
+ this.settings = Object.assign({}, DEFAULT_SETTINGS, (await this.loadData()) || {});
28
+ this.queueBusy = Promise.resolve();
29
+ if (!this.settings.protocolToken) {
30
+ this.settings.protocolToken = randomUUID();
31
+ await this.saveSettings();
32
+ }
33
+ this.addRibbonIcon("network", "Send current note/selection to MA", async () => {
34
+ await this.captureActiveNote();
35
+ });
36
+
37
+ const statusBarItem = this.addStatusBarItem();
38
+ statusBarItem.addClass("ma-status");
39
+ statusBarItem.setText("MA: vault context");
40
+
41
+ this.addCommand({
42
+ id: "ma-capture-active-note",
43
+ name: "Send current note/selection to MA",
44
+ callback: async () => this.captureActiveNote(),
45
+ });
46
+
47
+ this.addCommand({
48
+ id: "ma-drain-request-queue",
49
+ name: "Drain MA request queue",
50
+ callback: async () => {
51
+ const result = await this.drainRequestQueue();
52
+ new Notice(
53
+ `MA queue drained: ${result.processed.length} processed, ${result.refused.length} refused`,
54
+ );
55
+ },
56
+ });
57
+
58
+ this.addCommand({
59
+ id: "ma-open-context-preview",
60
+ name: "Preview current MA context",
61
+ checkCallback: (checking) => {
62
+ const view = this.app.workspace.getActiveViewOfType(MarkdownView);
63
+ if (!view) return false;
64
+ if (!checking) new ContextPreviewModal(this.app, this.createSelectionContext()).open();
65
+ return true;
66
+ },
67
+ });
68
+
69
+ this.addCommand({
70
+ id: "ma-open-map",
71
+ name: "Open MA map of content",
72
+ callback: async () => this.openNote("Meta-Architect/Map of Content.md"),
73
+ });
74
+
75
+ this.addSettingTab(new MetaArchitectSettingTab(this.app, this));
76
+ this.registerProtocolHandler();
77
+ this.registerVaultWatchers();
78
+ this.registerInterval(
79
+ window.setInterval(() => this.drainRequestQueue().catch(() => {}), 30 * 1000),
80
+ );
81
+ }
82
+
83
+ async loadSettings() {
84
+ this.settings = Object.assign({}, DEFAULT_SETTINGS, (await this.loadData()) || {});
85
+ }
86
+
87
+ async saveSettings() {
88
+ await this.saveData(this.settings);
89
+ }
90
+
91
+ createSelectionContext() {
92
+ const file = this.app.workspace.getActiveFile();
93
+ const view = this.app.workspace.getActiveViewOfType(MarkdownView);
94
+ const leaf = this.app.workspace.activeLeaf;
95
+ const editor = view?.editor || leaf?.view?.editor;
96
+ const selectedText = editor?.getSelection?.() || "";
97
+ return {
98
+ record_type: "obsidian_active_note_context",
99
+ records_as: "vault_context",
100
+ build_evidence: false,
101
+ captured_at: new Date().toISOString(),
102
+ active_note_path: file?.path || null,
103
+ selected_text: selectedText,
104
+ selected_text_char_count: selectedText.length,
105
+ cursor: editor?.getCursor?.() || null,
106
+ pane_state: leaf?.getViewState?.() || null,
107
+ metadata: file ? this.app.metadataCache.getFileCache(file) || {} : {},
108
+ };
109
+ }
110
+
111
+ async captureActiveNote() {
112
+ const context = this.createSelectionContext();
113
+ const file = this.app.workspace.getActiveFile();
114
+ if (file) {
115
+ await this.app.fileManager.processFrontMatter(file, (frontmatter) => {
116
+ frontmatter.ma_records_as = "vault_context";
117
+ frontmatter.ma_project = this.settings.projectName;
118
+ frontmatter.ma_capabilities = [
119
+ "obsidian_integration_core",
120
+ "active_note_selection_context",
121
+ "metadata_cache_deep_graph",
122
+ ];
123
+ if (this.settings.sourceWorkspace) {
124
+ frontmatter.ma_source_workspace = this.settings.sourceWorkspace;
125
+ }
126
+ });
127
+ }
128
+ await this.writeVaultContextNote(
129
+ this.settings.activeContextPath,
130
+ this.renderContextNote(context),
131
+ true,
132
+ );
133
+ await this.writeBinaryAttachment(
134
+ `${this.settings.attachmentDir}/active-context-${Date.now()}.json`,
135
+ JSON.stringify(context, null, 2),
136
+ "application/json",
137
+ );
138
+ new Notice("MA captured current Obsidian context");
139
+ return context;
140
+ }
141
+
142
+ renderContextNote(context) {
143
+ const activeLink = context.active_note_path
144
+ ? this.app.fileManager.generateMarkdownLink(
145
+ this.app.vault.getAbstractFileByPath(context.active_note_path),
146
+ this.settings.activeContextPath,
147
+ undefined,
148
+ "active note",
149
+ )
150
+ : "none";
151
+ return withVaultContextFrontmatter(`# Active Note Context
152
+
153
+ - Active note: ${activeLink}
154
+ - Selection chars: ${context.selected_text_char_count}
155
+ - Cursor: ${JSON.stringify(context.cursor)}
156
+ - Captured: ${context.captured_at}
157
+
158
+ ## Selected Text
159
+
160
+ ${context.selected_text || "_No active selection captured._"}
161
+
162
+ ## Obsidian MetadataCache
163
+
164
+ \`\`\`json
165
+ ${JSON.stringify(context.metadata, null, 2)}
166
+ \`\`\`
167
+
168
+ ## Pane State
169
+
170
+ \`\`\`json
171
+ ${JSON.stringify(context.pane_state || {}, null, 2)}
172
+ \`\`\`
173
+ `);
174
+ }
175
+
176
+ registerProtocolHandler() {
177
+ this.registerObsidianProtocolHandler("ma", async (params) => {
178
+ if (params.action === "queue") {
179
+ await this.queueRequest(params);
180
+ new Notice("MA request queued");
181
+ return;
182
+ }
183
+ if (params.action === "capture" || params.open === "active") {
184
+ if (!hasProtocolAuthority(params, this.settings)) {
185
+ throw new Error("obsidian_protocol_authority_required");
186
+ }
187
+ await this.captureActiveNote();
188
+ return;
189
+ }
190
+ if (!hasProtocolAuthority(params, this.settings)) {
191
+ throw new Error("obsidian_protocol_authority_required");
192
+ }
193
+ await this.openNote(params.path || "Meta-Architect/Map of Content.md");
194
+ });
195
+ }
196
+
197
+ registerVaultWatchers() {
198
+ for (const eventName of ["create", "modify", "delete", "rename"]) {
199
+ this.registerEvent(
200
+ this.app.vault.on(eventName, () => {
201
+ this.drainRequestQueue().catch(() => {});
202
+ }),
203
+ );
204
+ }
205
+ for (const eventName of ["resolved", "changed", "deleted"]) {
206
+ this.registerEvent(
207
+ this.app.metadataCache.on(eventName, () => {
208
+ this.drainRequestQueue().catch(() => {});
209
+ }),
210
+ );
211
+ }
212
+ for (const eventName of ["active-leaf-change", "layout-change"]) {
213
+ this.registerEvent(
214
+ this.app.workspace.on(eventName, () => {
215
+ this.drainRequestQueue().catch(() => {});
216
+ }),
217
+ );
218
+ }
219
+ }
220
+
221
+ async queueRequest(params) {
222
+ return this.withQueueLock(() => this.queueRequestUnlocked(params));
223
+ }
224
+
225
+ async queueRequestUnlocked(params) {
226
+ if (!hasProtocolAuthority(params, this.settings)) {
227
+ throw new Error("obsidian_protocol_queue_authority_required");
228
+ }
229
+ await this.ensureFolder(this.pathParent(this.settings.queuePath));
230
+ const file = this.app.vault.getAbstractFileByPath(this.settings.queuePath);
231
+ const queue = file ? JSON.parse(await this.app.vault.read(file)) : [];
232
+ queue.push({
233
+ ...Object.fromEntries(Object.entries(params).filter(([key]) => key !== "protocol_token")),
234
+ id: params.id || `ma-${Date.now()}`,
235
+ queued_at: new Date().toISOString(),
236
+ records_as: "vault_context",
237
+ protocol_token_hash: params.protocol_token_hash || tokenHash(params.protocol_token),
238
+ });
239
+ const payload = `${JSON.stringify(queue, null, 2)}\n`;
240
+ if (file) await this.app.vault.modify(file, payload);
241
+ else await this.app.vault.create(this.settings.queuePath, payload);
242
+ }
243
+
244
+ async drainRequestQueue() {
245
+ return this.withQueueLock(() => this.drainRequestQueueUnlocked());
246
+ }
247
+
248
+ async drainRequestQueueUnlocked() {
249
+ const file = this.app.vault.getAbstractFileByPath(this.settings.queuePath);
250
+ const queue = file ? JSON.parse(await this.app.vault.read(file)) : [];
251
+ const processed = [];
252
+ const refused = [];
253
+ const refusedQueueItems = [];
254
+ for (const request of queue) {
255
+ try {
256
+ const mutating = [
257
+ "capture_active_note",
258
+ "create_note",
259
+ "rename_note",
260
+ "write_attachment",
261
+ ].includes(request.action);
262
+ if (
263
+ (mutating && !hasProtocolAuthority(request, this.settings)) ||
264
+ (!mutating && !hasVaultContextAuthority(request, this.settings))
265
+ ) {
266
+ throw new Error("obsidian_queue_authority_required");
267
+ }
268
+ const target = request.note_path || request.path || "";
269
+ if (target) assertSafeVaultPath(target);
270
+ if (request.action === "capture_active_note") {
271
+ processed.push(await this.captureActiveNote());
272
+ } else if (request.action === "create_note") {
273
+ processed.push(await this.writeVaultContextNote(target, request.content, true));
274
+ } else if (request.action === "rename_note") {
275
+ assertSafeVaultPath(request.from);
276
+ assertSafeVaultPath(request.to);
277
+ const fileToRename = this.app.vault.getAbstractFileByPath(request.from);
278
+ await this.app.fileManager.renameFile(fileToRename, request.to);
279
+ processed.push({ action: "rename_note", from: request.from, to: request.to });
280
+ } else if (request.action === "write_attachment") {
281
+ processed.push(
282
+ await this.writeBinaryAttachment(target, request.data || "", request.content_type),
283
+ );
284
+ } else {
285
+ throw new Error(`unsupported_action:${request.action}`);
286
+ }
287
+ } catch (error) {
288
+ refusedQueueItems.push(request);
289
+ refused.push({
290
+ action: request?.action || null,
291
+ reason: error.message,
292
+ records_as: "vault_context",
293
+ build_evidence: false,
294
+ });
295
+ }
296
+ }
297
+ if (file && queue.length > 0) {
298
+ const nextQueue = `${JSON.stringify(refusedQueueItems, null, 2)}\n`;
299
+ const currentQueue = await this.app.vault.read(file);
300
+ if (currentQueue !== nextQueue) await this.app.vault.modify(file, nextQueue);
301
+ }
302
+ return {
303
+ record_type: "obsidian_plugin_queue_drain",
304
+ records_as: "vault_context",
305
+ build_evidence: false,
306
+ processed,
307
+ refused,
308
+ };
309
+ }
310
+
311
+ withQueueLock(task) {
312
+ const previous = this.queueBusy;
313
+ let release;
314
+ this.queueBusy = new Promise((resolve) => {
315
+ release = resolve;
316
+ });
317
+ return previous.then(task).finally(release);
318
+ }
319
+
320
+ async writeVaultContextNote(notePath, content, overwrite) {
321
+ assertSafeVaultPath(notePath);
322
+ assertMaOwnedPath(notePath, "Meta-Architect/");
323
+ if (!content?.trim()) throw new Error("vault_context note content required");
324
+ await this.ensureFolder(this.pathParent(notePath));
325
+ const file = this.app.vault.getAbstractFileByPath(notePath);
326
+ const payload = withVaultContextFrontmatter(content);
327
+ if (file && overwrite) await this.app.vault.modify(file, payload);
328
+ else if (file) throw new Error(`note_exists:${notePath}`);
329
+ else await this.app.vault.create(notePath, payload);
330
+ return {
331
+ action: "write_vault_context_note",
332
+ note_path: notePath,
333
+ records_as: "vault_context",
334
+ build_evidence: false,
335
+ };
336
+ }
337
+
338
+ async writeBinaryAttachment(attachmentPath, data, contentType) {
339
+ assertSafeVaultPath(attachmentPath);
340
+ assertMaOwnedPath(attachmentPath, "Meta-Architect/Attachments/");
341
+ if (!ALLOWED_ATTACHMENT_TYPES.has(contentType)) {
342
+ throw new Error(`attachment_type_forbidden:${contentType}`);
343
+ }
344
+ await this.ensureFolder(this.pathParent(attachmentPath));
345
+ const bytes = new TextEncoder().encode(String(data || ""));
346
+ if (bytes.byteLength > MAX_ATTACHMENT_BYTES) {
347
+ throw new Error(`attachment_too_large:${attachmentPath}:${bytes.byteLength}`);
348
+ }
349
+ if (this.app.vault.getAbstractFileByPath(attachmentPath)) {
350
+ throw new Error(`attachment_exists:${attachmentPath}`);
351
+ }
352
+ if (this.app.vault.createBinary) {
353
+ await this.app.vault.createBinary(attachmentPath, bytes.buffer);
354
+ } else {
355
+ await this.app.vault.adapter.writeBinary(attachmentPath, bytes.buffer);
356
+ }
357
+ return {
358
+ action: "write_attachment",
359
+ attachment_path: attachmentPath,
360
+ content_type: contentType || "application/octet-stream",
361
+ records_as: "vault_context",
362
+ build_evidence: false,
363
+ };
364
+ }
365
+
366
+ async openNote(notePath) {
367
+ const file = this.app.vault.getAbstractFileByPath(notePath);
368
+ if (!file) {
369
+ new Notice(`MA note not found: ${notePath}`);
370
+ return;
371
+ }
372
+ await this.app.workspace.getLeaf(true).openFile(file);
373
+ }
374
+
375
+ async ensureFolder(folderPath) {
376
+ if (!folderPath || folderPath === ".") return;
377
+ const segments = folderPath.split("/").filter(Boolean);
378
+ let current = "";
379
+ for (const segment of segments) {
380
+ current = current ? `${current}/${segment}` : segment;
381
+ if (!this.app.vault.getAbstractFileByPath(current)) {
382
+ await this.app.vault.createFolder(current).catch(() => {});
383
+ }
384
+ }
385
+ }
386
+
387
+ pathParent(filePath) {
388
+ const parts = filePath.split("/");
389
+ parts.pop();
390
+ return parts.join("/");
391
+ }
392
+ };
393
+
394
+ class ContextPreviewModal extends Modal {
395
+ constructor(app, context) {
396
+ super(app);
397
+ this.context = context;
398
+ }
399
+
400
+ onOpen() {
401
+ this.contentEl.addClass("ma-context-modal");
402
+ this.contentEl.createEl("h2", { text: "Meta-Architect Context Preview" });
403
+ this.contentEl.createEl("pre", { text: JSON.stringify(this.context, null, 2) });
404
+ }
405
+
406
+ onClose() {
407
+ this.contentEl.empty();
408
+ }
409
+ }
410
+
411
+ class MetaArchitectSettingTab extends PluginSettingTab {
412
+ constructor(app, plugin) {
413
+ super(app, plugin);
414
+ this.plugin = plugin;
415
+ }
416
+
417
+ display() {
418
+ const { containerEl } = this;
419
+ containerEl.empty();
420
+ containerEl.createEl("h2", { text: "Meta-Architect" });
421
+
422
+ new Setting(containerEl)
423
+ .setName("Project name")
424
+ .setDesc("Stored in MA frontmatter as ma_project.")
425
+ .addText((text) =>
426
+ text.setValue(this.plugin.settings.projectName).onChange(async (value) => {
427
+ this.plugin.settings.projectName = value || "Meta-Architect";
428
+ await this.plugin.saveSettings();
429
+ }),
430
+ );
431
+
432
+ new Setting(containerEl)
433
+ .setName("Active context note")
434
+ .setDesc("Where captured note/selection context is written.")
435
+ .addText((text) =>
436
+ text.setValue(this.plugin.settings.activeContextPath).onChange(async (value) => {
437
+ this.plugin.settings.activeContextPath = value || DEFAULT_SETTINGS.activeContextPath;
438
+ await this.plugin.saveSettings();
439
+ }),
440
+ );
441
+ }
442
+ }
443
+
444
+ function withVaultContextFrontmatter(content) {
445
+ if (content.trimStart().startsWith("---")) {
446
+ const start = content.indexOf("---");
447
+ const end = content.indexOf("\n---", start + 3);
448
+ if (end > start) {
449
+ let frontmatter = content.slice(start + 4, end);
450
+ for (const [key, value] of [
451
+ ["ma_records_as", "vault_context"],
452
+ ["ma_project", "Meta-Architect"],
453
+ ]) {
454
+ const pattern = new RegExp(`^${key}:.*$`, "m");
455
+ frontmatter = pattern.test(frontmatter)
456
+ ? frontmatter.replace(pattern, `${key}: ${value}`)
457
+ : `${key}: ${value}\n${frontmatter}`;
458
+ }
459
+ return `---\n${frontmatter}\n---${content.slice(end + 4)}`;
460
+ }
461
+ }
462
+ return `---
463
+ ma_records_as: vault_context
464
+ ma_project: Meta-Architect
465
+ ma_capabilities:
466
+ - obsidian_integration_core
467
+ ---
468
+
469
+ ${content}`;
470
+ }
471
+
472
+ function assertSafeVaultPath(targetPath) {
473
+ const raw = String(targetPath || "")
474
+ .replaceAll("\\", "/")
475
+ .replace(/^\/+/, "");
476
+ if (raw === ".." || raw.startsWith("../") || raw.includes("/../")) {
477
+ throw new Error("path_traversal_forbidden");
478
+ }
479
+ const normalized = pathNormalize(raw);
480
+ if (
481
+ !normalized ||
482
+ normalized === ".." ||
483
+ normalized.startsWith("../") ||
484
+ normalized.includes("/../")
485
+ ) {
486
+ throw new Error("path_traversal_forbidden");
487
+ }
488
+ if ([".obsidian/", ".trash/", ".git/"].some((prefix) => normalized.startsWith(prefix))) {
489
+ throw new Error("vault_control_path_forbidden");
490
+ }
491
+ for (const forbidden of FORBIDDEN_TARGETS) {
492
+ if (normalized === forbidden || normalized.startsWith(forbidden)) {
493
+ throw new Error(`forbidden_authoritative_mutation:${forbidden}`);
494
+ }
495
+ }
496
+ }
497
+
498
+ function assertMaOwnedPath(targetPath, prefix) {
499
+ const normalized = pathNormalize(String(targetPath || "").replaceAll("\\", "/"));
500
+ if (!normalized.startsWith(prefix)) throw new Error(`ma_owned_path_required:${prefix}`);
501
+ }
502
+
503
+ function pathNormalize(value) {
504
+ const parts = [];
505
+ for (const part of value.split("/")) {
506
+ if (!part || part === ".") continue;
507
+ if (part === "..") {
508
+ parts.pop();
509
+ } else {
510
+ parts.push(part);
511
+ }
512
+ }
513
+ return parts.join("/");
514
+ }
515
+
516
+ function hasVaultContextAuthority(params, settings) {
517
+ const requiredAuthority = settings.protocolAuthority || "vault_context";
518
+ return params?.authority === requiredAuthority;
519
+ }
520
+
521
+ function tokenHash(value) {
522
+ return createHash("sha256")
523
+ .update(String(value || ""))
524
+ .digest("hex");
525
+ }
526
+
527
+ function hasProtocolAuthority(params, settings) {
528
+ return (
529
+ Boolean(settings.protocolToken) &&
530
+ (params?.protocol_token === settings.protocolToken ||
531
+ params?.protocol_token_hash === tokenHash(settings.protocolToken)) &&
532
+ hasVaultContextAuthority(params, settings)
533
+ );
534
+ }