aes-cli 0.2.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.
- aes/__init__.py +5 -0
- aes/__main__.py +37 -0
- aes/analyzer.py +487 -0
- aes/commands/__init__.py +0 -0
- aes/commands/init.py +727 -0
- aes/commands/inspect.py +204 -0
- aes/commands/install.py +379 -0
- aes/commands/publish.py +432 -0
- aes/commands/search.py +65 -0
- aes/commands/status.py +153 -0
- aes/commands/sync.py +413 -0
- aes/commands/validate.py +77 -0
- aes/config.py +43 -0
- aes/domains.py +1382 -0
- aes/frameworks.py +522 -0
- aes/mcp_server.py +213 -0
- aes/registry.py +294 -0
- aes/scaffold/agent.yaml.jinja +135 -0
- aes/scaffold/agentignore.jinja +61 -0
- aes/scaffold/instructions.md.jinja +311 -0
- aes/scaffold/local.example.yaml.jinja +35 -0
- aes/scaffold/local.yaml.jinja +29 -0
- aes/scaffold/operations.md.jinja +33 -0
- aes/scaffold/orchestrator.md.jinja +95 -0
- aes/scaffold/permissions.yaml.jinja +151 -0
- aes/scaffold/setup.md.jinja +244 -0
- aes/scaffold/skill.md.jinja +27 -0
- aes/scaffold/skill.yaml.jinja +175 -0
- aes/scaffold/workflow.yaml.jinja +44 -0
- aes/scaffold/workflow_command.md.jinja +48 -0
- aes/schemas/agent.schema.json +188 -0
- aes/schemas/permissions.schema.json +100 -0
- aes/schemas/registry.schema.json +72 -0
- aes/schemas/skill.schema.json +209 -0
- aes/schemas/workflow.schema.json +92 -0
- aes/targets/__init__.py +29 -0
- aes/targets/_base.py +77 -0
- aes/targets/_composer.py +338 -0
- aes/targets/claude.py +153 -0
- aes/targets/copilot.py +48 -0
- aes/targets/cursor.py +46 -0
- aes/targets/windsurf.py +46 -0
- aes/validator.py +394 -0
- aes_cli-0.2.0.dist-info/METADATA +110 -0
- aes_cli-0.2.0.dist-info/RECORD +48 -0
- aes_cli-0.2.0.dist-info/WHEEL +5 -0
- aes_cli-0.2.0.dist-info/entry_points.txt +3 -0
- aes_cli-0.2.0.dist-info/top_level.txt +1 -0
aes/frameworks.py
ADDED
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
"""Framework-aware configuration overlays for aes init.
|
|
2
|
+
|
|
3
|
+
Provides base project type configs (api, web-frontend, fullstack, cli-tool,
|
|
4
|
+
library) and framework-specific overlays that augment them with tailored
|
|
5
|
+
skills, rules, and permissions.
|
|
6
|
+
|
|
7
|
+
When ``aes init`` detects a framework (e.g. FastAPI), it:
|
|
8
|
+
1. Looks up the base config for the project type (e.g. "api").
|
|
9
|
+
2. Merges any framework-specific overlay on top.
|
|
10
|
+
|
|
11
|
+
This replaces the "other" domain dead-end with useful, framework-aware
|
|
12
|
+
scaffolding for most real-world projects.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from copy import deepcopy
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
from typing import Dict, List, Optional
|
|
20
|
+
|
|
21
|
+
from aes.domains import DomainConfig, SkillDef, WorkflowDef, WorkflowStateDef, WorkflowTransitionDef
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# ---------------------------------------------------------------------------
|
|
25
|
+
# Framework overlay data class
|
|
26
|
+
# ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class FrameworkOverlay:
|
|
30
|
+
"""Lightweight overlay that augments a base project type config."""
|
|
31
|
+
|
|
32
|
+
framework: str
|
|
33
|
+
extra_rules: List[str] = field(default_factory=list)
|
|
34
|
+
extra_skills: List[SkillDef] = field(default_factory=list)
|
|
35
|
+
extra_quick_ref: str = ""
|
|
36
|
+
extra_gotchas: List[str] = field(default_factory=list)
|
|
37
|
+
permissions_shell_execute: List[str] = field(default_factory=list)
|
|
38
|
+
permissions_file_write: List[str] = field(default_factory=list)
|
|
39
|
+
permissions_confirm_shell: List[str] = field(default_factory=list)
|
|
40
|
+
permissions_deny_shell: List[str] = field(default_factory=list)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# ---------------------------------------------------------------------------
|
|
44
|
+
# Universal skills (used across base configs)
|
|
45
|
+
# ---------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
_TEST_RUNNER = SkillDef(
|
|
48
|
+
id="test-runner",
|
|
49
|
+
name="Run Tests",
|
|
50
|
+
version="1.0.0",
|
|
51
|
+
description="Run the project test suite with coverage reporting",
|
|
52
|
+
stage=1,
|
|
53
|
+
phase="quality",
|
|
54
|
+
trigger_command="", # filled by framework overlay
|
|
55
|
+
error_strategy="fail-fast",
|
|
56
|
+
tags=["testing", "quality"],
|
|
57
|
+
runbook_purpose="Run the full test suite and report results with coverage.",
|
|
58
|
+
runbook_when="- After making code changes\n- Before committing\n- Before deployment",
|
|
59
|
+
runbook_how="1. Run the test suite\n2. Check for failures\n3. Review coverage report\n4. Fix any failures before continuing",
|
|
60
|
+
runbook_decision_tree="Run tests\n |- All pass? -> Continue with task\n |- Failures? -> Read failure output, fix, re-run\n \\- Coverage dropped? -> Add tests for uncovered code",
|
|
61
|
+
runbook_error_handling="- **Test failure**: Read the full error, fix the root cause, re-run\n- **Import error**: Check dependencies are installed\n- **Timeout**: Look for hanging async operations or infinite loops",
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
_LINT_FIX = SkillDef(
|
|
65
|
+
id="lint-fix",
|
|
66
|
+
name="Lint and Fix",
|
|
67
|
+
version="1.0.0",
|
|
68
|
+
description="Run linters and auto-fix style issues",
|
|
69
|
+
stage=2,
|
|
70
|
+
phase="quality",
|
|
71
|
+
trigger_command="",
|
|
72
|
+
error_strategy="fail-fast",
|
|
73
|
+
tags=["linting", "quality", "style"],
|
|
74
|
+
runbook_purpose="Run linters to catch style issues and auto-fix what's possible.",
|
|
75
|
+
runbook_when="- Before committing code\n- After major refactoring\n- When CI reports lint failures",
|
|
76
|
+
runbook_how="1. Run the linter with auto-fix enabled\n2. Review any remaining issues that couldn't be auto-fixed\n3. Fix manually if needed",
|
|
77
|
+
runbook_decision_tree="Run linter\n |- All clean? -> Continue\n |- Auto-fixable? -> Apply fixes, verify\n \\- Manual fix needed? -> Fix and re-run",
|
|
78
|
+
runbook_error_handling="- **Config error**: Check linter config file exists and is valid\n- **Parse error**: File has syntax errors -- fix those first",
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
_DB_MIGRATE = SkillDef(
|
|
82
|
+
id="db-migrate",
|
|
83
|
+
name="Database Migrate",
|
|
84
|
+
version="1.0.0",
|
|
85
|
+
description="Run database migrations safely",
|
|
86
|
+
stage=3,
|
|
87
|
+
phase="infrastructure",
|
|
88
|
+
trigger_command="",
|
|
89
|
+
error_strategy="fail-fast",
|
|
90
|
+
tags=["database", "migrations"],
|
|
91
|
+
runbook_purpose="Apply pending database migrations safely with rollback awareness.",
|
|
92
|
+
runbook_when="- Schema changes needed\n- After pulling new code with migrations\n- During deployment",
|
|
93
|
+
runbook_how="1. Check current migration status\n2. Review pending migrations\n3. Apply migrations\n4. Verify schema is correct",
|
|
94
|
+
runbook_decision_tree="Check migration status\n |- No pending? -> Skip\n |- Pending migrations?\n | |- Review for destructive changes (DROP, DELETE)\n | |- Safe changes? -> Apply\n | \\- Destructive? -> Confirm with user first\n \\- Migration fails? -> Check error, do NOT retry blindly",
|
|
95
|
+
runbook_error_handling="- **Migration conflict**: Resolve the conflict, do not skip\n- **Lock timeout**: Another process may hold the lock\n- **Data loss warning**: Stop and confirm with user",
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# ---------------------------------------------------------------------------
|
|
100
|
+
# Base project type configs
|
|
101
|
+
# ---------------------------------------------------------------------------
|
|
102
|
+
|
|
103
|
+
_API_WORKFLOW = WorkflowDef(
|
|
104
|
+
id="feature-lifecycle",
|
|
105
|
+
entity="feature",
|
|
106
|
+
description="Feature development lifecycle for API services",
|
|
107
|
+
states=[
|
|
108
|
+
WorkflowStateDef("planned", "Requirements understood", initial=True),
|
|
109
|
+
WorkflowStateDef("implementing", "Code being written", active=True),
|
|
110
|
+
WorkflowStateDef("testing", "Tests running"),
|
|
111
|
+
WorkflowStateDef("deployed", "Live in target environment", terminal=True),
|
|
112
|
+
WorkflowStateDef("blocked", "Cannot proceed", terminal=True),
|
|
113
|
+
],
|
|
114
|
+
transitions=[
|
|
115
|
+
WorkflowTransitionDef("planned", "implementing",
|
|
116
|
+
conditions=["Requirements are clear"]),
|
|
117
|
+
WorkflowTransitionDef("implementing", "testing",
|
|
118
|
+
conditions=["Implementation complete"]),
|
|
119
|
+
WorkflowTransitionDef("testing", "deployed",
|
|
120
|
+
conditions=["All tests pass"]),
|
|
121
|
+
WorkflowTransitionDef("testing", "implementing",
|
|
122
|
+
conditions=["Tests fail"],
|
|
123
|
+
description="Fix failing tests"),
|
|
124
|
+
],
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
API_CONFIG = DomainConfig(
|
|
128
|
+
instructions_description="API service with endpoints, authentication, and database.",
|
|
129
|
+
instructions_quick_ref="", # filled by framework overlay
|
|
130
|
+
instructions_project_structure="", # generic, filled by overlay or /setup
|
|
131
|
+
instructions_rules=[
|
|
132
|
+
"**Tests first** -- run tests after every change.",
|
|
133
|
+
"**Auth on every endpoint** -- use middleware, not per-route checks.",
|
|
134
|
+
"**No raw SQL** -- use an ORM or query builder. Parameterize if raw SQL is unavoidable.",
|
|
135
|
+
"**Validate inputs** -- never trust user input at API boundaries.",
|
|
136
|
+
],
|
|
137
|
+
instructions_workflow_phases=[
|
|
138
|
+
{"title": "Understand Requirements", "content": "What endpoint? What data? What auth?"},
|
|
139
|
+
{"title": "Implement", "content": "Migration (if DB) -> endpoint -> validation -> tests."},
|
|
140
|
+
{"title": "Test (DO NOT SKIP)", "content": "Unit + integration tests must pass."},
|
|
141
|
+
{"title": "Deploy", "content": "Staging first, verify, then production."},
|
|
142
|
+
],
|
|
143
|
+
instructions_key_principle="Every endpoint is tested, authenticated, and validated. Ship incrementally.",
|
|
144
|
+
instructions_gotchas=[],
|
|
145
|
+
skills=[_TEST_RUNNER, _LINT_FIX],
|
|
146
|
+
orchestrator_pipeline="implement -> test -> review -> deploy",
|
|
147
|
+
orchestrator_status_flow="planned -> implementing -> testing -> deployed",
|
|
148
|
+
orchestrator_decision_tree="1. Understand the requirement\n2. Check if migration needed\n3. Implement the endpoint\n4. Write tests\n5. Run tests\n6. Fix failures\n7. Ready for review/deploy",
|
|
149
|
+
orchestrator_when_to_stop="- Feature implemented and tests passing\n- Deployed and verified",
|
|
150
|
+
workflow=_API_WORKFLOW,
|
|
151
|
+
permissions_shell_execute=[],
|
|
152
|
+
permissions_file_write=["src/**", "tests/**"],
|
|
153
|
+
permissions_deny_shell=["rm -rf *"],
|
|
154
|
+
permissions_confirm_shell=["git push *"],
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
_FRONTEND_WORKFLOW = WorkflowDef(
|
|
158
|
+
id="feature-lifecycle",
|
|
159
|
+
entity="feature",
|
|
160
|
+
description="Feature development lifecycle for frontend apps",
|
|
161
|
+
states=[
|
|
162
|
+
WorkflowStateDef("planned", "Design/requirements understood", initial=True),
|
|
163
|
+
WorkflowStateDef("implementing", "Building component", active=True),
|
|
164
|
+
WorkflowStateDef("testing", "Tests + visual review"),
|
|
165
|
+
WorkflowStateDef("deployed", "Live", terminal=True),
|
|
166
|
+
],
|
|
167
|
+
transitions=[
|
|
168
|
+
WorkflowTransitionDef("planned", "implementing",
|
|
169
|
+
conditions=["Requirements are clear"]),
|
|
170
|
+
WorkflowTransitionDef("implementing", "testing",
|
|
171
|
+
conditions=["Component renders correctly"]),
|
|
172
|
+
WorkflowTransitionDef("testing", "deployed",
|
|
173
|
+
conditions=["Tests pass"]),
|
|
174
|
+
WorkflowTransitionDef("testing", "implementing",
|
|
175
|
+
conditions=["Tests fail or visual issues"]),
|
|
176
|
+
],
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
FRONTEND_CONFIG = DomainConfig(
|
|
180
|
+
instructions_description="Frontend application with components, routing, and state management.",
|
|
181
|
+
instructions_rules=[
|
|
182
|
+
"**Component-first** -- build small, reusable components.",
|
|
183
|
+
"**Type safety** -- avoid `any` types. Use proper interfaces.",
|
|
184
|
+
"**Accessible** -- use semantic HTML, ARIA attributes where needed.",
|
|
185
|
+
"**Test interactions** -- test user behavior, not implementation details.",
|
|
186
|
+
],
|
|
187
|
+
instructions_workflow_phases=[
|
|
188
|
+
{"title": "Understand", "content": "What does the user see? What interactions?"},
|
|
189
|
+
{"title": "Build", "content": "Component -> styling -> state -> integration."},
|
|
190
|
+
{"title": "Test", "content": "Unit tests + visual review."},
|
|
191
|
+
{"title": "Ship", "content": "Build, deploy, verify."},
|
|
192
|
+
],
|
|
193
|
+
instructions_key_principle="Build for the user. Components should be small, tested, and accessible.",
|
|
194
|
+
skills=[_TEST_RUNNER, _LINT_FIX],
|
|
195
|
+
orchestrator_pipeline="design -> implement -> test -> deploy",
|
|
196
|
+
orchestrator_status_flow="planned -> implementing -> testing -> deployed",
|
|
197
|
+
workflow=_FRONTEND_WORKFLOW,
|
|
198
|
+
permissions_shell_execute=["npm run *", "npx *"],
|
|
199
|
+
permissions_file_write=["src/**", "tests/**", "__tests__/**"],
|
|
200
|
+
permissions_deny_shell=["rm -rf *"],
|
|
201
|
+
permissions_confirm_shell=["npm run deploy*", "git push *"],
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
FULLSTACK_CONFIG = DomainConfig(
|
|
205
|
+
instructions_description="Full-stack application with frontend, API, and database.",
|
|
206
|
+
instructions_rules=[
|
|
207
|
+
"**Type safety everywhere** -- shared types between frontend and API.",
|
|
208
|
+
"**API-first** -- design the API contract before building UI.",
|
|
209
|
+
"**Test both layers** -- unit tests for logic, integration tests for API, component tests for UI.",
|
|
210
|
+
"**Migrations before code** -- schema changes go first.",
|
|
211
|
+
],
|
|
212
|
+
instructions_workflow_phases=[
|
|
213
|
+
{"title": "Plan", "content": "API contract -> data model -> UI wireframe."},
|
|
214
|
+
{"title": "Backend", "content": "Migration -> API endpoint -> validation -> tests."},
|
|
215
|
+
{"title": "Frontend", "content": "Component -> API integration -> tests."},
|
|
216
|
+
{"title": "Ship", "content": "All tests pass -> staging -> production."},
|
|
217
|
+
],
|
|
218
|
+
instructions_key_principle="Full-stack means full responsibility. Test every layer.",
|
|
219
|
+
skills=[_TEST_RUNNER, _LINT_FIX, _DB_MIGRATE],
|
|
220
|
+
orchestrator_pipeline="plan -> backend -> frontend -> test -> deploy",
|
|
221
|
+
orchestrator_status_flow="planned -> backend -> frontend -> testing -> deployed",
|
|
222
|
+
workflow=_API_WORKFLOW, # reuse API workflow
|
|
223
|
+
permissions_shell_execute=["npm run *", "npx *"],
|
|
224
|
+
permissions_file_write=["src/**", "app/**", "tests/**", "migrations/**"],
|
|
225
|
+
permissions_deny_shell=["rm -rf *", "DROP DATABASE *"],
|
|
226
|
+
permissions_confirm_shell=["npm run deploy*", "git push *"],
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
CLI_CONFIG = DomainConfig(
|
|
230
|
+
instructions_description="Command-line tool with subcommands, argument parsing, and user interaction.",
|
|
231
|
+
instructions_rules=[
|
|
232
|
+
"**Clear error messages** -- users see stderr, make it helpful.",
|
|
233
|
+
"**Exit codes matter** -- 0 for success, non-zero for failure.",
|
|
234
|
+
"**Test the CLI** -- test argument parsing, output format, and error cases.",
|
|
235
|
+
"**Progressive disclosure** -- simple defaults, flags for power users.",
|
|
236
|
+
],
|
|
237
|
+
instructions_workflow_phases=[
|
|
238
|
+
{"title": "Design", "content": "What commands? What flags? What output?"},
|
|
239
|
+
{"title": "Implement", "content": "Command handler -> argument parsing -> output formatting."},
|
|
240
|
+
{"title": "Test", "content": "Unit tests + CLI integration tests."},
|
|
241
|
+
{"title": "Release", "content": "Version bump -> changelog -> publish."},
|
|
242
|
+
],
|
|
243
|
+
instructions_key_principle="A CLI is a user interface. Treat it like one.",
|
|
244
|
+
skills=[_TEST_RUNNER, _LINT_FIX],
|
|
245
|
+
orchestrator_pipeline="design -> implement -> test -> release",
|
|
246
|
+
orchestrator_status_flow="planned -> implementing -> testing -> released",
|
|
247
|
+
permissions_shell_execute=[],
|
|
248
|
+
permissions_file_write=["src/**", "tests/**"],
|
|
249
|
+
permissions_deny_shell=["rm -rf *"],
|
|
250
|
+
permissions_confirm_shell=["git push *"],
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
LIBRARY_CONFIG = DomainConfig(
|
|
254
|
+
instructions_description="Reusable library or package for other projects to consume.",
|
|
255
|
+
instructions_rules=[
|
|
256
|
+
"**Public API is a contract** -- don't break it without a major version bump.",
|
|
257
|
+
"**100% public API tested** -- every exported function has tests.",
|
|
258
|
+
"**Document everything public** -- docstrings on all exported symbols.",
|
|
259
|
+
"**No side effects on import** -- library code should be inert until called.",
|
|
260
|
+
],
|
|
261
|
+
instructions_workflow_phases=[
|
|
262
|
+
{"title": "Design API", "content": "What does the consumer need? Keep the surface small."},
|
|
263
|
+
{"title": "Implement", "content": "Internal logic -> public API -> tests -> docs."},
|
|
264
|
+
{"title": "Test", "content": "Unit tests + edge cases + docs tests."},
|
|
265
|
+
{"title": "Publish", "content": "Version bump -> changelog -> publish to registry."},
|
|
266
|
+
],
|
|
267
|
+
instructions_key_principle="Libraries are consumed by others. Stability and clarity above all.",
|
|
268
|
+
skills=[_TEST_RUNNER, _LINT_FIX],
|
|
269
|
+
orchestrator_pipeline="design -> implement -> test -> document -> publish",
|
|
270
|
+
orchestrator_status_flow="planned -> implementing -> testing -> published",
|
|
271
|
+
permissions_shell_execute=[],
|
|
272
|
+
permissions_file_write=["src/**", "lib/**", "tests/**"],
|
|
273
|
+
permissions_deny_shell=["rm -rf *"],
|
|
274
|
+
permissions_confirm_shell=["git push *"],
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
# Project type -> base config
|
|
278
|
+
BASE_CONFIGS: Dict[str, DomainConfig] = {
|
|
279
|
+
"api": API_CONFIG,
|
|
280
|
+
"web-frontend": FRONTEND_CONFIG,
|
|
281
|
+
"fullstack": FULLSTACK_CONFIG,
|
|
282
|
+
"cli-tool": CLI_CONFIG,
|
|
283
|
+
"library": LIBRARY_CONFIG,
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
# ---------------------------------------------------------------------------
|
|
288
|
+
# Framework overlays
|
|
289
|
+
# ---------------------------------------------------------------------------
|
|
290
|
+
|
|
291
|
+
FRAMEWORK_OVERLAYS: Dict[str, FrameworkOverlay] = {
|
|
292
|
+
"fastapi": FrameworkOverlay(
|
|
293
|
+
framework="fastapi",
|
|
294
|
+
extra_rules=[
|
|
295
|
+
"**Pydantic models for all requests/responses** -- no raw dicts.",
|
|
296
|
+
"**Async by default** -- use `async def` for route handlers.",
|
|
297
|
+
"**Dependency injection** -- use FastAPI's `Depends()` for shared logic.",
|
|
298
|
+
],
|
|
299
|
+
extra_skills=[
|
|
300
|
+
SkillDef(
|
|
301
|
+
id="db-migrate",
|
|
302
|
+
name="Database Migrate",
|
|
303
|
+
version="1.0.0",
|
|
304
|
+
description="Run Alembic database migrations",
|
|
305
|
+
stage=3,
|
|
306
|
+
phase="infrastructure",
|
|
307
|
+
trigger_command="alembic upgrade head",
|
|
308
|
+
error_strategy="fail-fast",
|
|
309
|
+
tags=["database", "alembic", "migrations"],
|
|
310
|
+
runbook_purpose="Run Alembic migrations to update the database schema.",
|
|
311
|
+
runbook_when="- Schema changes needed\n- After pulling code with new migrations",
|
|
312
|
+
runbook_how="1. `alembic current` -- check current revision\n2. `alembic heads` -- see pending migrations\n3. `alembic upgrade head` -- apply all pending\n4. Verify with `alembic current`",
|
|
313
|
+
runbook_decision_tree="Check alembic status\n |- Up to date? -> Skip\n |- Pending? -> Review migration, then apply\n \\- Conflict? -> Resolve merge conflict in migrations/",
|
|
314
|
+
runbook_error_handling="- **Migration conflict**: `alembic merge heads` to create merge migration\n- **Apply error**: `alembic downgrade -1` to rollback last",
|
|
315
|
+
),
|
|
316
|
+
],
|
|
317
|
+
extra_quick_ref="uvicorn app.main:app --reload # start dev server\nalembic upgrade head # run migrations\nalembic revision --autogenerate # create migration\npython -m pytest -v # run tests",
|
|
318
|
+
extra_gotchas=[
|
|
319
|
+
"Use `Optional[X]` not `X | None` for Pydantic fields (Python 3.9 compat).",
|
|
320
|
+
"Background tasks run after response -- don't rely on them for critical work.",
|
|
321
|
+
"`Depends()` creates a new instance per request by default.",
|
|
322
|
+
],
|
|
323
|
+
permissions_shell_execute=[
|
|
324
|
+
"python -m pytest *",
|
|
325
|
+
"uvicorn *",
|
|
326
|
+
"alembic *",
|
|
327
|
+
],
|
|
328
|
+
permissions_file_write=["app/**", "src/**", "tests/**", "alembic/**"],
|
|
329
|
+
permissions_confirm_shell=["alembic downgrade *"],
|
|
330
|
+
),
|
|
331
|
+
|
|
332
|
+
"django": FrameworkOverlay(
|
|
333
|
+
framework="django",
|
|
334
|
+
extra_rules=[
|
|
335
|
+
"**Use Django ORM** -- no raw SQL unless absolutely necessary.",
|
|
336
|
+
"**Class-based views for complex logic** -- function views for simple endpoints.",
|
|
337
|
+
"**Settings via environment** -- use `django-environ` or similar.",
|
|
338
|
+
],
|
|
339
|
+
extra_skills=[
|
|
340
|
+
SkillDef(
|
|
341
|
+
id="db-migrate",
|
|
342
|
+
name="Database Migrate",
|
|
343
|
+
version="1.0.0",
|
|
344
|
+
description="Run Django database migrations",
|
|
345
|
+
stage=3,
|
|
346
|
+
phase="infrastructure",
|
|
347
|
+
trigger_command="python manage.py migrate",
|
|
348
|
+
error_strategy="fail-fast",
|
|
349
|
+
tags=["database", "django", "migrations"],
|
|
350
|
+
runbook_purpose="Run Django migrations to update the database schema.",
|
|
351
|
+
runbook_when="- Schema changes needed\n- After pulling code with new migrations",
|
|
352
|
+
runbook_how="1. `python manage.py showmigrations` -- check status\n2. `python manage.py makemigrations` -- generate if needed\n3. `python manage.py migrate` -- apply pending\n4. Verify with `python manage.py showmigrations`",
|
|
353
|
+
runbook_decision_tree="Check migration status\n |- Up to date? -> Skip\n |- Model changes? -> makemigrations first, then migrate\n \\- Conflict? -> Resolve, then migrate",
|
|
354
|
+
runbook_error_handling="- **Conflict**: Resolve migration graph conflicts\n- **Data migration needed**: Write custom migration with `RunPython`",
|
|
355
|
+
),
|
|
356
|
+
],
|
|
357
|
+
extra_quick_ref="python manage.py runserver # start dev server\npython manage.py migrate # run migrations\npython manage.py makemigrations # create migration\npython manage.py test # run tests",
|
|
358
|
+
extra_gotchas=[
|
|
359
|
+
"Always run `makemigrations` before `migrate` to catch model changes.",
|
|
360
|
+
"Don't edit migration files manually unless writing data migrations.",
|
|
361
|
+
"`select_related` and `prefetch_related` prevent N+1 queries.",
|
|
362
|
+
],
|
|
363
|
+
permissions_shell_execute=[
|
|
364
|
+
"python manage.py *",
|
|
365
|
+
"python -m pytest *",
|
|
366
|
+
],
|
|
367
|
+
permissions_file_write=["*/models.py", "*/views.py", "*/serializers.py", "*/tests.py", "*/admin.py", "*/urls.py", "tests/**"],
|
|
368
|
+
permissions_confirm_shell=["python manage.py flush *"],
|
|
369
|
+
),
|
|
370
|
+
|
|
371
|
+
"flask": FrameworkOverlay(
|
|
372
|
+
framework="flask",
|
|
373
|
+
extra_rules=[
|
|
374
|
+
"**Application factory pattern** -- use `create_app()` for testability.",
|
|
375
|
+
"**Blueprints for organization** -- don't put everything in one file.",
|
|
376
|
+
"**Extensions for common tasks** -- Flask-SQLAlchemy, Flask-Migrate, etc.",
|
|
377
|
+
],
|
|
378
|
+
extra_quick_ref="flask run --reload # start dev server\nflask db upgrade # run migrations\npython -m pytest -v # run tests",
|
|
379
|
+
extra_gotchas=[
|
|
380
|
+
"Flask runs in debug mode only when `FLASK_DEBUG=1` is set.",
|
|
381
|
+
"Use `flask.current_app` inside request context, not the app instance.",
|
|
382
|
+
],
|
|
383
|
+
permissions_shell_execute=[
|
|
384
|
+
"flask *",
|
|
385
|
+
"python -m pytest *",
|
|
386
|
+
],
|
|
387
|
+
),
|
|
388
|
+
|
|
389
|
+
"nextjs": FrameworkOverlay(
|
|
390
|
+
framework="nextjs",
|
|
391
|
+
extra_rules=[
|
|
392
|
+
"**Server components by default** -- only use `'use client'` when needed.",
|
|
393
|
+
"**App Router** -- use the `app/` directory for routing.",
|
|
394
|
+
"**Server actions for mutations** -- prefer over API routes for form submissions.",
|
|
395
|
+
],
|
|
396
|
+
extra_quick_ref="npm run dev # start dev server\nnpm run build # production build\nnpm run test # run tests\nnpm run lint # run linter",
|
|
397
|
+
extra_gotchas=[
|
|
398
|
+
"Server components can't use hooks or browser APIs -- add `'use client'` first.",
|
|
399
|
+
"`fetch()` in server components is cached by default -- use `{ cache: 'no-store' }` for dynamic data.",
|
|
400
|
+
"Environment variables prefixed with `NEXT_PUBLIC_` are exposed to the browser.",
|
|
401
|
+
],
|
|
402
|
+
permissions_shell_execute=[
|
|
403
|
+
"npm run *",
|
|
404
|
+
"npx *",
|
|
405
|
+
],
|
|
406
|
+
permissions_file_write=["app/**", "src/**", "components/**", "lib/**", "tests/**", "__tests__/**"],
|
|
407
|
+
),
|
|
408
|
+
|
|
409
|
+
"express": FrameworkOverlay(
|
|
410
|
+
framework="express",
|
|
411
|
+
extra_rules=[
|
|
412
|
+
"**Middleware for cross-cutting concerns** -- auth, logging, error handling.",
|
|
413
|
+
"**Router for organization** -- separate route files per resource.",
|
|
414
|
+
"**Error middleware last** -- `(err, req, res, next)` at the end of the chain.",
|
|
415
|
+
],
|
|
416
|
+
extra_quick_ref="npm run dev # start dev server\nnpm run test # run tests\nnpm run lint # run linter",
|
|
417
|
+
extra_gotchas=[
|
|
418
|
+
"Error middleware must have 4 parameters `(err, req, res, next)` or Express skips it.",
|
|
419
|
+
"Always call `next(err)` in async route handlers to propagate errors.",
|
|
420
|
+
],
|
|
421
|
+
permissions_shell_execute=[
|
|
422
|
+
"npm run *",
|
|
423
|
+
"npx *",
|
|
424
|
+
"node *",
|
|
425
|
+
],
|
|
426
|
+
),
|
|
427
|
+
|
|
428
|
+
"react": FrameworkOverlay(
|
|
429
|
+
framework="react",
|
|
430
|
+
extra_rules=[
|
|
431
|
+
"**Hooks for state** -- prefer `useState` and `useReducer` over class components.",
|
|
432
|
+
"**Composition over inheritance** -- build with small, composable components.",
|
|
433
|
+
"**Memoize expensive renders** -- use `React.memo`, `useMemo`, `useCallback` when needed.",
|
|
434
|
+
],
|
|
435
|
+
extra_quick_ref="npm run dev # start dev server\nnpm run build # production build\nnpm run test # run tests",
|
|
436
|
+
extra_gotchas=[
|
|
437
|
+
"State updates are batched -- don't read state immediately after setting it.",
|
|
438
|
+
"Effect cleanup runs before re-running -- return a cleanup function from `useEffect`.",
|
|
439
|
+
],
|
|
440
|
+
permissions_shell_execute=[
|
|
441
|
+
"npm run *",
|
|
442
|
+
"npx *",
|
|
443
|
+
],
|
|
444
|
+
permissions_file_write=["src/**", "tests/**", "__tests__/**"],
|
|
445
|
+
),
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
# ---------------------------------------------------------------------------
|
|
450
|
+
# Merge logic
|
|
451
|
+
# ---------------------------------------------------------------------------
|
|
452
|
+
|
|
453
|
+
def resolve_config(
|
|
454
|
+
project_type: str,
|
|
455
|
+
frameworks: List[str],
|
|
456
|
+
language: str,
|
|
457
|
+
test_command: Optional[str] = None,
|
|
458
|
+
build_command: Optional[str] = None,
|
|
459
|
+
) -> Optional[DomainConfig]:
|
|
460
|
+
"""Resolve a DomainConfig for a project type + detected frameworks.
|
|
461
|
+
|
|
462
|
+
Returns None if no matching base config exists (caller should fall back
|
|
463
|
+
to the existing domain-based or "other" scaffolding).
|
|
464
|
+
"""
|
|
465
|
+
base = BASE_CONFIGS.get(project_type)
|
|
466
|
+
if base is None:
|
|
467
|
+
return None
|
|
468
|
+
|
|
469
|
+
config = deepcopy(base)
|
|
470
|
+
|
|
471
|
+
# Apply framework overlays
|
|
472
|
+
for fw in frameworks:
|
|
473
|
+
overlay = FRAMEWORK_OVERLAYS.get(fw)
|
|
474
|
+
if overlay is None:
|
|
475
|
+
continue
|
|
476
|
+
|
|
477
|
+
# Merge rules
|
|
478
|
+
config.instructions_rules.extend(overlay.extra_rules)
|
|
479
|
+
|
|
480
|
+
# Merge skills (avoid duplicates by ID)
|
|
481
|
+
existing_ids = {s.id for s in config.skills}
|
|
482
|
+
for skill in overlay.extra_skills:
|
|
483
|
+
if skill.id not in existing_ids:
|
|
484
|
+
config.skills.append(skill)
|
|
485
|
+
existing_ids.add(skill.id)
|
|
486
|
+
|
|
487
|
+
# Merge quick ref
|
|
488
|
+
if overlay.extra_quick_ref:
|
|
489
|
+
if config.instructions_quick_ref:
|
|
490
|
+
config.instructions_quick_ref += "\n" + overlay.extra_quick_ref
|
|
491
|
+
else:
|
|
492
|
+
config.instructions_quick_ref = overlay.extra_quick_ref
|
|
493
|
+
|
|
494
|
+
# Merge gotchas
|
|
495
|
+
config.instructions_gotchas.extend(overlay.extra_gotchas)
|
|
496
|
+
|
|
497
|
+
# Merge permissions
|
|
498
|
+
config.permissions_shell_execute.extend(overlay.permissions_shell_execute)
|
|
499
|
+
config.permissions_file_write.extend(overlay.permissions_file_write)
|
|
500
|
+
config.permissions_confirm_shell.extend(overlay.permissions_confirm_shell)
|
|
501
|
+
config.permissions_deny_shell.extend(overlay.permissions_deny_shell)
|
|
502
|
+
|
|
503
|
+
# Set test command on test-runner skill
|
|
504
|
+
if test_command:
|
|
505
|
+
for skill in config.skills:
|
|
506
|
+
if skill.id == "test-runner" and not skill.trigger_command:
|
|
507
|
+
skill.trigger_command = test_command
|
|
508
|
+
|
|
509
|
+
# Set build command in quick ref if not already present
|
|
510
|
+
if build_command and build_command not in (config.instructions_quick_ref or ""):
|
|
511
|
+
if config.instructions_quick_ref:
|
|
512
|
+
config.instructions_quick_ref += f"\n{build_command}"
|
|
513
|
+
else:
|
|
514
|
+
config.instructions_quick_ref = build_command
|
|
515
|
+
|
|
516
|
+
# Deduplicate permissions lists
|
|
517
|
+
config.permissions_shell_execute = list(dict.fromkeys(config.permissions_shell_execute))
|
|
518
|
+
config.permissions_file_write = list(dict.fromkeys(config.permissions_file_write))
|
|
519
|
+
config.permissions_confirm_shell = list(dict.fromkeys(config.permissions_confirm_shell))
|
|
520
|
+
config.permissions_deny_shell = list(dict.fromkeys(config.permissions_deny_shell))
|
|
521
|
+
|
|
522
|
+
return config
|