pycustodian 0.0.1__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.
- pycustodian-0.0.1/.claude/CLAUDE.md +414 -0
- pycustodian-0.0.1/.claude/commands/branch.md +98 -0
- pycustodian-0.0.1/.claude/skills/config-change/SKILL.md +122 -0
- pycustodian-0.0.1/.claude/skills/onboard/SKILL.md +140 -0
- pycustodian-0.0.1/.claude/skills/plan/SKILL.md +113 -0
- pycustodian-0.0.1/.claude/skills/refactor/SKILL.md +239 -0
- pycustodian-0.0.1/.claude/skills/release/SKILL.md +192 -0
- pycustodian-0.0.1/.claude/skills/ship/SKILL.md +218 -0
- pycustodian-0.0.1/.claude/skills/verify/SKILL.md +142 -0
- pycustodian-0.0.1/.cursor/rules/optophi-project-standards.mdc +40 -0
- pycustodian-0.0.1/.github/workflows/ci.yml +100 -0
- pycustodian-0.0.1/.github/workflows/publish.yml +120 -0
- pycustodian-0.0.1/.github/workflows/scorecards.yml +71 -0
- pycustodian-0.0.1/.gitignore +314 -0
- pycustodian-0.0.1/.import_linter_cache/.gitignore +2 -0
- pycustodian-0.0.1/.import_linter_cache/CACHEDIR.TAG +3 -0
- pycustodian-0.0.1/.import_linter_cache/df4d22b0047555b49d58aed898ce423ae203ab5b.data.json +1 -0
- pycustodian-0.0.1/.import_linter_cache/pycustodian.meta.json +1 -0
- pycustodian-0.0.1/.pre-commit-config.yaml +97 -0
- pycustodian-0.0.1/.semgrepignore +56 -0
- pycustodian-0.0.1/AGENTS.md +66 -0
- pycustodian-0.0.1/ARCHITECTURE.md +87 -0
- pycustodian-0.0.1/CHANGELOG.md +35 -0
- pycustodian-0.0.1/CLAUDE.md +1 -0
- pycustodian-0.0.1/CODE_OF_CONDUCT.md +82 -0
- pycustodian-0.0.1/CONTRIBUTING.md +138 -0
- pycustodian-0.0.1/LICENSE +21 -0
- pycustodian-0.0.1/Makefile +106 -0
- pycustodian-0.0.1/PKG-INFO +108 -0
- pycustodian-0.0.1/README.md +48 -0
- pycustodian-0.0.1/SECURITY.md +21 -0
- pycustodian-0.0.1/alembic.ini +41 -0
- pycustodian-0.0.1/cliff.toml +49 -0
- pycustodian-0.0.1/examples/quickstart.py +81 -0
- pycustodian-0.0.1/pyproject.toml +188 -0
- pycustodian-0.0.1/scripts/build.sh +118 -0
- pycustodian-0.0.1/scripts/ci.sh +252 -0
- pycustodian-0.0.1/scripts/pre-push.sh +105 -0
- pycustodian-0.0.1/scripts/release.sh +875 -0
- pycustodian-0.0.1/scripts/setup.sh +139 -0
- pycustodian-0.0.1/setup.sh +129 -0
- pycustodian-0.0.1/src/pycustodian/__init__.py +82 -0
- pycustodian-0.0.1/src/pycustodian/adapters/__init__.py +11 -0
- pycustodian-0.0.1/src/pycustodian/adapters/_codec.py +102 -0
- pycustodian-0.0.1/src/pycustodian/adapters/api/__init__.py +7 -0
- pycustodian-0.0.1/src/pycustodian/adapters/api/app.py +136 -0
- pycustodian-0.0.1/src/pycustodian/adapters/api/schemas.py +95 -0
- pycustodian-0.0.1/src/pycustodian/adapters/clock.py +13 -0
- pycustodian-0.0.1/src/pycustodian/adapters/errors.py +11 -0
- pycustodian-0.0.1/src/pycustodian/adapters/ingest/__init__.py +7 -0
- pycustodian-0.0.1/src/pycustodian/adapters/ingest/decode.py +48 -0
- pycustodian-0.0.1/src/pycustodian/adapters/ingest/kafka_consumer.py +92 -0
- pycustodian-0.0.1/src/pycustodian/adapters/integrations/__init__.py +39 -0
- pycustodian-0.0.1/src/pycustodian/adapters/integrations/bridges.py +52 -0
- pycustodian-0.0.1/src/pycustodian/adapters/integrations/pyactuator.py +70 -0
- pycustodian-0.0.1/src/pycustodian/adapters/integrations/pyfortis.py +74 -0
- pycustodian-0.0.1/src/pycustodian/adapters/integrations/pyoptima.py +47 -0
- pycustodian-0.0.1/src/pycustodian/adapters/memory/__init__.py +8 -0
- pycustodian-0.0.1/src/pycustodian/adapters/memory/event_store.py +51 -0
- pycustodian-0.0.1/src/pycustodian/adapters/memory/idempotency.py +23 -0
- pycustodian-0.0.1/src/pycustodian/adapters/prices.py +17 -0
- pycustodian-0.0.1/src/pycustodian/adapters/runtime.py +53 -0
- pycustodian-0.0.1/src/pycustodian/adapters/sql/__init__.py +17 -0
- pycustodian-0.0.1/src/pycustodian/adapters/sql/event_store.py +70 -0
- pycustodian-0.0.1/src/pycustodian/adapters/sql/migrations/env.py +52 -0
- pycustodian-0.0.1/src/pycustodian/adapters/sql/migrations/script.py.mako +27 -0
- pycustodian-0.0.1/src/pycustodian/adapters/sql/migrations/versions/0001_initial_event_store.py +39 -0
- pycustodian-0.0.1/src/pycustodian/adapters/sql/models.py +43 -0
- pycustodian-0.0.1/src/pycustodian/adapters/sql/session.py +25 -0
- pycustodian-0.0.1/src/pycustodian/adapters/sql/unit_of_work.py +34 -0
- pycustodian-0.0.1/src/pycustodian/application/__init__.py +11 -0
- pycustodian-0.0.1/src/pycustodian/application/post_cash.py +83 -0
- pycustodian-0.0.1/src/pycustodian/application/queries.py +53 -0
- pycustodian-0.0.1/src/pycustodian/application/record_fill.py +129 -0
- pycustodian-0.0.1/src/pycustodian/application/replay.py +17 -0
- pycustodian-0.0.1/src/pycustodian/application/service.py +123 -0
- pycustodian-0.0.1/src/pycustodian/application/value_book.py +20 -0
- pycustodian-0.0.1/src/pycustodian/cli.py +246 -0
- pycustodian-0.0.1/src/pycustodian/config/__init__.py +10 -0
- pycustodian-0.0.1/src/pycustodian/config/loader.py +30 -0
- pycustodian-0.0.1/src/pycustodian/config/models.py +28 -0
- pycustodian-0.0.1/src/pycustodian/domain/__init__.py +10 -0
- pycustodian-0.0.1/src/pycustodian/domain/accounts.py +97 -0
- pycustodian-0.0.1/src/pycustodian/domain/errors.py +45 -0
- pycustodian-0.0.1/src/pycustodian/domain/instruments.py +34 -0
- pycustodian-0.0.1/src/pycustodian/domain/journal.py +120 -0
- pycustodian-0.0.1/src/pycustodian/domain/lots.py +181 -0
- pycustodian-0.0.1/src/pycustodian/domain/money.py +64 -0
- pycustodian-0.0.1/src/pycustodian/domain/performance.py +83 -0
- pycustodian-0.0.1/src/pycustodian/domain/pnl.py +19 -0
- pycustodian-0.0.1/src/pycustodian/domain/positions.py +48 -0
- pycustodian-0.0.1/src/pycustodian/domain/posting.py +160 -0
- pycustodian-0.0.1/src/pycustodian/domain/projections.py +96 -0
- pycustodian-0.0.1/src/pycustodian/domain/valuation.py +91 -0
- pycustodian-0.0.1/src/pycustodian/ports/__init__.py +10 -0
- pycustodian-0.0.1/src/pycustodian/ports/clock.py +19 -0
- pycustodian-0.0.1/src/pycustodian/ports/errors.py +21 -0
- pycustodian-0.0.1/src/pycustodian/ports/event_store.py +39 -0
- pycustodian-0.0.1/src/pycustodian/ports/idempotency.py +24 -0
- pycustodian-0.0.1/src/pycustodian/ports/price_source.py +16 -0
- pycustodian-0.0.1/src/pycustodian/py.typed +0 -0
- pycustodian-0.0.1/tests/__init__.py +0 -0
- pycustodian-0.0.1/tests/conftest.py +28 -0
- pycustodian-0.0.1/tests/integration/__init__.py +0 -0
- pycustodian-0.0.1/tests/integration/_scenario.py +65 -0
- pycustodian-0.0.1/tests/integration/conftest.py +22 -0
- pycustodian-0.0.1/tests/integration/test_api.py +80 -0
- pycustodian-0.0.1/tests/integration/test_cli.py +58 -0
- pycustodian-0.0.1/tests/integration/test_codec.py +29 -0
- pycustodian-0.0.1/tests/integration/test_integrations.py +84 -0
- pycustodian-0.0.1/tests/integration/test_pyactuator_bridge.py +51 -0
- pycustodian-0.0.1/tests/integration/test_sql_event_store.py +36 -0
- pycustodian-0.0.1/tests/integration/test_store_parity_and_replay.py +46 -0
- pycustodian-0.0.1/tests/unit/__init__.py +0 -0
- pycustodian-0.0.1/tests/unit/test_config.py +34 -0
- pycustodian-0.0.1/tests/unit/test_decode.py +41 -0
- pycustodian-0.0.1/tests/unit/test_journal.py +53 -0
- pycustodian-0.0.1/tests/unit/test_lots.py +65 -0
- pycustodian-0.0.1/tests/unit/test_money.py +28 -0
- pycustodian-0.0.1/tests/unit/test_performance.py +36 -0
- pycustodian-0.0.1/tests/unit/test_post_cash.py +61 -0
- pycustodian-0.0.1/tests/unit/test_posting.py +77 -0
- pycustodian-0.0.1/tests/unit/test_projections.py +91 -0
- pycustodian-0.0.1/tests/unit/test_property.py +87 -0
- pycustodian-0.0.1/tests/unit/test_queries.py +65 -0
- pycustodian-0.0.1/tests/unit/test_record_fill.py +72 -0
- pycustodian-0.0.1/tests/unit/test_service_and_consumer.py +78 -0
- pycustodian-0.0.1/tests/unit/test_valuation.py +67 -0
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
# Claude Project Instructions
|
|
2
|
+
|
|
3
|
+
You are acting as a Senior Software Engineer specializing in high-performance, maintainable, and clean code.
|
|
4
|
+
|
|
5
|
+
## Tech Stack
|
|
6
|
+
|
|
7
|
+
Minimum versions for new work (must stay consistent with each package’s **`pyproject.toml`** `requires-python` and, where a UI exists, **`src/<package>/ui/package.json`**):
|
|
8
|
+
|
|
9
|
+
- **Python:** **3.11+** (same floor as `requires-python = ">=3.11"` in these repos).
|
|
10
|
+
- **Node.js:** **24+** when building or developing **Next.js** UIs under `src/<package>/ui/` (matches release CI and Docker UI stages in packages that ship a web UI).
|
|
11
|
+
- **Next.js:** **16+** (App Router).
|
|
12
|
+
- **React:** **19+** (including **react-dom 19+**).
|
|
13
|
+
|
|
14
|
+
**Typical backend stack:** FastAPI, Pandas, SQLAlchemy, Boto3—use only what the package you are editing already depends on.
|
|
15
|
+
|
|
16
|
+
**Typical frontend stack:** TypeScript, Tailwind CSS.
|
|
17
|
+
|
|
18
|
+
## Core Principles
|
|
19
|
+
|
|
20
|
+
1. **Simple yet Robust** -- Prefer simple solutions over complex ones. Code must be easy to read but handle edge cases and failures gracefully.
|
|
21
|
+
2. **Maintainable** -- Prioritize clarity over brevity. Use meaningful naming and proper abstraction.
|
|
22
|
+
3. **Security First** -- Never hardcode credentials, secrets, or API keys. Use environment variables. Never paste secrets into prompts, commits, or logs; if exposure is suspected, rotate the credential and notify maintainers per `SECURITY.md`.
|
|
23
|
+
4. **Tested** -- Write unit tests for all new functionality.
|
|
24
|
+
5. **Collaborator-aligned** -- When working inside a repository, treat `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, and `SECURITY.md` (if present) as binding as these instructions. Prefer the repo’s documented toolchain and review process over personal defaults.
|
|
25
|
+
|
|
26
|
+
## Working Style
|
|
27
|
+
|
|
28
|
+
Behavioral guidelines to reduce common LLM coding mistakes. Complement these with the project-specific standards below.
|
|
29
|
+
|
|
30
|
+
**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment.
|
|
31
|
+
|
|
32
|
+
### 1. Think Before Coding
|
|
33
|
+
|
|
34
|
+
**Don't assume. Don't hide confusion. Surface tradeoffs.**
|
|
35
|
+
|
|
36
|
+
Before implementing:
|
|
37
|
+
- State your assumptions explicitly. If uncertain, ask.
|
|
38
|
+
- If multiple interpretations exist, present them — don't pick silently.
|
|
39
|
+
- If a simpler approach exists, say so. Push back when warranted.
|
|
40
|
+
- If something is unclear, stop. Name what's confusing. Ask.
|
|
41
|
+
|
|
42
|
+
### 2. Simplicity First
|
|
43
|
+
|
|
44
|
+
**Minimum code that solves the problem. Nothing speculative.**
|
|
45
|
+
|
|
46
|
+
- No features beyond what was asked.
|
|
47
|
+
- No abstractions for single-use code.
|
|
48
|
+
- No "flexibility" or "configurability" that wasn't requested.
|
|
49
|
+
- No error handling for impossible scenarios.
|
|
50
|
+
- If you write 200 lines and it could be 50, rewrite it.
|
|
51
|
+
|
|
52
|
+
Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
|
|
53
|
+
|
|
54
|
+
### 3. Surgical Changes
|
|
55
|
+
|
|
56
|
+
**Touch only what you must. Clean up only your own mess.**
|
|
57
|
+
|
|
58
|
+
When editing existing code:
|
|
59
|
+
- Don't "improve" adjacent code, comments, or formatting.
|
|
60
|
+
- Don't refactor things that aren't broken.
|
|
61
|
+
- Match existing style, even if you'd do it differently.
|
|
62
|
+
- If you notice unrelated dead code, mention it — don't delete it.
|
|
63
|
+
|
|
64
|
+
When your changes create orphans:
|
|
65
|
+
- Remove imports/variables/functions that YOUR changes made unused.
|
|
66
|
+
- Don't remove pre-existing dead code unless asked.
|
|
67
|
+
|
|
68
|
+
The test: Every changed line should trace directly to the user's request.
|
|
69
|
+
|
|
70
|
+
### 4. Goal-Driven Execution
|
|
71
|
+
|
|
72
|
+
**Define success criteria. Loop until verified.**
|
|
73
|
+
|
|
74
|
+
Transform tasks into verifiable goals:
|
|
75
|
+
- "Add validation" → "Write tests for invalid inputs, then make them pass"
|
|
76
|
+
- "Fix the bug" → "Write a test that reproduces it, then make it pass"
|
|
77
|
+
- "Refactor X" → "Ensure tests pass before and after"
|
|
78
|
+
|
|
79
|
+
For multi-step tasks, state a brief plan:
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
1. [Step] → verify: [check]
|
|
83
|
+
2. [Step] → verify: [check]
|
|
84
|
+
3. [Step] → verify: [check]
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.
|
|
88
|
+
|
|
89
|
+
**These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.
|
|
90
|
+
|
|
91
|
+
## Rules
|
|
92
|
+
|
|
93
|
+
- **DO NOT** refactor without a clear reason or explicit request.
|
|
94
|
+
- **DO NOT** introduce external packages without checking existing ones first.
|
|
95
|
+
- **DO NOT** use `console.log` or `print` in production code. Use the `logging` module (Python) or structured logging.
|
|
96
|
+
- **DO NOT** commit generated files, secrets, or environment-specific config.
|
|
97
|
+
- Keep functions under 50 lines, focused on a single responsibility.
|
|
98
|
+
- Keep modules under 400 lines. If a file exceeds this, suggest splitting it.
|
|
99
|
+
- Use early returns for error handling. Catch errors locally; return meaningful messages or raise domain exceptions.
|
|
100
|
+
- **Frontend layout:** Prefer document flow, Flexbox, and CSS Grid; reserve `position: absolute` for overlays, tooltips, modals, and similar—not for main page structure. See **Frontend Standards → Layout and positioning**.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Python Standards
|
|
105
|
+
|
|
106
|
+
### Style
|
|
107
|
+
|
|
108
|
+
- Strictly follow PEP 8 and PEP 484 type hints.
|
|
109
|
+
- Use Ruff for linting and formatting (line-length 88). Run `ruff check src/ --fix` and `ruff format src/` before committing; pre-commit will run these and fail the commit if there are errors.
|
|
110
|
+
- Use **union syntax in `isinstance`**: write `isinstance(x, A | B)` not `isinstance(x, (A, B))` (Ruff rule UP038). Apply to all type unions (e.g. `int | float`, `list | dict`, `str | Path`).
|
|
111
|
+
- Use `from __future__ import annotations` in **new and modified** `.py` files; when editing a file that already lacks it, add it. Align whole modules when practical so the tree stays consistent.
|
|
112
|
+
- Modern typing everywhere: `dict[str, Any]`, `list[str]`, `str | None`, `tuple[int, ...]`. Never use `Dict`, `List`, `Optional`, `Union`, or `Tuple` from `typing` in new or modified code.
|
|
113
|
+
- Avoid unused imports (F401). For optional dependencies used only to set a flag (e.g. `HAS_NUMPY`), use `# noqa: F401` on the import. For side-effect-only imports, use `import x as _` or `# noqa: F401` with a short comment.
|
|
114
|
+
- **Avoid unused local variables (F841).** Do not assign to a variable only to set a flag if nothing ever reads that variable (e.g. `removed = True` in a loop with no later `if removed:`). Either use the value (e.g. branch on it), remove the assignment and keep control flow (`continue` / `break` alone), or prefix with `_` only when the assignment is required for unpacking (e.g. `_, err = fn()`). Pre-commit Ruff will fail the commit on F841.
|
|
115
|
+
- Avoid ambiguous variable names (E741): do not use `l`, `O`, `I`; use `part`, `item`, `idx`, etc.
|
|
116
|
+
- Use `capture_output=True` instead of `stdout=subprocess.PIPE, stderr=subprocess.PIPE` in `subprocess.run()` calls (Ruff rule UP022).
|
|
117
|
+
- Keep all module-level imports at the top of the file before executable code. Do not insert path/bootstrap logic, environment setup, or other statements between imports and later imports; Ruff E402 will fail pre-commit.
|
|
118
|
+
- Always run `ruff check <files> --fix` and `ruff format <files>` on **every modified file** (including `setup.py` and files outside `src/`) before considering the change complete. Pre-commit hooks check all staged files, not just `src/`.
|
|
119
|
+
- Mypy strict mode where already configured.
|
|
120
|
+
|
|
121
|
+
### Naming
|
|
122
|
+
|
|
123
|
+
- Modules: `snake_case.py`. Prefix with `_` for package-internal modules (e.g. `_types.py`, `_engine.py`). No prefix for public API modules.
|
|
124
|
+
- Classes: `PascalCase`. Protocols suffixed with purpose (e.g. `EventSink`, `StateStore`).
|
|
125
|
+
- Constants: `UPPER_SNAKE_CASE`.
|
|
126
|
+
- Private helpers: prefix with `_` (functions, methods, module-level).
|
|
127
|
+
- Enums: `PascalCase` class name, `UPPER_SNAKE_CASE` members. Always add a class docstring.
|
|
128
|
+
|
|
129
|
+
### Docstrings
|
|
130
|
+
|
|
131
|
+
- Use Google-style docstrings on all public functions, classes, and modules.
|
|
132
|
+
- Module-level docstring required: one-line summary of purpose.
|
|
133
|
+
- Private/internal functions: a one-liner is sufficient.
|
|
134
|
+
- All enums, dataclasses, and Pydantic models must have a class-level docstring.
|
|
135
|
+
- Example:
|
|
136
|
+
```python
|
|
137
|
+
def process(event: str, context: dict[str, Any]) -> TransitionResult:
|
|
138
|
+
"""Process an event against the current state.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
event: The trigger event name.
|
|
142
|
+
context: Mutable context dict passed through guards and actions.
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Result containing the transition outcome and actions to execute.
|
|
146
|
+
|
|
147
|
+
Raises:
|
|
148
|
+
InvalidTransitionError: If no matching transition is found.
|
|
149
|
+
"""
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Imports
|
|
153
|
+
|
|
154
|
+
- Order: stdlib, blank line, third-party, blank line, local. Ruff `isort` handles this.
|
|
155
|
+
- Prefer absolute imports (`from mypackage.effects import apply_effect`).
|
|
156
|
+
- Relative imports acceptable only within the same sub-package (e.g. `from . import models`).
|
|
157
|
+
- Lazy imports only when needed to break circular dependencies (document why with a comment).
|
|
158
|
+
|
|
159
|
+
### Logging
|
|
160
|
+
|
|
161
|
+
- Logger per module: `logger = logging.getLogger(__name__)`.
|
|
162
|
+
- Levels: `DEBUG` for internal flow, `INFO` for lifecycle events, `WARNING` for recoverable issues, `ERROR` for failures.
|
|
163
|
+
- **Always** use lazy formatting: `logger.info("processed %s", event)`. Never use f-strings in logging calls.
|
|
164
|
+
- Never log secrets, tokens, or full context dicts at INFO or above.
|
|
165
|
+
|
|
166
|
+
### Data classes and models
|
|
167
|
+
|
|
168
|
+
- Prefer `@dataclass(frozen=True, slots=True)` for immutable value objects.
|
|
169
|
+
- Use `field(default_factory=...)` for mutable defaults -- never use mutable default arguments.
|
|
170
|
+
- Pydantic `BaseModel` for config, API request/response, and validated external input.
|
|
171
|
+
- Plain dataclasses for internal domain objects that don't need validation.
|
|
172
|
+
|
|
173
|
+
### Async conventions
|
|
174
|
+
|
|
175
|
+
- Use `async def` for I/O-bound operations (HTTP, DB, file reads).
|
|
176
|
+
- Sync wrappers should delegate to async with `asyncio.run()` at the boundary, not deep in the call stack.
|
|
177
|
+
- Never mix `time.sleep()` in async code -- use `asyncio.sleep()`.
|
|
178
|
+
- For shared sync/async logic, extract the common parts into a helper and call it from both paths. Do not duplicate logic between sync and async methods.
|
|
179
|
+
|
|
180
|
+
### Defensive coding
|
|
181
|
+
|
|
182
|
+
- Validate inputs at the public API boundary. Internal functions can trust their callers.
|
|
183
|
+
- Use `typing.Protocol` for dependency injection -- never pass concrete classes where an interface will do.
|
|
184
|
+
- Prefer returning result objects over raising exceptions for expected failure cases. Reserve exceptions for truly exceptional situations.
|
|
185
|
+
- Use `contextlib.suppress()` over bare `except: pass`. Never silently swallow exceptions.
|
|
186
|
+
|
|
187
|
+
### HTTP API errors (information disclosure / CodeQL)
|
|
188
|
+
|
|
189
|
+
Static analyzers (CodeQL) flag paths where exception-derived text (`str(e)`, `repr(e)`, `e.args`, or embedding an `Exception` in JSON) can reach an HTTP response. This fleet uses **FastAPI** (`HTTPException`, `JSONResponse`). All routes go through the helpers in `<pkg>/api/_http_errors.py`.
|
|
190
|
+
|
|
191
|
+
#### Two trust tiers
|
|
192
|
+
|
|
193
|
+
Classify every caught exception into one of two tiers before choosing a helper:
|
|
194
|
+
|
|
195
|
+
- **Public-facing** — exceptions inheriting from `PublicFacingError`, *or* listed in a call site's `extra_safe_types`. Their `str(exc)` is authored in-tree (domain validation, name checkers, config parsers) and safe to surface. Helpers return a **structured** detail body: `{"message": "<exc text>", "code": "<ExceptionClassName>"}`.
|
|
196
|
+
- **Private** — everything else (DB drivers, ORMs, third-party libs, broad `except Exception`). Their text can leak SQL, schema, file paths, stack traces. Helpers return a fixed `public_detail` / `fallback_detail` string supplied by the caller.
|
|
197
|
+
|
|
198
|
+
Both tiers always log the full exception server-side via `logger.exception`.
|
|
199
|
+
|
|
200
|
+
#### Helper picker
|
|
201
|
+
|
|
202
|
+
| Situation | Helper |
|
|
203
|
+
| -------------------------------------------------------------- | ------ |
|
|
204
|
+
| `except <DomainException>` where the exception type is known safe | `raise_logged_<status>_from_exception(logger, exc, log_event=...)` |
|
|
205
|
+
| `except ValueError` narrowly — verified to come from our code | `raise_logged_<status>_from_exception(..., extra_safe_types=(ValueError,))` |
|
|
206
|
+
| Broad `except Exception` / driver errors / unknown provenance | `raise_logged_<status>(logger, exc, public_detail="…", log_event=...)` |
|
|
207
|
+
|
|
208
|
+
Status suffixes: `bad_request`, `not_found`, `conflict`, `unprocessable`, `internal`. All live in `_http_errors.py`.
|
|
209
|
+
|
|
210
|
+
#### Rules
|
|
211
|
+
|
|
212
|
+
- **Do not** call `raise HTTPException(..., detail=str(e))` from a route — the `detail=str(` pre-commit hook will block it. The `_from_exception` helpers are the single audited escape hatch that surfaces exception text (via a variable, type-gated to public-facing types).
|
|
213
|
+
- **Do** prefer raising a `PublicFacingError` subclass from new domain code — routes then don't need an `extra_safe_types` allowlist.
|
|
214
|
+
- **Do** log via `logger.exception(...)` (the helpers already do this) before raising.
|
|
215
|
+
- **Dev-only** diagnostic text (e.g. including `str(exc)` in JSON) must sit behind `is_development_environment()` (see `_http_errors.py`) — never unguarded in code that ships to production users.
|
|
216
|
+
- **Bad:** `except Exception as e: raise HTTPException(400, detail=str(e))` — both unsafe and blocked by pre-commit.
|
|
217
|
+
- **Bad:** `except ValueError: raise HTTPException(422, detail="Request could not be completed")` — swallows an actionable user-facing message.
|
|
218
|
+
- **Good:** `except ConfigurationError as exc: raise_logged_bad_request_from_exception(logger, exc, log_event="config_parse_failed", fallback_detail="Invalid configuration")`.
|
|
219
|
+
- **Good (during migration):** `except ValueError as exc: raise_logged_unprocessable_from_exception(logger, exc, log_event="…", extra_safe_types=(ValueError,))` — only when the caught `ValueError` is known to originate from our own validators.
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## Python Package Conventions
|
|
224
|
+
|
|
225
|
+
Follow existing patterns in a package when they differ from these defaults.
|
|
226
|
+
|
|
227
|
+
### Layout
|
|
228
|
+
|
|
229
|
+
- Source layout: `src/<package>/` with `py.typed` for PEP 561.
|
|
230
|
+
- Optional extras for heavy deps: `pip install package[api]`, `package[db]`, `package[dev]`.
|
|
231
|
+
- CLI entry point: `package.cli:main`.
|
|
232
|
+
- Config and seed data: YAML in `data/seed/*.yaml`, `data/templates/**/*.yaml`. Use `pathlib` for paths.
|
|
233
|
+
|
|
234
|
+
### `__init__.py` exports
|
|
235
|
+
|
|
236
|
+
- Only export what downstream users need from `from package import X`.
|
|
237
|
+
- Internal types, constants, and helpers stay importable from their submodule but do not appear in `__all__`.
|
|
238
|
+
- Review exports when adding new public API -- keep the surface area intentionally small.
|
|
239
|
+
|
|
240
|
+
### Errors and exceptions
|
|
241
|
+
|
|
242
|
+
- One base exception per package (e.g. `MyPackageError`). All domain exceptions inherit from it.
|
|
243
|
+
- Subclass by domain: `ConfigError`, `ValidationError`, `NotFoundError`.
|
|
244
|
+
- Attach context: `action_name`, `error_code`, path, or extra dict where helpful.
|
|
245
|
+
- Optional `ErrorCode` enum for machine-readable codes.
|
|
246
|
+
|
|
247
|
+
### Extensibility
|
|
248
|
+
|
|
249
|
+
- Prefer `typing.Protocol` with `@runtime_checkable` for pluggable interfaces (stores, sinks, hooks).
|
|
250
|
+
- Protocols live in `_types.py` or a `protocols.py` module.
|
|
251
|
+
- Use factory functions to create implementations with captured dependencies (closure pattern over class inheritance).
|
|
252
|
+
|
|
253
|
+
### Dependency management
|
|
254
|
+
|
|
255
|
+
- Pin direct dependencies to compatible ranges (`>=1.2,<2.0`). Never pin to exact versions in **libraries** (consumers need flexibility). Applications and lockfiles may pin exactly.
|
|
256
|
+
- Keep `[dev]` extras separate from runtime deps. Test/lint tools are always dev-only.
|
|
257
|
+
- Run `pip audit` or equivalent periodically. Never ship packages with known CVEs in dependencies.
|
|
258
|
+
- **New dependencies:** Prefer stdlib or existing transitive deps. If adding a direct dependency, justify it (problem, alternatives considered), check license compatibility with the project, maintenance signal, and binary wheel availability for supported platforms where relevant.
|
|
259
|
+
|
|
260
|
+
### YAML config conventions
|
|
261
|
+
|
|
262
|
+
- All keys: `snake_case`.
|
|
263
|
+
- Validate config at load time (Pydantic models or explicit checks). Fail fast with clear error messages.
|
|
264
|
+
- Keep config schema documented in `docs/reference/`.
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## Testing
|
|
269
|
+
|
|
270
|
+
- **Runner:** pytest with pytest-asyncio (`asyncio_mode = "auto"`).
|
|
271
|
+
- **Layout:** `tests/unit/` and `tests/integration/`. Markers: `unit`, `integration`, `slow`.
|
|
272
|
+
- **Naming:** `test_<module>.py` mirrors `src/<package>/<module>.py`. Test classes: `TestClassName`.
|
|
273
|
+
- **Fixtures:** Shared fixtures in `conftest.py`. Prefer factory fixtures over complex setup.
|
|
274
|
+
- **Assertions:** Plain `assert` for values. `pytest.raises` for exceptions. Descriptive failure messages when non-obvious.
|
|
275
|
+
- **Exception assertions:** Use the narrowest concrete exception in `pytest.raises(...)` (for example, `FileNotFoundError`, `ValueError`, `FrozenInstanceError`). Do not use `pytest.raises(Exception)`; Ruff B017 blocks blind exception assertions.
|
|
276
|
+
- **Mocking:** `unittest.mock.patch` / `MagicMock`. Mock at boundaries (I/O, external services), not internal logic.
|
|
277
|
+
- **Coverage:** Every new public function must have at least one happy-path and one error-path test.
|
|
278
|
+
- **No `print`:** Use `logging` or `capsys` fixture if testing output.
|
|
279
|
+
- **Test isolation:** Tests must not depend on execution order. No shared mutable state between tests. Use fresh fixtures per test.
|
|
280
|
+
- **Parameterize:** Use `@pytest.mark.parametrize` for testing multiple inputs against the same logic. Prefer it over copy-pasting test methods.
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## Open source, collaboration, and review
|
|
285
|
+
|
|
286
|
+
These expectations help external contributors (and any Claude-assisted session) stay aligned with maintainers and downstream users.
|
|
287
|
+
|
|
288
|
+
### Repository hygiene
|
|
289
|
+
|
|
290
|
+
- **Read first:** Before substantive edits, skim `README.md`, `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, `SECURITY.md`, and any `AGENTS.md` / `.cursor/rules` the repo provides. Follow their branching, signing, DCO, or changelog rules over generic advice here.
|
|
291
|
+
- **CI parity:** If the repo documents a single entrypoint (e.g. `make check`, `scripts/ci.sh`, or a GitHub Actions job name), prefer running that before opening a PR so local results match what reviewers see.
|
|
292
|
+
- **Scope:** One PR or change set = one coherent story (feature, fix, or docs). Do not bundle unrelated refactors, formatting-only sweeps, or drive-by renames unless explicitly requested.
|
|
293
|
+
- **Issues:** Link issues in commit messages or PR descriptions (`Fixes #nnn`, `Refs #nnn`) when the project uses them.
|
|
294
|
+
- **Branching:** Assume protected `main`; work on a feature branch and open a PR. Do not force-push to shared branches.
|
|
295
|
+
|
|
296
|
+
### API stability and deprecation
|
|
297
|
+
|
|
298
|
+
- **Public surface:** Treat symbols in `__all__` and documented entrypoints as contracts. Additions should be backward compatible within the same major version.
|
|
299
|
+
- **Deprecations:** Use `warnings.warn(..., category=DeprecationWarning, stacklevel=2)` with a clear message and removal timeline; document in the project’s changelog or migration guide.
|
|
300
|
+
- **Breaking changes:** Reserve for major SemVer bumps; describe migration steps in PR text and user-facing docs.
|
|
301
|
+
|
|
302
|
+
### Documentation and changelog
|
|
303
|
+
|
|
304
|
+
- **User-visible behavior:** Update `README.md`, MkDocs pages, OpenAPI descriptions, or CLI `--help` text when behavior, flags, or defaults change—same change set as the code when the repo expects it.
|
|
305
|
+
- **Changelog:** If the project maintains `CHANGELOG.md` or GitHub Releases notes, add an entry consistent with existing style (Keep a Changelog, etc.).
|
|
306
|
+
|
|
307
|
+
### Data, privacy, and examples
|
|
308
|
+
|
|
309
|
+
- **Fixtures and demos:** No real PII, production URLs, or live credentials. Use `example.com`, synthetic IDs, and obvious placeholders.
|
|
310
|
+
- **Logs and errors:** Do not log full request bodies or auth headers at INFO+; redact tokens and account identifiers where logs might be shared.
|
|
311
|
+
|
|
312
|
+
### Inclusive collaboration (wording and review)
|
|
313
|
+
|
|
314
|
+
- Prefer **clear, literal English** in comments, docs, and review replies; avoid culture-specific idioms and sarcasm that may not translate for global collaborators.
|
|
315
|
+
- Use **ISO 8601** dates in docs and release notes when precision matters (`2026-04-09`).
|
|
316
|
+
- **Accessibility:** For UI work, preserve keyboard navigation, focus order, labels, and contrast when touching components that affect users.
|
|
317
|
+
|
|
318
|
+
### AI-assisted contribution disclosure
|
|
319
|
+
|
|
320
|
+
- If the project or employer requires stating tool use in PRs, follow that policy. Otherwise, still write **human-reviewable** commits: imperative subject line, body explaining *why*, and tests that make intent obvious without relying on chat history.
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## FastAPI Patterns
|
|
325
|
+
|
|
326
|
+
- App factory: `create_application() -> FastAPI` with `lifespan` async context manager.
|
|
327
|
+
- Routes under `api/routes/v1/` with prefix `/api/v1`.
|
|
328
|
+
- Auth router (login/logout/me) without global auth. Protected routers use `dependencies=[Depends(get_current_user)]`.
|
|
329
|
+
- CORS from env (`CORS_ORIGINS`, `ENVIRONMENT`). Development fallback to localhost.
|
|
330
|
+
- Exception handling: `RequestValidationError` -> 422, global `Exception` -> 500. Detail only in `ENVIRONMENT=development`.
|
|
331
|
+
- Request/response models in `api/models/requests.py` and `api/models/responses.py`.
|
|
332
|
+
- Pydantic v2: `BaseModel`, `Field(..., description="...")`, `model_config`.
|
|
333
|
+
- SQLAlchemy 2 + Alembic: declarative models, migrations under `db/migrations/versions/`.
|
|
334
|
+
- Route handlers should be thin: validate input, call a service, return response. Business logic lives in services, not routes.
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
## Frontend Standards (Next.js / React)
|
|
339
|
+
|
|
340
|
+
- Use Next.js App Router. Favor Server Components; use `"use client"` sparingly.
|
|
341
|
+
- 100% TypeScript coverage. Strictly avoid `any`.
|
|
342
|
+
- Follow the Airbnb JavaScript Style Guide.
|
|
343
|
+
- Component files: `PascalCase.tsx`. Utilities and stores: `camelCase.ts`.
|
|
344
|
+
- **State:** Use **Zustand** for shared state (auth, theme, app config, global UI, toasts). Use `useState`/`useReducer` for local component state. Do not introduce Redux or other global state libraries.
|
|
345
|
+
- **Data fetching:** Prefer SWR or TanStack Query for server state; avoid raw `useEffect` + `fetch` + `useState` for API data.
|
|
346
|
+
- **API:** Centralize in one module (e.g. `lib/api.ts`) with typed methods. No ad-hoc `fetch` in components.
|
|
347
|
+
- **Styling:** Tailwind only; use theme tokens for colors/spacing. Semantic HTML and ARIA where needed.
|
|
348
|
+
- **Forms:** Use a consistent approach for non-trivial forms (e.g. React Hook Form + Zod if adopted in the project).
|
|
349
|
+
|
|
350
|
+
### Layout and positioning
|
|
351
|
+
|
|
352
|
+
- Prefer **semantic HTML** and **normal document flow** first. Structure pages with flow; arrange with layout tools, not ad-hoc coordinates.
|
|
353
|
+
- Use **Flexbox** for one-dimensional layouts (rows/columns of items).
|
|
354
|
+
- Use **CSS Grid** for two-dimensional layouts.
|
|
355
|
+
- **Avoid `position: absolute` and `position: relative` for primary page structure** unless there is a clear, necessary reason. Treat absolute positioning as a **last resort**.
|
|
356
|
+
- **Acceptable uses of absolute positioning** (only when appropriate): badges; icons inside a bounded container; overlays; tooltips; modals/backdrops; small decorative elements.
|
|
357
|
+
- If absolute positioning is used, the **parent must provide a clear positioning context** and bounded dimensions where needed.
|
|
358
|
+
- **Avoid brittle layout:** magic `top`/`left`, negative margins, or fixed heights for core structure unless explicitly required. Prefer **`gap`**, **padding**, **margin**, **`max-width`**, and alignment utilities over manual pixel offsets.
|
|
359
|
+
- Layouts must be **responsive** and usable on **narrow viewports** (e.g. mobile).
|
|
360
|
+
- Do not rely on exact content height or text length; components should tolerate **longer, shorter, or wrapped** content without overlap or clipping.
|
|
361
|
+
- Preserve **accessibility** and **semantic structure** while styling.
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
## Workflow
|
|
366
|
+
|
|
367
|
+
### Multi-package workspaces
|
|
368
|
+
|
|
369
|
+
- This workspace may contain several Python packages (each with its own `pyproject.toml`). **Run format, lint, type-check, and tests from the root of the package you changed**, not always the monorepo root, unless documented otherwise.
|
|
370
|
+
|
|
371
|
+
### Git
|
|
372
|
+
|
|
373
|
+
- Use Conventional Commits: `feat:`, `fix:`, `chore:`, `refactor:`, `docs:`, `test:`.
|
|
374
|
+
- One logical change per commit. Keep commits small and reviewable.
|
|
375
|
+
- SemVer: breaking changes bump major, new features bump minor, fixes bump patch.
|
|
376
|
+
- **Pre-commit:** Hooks run Ruff (check + format). Ensure `ruff check` and `ruff format` pass on **all staged files** before pushing so CI and pre-commit do not fail on style (e.g. UP038 `isinstance` unions). See **Before proposing changes** below.
|
|
377
|
+
|
|
378
|
+
### Before proposing changes
|
|
379
|
+
|
|
380
|
+
1. Review existing patterns in the package and any **Open source, collaboration, and review** expectations above.
|
|
381
|
+
2. Run **ruff** (Python) on **every modified file** so pre-commit does not fail:
|
|
382
|
+
- From the **relevant package root** (directory containing that package’s `pyproject.toml`): `ruff check src/ --fix` and `ruff format src/` when changes are under `src/`.
|
|
383
|
+
- **Also lint files outside `src/`** if modified (e.g. `setup.py`, `tests/`): `ruff check setup.py --fix && ruff format setup.py`. Pre-commit hooks check **all staged files**, not just `src/`.
|
|
384
|
+
- Fix any remaining issues: unused imports (F401), **unused assignments (F841 — no write-only locals)**, undefined names (F821), deprecated typing (`Dict`/`List`/`Union`/`Optional` → `dict`/`list`/`|`/`X | None`), **`isinstance(x, (A, B))` → `isinstance(x, A | B)` (UP038)**, `subprocess.run` stdout/stderr PIPE → `capture_output=True` (UP022), ambiguous names (E741, e.g. avoid `l`; use `part`, `item`, etc.).
|
|
385
|
+
- Use `typing.TYPE_CHECKING` and conditional imports for forward references (e.g. `OptimizationResult`) so annotations resolve for type checkers without runtime imports.
|
|
386
|
+
3. Run `pytest` (Python) or `npm test` (frontend). All tests must pass.
|
|
387
|
+
4. If the task is complex, break it into sequential steps.
|
|
388
|
+
|
|
389
|
+
### Code review checklist (self-review before committing)
|
|
390
|
+
|
|
391
|
+
- All new public APIs have type hints and Google-style docstrings.
|
|
392
|
+
- No dead locals: every assigned name is read, or the assignment is removed (Ruff F841).
|
|
393
|
+
- No f-strings in logging calls.
|
|
394
|
+
- `from __future__ import annotations` is present on touched files.
|
|
395
|
+
- No mutable default arguments.
|
|
396
|
+
- No bare `except:` or `except Exception: pass`.
|
|
397
|
+
- Tests exist for new functionality (happy path + error path).
|
|
398
|
+
- Module is under 400 lines.
|
|
399
|
+
- **OSS:** Docs/changelog updated if behavior or public API changed; no secrets or PII in tests or samples; dependency additions justified.
|
|
400
|
+
|
|
401
|
+
### DevOps scripts
|
|
402
|
+
|
|
403
|
+
- Scripts must be idempotent (check state before action).
|
|
404
|
+
- Use `pathlib` for file operations.
|
|
405
|
+
- Use vectorized operations (NumPy/Pandas) instead of loops for data processing.
|
|
406
|
+
|
|
407
|
+
### Context management
|
|
408
|
+
|
|
409
|
+
- Use `/clear` or `/compact` between unrelated tasks to maintain reasoning quality.
|
|
410
|
+
|
|
411
|
+
### When requirements are unclear
|
|
412
|
+
|
|
413
|
+
- **Ask** for scope, compatibility targets (Python versions, browsers), or acceptance criteria instead of guessing.
|
|
414
|
+
- If blocked on a product decision (e.g. new telemetry, breaking API), summarize options and tradeoffs in a short list and stop at the boundary—do not ship speculative behavior.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Create a git branch for a ticket using the Optophi naming convention and branch-base rules
|
|
3
|
+
argument-hint: [ticket-or-issue] [short-description-or-hotfix-flag]
|
|
4
|
+
allowed-tools: Bash(git status:*), Bash(git branch:*), Bash(git checkout:*), Bash(git switch:*), Bash(git rev-parse:*), Bash(git remote:*), Bash(pwd:*), Bash(gh issue view:*), Bash(command -v gh:*)
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
- Working directory: !`pwd`
|
|
10
|
+
- Current branch: !`git branch --show-current`
|
|
11
|
+
- Git status: !`git status --short --branch`
|
|
12
|
+
- Repo root: !`git rev-parse --show-toplevel`
|
|
13
|
+
- `gh` available: !`command -v gh >/dev/null 2>&1 && echo yes || echo no`
|
|
14
|
+
|
|
15
|
+
## Task
|
|
16
|
+
|
|
17
|
+
Create a git branch for ticket/issue: **$1**
|
|
18
|
+
Optional short description or hotfix flag: **$2**
|
|
19
|
+
|
|
20
|
+
Follow this workflow:
|
|
21
|
+
|
|
22
|
+
1. **Pick the base branch.**
|
|
23
|
+
- Default: `develop` (per `CONTRIBUTING.md` — feature work branches
|
|
24
|
+
from `develop`, not `main`).
|
|
25
|
+
- If `$2` contains `hotfix`, `--from=main`, or the task is clearly a
|
|
26
|
+
hotfix (production-critical), use `main` instead.
|
|
27
|
+
- Do **not** automatically `git fetch` or `git pull`. Local state
|
|
28
|
+
varies; let the user trigger updates explicitly if they want.
|
|
29
|
+
- If the chosen base branch does not exist locally, stop and tell
|
|
30
|
+
the user.
|
|
31
|
+
|
|
32
|
+
2. **Pick the branch prefix** using the Conventional Commit type this
|
|
33
|
+
work will produce:
|
|
34
|
+
|
|
35
|
+
| Type of change | Prefix |
|
|
36
|
+
|---|---|
|
|
37
|
+
| New feature | `feat/` |
|
|
38
|
+
| Bug fix | `fix/` |
|
|
39
|
+
| Refactor (behavior unchanged) | `refactor/` |
|
|
40
|
+
| Performance | `perf/` |
|
|
41
|
+
| Docs only | `docs/` |
|
|
42
|
+
| Tests only | `test/` |
|
|
43
|
+
| Chore / tooling / CI | `chore/` |
|
|
44
|
+
|
|
45
|
+
If the type is ambiguous from `$1` and `$2`, default to `feat/` and
|
|
46
|
+
say so in the output so the user can correct you.
|
|
47
|
+
|
|
48
|
+
3. **Build the slug.**
|
|
49
|
+
- Normalize the ticket reference:
|
|
50
|
+
- Numeric `123` stays `123`.
|
|
51
|
+
- `ABC-123`-style keys become lowercase, e.g. `abc-123`.
|
|
52
|
+
- If `$1` is non-numeric and not key-shaped, use it directly as
|
|
53
|
+
the slug head.
|
|
54
|
+
- Build the description slug from `$2` (and, optionally, the `gh`
|
|
55
|
+
lookup below):
|
|
56
|
+
- lowercase
|
|
57
|
+
- hyphen-separated
|
|
58
|
+
- strip special characters
|
|
59
|
+
- keep concise (≤ 5 words is a good rule of thumb)
|
|
60
|
+
- Final shape: `<prefix>/<ticket>-<slug>`.
|
|
61
|
+
|
|
62
|
+
4. **Optional: enrich from GitHub.** If `gh` is available and `$1` is a
|
|
63
|
+
numeric issue number, try:
|
|
64
|
+
```
|
|
65
|
+
gh issue view $1 --json title -q .title
|
|
66
|
+
```
|
|
67
|
+
If that succeeds, use the title to refine the slug (merge with
|
|
68
|
+
anything `$2` supplied). If `gh` is not installed or the call fails,
|
|
69
|
+
fall back to `$2` silently. Never block on GitHub being reachable.
|
|
70
|
+
|
|
71
|
+
5. **Pre-flight checks.**
|
|
72
|
+
- If `git status` shows uncommitted changes, warn the user and ask
|
|
73
|
+
whether to stash, commit, or stop before creating the branch.
|
|
74
|
+
- If the target branch already exists, do **not** fail silently:
|
|
75
|
+
report it and offer to switch to it instead.
|
|
76
|
+
|
|
77
|
+
6. **Create and switch.**
|
|
78
|
+
- `git switch -c <branch-name> <base>` to create and switch.
|
|
79
|
+
- `git switch <branch-name>` if it already exists.
|
|
80
|
+
|
|
81
|
+
7. **Confirm and suggest next.**
|
|
82
|
+
- Report the final branch name, the base it was cut from, and
|
|
83
|
+
whether it was created or switched-to.
|
|
84
|
+
- Suggest `/plan <task>` as the next step so the architectural
|
|
85
|
+
analysis runs before code is written.
|
|
86
|
+
|
|
87
|
+
## Output
|
|
88
|
+
|
|
89
|
+
Keep it short and action-oriented:
|
|
90
|
+
|
|
91
|
+
- **Proposed:** `<prefix>/<ticket>-<slug>` from `<base>`.
|
|
92
|
+
- **Action:** created / switched to / skipped (with reason).
|
|
93
|
+
- **Notes:** any judgment calls (inferred prefix, `gh` fallback, slug
|
|
94
|
+
truncation).
|
|
95
|
+
- **Next:** `/plan <task>`.
|
|
96
|
+
|
|
97
|
+
If anything is unclear — ambiguous prefix, uncommitted changes, missing
|
|
98
|
+
base branch — stop and ask rather than guessing.
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: config-change
|
|
3
|
+
description: Use whenever a change touches declared domain artifacts — YAML/JSON under data/seed/, Pydantic schema validators, FSM definitions, contract definitions, or the loader that parses them. Configuration-driven engineering means these changes carry more risk than typical code changes, because they define what the system is.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Skill: config-change
|
|
7
|
+
|
|
8
|
+
## When this skill fires
|
|
9
|
+
|
|
10
|
+
- Any edit to files under `data/seed/**` or `data/templates/**`.
|
|
11
|
+
- Any change to the Pydantic models that validate configuration shape.
|
|
12
|
+
- Any change to the parser, loader, or schema converter in the core.
|
|
13
|
+
- Any change to identifier validation rules.
|
|
14
|
+
- Any change to how domain artifacts are versioned or migrated.
|
|
15
|
+
|
|
16
|
+
## Why this is its own skill
|
|
17
|
+
|
|
18
|
+
Configuration-Driven Engineering is one of the package's two laws (see
|
|
19
|
+
`ARCHITECTURE.md`). Domain artifacts live in configuration so non-engineers
|
|
20
|
+
can author them, so artifacts can be versioned and diffed as data, and so
|
|
21
|
+
the engine stays small. That means configuration changes are **user-visible
|
|
22
|
+
contract changes** — treat them as such.
|
|
23
|
+
|
|
24
|
+
## Steps
|
|
25
|
+
|
|
26
|
+
### 1. Identify the kind of change
|
|
27
|
+
|
|
28
|
+
- **Additive** — a new optional field, a new artifact, an additional
|
|
29
|
+
identifier. Usually backward compatible.
|
|
30
|
+
- **Rename or remove** — field renamed, removed, or its type changed.
|
|
31
|
+
Breaking.
|
|
32
|
+
- **Required-field addition** — new field that existing artifacts don't
|
|
33
|
+
set. Breaking without a default.
|
|
34
|
+
- **Semantics change** — same shape, different meaning (e.g. `retry_count`
|
|
35
|
+
now means "including first attempt" instead of "excluding"). Subtle and
|
|
36
|
+
dangerous; treat as breaking.
|
|
37
|
+
|
|
38
|
+
Name the kind of change explicitly in the PR description.
|
|
39
|
+
|
|
40
|
+
### 2. Validate the full lifecycle
|
|
41
|
+
|
|
42
|
+
A configuration change must survive the full parse → store → build pipeline:
|
|
43
|
+
|
|
44
|
+
- [ ] **Parse** — load the changed artifact through the parser. Invalid
|
|
45
|
+
shapes fail at load time with a clear, actionable error message.
|
|
46
|
+
- [ ] **Store** — round-trip through each supported backend: SQLite,
|
|
47
|
+
Postgres, MongoDB. Reading back must yield an equivalent object.
|
|
48
|
+
- [ ] **Build** — construct the runtime artifact (validator, pipeline,
|
|
49
|
+
state machine, entity session) from the parsed/persisted form.
|
|
50
|
+
- [ ] **Execute** — exercise the artifact's signature behaviour against a
|
|
51
|
+
realistic input.
|
|
52
|
+
|
|
53
|
+
If the parser or schema changed, also load every existing seed file and
|
|
54
|
+
confirm they still parse.
|
|
55
|
+
|
|
56
|
+
### 3. Identifier rules
|
|
57
|
+
|
|
58
|
+
- Identifiers in YAML (states, triggers, contract names, rule names) are
|
|
59
|
+
lowercase + underscores only, unless the package's existing seed data
|
|
60
|
+
documents a different convention.
|
|
61
|
+
- Use the package's identifier validation helper (for example,
|
|
62
|
+
PyStator's `_validate_name()`). Do not add a new validator.
|
|
63
|
+
- Identifier collisions across the artifact must fail at parse time, not
|
|
64
|
+
at build time.
|
|
65
|
+
|
|
66
|
+
### 4. Schema evolution
|
|
67
|
+
|
|
68
|
+
- [ ] Is this change backward compatible with existing seed data? Load
|
|
69
|
+
every file under `data/seed/` and confirm it still parses.
|
|
70
|
+
- [ ] If breaking, does it warrant a major version bump?
|
|
71
|
+
- [ ] Is there a migration path? A script, a note in the changelog, or
|
|
72
|
+
both.
|
|
73
|
+
- [ ] Is the schema still documented in `docs/reference/`? Update it in
|
|
74
|
+
the same PR.
|
|
75
|
+
|
|
76
|
+
### 5. Cross-package implications
|
|
77
|
+
|
|
78
|
+
- For a PyStator FSM change: verify the `context_validator` hook contract
|
|
79
|
+
still holds. Test hierarchical / parallel transitions if they are
|
|
80
|
+
affected.
|
|
81
|
+
- For a PyCharter contract change: verify `validate_for_state` still
|
|
82
|
+
passes for each FSM state bound to the contract.
|
|
83
|
+
- For any change to `metadata.governance_rules.lifecycle`: this is a
|
|
84
|
+
sanctioned bridge point — changes here affect both packages.
|
|
85
|
+
|
|
86
|
+
### 6. Tests
|
|
87
|
+
|
|
88
|
+
- [ ] Unit tests cover the schema validator with valid and invalid input.
|
|
89
|
+
- [ ] Integration tests cover the parse → store → build lifecycle.
|
|
90
|
+
- [ ] Backend-parity tests confirm the store round-trips the new shape
|
|
91
|
+
across SQLite, Postgres, and MongoDB.
|
|
92
|
+
|
|
93
|
+
## Output
|
|
94
|
+
|
|
95
|
+
Before committing, produce:
|
|
96
|
+
|
|
97
|
+
- A short summary of the change kind (additive / rename / remove / required
|
|
98
|
+
/ semantic).
|
|
99
|
+
- The list of seed files touched or validated.
|
|
100
|
+
- Confirmation that parse → store → build → execute succeeded for at
|
|
101
|
+
least one realistic artifact.
|
|
102
|
+
- Migration notes, if breaking.
|
|
103
|
+
|
|
104
|
+
## Done criteria
|
|
105
|
+
|
|
106
|
+
- Every existing seed file still parses under the new schema, or a
|
|
107
|
+
migration note explains which do not and why.
|
|
108
|
+
- Store round-trip succeeds across all three backends.
|
|
109
|
+
- Docs in `docs/reference/` are updated.
|
|
110
|
+
- PR description flags this as a configuration change and calls out
|
|
111
|
+
backward-compatibility.
|
|
112
|
+
|
|
113
|
+
## Common mistakes to avoid
|
|
114
|
+
|
|
115
|
+
- Adding a new required field without a default or a migration.
|
|
116
|
+
- Renaming a field in the schema but forgetting to update seed data.
|
|
117
|
+
- Testing the new artifact only — forgetting to verify existing artifacts
|
|
118
|
+
still parse.
|
|
119
|
+
- Validating at build time what should have failed at parse time. Fail
|
|
120
|
+
fast.
|
|
121
|
+
- Relying on SQLite-only integration tests when Postgres or MongoDB has
|
|
122
|
+
a different representation (JSONB vs. document).
|