diff --git a/scripts/sc2.py b/scripts/sc2.py new file mode 100644 index 00000000..fec840ab --- /dev/null +++ b/scripts/sc2.py @@ -0,0 +1,8 @@ +import ast, sys +p = '/home/hmo/MoFin/server.py' +try: + ast.parse(open(p).read()) + print('SYNTAX OK:', p) +except SyntaxError as e: + print('SYNTAX ERROR:', e) + sys.exit(1) \ No newline at end of file diff --git a/server.py b/server.py index baa15f4a..99b2648b 100644 --- a/server.py +++ b/server.py @@ -1340,6 +1340,72 @@ def api_xmpp_keys(): return jsonify({"error": "xmpp_logger not available"}), 500 +# ── 开发原则 Tab 端点(仿 AgentsMeeting: G规范/K测试/H需求)── + +DOCS_DIR = Path(__file__).resolve().parent / "docs" +HEALTH_REPORT = Path(__file__).resolve().parent / "gateway" / "temp" / "last_health_check.json" + + +@app.route("/api/spec") +def api_spec(): + """G 规范:docs/dev-spec.md 内容""" + f = DOCS_DIR / "dev-spec.md" + if not f.exists(): + return jsonify({"ok": False, "error": "dev-spec.md not found"}) + return jsonify({"ok": True, "content": f.read_text(encoding="utf-8")}) + + +@app.route("/api/spec/history") +def api_spec_history(): + """dev-spec.md 的 git 历史""" + import subprocess + try: + r = subprocess.run( + ["git", "log", "--oneline", "-20", "--", "docs/dev-spec.md"], + capture_output=True, timeout=10, text=True, + cwd=str(Path(__file__).resolve().parent)) + lines = [l for l in r.stdout.splitlines() if l.strip()] + return jsonify({"ok": True, "log": lines, "count": len(lines)}) + except Exception as e: + return jsonify({"ok": False, "error": str(e)[:100]}) + + +@app.route("/api/tests") +def api_tests(): + """K 测试:agents_health_check 服务检查结果,渲染为 pass/fail 测试报告""" + if not HEALTH_REPORT.exists(): + return jsonify({"ok": False, "error": "health report not found (cron not run yet)"}) + try: + rep = json.loads(HEALTH_REPORT.read_text(encoding="utf-8")) + except Exception as e: + return jsonify({"ok": False, "error": str(e)[:100]}) + tests = [] + for svc in rep.get("services", []): + ok = svc.get("health", {}).get("ok", False) + tests.append({ + "name": f"{svc.get('label', svc.get('name'))} ({svc.get('name')})", + "ok": ok, + "expected": False, + "detail": svc.get("detail", ""), + }) + passed = sum(1 for t in tests if t["ok"]) + return jsonify({ + "ok": True, + "tests": tests, + "summary": {"total": len(tests), "passed": passed, "failed": len(tests) - passed}, + "time": rep.get("generated_at", ""), + }) + + +@app.route("/api/prd") +def api_prd(): + """H 需求:docs/prd.md(不存在则返回未建立)""" + f = DOCS_DIR / "prd.md" + if not f.exists(): + return jsonify({"ok": False, "error": "prd.md 尚未建立"}) + return jsonify({"ok": True, "content": f.read_text(encoding="utf-8")}) + + # 注册提示词管理路由 register_routes(app) diff --git a/static/index.html b/static/index.html index 4c275241..fba53f7c 100644 --- a/static/index.html +++ b/static/index.html @@ -12,6 +12,8 @@ body { background: #0f0f13; color: #e2e8f0; } .tab-btn { transition: all 0.15s; } .tab-btn.active { background: #1e293b; color: #60a5fa; border-color: #60a5fa; } +.princ-btn { transition: all 0.15s; color: #8b949e; } +.princ-btn.active { background: #1e293b; color: #60a5fa; } .card { background: #1a1a24; border: 1px solid #2a2a3a; border-radius: 12px; } .badge-up { color: #22c55e; } .badge-down { color: #ef4444; } @@ -63,6 +65,7 @@ body { background: #0f0f13; color: #e2e8f0; } + 📸 上传 @@ -78,6 +81,18 @@ body { background: #0f0f13; color: #e2e8f0; }
+ '; } + + // ── 原有健康监控(功能树/Cron/数据实体/数据流)—— 恢复自 mofin_health.html ── + html += '' + code.replace(/';
+ continue;
+ }
+ if (block.startsWith('#### ')) { h += '' + inMd(block.slice(5).replace(/'; continue; }
+ if (block.startsWith('### ')) { h += '' + inMd(block.slice(4).replace(/'; continue; }
+ if (block.startsWith('## ')) { h += '' + inMd(block.slice(3).replace(/'; continue; }
+ if (block.startsWith('# ')) { h += '' + inMd(block.slice(2).replace(/'; continue; }
+ if (block.startsWith('> ')) { h += '' + inMd(block.slice(2).replace(/'; continue; }
+ if (block.startsWith('---') || block.startsWith('***')) { h += '
'; continue; }
+ if (block.startsWith('- ') || block.startsWith('* ')) {
+ const items = block.split('\n');
+ h += '';
+ items.forEach(li => { const t = li.replace(/^[-*]\s*/, '').trim(); if (t) h += '- ' + inMd(t.replace(/'; });
+ h += '
';
+ continue;
+ }
+ if (/^\d+\.\s/.test(block)) {
+ const items = block.split('\n');
+ h += '';
+ items.forEach(li => { const t = li.replace(/^\d+\.\s*/, '').trim(); if (t) h += '- ' + inMd(t.replace(/'; });
+ h += '
';
+ continue;
+ }
+ if (block.indexOf('|') >= 0 && block.indexOf('\n|') >= 0) {
+ const rows = block.split('\n');
+ let inTable = false;
+ rows.forEach(row => {
+ if (row.indexOf('---') >= 0) return;
+ const cells = row.split('|').filter(c => c.trim());
+ if (cells.length < 2) return;
+ if (!inTable) {
+ h += '' + cells.map(c => '' + inMd(c.trim().replace(/').join('') + ' ';
+ inTable = true;
+ } else {
+ h += '' + cells.map(c => '' + inMd(c.trim().replace(/').join('') + ' ';
+ }
+ });
+ if (inTable) h += '
';
+ continue;
+ }
+ h += '' + inMd(block.replace(/';
+ }
+ return h;
+}
+function inMd(t) {
+ return t.replace(/\*\*([^*]+)\*\*/g, '$1')
+ .replace(/\*([^*]+)\*/g, '$1')
+ .replace(/`([^`]+)`/g, '$1')
+ .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1');
+}