From e3e45a23f2b69bc94b71886b833494075aead786 Mon Sep 17 00:00:00 2001 From: laptran Date: Sat, 13 Jun 2026 21:30:08 -0400 Subject: [PATCH] Fix dashboard task directory resolution for projects Dashboard was hardcoded to read tasks from .automaton/tasks/, but the Orchestrator creates tasks at tasks/ (project root). Added _find_tasks_dir() that checks both locations and _get_review_path() that reads REVIEW.md from whichever location the task lives in. Priority: .automaton/tasks/ first, then tasks/ as fallback. This handles framework mode (tasks in .automaton/), project mode (tasks at root), and symlinked projects. --- .../core/__pycache__/task.cpython-314.pyc | Bin 16541 -> 16596 bytes .../ui/__pycache__/app.cpython-314.pyc | Bin 20996 -> 23073 bytes automaton/dashboard/ui/app.py | 32 ++++++++++++++---- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/automaton/dashboard/core/__pycache__/task.cpython-314.pyc b/automaton/dashboard/core/__pycache__/task.cpython-314.pyc index 36ba5dddbb79c185e54060e32e5fc451eda07cae..1a3b194d24c5cd94285bceae6a06971988a285a0 100644 GIT binary patch delta 2361 zcma)7X-r&I6rMZ7Y;40;n9@!Q1Iz})PNgM*p+FT|qyjb)m+8n1b#O}Y&ZAo6LTN>c z(H|V6jTJLSqta@$c`iS+BH~7E3{{dgkN&9svHevoX`0knJ?B0QG}^*T-uKQu=e&E) zJ=-1I&t~_tq{8H+cnv<8XT3d_jwd8)4E^mU#z~QHJ2- zF5=b-0F&9nYN5jXm%&Gjde{)Z)7wO(iC_zX0l}^1w-YV4(IRCZQ1@wusrWZ5J4kj9 z!48701P+321T6?lZMPD$6<$p!Wv%iL3EwfV4>^i~hopYqJI**caC3|DMAA{l>Y>MS z%yU0#;t%BK&3`~4_d{LEiLx%z)APHDBMz8HR6aogK?Ff~>QGPD4&Kxu7a8G?A}nWO zF7?SQVM4dWgxEa-y=ezlzR~LR|Jg|O5Z+6Fql`RqYZblJ-xmofx^Sy0=qHGyEAZ5Lr zHbQzjz8gRM5Wz64wSKnpJ^r@J347_kCkP^NV)fX{LpPZHn|%ZQcN+qFL8YOX16&Gs z2fC!FqT3acctn1F%^b7)P{!*Kl!Wd*kzk1X`vTI#YVMAZYzT&J!yS!Cy3L|0xt`cm zI!atH+$D)VQsN@vsKiBCQ?sn39Od9TBTIRgw1uT{qK05#y*=;_kz`Xb_3=G@AujFX zy~GpgTMP_^nFGNq1Y-otbY3UvbjsJ)$1&Cky4;t7KH|g(9zbv#)B`?64ACIM!zNnN z#Beh)21JY47X|>ynh~DEH(HD^pt-84W3E34-U|M%se?arv)AK-J5kz2L+Ia!Oz7~P zACfEWZ);&&fyZLr!8Rf*n61&Q)wIRKFZo&EDaeMs1?7;2?}dVUM;&&!ke@Q@$cF0< zlTp=77Mec6vjToCmK%0bRDFj0lMVN}TNpTf&+eolO&F?i6^z z=`13LI^1!hR`_OI@CE`(60atpV&kIhs$kApmmY;AAid=FQQ z&8>n^r*H#Qy2=fMv_n4B)>z@7E7K68JNrFA0k%r0?V&9_maLA_myZT%45`w;K*5c zsI1{wNxJ@f|B|WVC3nX!th~^g^P91Tp6JG>>*=5K3s@4bixeKdD6poAw{t>~) zB-Qc>c&xlie+m~D;duGan|(+r=EbA@C)(7+x3WDie_L@`Uph%@dbMO@NG~})MTb*1 z7W2TX;ljr5q!*EiKD{RN;l52R%m(9|s@%f$uZTNE@HGKNpNm9Mq&T_78z8a9W)KtX z8}QV$IDbSg-cytqbMj=ZT)8`*Lk_qSl delta 2319 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 delta 5611 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 DQKmKt diff --git a/automaton/dashboard/ui/app.py b/automaton/dashboard/ui/app.py index 1722bf8..61e1900 100644 --- a/automaton/dashboard/ui/app.py +++ b/automaton/dashboard/ui/app.py @@ -104,13 +104,25 @@ class DashboardHandler(SimpleHTTPRequestHandler): except Exception: self._send_error(500, "Internal error") + @staticmethod + def _find_tasks_dir(project_root: Path) -> Path | None: + """Return the tasks directory. Checks both standard locations.""" + candidates = [ + project_root / ".automaton" / "tasks", + project_root / "tasks", + ] + for p in candidates: + if p.exists() and p.is_dir(): + return p + return candidates[0] + def _serve_tasks(self): project_root = find_automaton_root() if not project_root: tasks = [] else: - tasks_dir = project_root / ".automaton" / "tasks" - tasks = discover_tasks(tasks_dir) + tasks_dir = self._find_tasks_dir(project_root) + tasks = discover_tasks(tasks_dir) if tasks_dir else [] tasks_data = [ { @@ -168,7 +180,8 @@ class DashboardHandler(SimpleHTTPRequestHandler): if not project_root: self._send_error(404, "Not in automaton project") return - tasks = discover_tasks(project_root / "tasks") + tasks_dir = self._find_tasks_dir(project_root) + tasks = discover_tasks(tasks_dir) if tasks_dir else [] task = next((t for t in tasks if t.name == task_name), None) if not task: self._send_error(404, "Task not found") @@ -198,7 +211,14 @@ class DashboardHandler(SimpleHTTPRequestHandler): project_root = find_automaton_root() if not project_root or not self._validate_task_name(task_name): return None - return project_root / ".automaton" / "tasks" / task_name / self.REVIEW_FILE + candidates = [ + project_root / ".automaton" / "tasks" / task_name / self.REVIEW_FILE, + project_root / "tasks" / task_name / self.REVIEW_FILE, + ] + for p in candidates: + if p.exists(): + return p + return candidates[0] def _get_review_status(self, task_name: str) -> dict: review_path = self._get_review_path(task_name) @@ -256,8 +276,8 @@ class DashboardHandler(SimpleHTTPRequestHandler): if not project_root: self._send_json({"pending": 0, "approved": 0, "changes_requested": 0}) return - tasks_dir = project_root / ".automaton" / "tasks" - if not tasks_dir.exists(): + tasks_dir = self._find_tasks_dir(project_root) + if not tasks_dir or not tasks_dir.exists(): self._send_json({"pending": 0, "approved": 0, "changes_requested": 0}) return counts = {"pending": 0, "approved": 0, "changes_requested": 0}