Code conventions keep StoryMatrix portable, deterministic, and aligned with its clean-architecture boundaries.
๐งฑ Layer dependency rule
Dependencies point inward (AGENTS.md). The Domain layer depends on nothing beyond Pydantic v2, typing, and UUID utilities; Application depends on Domain; Infrastructure implements ports for Application and Domain; Crew configures LLM-driven workflows around the application boundary.
๐ค Lazy-inner-import DI
Factories keep heavy-adapter or C-extension imports inside the callable that constructs an adapter. This prevents startup crashes from conflicting C extensions such as ffmpeg, pydub, and torch (src/storymatrix/infrastructure/container.py). The convention covers timeline, SFX, and other crash-prone heavy adapters. Music is a deliberate exception: LocalMusicAdapter and MockMusicAdapter are lightweight enough to import at module scope.
# โ
Lazy import inside a dependency-injector factory
pillow_image_adapter = providers.Singleton(
lambda pillow_config: getattr(
__import__(
"storymatrix.infrastructure.adapters.image.pillow_adapter",
fromlist=["PillowAdapter"],
),
"PillowAdapter",
)(config=pillow_config),
pillow_config=config.providers.pillow,
)The import executes when the provider constructs PillowAdapter, not while the container module loads. The same lazy pattern applies to database engines, voice loading, mock services, SFX, and timeline adapters; music remains the explicit module-scope exception (src/storymatrix/infrastructure/container.py).
๐ Deterministic UUID5 identity
Identity helpers use fixed namespaces and UUID5 so the same normalized name or asset path maps to the same identifier (src/storymatrix/domain/utils/identity.py).
| Purpose | Namespace | Exact name string |
|---|---|---|
| Character | 6ba7b810-9dad-11d1-80b4-00c04fd430c8 | storymatrix_character_{character_name.lower().strip()} |
| Segment | 6ba7b810-9dad-11d1-80b4-00c04fd430c8 | storymatrix_segment|{scene_index}|{segment_index}|{segment_type}|{content.strip()} |
| Asset | 1b4e28ba-2fa1-11d2-883f-0016d3cca427 | file_path_str passed to the asset namespace |
generate_deterministic_uuid(name, namespace) calls uuid.uuid5(namespace, name); character and segment helpers return UUID strings, while the asset helper returns a UUID object (src/storymatrix/domain/utils/identity.py).
โก Async boundaries
Adapter operations use async def and await. Synchronous CLI entries bridge into asynchronous use cases with asyncio.run() (AGENTS.md, src/storymatrix/cli/main.py).
๐ Absolute paths
File locations use pathlib.Path, and absolute-path handling is a project convention rather than something every factory enforces (AGENTS.md). Container factories wrap configured locations in Path; both the configured YAML value and the code default are relative paths, so Path(_safe_config_get(cfg, "app.output_dir", "out")) does not call .resolve() (src/storymatrix/infrastructure/container.py, storymatrix_config.yaml).
๐ค Naming
| Construct | Convention |
|---|---|
| Classes | PascalCase |
| Functions and methods | snake_case |
| Constants | UPPER_CASE |
These names align with the repository guidance and Python tooling configuration (AGENTS.md, pyproject.toml).
๐งน Ruff
pyproject.toml sets line length to 88 and targets Python 3.11. The lint selection and ignores are:
| Setting | Exact value |
|---|---|
select | E, W, F, I, B, C4, UP, RUF |
ignore | E501, B008, RUF001, RUF002, RUF003 |
per-file-ignores | tests/**/*.py = ["S101"]; scripts/chroma_importer.py, scripts/granular_container_importer.py, scripts/validate_playai_integration.py each ignore E402 |
| Formatter quotes | double |
| Formatter indentation | space |
| Docstring code formatting | enabled |
๐งฌ Mypy
The mypy configuration uses Python 3.11 and these strictness flags (pyproject.toml):
| Flag | Value |
|---|---|
warn_return_any | true |
warn_unused_configs | true |
disallow_untyped_defs | true |
disallow_incomplete_defs | true |
check_untyped_defs | true |
disallow_untyped_decorators | true |
strict_optional | true |
warn_redundant_casts | true |
warn_unused_ignores | true |
Missing imports are ignored only for pydub.*, librosa.*, soundfile.*, and elevenlabs.* (pyproject.toml).
๐ Markdown
.markdownlint.yaml enables the default rule set and configures MD013 to permit 120-character prose and heading lines, ignore code-block length, and skip table length checks. It allows inline HTML (MD033), does not require a first-line H1 (MD041), scopes duplicate-heading checks to siblings (MD024), requires blank lines around headings (MD022), allows punctuation .,;: in headings (MD026), enforces ordered-list prefixes (MD029), and requires fenced code blocks (MD046).