meridian-server 0.1.0.dev1__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.
Files changed (79) hide show
  1. meridian/MERIDIAN.md +79 -0
  2. meridian/__init__.py +3 -0
  3. meridian/__main__.py +137 -0
  4. meridian/__main__entry.py +54 -0
  5. meridian/_deps.py +552 -0
  6. meridian/dashboard.py +122 -0
  7. meridian/db/__init__.py +7038 -0
  8. meridian/db/migrations.py +1471 -0
  9. meridian/demo_seed.py +305 -0
  10. meridian/enqueue.py +214 -0
  11. meridian/error_alerting.py +182 -0
  12. meridian/executor_config.py +86 -0
  13. meridian/git_md.py +88 -0
  14. meridian/goal_md.py +433 -0
  15. meridian/handoff.py +980 -0
  16. meridian/hosted.py +2456 -0
  17. meridian/integrations/__init__.py +1 -0
  18. meridian/integrations/langgraph.py +166 -0
  19. meridian/limits.py +116 -0
  20. meridian/mcp/__init__.py +3 -0
  21. meridian/mcp/handler.py +1689 -0
  22. meridian/mcp/stdio_handler.py +1601 -0
  23. meridian/mcp_tools.py +622 -0
  24. meridian/md_anchors.py +352 -0
  25. meridian/models.py +345 -0
  26. meridian/pg_adapter.py +1591 -0
  27. meridian/roles.py +107 -0
  28. meridian/routes/__init__.py +1 -0
  29. meridian/routes/admin.py +227 -0
  30. meridian/routes/auth.py +134 -0
  31. meridian/routes/billing.py +459 -0
  32. meridian/routes/blog.py +229 -0
  33. meridian/routes/decisions.py +226 -0
  34. meridian/routes/export.py +84 -0
  35. meridian/routes/files.py +360 -0
  36. meridian/routes/github.py +343 -0
  37. meridian/routes/handoff.py +81 -0
  38. meridian/routes/hitl.py +129 -0
  39. meridian/routes/hooks.py +188 -0
  40. meridian/routes/notes.py +82 -0
  41. meridian/routes/projects.py +1094 -0
  42. meridian/routes/sessions.py +115 -0
  43. meridian/routes/sprint.py +311 -0
  44. meridian/routes/tasks.py +221 -0
  45. meridian/routes/workspace.py +165 -0
  46. meridian/sdk.py +196 -0
  47. meridian/server.py +5443 -0
  48. meridian/static/dashboard-demo.js +393 -0
  49. meridian/static/dashboard-files.js +208 -0
  50. meridian/static/dashboard-mcp.js +32 -0
  51. meridian/static/dashboard-notes.js +185 -0
  52. meridian/static/dashboard-rewind.js +726 -0
  53. meridian/static/dashboard-settings.js +3996 -0
  54. meridian/static/dashboard-sprint.js +987 -0
  55. meridian/static/dashboard-timeline.js +1397 -0
  56. meridian/static/dashboard-utils.js +98 -0
  57. meridian/static/dashboard.bundle.js +11385 -0
  58. meridian/static/dashboard.css +846 -0
  59. meridian/static/dashboard.js +11119 -0
  60. meridian/static/logo-wordmark.svg +13 -0
  61. meridian/static/logo.svg +15 -0
  62. meridian/static/meridian_logo.png +0 -0
  63. meridian/static/the-solution-dashboard-card.png +0 -0
  64. meridian/static/the-solution-dashboard.png +0 -0
  65. meridian/symbols.py +271 -0
  66. meridian/templates/changelog.html +109 -0
  67. meridian/templates/dashboard.html +143 -0
  68. meridian/templates/handoff.md.j2 +127 -0
  69. meridian/templates/install_mcp.html +311 -0
  70. meridian/templates/landing.html +916 -0
  71. meridian/templates/onboarding.html +224 -0
  72. meridian/templates/pricing.html +441 -0
  73. meridian/templates/privacy.html +84 -0
  74. meridian/templates/terms.html +66 -0
  75. meridian/toml_config.py +162 -0
  76. meridian_server-0.1.0.dev1.dist-info/METADATA +33 -0
  77. meridian_server-0.1.0.dev1.dist-info/RECORD +79 -0
  78. meridian_server-0.1.0.dev1.dist-info/WHEEL +4 -0
  79. meridian_server-0.1.0.dev1.dist-info/licenses/LICENSE +184 -0
meridian/MERIDIAN.md ADDED
@@ -0,0 +1,79 @@
1
+ # Meridian Session Instructions
2
+
3
+ You are connected to Meridian — a shared project memory across AI coding sessions.
4
+ Multiple sessions (Claude Code tabs, Cursor, Windsurf, etc.) can be coordinating
5
+ on this project concurrently. Treat the Meridian state as the source of truth
6
+ for "what has been decided, claimed, and shipped."
7
+
8
+ ## ALWAYS
9
+
10
+ - **`log_task` after every meaningful action** so parallel sessions can see your work.
11
+ One line per task. Mark `status` honestly: `done`, `pending`, `failed`.
12
+ - **`set_decision`** for architectural, irreversible, or surprising choices.
13
+ These appear in the decisions log (newest first) and persist across sessions.
14
+ - **`generate_handoff`** when context is filling up (~80k tokens) or before you stop.
15
+ A clean handoff lets a fresh session resume without re-deriving everything.
16
+ - **`claim_task`** before starting work on something a parallel session might pick up.
17
+ **`release_task`** if you bail — don't leave it claimed.
18
+ - **`claim_file(session_id, file_path)`** before editing shared files when parallel
19
+ sessions are active. **`release_file(session_id, file_path)`** when done.
20
+ Use **`idle_until_session_done(watching_session_id)`** to wait before taking
21
+ over a file another session holds. Locks auto-expire after 2 hours. For a big
22
+ shared file, claim a single symbol instead so another session can edit a
23
+ different one: pass **`symbol`** (e.g. `"AuthRouter"` or `"AuthRouter.login"`)
24
+ **and `content`** (the file's full source) — Meridian parses it and blocks only
25
+ on an overlapping line range, telling you which symbols are still safe.
26
+ - **After completing each sprint item**, call `get_sprint_progress(project_id,
27
+ session_id)` before claiming the next one. Its `board_change` field reports any
28
+ items a planner injected since this session started (also echoed on
29
+ `complete_sprint_item`/`claim_sprint_item`), so single-terminal runs pick up
30
+ mid-run additions at the item boundary without interrupting the current task.
31
+ - **`update_md_section`** proposes a replacement for an anchored CLAUDE.md/AGENTS.md
32
+ section. Autonomous executor sessions leave it gated behind a dashboard HITL.
33
+ Human planning sessions (claude.ai) may pass **`force=true`** to skip the HITL and
34
+ apply the change directly — only use `force` when a human is driving the session.
35
+ - **Mid-run corrections**: after each `complete_sprint_item`, call
36
+ `list_hitl_requests(status='pending')` and handle any request with
37
+ `kind='correction'` before the next item: apply the correction, `log_task` it,
38
+ and `answer_hitl(request_id, "acknowledged")`. Corrections never block an
39
+ unattended run — fail open, log, and keep going. (Plain `kind='question'`
40
+ requests stay blocking/auto-answerable as before.)
41
+
42
+ ## ON SESSION START
43
+
44
+ Call `start_session(project_id, session_name)` once. That single call:
45
+ - registers this session
46
+ - returns the current goal (north star + version goal + sprint)
47
+ - returns the last 10 tasks (ambient context)
48
+ - returns the list of other active sessions
49
+ - tells you whether a handoff file exists from a prior session
50
+
51
+ If a handoff exists, read it before doing anything else.
52
+
53
+ ## ON SESSION END
54
+
55
+ Before you stop, call `generate_handoff` so the next session can resume cleanly.
56
+ Ensure any tasks you logged as `pending` are either completed or released.
57
+
58
+ ## DOCS FILES
59
+
60
+ - **Never hand-write `docs/mcp-tools.md`** — it is auto-generated. Regenerate it
61
+ with `pixi run python -c "import asyncio; from meridian import server as srv; doc = asyncio.run(srv.mcp_tools_doc()); open('docs/mcp-tools.md','w').write(doc)"` whenever MCP tool definitions change.
62
+ - **Never auto-write other `docs/*.md` files** unless the sprint item explicitly
63
+ says to update them. Executor sessions writing docs speculatively cause the docs
64
+ to go backwards (replacing current content with stale or hallucinated content).
65
+ If a docs update is needed, file it as a separate sprint item for human review.
66
+
67
+ ## DESIGN PRINCIPLES
68
+
69
+ - The version goal is **stable** — only humans (or you when explicitly directed)
70
+ should change it. Auto-summaries and sprint changes do not bump it.
71
+ - The sprint changes per cycle; the north star changes rarely.
72
+ - Decisions are append-only — never edit prior entries. If a decision is reversed,
73
+ log a new decision describing the reversal.
74
+ - Tasks are immutable once logged. If a task was wrong, log a corrective task.
75
+
76
+ ## OVERRIDE
77
+
78
+ Drop a `MERIDIAN.md` file at your project root to replace these defaults.
79
+ The project-root file wins over Meridian's built-in.
meridian/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """Meridian — multi-session Claude coordinator."""
2
+
3
+ __version__ = "0.1.0"
meridian/__main__.py ADDED
@@ -0,0 +1,137 @@
1
+ """Entrypoint for ``python -m meridian``.
2
+
3
+ Default: run the FastAPI HTTP server on port 7878. Useful for local
4
+ development, the demo script, and any HTTP client.
5
+
6
+ With ``--mcp``: run the MCP stdio server. This is the mode you wire into
7
+ Claude Desktop / Claude Code via an ``mcpServers`` config block, e.g.
8
+
9
+ ```json
10
+ {
11
+ "mcpServers": {
12
+ "meridian": {
13
+ "command": "pixi",
14
+ "args": ["run", "python", "-m", "meridian", "--mcp"],
15
+ "cwd": "/absolute/path/to/meridian/repository"
16
+ }
17
+ }
18
+ }
19
+ ```
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import argparse
25
+ import asyncio
26
+ import os
27
+ import signal
28
+ import socket
29
+ import subprocess
30
+ import sys
31
+
32
+
33
+ def _kill_port(port: int) -> None:
34
+ """Kill any process listening on the given port before starting."""
35
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
36
+ if s.connect_ex(("127.0.0.1", port)) != 0:
37
+ return # port is free
38
+ if sys.platform == "win32":
39
+ result = subprocess.run(
40
+ f"netstat -ano | findstr :{port}",
41
+ shell=True, capture_output=True, text=True,
42
+ )
43
+ for line in result.stdout.splitlines():
44
+ parts = line.split()
45
+ if parts and parts[-1].isdigit():
46
+ pid = int(parts[-1])
47
+ try:
48
+ os.kill(pid, signal.SIGTERM)
49
+ except OSError:
50
+ pass
51
+ break
52
+ else:
53
+ result = subprocess.run(
54
+ ["lsof", "-ti", f":{port}"], capture_output=True, text=True
55
+ )
56
+ for pid_str in result.stdout.split():
57
+ if pid_str.isdigit():
58
+ try:
59
+ os.kill(int(pid_str), signal.SIGTERM)
60
+ except OSError:
61
+ pass
62
+
63
+ # psycopg3 requires SelectorEventLoop on Windows (ProactorEventLoop not supported).
64
+ # Must be set before any asyncio.run() or uvicorn.run() call.
65
+ if sys.platform == "win32":
66
+ import selectors
67
+ # psycopg3 requires SelectorEventLoop — override Windows default ProactorEventLoop
68
+ asyncio.set_event_loop_policy(asyncio.DefaultEventLoopPolicy())
69
+ loop = asyncio.SelectorEventLoop(selectors.SelectSelector())
70
+ asyncio.set_event_loop(loop)
71
+
72
+
73
+ def main(argv: list[str] | None = None) -> int:
74
+ """CLI dispatch: HTTP server by default, MCP stdio with ``--mcp``."""
75
+ parser = argparse.ArgumentParser(
76
+ prog="meridian",
77
+ description="Multi-session Claude coordinator.",
78
+ )
79
+ parser.add_argument(
80
+ "--mcp",
81
+ action="store_true",
82
+ help="Run the MCP server over stdio (for Claude Desktop / Code).",
83
+ )
84
+ parser.add_argument(
85
+ "--host",
86
+ default=os.environ.get("MERIDIAN_HOST", "127.0.0.1"),
87
+ help="HTTP bind host (default 127.0.0.1, override with MERIDIAN_HOST).",
88
+ )
89
+ parser.add_argument(
90
+ "--port",
91
+ type=int,
92
+ default=int(os.environ.get("MERIDIAN_PORT", "7878")),
93
+ help="HTTP port (default 7878, override with MERIDIAN_PORT).",
94
+ )
95
+ args = parser.parse_args(argv)
96
+
97
+ if args.mcp:
98
+ from .server import build_mcp_server
99
+
100
+ _, run_stdio = build_mcp_server()
101
+ # On Windows asyncio.run() creates a new ProactorEventLoop, breaking psycopg3.
102
+ # Use the SelectorEventLoop we set at module scope instead.
103
+ loop = asyncio.get_event_loop()
104
+ loop.run_until_complete(run_stdio())
105
+ return 0
106
+
107
+ import uvicorn
108
+ import sys as _sys
109
+
110
+ _kill_port(args.port)
111
+
112
+ if _sys.platform == "win32":
113
+ # psycopg3 requires SelectorEventLoop. On Windows, asyncio.run()
114
+ # (used by uvicorn.run()) creates ProactorEventLoop by default.
115
+ # Bypass asyncio.run() entirely: run uvicorn.Server on our SelectorEventLoop.
116
+ config = uvicorn.Config(
117
+ "meridian.server:app",
118
+ host=args.host,
119
+ port=args.port,
120
+ reload=False,
121
+ loop="none",
122
+ )
123
+ server = uvicorn.Server(config)
124
+ loop = asyncio.get_event_loop() # SelectorEventLoop set above in module scope
125
+ loop.run_until_complete(server.serve())
126
+ else:
127
+ uvicorn.run(
128
+ "meridian.server:app",
129
+ host=args.host,
130
+ port=args.port,
131
+ reload=False,
132
+ )
133
+ return 0
134
+
135
+
136
+ if __name__ == "__main__":
137
+ sys.exit(main())
@@ -0,0 +1,54 @@
1
+ """PyInstaller entry point for the Meridian desktop executable.
2
+
3
+ When run as a frozen exe:
4
+ - DB lives in ~/.meridian/meridian.db (NOT next to the exe)
5
+ - Starts uvicorn on MERIDIAN_PORT (default 7700)
6
+ - Opens the browser after a short delay
7
+ - Blocks until killed
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import os
12
+ import sys
13
+ import time
14
+ import threading
15
+ import webbrowser
16
+ from pathlib import Path
17
+
18
+
19
+ def _set_frozen_defaults() -> None:
20
+ """Override default DB/data paths so they land in ~/.meridian, not next to the exe."""
21
+ if getattr(sys, "frozen", False):
22
+ home_dir = Path.home() / ".meridian"
23
+ home_dir.mkdir(parents=True, exist_ok=True)
24
+ os.environ.setdefault("MERIDIAN_DB", str(home_dir / "meridian.db"))
25
+ os.environ.setdefault("MERIDIAN_DATA_DIR", str(home_dir / "data"))
26
+
27
+
28
+ def _open_browser(port: int, delay: float = 1.5) -> None:
29
+ """Open the dashboard in the default browser after a short delay.
30
+
31
+ Opens /dashboard (where the first-run setup wizard lives) rather than
32
+ the landing page so binary users go straight into the app.
33
+ """
34
+ def _open():
35
+ time.sleep(delay)
36
+ webbrowser.open(f"http://localhost:{port}/dashboard")
37
+ t = threading.Thread(target=_open, daemon=True)
38
+ t.start()
39
+
40
+
41
+ def main() -> None:
42
+ """Start the Meridian server and open the dashboard."""
43
+ _set_frozen_defaults()
44
+
45
+ import uvicorn
46
+ from meridian.server import app
47
+
48
+ port = int(os.environ.get("MERIDIAN_PORT", 7700))
49
+ _open_browser(port)
50
+ uvicorn.run(app, host="0.0.0.0", port=port)
51
+
52
+
53
+ if __name__ == "__main__":
54
+ main()