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.
- devflow/__init__.py +0 -0
- devflow/adapters/__init__.py +47 -0
- devflow/adapters/agents.py +49 -0
- devflow/adapters/base.py +81 -0
- devflow/adapters/claude_code.py +120 -0
- devflow/adapters/codex.py +111 -0
- devflow/adapters/detector.py +118 -0
- devflow/adapters/instructions.py +263 -0
- devflow/adapters/kiro.py +211 -0
- devflow/adapters/opencode.py +237 -0
- devflow/adapters/registry.py +41 -0
- devflow/cli.py +39 -0
- devflow/commands/__init__.py +6 -0
- devflow/commands/approve.py +98 -0
- devflow/commands/harness.py +98 -0
- devflow/commands/init.py +544 -0
- devflow/commands/spec.py +514 -0
- devflow/commands/status.py +156 -0
- devflow/core/__init__.py +15 -0
- devflow/core/architecture.py +469 -0
- devflow/core/artifacts/__init__.py +29 -0
- devflow/core/artifacts/errors.py +19 -0
- devflow/core/artifacts/models.py +82 -0
- devflow/core/artifacts/store.py +130 -0
- devflow/core/clock.py +67 -0
- devflow/core/config.py +706 -0
- devflow/core/constitution.py +166 -0
- devflow/core/discovery.py +1304 -0
- devflow/core/ports.py +159 -0
- devflow/core/profile_manager.py +311 -0
- devflow/core/project.py +533 -0
- devflow/core/spec_workflow.py +932 -0
- devflow/core/specification.py +2443 -0
- devflow/core/state/__init__.py +75 -0
- devflow/core/state/errors.py +39 -0
- devflow/core/state/events.py +725 -0
- devflow/core/state/locator.py +116 -0
- devflow/core/state/lock.py +69 -0
- devflow/core/state/manager.py +960 -0
- devflow/core/state/models.py +164 -0
- devflow/core/state/transitions.py +114 -0
- devflow/models/__init__.py +6 -0
- devflow/models/profile.py +63 -0
- devflow/models/project.py +47 -0
- devflow/profiles/ai-rag/constitution/ai-rag-standards.md +49 -0
- devflow/profiles/ai-rag/profile.yaml +28 -0
- devflow/profiles/base/constitution/architecture.md +58 -0
- devflow/profiles/base/constitution/code-standards.md +85 -0
- devflow/profiles/base/constitution/constitution.md +85 -0
- devflow/profiles/base/profile.yaml +15 -0
- devflow/profiles/django-backend/constitution/django-standards.md +64 -0
- devflow/profiles/django-backend/profile.yaml +24 -0
- devflow/profiles/fastapi-backend/constitution/fastapi-standards.md +59 -0
- devflow/profiles/fastapi-backend/profile.yaml +23 -0
- devflow/prompts/__init__.py +200 -0
- devflow/prompts/discovery.md +119 -0
- devflow/prompts/spec.greenfield.md +149 -0
- devflow/prompts/spec.md +116 -0
- devflow_toolkit-0.3.0.dist-info/METADATA +628 -0
- devflow_toolkit-0.3.0.dist-info/RECORD +63 -0
- devflow_toolkit-0.3.0.dist-info/WHEEL +4 -0
- devflow_toolkit-0.3.0.dist-info/entry_points.txt +2 -0
- devflow_toolkit-0.3.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"""Workflow state models."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from devflow.core.artifacts.models import ArtifactRecord
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class WorkflowStatus(str, Enum):
|
|
13
|
+
"""State of a change in the DevFlow lifecycle."""
|
|
14
|
+
|
|
15
|
+
DRAFT = "draft"
|
|
16
|
+
AWAITING_CLARIFICATION = "awaiting_clarification"
|
|
17
|
+
SPEC_APPROVED = "spec_approved"
|
|
18
|
+
PLANNED = "planned"
|
|
19
|
+
PLAN_APPROVED = "plan_approved"
|
|
20
|
+
IMPLEMENTING = "implementing"
|
|
21
|
+
IMPLEMENTED = "implemented"
|
|
22
|
+
REVIEWED = "reviewed"
|
|
23
|
+
REVIEW_APPROVED = "review_approved"
|
|
24
|
+
CHANGES_REQUESTED = "changes_requested"
|
|
25
|
+
DONE = "done"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
WORKFLOW_STATUSES = tuple(status.value for status in WorkflowStatus)
|
|
29
|
+
|
|
30
|
+
STAGE_TARGETS = {
|
|
31
|
+
"spec": WorkflowStatus.SPEC_APPROVED.value,
|
|
32
|
+
"plan": WorkflowStatus.PLAN_APPROVED.value,
|
|
33
|
+
"review": WorkflowStatus.REVIEW_APPROVED.value,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
STAGE_ARTIFACTS = {
|
|
37
|
+
"spec": ("spec",),
|
|
38
|
+
"discovery": ("discovery",),
|
|
39
|
+
"plan": ("plan", "tasks"),
|
|
40
|
+
"review": ("review",),
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
# Discovery is approved together with the specification it informed.
|
|
44
|
+
# Legacy changes created before Discovery have no such artifact.
|
|
45
|
+
STAGE_OPTIONAL_ARTIFACTS = {
|
|
46
|
+
"spec": ("discovery",),
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass(frozen=True)
|
|
51
|
+
class ApprovalRecord:
|
|
52
|
+
"""A human approval bound to one or more artifact digests."""
|
|
53
|
+
|
|
54
|
+
stage: str
|
|
55
|
+
by: str
|
|
56
|
+
at: str
|
|
57
|
+
artifacts: dict[str, str] = field(default_factory=dict)
|
|
58
|
+
architecture_digest: str = ""
|
|
59
|
+
|
|
60
|
+
def to_dict(self) -> dict[str, Any]:
|
|
61
|
+
return {
|
|
62
|
+
"by": self.by,
|
|
63
|
+
"at": self.at,
|
|
64
|
+
"artifacts": dict(self.artifacts),
|
|
65
|
+
"architecture_digest": self.architecture_digest,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def from_dict(
|
|
70
|
+
cls,
|
|
71
|
+
stage: str,
|
|
72
|
+
data: dict[str, Any],
|
|
73
|
+
) -> ApprovalRecord:
|
|
74
|
+
artifacts = data.get("artifacts") or {}
|
|
75
|
+
if not isinstance(artifacts, dict):
|
|
76
|
+
artifacts = {}
|
|
77
|
+
return cls(
|
|
78
|
+
stage=stage,
|
|
79
|
+
by=str(data.get("by", "user")),
|
|
80
|
+
at=str(data.get("at", "")),
|
|
81
|
+
artifacts={
|
|
82
|
+
str(key): str(value)
|
|
83
|
+
for key, value in artifacts.items()
|
|
84
|
+
},
|
|
85
|
+
architecture_digest=str(data.get("architecture_digest", "")),
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@dataclass
|
|
90
|
+
class WorkflowState:
|
|
91
|
+
"""Current projection of a change workflow."""
|
|
92
|
+
|
|
93
|
+
version: int = 1
|
|
94
|
+
spec_id: str = ""
|
|
95
|
+
change: str = ""
|
|
96
|
+
status: str = WorkflowStatus.DRAFT.value
|
|
97
|
+
created_at: str = ""
|
|
98
|
+
updated_at: str = ""
|
|
99
|
+
last_event_seq: int = 0
|
|
100
|
+
artifacts: dict[str, ArtifactRecord] = field(default_factory=dict)
|
|
101
|
+
approvals: dict[str, ApprovalRecord] = field(default_factory=dict)
|
|
102
|
+
current_implementation: str | None = None
|
|
103
|
+
current_review: str | None = None
|
|
104
|
+
|
|
105
|
+
def to_dict(self) -> dict[str, Any]:
|
|
106
|
+
return {
|
|
107
|
+
"version": self.version,
|
|
108
|
+
"spec_id": self.spec_id,
|
|
109
|
+
"change": self.change,
|
|
110
|
+
"status": self.status,
|
|
111
|
+
"created_at": self.created_at,
|
|
112
|
+
"updated_at": self.updated_at,
|
|
113
|
+
"last_event_seq": self.last_event_seq,
|
|
114
|
+
"artifacts": {
|
|
115
|
+
key: record.to_dict()
|
|
116
|
+
for key, record in sorted(self.artifacts.items())
|
|
117
|
+
},
|
|
118
|
+
"approvals": {
|
|
119
|
+
key: approval.to_dict()
|
|
120
|
+
for key, approval in sorted(self.approvals.items())
|
|
121
|
+
},
|
|
122
|
+
"current_implementation": self.current_implementation,
|
|
123
|
+
"current_review": self.current_review,
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
@classmethod
|
|
127
|
+
def from_dict(cls, data: dict[str, Any]) -> WorkflowState:
|
|
128
|
+
artifacts_data = data.get("artifacts") or {}
|
|
129
|
+
approvals_data = data.get("approvals") or {}
|
|
130
|
+
artifacts: dict[str, ArtifactRecord] = {}
|
|
131
|
+
approvals: dict[str, ApprovalRecord] = {}
|
|
132
|
+
if isinstance(artifacts_data, dict):
|
|
133
|
+
for key, value in artifacts_data.items():
|
|
134
|
+
if isinstance(value, dict):
|
|
135
|
+
artifacts[str(key)] = ArtifactRecord.from_dict(value)
|
|
136
|
+
if isinstance(approvals_data, dict):
|
|
137
|
+
for key, value in approvals_data.items():
|
|
138
|
+
if isinstance(value, dict):
|
|
139
|
+
approvals[str(key)] = ApprovalRecord.from_dict(
|
|
140
|
+
str(key),
|
|
141
|
+
value,
|
|
142
|
+
)
|
|
143
|
+
return cls(
|
|
144
|
+
version=int(data.get("version", 1)),
|
|
145
|
+
spec_id=str(data.get("spec_id", "")),
|
|
146
|
+
change=str(data.get("change", "")),
|
|
147
|
+
status=str(data.get("status", WorkflowStatus.DRAFT.value)),
|
|
148
|
+
created_at=str(data.get("created_at", "")),
|
|
149
|
+
updated_at=str(data.get("updated_at", "")),
|
|
150
|
+
last_event_seq=int(data.get("last_event_seq", 0)),
|
|
151
|
+
artifacts=artifacts,
|
|
152
|
+
approvals=approvals,
|
|
153
|
+
current_implementation=_optional_string(
|
|
154
|
+
data.get("current_implementation")
|
|
155
|
+
),
|
|
156
|
+
current_review=_optional_string(data.get("current_review")),
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _optional_string(value: Any) -> str | None:
|
|
161
|
+
if value is None:
|
|
162
|
+
return None
|
|
163
|
+
text = str(value)
|
|
164
|
+
return text or None
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""Allowed workflow transitions and their artifact requirements."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from devflow.core.artifacts.models import ArtifactStatus
|
|
6
|
+
from devflow.core.state.errors import InvalidTransitionError
|
|
7
|
+
from devflow.core.state.models import WorkflowState, WorkflowStatus
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
ALLOWED_TRANSITIONS: dict[str, frozenset[str]] = {
|
|
11
|
+
WorkflowStatus.DRAFT.value: frozenset(
|
|
12
|
+
{
|
|
13
|
+
WorkflowStatus.AWAITING_CLARIFICATION.value,
|
|
14
|
+
WorkflowStatus.SPEC_APPROVED.value,
|
|
15
|
+
}
|
|
16
|
+
),
|
|
17
|
+
WorkflowStatus.AWAITING_CLARIFICATION.value: frozenset(
|
|
18
|
+
{WorkflowStatus.DRAFT.value}
|
|
19
|
+
),
|
|
20
|
+
WorkflowStatus.SPEC_APPROVED.value: frozenset(
|
|
21
|
+
{WorkflowStatus.PLANNED.value}
|
|
22
|
+
),
|
|
23
|
+
WorkflowStatus.PLANNED.value: frozenset(
|
|
24
|
+
{WorkflowStatus.PLAN_APPROVED.value}
|
|
25
|
+
),
|
|
26
|
+
WorkflowStatus.PLAN_APPROVED.value: frozenset(
|
|
27
|
+
{WorkflowStatus.IMPLEMENTING.value}
|
|
28
|
+
),
|
|
29
|
+
WorkflowStatus.IMPLEMENTING.value: frozenset(
|
|
30
|
+
{
|
|
31
|
+
WorkflowStatus.IMPLEMENTED.value,
|
|
32
|
+
WorkflowStatus.CHANGES_REQUESTED.value,
|
|
33
|
+
}
|
|
34
|
+
),
|
|
35
|
+
WorkflowStatus.IMPLEMENTED.value: frozenset(
|
|
36
|
+
{WorkflowStatus.REVIEWED.value}
|
|
37
|
+
),
|
|
38
|
+
WorkflowStatus.REVIEWED.value: frozenset(
|
|
39
|
+
{
|
|
40
|
+
WorkflowStatus.REVIEW_APPROVED.value,
|
|
41
|
+
WorkflowStatus.CHANGES_REQUESTED.value,
|
|
42
|
+
}
|
|
43
|
+
),
|
|
44
|
+
WorkflowStatus.REVIEW_APPROVED.value: frozenset(
|
|
45
|
+
{WorkflowStatus.DONE.value}
|
|
46
|
+
),
|
|
47
|
+
WorkflowStatus.CHANGES_REQUESTED.value: frozenset(
|
|
48
|
+
{WorkflowStatus.IMPLEMENTING.value}
|
|
49
|
+
),
|
|
50
|
+
WorkflowStatus.DONE.value: frozenset(),
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
TARGET_ARTIFACTS: dict[str, tuple[str, ...]] = {
|
|
54
|
+
WorkflowStatus.SPEC_APPROVED.value: ("spec",),
|
|
55
|
+
WorkflowStatus.PLANNED.value: ("spec",),
|
|
56
|
+
WorkflowStatus.PLAN_APPROVED.value: ("spec", "plan"),
|
|
57
|
+
WorkflowStatus.IMPLEMENTING.value: ("spec", "plan"),
|
|
58
|
+
WorkflowStatus.IMPLEMENTED.value: ("spec", "plan"),
|
|
59
|
+
WorkflowStatus.REVIEWED.value: ("spec", "plan"),
|
|
60
|
+
WorkflowStatus.REVIEW_APPROVED.value: ("spec", "plan", "review"),
|
|
61
|
+
WorkflowStatus.CHANGES_REQUESTED.value: ("spec", "plan"),
|
|
62
|
+
WorkflowStatus.DONE.value: ("spec", "plan", "review"),
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def can_transition(current: str, target: str) -> bool:
|
|
67
|
+
"""Return whether a workflow may move from one state to another."""
|
|
68
|
+
if current == target:
|
|
69
|
+
return True
|
|
70
|
+
return target in ALLOWED_TRANSITIONS.get(current, frozenset())
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def ensure_transition(current: str, target: str) -> None:
|
|
74
|
+
"""Raise when a workflow transition is not allowed."""
|
|
75
|
+
if can_transition(current, target):
|
|
76
|
+
return
|
|
77
|
+
msg = (
|
|
78
|
+
f"Invalid workflow transition: {current} -> {target}. "
|
|
79
|
+
"Resolve the current stage before advancing."
|
|
80
|
+
)
|
|
81
|
+
raise InvalidTransitionError(msg)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def required_artifacts(target: str) -> tuple[str, ...]:
|
|
85
|
+
"""Return artifact kinds required before entering a target state."""
|
|
86
|
+
return TARGET_ARTIFACTS.get(target, ())
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def artifact_blockers(
|
|
90
|
+
state: WorkflowState,
|
|
91
|
+
target: str,
|
|
92
|
+
) -> tuple[str, ...]:
|
|
93
|
+
"""Return artifact kinds that are not approved for a target state."""
|
|
94
|
+
blockers: list[str] = []
|
|
95
|
+
for kind in required_artifacts(target):
|
|
96
|
+
record = state.artifacts.get(kind)
|
|
97
|
+
if record is None:
|
|
98
|
+
blockers.append(f"{kind} (missing)")
|
|
99
|
+
elif record.status != ArtifactStatus.APPROVED.value:
|
|
100
|
+
blockers.append(f"{kind} ({record.status})")
|
|
101
|
+
return tuple(blockers)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def ensure_artifacts_ready(state: WorkflowState, target: str) -> None:
|
|
105
|
+
"""Raise when required artifacts are not approved."""
|
|
106
|
+
blockers = artifact_blockers(state, target)
|
|
107
|
+
if not blockers:
|
|
108
|
+
return
|
|
109
|
+
msg = (
|
|
110
|
+
f"Cannot enter {target}: required artifacts are not approved: "
|
|
111
|
+
+ ", ".join(blockers)
|
|
112
|
+
+ "."
|
|
113
|
+
)
|
|
114
|
+
raise InvalidTransitionError(msg)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Project profile data model."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(frozen=True)
|
|
9
|
+
class Profile:
|
|
10
|
+
"""A reusable project profile bundled with DevFlow."""
|
|
11
|
+
|
|
12
|
+
name: str
|
|
13
|
+
version: str
|
|
14
|
+
description: str
|
|
15
|
+
path: Path
|
|
16
|
+
constitution: tuple[str, ...] = field(default_factory=tuple)
|
|
17
|
+
templates: tuple[str, ...] = field(default_factory=tuple)
|
|
18
|
+
technologies: tuple[str, ...] = field(default_factory=tuple)
|
|
19
|
+
detection_files: tuple[str, ...] = field(default_factory=tuple)
|
|
20
|
+
detection_dependencies: tuple[str, ...] = field(default_factory=tuple)
|
|
21
|
+
detection_indicators: tuple[str, ...] = field(default_factory=tuple)
|
|
22
|
+
|
|
23
|
+
@classmethod
|
|
24
|
+
def from_manifest(
|
|
25
|
+
cls,
|
|
26
|
+
path: Path,
|
|
27
|
+
data: dict[str, Any],
|
|
28
|
+
) -> "Profile":
|
|
29
|
+
"""Build a profile from a profile.yaml manifest."""
|
|
30
|
+
detection = data.get("detection") or {}
|
|
31
|
+
if not isinstance(detection, dict):
|
|
32
|
+
msg = "Profile detection must be a mapping."
|
|
33
|
+
raise ValueError(msg)
|
|
34
|
+
|
|
35
|
+
name = data.get("name")
|
|
36
|
+
if not isinstance(name, str) or not name:
|
|
37
|
+
msg = "Profile manifest must define a non-empty name."
|
|
38
|
+
raise ValueError(msg)
|
|
39
|
+
|
|
40
|
+
def string_tuple(value: Any) -> tuple[str, ...]:
|
|
41
|
+
if value is None:
|
|
42
|
+
return ()
|
|
43
|
+
if not isinstance(value, list):
|
|
44
|
+
msg = f"Profile field must be a list: {value!r}"
|
|
45
|
+
raise ValueError(msg)
|
|
46
|
+
return tuple(str(item) for item in value)
|
|
47
|
+
|
|
48
|
+
return cls(
|
|
49
|
+
name=name,
|
|
50
|
+
version=str(data.get("version", "0.0.0")),
|
|
51
|
+
description=str(data.get("description", "")),
|
|
52
|
+
path=path,
|
|
53
|
+
constitution=string_tuple(data.get("constitution")),
|
|
54
|
+
templates=string_tuple(data.get("templates")),
|
|
55
|
+
technologies=string_tuple(data.get("technologies")),
|
|
56
|
+
detection_files=string_tuple(detection.get("files")),
|
|
57
|
+
detection_dependencies=string_tuple(
|
|
58
|
+
detection.get("dependencies")
|
|
59
|
+
),
|
|
60
|
+
detection_indicators=string_tuple(
|
|
61
|
+
detection.get("indicators")
|
|
62
|
+
),
|
|
63
|
+
)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Detected project data model and project modes."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ProjectMode(str, Enum):
|
|
9
|
+
"""Context used to prepare one specification."""
|
|
10
|
+
|
|
11
|
+
EXISTING = "existing"
|
|
12
|
+
GREENFIELD = "greenfield"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
PROJECT_MODES = tuple(mode.value for mode in ProjectMode)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def canonical_project_mode(value: str) -> str:
|
|
19
|
+
"""Normalize a project mode name."""
|
|
20
|
+
return value.strip().lower().replace("_", "-")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class Project:
|
|
25
|
+
"""A software project inspected by DevFlow."""
|
|
26
|
+
|
|
27
|
+
root: Path
|
|
28
|
+
name: str
|
|
29
|
+
project_type: str
|
|
30
|
+
languages: list[str] = field(default_factory=list)
|
|
31
|
+
frameworks: list[str] = field(default_factory=list)
|
|
32
|
+
databases: list[str] = field(default_factory=list)
|
|
33
|
+
dependencies: set[str] = field(default_factory=set)
|
|
34
|
+
manifests: list[str] = field(default_factory=list)
|
|
35
|
+
source_files: list[str] = field(default_factory=list)
|
|
36
|
+
profiles: list[str] = field(default_factory=list)
|
|
37
|
+
|
|
38
|
+
def to_config(self, profiles: list[str]) -> dict:
|
|
39
|
+
"""Return the persistent project section for config.yaml."""
|
|
40
|
+
return {
|
|
41
|
+
"name": self.name,
|
|
42
|
+
"type": self.project_type,
|
|
43
|
+
"languages": self.languages,
|
|
44
|
+
"frameworks": self.frameworks,
|
|
45
|
+
"databases": self.databases,
|
|
46
|
+
"profiles": profiles,
|
|
47
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# AI and RAG Development Standards
|
|
2
|
+
|
|
3
|
+
## 1. Retrieval Quality
|
|
4
|
+
|
|
5
|
+
- Define measurable retrieval quality expectations.
|
|
6
|
+
- Preserve document metadata used for filtering and citation.
|
|
7
|
+
- Evaluate changes to chunking, embeddings and ranking.
|
|
8
|
+
- Avoid mixing incompatible embedding models or dimensions.
|
|
9
|
+
- Test retrieval with representative queries and edge cases.
|
|
10
|
+
|
|
11
|
+
## 2. Data and Ingestion
|
|
12
|
+
|
|
13
|
+
- Make ingestion idempotent where possible.
|
|
14
|
+
- Track document identity, source and version.
|
|
15
|
+
- Handle malformed, empty and duplicate documents.
|
|
16
|
+
- Respect data privacy, retention and access rules.
|
|
17
|
+
- Avoid logging sensitive document content by default.
|
|
18
|
+
|
|
19
|
+
## 3. Prompts and Generation
|
|
20
|
+
|
|
21
|
+
- Keep prompts versioned and reviewed.
|
|
22
|
+
- Separate system instructions from user content.
|
|
23
|
+
- Treat retrieved content as untrusted input.
|
|
24
|
+
- Require citations when answers depend on retrieved context.
|
|
25
|
+
- Define behavior when no relevant context is found.
|
|
26
|
+
|
|
27
|
+
## 4. Vector Stores
|
|
28
|
+
|
|
29
|
+
- Use the existing vector store abstraction.
|
|
30
|
+
- Consider collection isolation and access control.
|
|
31
|
+
- Define index creation and migration procedures.
|
|
32
|
+
- Handle unavailable or partially available stores.
|
|
33
|
+
- Monitor latency, recall and storage growth.
|
|
34
|
+
|
|
35
|
+
## 5. Model Integrations
|
|
36
|
+
|
|
37
|
+
- Isolate providers behind adapters or service boundaries.
|
|
38
|
+
- Configure timeouts, retries and fallback behavior.
|
|
39
|
+
- Avoid hard-coding model names, keys or endpoints.
|
|
40
|
+
- Track token usage and cost where appropriate.
|
|
41
|
+
- Handle provider errors without exposing secrets.
|
|
42
|
+
|
|
43
|
+
## 6. Evaluation and Testing
|
|
44
|
+
|
|
45
|
+
- Add offline evaluation for retrieval and answer quality.
|
|
46
|
+
- Use deterministic fixtures for unit tests.
|
|
47
|
+
- Separate evaluation data from production data.
|
|
48
|
+
- Track regressions when changing models or prompts.
|
|
49
|
+
- Document assumptions and known limitations.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: ai-rag
|
|
2
|
+
version: "1.0.0"
|
|
3
|
+
description: AI and retrieval-augmented generation profile.
|
|
4
|
+
|
|
5
|
+
detection:
|
|
6
|
+
files: []
|
|
7
|
+
dependencies:
|
|
8
|
+
- langchain
|
|
9
|
+
- llama-index
|
|
10
|
+
- chromadb
|
|
11
|
+
- faiss-cpu
|
|
12
|
+
- qdrant-client
|
|
13
|
+
- sentence-transformers
|
|
14
|
+
indicators:
|
|
15
|
+
- vector_database
|
|
16
|
+
- embeddings
|
|
17
|
+
- retrieval_pipeline
|
|
18
|
+
|
|
19
|
+
constitution:
|
|
20
|
+
- ai-rag-standards.md
|
|
21
|
+
|
|
22
|
+
templates: []
|
|
23
|
+
|
|
24
|
+
technologies:
|
|
25
|
+
- python
|
|
26
|
+
- artificial-intelligence
|
|
27
|
+
- rag
|
|
28
|
+
- vector-databases
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Architecture Guidelines
|
|
2
|
+
|
|
3
|
+
## 1. Design Principles
|
|
4
|
+
|
|
5
|
+
- Apply SOLID principles when they provide clear value.
|
|
6
|
+
- Maintain separation of concerns.
|
|
7
|
+
- Prefer modular and loosely coupled components.
|
|
8
|
+
- Keep business logic independent from infrastructure
|
|
9
|
+
when appropriate.
|
|
10
|
+
- Avoid unnecessary abstractions and complexity.
|
|
11
|
+
|
|
12
|
+
## 2. Business Logic
|
|
13
|
+
|
|
14
|
+
- Place business logic in the component responsible
|
|
15
|
+
for the corresponding domain.
|
|
16
|
+
- Avoid duplicating business rules.
|
|
17
|
+
- Reuse existing services and domain components
|
|
18
|
+
whenever appropriate.
|
|
19
|
+
- Keep controllers and API views focused on
|
|
20
|
+
request handling and response generation.
|
|
21
|
+
|
|
22
|
+
## 3. Data Access
|
|
23
|
+
|
|
24
|
+
- Optimize database access according to the actual
|
|
25
|
+
query requirements.
|
|
26
|
+
- Avoid N+1 query problems.
|
|
27
|
+
- Retrieve only the data required by the operation.
|
|
28
|
+
- Use appropriate database indexes and constraints.
|
|
29
|
+
- Consider transaction boundaries and data integrity.
|
|
30
|
+
|
|
31
|
+
## 4. Integration and Communication
|
|
32
|
+
|
|
33
|
+
- Isolate external integrations behind appropriate
|
|
34
|
+
interfaces or service components.
|
|
35
|
+
- Handle timeouts, connection errors and unexpected
|
|
36
|
+
responses.
|
|
37
|
+
- Consider retries and recovery mechanisms when needed.
|
|
38
|
+
- Avoid tightly coupling internal business logic
|
|
39
|
+
to external API implementations.
|
|
40
|
+
|
|
41
|
+
## 5. Reliability
|
|
42
|
+
|
|
43
|
+
- Consider partial failures and unexpected execution
|
|
44
|
+
states.
|
|
45
|
+
- Preserve data consistency.
|
|
46
|
+
- Avoid duplicate processing where operations can
|
|
47
|
+
be repeated.
|
|
48
|
+
- Implement observability and traceability when
|
|
49
|
+
appropriate.
|
|
50
|
+
|
|
51
|
+
## 6. Performance
|
|
52
|
+
|
|
53
|
+
- Identify actual performance bottlenecks before
|
|
54
|
+
optimizing.
|
|
55
|
+
- Avoid unnecessary database queries and processing.
|
|
56
|
+
- Prefer efficient data access patterns.
|
|
57
|
+
- Consider asynchronous processing for workloads
|
|
58
|
+
that justify it.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Code Standards
|
|
2
|
+
|
|
3
|
+
## 1. General Principles
|
|
4
|
+
|
|
5
|
+
- Follow clean code principles and language conventions.
|
|
6
|
+
- Prioritize readability and maintainability.
|
|
7
|
+
- Use descriptive names for variables, functions and classes.
|
|
8
|
+
- Avoid unnecessary abbreviations.
|
|
9
|
+
- Avoid over-engineering.
|
|
10
|
+
|
|
11
|
+
## 2. Formatting
|
|
12
|
+
|
|
13
|
+
- Maximum line length: 79 characters.
|
|
14
|
+
- Use 2 spaces for indentation.
|
|
15
|
+
- Never use tabs.
|
|
16
|
+
- Keep formatting consistent across the project.
|
|
17
|
+
- If a statement exceeds the maximum line length,
|
|
18
|
+
split it into multiple lines.
|
|
19
|
+
|
|
20
|
+
Note: The 2-space indentation is a project-specific
|
|
21
|
+
convention and overrides the default PEP 8 indentation
|
|
22
|
+
recommendation.
|
|
23
|
+
|
|
24
|
+
## 3. Imports
|
|
25
|
+
|
|
26
|
+
- Use absolute imports within the project.
|
|
27
|
+
- Never use relative imports between project modules.
|
|
28
|
+
- Follow this import order:
|
|
29
|
+
1. Python standard library.
|
|
30
|
+
2. Third-party libraries.
|
|
31
|
+
3. Framework imports, when applicable.
|
|
32
|
+
4. Local project imports.
|
|
33
|
+
- Sort imports alphabetically within each group.
|
|
34
|
+
|
|
35
|
+
Example:
|
|
36
|
+
|
|
37
|
+
import logging
|
|
38
|
+
from typing import Any
|
|
39
|
+
|
|
40
|
+
import requests
|
|
41
|
+
|
|
42
|
+
from framework import transaction
|
|
43
|
+
|
|
44
|
+
from project.models.entity import Entity
|
|
45
|
+
|
|
46
|
+
## 4. Naming Conventions
|
|
47
|
+
|
|
48
|
+
- Use descriptive names for variables and functions.
|
|
49
|
+
- Use PascalCase for classes.
|
|
50
|
+
- Use snake_case for variables, functions and modules.
|
|
51
|
+
- Boolean variables should use prefixes such as:
|
|
52
|
+
is_, has_, can_ or should_.
|
|
53
|
+
- Avoid ambiguous or overly abbreviated names.
|
|
54
|
+
|
|
55
|
+
## 5. Type Hints
|
|
56
|
+
|
|
57
|
+
- Add type hints to function parameters.
|
|
58
|
+
- Add return type annotations to functions.
|
|
59
|
+
- Use typing for complex types when appropriate.
|
|
60
|
+
- Use Any from the typing module when a dynamic
|
|
61
|
+
type is genuinely necessary.
|
|
62
|
+
- Avoid using Any when a more precise type is possible.
|
|
63
|
+
|
|
64
|
+
## 6. Comments and Documentation
|
|
65
|
+
|
|
66
|
+
- Write code, comments, docstrings and technical
|
|
67
|
+
documentation in English.
|
|
68
|
+
- Use Spanish only for user-facing content when needed.
|
|
69
|
+
- Avoid comments that merely repeat the code.
|
|
70
|
+
- Explain complex logic, non-obvious decisions
|
|
71
|
+
and important edge cases.
|
|
72
|
+
- Add docstrings to public APIs, complex functions
|
|
73
|
+
and non-obvious classes.
|
|
74
|
+
- Use Google-style docstrings.
|
|
75
|
+
- Avoid unnecessary docstrings for simple functions
|
|
76
|
+
and standard framework patterns.
|
|
77
|
+
|
|
78
|
+
## 7. Function Design
|
|
79
|
+
|
|
80
|
+
- Keep functions focused on a single responsibility.
|
|
81
|
+
- Avoid unnecessarily large functions.
|
|
82
|
+
- When a function has more than two parameters,
|
|
83
|
+
place each parameter on a separate line.
|
|
84
|
+
- Apply the same multiline formatting when a function
|
|
85
|
+
declaration exceeds the maximum line length.
|