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>
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:
- Read CSV with Python's
csv.DictReader - Map file basename to entity type (e.g.,
02-capability-map.csv→ Capability) - Split space-delimited fields into
list[str] - 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:
- Extract YAML frontmatter between
---markers usingyaml.safe_load - Produce a DesignDocument for every MD file that has frontmatter
- 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