# Arch Design Dashboard — Full Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Implement the complete Arch Design Dashboard — a web app for visualizing and managing architecture design documents, from empty scaffold to fully working application. **Architecture:** DDD-layered backend (FastAPI) with 6 modules (design, project, scanner, graph, editor, impl_tracker), Vue 3 SPA frontend with 3 modules (project, graph, editor), D3.js graph visualization. No database — design files are single source of truth, projects persisted as JSON. **Tech Stack:** Python 3.12 / FastAPI / Uvicorn / uv (backend), Vue 3 / TypeScript / Vite / Pinia / D3.js / Axios (frontend), Docker Compose / Nginx (deployment) **Spec:** `docs/superpowers/specs/2026-03-23-full-implementation-design.md` --- ## File Map ### Files to Create (new) ``` backend/pyproject.toml backend/.python-version backend/tests/__init__.py backend/tests/test_design_entities.py backend/tests/test_design_services.py backend/tests/test_project.py backend/tests/test_scanner_parsers.py backend/tests/test_scanner_service.py backend/tests/test_graph_service.py backend/tests/test_editor_service.py backend/tests/test_impl_tracker.py backend/tests/test_api_project.py backend/tests/test_api_scanner.py backend/tests/test_api_graph.py backend/tests/test_api_editor.py backend/tests/test_api_impl_tracker.py backend/app/modules/editor/infrastructure/file_io.py backend/app/modules/impl_tracker/infrastructure/code_scanner.py backend/app/modules/impl_tracker/infrastructure/llm_client.py frontend/package.json frontend/vite.config.ts frontend/tsconfig.json frontend/tsconfig.node.json frontend/index.html frontend/src/style.css docker-compose.yml backend/Dockerfile frontend/Dockerfile frontend/nginx.conf ``` ### Files to Populate (exist as 0-byte stubs) All 86 files under `backend/app/` and `frontend/src/` listed in the scaffold. --- ## Task 1: Backend Build Configuration **Files:** - Create: `backend/pyproject.toml` - Create: `backend/.python-version` - Create: `backend/tests/__init__.py` - Create: `backend/tests/conftest.py` **Note on `__init__.py` files:** The scaffold contains ~25 empty `__init__.py` files across all modules. These remain as empty files unless a task explicitly specifies content for them. They are included in `git add` via directory-level adds. - [ ] **Step 1: Create pyproject.toml** ```toml [project] name = "arch-design-dashboard" version = "0.1.0" description = "Architecture Design Dashboard Backend" requires-python = ">=3.12" dependencies = [ "fastapi>=0.115.0", "uvicorn[standard]>=0.30.0", "pyyaml>=6.0", "python-multipart>=0.0.9", ] [tool.pytest.ini_options] testpaths = ["tests"] pythonpath = ["."] [dependency-groups] dev = [ "pytest>=8.0", "httpx>=0.27.0", ] ``` - [ ] **Step 2: Create .python-version** ``` 3.12 ``` - [ ] **Step 3: Create tests/__init__.py (empty)** - [ ] **Step 4: Create tests/conftest.py (shared fixtures)** ```python import os from pathlib import Path import pytest from fastapi.testclient import TestClient @pytest.fixture def tmp_registry(tmp_path: Path): """Set REGISTRY_PATH env var to a temp file for test isolation.""" registry = str(tmp_path / "projects.json") os.environ["REGISTRY_PATH"] = registry yield registry os.environ.pop("REGISTRY_PATH", None) @pytest.fixture def client(tmp_registry): """Create a test client with isolated registry.""" from app.main import create_app app = create_app() return TestClient(app) @pytest.fixture def design_dir(tmp_path: Path) -> Path: """Create a minimal design directory for testing.""" d = tmp_path / "design" d.mkdir() return d ``` - [ ] **Step 4: Install dependencies and verify** Run: `cd /workspace/arch-design-agent-skill-dashboard/backend && uv sync` Expected: Dependencies installed successfully. - [ ] **Step 5: Verify pytest runs (no tests yet)** Run: `cd /workspace/arch-design-agent-skill-dashboard/backend && uv run pytest --co -q` Expected: "no tests ran" or similar (no errors). - [ ] **Step 6: Commit** ```bash git add backend/pyproject.toml backend/.python-version backend/tests/__init__.py backend/uv.lock git commit -m "build: add backend pyproject.toml and test infrastructure" ``` --- ## Task 2: Shared Kernel — Exceptions **Files:** - Modify: `backend/app/shared/__init__.py` - Modify: `backend/app/shared/kernel/__init__.py` - Modify: `backend/app/shared/kernel/exceptions.py` - [ ] **Step 1: Write exceptions.py** ```python class NotFoundError(Exception): def __init__(self, entity: str, entity_id: str) -> None: self.entity = entity self.entity_id = entity_id super().__init__(f"{entity} not found: {entity_id}") class ValidationError(Exception): def __init__(self, message: str) -> None: self.message = message super().__init__(message) class FileSystemError(Exception): def __init__(self, path: str, message: str) -> None: self.path = path self.message = message super().__init__(f"Filesystem error at {path}: {message}") ``` - [ ] **Step 2: Verify import works** Run: `cd /workspace/arch-design-agent-skill-dashboard/backend && uv run python -c "from app.shared.kernel.exceptions import NotFoundError, ValidationError, FileSystemError; print('OK')"` Expected: `OK` - [ ] **Step 3: Commit** ```bash git add backend/app/shared/ git commit -m "feat(shared): add kernel exceptions" ``` --- ## Task 3: Shared Infrastructure — Config & Filesystem **Files:** - Modify: `backend/app/shared/infrastructure/__init__.py` - Modify: `backend/app/shared/infrastructure/config.py` - Modify: `backend/app/shared/infrastructure/filesystem.py` - [ ] **Step 1: Write config.py** ```python from dataclasses import dataclass, field from pathlib import Path @dataclass class Settings: registry_path: Path = field( default_factory=lambda: Path.home() / ".arch-design-dashboard" / "projects.json" ) ``` - [ ] **Step 2: Write filesystem.py** ```python from pathlib import Path from app.shared.kernel.exceptions import FileSystemError def read_text(path: Path) -> str: try: return path.read_text(encoding="utf-8") except OSError as e: raise FileSystemError(str(path), str(e)) from e def write_text(path: Path, content: str) -> None: try: path.parent.mkdir(parents=True, exist_ok=True) path.write_text(content, encoding="utf-8") except OSError as e: raise FileSystemError(str(path), str(e)) from e def list_files(directory: Path, extensions: list[str] | None = None) -> list[Path]: if not directory.is_dir(): raise FileSystemError(str(directory), "Not a directory") files: list[Path] = [] for p in sorted(directory.rglob("*")): if p.is_file(): if extensions is None or p.suffix in extensions: files.append(p) return files def file_exists(path: Path) -> bool: return path.is_file() ``` - [ ] **Step 3: Verify imports** Run: `cd /workspace/arch-design-agent-skill-dashboard/backend && uv run python -c "from app.shared.infrastructure.config import Settings; from app.shared.infrastructure.filesystem import read_text, write_text, list_files, file_exists; print('OK')"` Expected: `OK` - [ ] **Step 4: Commit** ```bash git add backend/app/shared/ git commit -m "feat(shared): add config and filesystem utilities" ``` --- ## Task 4: MOD-DESIGN Domain — Value Objects **Files:** - Modify: `backend/app/modules/design/__init__.py` - Modify: `backend/app/modules/design/domain/__init__.py` - Modify: `backend/app/modules/design/domain/value_objects.py` - [ ] **Step 1: Write the test** Create: `backend/tests/test_design_entities.py` ```python from app.modules.design.domain.value_objects import ( ArchitectureLayer, FileStatus, ModuleLayer, ) def test_file_status_values(): assert FileStatus.OK == "ok" assert FileStatus.SPARSE == "sparse" assert FileStatus.MISSING == "missing" assert FileStatus.TEMPLATE_RESIDUE == "template-residue" assert FileStatus.PLACEHOLDER_HEAVY == "placeholder-heavy" def test_architecture_layer_values(): assert ArchitectureLayer.BUSINESS == "business" assert ArchitectureLayer.APPLICATION == "application" assert ArchitectureLayer.DATA == "data" assert ArchitectureLayer.TECHNOLOGY == "technology" def test_module_layer_values(): assert ModuleLayer.DOMAIN == "domain" assert ModuleLayer.APPLICATION == "application" assert ModuleLayer.INFRASTRUCTURE == "infrastructure" assert ModuleLayer.INTERFACES == "interfaces" ``` - [ ] **Step 2: Run test — should fail** Run: `cd /workspace/arch-design-agent-skill-dashboard/backend && uv run pytest tests/test_design_entities.py -v` Expected: FAIL (empty module) - [ ] **Step 3: Write value_objects.py** ```python from enum import Enum class FileStatus(str, Enum): OK = "ok" SPARSE = "sparse" MISSING = "missing" TEMPLATE_RESIDUE = "template-residue" PLACEHOLDER_HEAVY = "placeholder-heavy" class ArchitectureLayer(str, Enum): BUSINESS = "business" APPLICATION = "application" DATA = "data" TECHNOLOGY = "technology" class ModuleLayer(str, Enum): DOMAIN = "domain" APPLICATION = "application" INFRASTRUCTURE = "infrastructure" INTERFACES = "interfaces" ``` - [ ] **Step 4: Run test — should pass** Run: `cd /workspace/arch-design-agent-skill-dashboard/backend && uv run pytest tests/test_design_entities.py -v` Expected: 3 passed - [ ] **Step 5: Commit** ```bash git add backend/app/modules/design/ backend/tests/test_design_entities.py git commit -m "feat(design): add value objects — FileStatus, ArchitectureLayer, ModuleLayer" ``` --- ## Task 5: MOD-DESIGN Domain — Entities (31 dataclasses) **Files:** - Modify: `backend/app/modules/design/domain/entities.py` - Modify: `backend/tests/test_design_entities.py` (append tests) - [ ] **Step 1: Add entity creation tests** Append to `backend/tests/test_design_entities.py`: ```python from app.modules.design.domain.entities import ( ADR, ApiContract, Capability, ChangeLogEntry, CodebaseAlignment, DataFlow, DataSecurity, DesignDocument, Domain, DomainEntity, DomainModule, Entity, Environment, ExternalSystem, Integration, Module, ModuleBoundaryRule, OperationalBaseline, ReleasePlan, RuntimeComponent, RuntimeTopology, Scenario, ScopeAndGoals, SharedTerm, SolutionLayer, SystemContext, TechSelection, TraceabilityLink, UbiquitousTerm, UserJourney, ValueFlow, ) def test_capability_creation(): cap = Capability( capability_id="CAP-01", name="test", description="desc", priority="must", phase="MVP", related_value_flows=["VF-01"], ) assert cap.capability_id == "CAP-01" assert cap.related_value_flows == ["VF-01"] def test_module_creation(): mod = Module( module_id="MOD-01", name="test", layer="backend", description="desc", phase="MVP", depends_on=["MOD-02"], capabilities=["CAP-01"], ) assert mod.depends_on == ["MOD-02"] def test_traceability_link_list_fields(): tl = TraceabilityLink( trace_id="TR-01", capability_id="CAP-01", module_id="MOD-01", entity_ids=["ENT-01", "ENT-02"], value_flow_ids=["VF-01"], notes="test", ) assert len(tl.entity_ids) == 2 def test_design_document_list_fields(): dd = DesignDocument( doc_id="DOC-01", title="test", version="0.1", status="draft", owners=["owner1"], upstream=["a.md"], downstream=["b.md"], file_path="test.md", ) assert dd.owners == ["owner1"] def test_all_31_entities_importable(): """Verify all 31 entity classes can be imported.""" entities = [ Capability, ValueFlow, UserJourney, ScopeAndGoals, Module, Integration, ExternalSystem, ApiContract, CodebaseAlignment, ModuleBoundaryRule, SystemContext, SolutionLayer, Entity, DataFlow, DataSecurity, TechSelection, RuntimeComponent, RuntimeTopology, Environment, OperationalBaseline, ReleasePlan, TraceabilityLink, ChangeLogEntry, ADR, DesignDocument, Domain, UbiquitousTerm, SharedTerm, Scenario, DomainModule, DomainEntity, ] assert len(entities) == 31 ``` - [ ] **Step 2: Run tests — should fail** Run: `cd /workspace/arch-design-agent-skill-dashboard/backend && uv run pytest tests/test_design_entities.py -v` Expected: FAIL (empty entities.py) - [ ] **Step 3: Write entities.py with all 31 dataclasses** Write `backend/app/modules/design/domain/entities.py` with all 31 `@dataclass` definitions exactly as specified in the spec (Section 3.2). Every `list[str]` field that comes from CSV space-delimited values must use `list[str]` type. Dataclasses to implement (grouped by architecture layer): - **Business:** Capability, ValueFlow, UserJourney, ScopeAndGoals - **Application:** Module, Integration, ExternalSystem, ApiContract, CodebaseAlignment, ModuleBoundaryRule, SystemContext, SolutionLayer - **Data:** Entity, DataFlow, DataSecurity - **Technology:** TechSelection, RuntimeComponent, RuntimeTopology, Environment, OperationalBaseline, ReleasePlan - **Cross-layer:** TraceabilityLink, ChangeLogEntry, ADR, DesignDocument - **Domain:** Domain, UbiquitousTerm, SharedTerm, Scenario, DomainModule, DomainEntity Refer to spec Section 3.2 for exact field definitions of each class. - [ ] **Step 4: Run tests — should pass** Run: `cd /workspace/arch-design-agent-skill-dashboard/backend && uv run pytest tests/test_design_entities.py -v` Expected: All passed - [ ] **Step 5: Commit** ```bash git add backend/app/modules/design/domain/entities.py backend/tests/test_design_entities.py git commit -m "feat(design): add 31 design entity dataclasses" ``` --- ## Task 6: MOD-DESIGN Domain — Validation Service **Files:** - Modify: `backend/app/modules/design/domain/services.py` - Create: `backend/tests/test_design_services.py` - [ ] **Step 1: Write the tests** ```python import pytest from app.modules.design.domain.entities import ( Capability, Entity, Module, TraceabilityLink, ) from app.modules.design.domain.services import DesignValidationService from app.modules.design.domain.value_objects import FileStatus class TestFileStatusDetermination: def test_empty_content_is_missing(self): assert DesignValidationService.determine_file_status("", "test.csv") == FileStatus.MISSING def test_csv_header_only_is_sparse(self): assert DesignValidationService.determine_file_status("id,name\n", "test.csv") == FileStatus.SPARSE def test_csv_with_data_is_ok(self): content = "id,name\n1,foo\n2,bar\n" assert DesignValidationService.determine_file_status(content, "test.csv") == FileStatus.OK def test_md_too_short_is_sparse(self): assert DesignValidationService.determine_file_status("# Title\n\nShort.\n", "test.md") == FileStatus.SPARSE def test_template_residue_detected(self): content = "id,name\nTODO,\nreal,data\n" assert DesignValidationService.determine_file_status(content, "test.csv") == FileStatus.TEMPLATE_RESIDUE def test_placeholder_heavy(self): content = "id,name,desc\nTODO,TODO,TODO\nTODO,TODO,TODO\n" assert DesignValidationService.determine_file_status(content, "test.csv") == FileStatus.PLACEHOLDER_HEAVY def test_ok_md_file(self): content = "---\ndoc_id: DOC-01\ntitle: Test\n---\n\n# Title\n\nThis is a real document with enough content.\nLine 4.\nLine 5.\nLine 6.\n" assert DesignValidationService.determine_file_status(content, "test.md") == FileStatus.OK class TestConstraintValidation: def _make_cap(self, cap_id: str) -> Capability: return Capability(cap_id, "n", "d", "must", "MVP", []) def _make_mod(self, mod_id: str) -> Module: return Module(mod_id, "n", "backend", "d", "MVP", [], []) def _make_ent(self, ent_id: str, owner: str) -> Entity: return Entity(ent_id, "n", "d", owner, "d", "MVP", "f.csv") def _make_link(self, trace_id: str, cap: str, mod: str, ents: list[str]) -> TraceabilityLink: return TraceabilityLink(trace_id, cap, mod, ents, [], "") def test_capability_without_module_link_is_violation(self): caps = [self._make_cap("CAP-01")] links = [] # no link references CAP-01 violations = DesignValidationService.check_capability_module_linkage(caps, links) assert len(violations) == 1 def test_entity_without_owner_is_violation(self): entities = [self._make_ent("ENT-01", "")] violations = DesignValidationService.check_entity_owner(entities) assert len(violations) == 1 def test_valid_traceability_passes(self): caps = [self._make_cap("CAP-01")] mods = [self._make_mod("MOD-01")] ents = [self._make_ent("ENT-01", "MOD-01")] links = [self._make_link("TR-01", "CAP-01", "MOD-01", ["ENT-01"])] violations = DesignValidationService.check_traceability_references(links, caps, mods, ents) assert len(violations) == 0 def test_broken_traceability_reference_is_violation(self): caps = [self._make_cap("CAP-01")] mods = [self._make_mod("MOD-01")] ents = [self._make_ent("ENT-01", "MOD-01")] links = [self._make_link("TR-01", "CAP-MISSING", "MOD-01", ["ENT-01"])] violations = DesignValidationService.check_traceability_references(links, caps, mods, ents) assert len(violations) >= 1 ``` - [ ] **Step 2: Run tests — should fail** Run: `cd /workspace/arch-design-agent-skill-dashboard/backend && uv run pytest tests/test_design_services.py -v` Expected: FAIL - [ ] **Step 3: Implement services.py** ```python from dataclasses import dataclass from app.modules.design.domain.entities import ( Capability, Entity, Module, TraceabilityLink, ) from app.modules.design.domain.value_objects import FileStatus TEMPLATE_MARKERS = ["TODO", "EXAMPLE", " FileStatus: if not content or not content.strip(): return FileStatus.MISSING lines = [l for l in content.strip().splitlines() if l.strip()] is_csv = file_path.endswith(".csv") # Sparse check if is_csv and len(lines) < 2: return FileStatus.SPARSE if not is_csv and len(lines) < 5: return FileStatus.SPARSE # Count placeholder tokens total_cells = 0 placeholder_cells = 0 for line in lines: cells = line.split(",") if is_csv else [line] for cell in cells: total_cells += 1 if any(m.lower() in cell.lower() for m in TEMPLATE_MARKERS): placeholder_cells += 1 # Template residue: any marker present if placeholder_cells > 0 and placeholder_cells / total_cells <= 0.3: return FileStatus.TEMPLATE_RESIDUE # Placeholder heavy: >30% if total_cells > 0 and placeholder_cells / total_cells > 0.3: return FileStatus.PLACEHOLDER_HEAVY return FileStatus.OK @staticmethod def check_capability_module_linkage( capabilities: list[Capability], traceability_links: list[TraceabilityLink], ) -> list[ConstraintViolation]: linked_caps = {link.capability_id for link in traceability_links} return [ ConstraintViolation( rule="capability_module_linkage", entity_id=cap.capability_id, message=f"Capability {cap.capability_id} has no TraceabilityLink to any module", ) for cap in capabilities if cap.capability_id not in linked_caps ] @staticmethod def check_entity_owner(entities: list[Entity]) -> list[ConstraintViolation]: return [ ConstraintViolation( rule="entity_owner", entity_id=ent.entity_id, message=f"Entity {ent.entity_id} has no owner module", ) for ent in entities if not ent.owner_module ] @staticmethod def check_traceability_references( links: list[TraceabilityLink], capabilities: list[Capability], modules: list[Module], entities: list[Entity], ) -> list[ConstraintViolation]: cap_ids = {c.capability_id for c in capabilities} mod_ids = {m.module_id for m in modules} ent_ids = {e.entity_id for e in entities} violations: list[ConstraintViolation] = [] for link in links: if link.capability_id not in cap_ids: violations.append(ConstraintViolation( "traceability_ref", link.trace_id, f"References unknown capability {link.capability_id}", )) if link.module_id not in mod_ids: violations.append(ConstraintViolation( "traceability_ref", link.trace_id, f"References unknown module {link.module_id}", )) for eid in link.entity_ids: if eid not in ent_ids: violations.append(ConstraintViolation( "traceability_ref", link.trace_id, f"References unknown entity {eid}", )) return violations @classmethod def validate_all( cls, capabilities: list[Capability], modules: list[Module], entities: list[Entity], traceability_links: list[TraceabilityLink], ) -> list[ConstraintViolation]: violations: list[ConstraintViolation] = [] violations.extend(cls.check_capability_module_linkage(capabilities, traceability_links)) violations.extend(cls.check_entity_owner(entities)) violations.extend(cls.check_traceability_references( traceability_links, capabilities, modules, entities, )) return violations ``` - [ ] **Step 4: Run tests — should pass** Run: `cd /workspace/arch-design-agent-skill-dashboard/backend && uv run pytest tests/test_design_services.py -v` Expected: All passed - [ ] **Step 5: Commit** ```bash git add backend/app/modules/design/domain/services.py backend/tests/test_design_services.py git commit -m "feat(design): add DesignValidationService — file status detection and constraint rules" ``` --- ## Task 7: MOD-PROJECT Domain **Files:** - Modify: `backend/app/modules/project/domain/entities.py` - Modify: `backend/app/modules/project/domain/repositories.py` - Modify: `backend/app/modules/project/__init__.py` - Modify: `backend/app/modules/project/domain/__init__.py` - [ ] **Step 1: Write entities.py** ```python from dataclasses import dataclass from datetime import datetime @dataclass class Project: id: str name: str design_dir: str code_dir: str | None created_at: datetime ``` - [ ] **Step 2: Write repositories.py** ```python from abc import ABC, abstractmethod from app.modules.project.domain.entities import Project class ProjectRepository(ABC): @abstractmethod def list_all(self) -> list[Project]: ... @abstractmethod def get_by_id(self, project_id: str) -> Project | None: ... @abstractmethod def save(self, project: Project) -> None: ... @abstractmethod def delete(self, project_id: str) -> None: ... ``` - [ ] **Step 3: Verify import** Run: `cd /workspace/arch-design-agent-skill-dashboard/backend && uv run python -c "from app.modules.project.domain.entities import Project; from app.modules.project.domain.repositories import ProjectRepository; print('OK')"` Expected: `OK` - [ ] **Step 4: Commit** ```bash git add backend/app/modules/project/ git commit -m "feat(project): add Project entity and ProjectRepository interface" ``` --- ## Task 8: MOD-PROJECT Infrastructure — JsonProjectRepository **Files:** - Modify: `backend/app/modules/project/infrastructure/__init__.py` - Modify: `backend/app/modules/project/infrastructure/json_repository.py` - Create: `backend/tests/test_project.py` - [ ] **Step 1: Write the test** ```python import json from pathlib import Path import pytest from app.modules.project.domain.entities import Project from app.modules.project.infrastructure.json_repository import JsonProjectRepository @pytest.fixture def repo(tmp_path: Path) -> JsonProjectRepository: return JsonProjectRepository(tmp_path / "projects.json") def test_empty_repo_returns_empty_list(repo: JsonProjectRepository): assert repo.list_all() == [] def test_save_and_get(repo: JsonProjectRepository): from datetime import datetime p = Project(id="id1", name="test", design_dir="/tmp/d", code_dir=None, created_at=datetime(2026, 1, 1)) repo.save(p) assert repo.get_by_id("id1") is not None assert repo.get_by_id("id1").name == "test" def test_list_all(repo: JsonProjectRepository): from datetime import datetime p1 = Project(id="id1", name="a", design_dir="/d1", code_dir=None, created_at=datetime(2026, 1, 1)) p2 = Project(id="id2", name="b", design_dir="/d2", code_dir=None, created_at=datetime(2026, 1, 2)) repo.save(p1) repo.save(p2) assert len(repo.list_all()) == 2 def test_delete(repo: JsonProjectRepository): from datetime import datetime p = Project(id="id1", name="test", design_dir="/d", code_dir=None, created_at=datetime(2026, 1, 1)) repo.save(p) repo.delete("id1") assert repo.get_by_id("id1") is None def test_get_nonexistent_returns_none(repo: JsonProjectRepository): assert repo.get_by_id("nope") is None ``` - [ ] **Step 2: Run test — should fail** Run: `cd /workspace/arch-design-agent-skill-dashboard/backend && uv run pytest tests/test_project.py -v` Expected: FAIL - [ ] **Step 3: Implement json_repository.py** ```python import json from datetime import datetime from pathlib import Path from app.modules.project.domain.entities import Project from app.modules.project.domain.repositories import ProjectRepository class JsonProjectRepository(ProjectRepository): def __init__(self, path: Path) -> None: self._path = path def _load(self) -> list[dict]: if not self._path.exists(): return [] return json.loads(self._path.read_text(encoding="utf-8")) def _save(self, data: list[dict]) -> None: self._path.parent.mkdir(parents=True, exist_ok=True) self._path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8") @staticmethod def _to_dict(p: Project) -> dict: return { "id": p.id, "name": p.name, "design_dir": p.design_dir, "code_dir": p.code_dir, "created_at": p.created_at.isoformat(), } @staticmethod def _from_dict(d: dict) -> Project: return Project( id=d["id"], name=d["name"], design_dir=d["design_dir"], code_dir=d.get("code_dir"), created_at=datetime.fromisoformat(d["created_at"]), ) def list_all(self) -> list[Project]: return [self._from_dict(d) for d in self._load()] def get_by_id(self, project_id: str) -> Project | None: for d in self._load(): if d["id"] == project_id: return self._from_dict(d) return None def save(self, project: Project) -> None: data = self._load() data = [d for d in data if d["id"] != project.id] data.append(self._to_dict(project)) self._save(data) def delete(self, project_id: str) -> None: data = [d for d in self._load() if d["id"] != project_id] self._save(data) ``` - [ ] **Step 4: Run tests — should pass** Run: `cd /workspace/arch-design-agent-skill-dashboard/backend && uv run pytest tests/test_project.py -v` Expected: All passed - [ ] **Step 5: Commit** ```bash git add backend/app/modules/project/infrastructure/ backend/tests/test_project.py git commit -m "feat(project): add JsonProjectRepository with JSON file persistence" ``` --- ## Task 9: MOD-PROJECT Application — ProjectService **Files:** - Modify: `backend/app/modules/project/application/__init__.py` - Modify: `backend/app/modules/project/application/services.py` - Modify: `backend/tests/test_project.py` (append) - [ ] **Step 1: Add service tests** Append to `backend/tests/test_project.py`: ```python from app.modules.project.application.services import ProjectService from app.shared.kernel.exceptions import NotFoundError, ValidationError @pytest.fixture def service(tmp_path: Path) -> ProjectService: repo = JsonProjectRepository(tmp_path / "projects.json") return ProjectService(repo) def test_create_project_validates_design_dir(service: ProjectService, tmp_path: Path): design_dir = tmp_path / "design" design_dir.mkdir() project = service.create_project("test", str(design_dir)) assert project.name == "test" assert project.id # UUID generated def test_create_project_rejects_missing_dir(service: ProjectService): with pytest.raises(ValidationError): service.create_project("test", "/nonexistent/path") def test_get_project_not_found(service: ProjectService): with pytest.raises(NotFoundError): service.get_project("nonexistent") def test_delete_project(service: ProjectService, tmp_path: Path): design_dir = tmp_path / "design" design_dir.mkdir() p = service.create_project("test", str(design_dir)) service.delete_project(p.id) with pytest.raises(NotFoundError): service.get_project(p.id) ``` - [ ] **Step 2: Run tests — should fail** Run: `cd /workspace/arch-design-agent-skill-dashboard/backend && uv run pytest tests/test_project.py -v -k "service"` Expected: FAIL - [ ] **Step 3: Implement services.py** ```python import uuid from datetime import datetime, timezone from pathlib import Path from app.modules.project.domain.entities import Project from app.modules.project.domain.repositories import ProjectRepository from app.shared.kernel.exceptions import NotFoundError, ValidationError class ProjectService: def __init__(self, repository: ProjectRepository) -> None: self._repo = repository def list_projects(self) -> list[Project]: return self._repo.list_all() def create_project( self, name: str, design_dir: str, code_dir: str | None = None, ) -> Project: if not Path(design_dir).is_dir(): raise ValidationError(f"Design directory does not exist: {design_dir}") project = Project( id=str(uuid.uuid4()), name=name, design_dir=design_dir, code_dir=code_dir, created_at=datetime.now(timezone.utc), ) self._repo.save(project) return project def get_project(self, project_id: str) -> Project: project = self._repo.get_by_id(project_id) if project is None: raise NotFoundError("Project", project_id) return project def delete_project(self, project_id: str) -> None: project = self._repo.get_by_id(project_id) if project is None: raise NotFoundError("Project", project_id) self._repo.delete(project_id) ``` - [ ] **Step 4: Run tests — should pass** Run: `cd /workspace/arch-design-agent-skill-dashboard/backend && uv run pytest tests/test_project.py -v` Expected: All passed - [ ] **Step 5: Commit** ```bash git add backend/app/modules/project/application/ backend/tests/test_project.py git commit -m "feat(project): add ProjectService with CRUD and path validation" ``` --- ## Task 10: MOD-PROJECT Interfaces — HTTP Router **Files:** - Modify: `backend/app/modules/project/interfaces/http/router.py` - Modify: `backend/app/modules/project/interfaces/__init__.py` - Modify: `backend/app/modules/project/interfaces/http/__init__.py` - Modify: `backend/app/main.py` - Create: `backend/tests/test_api_project.py` - [ ] **Step 1: Write API tests** Uses `client` and `design_dir` fixtures from `conftest.py`. ```python import pytest def test_health(client): r = client.get("/api/health") assert r.status_code == 200 assert r.json()["status"] == "ok" def test_list_projects_empty(client): r = client.get("/api/projects") assert r.status_code == 200 assert r.json() == [] def test_create_and_get_project(client, design_dir): r = client.post("/api/projects", json={"name": "test", "design_dir": str(design_dir)}) assert r.status_code == 201 pid = r.json()["id"] r = client.get(f"/api/projects/{pid}") assert r.status_code == 200 assert r.json()["name"] == "test" def test_create_project_invalid_dir(client): r = client.post("/api/projects", json={"name": "test", "design_dir": "/nonexistent"}) assert r.status_code == 400 def test_delete_project(client, design_dir): r = client.post("/api/projects", json={"name": "test", "design_dir": str(design_dir)}) pid = r.json()["id"] r = client.delete(f"/api/projects/{pid}") assert r.status_code == 204 r = client.get(f"/api/projects/{pid}") assert r.status_code == 404 def test_get_nonexistent_project(client): r = client.get("/api/projects/nonexistent") assert r.status_code == 404 ``` - [ ] **Step 2: Write router.py** ```python from fastapi import APIRouter, Response from pydantic import BaseModel from app.modules.project.application.services import ProjectService router = APIRouter(prefix="/projects", tags=["project"]) _service: ProjectService | None = None def init_router(service: ProjectService) -> None: global _service _service = service class CreateProjectRequest(BaseModel): name: str design_dir: str code_dir: str | None = None class ProjectResponse(BaseModel): id: str name: str design_dir: str code_dir: str | None created_at: str def _to_response(p) -> dict: return { "id": p.id, "name": p.name, "design_dir": p.design_dir, "code_dir": p.code_dir, "created_at": p.created_at.isoformat(), } @router.get("") def list_projects(): return [_to_response(p) for p in _service.list_projects()] @router.post("", status_code=201) def create_project(req: CreateProjectRequest): p = _service.create_project(req.name, req.design_dir, req.code_dir) return _to_response(p) @router.get("/{project_id}") def get_project(project_id: str): p = _service.get_project(project_id) return _to_response(p) @router.delete("/{project_id}", status_code=204) def delete_project(project_id: str): _service.delete_project(project_id) return Response(status_code=204) ``` - [ ] **Step 3: Write main.py with app factory** ```python import os from pathlib import Path from fastapi import FastAPI, Request from fastapi.responses import JSONResponse from app.shared.kernel.exceptions import NotFoundError, ValidationError from app.shared.infrastructure.config import Settings from app.modules.project.infrastructure.json_repository import JsonProjectRepository from app.modules.project.application.services import ProjectService from app.modules.project.interfaces.http.router import router as project_router, init_router as init_project_router def create_app() -> FastAPI: app = FastAPI(title="Arch Design Dashboard API", version="0.1.0") # Settings registry_path = Path(os.environ.get("REGISTRY_PATH", str(Settings().registry_path))) # Wire Project module project_repo = JsonProjectRepository(registry_path) project_service = ProjectService(project_repo) init_project_router(project_service) # Register routers app.include_router(project_router, prefix="/api") # Health check @app.get("/api/health") def health(): return {"status": "ok"} # Exception handlers @app.exception_handler(NotFoundError) async def not_found_handler(request: Request, exc: NotFoundError): return JSONResponse(status_code=404, content={"detail": str(exc)}) @app.exception_handler(ValidationError) async def validation_handler(request: Request, exc: ValidationError): return JSONResponse(status_code=400, content={"detail": exc.message}) return app # For uvicorn: use `uvicorn app.main:create_app --factory` # or for simple usage: app = create_app() ``` Note: Tests must call `create_app()` directly to get a fresh app instance with test-specific config. The module-level `app` is only used for `uvicorn app.main:app` convenience. Tests override `REGISTRY_PATH` env var before calling `create_app()`. - [ ] **Step 4: Run tests — should pass** Run: `cd /workspace/arch-design-agent-skill-dashboard/backend && uv run pytest tests/test_api_project.py -v` Expected: All passed - [ ] **Step 5: Verify server starts** Run: `cd /workspace/arch-design-agent-skill-dashboard/backend && timeout 5 uv run uvicorn app.main:app --port 8900 || true` Expected: Server starts (timeout kills it after 5s) - [ ] **Step 6: Commit** ```bash git add backend/app/modules/project/interfaces/ backend/app/main.py backend/tests/test_api_project.py git commit -m "feat(project): add REST API — CRUD endpoints with FastAPI" ``` --- ## Task 11: MOD-SCANNER Domain — Entities **Files:** - Modify: `backend/app/modules/scanner/domain/__init__.py` - Modify: `backend/app/modules/scanner/domain/entities.py` - Modify: `backend/app/modules/scanner/__init__.py` - [ ] **Step 1: Write scanner domain entities** Write `backend/app/modules/scanner/domain/entities.py` with `FileStatusEntry`, `ScanSummary`, and `ScanResult` exactly as specified in spec Section 3.4. ScanResult carries all parsed Design entity lists plus file status info. - [ ] **Step 2: Verify import** Run: `cd /workspace/arch-design-agent-skill-dashboard/backend && uv run python -c "from app.modules.scanner.domain.entities import ScanResult, FileStatusEntry, ScanSummary; print('OK')"` Expected: `OK` - [ ] **Step 3: Commit** ```bash git add backend/app/modules/scanner/ git commit -m "feat(scanner): add ScanResult, FileStatusEntry, ScanSummary domain entities" ``` --- ## Task 12: MOD-SCANNER Infrastructure — CSV Parser **Files:** - Modify: `backend/app/modules/scanner/infrastructure/parsers/csv_parser.py` - Modify: `backend/app/modules/scanner/infrastructure/parsers/__init__.py` - Modify: `backend/app/modules/scanner/infrastructure/__init__.py` - Create: `backend/tests/test_scanner_parsers.py` - [ ] **Step 1: Write CSV parser tests** Test that the CSV parser can read the real design CSV files and produce the correct Design entity types. Use the actual `design/business-architecture/02-capability-map.csv` as test input. Key tests: - Parse capability-map.csv → list[Capability] with correct field mapping - Parse modules.csv → list[Module] with list[str] fields split from spaces - Parse traceability.csv → list[TraceabilityLink] with space-split entity_ids - Parse unknown CSV filename → returns empty list (graceful) - [ ] **Step 2: Run tests — should fail** - [ ] **Step 3: Implement csv_parser.py** The parser must: 1. Read CSV with Python's `csv.DictReader` 2. Map file basename to entity type (e.g., `02-capability-map.csv` → Capability) 3. Split space-delimited fields into `list[str]` 4. Return a dict of entity type → list of instances File-to-entity mapping: - `*capability-map*` → Capability - `*modules*` → Module (application-architecture only) - `*value-flows*` → ValueFlow - `*user-journeys*` → UserJourney - `*integrations*` → Integration - `*external-systems*` → ExternalSystem - `*entities*` → Entity (data-architecture only) - `*data-flows*` → DataFlow - `*data-security*` → DataSecurity - `*technology-selection*` → TechSelection - `*runtime-components*` → RuntimeComponent - `*environments*` → Environment - `*codebase-alignment*` → CodebaseAlignment - `*change-log*` → ChangeLogEntry - `traceability.csv` → TraceabilityLink - `*shared-terminology*` → SharedTerm - `*ubiquitous-language*` → UbiquitousTerm - `*scenarios-and-flows*` → Scenario - `*domain-modules*` → DomainModule - `*domain-entities*` → DomainEntity - [ ] **Step 4: Run tests — should pass** - [ ] **Step 5: Commit** ```bash git add backend/app/modules/scanner/infrastructure/parsers/csv_parser.py backend/tests/test_scanner_parsers.py git commit -m "feat(scanner): add CSV parser — maps 20 CSV file types to Design entities" ``` --- ## Task 13: MOD-SCANNER Infrastructure — MD Parser **Files:** - Modify: `backend/app/modules/scanner/infrastructure/parsers/md_parser.py` - Modify: `backend/tests/test_scanner_parsers.py` (append) - [ ] **Step 1: Write MD parser tests** Test that: - Parse MD with YAML frontmatter → DesignDocument (doc_id, title, status, upstream, downstream, file_path) - Parse scope-and-goals.md → ScopeAndGoals - Parse system-context.md → SystemContext - Parse MD without frontmatter → DesignDocument with minimal fields - Parse ADR template → ADR entity - Parse domain-overview.md → Domain entity - [ ] **Step 2: Run tests — should fail** - [ ] **Step 3: Implement md_parser.py** The parser must: 1. Extract YAML frontmatter between `---` markers using `yaml.safe_load` 2. Produce a DesignDocument for every MD file that has frontmatter 3. For specific files, also produce specialized entities: - `*scope-and-goals*` → ScopeAndGoals - `*system-context*` → SystemContext - `*solution-layering*` → SolutionLayer - `*module-boundary*` → ModuleBoundaryRule - `*runtime-topology*` → RuntimeTopology - `*operational-baseline*` → OperationalBaseline - `*release-and-rollback*` → ReleasePlan - `*domain-overview*` → Domain - `*domain-decisions*` → (skip, no entity needed beyond DesignDocument) - `ADR-*` (not template) → ADR - [ ] **Step 4: Run tests — should pass** - [ ] **Step 5: Commit** ```bash git add backend/app/modules/scanner/infrastructure/parsers/md_parser.py backend/tests/test_scanner_parsers.py git commit -m "feat(scanner): add Markdown parser — frontmatter extraction and specialized entity mapping" ``` --- ## Task 14: MOD-SCANNER Infrastructure — YAML & OpenAPI Parsers **Files:** - Modify: `backend/app/modules/scanner/infrastructure/parsers/yaml_parser.py` - Modify: `backend/app/modules/scanner/infrastructure/parsers/openapi_parser.py` - Modify: `backend/tests/test_scanner_parsers.py` (append) - [ ] **Step 1: Write tests for OpenAPI parser** Test that parsing `04-api-contracts.openapi.yaml` produces a list of ApiContract with correct path/method/operationId. - [ ] **Step 2: Implement yaml_parser.py** (thin wrapper around yaml.safe_load) - [ ] **Step 3: Implement openapi_parser.py** Parse OpenAPI YAML → iterate `paths` → for each path+method, create an `ApiContract(doc_id, path, method, operation_id, summary)`. - [ ] **Step 4: Run tests — should pass** - [ ] **Step 5: Commit** ```bash git add backend/app/modules/scanner/infrastructure/parsers/ backend/tests/test_scanner_parsers.py git commit -m "feat(scanner): add YAML and OpenAPI parsers" ``` --- ## Task 15: MOD-SCANNER Application — ScanService **Files:** - Modify: `backend/app/modules/scanner/application/__init__.py` - Modify: `backend/app/modules/scanner/application/services.py` - Create: `backend/tests/test_scanner_service.py` - [ ] **Step 1: Write ScanService tests** Test using the real `design/` directory from the project: - `scan()` produces a ScanResult with populated entity lists - `scan()` produces file_statuses for all design files - `scan()` summary counts match file_statuses - `get_latest_scan()` returns None before first scan - `get_latest_scan()` returns cached result after scan - [ ] **Step 2: Run tests — should fail** - [ ] **Step 3: Implement services.py** ```python from datetime import datetime, timezone from pathlib import Path from app.modules.design.domain.entities import * # all 31 entities from app.modules.design.domain.services import DesignValidationService from app.modules.design.domain.value_objects import FileStatus from app.modules.project.domain.entities import Project from app.modules.scanner.domain.entities import FileStatusEntry, ScanResult, ScanSummary from app.modules.scanner.infrastructure.parsers.csv_parser import CsvParser from app.modules.scanner.infrastructure.parsers.md_parser import MdParser from app.modules.scanner.infrastructure.parsers.openapi_parser import OpenapiParser class ScanService: def __init__(self) -> None: self._cache: dict[str, ScanResult] = {} def scan(self, project: Project) -> ScanResult: design_dir = Path(project.design_dir) csv_parser = CsvParser() md_parser = MdParser() openapi_parser = OpenapiParser() # Collect all parsed entities all_entities: dict[str, list] = {} # entity_type_name -> list # Scan all files and collect file statuses file_statuses: list[FileStatusEntry] = [] for file_path in sorted(design_dir.rglob("*")): if not file_path.is_file(): continue if file_path.name.startswith("."): continue relative = str(file_path.relative_to(design_dir)) content = file_path.read_text(encoding="utf-8") status = DesignValidationService.determine_file_status(content, file_path.name) lines = len([l for l in content.splitlines() if l.strip()]) file_statuses.append(FileStatusEntry(path=relative, status=status, content_lines=lines)) # Parse by type if file_path.suffix == ".csv": parsed = csv_parser.parse(file_path) for key, items in parsed.items(): all_entities.setdefault(key, []).extend(items) elif file_path.suffix == ".md": parsed = md_parser.parse(file_path) for key, items in parsed.items(): all_entities.setdefault(key, []).extend(items) elif file_path.suffix in (".yaml", ".yml"): if "openapi" in file_path.name or "api-contracts" in file_path.name: parsed = openapi_parser.parse(file_path) for key, items in parsed.items(): all_entities.setdefault(key, []).extend(items) # Build summary summary = ScanSummary( total_files=len(file_statuses), ok=sum(1 for f in file_statuses if f.status == FileStatus.OK), sparse=sum(1 for f in file_statuses if f.status == FileStatus.SPARSE), missing=sum(1 for f in file_statuses if f.status == FileStatus.MISSING), placeholder_heavy=sum(1 for f in file_statuses if f.status == FileStatus.PLACEHOLDER_HEAVY), template_residue=sum(1 for f in file_statuses if f.status == FileStatus.TEMPLATE_RESIDUE), ) # Assemble ScanResult — map entity type names to ScanResult fields result = ScanResult( project_id=project.id, scanned_at=datetime.now(timezone.utc), file_statuses=file_statuses, summary=summary, capabilities=all_entities.get("capabilities", []), modules=all_entities.get("modules", []), entities=all_entities.get("entities", []), value_flows=all_entities.get("value_flows", []), user_journeys=all_entities.get("user_journeys", []), integrations=all_entities.get("integrations", []), data_flows=all_entities.get("data_flows", []), traceability_links=all_entities.get("traceability_links", []), external_systems=all_entities.get("external_systems", []), runtime_components=all_entities.get("runtime_components", []), tech_selections=all_entities.get("tech_selections", []), environments=all_entities.get("environments", []), design_documents=all_entities.get("design_documents", []), change_log_entries=all_entities.get("change_log_entries", []), adrs=all_entities.get("adrs", []), shared_terms=all_entities.get("shared_terms", []), domains=all_entities.get("domains", []), ubiquitous_terms=all_entities.get("ubiquitous_terms", []), scenarios=all_entities.get("scenarios", []), domain_modules=all_entities.get("domain_modules", []), domain_entities=all_entities.get("domain_entities", []), data_securities=all_entities.get("data_securities", []), codebase_alignments=all_entities.get("codebase_alignments", []), api_contracts=all_entities.get("api_contracts", []), scope_and_goals=next(iter(all_entities.get("scope_and_goals", [])), None), system_context=next(iter(all_entities.get("system_context", [])), None), solution_layer=next(iter(all_entities.get("solution_layer", [])), None), module_boundary_rule=next(iter(all_entities.get("module_boundary_rule", [])), None), runtime_topology=next(iter(all_entities.get("runtime_topology", [])), None), operational_baseline=next(iter(all_entities.get("operational_baseline", [])), None), release_plan=next(iter(all_entities.get("release_plan", [])), None), ) self._cache[project.id] = result return result def get_latest_scan(self, project_id: str) -> ScanResult | None: return self._cache.get(project_id) ``` - [ ] **Step 4: Run tests — should pass** - [ ] **Step 5: Commit** ```bash git add backend/app/modules/scanner/application/ backend/tests/test_scanner_service.py git commit -m "feat(scanner): add ScanService — orchestrates parsers, file status, and entity collection" ``` --- ## Task 16: MOD-SCANNER Interfaces — HTTP Router **Files:** - Modify: `backend/app/modules/scanner/interfaces/http/router.py` - Modify: `backend/app/modules/scanner/interfaces/__init__.py` - Modify: `backend/app/modules/scanner/interfaces/http/__init__.py` - Modify: `backend/app/main.py` (wire scanner) - Create: `backend/tests/test_api_scanner.py` - [ ] **Step 1: Write API tests** Test scan trigger, get latest scan, and all 13 entity query endpoints (10 list + 3 detail). Use a real design/ directory as fixture. - [ ] **Step 2: Implement router.py** The scanner router provides: - `POST /projects/{project_id}/scan` → trigger scan → return ScanResultResponse (trimmed: no entity lists) - `GET /projects/{project_id}/scan` → get cached scan → return ScanResultResponse - 10 list endpoints for entity types - 3 detail endpoints (CapabilityDetail, ModuleDetail, EntityDetail) with join logic The router needs access to both ProjectService (to resolve project_id → Project) and ScanService. - [ ] **Step 3: Wire scanner into main.py** Add ScanService instantiation and wire the scanner router in `create_app()`. - [ ] **Step 4: Run tests — should pass** - [ ] **Step 5: Commit** ```bash git add backend/app/modules/scanner/interfaces/ backend/app/main.py backend/tests/test_api_scanner.py git commit -m "feat(scanner): add REST API — scan trigger, entity query endpoints" ``` --- ## Task 17: MOD-GRAPH Domain — Entities **Files:** - Modify: `backend/app/modules/graph/domain/entities.py` - Modify: `backend/app/modules/graph/domain/__init__.py` - Modify: `backend/app/modules/graph/__init__.py` - [ ] **Step 1: Write graph domain entities** Write `GraphNode`, `GraphEdge`, `GraphGroup`, `GraphView` exactly as specified in spec Section 3.5. - [ ] **Step 2: Verify import** - [ ] **Step 3: Commit** ```bash git add backend/app/modules/graph/ git commit -m "feat(graph): add GraphNode, GraphEdge, GraphGroup, GraphView domain entities" ``` --- ## Task 18: MOD-GRAPH Application — GraphService **Files:** - Modify: `backend/app/modules/graph/application/services.py` - Modify: `backend/app/modules/graph/application/__init__.py` - Create: `backend/tests/test_graph_service.py` - [ ] **Step 1: Write tests** Test `build_panorama()`: - Creates 5 groups (business, application, data, technology, cross-layer) - Capabilities become nodes in business group - Modules become nodes in application group - Entities become nodes in data group - RuntimeComponents become nodes in technology group - TraceabilityLinks generate edges (capability→module, module→entity) - Module.depends_on generates edges - Integration generates edges Test `get_neighbors()`: - Returns only direct neighbor nodes and connecting edges - Returns empty GraphView for unknown node_id - [ ] **Step 2: Run tests — should fail** - [ ] **Step 3: Implement services.py** Build the panorama by iterating over ScanResult entities and constructing nodes, edges, and groups. See spec Section 3.5 for the 9-step algorithm. For `get_neighbors()`: filter the full graph to only nodes that share an edge with the target node. - [ ] **Step 4: Run tests — should pass** - [ ] **Step 5: Commit** ```bash git add backend/app/modules/graph/application/ backend/tests/test_graph_service.py git commit -m "feat(graph): add GraphService — panorama construction and neighbor query" ``` --- ## Task 19: MOD-GRAPH Interfaces — HTTP Router **Files:** - Modify: `backend/app/modules/graph/interfaces/http/router.py` - Modify: `backend/app/main.py` (wire graph) - Create: `backend/tests/test_api_graph.py` - [ ] **Step 1: Write API tests** - [ ] **Step 2: Implement router.py** - `GET /projects/{project_id}/graph` → trigger scan if needed → build panorama → return GraphView JSON - `GET /projects/{project_id}/graph/nodes/{node_id}/neighbors` → get neighbors → return GraphView JSON - [ ] **Step 3: Wire graph into main.py** - [ ] **Step 4: Run tests — should pass** - [ ] **Step 5: Commit** ```bash git add backend/app/modules/graph/ backend/app/main.py backend/tests/test_api_graph.py git commit -m "feat(graph): add REST API — panorama and neighbor query endpoints" ``` --- ## Task 20: MOD-EDITOR Domain + Infrastructure **Files:** - Modify: `backend/app/modules/editor/domain/entities.py` - Create: `backend/app/modules/editor/infrastructure/file_io.py` - [ ] **Step 1: Write editor domain entities** `EditableFile`, `AffectedFile`, `ImpactResult` as specified in spec Section 3.6. - [ ] **Step 2: Write file_io.py** Read/write design files using `app.shared.infrastructure.filesystem`. - [ ] **Step 3: Commit** ```bash git add backend/app/modules/editor/ git commit -m "feat(editor): add domain entities and file I/O infrastructure" ``` --- ## Task 21: MOD-EDITOR Application + Interfaces **Files:** - Modify: `backend/app/modules/editor/application/services.py` - Modify: `backend/app/modules/editor/interfaces/http/router.py` - Modify: `backend/app/main.py` (wire editor) - Create: `backend/tests/test_editor_service.py` - Create: `backend/tests/test_api_editor.py` - [ ] **Step 1: Write EditorService tests** Test: - `get_file()` returns EditableFile with correct format detection - `save_file()` writes content and triggers rescan - `get_impact()` finds downstream affected files via DesignDocument relationships - [ ] **Step 2: Implement EditorService** ```python class EditorService: def __init__(self, scan_service: ScanService) -> None: self._scan_service = scan_service def get_file(self, project: Project, relative_path: str) -> EditableFile: ... # Read file, detect format from extension, return EditableFile def save_file(self, project: Project, relative_path: str, content: str) -> ScanResult: ... # Write file, trigger rescan, return new ScanResult def get_impact(self, project: Project, relative_path: str, scan_result: ScanResult) -> ImpactResult: ... # Walk DesignDocument.downstream + TraceabilityLink to find affected files ``` - [ ] **Step 3: Write router.py and API tests** - [ ] **Step 4: Wire into main.py** - [ ] **Step 5: Run all tests — should pass** - [ ] **Step 6: Commit** ```bash git add backend/app/modules/editor/ backend/app/main.py backend/tests/test_editor_service.py backend/tests/test_api_editor.py git commit -m "feat(editor): add EditorService and REST API — file read/write and impact analysis" ``` --- ## Task 22: MOD-IMPL-TRACKER Domain + Infrastructure **Files:** - Modify: `backend/app/modules/impl_tracker/domain/entities.py` - Create: `backend/app/modules/impl_tracker/infrastructure/code_scanner.py` - Create: `backend/app/modules/impl_tracker/infrastructure/llm_client.py` - [ ] **Step 1: Write domain entities** `ImplProgress`, `CodeStructure` as specified in spec Section 3.7. - [ ] **Step 2: Write code_scanner.py** Scan a code directory → return CodeStructure (directories, files, matched modules by cross-referencing CodebaseAlignment). - [ ] **Step 3: Write llm_client.py** Stub LLM client that returns a placeholder response. Real LLM integration is optional — the system works without it (falls back to auto-scan only). - [ ] **Step 4: Commit** ```bash git add backend/app/modules/impl_tracker/ git commit -m "feat(impl_tracker): add domain entities and infrastructure — code scanner, LLM client stub" ``` --- ## Task 23: MOD-IMPL-TRACKER Application + Interfaces **Files:** - Modify: `backend/app/modules/impl_tracker/application/services.py` - Modify: `backend/app/modules/impl_tracker/interfaces/http/router.py` - Modify: `backend/app/main.py` (wire impl_tracker) - Create: `backend/tests/test_impl_tracker.py` - Create: `backend/tests/test_api_impl_tracker.py` - [ ] **Step 1: Write ImplTrackerService tests** Test: - `evaluate()` with code_dir=None returns empty list - `evaluate()` with real code dir returns ImplProgress per module - `set_manual_progress()` overrides auto value - `get_progress()` returns cached results - [ ] **Step 2: Implement ImplTrackerService** Three-tier evaluation as per spec Section 3.7. - [ ] **Step 3: Write router.py and API tests** Endpoints: - `POST /projects/{project_id}/impl-progress` → evaluate - `GET /projects/{project_id}/impl-progress` → get cached - `PUT /projects/{project_id}/impl-progress/{module_id}` → manual override - [ ] **Step 4: Wire into main.py** - [ ] **Step 5: Run all tests — should pass** - [ ] **Step 6: Commit** ```bash git add backend/app/modules/impl_tracker/ backend/app/main.py backend/tests/test_impl_tracker.py backend/tests/test_api_impl_tracker.py git commit -m "feat(impl_tracker): add ImplTrackerService and REST API — auto/llm/manual progress evaluation" ``` --- ## Task 24: Backend Integration Test — Full API **Files:** - No new files, run existing tests - [ ] **Step 1: Run all backend tests** Run: `cd /workspace/arch-design-agent-skill-dashboard/backend && uv run pytest -v` Expected: All tests pass. - [ ] **Step 2: Verify API starts and health responds** Run: `cd /workspace/arch-design-agent-skill-dashboard/backend && timeout 3 uv run uvicorn app.main:app --port 8900 &; sleep 2; curl -s http://localhost:8900/api/health; kill %1` Expected: `{"status":"ok"}` - [ ] **Step 3: Commit (if any fixes needed)** --- ## Task 25: Frontend Build Configuration **Files:** - Create: `frontend/package.json` - Create: `frontend/vite.config.ts` - Create: `frontend/tsconfig.json` - Create: `frontend/tsconfig.node.json` - Create: `frontend/index.html` - [ ] **Step 1: Create package.json** ```json { "name": "arch-design-dashboard", "version": "0.1.0", "private": true, "type": "module", "scripts": { "dev": "vite", "build": "vue-tsc -b && vite build", "preview": "vite preview" }, "dependencies": { "axios": "^1.7.0", "d3": "^7.9.0", "pinia": "^2.2.0", "vue": "^3.5.0", "vue-router": "^4.4.0" }, "devDependencies": { "@types/d3": "^7.4.0", "@vitejs/plugin-vue": "^5.1.0", "typescript": "~5.6.0", "vite": "^6.0.0", "vue-tsc": "^2.1.0" } } ``` - [ ] **Step 2: Create vite.config.ts** ```typescript import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' export default defineConfig({ plugins: [vue()], server: { proxy: { '/api': { target: 'http://localhost:8900', changeOrigin: true, }, }, }, }) ``` - [ ] **Step 3: Create tsconfig.json** ```json { "compilerOptions": { "target": "ES2020", "module": "ESNext", "moduleResolution": "bundler", "strict": true, "jsx": "preserve", "resolveJsonModule": true, "isolatedModules": true, "esModuleInterop": true, "lib": ["ES2020", "DOM", "DOM.Iterable"], "skipLibCheck": true, "noEmit": true, "paths": { "@/*": ["./src/*"] }, "baseUrl": "." }, "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"], "references": [{ "path": "./tsconfig.node.json" }] } ``` - [ ] **Step 4: Create tsconfig.node.json** ```json { "compilerOptions": { "target": "ES2022", "module": "ESNext", "moduleResolution": "bundler", "allowImportingTsExtensions": true, "strict": true, "noEmit": true, "skipLibCheck": true }, "include": ["vite.config.ts"] } ``` - [ ] **Step 5: Create index.html** ```html Arch Design Dashboard
``` - [ ] **Step 6: Install dependencies** Run: `cd /workspace/arch-design-agent-skill-dashboard/frontend && npm install` Expected: Dependencies installed successfully. - [ ] **Step 7: Commit** ```bash git add frontend/package.json frontend/vite.config.ts frontend/tsconfig.json frontend/tsconfig.node.json frontend/index.html frontend/package-lock.json git commit -m "build: add frontend configuration — Vite, TypeScript, Vue 3" ``` --- ## Task 26: Frontend Shared Layer **Files:** - Modify: `frontend/src/main.ts` - Modify: `frontend/src/App.vue` - Modify: `frontend/src/shared/api.ts` - Modify: `frontend/src/shared/types/api.ts` - Modify: `frontend/src/router/index.ts` - Create: `frontend/src/style.css` - [ ] **Step 1: Write shared/types/api.ts** Define all TypeScript interfaces matching the backend API response schemas: `Project`, `ScanResult`, `ScanSummary`, `FileStatusEntry`, `GraphView`, `GraphNode`, `GraphEdge`, `GraphGroup`, `Capability`, `Module`, `Entity`, `Integration`, `ValueFlow`, `UserJourney`, `DataFlow`, `ExternalSystem`, `TraceabilityLink`, `RuntimeComponent`, `FileContent`, `ImpactResult`, `ImplProgress`, `CapabilityDetail`, `ModuleDetail`, `EntityDetail`. - [ ] **Step 2: Write shared/api.ts** ```typescript import axios from 'axios' const api = axios.create({ baseURL: '/api' }) export default api ``` - [ ] **Step 3: Write router/index.ts** 3 routes: `/` → ProjectList, `/projects/:id` → GraphPanorama, `/projects/:id/editor` → EditorPage (Phase 2). - [ ] **Step 4: Write main.ts** Create Vue app, install Pinia and Router. - [ ] **Step 5: Write App.vue** Layout with sidebar (always visible project list) and main content area with ``. - [ ] **Step 6: Write style.css** Basic CSS for app layout (sidebar + content grid), colors, typography. - [ ] **Step 7: Verify build** Run: `cd /workspace/arch-design-agent-skill-dashboard/frontend && npx vue-tsc --noEmit && npx vite build` Expected: Build succeeds (components are empty stubs, but types/config should work). - [ ] **Step 8: Commit** ```bash git add frontend/src/ git commit -m "feat(frontend): add shared layer — types, API client, router, app layout" ``` --- ## Task 27: MOD-FE-PROJECT — Project Management UI **Files:** - Modify: `frontend/src/modules/project/types/index.ts` - Modify: `frontend/src/modules/project/api/index.ts` - Modify: `frontend/src/modules/project/composables/useProject.ts` - Modify: `frontend/src/modules/project/components/ProjectList.vue` - Modify: `frontend/src/modules/project/components/ProjectOverview.vue` - [ ] **Step 1: Write project types** Re-export or extend shared Project type. - [ ] **Step 2: Write project API functions** `listProjects`, `createProject`, `getProject`, `deleteProject` — all calling backend via shared api client. - [ ] **Step 3: Write useProject Pinia store** State: `projects: Project[]`, `currentProject: Project | null`, `loading: boolean`, `error: string | null`. Actions: `fetchProjects`, `createProject`, `selectProject`, `deleteProject`. - [ ] **Step 4: Write ProjectOverview.vue** Card component showing project name, design_dir, created_at. Click selects project. Delete button with confirmation. - [ ] **Step 5: Write ProjectList.vue** - List of ProjectOverview cards - "Add Project" button → inline form (name + design_dir + optional code_dir) - On create success → navigate to `/projects/:id` - Empty state message when no projects - [ ] **Step 6: Verify frontend builds** Run: `cd /workspace/arch-design-agent-skill-dashboard/frontend && npx vue-tsc --noEmit` - [ ] **Step 7: Commit** ```bash git add frontend/src/modules/project/ git commit -m "feat(fe-project): add project management UI — list, create, delete" ``` --- ## Task 28: MOD-FE-GRAPH — Graph Visualization **Files:** - Modify: `frontend/src/modules/graph/types/index.ts` - Modify: `frontend/src/modules/graph/api/index.ts` - Modify: `frontend/src/modules/graph/composables/useGraph.ts` - Modify: `frontend/src/modules/graph/components/GraphPanorama.vue` - Modify: `frontend/src/modules/graph/components/GraphDetail.vue` - [ ] **Step 1: Write graph types** Re-export GraphView, GraphNode, GraphEdge, GraphGroup types. - [ ] **Step 2: Write graph API functions** `triggerScan`, `getLatestScan`, `getGraph`, `getNodeNeighbors`, plus all entity query endpoints. - [ ] **Step 3: Write useGraph Pinia store** State: `graphView`, `selectedNode`, `scanResult`, `loading`. Actions: `loadGraph` (triggerScan → getGraph), `selectNode`, `loadNeighbors`, `clearSelection`. - [ ] **Step 4: Write GraphPanorama.vue** This is the core visualization component: - On mount: call `loadGraph(projectId)` → trigger scan → get graph data - D3.js force simulation with group clustering - Node rendering: - Colors: ok=#4CAF50, sparse=#FFC107, missing=#F44336, template-residue=#FF9800, placeholder-heavy=#9C27B0, unknown=#9E9E9E - Shapes: capability=circle, module=rect, entity=diamond, other=circle - Edge rendering: - traces_to=solid, depends_on=dashed, owns=thick solid - Interactions: - Hover → tooltip (id, type, status, label) - Click → select node → show GraphDetail - Double-click → load neighbors (drill-down) - Zoom/pan via D3 zoom behavior - Scan summary panel (top-right corner): total/ok/sparse/missing counts - [ ] **Step 5: Write GraphDetail.vue** Slide-out side panel: - Node properties (all fields) - Related entities list (clickable) - Phase 2 placeholders: Edit button, Impact Analysis button - [ ] **Step 6: Verify frontend builds** - [ ] **Step 7: Commit** ```bash git add frontend/src/modules/graph/ git commit -m "feat(fe-graph): add D3.js graph visualization — panorama, drill-down, status colors" ``` --- ## Task 29: MOD-FE-EDITOR — File Editor UI (Phase 2) **Files:** - Modify: `frontend/src/modules/editor/types/index.ts` - Modify: `frontend/src/modules/editor/api/index.ts` - Modify: `frontend/src/modules/editor/composables/useEditor.ts` - Modify: `frontend/src/modules/editor/components/CsvEditor.vue` - Modify: `frontend/src/modules/editor/components/MdEditor.vue` - [ ] **Step 1: Write editor types** FileContent, ImpactResult, ImplProgress types. - [ ] **Step 2: Write editor API functions** `getFile`, `saveFile`, `getFileImpact`. - [ ] **Step 3: Write useEditor Pinia store** State: `currentFile`, `impactResult`, `saving`, `error`. Actions: `loadFile`, `saveFile`, `analyzeImpact`. - [ ] **Step 4: Write CsvEditor.vue** - Parse CSV content into rows/columns - Render as HTML `` with contenteditable cells - Add/remove row buttons - Save button → serialize back to CSV string → call saveFile API - On save success → trigger graph refresh - [ ] **Step 5: Write MdEditor.vue** - Split view: left textarea, right preview - Render Markdown preview (basic: headers, lists, code blocks — use simple regex or a lightweight lib) - Save button → call saveFile API - [ ] **Step 6: Update router to add editor route** Add `/projects/:id/editor` route pointing to an EditorPage that conditionally renders CsvEditor or MdEditor based on file format. - [ ] **Step 7: Verify frontend builds** - [ ] **Step 8: Commit** ```bash git add frontend/src/modules/editor/ frontend/src/router/ git commit -m "feat(fe-editor): add CSV table editor and Markdown editor components" ``` --- ## Task 30: Frontend Full Build Verification - [ ] **Step 1: Run TypeScript check** Run: `cd /workspace/arch-design-agent-skill-dashboard/frontend && npx vue-tsc --noEmit` Expected: No errors - [ ] **Step 2: Run production build** Run: `cd /workspace/arch-design-agent-skill-dashboard/frontend && npx vite build` Expected: Build succeeds, output in `dist/` - [ ] **Step 3: Commit any fixes** --- ## Task 31: Docker Deployment Configuration **Files:** - Create: `docker-compose.yml` - Create: `backend/Dockerfile` - Create: `frontend/Dockerfile` - Create: `frontend/nginx.conf` - [ ] **Step 1: Create backend/Dockerfile** ```dockerfile FROM python:3.12-slim WORKDIR /app COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv COPY pyproject.toml uv.lock ./ RUN uv sync --frozen --no-dev COPY app/ app/ CMD ["uv", "run", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8900"] ``` - [ ] **Step 2: Create frontend/Dockerfile** ```dockerfile FROM node:20-alpine AS build WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci COPY . . RUN npm run build FROM nginx:alpine COPY --from=build /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 ``` - [ ] **Step 3: Create frontend/nginx.conf** ```nginx server { listen 80; root /usr/share/nginx/html; index index.html; location /api/ { proxy_pass http://backend:8900; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location / { try_files $uri $uri/ /index.html; } } ``` - [ ] **Step 4: Create docker-compose.yml** ```yaml services: backend: build: ./backend ports: - "8900:8900" volumes: - ${DESIGN_DIR:-.}:/data/design:rw - ${CODE_DIR:-/dev/null}:/data/code:ro - registry-data:/data/registry environment: - REGISTRY_PATH=/data/registry/projects.json frontend: build: ./frontend ports: - "80:80" depends_on: - backend volumes: registry-data: ``` - [ ] **Step 5: Commit** ```bash git add docker-compose.yml backend/Dockerfile frontend/Dockerfile frontend/nginx.conf git commit -m "build: add Docker deployment — Compose, Dockerfiles, Nginx config" ``` --- ## Task 32: End-to-End Verification - [ ] **Step 1: Run all backend tests** Run: `cd /workspace/arch-design-agent-skill-dashboard/backend && uv run pytest -v` Expected: All tests pass - [ ] **Step 2: Run frontend build** Run: `cd /workspace/arch-design-agent-skill-dashboard/frontend && npm run build` Expected: Build succeeds - [ ] **Step 3: Start backend and verify key API flows** Start server, create a project pointing to this repo's own `design/` directory, trigger scan, get graph, verify entities are populated. - [ ] **Step 4: Final commit if any fixes needed**