7.9 KiB
AGENTS.md
Guide for coding agents operating in this repository. MiroFish is a multi-agent
AI prediction engine: Flask backend (backend/) + Vue 3 frontend (frontend/)
- shared i18n (
locales/). Monorepo orchestrated by rootpackage.jsonviaconcurrently. Code comments/docstrings are English; identifiers are English. License: AGPL-3.0.
Build / Run / Test Commands
All commands run from repo root unless noted. Node >=18, Python >=3.11 <=3.12,
uv for Python packages.
# Install everything (root + frontend npm + backend uv venv)
npm run setup:all
# Dev (both services, concurrent, --kill-others)
npm run dev
npm run backend # cd backend && uv run python run.py (Flask :5001)
npm run frontend # cd frontend && npm run dev (Vite :3000)
# Production build (frontend only)
npm run build
# Backend deps only
npm run setup:backend # cd backend && uv sync
# Docker
docker compose up -d # reads root .env, ports 3000+5001, mounts backend/uploads
Tests
There are no automated tests. pytest + pytest-asyncio are declared in
backend/pyproject.toml but unused — no tests/ dir, no conftest.py, no
[tool.pytest.ini_options]. Frontend has no vitest/jest.
If you add tests, run them with:
cd backend && uv run pytest # all
cd backend && uv run pytest path/to/test_file.py # single file
cd backend && uv run pytest path/to/test_file.py::test_name # single test
backend/scripts/test_profile_format.py is a manual print-based script, not
a pytest test — run it with uv run python scripts/test_profile_format.py.
Lint / Format
None configured. No ruff/black/flake8/mypy/isort on the backend; no eslint/prettier on the frontend. No CI lint workflow (only Docker image build). Match the existing de-facto style described below; do not add a linter unless asked.
Backend Conventions (Python / Flask)
Package layout: backend/app/ with api/ (blueprints), models/
(dataclasses), services/ (business logic), utils/ (logger, retry, llm_client,
locale). Entry point backend/run.py. Each __init__.py is a manifest: docstring
- relative imports +
__all__.
Imports: Relative for intra-package (from .config import Config,
from ..services.x import Y). Absolute from app... only in run.py and
scripts/ (which shim sys.path). Group: stdlib → third-party → local, blank-line
separated. Late imports inside functions are acceptable for optional/heavy deps.
Naming: snake_case modules/functions, PascalCase classes, UPPER_SNAKE
constants, _-prefixed private helpers. Blueprints: snake_case_bp
(graph_bp, simulation_bp, report_bp). String IDs prefixed
(proj_, sim_, mirofish_).
Models: @dataclass + (str, Enum) for status enums. Hand-written
to_dict() / from_dict() on every model. No app-defined Pydantic models —
pydantic is only used in generated ontology code templates and the OASIS SDK.
Type hints: Annotate function signatures (typing.Dict/Any/List/Optional/ Callable/Tuple). Locals usually unannotated. Be consistent with neighbors.
Errors: No custom exception classes; raise ValueError for bad state. Every
Flask route wraps in try/except Exception as e: returning the uniform envelope:
except Exception as e:
return jsonify({"success": False, "error": str(e),
"traceback": traceback.format_exc()}), 500
Success: {"success": True, "data": {...}} (often + "count", "message").
ValueError → 400/404. Background threads catch exceptions and flip the
TaskManager/Project status to FAILED.
Logging: from ..utils.logger import get_logger then
logger = get_logger('mirofish.<area>') at module top (areas: api,
api.simulation, build, request, retry, simulation, etc.).
f-string messages. Do not use logging.getLogger(__name__) —
ontology_generator.py does this and is the one known inconsistency. Rotating
file handler writes to backend/logs/<date>.log.
Async: Flask is sync (threaded=True). Long work goes to
threading.Thread(target=..., daemon=True). Background threads must re-call
set_locale() — locale is thread-local; capture get_locale() before spawning
and restore it inside the thread. Retry helpers in utils/retry.py:
retry_with_backoff (sync), retry_with_backoff_async (async),
RetryableAPIClient.
Config: app/config.py Config class loads root .env via
dotenv.load_dotenv('../../.env') (relative to config.py). Required:
LLM_API_KEY. Optional: LLM_BASE_URL, LLM_MODEL_NAME,
LLM_BOOST_*, FLASK_HOST/PORT/DEBUG, OASIS_DEFAULT_MAX_ROUNDS,
REPORT_AGENT_*, SECRET_KEY. Config.validate() is called in run.py before
serving. Frontend env: VITE_API_BASE_URL (default http://localhost:5001).
Docstrings: Module docstring (English, one line) at top of every .py.
Functions/classes: English summary, Google-style Args:/Returns:. Flask routes
embed request/response JSON shapes as literal blocks — treat these as the API docs.
Windows: run.py applies a UTF-8 stdout fix before other imports. Keep that
block first.
Frontend Conventions (Vue 3 + Vite, plain JS)
SFCs: <script setup> Composition API only. No Options API, no
defineComponent({}). defineProps({...object syntax...}),
defineEmits([...]). Use ref, reactive, computed, watch, useRouter(),
useI18n().
Style: No semicolons. 2-space indent. Single quotes. Trailing commas in multi-line objects/arrays. Plain JS (no TypeScript).
Imports: Vue ecosystem first (vue, vue-router, vue-i18n), then local
modules. Relative paths dominate (../api/simulation); @ → src and
@locales → root locales/ aliases exist in vite.config.js and may be used.
Naming: PascalCase.vue files (views suffixed *View.vue; step components
Step1..Step5). camelCase JS vars/functions/refs. kebab-case template tags
and CSS classes. Route names PascalCase.
State: No Pinia/Vuex. store/ holds reactive() singletons exporting
setter/getter/clear functions. Component-local state via ref/reactive.
API: Single axios.create() instance in src/api/index.js (baseURL from
VITE_API_BASE_URL || 'http://localhost:5001', 5min timeout). Request interceptor
injects Accept-Language from i18n.global.locale.value; response interceptor
checks the success envelope and rejects on failure. requestWithRetry(fn, maxRetries=3, delay=1000) with exponential backoff. Per-resource modules
(graph.js, simulation.js, report.js) export named functions. JSDoc
@param/@returns on each.
i18n: vue-i18n legacy: false, useI18n(), $t(), <i18n-t> for rich
inline text. Messages live in root locales/*.json (en.json, zh.json),
shared with the backend's utils/locale.py. Persisted locale in localStorage
(key locale, default zh).
CSS: Plain CSS, no preprocessor. <style scoped> default on components;
App.vue has unscoped global resets. Global font: 'JetBrains Mono', 'Space Grotesk', 'Noto Sans SC', monospace. Palette: black/white + orange
(#FF4500/#FF5722).
Ports / Proxy
Backend 5001, frontend dev 3000. Vite proxies /api → http://localhost:5001
(changeOrigin, secure: false). Note: api/index.js defaults to an absolute
baseURL, so set VITE_API_BASE_URL='' to use the proxy.
Known Inconsistencies (fix when touching the file)
- Frontend mixes
export function foo()andexport const foo = () => {}. traceback.format_exc()is leaked in 500 responses (debug aid; not prod-safe).scripts/test_profile_format.pyis named like a pytest test but is a manual print-based script.
Agent Workflow Notes
- Match the style of neighboring files; this repo has no linter to enforce.
- Do not add dependencies (Python or JS) for what a few lines can do.
- Keep the
{success, data}/{success, error}envelope on every new route. - Re-set locale in any new background thread.
- Don't commit
.env; the root.envis gitignored and holds live keys.