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).

PurposeNamespaceExact name string
Character6ba7b810-9dad-11d1-80b4-00c04fd430c8storymatrix_character_{character_name.lower().strip()}
Segment6ba7b810-9dad-11d1-80b4-00c04fd430c8storymatrix_segment|{scene_index}|{segment_index}|{segment_type}|{content.strip()}
Asset1b4e28ba-2fa1-11d2-883f-0016d3cca427file_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

ConstructConvention
ClassesPascalCase
Functions and methodssnake_case
ConstantsUPPER_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:

SettingExact value
selectE, W, F, I, B, C4, UP, RUF
ignoreE501, B008, RUF001, RUF002, RUF003
per-file-ignorestests/**/*.py = ["S101"]; scripts/chroma_importer.py, scripts/granular_container_importer.py, scripts/validate_playai_integration.py each ignore E402
Formatter quotesdouble
Formatter indentationspace
Docstring code formattingenabled

๐Ÿงฌ Mypy

The mypy configuration uses Python 3.11 and these strictness flags (pyproject.toml):

FlagValue
warn_return_anytrue
warn_unused_configstrue
disallow_untyped_defstrue
disallow_incomplete_defstrue
check_untyped_defstrue
disallow_untyped_decoratorstrue
strict_optionaltrue
warn_redundant_caststrue
warn_unused_ignorestrue

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).