devhive 0.1.23__tar.gz → 0.1.25__tar.gz
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.
- {devhive-0.1.23 → devhive-0.1.25}/MANIFEST.in +2 -0
- {devhive-0.1.23/devhive.egg-info → devhive-0.1.25}/PKG-INFO +8 -2
- {devhive-0.1.23 → devhive-0.1.25}/README.md +6 -0
- {devhive-0.1.23 → devhive-0.1.25/devhive.egg-info}/PKG-INFO +8 -2
- {devhive-0.1.23 → devhive-0.1.25}/devhive.egg-info/SOURCES.txt +3 -1
- {devhive-0.1.23 → devhive-0.1.25}/devhive_cli/main.py +47 -0
- devhive-0.1.25/devhive_cli/templates_backend.py +424 -0
- {devhive-0.1.23 → devhive-0.1.25}/setup.py +2 -2
- devhive-0.1.25/templates/AGENTS.frontend.md +160 -0
- {devhive-0.1.23 → devhive-0.1.25}/LICENSE +0 -0
- {devhive-0.1.23 → devhive-0.1.25}/community_skills/architecture-patterns/SKILL.md +0 -0
- {devhive-0.1.23 → devhive-0.1.25}/community_skills/architecture-patterns/resources/implementation-playbook.md +0 -0
- {devhive-0.1.23 → devhive-0.1.25}/community_skills/backend-arthitect/SKILL.md +0 -0
- {devhive-0.1.23 → devhive-0.1.25}/community_skills/backend-security-coder/SKILL.md +0 -0
- {devhive-0.1.23 → devhive-0.1.25}/community_skills/docker-expert/SKILL.md +0 -0
- {devhive-0.1.23 → devhive-0.1.25}/community_skills/frontend-design/SKILL.md +0 -0
- {devhive-0.1.23 → devhive-0.1.25}/community_skills/frontend-developer/SKILL.md +0 -0
- {devhive-0.1.23 → devhive-0.1.25}/community_skills/theme-factory/SKILL.md +0 -0
- {devhive-0.1.23 → devhive-0.1.25}/community_skills/theme-factory/theme-showcase.pdf +0 -0
- {devhive-0.1.23 → devhive-0.1.25}/community_skills/theme-factory/themes/arctic-frost.md +0 -0
- {devhive-0.1.23 → devhive-0.1.25}/community_skills/theme-factory/themes/botanical-garden.md +0 -0
- {devhive-0.1.23 → devhive-0.1.25}/community_skills/theme-factory/themes/desert-rose.md +0 -0
- {devhive-0.1.23 → devhive-0.1.25}/community_skills/theme-factory/themes/forest-canopy.md +0 -0
- {devhive-0.1.23 → devhive-0.1.25}/community_skills/theme-factory/themes/golden-hour.md +0 -0
- {devhive-0.1.23 → devhive-0.1.25}/community_skills/theme-factory/themes/midnight-galaxy.md +0 -0
- {devhive-0.1.23 → devhive-0.1.25}/community_skills/theme-factory/themes/modern-minimalist.md +0 -0
- {devhive-0.1.23 → devhive-0.1.25}/community_skills/theme-factory/themes/ocean-depths.md +0 -0
- {devhive-0.1.23 → devhive-0.1.25}/community_skills/theme-factory/themes/sunset-boulevard.md +0 -0
- {devhive-0.1.23 → devhive-0.1.25}/community_skills/theme-factory/themes/tech-innovation.md +0 -0
- {devhive-0.1.23 → devhive-0.1.25}/devhive.egg-info/dependency_links.txt +0 -0
- {devhive-0.1.23 → devhive-0.1.25}/devhive.egg-info/entry_points.txt +0 -0
- {devhive-0.1.23 → devhive-0.1.25}/devhive.egg-info/requires.txt +0 -0
- {devhive-0.1.23 → devhive-0.1.25}/devhive.egg-info/top_level.txt +0 -0
- {devhive-0.1.23 → devhive-0.1.25}/devhive_cli/__init__.py +0 -0
- {devhive-0.1.23 → devhive-0.1.25}/setup.cfg +0 -0
- {devhive-0.1.23 → devhive-0.1.25}/skills/devhive_architect/SKILL.md +0 -0
- {devhive-0.1.23 → devhive-0.1.25}/skills/devhive_auditor/SKILL.md +0 -0
- {devhive-0.1.23 → devhive-0.1.25}/skills/devhive_backender/SKILL.md +0 -0
- {devhive-0.1.23 → devhive-0.1.25}/skills/devhive_dba/SKILL.md +0 -0
- {devhive-0.1.23 → devhive-0.1.25}/skills/devhive_designer/SKILL.md +0 -0
- {devhive-0.1.23 → devhive-0.1.25}/skills/devhive_devops/SKILL.md +0 -0
- {devhive-0.1.23 → devhive-0.1.25}/skills/devhive_explorer/SKILL.md +0 -0
- {devhive-0.1.23 → devhive-0.1.25}/skills/devhive_frontender/SKILL.md +0 -0
- {devhive-0.1.23 → devhive-0.1.25}/skills/devhive_orchestrator/SKILL.md +0 -0
- {devhive-0.1.23 → devhive-0.1.25}/skills/devhive_perf/SKILL.md +0 -0
- {devhive-0.1.23 → devhive-0.1.25}/skills/devhive_proposal/SKILL.md +0 -0
- {devhive-0.1.23 → devhive-0.1.25}/skills/devhive_qa/SKILL.md +0 -0
- {devhive-0.1.23 → devhive-0.1.25}/skills/devhive_releaser/SKILL.md +0 -0
- {devhive-0.1.23 → devhive-0.1.25}/skills/devhive_sast/SKILL.md +0 -0
- {devhive-0.1.23 → devhive-0.1.25}/skills/devhive_taskplanner/SKILL.md +0 -0
- {devhive-0.1.23 → devhive-0.1.25}/skills/devhive_techwriter/SKILL.md +0 -0
- {devhive-0.1.23 → devhive-0.1.25}/skills/devhive_workflow.md +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devhive
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.25
|
|
4
4
|
Summary: CLI to manage DevHive skills and autonomous AI agents for OpenCode.
|
|
5
5
|
Home-page: https://github.com/ramBlanco/devhive
|
|
6
6
|
Author: ramBlanco
|
|
7
7
|
Classifier: Programming Language :: Python :: 3
|
|
8
8
|
Classifier: License :: OSI Approved :: MIT License
|
|
9
9
|
Classifier: Operating System :: OS Independent
|
|
10
|
-
Requires-Python: >=3.
|
|
10
|
+
Requires-Python: >=3.7
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
12
|
License-File: LICENSE
|
|
13
13
|
Requires-Dist: click>=8.0.0
|
|
@@ -65,6 +65,12 @@ devhive install
|
|
|
65
65
|
|
|
66
66
|
# Update your existing skills to the latest versions
|
|
67
67
|
devhive update-skill
|
|
68
|
+
|
|
69
|
+
# Initialize a modern Next.js/React frontend AI guidelines file in your current project
|
|
70
|
+
devhive init-frontend
|
|
71
|
+
|
|
72
|
+
# Initialize a Hexagonal Architecture backend AI guidelines file (Interactive Prompt)
|
|
73
|
+
devhive init-backend
|
|
68
74
|
```
|
|
69
75
|
|
|
70
76
|
## Usage (The `@devhive` Agent)
|
|
@@ -42,6 +42,12 @@ devhive install
|
|
|
42
42
|
|
|
43
43
|
# Update your existing skills to the latest versions
|
|
44
44
|
devhive update-skill
|
|
45
|
+
|
|
46
|
+
# Initialize a modern Next.js/React frontend AI guidelines file in your current project
|
|
47
|
+
devhive init-frontend
|
|
48
|
+
|
|
49
|
+
# Initialize a Hexagonal Architecture backend AI guidelines file (Interactive Prompt)
|
|
50
|
+
devhive init-backend
|
|
45
51
|
```
|
|
46
52
|
|
|
47
53
|
## Usage (The `@devhive` Agent)
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devhive
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.25
|
|
4
4
|
Summary: CLI to manage DevHive skills and autonomous AI agents for OpenCode.
|
|
5
5
|
Home-page: https://github.com/ramBlanco/devhive
|
|
6
6
|
Author: ramBlanco
|
|
7
7
|
Classifier: Programming Language :: Python :: 3
|
|
8
8
|
Classifier: License :: OSI Approved :: MIT License
|
|
9
9
|
Classifier: Operating System :: OS Independent
|
|
10
|
-
Requires-Python: >=3.
|
|
10
|
+
Requires-Python: >=3.7
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
12
|
License-File: LICENSE
|
|
13
13
|
Requires-Dist: click>=8.0.0
|
|
@@ -65,6 +65,12 @@ devhive install
|
|
|
65
65
|
|
|
66
66
|
# Update your existing skills to the latest versions
|
|
67
67
|
devhive update-skill
|
|
68
|
+
|
|
69
|
+
# Initialize a modern Next.js/React frontend AI guidelines file in your current project
|
|
70
|
+
devhive init-frontend
|
|
71
|
+
|
|
72
|
+
# Initialize a Hexagonal Architecture backend AI guidelines file (Interactive Prompt)
|
|
73
|
+
devhive init-backend
|
|
68
74
|
```
|
|
69
75
|
|
|
70
76
|
## Usage (The `@devhive` Agent)
|
|
@@ -29,6 +29,7 @@ devhive.egg-info/requires.txt
|
|
|
29
29
|
devhive.egg-info/top_level.txt
|
|
30
30
|
devhive_cli/__init__.py
|
|
31
31
|
devhive_cli/main.py
|
|
32
|
+
devhive_cli/templates_backend.py
|
|
32
33
|
skills/devhive_workflow.md
|
|
33
34
|
skills/devhive_architect/SKILL.md
|
|
34
35
|
skills/devhive_auditor/SKILL.md
|
|
@@ -45,4 +46,5 @@ skills/devhive_qa/SKILL.md
|
|
|
45
46
|
skills/devhive_releaser/SKILL.md
|
|
46
47
|
skills/devhive_sast/SKILL.md
|
|
47
48
|
skills/devhive_taskplanner/SKILL.md
|
|
48
|
-
skills/devhive_techwriter/SKILL.md
|
|
49
|
+
skills/devhive_techwriter/SKILL.md
|
|
50
|
+
templates/AGENTS.frontend.md
|
|
@@ -3,12 +3,14 @@ import shutil
|
|
|
3
3
|
import subprocess
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
import click
|
|
6
|
+
from devhive_cli.templates_backend import generate_backend_agents_md
|
|
6
7
|
|
|
7
8
|
# Paths configuration
|
|
8
9
|
CLI_DIR = Path(__file__).resolve().parent
|
|
9
10
|
ROOT_DIR = CLI_DIR.parent
|
|
10
11
|
LOCAL_SKILLS_DIR = ROOT_DIR / "skills"
|
|
11
12
|
COMMUNITY_SKILLS_DIR = ROOT_DIR / "community_skills"
|
|
13
|
+
TEMPLATES_DIR = ROOT_DIR / "templates"
|
|
12
14
|
TARGET_DIR = Path.home() / ".config" / "opencode" / "skills"
|
|
13
15
|
|
|
14
16
|
@click.group()
|
|
@@ -75,5 +77,50 @@ def update_skill():
|
|
|
75
77
|
install_remote_skills()
|
|
76
78
|
click.secho("\nUpdate Complete! 🚀", fg="green", bold=True)
|
|
77
79
|
|
|
80
|
+
@cli.command()
|
|
81
|
+
def init_frontend():
|
|
82
|
+
"""Initialize a modern frontend AGENTS.md template in the current directory."""
|
|
83
|
+
template_path = TEMPLATES_DIR / "AGENTS.frontend.md"
|
|
84
|
+
dest_path = Path.cwd() / "AGENTS.md"
|
|
85
|
+
|
|
86
|
+
if not template_path.exists():
|
|
87
|
+
click.secho("Error: Frontend template not found in the devhive package.", fg="red")
|
|
88
|
+
return
|
|
89
|
+
|
|
90
|
+
if dest_path.exists():
|
|
91
|
+
click.secho("AGENTS.md already exists in this directory. Aborting to prevent overwrite.", fg="yellow")
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
shutil.copy2(template_path, dest_path)
|
|
95
|
+
click.secho("✅ Successfully created AGENTS.md with modern Next.js/React frontend guidelines!", fg="green", bold=True)
|
|
96
|
+
|
|
97
|
+
@cli.command()
|
|
98
|
+
def init_backend():
|
|
99
|
+
"""Initialize a modern backend AGENTS.md template with Hexagonal Architecture."""
|
|
100
|
+
dest_path = Path.cwd() / "AGENTS.md"
|
|
101
|
+
|
|
102
|
+
if dest_path.exists():
|
|
103
|
+
click.secho("AGENTS.md already exists in this directory. Aborting to prevent overwrite.", fg="yellow")
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
language = click.prompt("Which backend language?", type=click.Choice(['python', 'node']))
|
|
107
|
+
|
|
108
|
+
use_di = False
|
|
109
|
+
if language == 'python':
|
|
110
|
+
framework = click.prompt("Which framework?", type=click.Choice(['fastapi', 'django', 'none']))
|
|
111
|
+
else:
|
|
112
|
+
framework = click.prompt("Which framework?", type=click.Choice(['express', 'nestjs', 'none']))
|
|
113
|
+
if framework != 'nestjs':
|
|
114
|
+
use_di = click.confirm("Do you want to use InversifyJS for Dependency Injection?")
|
|
115
|
+
|
|
116
|
+
iac = click.prompt("Which IaC tool are you using?", type=click.Choice(['cdk', 'terraform', 'serverless']))
|
|
117
|
+
|
|
118
|
+
md_content = generate_backend_agents_md(language, framework, iac, use_di)
|
|
119
|
+
|
|
120
|
+
with open(dest_path, "w", encoding="utf-8") as f:
|
|
121
|
+
f.write(md_content)
|
|
122
|
+
|
|
123
|
+
click.secho("✅ Successfully created AGENTS.md with Hexagonal Architecture guidelines!", fg="green", bold=True)
|
|
124
|
+
|
|
78
125
|
if __name__ == "__main__":
|
|
79
126
|
cli()
|
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
def generate_backend_agents_md(language: str, framework: str, iac: str, use_di: bool) -> str:
|
|
2
|
+
stack_list = []
|
|
3
|
+
if language == 'python':
|
|
4
|
+
stack_list.append("- **Language**: Python 3.10+ (Strict Type Hinting)")
|
|
5
|
+
if framework == 'fastapi':
|
|
6
|
+
stack_list.append("- **Framework**: FastAPI")
|
|
7
|
+
elif framework == 'django':
|
|
8
|
+
stack_list.append("- **Framework**: Django")
|
|
9
|
+
else:
|
|
10
|
+
stack_list.append("- **Framework**: None (Pure Python / Lambdas)")
|
|
11
|
+
else:
|
|
12
|
+
stack_list.append("- **Language**: Node.js + TypeScript (Strict Mode)")
|
|
13
|
+
if framework == 'express':
|
|
14
|
+
stack_list.append("- **Framework**: Express")
|
|
15
|
+
elif framework == 'nestjs':
|
|
16
|
+
stack_list.append("- **Framework**: NestJS (Built-in DI)")
|
|
17
|
+
use_di = False # NestJS has its own DI
|
|
18
|
+
else:
|
|
19
|
+
stack_list.append("- **Framework**: None (Pure Node.js / Lambdas)")
|
|
20
|
+
|
|
21
|
+
if use_di:
|
|
22
|
+
stack_list.append("- **Dependency Injection**: InversifyJS")
|
|
23
|
+
|
|
24
|
+
if iac == 'cdk':
|
|
25
|
+
stack_list.append("- **Infrastructure as Code**: AWS CDK")
|
|
26
|
+
elif iac == 'terraform':
|
|
27
|
+
stack_list.append("- **Infrastructure as Code**: Terraform")
|
|
28
|
+
else:
|
|
29
|
+
stack_list.append("- **Infrastructure as Code**: Serverless Framework")
|
|
30
|
+
|
|
31
|
+
stack_section = "\n".join(stack_list)
|
|
32
|
+
|
|
33
|
+
# Base structure
|
|
34
|
+
md = f"""# Backend Architecture & AI Agent Guidelines
|
|
35
|
+
|
|
36
|
+
This `AGENTS.md` (or `GUIDELINES.md`) file serves as the strict instruction manual for OpenCode and DevHive agents. It defines the technical stack, architectural patterns, and coding conventions for this backend project.
|
|
37
|
+
|
|
38
|
+
## 🛠️ 1. Tech Stack
|
|
39
|
+
{stack_section}
|
|
40
|
+
|
|
41
|
+
## 🧠 2. OpenCode Skills to Load
|
|
42
|
+
Agents **MUST** load the following specialized skills before executing tasks:
|
|
43
|
+
- `architecture-patterns`: For maintaining clean separation of concerns and Hexagonal Architecture.
|
|
44
|
+
- `backend-architect`: For scalable API design and domain modeling.
|
|
45
|
+
- `backend-security-coder`: For secure coding practices, input validation, and auth.
|
|
46
|
+
- `devhive-devops`: For {iac.upper()} infrastructure setup.
|
|
47
|
+
|
|
48
|
+
## 📂 3. Project Structure (Hexagonal Architecture)
|
|
49
|
+
For maximum scalability, testability, and maintainability, the project MUST strictly follow Hexagonal Architecture (Ports & Adapters).
|
|
50
|
+
|
|
51
|
+
```text
|
|
52
|
+
src/
|
|
53
|
+
├── domain/ # ENTERPRISE BUSINESS RULES (No external dependencies)
|
|
54
|
+
│ ├── entities/ # Pure domain models
|
|
55
|
+
│ └── exceptions/ # Domain-specific errors
|
|
56
|
+
├── application/ # APPLICATION BUSINESS RULES (Use Cases & Ports)
|
|
57
|
+
│ ├── use_cases/ # Orchestrates domain logic
|
|
58
|
+
│ └── ports/ # Interfaces for outbound communication (Repositories, External APIs)
|
|
59
|
+
├── infrastructure/ # ADAPTERS (Database, External Services, Third-party libs)
|
|
60
|
+
│ ├── database/ # Concrete implementation of repository ports
|
|
61
|
+
│ └── di/ # Dependency Injection container/setup
|
|
62
|
+
└── presentation/ # ENTRY POINTS (Controllers, REST APIs, GraphQL, Lambdas)
|
|
63
|
+
└── controllers/ # Parses HTTP requests and calls use cases
|
|
64
|
+
|
|
65
|
+
infrastructure/ # IaC DEFINITIONS (Outside src/)
|
|
66
|
+
└── # {iac.upper()} configurations
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## 📐 4. Architectural Rules & Clean Code (CRITICAL)
|
|
70
|
+
|
|
71
|
+
1. **The Dependency Rule**: Source code dependencies MUST only point inward, toward the `domain/`.
|
|
72
|
+
- `domain/` knows nothing about anything else.
|
|
73
|
+
- `application/` depends only on `domain/`.
|
|
74
|
+
- `infrastructure/` and `presentation/` depend on `application/` and `domain/`.
|
|
75
|
+
- **Never import an infrastructure detail (like a DB model) into a use case.**
|
|
76
|
+
|
|
77
|
+
2. **Ports and Adapters**:
|
|
78
|
+
- Outbound communication (like DB queries) MUST be defined as an interface (Port) in the `application/` layer.
|
|
79
|
+
- The actual database logic MUST be implemented as an Adapter in the `infrastructure/` layer.
|
|
80
|
+
|
|
81
|
+
3. **Dependency Injection**:
|
|
82
|
+
- Use Cases must receive their dependencies (Ports) via constructor injection.
|
|
83
|
+
- The Presentation layer resolves these dependencies and passes them to the Use Cases.
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
if language == 'node' and use_di:
|
|
87
|
+
md += """
|
|
88
|
+
4. **InversifyJS Usage**:
|
|
89
|
+
- Decorate concrete adapters in `infrastructure/` with `@injectable()`.
|
|
90
|
+
- Bind interfaces to symbols in an `inversify.config.ts` container file.
|
|
91
|
+
- Use the container in the `presentation/` layer to resolve use cases.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
md += "\n## 💻 5. Code Examples\n\n"
|
|
95
|
+
|
|
96
|
+
if language == 'python':
|
|
97
|
+
if framework == 'fastapi':
|
|
98
|
+
md += """### Example: FastAPI + Hexagonal Architecture
|
|
99
|
+
|
|
100
|
+
**1. Port (Application Layer)**
|
|
101
|
+
*File: `src/application/ports/user_repository.py`*
|
|
102
|
+
```python
|
|
103
|
+
from typing import Protocol
|
|
104
|
+
from src.domain.entities.user import User
|
|
105
|
+
|
|
106
|
+
class UserRepository(Protocol):
|
|
107
|
+
def get_by_id(self, user_id: str) -> User | None:
|
|
108
|
+
...
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**2. Adapter (Infrastructure Layer)**
|
|
112
|
+
*File: `src/infrastructure/database/postgres_user_repository.py`*
|
|
113
|
+
```python
|
|
114
|
+
from src.application.ports.user_repository import UserRepository
|
|
115
|
+
from src.domain.entities.user import User
|
|
116
|
+
|
|
117
|
+
class PostgresUserRepository:
|
|
118
|
+
def __init__(self, db_session):
|
|
119
|
+
self.db = db_session
|
|
120
|
+
|
|
121
|
+
def get_by_id(self, user_id: str) -> User | None:
|
|
122
|
+
# DB logic here returning Domain Entity
|
|
123
|
+
return User(id=user_id, name="Alice")
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**3. Use Case (Application Layer)**
|
|
127
|
+
*File: `src/application/use_cases/get_user.py`*
|
|
128
|
+
```python
|
|
129
|
+
from src.application.ports.user_repository import UserRepository
|
|
130
|
+
from src.domain.entities.user import User
|
|
131
|
+
from src.domain.exceptions import UserNotFoundError
|
|
132
|
+
|
|
133
|
+
class GetUserUseCase:
|
|
134
|
+
def __init__(self, user_repo: UserRepository):
|
|
135
|
+
self.user_repo = user_repo
|
|
136
|
+
|
|
137
|
+
def execute(self, user_id: str) -> User:
|
|
138
|
+
user = self.user_repo.get_by_id(user_id)
|
|
139
|
+
if not user:
|
|
140
|
+
raise UserNotFoundError(f"User {user_id} not found")
|
|
141
|
+
return user
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**4. Controller (Presentation Layer)**
|
|
145
|
+
*File: `src/presentation/controllers/user_controller.py`*
|
|
146
|
+
```python
|
|
147
|
+
from fastapi import APIRouter, Depends, HTTPException
|
|
148
|
+
from src.application.use_cases.get_user import GetUserUseCase
|
|
149
|
+
from src.infrastructure.database.postgres_user_repository import PostgresUserRepository
|
|
150
|
+
from src.domain.exceptions import UserNotFoundError
|
|
151
|
+
|
|
152
|
+
router = APIRouter()
|
|
153
|
+
|
|
154
|
+
# Poor man's DI for FastAPI
|
|
155
|
+
def get_user_use_case() -> GetUserUseCase:
|
|
156
|
+
repo = PostgresUserRepository(db_session=None)
|
|
157
|
+
return GetUserUseCase(user_repo=repo)
|
|
158
|
+
|
|
159
|
+
@router.get("/users/{user_id}")
|
|
160
|
+
def get_user(user_id: str, use_case: GetUserUseCase = Depends(get_user_use_case)):
|
|
161
|
+
try:
|
|
162
|
+
return use_case.execute(user_id)
|
|
163
|
+
except UserNotFoundError as e:
|
|
164
|
+
raise HTTPException(status_code=404, detail=str(e))
|
|
165
|
+
```
|
|
166
|
+
"""
|
|
167
|
+
elif framework == 'django':
|
|
168
|
+
md += """### Example: Django + Hexagonal Architecture
|
|
169
|
+
|
|
170
|
+
**Note on Django**: Do NOT put business logic in Django Views or Models. Use Django ONLY as the `infrastructure/` and `presentation/` layers.
|
|
171
|
+
|
|
172
|
+
**1. Port (Application Layer)**
|
|
173
|
+
*File: `src/application/ports/user_repository.py`*
|
|
174
|
+
```python
|
|
175
|
+
import abc
|
|
176
|
+
from src.domain.entities.user import User
|
|
177
|
+
|
|
178
|
+
class UserRepository(abc.ABC):
|
|
179
|
+
@abc.abstractmethod
|
|
180
|
+
def get_by_id(self, user_id: str) -> User | None:
|
|
181
|
+
pass
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**2. Adapter (Infrastructure Layer)**
|
|
185
|
+
*File: `src/infrastructure/database/django_user_repository.py`*
|
|
186
|
+
```python
|
|
187
|
+
from src.application.ports.user_repository import UserRepository
|
|
188
|
+
from src.domain.entities.user import User
|
|
189
|
+
from myapp.models import DjangoUserModel # Django ORM Model
|
|
190
|
+
|
|
191
|
+
class DjangoUserRepository(UserRepository):
|
|
192
|
+
def get_by_id(self, user_id: str) -> User | None:
|
|
193
|
+
try:
|
|
194
|
+
orm_user = DjangoUserModel.objects.get(id=user_id)
|
|
195
|
+
return User(id=str(orm_user.id), name=orm_user.name) # Map ORM to Domain
|
|
196
|
+
except DjangoUserModel.DoesNotExist:
|
|
197
|
+
return None
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**3. Controller (Presentation Layer)**
|
|
201
|
+
*File: `src/presentation/views/user_views.py`*
|
|
202
|
+
```python
|
|
203
|
+
from django.http import JsonResponse
|
|
204
|
+
from src.application.use_cases.get_user import GetUserUseCase
|
|
205
|
+
from src.infrastructure.database.django_user_repository import DjangoUserRepository
|
|
206
|
+
from src.domain.exceptions import UserNotFoundError
|
|
207
|
+
|
|
208
|
+
def get_user_view(request, user_id: str):
|
|
209
|
+
repo = DjangoUserRepository()
|
|
210
|
+
use_case = GetUserUseCase(user_repo=repo)
|
|
211
|
+
|
|
212
|
+
try:
|
|
213
|
+
user = use_case.execute(user_id)
|
|
214
|
+
return JsonResponse({"id": user.id, "name": user.name})
|
|
215
|
+
except UserNotFoundError as e:
|
|
216
|
+
return JsonResponse({"error": str(e)}, status=404)
|
|
217
|
+
```
|
|
218
|
+
"""
|
|
219
|
+
else:
|
|
220
|
+
md += """### Example: Pure Python + Hexagonal Architecture
|
|
221
|
+
|
|
222
|
+
**1. Port (Application Layer)**
|
|
223
|
+
*File: `src/application/ports/user_repository.py`*
|
|
224
|
+
```python
|
|
225
|
+
from typing import Protocol
|
|
226
|
+
from src.domain.entities.user import User
|
|
227
|
+
|
|
228
|
+
class UserRepository(Protocol):
|
|
229
|
+
def get_by_id(self, user_id: str) -> User | None:
|
|
230
|
+
...
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
**2. Use Case (Application Layer)**
|
|
234
|
+
*File: `src/application/use_cases/get_user.py`*
|
|
235
|
+
```python
|
|
236
|
+
from src.application.ports.user_repository import UserRepository
|
|
237
|
+
from src.domain.entities.user import User
|
|
238
|
+
|
|
239
|
+
class GetUserUseCase:
|
|
240
|
+
def __init__(self, user_repo: UserRepository):
|
|
241
|
+
self.user_repo = user_repo
|
|
242
|
+
|
|
243
|
+
def execute(self, user_id: str) -> User:
|
|
244
|
+
return self.user_repo.get_by_id(user_id)
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**3. Handler (Presentation Layer - Lambda)**
|
|
248
|
+
*File: `src/presentation/handlers/user_handler.py`*
|
|
249
|
+
```python
|
|
250
|
+
from src.application.use_cases.get_user import GetUserUseCase
|
|
251
|
+
from src.infrastructure.database.dynamo_user_repository import DynamoUserRepository
|
|
252
|
+
|
|
253
|
+
# Initialize dependencies at the module level for Lambda cold starts
|
|
254
|
+
repo = DynamoUserRepository()
|
|
255
|
+
use_case = GetUserUseCase(user_repo=repo)
|
|
256
|
+
|
|
257
|
+
def lambda_handler(event, context):
|
|
258
|
+
user_id = event.get('pathParameters', {}).get('userId')
|
|
259
|
+
user = use_case.execute(user_id)
|
|
260
|
+
return {"statusCode": 200, "body": user.to_json()}
|
|
261
|
+
```
|
|
262
|
+
"""
|
|
263
|
+
else: # Node.js
|
|
264
|
+
if use_di:
|
|
265
|
+
md += """### Example: Node.js + TypeScript + InversifyJS
|
|
266
|
+
|
|
267
|
+
**1. Port (Application Layer)**
|
|
268
|
+
*File: `src/application/ports/IUserRepository.ts`*
|
|
269
|
+
```typescript
|
|
270
|
+
import { User } from "../../domain/entities/User";
|
|
271
|
+
|
|
272
|
+
export const TYPES = {
|
|
273
|
+
IUserRepository: Symbol.for("IUserRepository"),
|
|
274
|
+
GetUserUseCase: Symbol.for("GetUserUseCase"),
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
export interface IUserRepository {
|
|
278
|
+
getById(userId: string): Promise<User | null>;
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
**2. Adapter (Infrastructure Layer)**
|
|
283
|
+
*File: `src/infrastructure/database/PostgresUserRepository.ts`*
|
|
284
|
+
```typescript
|
|
285
|
+
import { injectable } from "inversify";
|
|
286
|
+
import "reflect-metadata";
|
|
287
|
+
import { IUserRepository } from "../../application/ports/IUserRepository";
|
|
288
|
+
import { User } from "../../domain/entities/User";
|
|
289
|
+
|
|
290
|
+
@injectable()
|
|
291
|
+
export class PostgresUserRepository implements IUserRepository {
|
|
292
|
+
async getById(userId: string): Promise<User | null> {
|
|
293
|
+
// DB Logic returning Domain Entity
|
|
294
|
+
return new User(userId, "Alice");
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
**3. Use Case (Application Layer)**
|
|
300
|
+
*File: `src/application/useCases/GetUserUseCase.ts`*
|
|
301
|
+
```typescript
|
|
302
|
+
import { inject, injectable } from "inversify";
|
|
303
|
+
import "reflect-metadata";
|
|
304
|
+
import { IUserRepository, TYPES } from "../ports/IUserRepository";
|
|
305
|
+
import { User } from "../../domain/entities/User";
|
|
306
|
+
|
|
307
|
+
@injectable()
|
|
308
|
+
export class GetUserUseCase {
|
|
309
|
+
constructor(
|
|
310
|
+
@inject(TYPES.IUserRepository) private userRepository: IUserRepository
|
|
311
|
+
) {}
|
|
312
|
+
|
|
313
|
+
async execute(userId: string): Promise<User> {
|
|
314
|
+
const user = await this.userRepository.getById(userId);
|
|
315
|
+
if (!user) throw new Error("User not found");
|
|
316
|
+
return user;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
**4. DI Container Setup (Infrastructure Layer)**
|
|
322
|
+
*File: `src/infrastructure/di/container.ts`*
|
|
323
|
+
```typescript
|
|
324
|
+
import { Container } from "inversify";
|
|
325
|
+
import { TYPES, IUserRepository } from "../../application/ports/IUserRepository";
|
|
326
|
+
import { PostgresUserRepository } from "../database/PostgresUserRepository";
|
|
327
|
+
import { GetUserUseCase } from "../../application/useCases/GetUserUseCase";
|
|
328
|
+
|
|
329
|
+
const container = new Container();
|
|
330
|
+
|
|
331
|
+
container.bind<IUserRepository>(TYPES.IUserRepository).to(PostgresUserRepository);
|
|
332
|
+
container.bind<GetUserUseCase>(TYPES.GetUserUseCase).to(GetUserUseCase);
|
|
333
|
+
|
|
334
|
+
export { container };
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
**5. Controller (Presentation Layer)**
|
|
338
|
+
*File: `src/presentation/controllers/UserController.ts`*
|
|
339
|
+
```typescript
|
|
340
|
+
import { Request, Response } from "express";
|
|
341
|
+
import { container } from "../../infrastructure/di/container";
|
|
342
|
+
import { TYPES } from "../../application/ports/IUserRepository";
|
|
343
|
+
import { GetUserUseCase } from "../../application/useCases/GetUserUseCase";
|
|
344
|
+
|
|
345
|
+
export class UserController {
|
|
346
|
+
async getUser(req: Request, res: Response) {
|
|
347
|
+
const useCase = container.get<GetUserUseCase>(TYPES.GetUserUseCase);
|
|
348
|
+
try {
|
|
349
|
+
const user = await useCase.execute(req.params.id);
|
|
350
|
+
res.json(user);
|
|
351
|
+
} catch (error) {
|
|
352
|
+
res.status(404).json({ error: error.message });
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
"""
|
|
358
|
+
else:
|
|
359
|
+
md += """### Example: Node.js + TypeScript (Manual DI)
|
|
360
|
+
|
|
361
|
+
**1. Port (Application Layer)**
|
|
362
|
+
*File: `src/application/ports/IUserRepository.ts`*
|
|
363
|
+
```typescript
|
|
364
|
+
import { User } from "../../domain/entities/User";
|
|
365
|
+
|
|
366
|
+
export interface IUserRepository {
|
|
367
|
+
getById(userId: string): Promise<User | null>;
|
|
368
|
+
}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
**2. Adapter (Infrastructure Layer)**
|
|
372
|
+
*File: `src/infrastructure/database/PostgresUserRepository.ts`*
|
|
373
|
+
```typescript
|
|
374
|
+
import { IUserRepository } from "../../application/ports/IUserRepository";
|
|
375
|
+
import { User } from "../../domain/entities/User";
|
|
376
|
+
|
|
377
|
+
export class PostgresUserRepository implements IUserRepository {
|
|
378
|
+
async getById(userId: string): Promise<User | null> {
|
|
379
|
+
// DB Logic returning Domain Entity
|
|
380
|
+
return new User(userId, "Alice");
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
**3. Use Case (Application Layer)**
|
|
386
|
+
*File: `src/application/useCases/GetUserUseCase.ts`*
|
|
387
|
+
```typescript
|
|
388
|
+
import { IUserRepository } from "../ports/IUserRepository";
|
|
389
|
+
import { User } from "../../domain/entities/User";
|
|
390
|
+
|
|
391
|
+
export class GetUserUseCase {
|
|
392
|
+
constructor(private userRepository: IUserRepository) {}
|
|
393
|
+
|
|
394
|
+
async execute(userId: string): Promise<User> {
|
|
395
|
+
const user = await this.userRepository.getById(userId);
|
|
396
|
+
if (!user) throw new Error("User not found");
|
|
397
|
+
return user;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
**4. Controller (Presentation Layer)**
|
|
403
|
+
*File: `src/presentation/controllers/UserController.ts`*
|
|
404
|
+
```typescript
|
|
405
|
+
import { Request, Response } from "express";
|
|
406
|
+
import { GetUserUseCase } from "../../application/useCases/GetUserUseCase";
|
|
407
|
+
import { PostgresUserRepository } from "../../infrastructure/database/PostgresUserRepository";
|
|
408
|
+
|
|
409
|
+
export class UserController {
|
|
410
|
+
async getUser(req: Request, res: Response) {
|
|
411
|
+
const repo = new PostgresUserRepository();
|
|
412
|
+
const useCase = new GetUserUseCase(repo);
|
|
413
|
+
try {
|
|
414
|
+
const user = await useCase.execute(req.params.id);
|
|
415
|
+
res.json(user);
|
|
416
|
+
} catch (error) {
|
|
417
|
+
res.status(404).json({ error: error.message });
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
```
|
|
422
|
+
"""
|
|
423
|
+
|
|
424
|
+
return md
|
|
@@ -8,7 +8,7 @@ with open(os.path.join(this_directory, 'README.md'), encoding='utf-8') as f:
|
|
|
8
8
|
|
|
9
9
|
setup(
|
|
10
10
|
name="devhive",
|
|
11
|
-
version="0.1.
|
|
11
|
+
version="0.1.25",
|
|
12
12
|
packages=find_packages(),
|
|
13
13
|
include_package_data=True,
|
|
14
14
|
install_requires=[
|
|
@@ -29,5 +29,5 @@ setup(
|
|
|
29
29
|
"License :: OSI Approved :: MIT License",
|
|
30
30
|
"Operating System :: OS Independent",
|
|
31
31
|
],
|
|
32
|
-
python_requires='>=3.
|
|
32
|
+
python_requires='>=3.7',
|
|
33
33
|
)
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# Frontend Architecture & AI Agent Guidelines
|
|
2
|
+
|
|
3
|
+
This `AGENTS.md` (or `GUIDELINES.md`) file serves as the strict instruction manual for OpenCode and DevHive agents. It defines the technical stack, architectural patterns, and coding conventions for this frontend project.
|
|
4
|
+
|
|
5
|
+
## 🛠️ 1. Tech Stack
|
|
6
|
+
- **Framework**: Next.js 15 (App Router)
|
|
7
|
+
- **Library**: React 19
|
|
8
|
+
- **Language**: TypeScript (Strict Mode)
|
|
9
|
+
- **Styling**: Tailwind CSS v3/v4 + `clsx` + `tailwind-merge`
|
|
10
|
+
- **Components**: shadcn/ui (Radix UI primitives)
|
|
11
|
+
- **Icons**: Lucide React
|
|
12
|
+
- **Data Fetching**: React Server Components (RSC) & Server Actions
|
|
13
|
+
|
|
14
|
+
## 🧠 2. OpenCode Skills to Load
|
|
15
|
+
Agents **MUST** load the following specialized skills before executing tasks:
|
|
16
|
+
- `frontend-developer`: For modern React 19 / Next.js 15 patterns.
|
|
17
|
+
- `frontend-design`: For high-quality, non-generic UI aesthetics.
|
|
18
|
+
- `shadcn`: For proper installation and composition of shadcn/ui components.
|
|
19
|
+
- `theme-factory`: For applying cohesive color palettes and typography.
|
|
20
|
+
- `architecture-patterns`: For maintaining clean separation of concerns.
|
|
21
|
+
|
|
22
|
+
## 📂 3. Project Structure (Feature-Based Architecture)
|
|
23
|
+
For maximum scalability and maintainability, the project follows a feature-based architecture within the `src/` directory.
|
|
24
|
+
|
|
25
|
+
```text
|
|
26
|
+
src/
|
|
27
|
+
├── app/ # Next.js App Router (Pages, Layouts, API Routes)
|
|
28
|
+
│ ├── (auth)/ # Route groups for logical separation
|
|
29
|
+
│ ├── dashboard/ # Dashboard routes
|
|
30
|
+
│ └── layout.tsx # Root layout (Providers, Fonts, Metadata)
|
|
31
|
+
├── components/
|
|
32
|
+
│ ├── ui/ # Pure shadcn/ui components (DO NOT EDIT DIRECTLY unless styling)
|
|
33
|
+
│ └── shared/ # Reusable global components (Navbar, Footer, etc.)
|
|
34
|
+
├── features/ # Feature-based modules (THE CORE OF THE APP)
|
|
35
|
+
│ ├── users/ # "Users" feature module
|
|
36
|
+
│ │ ├── actions.ts # Server Actions for this feature
|
|
37
|
+
│ │ ├── components/ # Feature-specific React components
|
|
38
|
+
│ │ └── types.ts # Feature-specific TypeScript interfaces
|
|
39
|
+
│ └── products/ # "Products" feature module
|
|
40
|
+
├── lib/ # Utility functions (utils.ts, formatting, constants)
|
|
41
|
+
└── hooks/ # Global custom React hooks
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## 📐 4. Architectural Rules & Best Practices
|
|
45
|
+
|
|
46
|
+
1. **Default to Server Components (RSC)**:
|
|
47
|
+
- All components inside `app/` and `features/` MUST be Server Components by default to optimize bundle size and SEO.
|
|
48
|
+
- ONLY add the `"use client"` directive at the very top of a file if the component requires interactivity (`useState`, `useEffect`, `onClick`, browser APIs).
|
|
49
|
+
- Keep Client Components as low in the tree (leaf nodes) as possible.
|
|
50
|
+
|
|
51
|
+
2. **Server Actions for Mutations**:
|
|
52
|
+
- Do NOT build traditional API routes (`route.ts`) for internal app data mutations.
|
|
53
|
+
- Use Next.js Server Actions (`"use server"`) placed in `src/features/[name]/actions.ts`.
|
|
54
|
+
- Always validate inputs inside Server Actions (using Zod or similar) before mutating data.
|
|
55
|
+
|
|
56
|
+
3. **Shadcn/ui Usage**:
|
|
57
|
+
- Use the OpenCode CLI or `npx shadcn@latest add [component]` to install components.
|
|
58
|
+
- Combine UI components to build complex blocks (e.g., combining `Card`, `Button`, and `Input` to make a LoginForm).
|
|
59
|
+
|
|
60
|
+
4. **Type Safety**:
|
|
61
|
+
- Avoid `any`. Define interfaces/types in `types.ts` files.
|
|
62
|
+
- Export types and reuse them across Server Actions and Client Components.
|
|
63
|
+
|
|
64
|
+
## 💻 5. Code Examples
|
|
65
|
+
|
|
66
|
+
### Example A: Data Fetching in a Server Component (RSC)
|
|
67
|
+
*File: `src/app/dashboard/page.tsx`*
|
|
68
|
+
```tsx
|
|
69
|
+
import { Suspense } from "react";
|
|
70
|
+
import { getUserProfile } from "@/features/users/actions";
|
|
71
|
+
import { UserProfileCard } from "@/features/users/components/user-profile-card";
|
|
72
|
+
import { Skeleton } from "@/components/ui/skeleton";
|
|
73
|
+
|
|
74
|
+
// This is a Server Component (no "use client")
|
|
75
|
+
export default async function DashboardPage() {
|
|
76
|
+
// Fetch data directly on the server
|
|
77
|
+
const user = await getUserProfile();
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<main className="container mx-auto p-6">
|
|
81
|
+
<h1 className="text-3xl font-bold mb-6">Dashboard</h1>
|
|
82
|
+
<Suspense fallback={<Skeleton className="h-[200px] w-full rounded-xl" />}>
|
|
83
|
+
<UserProfileCard user={user} />
|
|
84
|
+
</Suspense>
|
|
85
|
+
</main>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Example B: Server Action for Data Mutation
|
|
91
|
+
*File: `src/features/users/actions.ts`*
|
|
92
|
+
```typescript
|
|
93
|
+
"use server";
|
|
94
|
+
|
|
95
|
+
import { revalidatePath } from "next/cache";
|
|
96
|
+
|
|
97
|
+
export type UpdateUserResponse = {
|
|
98
|
+
success: boolean;
|
|
99
|
+
message: string;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export async function updateUserName(formData: FormData): Promise<UpdateUserResponse> {
|
|
103
|
+
const newName = formData.get("name") as string;
|
|
104
|
+
|
|
105
|
+
if (!newName || newName.length < 3) {
|
|
106
|
+
return { success: false, message: "Name must be at least 3 characters." };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Perform database mutation here...
|
|
110
|
+
// await db.user.update(...)
|
|
111
|
+
|
|
112
|
+
// Revalidate the cache so the UI updates instantly
|
|
113
|
+
revalidatePath("/dashboard");
|
|
114
|
+
|
|
115
|
+
return { success: true, message: "Profile updated successfully." };
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Example C: Client Component using Shadcn/ui & Server Actions
|
|
120
|
+
*File: `src/features/users/components/edit-name-form.tsx`*
|
|
121
|
+
```tsx
|
|
122
|
+
"use client";
|
|
123
|
+
|
|
124
|
+
import { useState, useTransition } from "react";
|
|
125
|
+
import { updateUserName } from "@/features/users/actions";
|
|
126
|
+
import { Button } from "@/components/ui/button";
|
|
127
|
+
import { Input } from "@/components/ui/input";
|
|
128
|
+
import { toast } from "@/components/ui/use-toast"; // shadcn toast
|
|
129
|
+
|
|
130
|
+
export function EditNameForm() {
|
|
131
|
+
const [isPending, startTransition] = useTransition();
|
|
132
|
+
|
|
133
|
+
const handleSubmit = (formData: FormData) => {
|
|
134
|
+
startTransition(async () => {
|
|
135
|
+
const result = await updateUserName(formData);
|
|
136
|
+
|
|
137
|
+
if (result.success) {
|
|
138
|
+
toast({ title: "Success", description: result.message });
|
|
139
|
+
} else {
|
|
140
|
+
toast({ title: "Error", description: result.message, variant: "destructive" });
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<form action={handleSubmit} className="flex gap-4 items-center">
|
|
147
|
+
<Input
|
|
148
|
+
name="name"
|
|
149
|
+
placeholder="Enter new name"
|
|
150
|
+
required
|
|
151
|
+
disabled={isPending}
|
|
152
|
+
className="max-w-sm"
|
|
153
|
+
/>
|
|
154
|
+
<Button type="submit" disabled={isPending}>
|
|
155
|
+
{isPending ? "Saving..." : "Save Name"}
|
|
156
|
+
</Button>
|
|
157
|
+
</form>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
```
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{devhive-0.1.23 → devhive-0.1.25}/community_skills/theme-factory/themes/modern-minimalist.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|