arch-design-agent-skill-das.../docs/superpowers/plans/2026-03-23-full-implementation.md
openclaw 2d97a3333c docs: add full implementation plan — 32 tasks, TDD approach
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 <noreply@anthropic.com>
2026-03-23 15:22:38 +00:00

71 KiB

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
[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)

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
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

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
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

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
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
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

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
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
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:

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
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

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,<replace this>\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
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", "<replace", "{{", "Lorem ipsum"]


@dataclass
class ConstraintViolation:
    rule: str
    entity_id: str
    message: str


class DesignValidationService:
    @staticmethod
    def determine_file_status(content: str, file_path: str) -> 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
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

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
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
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

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
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
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:

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
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
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.

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
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
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
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
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

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

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

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

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

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

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

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

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

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
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

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

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
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

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

{
  "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
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
{
  "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
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "strict": true,
    "noEmit": true,
    "skipLibCheck": true
  },
  "include": ["vite.config.ts"]
}
  • Step 5: Create index.html
<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Arch Design Dashboard</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>
  • Step 6: Install dependencies

Run: cd /workspace/arch-design-agent-skill-dashboard/frontend && npm install Expected: Dependencies installed successfully.

  • Step 7: Commit
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
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 <router-view />.

  • 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
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
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

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 <table> 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

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

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
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
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
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
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