@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.
- package/.codex/agents/Architect.toml +1 -2
- package/.codex/agents/Auditor.toml +1 -2
- package/.codex/agents/Builder.toml +1 -2
- package/.codex/agents/Flow.toml +1 -2
- package/.codex/agents/Sage.toml +1 -2
- package/.codex/agents/Vibe.toml +1 -2
- package/.codex/hooks.json +24 -13
- package/.codex/prompts/enforcement.md +4 -0
- package/.codex/prompts/onboarding.md +6 -1
- package/.codex/prompts/release-rules.md +0 -0
- package/.codex/prompts/skill-contract.md +4 -0
- package/COVERAGE.md +211 -0
- package/DEMO.md +274 -0
- package/LICENSE +0 -0
- package/README.md +132 -656
- package/bin/ma.js +748 -55
- package/data/clone-data.ledger.json +41 -0
- package/data/clone-data.proof.json +50 -0
- package/data/clone-data.rvf +37 -0
- package/docs/README.md +10 -1
- package/docs/autonomous-tasks.md +46 -0
- package/docs/codex-integration.md +16 -0
- package/docs/getting-started.md +62 -22
- package/docs/mcp-setup.md +64 -0
- package/docs/qa/release-issue-gates-0.14.0.json +644 -0
- package/docs/qa/release-readiness-0.1.5.md +1 -1
- package/docs/qa/{release-readiness-0.1.12.md → release-readiness-0.14.0.md} +25 -9
- package/docs/quality.md +24 -0
- package/docs/reference/native-security-playbooks.md +6 -0
- package/docs/reference/native-source-selection.md +6 -0
- package/docs/release-spec.md +39 -10
- package/docs/skills.md +14 -1
- package/index.js +295 -2
- package/mcp/collections.json +23 -6
- package/mcp/fallback.json +0 -0
- package/mcp/local/code-intel.js +179 -41
- package/mcp/local/context.js +180 -0
- package/mcp/local/memory.js +17 -6
- package/mcp/local/quality.js +39 -0
- package/mcp/local/state.js +7 -3
- package/mcp/local/team-run.js +56 -4
- package/mcp/local-capabilities.json +9 -0
- package/mcp/servers.json +24 -0
- package/package.json +29 -4
- package/plugins/meta-architect/.app.json +1 -1
- package/plugins/meta-architect/.codex-plugin/plugin.json +1 -1
- package/plugins/meta-architect/.mcp.json +1 -1
- package/plugins/meta-architect/README.md +8 -1
- package/plugins/meta-architect/obsidian/main.js +534 -0
- package/plugins/meta-architect/obsidian/manifest.json +9 -0
- package/plugins/meta-architect/obsidian/styles.css +7 -0
- package/plugins/meta-architect/skills/arch/SKILL.md +2 -0
- package/plugins/meta-architect/skills/build/SKILL.md +3 -0
- package/plugins/meta-architect/skills/flow/SKILL.md +2 -0
- package/plugins/meta-architect/skills/sage/SKILL.md +2 -0
- package/plugins/meta-architect/skills/sage/references/source-selection.md +8 -0
- package/plugins/meta-architect/skills/vet/SKILL.md +2 -0
- package/plugins/meta-architect/skills/vet/references/security-playbooks.md +7 -0
- package/plugins/meta-architect/skills/vibe/SKILL.md +2 -0
- package/schemas/autonomous-task-queue.schema.json +56 -0
- package/schemas/handoff-packet.schema.json +36 -0
- package/schemas/setup-receipt.schema.json +28 -0
- package/schemas/skill-frontmatter.schema.json +20 -0
- package/schemas/support-bundle.schema.json +37 -0
- package/schemas/task-contract.schema.json +31 -0
- package/scripts/active-autonomy-hook.mjs +226 -0
- package/scripts/build-linux-packages.mjs +7 -3
- package/scripts/cleanup-test-fixtures.sh +55 -0
- package/scripts/context-hydration-hook.mjs +89 -0
- package/scripts/demo-smoke.js +285 -0
- package/scripts/doctor.js +27 -12
- package/scripts/install.sh +57 -0
- package/scripts/install.sh.sha256 +1 -0
- package/scripts/linux-package-smoke.mjs +2 -2
- package/scripts/package-size-check.mjs +26 -0
- package/scripts/plugin-sync.js +83 -17
- package/scripts/postinstall.js +17 -10
- package/scripts/prepack.js +9 -0
- package/scripts/ralph/prompt.md +35 -0
- package/scripts/release-sync.js +72 -7
- package/scripts/release-verify.js +260 -2
- package/scripts/setup-npmrc.js +48 -23
- package/scripts/skills-install.js +9 -2
- package/scripts/skills-manifest.js +2 -21
- package/scripts/skills-pack.js +2 -2
- package/scripts/skills-validate.js +5 -55
- package/skills/arch/SKILL.md +2 -0
- package/skills/build/SKILL.md +3 -0
- package/skills/flow/SKILL.md +2 -0
- package/skills/index.json +0 -0
- package/skills/sage/SKILL.md +2 -0
- package/skills/sage/references/source-selection.md +8 -0
- package/skills/vet/SKILL.md +2 -0
- package/skills/vet/references/security-playbooks.md +7 -0
- package/skills/vibe/SKILL.md +2 -0
- package/sprint/00-idea.md +0 -0
- package/sprint/01-architecture.md +0 -0
- package/sprint/02-oss-evidence.md +0 -0
- package/sprint/03-logic.md +0 -0
- package/sprint/04-security.md +0 -0
- package/sprint/05-dx-ux.md +0 -0
- package/sprint/06-build-plan.md +0 -0
- package/sprint/07-release.md +0 -0
- package/src/agents.js +279 -0
- package/src/bootstrap.js +268 -62
- package/src/build-gate.js +2 -2
- package/src/codex-app-server.js +291 -0
- package/src/decision-log.js +0 -0
- package/src/fs-utils.js +175 -4
- package/src/launcher.js +28 -21
- package/src/mcp-config.js +9 -1
- package/src/mcp-live-client.js +422 -3
- package/src/paths.js +10 -1
- package/src/policy.js +3 -3
- package/src/prelaunch.js +189 -0
- package/src/quality/ai-quality-orchestrator.js +328 -0
- package/src/release-issue-gates.js +252 -0
- package/src/release-operations.js +62 -0
- package/src/release-state.js +0 -0
- package/src/runtime/active-autonomy-core.js +208 -0
- package/src/runtime/agent-compat.js +31 -0
- package/src/runtime/alignment-sentinel.js +165 -0
- package/src/runtime/architect-review.js +326 -0
- package/src/runtime/autonomous-tasks.js +408 -0
- package/src/runtime/build-readiness.js +28 -4
- package/src/runtime/code-graph-rehearse.js +113 -0
- package/src/runtime/codeburn-core.js +112 -0
- package/src/runtime/context-authority.js +40 -0
- package/src/runtime/context-economy-core.js +316 -0
- package/src/runtime/continuity-graph.js +201 -0
- package/src/runtime/continuity-notes.js +71 -26
- package/src/runtime/core-source-ingest.js +379 -0
- package/src/runtime/detached-provider.js +142 -0
- package/src/runtime/doctor-report.js +18 -0
- package/src/runtime/environment-awareness-core.js +460 -0
- package/src/runtime/exposure-catalog.js +276 -0
- package/src/runtime/graphify-core.js +167 -0
- package/src/runtime/guidance-stack.js +9 -2
- package/src/runtime/handoff-packets.js +92 -0
- package/src/runtime/headroom-core.js +104 -0
- package/src/runtime/helper-orchestration-core.js +307 -0
- package/src/runtime/hook-profiles.js +20 -0
- package/src/runtime/learning-loop-core.js +344 -0
- package/src/runtime/live-agent-verification.js +122 -0
- package/src/runtime/maestro-events.js +18 -0
- package/src/runtime/maestro-manager.js +19 -0
- package/src/runtime/maestro-output.js +58 -0
- package/src/runtime/maestro-state.js +125 -0
- package/src/runtime/managed-markdown.js +29 -0
- package/src/runtime/mcp-authority.js +147 -0
- package/src/runtime/mcp-policy.js +192 -0
- package/src/runtime/obsidian-integration-core.js +1089 -0
- package/src/runtime/obsidian-plugin-bridge.js +1045 -0
- package/src/runtime/pi-maestro-core.js +89 -0
- package/src/runtime/preferences.js +79 -0
- package/src/runtime/project-context.js +636 -0
- package/src/runtime/prompt-strategy-core.js +230 -0
- package/src/runtime/quorum-review.js +90 -0
- package/src/runtime/ralph-execution-core.js +217 -0
- package/src/runtime/redaction-gateway.js +373 -0
- package/src/runtime/runtime-state.js +935 -2
- package/src/runtime/schema-migrations.js +162 -0
- package/src/runtime/semantic-recording-core.js +147 -0
- package/src/runtime/signal-hooks.js +28 -0
- package/src/runtime/skills-registry-export.js +754 -0
- package/src/runtime/task-contracts.js +83 -0
- package/src/runtime/universal-plugin-broker-core.js +898 -0
- package/src/runtime/workspace-intelligence-runtime.js +674 -0
- package/src/runtime/workspace-virtualizer.js +102 -0
- package/src/runtime-artifacts.js +225 -62
- package/src/setup-lifecycle.js +359 -0
- package/src/skill-frontmatter.js +78 -0
- package/src/skill-installer.js +226 -71
- package/src/skills.js +1166 -96
- package/src/state-sync.js +0 -0
- package/src/test-fixtures.js +543 -0
- package/src/tui/grid.js +67 -0
- package/src/tui/status-grid.js +23 -0
- package/support-bundle.json +114 -0
- package/templates/AGENTS.md +6 -2
- package/templates/catalog-manifest.json +0 -0
- package/templates/model-instructions/core.md +0 -0
- package/templates/model-instructions/release.md +0 -0
- package/templates/model-instructions/security.md +0 -0
- package/templates/quality/ai-quality-rules.yml +37 -0
- package/docs/assets/image/Screenshot(1).png +0 -0
- package/docs/assets/image/Screenshot(2).png +0 -0
- package/docs/assets/image/Screenshot(3).png +0 -0
- package/docs/assets/image/Screenshot(4).png +0 -0
- package/docs/assets/image/Screenshot(5).png +0 -0
- package/docs/assets/image/Screenshot(6).png +0 -0
- package/docs/assets/image/Screenshot(7).png +0 -0
- package/docs/assets/image/Screenshot(8).png +0 -0
- package/docs/assets/image/Screenshot(9).png +0 -0
- package/docs/assets/meta-architect-logo.png +0 -0
- package/docs/assets/meta-architect-logo.svg +0 -8
- package/docs/onboarding.md +0 -65
- 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.
|
|
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
|
-
"
|
|
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.
|
|
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"
|
|
@@ -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.
|
|
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
|
+
}
|