Manages the registry of installed workflows. Tracks installed workflows and their metadata in ``.specify/workflows/workflow-registry.json``.
| 56 | |
| 57 | |
| 58 | class WorkflowRegistry: |
| 59 | """Manages the registry of installed workflows. |
| 60 | |
| 61 | Tracks installed workflows and their metadata in |
| 62 | ``.specify/workflows/workflow-registry.json``. |
| 63 | """ |
| 64 | |
| 65 | REGISTRY_FILE = "workflow-registry.json" |
| 66 | SCHEMA_VERSION = "1.0" |
| 67 | |
| 68 | def __init__(self, project_root: Path) -> None: |
| 69 | self.project_root = project_root |
| 70 | self.workflows_dir = project_root / ".specify" / "workflows" |
| 71 | self.registry_path = self.workflows_dir / self.REGISTRY_FILE |
| 72 | self.data = self._load() |
| 73 | |
| 74 | def _load(self) -> dict[str, Any]: |
| 75 | """Load registry from disk or create default.""" |
| 76 | if self.registry_path.exists(): |
| 77 | try: |
| 78 | with open(self.registry_path, encoding="utf-8") as f: |
| 79 | return json.load(f) |
| 80 | except (json.JSONDecodeError, ValueError): |
| 81 | # Corrupted registry file — reset to default |
| 82 | return {"schema_version": self.SCHEMA_VERSION, "workflows": {}} |
| 83 | return {"schema_version": self.SCHEMA_VERSION, "workflows": {}} |
| 84 | |
| 85 | def save(self) -> None: |
| 86 | """Persist registry to disk.""" |
| 87 | self.workflows_dir.mkdir(parents=True, exist_ok=True) |
| 88 | with open(self.registry_path, "w", encoding="utf-8") as f: |
| 89 | json.dump(self.data, f, indent=2) |
| 90 | |
| 91 | def add(self, workflow_id: str, metadata: dict[str, Any]) -> None: |
| 92 | """Add or update an installed workflow entry.""" |
| 93 | from datetime import datetime, timezone |
| 94 | |
| 95 | existing = self.data["workflows"].get(workflow_id, {}) |
| 96 | metadata["installed_at"] = existing.get( |
| 97 | "installed_at", datetime.now(timezone.utc).isoformat() |
| 98 | ) |
| 99 | metadata["updated_at"] = datetime.now(timezone.utc).isoformat() |
| 100 | self.data["workflows"][workflow_id] = metadata |
| 101 | self.save() |
| 102 | |
| 103 | def remove(self, workflow_id: str) -> bool: |
| 104 | """Remove an installed workflow entry. Returns True if found.""" |
| 105 | if workflow_id in self.data["workflows"]: |
| 106 | del self.data["workflows"][workflow_id] |
| 107 | self.save() |
| 108 | return True |
| 109 | return False |
| 110 | |
| 111 | def get(self, workflow_id: str) -> dict[str, Any] | None: |
| 112 | """Get metadata for an installed workflow.""" |
| 113 | return self.data["workflows"].get(workflow_id) |
| 114 | |
| 115 | def list(self) -> dict[str, dict[str, Any]]: |
no outgoing calls