diff --git a/.planning/phases/01-foundation/01-01-PLAN.md b/.planning/phases/01-foundation/01-01-PLAN.md new file mode 100644 index 0000000..87ae3d1 --- /dev/null +++ b/.planning/phases/01-foundation/01-01-PLAN.md @@ -0,0 +1,132 @@ +--- +phase: 01-foundation +plan: 01 +type: execute +--- + + +Set up project scaffolding with pyproject.toml, ruff, pre-commit, and src layout. + +Purpose: Establish consistent tooling from day one—linting, formatting, testing infrastructure. +Output: Working Python project structure with `uv sync` installing all deps, ruff/pre-commit configured. + + + +~/.claude/get-shit-done/workflows/execute-phase.md +~/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@SPEC.md +@CLAUDE.md + +**Constraints from PROJECT.md:** +- Python 3.11+ +- ruff (line length 100) +- pytest, 80%+ coverage on core logic +- Type hints required on public functions +- Docstrings required on modules and classes +- Dependencies unpinned unless security required + + + + + + Task 1: Create pyproject.toml with dependencies and tool config + pyproject.toml + +Create pyproject.toml with: + +**[project] section:** +- name = "moai" +- version = "0.1.0" +- description = "Multi-AI collaborative brainstorming platform" +- requires-python = ">=3.11" +- dependencies: python-telegram-bot, sqlalchemy, httpx, aiosqlite (for async SQLite) + +**[project.optional-dependencies]:** +- dev: pytest, pytest-cov, pytest-asyncio, ruff, pre-commit + +**[tool.ruff] section:** +- line-length = 100 +- target-version = "py311" + +**[tool.ruff.lint]:** +- select = ["E", "F", "I", "N", "W", "UP"] + +**[tool.pytest.ini_options]:** +- testpaths = ["tests"] +- asyncio_mode = "auto" + +**[build-system]:** +- requires = ["hatchling"] +- build-backend = "hatchling.build" + +Use hatchling as build backend (modern, works well with uv). +Do NOT pin dependency versions. + + uv sync completes without errors + pyproject.toml valid, all dependencies installable + + + + Task 2: Create pre-commit configuration + .pre-commit-config.yaml + +Create .pre-commit-config.yaml with: + +**Hooks:** +1. ruff (linting): repo = https://github.com/astral-sh/ruff-pre-commit, hooks = [ruff, ruff-format] +2. Standard pre-commit hooks: trailing-whitespace, end-of-file-fixer, check-yaml + +Use latest rev for ruff-pre-commit (check GitHub for current version, approximately v0.11.x). + +Do NOT add pytest hook—running tests on every commit is too slow. Tests run manually or in CI. + + pre-commit install && pre-commit run --all-files passes + Pre-commit hooks installed and passing + + + + Task 3: Create src layout and package structure + src/moai/__init__.py, src/moai/bot/__init__.py, src/moai/bot/handlers/__init__.py, src/moai/core/__init__.py, tests/__init__.py + +Create directory structure per SPEC.md: + +src/moai/__init__.py - Package marker with __version__ = "0.1.0" +src/moai/bot/__init__.py - Bot subpackage marker (docstring: "Telegram bot handlers and entry point") +src/moai/bot/handlers/__init__.py - Handlers subpackage marker +src/moai/core/__init__.py - Core subpackage marker (docstring: "Core business logic, models, and services") +tests/__init__.py - Test package marker + +Each __init__.py should have a module docstring describing its purpose. +Use triple-quoted docstrings at the top of each file. + + python -c "import moai; print(moai.__version__)" prints "0.1.0" + Package importable, structure matches SPEC.md + + + + + +Before declaring plan complete: +- [ ] `uv sync` succeeds without errors +- [ ] `pre-commit run --all-files` passes +- [ ] `python -c "import moai"` succeeds +- [ ] `ruff check src tests` passes +- [ ] Directory structure matches SPEC.md file structure + + + + +- All tasks completed +- All verification checks pass +- No linting errors +- Package installable and importable + + + +After completion, create `.planning/phases/01-foundation/01-01-SUMMARY.md` + diff --git a/.planning/phases/01-foundation/01-02-PLAN.md b/.planning/phases/01-foundation/01-02-PLAN.md new file mode 100644 index 0000000..b28852d --- /dev/null +++ b/.planning/phases/01-foundation/01-02-PLAN.md @@ -0,0 +1,163 @@ +--- +phase: 01-foundation +plan: 02 +type: execute +--- + + +Create SQLAlchemy models for Project, Discussion, Round, Message, and Consensus. + +Purpose: Define the data model that powers all discussion features. +Output: Complete models.py with all relationships, ready for database creation. + + + +~/.claude/get-shit-done/workflows/execute-phase.md +~/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@SPEC.md +@CLAUDE.md + +**Data model from SPEC.md:** +``` +Project (has many) -> Discussion (has many) -> Round (has many) -> Message + \-> Discussion (has one) -> Consensus +``` + +**Field specifications:** +- Project: id (uuid), name, created_at, updated_at, models (JSON array), settings (JSON) +- Discussion: id, project_id (FK), question, type (open|discuss), status (active|completed), created_at +- Round: id, discussion_id (FK), round_number, type (parallel|sequential) +- Message: id, round_id (FK), model, content, timestamp, is_direct +- Consensus: id, discussion_id (FK), agreements (JSON array), disagreements (JSON array), generated_at, generated_by + +**Constraints:** +- Use SQLAlchemy 2.0 style (mapped_column, DeclarativeBase) +- Type hints required +- SQLite compatible (use JSON type, not ARRAY) + + + + + + Task 1: Create base model and enums + src/moai/core/models.py + +Create src/moai/core/models.py with: + +**Imports:** SQLAlchemy 2.0 style (DeclarativeBase, Mapped, mapped_column, relationship), uuid, datetime, enum + +**Base class:** +- class Base(DeclarativeBase): pass + +**Enums (use Python Enum, store as string):** +- DiscussionType: OPEN = "open", DISCUSS = "discuss" +- DiscussionStatus: ACTIVE = "active", COMPLETED = "completed" +- RoundType: PARALLEL = "parallel", SEQUENTIAL = "sequential" + +**UUID helper:** +- Use uuid4() for default IDs +- Store as String(36) for SQLite compatibility (NOT native UUID type) + +Add module docstring explaining the data model. + + python -c "from moai.core.models import Base, DiscussionType, DiscussionStatus, RoundType" + Base class and enums importable + + + + Task 2: Create Project and Discussion models + src/moai/core/models.py + +Add to models.py: + +**Project model:** +- id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid4())) +- name: Mapped[str] = mapped_column(String(255)) +- created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) +- updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) +- models: Mapped[list] = mapped_column(JSON, default=list) - stores ["claude", "gpt", "gemini"] +- settings: Mapped[dict] = mapped_column(JSON, default=dict) - stores {default_rounds, consensus_threshold, system_prompt_override} +- discussions: Mapped[list["Discussion"]] = relationship(back_populates="project", cascade="all, delete-orphan") + +**Discussion model:** +- id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid4())) +- project_id: Mapped[str] = mapped_column(ForeignKey("project.id")) +- question: Mapped[str] = mapped_column(Text) +- type: Mapped[DiscussionType] = mapped_column(Enum(DiscussionType)) +- status: Mapped[DiscussionStatus] = mapped_column(Enum(DiscussionStatus), default=DiscussionStatus.ACTIVE) +- created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) +- project: Mapped["Project"] = relationship(back_populates="discussions") +- rounds: Mapped[list["Round"]] = relationship(back_populates="discussion", cascade="all, delete-orphan") +- consensus: Mapped["Consensus"] = relationship(back_populates="discussion", uselist=False, cascade="all, delete-orphan") + +Use __tablename__ = "project" and "discussion" (singular, lowercase). + + python -c "from moai.core.models import Project, Discussion; print(Project.__tablename__, Discussion.__tablename__)" + Project and Discussion models defined with bidirectional relationships + + + + Task 3: Create Round, Message, and Consensus models + src/moai/core/models.py + +Add to models.py: + +**Round model:** +- id: Mapped[str] (uuid, primary key) +- discussion_id: Mapped[str] = mapped_column(ForeignKey("discussion.id")) +- round_number: Mapped[int] +- type: Mapped[RoundType] = mapped_column(Enum(RoundType)) +- discussion: Mapped["Discussion"] = relationship(back_populates="rounds") +- messages: Mapped[list["Message"]] = relationship(back_populates="round", cascade="all, delete-orphan") + +**Message model:** +- id: Mapped[str] (uuid, primary key) +- round_id: Mapped[str] = mapped_column(ForeignKey("round.id")) +- model: Mapped[str] = mapped_column(String(50)) - e.g., "claude", "gpt", "gemini" +- content: Mapped[str] = mapped_column(Text) +- timestamp: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) +- is_direct: Mapped[bool] = mapped_column(Boolean, default=False) - true if @mentioned +- round: Mapped["Round"] = relationship(back_populates="messages") + +**Consensus model:** +- id: Mapped[str] (uuid, primary key) +- discussion_id: Mapped[str] = mapped_column(ForeignKey("discussion.id"), unique=True) +- agreements: Mapped[list] = mapped_column(JSON, default=list) - bullet point strings +- disagreements: Mapped[list] = mapped_column(JSON, default=list) - [{topic, positions: {model: position}}] +- generated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) +- generated_by: Mapped[str] = mapped_column(String(50)) - which model summarized +- discussion: Mapped["Discussion"] = relationship(back_populates="consensus") + +Use __tablename__ = "round", "message", "consensus" (singular, lowercase). + + python -c "from moai.core.models import Round, Message, Consensus; print('All models imported')" + All 5 models defined with complete relationships + + + + + +Before declaring plan complete: +- [ ] `python -c "from moai.core.models import Base, Project, Discussion, Round, Message, Consensus"` succeeds +- [ ] `ruff check src/moai/core/models.py` passes +- [ ] All foreign keys reference correct tables +- [ ] All relationships are bidirectional +- [ ] All fields have type hints + + + + +- All tasks completed +- All 5 models importable +- No linting errors +- Relationships match SPEC.md data model diagram + + + +After completion, create `.planning/phases/01-foundation/01-02-SUMMARY.md` + diff --git a/.planning/phases/01-foundation/01-03-PLAN.md b/.planning/phases/01-foundation/01-03-PLAN.md new file mode 100644 index 0000000..341017d --- /dev/null +++ b/.planning/phases/01-foundation/01-03-PLAN.md @@ -0,0 +1,184 @@ +--- +phase: 01-foundation +plan: 03 +type: execute +--- + + +Create database module with async session management and write model tests. + +Purpose: Enable database operations and validate models work correctly. +Output: Working database.py with async session factory, passing model tests. + + + +~/.claude/get-shit-done/workflows/execute-phase.md +~/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@SPEC.md +@CLAUDE.md +@src/moai/core/models.py + +**Tech choices:** +- SQLAlchemy 2.0 async (create_async_engine, AsyncSession) +- aiosqlite for async SQLite +- pytest-asyncio for async tests + +**From CLAUDE.md:** +- Testing: pytest, target 80%+ coverage on core logic +- Database: SQLAlchemy + SQLite, upgrades to PostgreSQL in Phase 2 + + + + + + Task 1: Create database module with async session management + src/moai/core/database.py + +Create src/moai/core/database.py with: + +**Imports:** sqlalchemy.ext.asyncio (create_async_engine, AsyncSession, async_sessionmaker), contextlib + +**Module-level:** +- DATABASE_URL: str = "sqlite+aiosqlite:///./moai.db" (default, can be overridden) +- engine: AsyncEngine = None (initialized lazily) +- async_session_factory: async_sessionmaker = None + +**Functions:** +1. init_db(url: str | None = None) -> None: + - Creates engine with echo=False + - Creates async_session_factory + - Stores in module globals + - Use: `create_async_engine(url, echo=False)` + +2. async create_tables() -> None: + - Imports Base from models + - Runs `async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all)` + +3. @contextlib.asynccontextmanager + async def get_session() -> AsyncGenerator[AsyncSession, None]: + - Yields session from factory + - Handles commit on success, rollback on exception + - Pattern: `async with async_session_factory() as session: yield session; await session.commit()` + +4. async def close_db() -> None: + - Disposes engine: `await engine.dispose()` + +Add module docstring explaining session management pattern. + + python -c "from moai.core.database import init_db, create_tables, get_session, close_db" + Database module importable with all functions + + + + Task 2: Create model tests with in-memory database + tests/test_models.py + +Create tests/test_models.py with: + +**Fixtures:** +- @pytest.fixture + async def db_session(): + - init_db("sqlite+aiosqlite:///:memory:") + - await create_tables() + - async with get_session() as session: yield session + - await close_db() + +**Test cases:** + +1. test_create_project: + - Create Project(name="Test Project", models=["claude", "gpt"]) + - Add to session, commit + - Assert id is set (UUID string), name correct, models correct + +2. test_create_discussion_with_project: + - Create Project, add Discussion linked to it + - Assert discussion.project_id matches project.id + - Assert project.discussions contains the discussion + +3. test_create_full_discussion_chain: + - Create Project -> Discussion -> Round -> Message + - Verify all relationships work + - Verify cascade (all linked when navigating relationships) + +4. test_create_consensus: + - Create Discussion with Consensus + - Assert discussion.consensus is set + - Assert consensus.discussion links back + +5. test_project_cascade_delete: + - Create Project with Discussion with Round with Message + - Delete Project + - Assert all children deleted (cascade) + +Use pytest.mark.asyncio on all async tests. +Import all models and database functions. + + pytest tests/test_models.py -v passes all tests + 5 model tests passing, cascade behavior verified + + + + Task 3: Add .gitignore entries and verify full test suite + .gitignore + +Update .gitignore to add: + +``` +# Database +*.db +*.sqlite +*.sqlite3 + +# Python +__pycache__/ +*.pyc +.pytest_cache/ +.coverage +htmlcov/ + +# Virtual environments +.venv/ +venv/ + +# IDE +.idea/ +.vscode/ +*.swp +``` + +Then run full test suite with coverage to verify everything works together. + + pytest --cov=moai --cov-report=term-missing shows coverage, all tests pass + .gitignore updated, tests pass with coverage report + + + + + +Before declaring plan complete: +- [ ] `pytest tests/test_models.py -v` passes all 5 tests +- [ ] `pytest --cov=moai --cov-report=term-missing` runs successfully +- [ ] `ruff check src tests` passes +- [ ] Database file (moai.db) is gitignored +- [ ] Phase 1 complete: scaffolding, models, database all working + + + + +- All tasks completed +- All tests pass +- No linting errors +- Phase 1: Foundation complete + + + +After completion, create `.planning/phases/01-foundation/01-03-SUMMARY.md` with: +- Summary of all 3 plans in Phase 1 +- Final verification that foundation is complete +- Ready for Phase 2: Bot Core +