mimry 0.1.0__py3-none-any.whl

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.
mimry/__init__.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
mimry/adapters.py ADDED
@@ -0,0 +1,189 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import asdict, dataclass
4
+ from typing import Literal
5
+
6
+ AdapterStatus = Literal["active", "planned"]
7
+ AdapterKind = Literal["code", "document", "data", "media", "project"]
8
+
9
+
10
+ @dataclass(frozen=True)
11
+ class AdapterInfo:
12
+ name: str
13
+ status: AdapterStatus
14
+ kind: AdapterKind
15
+ extensions: tuple[str, ...]
16
+ parser: str
17
+ emits: tuple[str, ...]
18
+ agent_use: str
19
+ notes: str
20
+
21
+ def to_dict(self) -> dict[str, object]:
22
+ data = asdict(self)
23
+ data["extensions"] = list(self.extensions)
24
+ data["emits"] = list(self.emits)
25
+ return data
26
+
27
+
28
+ BUILTIN_ADAPTERS: tuple[AdapterInfo, ...] = (
29
+ AdapterInfo(
30
+ name="python-ast",
31
+ status="active",
32
+ kind="code",
33
+ extensions=(".py",),
34
+ parser="stdlib ast",
35
+ emits=("files", "symbols", "imports", "defines_edges", "line_ranges"),
36
+ agent_use="Find Python services, tests, functions, classes, imports, and edit locations without reading every file.",
37
+ notes="Currently extracts FunctionDef, AsyncFunctionDef, ClassDef, imports, line_start, line_end.",
38
+ ),
39
+ AdapterInfo(
40
+ name="typescript-ast",
41
+ status="active",
42
+ kind="code",
43
+ extensions=(".js", ".jsx", ".ts", ".tsx"),
44
+ parser="tree-sitter-language-pack",
45
+ emits=(
46
+ "files",
47
+ "symbols",
48
+ "imports",
49
+ "exports",
50
+ "defines_edges",
51
+ "component_edges",
52
+ "call_edges",
53
+ "jsx_elements",
54
+ "line_ranges",
55
+ ),
56
+ agent_use="Frontend agents can find React/TypeScript components, functions, classes, methods, imports, JSX usage, call relationships, and edit locations without reading every TSX file.",
57
+ notes="Extracts functions, classes, methods, variable/arrow components, JSX element references, import/export hints (ES and CommonJS require), and line ranges. Call sites resolve to symbol-to-symbol call edges. Module-scope caller attribution is exact; a call on a `const x = f()` line is still attributed to the local binding.",
58
+ ),
59
+ AdapterInfo(
60
+ name="config-manifest",
61
+ status="active",
62
+ kind="data",
63
+ extensions=(
64
+ "package.json",
65
+ "pyproject.toml",
66
+ "tsconfig.json",
67
+ "vite.config.*",
68
+ "next.config.*",
69
+ "app.json",
70
+ "app.config.*",
71
+ ".env.example",
72
+ "CLAUDE.md",
73
+ "AGENTS.md",
74
+ "README.md",
75
+ ),
76
+ parser="manifest-specific safe metadata parsers",
77
+ emits=("commands", "package_manager", "frameworks", "entrypoints", "env_names", "agent_rules"),
78
+ agent_use="Gives coding agents exact commands, package managers, frameworks, env variable names, and repo rules before edits without exposing secret values.",
79
+ notes="Operating-context adapter layered alongside the graph; does not replace code graph extraction.",
80
+ ),
81
+ AdapterInfo(
82
+ name="nextjs-app-router",
83
+ status="active",
84
+ kind="project",
85
+ extensions=("page.tsx", "layout.tsx", "route.ts", "loading.tsx", "error.tsx", "not-found.tsx"),
86
+ parser="safe filesystem router detector + TypeScript AST facts",
87
+ emits=("routes", "layouts", "api_routes", "dynamic_segments", "verification_hints"),
88
+ agent_use="Maps Next.js App Router pages, layouts, loading/error/not-found surfaces, dynamic segments, and API route files for web agents.",
89
+ notes="Does not execute Next.js or read env values; emits route facts into file metadata for ranking/context packs.",
90
+ ),
91
+ AdapterInfo(
92
+ name="fastapi",
93
+ status="active",
94
+ kind="project",
95
+ extensions=(".py",),
96
+ parser="Python AST FastAPI/APIRouter decorator detector",
97
+ emits=("api_routes", "http_methods", "endpoint_symbols", "route_edges"),
98
+ agent_use="Lets backend agents find FastAPI endpoints, methods, paths, and endpoint functions without broad Python spelunking.",
99
+ notes="Detects literal @app/@router HTTP method paths only; no app execution or dependency resolution.",
100
+ ),
101
+ AdapterInfo(
102
+ name="react-native-expo",
103
+ status="active",
104
+ kind="project",
105
+ extensions=("app.json", "app.config.*", ".tsx", ".ts"),
106
+ parser="Expo config + Expo Router filesystem detector",
107
+ emits=("mobile_surfaces", "expo_routes", "screens", "native_config_hints", "app_config_edges"),
108
+ agent_use="Surfaces Expo Router routes, screen/component files, and native iOS/Android config hints without simulator access.",
109
+ notes="Safe manifest/filesystem parsing only; does not run Expo, Metro, xcodebuild, adb, or simulators.",
110
+ ),
111
+ AdapterInfo(
112
+ name="sql-schema",
113
+ status="active",
114
+ kind="data",
115
+ extensions=(".sql", ".py"),
116
+ parser="conservative CREATE TABLE regex over text/Python string literals",
117
+ emits=("tables", "columns", "schema_symbols", "migration_hints"),
118
+ agent_use="Finds straightforward SQL/SQLite tables and columns while keeping migrations as schema evidence instead of runtime edit targets.",
119
+ notes="Read-only text parsing; never executes migrations, opens databases, or performs writes.",
120
+ ),
121
+ AdapterInfo(
122
+ name="markdown-docs",
123
+ status="active",
124
+ kind="document",
125
+ extensions=(".md", ".mdx"),
126
+ parser="Markdown heading/frontmatter/link/wiki-link parser",
127
+ emits=("headings", "frontmatter", "doc_links", "wiki_links", "doc_relationship_hints"),
128
+ agent_use="Turns docs, plans, specs, and Obsidian-style notes into structured context without overpowering code-edit queries.",
129
+ notes="Extracts headings, title/type/tags frontmatter, markdown links, and [[wiki links]]; stores no full document body.",
130
+ ),
131
+ AdapterInfo(
132
+ name="generic-text",
133
+ status="active",
134
+ kind="document",
135
+ extensions=("*",),
136
+ parser="safe text metadata and content hints",
137
+ emits=("files", "metadata", "content_hints"),
138
+ agent_use="Fallback coverage for docs, configs, unknown text files, and ranking hints.",
139
+ notes="Does not store full text by default; sensitive files are skipped by scanner policy.",
140
+ ),
141
+ )
142
+
143
+ PLANNED_ADAPTERS: tuple[AdapterInfo, ...] = (
144
+ AdapterInfo(
145
+ name="js-ts-regex",
146
+ status="planned",
147
+ kind="code",
148
+ extensions=(".ts", ".tsx", ".js", ".jsx"),
149
+ parser="regex import/export scanner",
150
+ emits=("files", "imports", "exports", "defines_edges"),
151
+ agent_use="Fallback path if tree-sitter is unavailable in a constrained environment.",
152
+ notes="Kept as fallback; active adapter is now typescript-ast.",
153
+ ),
154
+ AdapterInfo(
155
+ name="sql-alembic",
156
+ status="planned",
157
+ kind="data",
158
+ extensions=(".sql", ".py"),
159
+ parser="Alembic migration lineage detector layered on sql-schema",
160
+ emits=("migration_edges", "schema_versions", "upgrade_downgrade_symbols"),
161
+ agent_use="Adds richer migration lineage and DB edit gates after the active read-only schema facts are enough.",
162
+ notes="Planned follow-up; active sql-schema already detects straightforward tables and columns safely.",
163
+ ),
164
+ AdapterInfo(
165
+ name="swift-ios",
166
+ status="planned",
167
+ kind="code",
168
+ extensions=(".swift", ".xcodeproj", ".pbxproj"),
169
+ parser="SwiftSyntax or pragmatic Swift/Xcode parser",
170
+ emits=("targets", "entitlements", "app_groups", "native_symbols", "extension_edges"),
171
+ agent_use="Needed for Maggy iOS share extension and App Group work.",
172
+ notes="Later, but important for mobile native boundaries.",
173
+ ),
174
+ )
175
+
176
+
177
+ def list_adapters(include_planned: bool = True) -> list[dict[str, object]]:
178
+ adapters = list(BUILTIN_ADAPTERS)
179
+ if include_planned:
180
+ adapters += list(PLANNED_ADAPTERS)
181
+ return [a.to_dict() for a in adapters]
182
+
183
+
184
+ def adapter_for_extension(extension: str) -> AdapterInfo:
185
+ ext = extension.lower()
186
+ for adapter in BUILTIN_ADAPTERS:
187
+ if ext in adapter.extensions:
188
+ return adapter
189
+ return BUILTIN_ADAPTERS[-1]
@@ -0,0 +1,231 @@
1
+ """Isolated real-client integration smoke for MIMRY's MCP server."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import asyncio
7
+ import json
8
+ import os
9
+ import shutil
10
+ import subprocess
11
+ import sys
12
+ import tempfile
13
+ from pathlib import Path
14
+ from typing import Any
15
+
16
+ from fastmcp import Client
17
+ from fastmcp.client.transports import StdioTransport
18
+
19
+ REQUIRED_TOOLS = {
20
+ "mimry_context",
21
+ "mimry_find",
22
+ "mimry_init",
23
+ "mimry_preflight",
24
+ "mimry_status",
25
+ }
26
+ _ENV_ALLOWLIST = ("PATH", "LANG", "LC_ALL", "LC_CTYPE", "TMPDIR", "SSL_CERT_FILE", "SSL_CERT_DIR", "SYSTEMROOT")
27
+
28
+
29
+ def _run(command: list[str], *, cwd: Path, env: dict[str, str], timeout: int = 45) -> subprocess.CompletedProcess[str]:
30
+ return subprocess.run(
31
+ command,
32
+ cwd=cwd,
33
+ env=env,
34
+ text=True,
35
+ capture_output=True,
36
+ timeout=timeout,
37
+ check=False,
38
+ )
39
+
40
+
41
+ def _require_ok(result: subprocess.CompletedProcess[str], label: str) -> str:
42
+ if result.returncode:
43
+ detail = (result.stderr or result.stdout).strip()
44
+ raise RuntimeError(f"{label} failed ({result.returncode}): {detail}")
45
+ return result.stdout.strip()
46
+
47
+
48
+ def _client_env(sandbox: Path, *, client: str) -> dict[str, str]:
49
+ """Build a fail-closed client environment without operator credentials."""
50
+ env = {name: os.environ[name] for name in _ENV_ALLOWLIST if os.environ.get(name)}
51
+ home = sandbox / f"{client}-home"
52
+ config = sandbox / f"{client}-config"
53
+ cache = sandbox / f"{client}-cache"
54
+ data = sandbox / f"{client}-data"
55
+ appdata = home / "AppData" / "Roaming"
56
+ localappdata = home / "AppData" / "Local"
57
+ for path in (home, config, cache, data, appdata, localappdata):
58
+ path.mkdir(parents=True, exist_ok=True)
59
+ env.update(
60
+ {
61
+ "HOME": str(home),
62
+ # Windows resolves Path.home() from USERPROFILE (then HOMEDRIVE+HOMEPATH)
63
+ # and ignores HOME entirely. Without these the server subprocess raises
64
+ # "Could not determine home directory" at startup and the client just
65
+ # waits out its timeout with no diagnosable error. Point them at the same
66
+ # sandbox home so isolation is unchanged.
67
+ "USERPROFILE": str(home),
68
+ "HOMEDRIVE": home.drive,
69
+ "HOMEPATH": str(home)[len(home.drive) :],
70
+ "XDG_CONFIG_HOME": str(config),
71
+ "XDG_CACHE_HOME": str(cache),
72
+ "XDG_DATA_HOME": str(data),
73
+ "APPDATA": str(appdata),
74
+ "LOCALAPPDATA": str(localappdata),
75
+ "FASTMCP_HOME": str(sandbox / f"{client}-fastmcp"),
76
+ "MIMRY_CACHE_HOME": str(sandbox / "mimry-cache"),
77
+ "NO_COLOR": "1",
78
+ }
79
+ )
80
+ if client == "claude":
81
+ env["CLAUDE_CONFIG_DIR"] = str(config)
82
+ elif client == "codex":
83
+ env["CODEX_HOME"] = str(config)
84
+ return env
85
+
86
+
87
+ async def _protocol_smoke(repo: Path, cache: Path, server_command: list[str]) -> dict[str, Any]:
88
+ env = _client_env(cache.parent, client="protocol")
89
+ env["MIMRY_CACHE_HOME"] = str(cache)
90
+ transport = StdioTransport(server_command[0], server_command[1:], cwd=str(repo), env=env)
91
+ async with Client(transport, timeout=30) as client:
92
+ tools = await client.list_tools()
93
+ tool_names = {tool.name for tool in tools}
94
+ missing = sorted(REQUIRED_TOOLS - tool_names)
95
+ if missing:
96
+ raise RuntimeError(f"MCP protocol is missing required tools: {', '.join(missing)}")
97
+
98
+ init = (await client.call_tool("mimry_init", {"root": str(repo), "skip_graph": True})).data
99
+ if init.get("returncode") != 0:
100
+ raise RuntimeError(f"mimry_init failed over MCP: {init}")
101
+
102
+ preflight = (
103
+ await client.call_tool(
104
+ "mimry_preflight",
105
+ {"query": "locate daily agent entrypoint", "root": str(repo)},
106
+ )
107
+ ).data
108
+ if preflight.get("returncode") != 0 or preflight.get("index_state") != "current":
109
+ raise RuntimeError(f"mimry_preflight failed over MCP: {preflight}")
110
+
111
+ found = (
112
+ await client.call_tool(
113
+ "mimry_find",
114
+ {"query": "daily agent entrypoint", "root": str(repo), "limit": 3},
115
+ )
116
+ ).data
117
+ paths = [row.get("path") for row in found.get("results", [])]
118
+ if "src/entrypoint.py" not in paths:
119
+ raise RuntimeError(f"mimry_find did not return the fixture source: {paths}")
120
+
121
+ context = (
122
+ await client.call_tool(
123
+ "mimry_context",
124
+ {"query": "daily agent entrypoint", "root": str(repo)},
125
+ )
126
+ ).data
127
+ context_path = Path(context.get("output", ""))
128
+ if not context_path.is_file() or "## Source of Truth Reminder" not in context_path.read_text(encoding="utf-8"):
129
+ raise RuntimeError("mimry_context did not write an evidence-grade context pack")
130
+
131
+ return {
132
+ "state": "PASS",
133
+ "transport": "stdio",
134
+ "tools": len(tool_names),
135
+ "required_tools": sorted(REQUIRED_TOOLS),
136
+ "find_paths": paths,
137
+ "context": str(context_path.relative_to(repo)),
138
+ }
139
+
140
+
141
+ def _claude_smoke(sandbox: Path, repo: Path, server_command: list[str]) -> dict[str, Any]:
142
+ binary = shutil.which("claude")
143
+ if not binary:
144
+ return {"state": "UNVERIFIED", "blocker": "claude binary not found"}
145
+
146
+ env = _client_env(sandbox, client="claude")
147
+ add = _run([binary, "mcp", "add", "--scope", "user", "mimry-smoke", "--", *server_command], cwd=repo, env=env)
148
+ _require_ok(add, "Claude Code MCP registration")
149
+ get = _run([binary, "mcp", "get", "mimry-smoke"], cwd=repo, env=env)
150
+ output = _require_ok(get, "Claude Code MCP health check")
151
+ if "Connected" not in output or server_command[0] not in output:
152
+ raise RuntimeError(f"Claude Code did not report the isolated MIMRY server connected: {output}")
153
+ return {
154
+ "state": "PASS",
155
+ "version": _require_ok(_run([binary, "--version"], cwd=repo, env=env), "Claude Code version"),
156
+ "health": "Connected",
157
+ "config_home": "temporary isolated sandbox",
158
+ }
159
+
160
+
161
+ def _codex_smoke(sandbox: Path, repo: Path, server_command: list[str]) -> dict[str, Any]:
162
+ binary = shutil.which("codex")
163
+ if not binary:
164
+ return {"state": "UNVERIFIED", "blocker": "codex binary not found"}
165
+
166
+ env = _client_env(sandbox, client="codex")
167
+ add = _run([binary, "mcp", "add", "mimry-smoke", "--", *server_command], cwd=repo, env=env)
168
+ _require_ok(add, "Codex MCP registration")
169
+ get = _run([binary, "mcp", "get", "mimry-smoke", "--json"], cwd=repo, env=env)
170
+ payload = json.loads(_require_ok(get, "Codex MCP config readback"))
171
+ transport = payload.get("transport", {})
172
+ if not payload.get("enabled") or transport.get("type") != "stdio" or transport.get("command") != server_command[0]:
173
+ raise RuntimeError(f"Codex MCP registration readback mismatch: {payload}")
174
+ return {
175
+ "state": "PASS",
176
+ "version": _require_ok(_run([binary, "--version"], cwd=repo, env=env), "Codex version"),
177
+ "transport": "stdio",
178
+ "config_readback": "matched",
179
+ "config_home": "temporary isolated sandbox",
180
+ }
181
+
182
+
183
+ def _herdr_status() -> dict[str, Any]:
184
+ binary = shutil.which("herdr")
185
+ blocker = (
186
+ "Herdr runtime detected, but pane-only round-trip proof requires an active isolated pane harness."
187
+ if binary
188
+ else "Herdr runtime not detected; pane-only integration was not simulated."
189
+ )
190
+ return {"state": "UNVERIFIED", "detected": bool(binary), "blocker": blocker}
191
+
192
+
193
+ def main(argv: list[str] | None = None) -> int:
194
+ parser = argparse.ArgumentParser(description="Safely smoke MIMRY through real MCP, Claude Code, and Codex clients.")
195
+ parser.add_argument("--json-output", type=Path, help="Optional path for the machine-readable report.")
196
+ args = parser.parse_args(argv)
197
+
198
+ with tempfile.TemporaryDirectory(prefix="mimry-agent-integration-") as raw_sandbox:
199
+ sandbox = Path(raw_sandbox)
200
+ repo = sandbox / "repo"
201
+ source = repo / "src" / "entrypoint.py"
202
+ source.parent.mkdir(parents=True)
203
+ source.write_text("def daily_agent_entrypoint():\n return 'ready'\n", encoding="utf-8")
204
+ (repo / "README.md").write_text("# Integration fixture\n", encoding="utf-8")
205
+ server_command = [sys.executable, "-m", "mimry.mcp_server"]
206
+
207
+ try:
208
+ report = {
209
+ "schema_version": 1,
210
+ "sandbox": "temporary and deleted after run",
211
+ "mcp_protocol": asyncio.run(_protocol_smoke(repo, sandbox / "cache", server_command)),
212
+ "claude_code": _claude_smoke(sandbox, repo, server_command),
213
+ "codex": _codex_smoke(sandbox, repo, server_command),
214
+ "herdr": _herdr_status(),
215
+ }
216
+ except Exception as exc:
217
+ report = {"schema_version": 1, "state": "FAIL", "error": str(exc)}
218
+ code = 1
219
+ else:
220
+ code = 0
221
+
222
+ rendered = json.dumps(report, indent=2, sort_keys=True)
223
+ print(rendered)
224
+ if args.json_output:
225
+ args.json_output.parent.mkdir(parents=True, exist_ok=True)
226
+ args.json_output.write_text(rendered + "\n", encoding="utf-8")
227
+ return code
228
+
229
+
230
+ if __name__ == "__main__":
231
+ raise SystemExit(main())