From a8f097817db6ef09d3a4ea2e4d46ac55f6ad6e9e Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Mon, 2 Feb 2026 20:15:38 +0530 Subject: [PATCH] chore(backend): add black/isort/flake8 + pre-commit --- .pre-commit-config.yaml | 28 ++++ backend/.flake8 | 10 ++ backend/alembic.ini | 2 +- backend/alembic/README | 2 +- .../alembic/__pycache__/env.cpython-312.pyc | Bin 2468 -> 0 bytes backend/alembic/env.py | 6 +- ...2c1b9c8e12_add_teams_and_team_ownership.py | 2 +- .../versions/bacd5e6a253d_baseline_schema.py | 1 + backend/app/api/activities.py | 4 +- backend/app/api/org.py | 64 ++++++-- backend/app/api/projects.py | 59 ++++++-- backend/app/api/work.py | 139 +++++++++++++++--- .../core/__pycache__/config.cpython-312.pyc | Bin 677 -> 693 bytes .../db/__pycache__/session.cpython-312.pyc | Bin 928 -> 944 bytes backend/app/integrations/notify.py | 4 +- backend/app/integrations/openclaw.py | 9 +- backend/app/models/__init__.py | 2 +- backend/app/models/org.py | 2 - backend/pyproject.toml | 9 ++ backend/requirements-dev.txt | 4 + backend/scripts/lint.sh | 9 ++ frontend/public/file.svg | 2 +- frontend/public/globe.svg | 2 +- frontend/public/next.svg | 2 +- frontend/public/vercel.svg | 2 +- frontend/public/window.svg | 2 +- 26 files changed, 299 insertions(+), 67 deletions(-) create mode 100644 .pre-commit-config.yaml create mode 100644 backend/.flake8 delete mode 100644 backend/alembic/__pycache__/env.cpython-312.pyc create mode 100644 backend/pyproject.toml create mode 100644 backend/requirements-dev.txt create mode 100755 backend/scripts/lint.sh diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..1c99266b --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,28 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: end-of-file-fixer + - id: trailing-whitespace + - id: check-yaml + - id: check-added-large-files + + - repo: https://github.com/psf/black + rev: 24.10.0 + hooks: + - id: black + language_version: python3 + files: ^backend/.*\.py$ + + - repo: https://github.com/PyCQA/isort + rev: 5.13.2 + hooks: + - id: isort + files: ^backend/.*\.py$ + + - repo: https://github.com/PyCQA/flake8 + rev: 7.1.1 + hooks: + - id: flake8 + files: ^backend/.*\.py$ + args: [--config=backend/.flake8] diff --git a/backend/.flake8 b/backend/.flake8 new file mode 100644 index 00000000..e2203bb5 --- /dev/null +++ b/backend/.flake8 @@ -0,0 +1,10 @@ +[flake8] +max-line-length = 100 +extend-ignore = E203, W503, E501 +exclude = + .venv, + backend/.venv, + alembic, + backend/alembic, + **/__pycache__, + **/*.pyc diff --git a/backend/alembic.ini b/backend/alembic.ini index e8b4e3c1..f0da7e5b 100644 --- a/backend/alembic.ini +++ b/backend/alembic.ini @@ -86,7 +86,7 @@ path_separator = os # database URL. This is consumed by the user-maintained env.py script only. # other means of configuring database URLs may be customized within the env.py # file. -sqlalchemy.url = +sqlalchemy.url = [post_write_hooks] diff --git a/backend/alembic/README b/backend/alembic/README index 98e4f9c4..2500aa1b 100644 --- a/backend/alembic/README +++ b/backend/alembic/README @@ -1 +1 @@ -Generic single-database configuration. \ No newline at end of file +Generic single-database configuration. diff --git a/backend/alembic/__pycache__/env.cpython-312.pyc b/backend/alembic/__pycache__/env.cpython-312.pyc deleted file mode 100644 index ddf714ab5f1aac98ccd03a0e91c87da23141344e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2468 zcmah~&2Jk;6rc5ec-NneW2cQ{QrAhG8Z|W>s$MG85}_WTQA&|eeOYb1V|UGdxU=gf zDXB0L2M|c8r`}K{IDpc>0nx&ZOO~W^x^hJk?SWfRT1CoO73s)`E!7ENF)I zLPP|K7gH5SSyF5)w}}1&+NDbp=zF8V1=L_&Lyiz|Dx-!}R%7mQ>wQaRcsC8fOC$Kp z8NkrcfBYIbY#LblScZC7@5dUjfu_)#$Yz#947v(_sAPq`ik6uYzwm(Y!_yJCCyZA$d(2%t~#>SbnKGg2bMt5B2VhU zr$sQyP<~_y1GHoZ(q}7GibO9#TBdAhbrJxRq`}gr@y(MY-86RCLbhr(9q>seKuJIQ zLzp0ck&!P;?fEVj?Qj{F%WTJz-D9)cW7+Mo!rhA-nNu$$o)?}cP-LVd61FUaGhj?gvLX+T{w&F>&)j=PzZ4gEPz$QQ&km@726!(OF zON`QzuzNZ$)f38^sRgoL8PJ+1G?kvX(9-oq@a^|n>eR0hLL=e(CgYUK!c=0tcu+Zh zS$NU@Ig}+GljC4w)3oOE{SwK%Tq34lA+um}k{W*x6I4h#+esJQbn&|zo!Rs5?D@^~ zh2PQ_e|o=tsau@;R%>6{NEKXe>S=0vTg+@HbKTU~ow?g{&%@lP(0+F(h9a4clyjxr zrZl;OIDY2t>wfW!yW;rW$}h(sikF|p)17$HjTblLC!vI-nQm;f6Dzo}!d7e=s!N*L z3&Tc#_nrXk4mH{z2>0!2IPCosZ_KmogOSYJXZZ(3kY0e0C zTEz5N+ad6F(7Srvf@R{;Q7^= z$XrrsHenON!A{zs;VHbX`>!6M_2{u+L})FNJY|pc0YM*GnRZi84BBTe+4S}CTNt5P z@{*bzG&Y%fC2^{!&ye2)_r-G{ZRz+eat36yJKD=I?XV2PJVu3QXyzH3_#Ks>q3Ory zwHE?njy{o&d_DQaWLtQ`N*y-qve^x`_=rn str: Never returns localhost/ because agents may run on another machine.""" - - import os, re, subprocess - + import os + import re + import subprocess explicit = os.environ.get("MISSION_CONTROL_BASE_URL") if explicit: return explicit.rstrip("/") - try: out = subprocess.check_output(["bash", "-lc", "hostname -I"], text=True).strip() # pick first RFC1918-ish IPv4, skip docker/loopback @@ -49,12 +48,16 @@ def _public_api_base_url() -> str: continue if ip.startswith("172.17."): continue - if ip.startswith("192.168.") or ip.startswith("10.") or ip.startswith("172.16.") or ip.startswith("172."): + if ( + ip.startswith("192.168.") + or ip.startswith("10.") + or ip.startswith("172.16.") + or ip.startswith("172.") + ): return f"http://{ip}:8000" except Exception: pass - # Fallback placeholder (should be overridden by env var) return "http://:8000" @@ -202,7 +205,11 @@ def create_team( entity_type="team", entity_id=team.id, verb="created", - payload={"name": team.name, "department_id": team.department_id, "lead_employee_id": team.lead_employee_id}, + payload={ + "name": team.name, + "department_id": team.department_id, + "lead_employee_id": team.lead_employee_id, + }, ) session.commit() except IntegrityError: @@ -231,7 +238,14 @@ def update_team( session.add(team) try: session.flush() - log_activity(session, actor_employee_id=actor_employee_id, entity_type="team", entity_id=team.id, verb="updated", payload=data) + log_activity( + session, + actor_employee_id=actor_employee_id, + entity_type="team", + entity_id=team.id, + verb="updated", + payload=data, + ) session.commit() except IntegrityError: session.rollback() @@ -241,7 +255,6 @@ def update_team( return team - @router.post("/departments", response_model=Department) def create_department( payload: DepartmentCreate, @@ -270,7 +283,9 @@ def create_department( session.commit() except IntegrityError: session.rollback() - raise HTTPException(status_code=409, detail="Department already exists or violates constraints") + raise HTTPException( + status_code=409, detail="Department already exists or violates constraints" + ) session.refresh(dept) return dept @@ -294,7 +309,14 @@ def update_department( session.add(dept) session.commit() session.refresh(dept) - log_activity(session, actor_employee_id=actor_employee_id, entity_type="department", entity_id=dept.id, verb="updated", payload=data) + log_activity( + session, + actor_employee_id=actor_employee_id, + entity_type="department", + entity_id=dept.id, + verb="updated", + payload=data, + ) session.commit() return dept @@ -354,7 +376,14 @@ def update_employee( session.add(emp) try: session.flush() - log_activity(session, actor_employee_id=actor_employee_id, entity_type="employee", entity_id=emp.id, verb="updated", payload=data) + log_activity( + session, + actor_employee_id=actor_employee_id, + entity_type="employee", + entity_id=emp.id, + verb="updated", + payload=data, + ) session.commit() except IntegrityError: session.rollback() @@ -401,7 +430,10 @@ def deprovision_employee_agent( try: client.tools_invoke( "sessions_send", - {"sessionKey": emp.openclaw_session_key, "message": "You are being deprovisioned. Stop all work and ignore future messages."}, + { + "sessionKey": emp.openclaw_session_key, + "message": "You are being deprovisioned. Stop all work and ignore future messages.", + }, timeout_s=5.0, ) except Exception: diff --git a/backend/app/api/projects.py b/backend/app/api/projects.py index 2b788eee..2f90232f 100644 --- a/backend/app/api/projects.py +++ b/backend/app/api/projects.py @@ -4,7 +4,7 @@ from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.exc import IntegrityError from sqlmodel import Session, select -from app.api.utils import log_activity, get_actor_employee_id +from app.api.utils import get_actor_employee_id, log_activity from app.db.session import get_session from app.models.projects import Project, ProjectMember from app.schemas.projects import ProjectCreate, ProjectUpdate @@ -45,15 +45,21 @@ def create_project( session.commit() except IntegrityError: session.rollback() - raise HTTPException(status_code=409, detail="Project already exists or violates constraints") + raise HTTPException( + status_code=409, detail="Project already exists or violates constraints" + ) session.refresh(proj) return proj - @router.patch("/{project_id}", response_model=Project) -def update_project(project_id: int, payload: ProjectUpdate, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)): +def update_project( + project_id: int, + payload: ProjectUpdate, + session: Session = Depends(get_session), + actor_employee_id: int = Depends(get_actor_employee_id), +): proj = session.get(Project, project_id) if not proj: raise HTTPException(status_code=404, detail="Project not found") @@ -65,7 +71,14 @@ def update_project(project_id: int, payload: ProjectUpdate, session: Session = D session.add(proj) session.commit() session.refresh(proj) - log_activity(session, actor_employee_id=actor_employee_id, entity_type="project", entity_id=proj.id, verb="updated", payload=data) + log_activity( + session, + actor_employee_id=actor_employee_id, + entity_type="project", + entity_id=proj.id, + verb="updated", + payload=data, + ) session.commit() return proj @@ -73,16 +86,29 @@ def update_project(project_id: int, payload: ProjectUpdate, session: Session = D @router.get("/{project_id}/members", response_model=list[ProjectMember]) def list_project_members(project_id: int, session: Session = Depends(get_session)): return session.exec( - select(ProjectMember).where(ProjectMember.project_id == project_id).order_by(ProjectMember.id.asc()) + select(ProjectMember) + .where(ProjectMember.project_id == project_id) + .order_by(ProjectMember.id.asc()) ).all() @router.post("/{project_id}/members", response_model=ProjectMember) -def add_project_member(project_id: int, payload: ProjectMember, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)): - existing = session.exec(select(ProjectMember).where(ProjectMember.project_id == project_id, ProjectMember.employee_id == payload.employee_id)).first() +def add_project_member( + project_id: int, + payload: ProjectMember, + session: Session = Depends(get_session), + actor_employee_id: int = Depends(get_actor_employee_id), +): + existing = session.exec( + select(ProjectMember).where( + ProjectMember.project_id == project_id, ProjectMember.employee_id == payload.employee_id + ) + ).first() if existing: raise HTTPException(status_code=409, detail="Member already added") - member = ProjectMember(project_id=project_id, employee_id=payload.employee_id, role=payload.role) + member = ProjectMember( + project_id=project_id, employee_id=payload.employee_id, role=payload.role + ) session.add(member) session.commit() session.refresh(member) @@ -99,7 +125,12 @@ def add_project_member(project_id: int, payload: ProjectMember, session: Session @router.delete("/{project_id}/members/{member_id}") -def remove_project_member(project_id: int, member_id: int, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)): +def remove_project_member( + project_id: int, + member_id: int, + session: Session = Depends(get_session), + actor_employee_id: int = Depends(get_actor_employee_id), +): member = session.get(ProjectMember, member_id) if not member or member.project_id != project_id: raise HTTPException(status_code=404, detail="Project member not found") @@ -118,7 +149,13 @@ def remove_project_member(project_id: int, member_id: int, session: Session = De @router.patch("/{project_id}/members/{member_id}", response_model=ProjectMember) -def update_project_member(project_id: int, member_id: int, payload: ProjectMember, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)): +def update_project_member( + project_id: int, + member_id: int, + payload: ProjectMember, + session: Session = Depends(get_session), + actor_employee_id: int = Depends(get_actor_employee_id), +): member = session.get(ProjectMember, member_id) if not member or member.project_id != project_id: raise HTTPException(status_code=404, detail="Project member not found") diff --git a/backend/app/api/work.py b/backend/app/api/work.py index dc584696..2a01510c 100644 --- a/backend/app/api/work.py +++ b/backend/app/api/work.py @@ -2,16 +2,16 @@ from __future__ import annotations from datetime import datetime -from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks -from sqlmodel import Session, select +from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException from sqlalchemy.exc import IntegrityError +from sqlmodel import Session, select -from app.api.utils import log_activity, get_actor_employee_id +from app.api.utils import get_actor_employee_id, log_activity from app.db.session import get_session +from app.integrations.notify import NotifyContext, notify_openclaw from app.models.org import Employee from app.models.work import Task, TaskComment from app.schemas.work import TaskCommentCreate, TaskCreate, TaskUpdate -from app.integrations.notify import NotifyContext, notify_openclaw router = APIRouter(tags=["work"]) @@ -33,7 +33,9 @@ def _validate_task_assignee(session: Session, assignee_employee_id: int) -> None if emp.status != "active": raise HTTPException(status_code=400, detail="Cannot assign task to inactive agent") if not emp.notify_enabled: - raise HTTPException(status_code=400, detail="Cannot assign task to agent with notifications disabled") + raise HTTPException( + status_code=400, detail="Cannot assign task to agent with notifications disabled" + ) if not emp.openclaw_session_key: raise HTTPException(status_code=400, detail="Cannot assign task to unprovisioned agent") @@ -47,9 +49,16 @@ def list_tasks(project_id: int | None = None, session: Session = Depends(get_ses @router.post("/tasks", response_model=Task) -def create_task(payload: TaskCreate, background: BackgroundTasks, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)): +def create_task( + payload: TaskCreate, + background: BackgroundTasks, + session: Session = Depends(get_session), + actor_employee_id: int = Depends(get_actor_employee_id), +): if payload.created_by_employee_id is None: - payload = TaskCreate(**{**payload.model_dump(), "created_by_employee_id": actor_employee_id}) + payload = TaskCreate( + **{**payload.model_dump(), "created_by_employee_id": actor_employee_id} + ) if payload.assignee_employee_id is not None: _validate_task_assignee(session, payload.assignee_employee_id) @@ -58,7 +67,9 @@ def create_task(payload: TaskCreate, background: BackgroundTasks, session: Sessi if payload.reviewer_employee_id is None and payload.assignee_employee_id is not None: assignee = session.get(Employee, payload.assignee_employee_id) if assignee is not None and assignee.manager_id is not None: - payload = TaskCreate(**{**payload.model_dump(), "reviewer_employee_id": assignee.manager_id}) + payload = TaskCreate( + **{**payload.model_dump(), "reviewer_employee_id": assignee.manager_id} + ) task = Task(**payload.model_dump()) if task.status not in ALLOWED_STATUSES: @@ -82,18 +93,32 @@ def create_task(payload: TaskCreate, background: BackgroundTasks, session: Sessi raise HTTPException(status_code=409, detail="Task create violates constraints") session.refresh(task) - background.add_task(notify_openclaw, session, NotifyContext(event="task.created", actor_employee_id=actor_employee_id, task=task)) + background.add_task( + notify_openclaw, + session, + NotifyContext(event="task.created", actor_employee_id=actor_employee_id, task=task), + ) # Explicitly return a serializable payload (guards against empty {} responses) return Task.model_validate(task) @router.patch("/tasks/{task_id}", response_model=Task) -def update_task(task_id: int, payload: TaskUpdate, background: BackgroundTasks, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)): +def update_task( + task_id: int, + payload: TaskUpdate, + background: BackgroundTasks, + session: Session = Depends(get_session), + actor_employee_id: int = Depends(get_actor_employee_id), +): task = session.get(Task, task_id) if not task: raise HTTPException(status_code=404, detail="Task not found") - before = {"assignee_employee_id": task.assignee_employee_id, "reviewer_employee_id": task.reviewer_employee_id, "status": task.status} + before = { + "assignee_employee_id": task.assignee_employee_id, + "reviewer_employee_id": task.reviewer_employee_id, + "status": task.status, + } data = payload.model_dump(exclude_unset=True) if "assignee_employee_id" in data and data["assignee_employee_id"] is not None: @@ -108,7 +133,14 @@ def update_task(task_id: int, payload: TaskUpdate, background: BackgroundTasks, try: session.flush() - log_activity(session, actor_employee_id=actor_employee_id, entity_type="task", entity_id=task.id, verb="updated", payload=data) + log_activity( + session, + actor_employee_id=actor_employee_id, + entity_type="task", + entity_id=task.id, + verb="updated", + payload=data, + ) session.commit() except IntegrityError: session.rollback() @@ -119,19 +151,53 @@ def update_task(task_id: int, payload: TaskUpdate, background: BackgroundTasks, # notify based on meaningful changes changed = {} if before.get("assignee_employee_id") != task.assignee_employee_id: - changed["assignee_employee_id"] = {"from": before.get("assignee_employee_id"), "to": task.assignee_employee_id} - background.add_task(notify_openclaw, session, NotifyContext(event="task.assigned", actor_employee_id=actor_employee_id, task=task, changed_fields=changed)) + changed["assignee_employee_id"] = { + "from": before.get("assignee_employee_id"), + "to": task.assignee_employee_id, + } + background.add_task( + notify_openclaw, + session, + NotifyContext( + event="task.assigned", + actor_employee_id=actor_employee_id, + task=task, + changed_fields=changed, + ), + ) if before.get("status") != task.status: changed["status"] = {"from": before.get("status"), "to": task.status} - background.add_task(notify_openclaw, session, NotifyContext(event="status.changed", actor_employee_id=actor_employee_id, task=task, changed_fields=changed)) + background.add_task( + notify_openclaw, + session, + NotifyContext( + event="status.changed", + actor_employee_id=actor_employee_id, + task=task, + changed_fields=changed, + ), + ) if not changed and data: - background.add_task(notify_openclaw, session, NotifyContext(event="task.updated", actor_employee_id=actor_employee_id, task=task, changed_fields=data)) + background.add_task( + notify_openclaw, + session, + NotifyContext( + event="task.updated", + actor_employee_id=actor_employee_id, + task=task, + changed_fields=data, + ), + ) return Task.model_validate(task) @router.delete("/tasks/{task_id}") -def delete_task(task_id: int, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)): +def delete_task( + task_id: int, + session: Session = Depends(get_session), + actor_employee_id: int = Depends(get_actor_employee_id), +): task = session.get(Task, task_id) if not task: raise HTTPException(status_code=404, detail="Task not found") @@ -139,7 +205,13 @@ def delete_task(task_id: int, session: Session = Depends(get_session), actor_emp session.delete(task) try: session.flush() - log_activity(session, actor_employee_id=actor_employee_id, entity_type="task", entity_id=task_id, verb="deleted") + log_activity( + session, + actor_employee_id=actor_employee_id, + entity_type="task", + entity_id=task_id, + verb="deleted", + ) session.commit() except IntegrityError: session.rollback() @@ -150,20 +222,35 @@ def delete_task(task_id: int, session: Session = Depends(get_session), actor_emp @router.get("/task-comments", response_model=list[TaskComment]) def list_task_comments(task_id: int, session: Session = Depends(get_session)): - return session.exec(select(TaskComment).where(TaskComment.task_id == task_id).order_by(TaskComment.id.asc())).all() + return session.exec( + select(TaskComment).where(TaskComment.task_id == task_id).order_by(TaskComment.id.asc()) + ).all() @router.post("/task-comments", response_model=TaskComment) -def create_task_comment(payload: TaskCommentCreate, background: BackgroundTasks, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)): +def create_task_comment( + payload: TaskCommentCreate, + background: BackgroundTasks, + session: Session = Depends(get_session), + actor_employee_id: int = Depends(get_actor_employee_id), +): if payload.author_employee_id is None: - payload = TaskCommentCreate(**{**payload.model_dump(), "author_employee_id": actor_employee_id}) + payload = TaskCommentCreate( + **{**payload.model_dump(), "author_employee_id": actor_employee_id} + ) c = TaskComment(**payload.model_dump()) session.add(c) try: session.flush() - log_activity(session, actor_employee_id=actor_employee_id, entity_type="task", entity_id=c.task_id, verb="commented") + log_activity( + session, + actor_employee_id=actor_employee_id, + entity_type="task", + entity_id=c.task_id, + verb="commented", + ) session.commit() except IntegrityError: session.rollback() @@ -172,5 +259,11 @@ def create_task_comment(payload: TaskCommentCreate, background: BackgroundTasks, session.refresh(c) task = session.get(Task, c.task_id) if task is not None: - background.add_task(notify_openclaw, session, NotifyContext(event="comment.created", actor_employee_id=actor_employee_id, task=task, comment=c)) + background.add_task( + notify_openclaw, + session, + NotifyContext( + event="comment.created", actor_employee_id=actor_employee_id, task=task, comment=c + ), + ) return TaskComment.model_validate(c) diff --git a/backend/app/core/__pycache__/config.cpython-312.pyc b/backend/app/core/__pycache__/config.cpython-312.pyc index 29ad631d1dcf6e941c86e23f0552e463f5ff4b1c..2695f548abf0cb8749b2d3ea767d4064a5ca15d4 100644 GIT binary patch delta 55 zcmZ3=x|NmdG%qg~0}$|U-N-eIQQlKOBR@A)Ke0G5Be5tkPd}hC8OX>DD9X=DO)e>( IyqWP60KR_`tN;K2 delta 39 tcmdnWx|EgcG%qg~0}vQ2-pDnJk>653BR@A)Ke0G5Be5tkZ}MTrPXNzZ3|;^L diff --git a/backend/app/db/__pycache__/session.cpython-312.pyc b/backend/app/db/__pycache__/session.cpython-312.pyc index 0d1077b347bf6e8eddc53bc97a942565b7d514c7..7d7d28362e8b937a41e70d7459061dea9b747208 100644 GIT binary patch delta 57 zcmZ3$zJZZVi4w#B delta 41 vcmdnMzJQ(kG%qg~0}vQ2ZrI43!N_l+pOK%Ns-IY#n2}hNn74TnqZ|_e str: if len(snippet) > 180: snippet = snippet[:177] + "..." snippet = f"\nComment: {snippet}" - return f"New comment on {base}.{snippet}\nWork ONE task only; reply/update in Mission Control." + return ( + f"New comment on {base}.{snippet}\nWork ONE task only; reply/update in Mission Control." + ) if ctx.event == "status.changed": return f"Status changed on {base} → {t.status}.\nWork ONE task only; update Mission Control with next step." diff --git a/backend/app/integrations/openclaw.py b/backend/app/integrations/openclaw.py index 26fba7dd..b5301123 100644 --- a/backend/app/integrations/openclaw.py +++ b/backend/app/integrations/openclaw.py @@ -19,7 +19,14 @@ class OpenClawClient: return None return cls(url, token) - def tools_invoke(self, tool: str, args: dict[str, Any], *, session_key: str | None = None, timeout_s: float = 5.0) -> dict[str, Any]: + def tools_invoke( + self, + tool: str, + args: dict[str, Any], + *, + session_key: str | None = None, + timeout_s: float = 5.0, + ) -> dict[str, Any]: payload: dict[str, Any] = {"tool": tool, "args": args} if session_key is not None: payload["sessionKey"] = session_key diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py index 9bd9a65b..85f0fd7a 100644 --- a/backend/app/models/__init__.py +++ b/backend/app/models/__init__.py @@ -1,5 +1,5 @@ from app.models.activity import Activity -from app.models.org import Department, Team, Employee +from app.models.org import Department, Employee, Team from app.models.projects import Project, ProjectMember from app.models.work import Task, TaskComment diff --git a/backend/app/models/org.py b/backend/app/models/org.py index 81a07c49..826841da 100644 --- a/backend/app/models/org.py +++ b/backend/app/models/org.py @@ -1,7 +1,5 @@ from __future__ import annotations -from typing import Optional - from sqlmodel import Field, SQLModel diff --git a/backend/pyproject.toml b/backend/pyproject.toml new file mode 100644 index 00000000..02f5b30a --- /dev/null +++ b/backend/pyproject.toml @@ -0,0 +1,9 @@ +[tool.black] +line-length = 100 +target-version = ["py312"] +extend-exclude = '(\.venv|alembic/versions)' + +[tool.isort] +profile = "black" +line_length = 100 +skip = [".venv", "alembic/versions"] diff --git a/backend/requirements-dev.txt b/backend/requirements-dev.txt new file mode 100644 index 00000000..4d452174 --- /dev/null +++ b/backend/requirements-dev.txt @@ -0,0 +1,4 @@ +black==24.10.0 +isort==5.13.2 +flake8==7.1.1 +pre-commit==4.1.0 diff --git a/backend/scripts/lint.sh b/backend/scripts/lint.sh new file mode 100755 index 00000000..36a08089 --- /dev/null +++ b/backend/scripts/lint.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +set -euo pipefail +cd "$(dirname "$0")/.." + +. .venv/bin/activate + +python -m black . +python -m isort . +python -m flake8 . diff --git a/frontend/public/file.svg b/frontend/public/file.svg index 004145cd..16fe3d3a 100644 --- a/frontend/public/file.svg +++ b/frontend/public/file.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/frontend/public/globe.svg b/frontend/public/globe.svg index 567f17b0..c7215fe0 100644 --- a/frontend/public/globe.svg +++ b/frontend/public/globe.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/frontend/public/next.svg b/frontend/public/next.svg index 5174b28c..5bb00d40 100644 --- a/frontend/public/next.svg +++ b/frontend/public/next.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/frontend/public/vercel.svg b/frontend/public/vercel.svg index 77053960..52151572 100644 --- a/frontend/public/vercel.svg +++ b/frontend/public/vercel.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/frontend/public/window.svg b/frontend/public/window.svg index b2b2a44f..d05e7a1b 100644 --- a/frontend/public/window.svg +++ b/frontend/public/window.svg @@ -1 +1 @@ - \ No newline at end of file +