devflow-toolkit 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. devflow/__init__.py +0 -0
  2. devflow/adapters/__init__.py +47 -0
  3. devflow/adapters/agents.py +49 -0
  4. devflow/adapters/base.py +81 -0
  5. devflow/adapters/claude_code.py +120 -0
  6. devflow/adapters/codex.py +111 -0
  7. devflow/adapters/detector.py +118 -0
  8. devflow/adapters/instructions.py +263 -0
  9. devflow/adapters/kiro.py +211 -0
  10. devflow/adapters/opencode.py +237 -0
  11. devflow/adapters/registry.py +41 -0
  12. devflow/cli.py +39 -0
  13. devflow/commands/__init__.py +6 -0
  14. devflow/commands/approve.py +98 -0
  15. devflow/commands/harness.py +98 -0
  16. devflow/commands/init.py +544 -0
  17. devflow/commands/spec.py +514 -0
  18. devflow/commands/status.py +156 -0
  19. devflow/core/__init__.py +15 -0
  20. devflow/core/architecture.py +469 -0
  21. devflow/core/artifacts/__init__.py +29 -0
  22. devflow/core/artifacts/errors.py +19 -0
  23. devflow/core/artifacts/models.py +82 -0
  24. devflow/core/artifacts/store.py +130 -0
  25. devflow/core/clock.py +67 -0
  26. devflow/core/config.py +706 -0
  27. devflow/core/constitution.py +166 -0
  28. devflow/core/discovery.py +1304 -0
  29. devflow/core/ports.py +159 -0
  30. devflow/core/profile_manager.py +311 -0
  31. devflow/core/project.py +533 -0
  32. devflow/core/spec_workflow.py +932 -0
  33. devflow/core/specification.py +2443 -0
  34. devflow/core/state/__init__.py +75 -0
  35. devflow/core/state/errors.py +39 -0
  36. devflow/core/state/events.py +725 -0
  37. devflow/core/state/locator.py +116 -0
  38. devflow/core/state/lock.py +69 -0
  39. devflow/core/state/manager.py +960 -0
  40. devflow/core/state/models.py +164 -0
  41. devflow/core/state/transitions.py +114 -0
  42. devflow/models/__init__.py +6 -0
  43. devflow/models/profile.py +63 -0
  44. devflow/models/project.py +47 -0
  45. devflow/profiles/ai-rag/constitution/ai-rag-standards.md +49 -0
  46. devflow/profiles/ai-rag/profile.yaml +28 -0
  47. devflow/profiles/base/constitution/architecture.md +58 -0
  48. devflow/profiles/base/constitution/code-standards.md +85 -0
  49. devflow/profiles/base/constitution/constitution.md +85 -0
  50. devflow/profiles/base/profile.yaml +15 -0
  51. devflow/profiles/django-backend/constitution/django-standards.md +64 -0
  52. devflow/profiles/django-backend/profile.yaml +24 -0
  53. devflow/profiles/fastapi-backend/constitution/fastapi-standards.md +59 -0
  54. devflow/profiles/fastapi-backend/profile.yaml +23 -0
  55. devflow/prompts/__init__.py +200 -0
  56. devflow/prompts/discovery.md +119 -0
  57. devflow/prompts/spec.greenfield.md +149 -0
  58. devflow/prompts/spec.md +116 -0
  59. devflow_toolkit-0.3.0.dist-info/METADATA +628 -0
  60. devflow_toolkit-0.3.0.dist-info/RECORD +63 -0
  61. devflow_toolkit-0.3.0.dist-info/WHEEL +4 -0
  62. devflow_toolkit-0.3.0.dist-info/entry_points.txt +2 -0
  63. devflow_toolkit-0.3.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,166 @@
1
+ """Loading the project constitution."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+
8
+ from devflow.core.config import (
9
+ ConfigError,
10
+ DevFlowConfig,
11
+ validate_document_name,
12
+ )
13
+ from devflow.core.profile_manager import ProfileManager
14
+ from devflow.models.profile import Profile
15
+
16
+
17
+ class ConstitutionError(Exception):
18
+ """Raised when constitution documents cannot be loaded."""
19
+
20
+
21
+ def _document_name(filename: str) -> str:
22
+ """Validate a constitution document name for this loader."""
23
+ try:
24
+ return validate_document_name(filename, "constitution")
25
+ except ConfigError as error:
26
+ raise ConstitutionError(str(error)) from error
27
+
28
+
29
+ def _document_path(base_dir: Path, filename: str) -> Path:
30
+ """Resolve a constitution document without allowing path escapes."""
31
+ name = _document_name(filename)
32
+ base = base_dir.resolve()
33
+ candidate = (base / name).resolve()
34
+ try:
35
+ candidate.relative_to(base)
36
+ except ValueError as error:
37
+ msg = (
38
+ "Constitution document must stay inside the constitution "
39
+ f"directory: {filename}"
40
+ )
41
+ raise ConstitutionError(msg) from error
42
+ return candidate
43
+
44
+
45
+ @dataclass(frozen=True)
46
+ class ConstitutionDocument:
47
+ """A single constitution document."""
48
+
49
+ name: str
50
+ path: Path
51
+ content: str
52
+
53
+
54
+ @dataclass(frozen=True)
55
+ class Constitution:
56
+ """The set of guidelines governing a project."""
57
+
58
+ documents: tuple[ConstitutionDocument, ...]
59
+ profiles: tuple[Profile, ...]
60
+ warnings: tuple[str, ...] = ()
61
+
62
+ @property
63
+ def names(self) -> tuple[str, ...]:
64
+ """Return the loaded document names."""
65
+ return tuple(document.name for document in self.documents)
66
+
67
+
68
+ class ConstitutionLoader:
69
+ """Load base and profile-specific constitution documents."""
70
+
71
+ def __init__(self, profile_manager: ProfileManager | None = None) -> None:
72
+ self.profile_manager = profile_manager or ProfileManager()
73
+
74
+ def load(
75
+ self,
76
+ config: DevFlowConfig,
77
+ profiles: list[Profile],
78
+ ) -> Constitution:
79
+ """Load configured and profile-specific constitution documents."""
80
+ base_dir = config.constitution.path
81
+ if not base_dir.is_dir():
82
+ msg = (
83
+ f"Constitution directory not found: {base_dir}. "
84
+ "Run 'devflow init' to restore the guidelines."
85
+ )
86
+ raise ConstitutionError(msg)
87
+
88
+ names: list[str] = [
89
+ _document_name(filename) for filename in config.constitution.files
90
+ ]
91
+ warnings: list[str] = []
92
+
93
+ selected_profiles: list[Profile] = []
94
+ if config.constitution.base:
95
+ selected_profiles.extend(
96
+ profile for profile in profiles if profile.name == "base"
97
+ )
98
+ if config.constitution.profile_specific:
99
+ selected_profiles.extend(
100
+ profile for profile in profiles if profile.name != "base"
101
+ )
102
+
103
+ for filename in self.profile_manager.constitution_documents(
104
+ selected_profiles
105
+ ):
106
+ name = _document_name(filename)
107
+ if name not in names:
108
+ candidate = _document_path(base_dir, name)
109
+ if candidate.is_file():
110
+ names.append(name)
111
+ else:
112
+ warnings.append(
113
+ f"Profile guideline is not installed: {name}"
114
+ )
115
+
116
+ if not names:
117
+ for path in sorted(base_dir.glob("*.md")):
118
+ try:
119
+ candidate = _document_path(base_dir, path.name)
120
+ except ConstitutionError:
121
+ warnings.append(
122
+ "Skipped constitution document outside "
123
+ f"{base_dir}: {path.name}"
124
+ )
125
+ continue
126
+ if candidate.is_file():
127
+ names.append(path.name)
128
+
129
+ if not names:
130
+ msg = f"No constitution documents found in {base_dir}."
131
+ raise ConstitutionError(msg)
132
+
133
+ documents: list[ConstitutionDocument] = []
134
+ seen: set[str] = set()
135
+ for filename in names:
136
+ if filename in seen:
137
+ continue
138
+ seen.add(filename)
139
+
140
+ path = _document_path(base_dir, filename)
141
+ if not path.is_file():
142
+ msg = (
143
+ f"Constitution document not found: {filename}. "
144
+ "Run 'devflow init' or update .devflow/config.yaml."
145
+ )
146
+ raise ConstitutionError(msg)
147
+
148
+ try:
149
+ content = path.read_text(encoding="utf-8")
150
+ except OSError as error:
151
+ msg = f"Could not read constitution document: {path}"
152
+ raise ConstitutionError(msg) from error
153
+
154
+ documents.append(
155
+ ConstitutionDocument(
156
+ name=filename,
157
+ path=path,
158
+ content=content,
159
+ )
160
+ )
161
+
162
+ return Constitution(
163
+ documents=tuple(documents),
164
+ profiles=tuple(profiles),
165
+ warnings=tuple(warnings),
166
+ )