chore: reorganize into polyglot monorepo (workshop)

- Move bigmind/ -> mcp/bigmind/
- Move webscraper/ -> mcp/webscraper/
- Move mss-failsafe/ -> java/mss-failsafe/
- Move Wellmann-Shop/ -> java/wellmann-shop/ (normalize to kebab-case)
- Add .roo/ IDE config files to tracking
- Add plans/REPO_STRATEGY.md (monorepo strategy document)
- Expand .gitignore: Java/Maven, Node/TS, coverage, uv.lock
- Rewrite README.md as navigation index
- Update .roo/mcp.json webscraper path to mcp/webscraper/
This commit is contained in:
Patrick Plate
2026-04-04 08:51:15 +02:00
parent 4167e15ed9
commit 155d56e8e8
1598 changed files with 19429 additions and 23 deletions
+190
View File
@@ -0,0 +1,190 @@
"""BigMind Profile Web Server — Flask app served on localhost:BIGMIND_PORT (default 7700).
Started automatically as a daemon thread when the MCP server starts.
Serves a single live profile page built from the BigMind DB.
"""
import os
import threading
import logging
from datetime import datetime, timezone, timedelta
from bigmind.web_render import _render_html # all HTML rendering lives there
logger = logging.getLogger("BigMindWeb")
_PORT = int(os.environ.get("BIGMIND_PORT", "7700"))
_AUTOOPEN = os.environ.get("BIGMIND_AUTOOPEN", "").lower() in ("1", "true", "yes")
_server_started = False
# ── Flask app ─────────────────────────────────────────────────────────────────
def _create_app():
from flask import Flask, jsonify, request
from bigmind import memory_store
from bigmind.profile_builder import build_profile_data
app = Flask(__name__)
app.logger.setLevel(logging.WARNING) # silence Flask request logs
@app.route("/")
def profile():
user = memory_store.get_or_create_user(memory_store.get_current_username())
data = build_profile_data(user["id"])
return _render_html(data)
@app.route("/api/session/<session_id>")
def api_session(session_id):
"""Return Tier-2 summary JSON for a given session id."""
detail = memory_store.get_session_detail(session_id)
if not detail:
return jsonify({"error": "No detailed summary for this session."})
return jsonify(detail)
@app.route("/api/search")
def api_search():
"""Unified memory search — facts + chunks + session one-liners."""
q = (request.args.get("q") or "").strip()
if not q:
return jsonify([])
user = memory_store.get_or_create_user(memory_store.get_current_username())
uid = user["id"]
results = []
# Facts
try:
facts = memory_store.search_facts(uid, q, limit=5)
for f in facts:
results.append({
"type": "fact",
"content": f"[{f.get('category','')}] {f.get('fact','')}",
"date": (f.get("created_at") or "")[:10],
"score": 3,
})
except Exception:
pass
# Chunks
try:
chunks = memory_store.search_chunks(uid, q, limit=5)
for c in chunks:
results.append({
"type": "chunk",
"content": (c.get("content") or "")[:300],
"date": (c.get("created_at") or "")[:10],
"score": 2,
})
except Exception:
pass
# Session one-liners (simple LIKE — no FTS needed)
try:
from bigmind.db import db as _db
with _db() as conn:
rows = conn.execute(
"""SELECT id, one_liner, started_at FROM sessions
WHERE user_id=? AND ended_at IS NOT NULL
AND one_liner LIKE ?
ORDER BY started_at DESC LIMIT 5""",
(uid, f"%{q}%"),
).fetchall()
for r in rows:
results.append({
"type": "session",
"content": r["one_liner"],
"date": (r.get("started_at") or "")[:10],
"score": 1,
})
except Exception:
pass
# Sort by type score desc, deduplicate by content
seen = set()
final = []
for r in sorted(results, key=lambda x: -x["score"]):
key = r["content"][:80]
if key not in seen:
seen.add(key)
final.append(r)
return jsonify(final[:15])
return app
# ── Daemon thread startup ─────────────────────────────────────────────────────
def _port_in_use(port: int) -> bool:
"""Return True if something is already listening on 127.0.0.1:<port>."""
import socket
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.settimeout(0.2)
return s.connect_ex(("127.0.0.1", port)) == 0
def start_web_server() -> str:
"""Start the Flask profile server in a background daemon thread.
Safe to call multiple times (only starts once). If another BigMind
instance is already serving the port this process skips Flask startup
gracefully — the MCP tools still work, the profile page is served by
the other instance (same DB, same data).
The fix for the multi-IDE port-conflict bug: we check the port *before*
setting _server_started = True. Previously the flag was set immediately
after t.start(), so a failed Flask bind (Address already in use) left
_server_started = True with no Flask running — permanent lock-out.
"""
global _server_started
if _server_started:
return f"http://localhost:{_PORT}"
if _port_in_use(_PORT):
# Another BigMind process already owns the port — skip Flask startup.
# Don't set _server_started = True so if that process dies and this
# one is restarted, it can try again cleanly.
logger.info(
"BigMind profile server already running at http://localhost:%d "
"(another IDE instance). Skipping Flask startup.", _PORT
)
return f"http://localhost:{_PORT}"
app = _create_app()
_started_event = threading.Event()
_bind_failed = [] # non-empty if Flask couldn't bind
def _run():
import logging as _log
_log.getLogger("werkzeug").setLevel(_log.ERROR)
try:
_started_event.set() # signal that the thread is running
app.run(host="127.0.0.1", port=_PORT, debug=False, use_reloader=False)
except OSError as exc:
_bind_failed.append(str(exc))
logger.warning("BigMind web server failed to bind port %d: %s", _PORT, exc)
t = threading.Thread(target=_run, daemon=True, name="BigMindWebServer")
t.start()
_started_event.wait(timeout=2.0) # wait for thread to actually start
# Only mark as started if Flask didn't immediately report a bind error
if not _bind_failed:
_server_started = True
logger.info("BigMind profile server started at http://localhost:%d", _PORT)
if _AUTOOPEN:
import webbrowser, time
time.sleep(1.0)
webbrowser.open(f"http://localhost:{_PORT}")
else:
logger.warning(
"BigMind web server could not start (port %d in use). "
"Profile page unavailable from this IDE instance.", _PORT
)
return f"http://localhost:{_PORT}"
def get_profile_url() -> str:
return f"http://localhost:{_PORT}"