From 2d97a3333c39a8d09306166e3d710670232a4104 Mon Sep 17 00:00:00 2001 From: openclaw Date: Mon, 23 Mar 2026 15:22:38 +0000 Subject: [PATCH] =?UTF-8?q?docs:=20add=20full=20implementation=20plan=20?= =?UTF-8?q?=E2=80=94=2032=20tasks,=20TDD=20approach?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Covers all 6 backend modules (design, project, scanner, graph, editor, impl_tracker), 3 frontend modules (project, graph, editor), build config, and Docker deployment. Each backend module follows Domain → Infrastructure → Application → Interfaces order. Co-Authored-By: Claude Opus 4.6 --- .../plans/2026-03-23-full-implementation.md | 2316 +++++++++++++++++ 1 file changed, 2316 insertions(+) create mode 100644 docs/superpowers/plans/2026-03-23-full-implementation.md diff --git a/docs/superpowers/plans/2026-03-23-full-implementation.md b/docs/superpowers/plans/2026-03-23-full-implementation.md new file mode 100644 index 0000000..2b118a8 --- /dev/null +++ b/docs/superpowers/plans/2026-03-23-full-implementation.md @@ -0,0 +1,2316 @@ +# 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**