Skip to content

Module map

Seven uv workspace members live under packages/*, declared at pyproject.toml:100; the root carries no [project] table because it is a virtual workspace holding only the member list, the shared dev dependency-group, and the shared tool config (pyproject.toml:1). The internal import graph is a star: atif-cli declares five siblings as ==-pinned dependencies (packages/atif-cli/pyproject.toml:32), plus one further edge from atif-analytics to atif-models that a forbidden import-linter contract leaves open (pyproject.toml:520), with every other pair closed by an independence contract (pyproject.toml:515). Every cross-package import sits inside a function body or a TYPE_CHECKING block so the CLI’s fast path pulls in no duckdb, harbor, lancedb, boto3, or polars, which is why PLC0415 is ignored workspace-wide (pyproject.toml:164). Modules below are ordered by total source LOC, descending; LOC figures are wc -l over the file.

run_analyze composes eight pipelines in a fixed stage order — cluster, terms, community, classify, trajectory, conflicts, friction, perceived (packages/atif-analytics/src/atif_analytics/application/analyze.py:36). The first three are structural math at zero LLM cost (:8); the remaining five call a model and honor dry_run, which defaults to True as a cost guard so those stages return plan dicts instead of spending (:19). Every classifier system prompt lives in one module, four of them assembled by concatenating a shared appendix (packages/atif-analytics/src/atif_analytics/application/prompts.py:1006), and the pydantic v2 response schemas they bind against are pure domain models whose field descriptions are themselves part of the prompt surface (packages/atif-analytics/src/atif_analytics/domain/models.py:3). This is the one member permitted to import a sibling — atif-models and nothing else (pyproject.toml:520).

register(con, corpus_root) binds a DuckDB connection to a contract-shaped corpus tree and exposes 16 views plus 9 macros (packages/atif-duck/src/atif_duck/__init__.py:9, packages/atif-duck/src/atif_duck/infrastructure/registry.py:1212). Those names are not introspected at runtime: a static catalog answers atif-sql schema in under 50 ms with no DuckDB bind, and two drift tests assert it column-for-column against DESCRIBE and signature-for-signature against the DDL (packages/atif-duck/src/atif_duck/domain/catalog.py:3). The 12 analytics views and 13 analytics macros register separately, each only when its backing parquet is populated, because a corpus with no atif-sql analyze run is the default state (packages/atif-duck/src/atif_duck/infrastructure/analytics.py:124). It is the one member with domain/ and infrastructure/ but no application/, a deliberate shape that is why it carries no layers contract among the seven (pyproject.toml:462).

The composition root: it declares five siblings as ==-pinned dependencies (packages/atif-cli/pyproject.toml:32) and wires every cross-package seam — the ConverterPort adapter, the clock, version pins, the DuckDB connection (packages/atif-cli/src/atif_cli/app.py:5). Ten commands hang off one cyclopts App: nine @app.command functions from convert (packages/atif-cli/src/atif_cli/app.py:226) to schema (:1055), plus the cron sub-app registered at :65, with main (:1093) exposed as the single console script named atif-sql (packages/atif-cli/pyproject.toml:42). Heavy imports — duckdb, harbor through atif-converter, pydantic through atif-corpus — are deferred into the command bodies that use them so schema, --help, and --version stay on a lean import graph that a fresh-interpreter test pins (packages/atif-cli/src/atif_cli/app.py:22). Failures resolve to stable exit codes, 64 for parse, 65 for catalog, 70 for runtime, split between a pure taxonomy module (packages/atif-cli/src/atif_cli/errors.py:25) and a driver-dependent classifier (packages/atif-cli/src/atif_cli/duck_errors.py:34) so the lean path never imports duckdb.

The vector-search write path: Cohere Embed v4 on Bedrock behind EmbeddingProvider, a local LanceDB store behind VectorStorePort, and a corpus reader behind TextRowsPort (packages/atif-embed/src/atif_embed/domain/ports.py:31). run_backfill anti-joins step text against the store’s uuid-to-text-hash map, embeds the misses, and bounds loss three ways — chunked discovery, mid-run checkpoints, and per-batch isolation inside a chunk (packages/atif-embed/src/atif_embed/application/embed.py:61). Two stamps keep the vector space honest: model_id and dimension on every row, checked on both the write and the read path so a provider switch fails loud instead of corrupting kNN (packages/atif-embed/src/atif_embed/domain/embedding_guard.py:3), and text_hash of the exact text a row was built from, so a re-conversion under a stable uuid reads as stale (packages/atif-embed/src/atif_embed/domain/text_stamp.py:3). Additive schema columns migrate online through a metadata-only add_columns, keyed on a SCHEMA_VERSION sidecar file (packages/atif-embed/src/atif_embed/infrastructure/lance_store.py:61).

One materialization pass is sweep, scan, plan, convert, write, advance watermark, and materialize is that pass (packages/atif-corpus/src/atif_corpus/application/materialize.py:476). The decision half is pure — the domain never stats a file and never reads a clock, so build_plan partitions scanned sessions into to-materialize, up-to-date, and skipped-live deterministically from mtimes in epoch nanoseconds plus an injected now (packages/atif-corpus/src/atif_corpus/domain/sessions.py:159). The write half is atomic by construction: artifacts land in a temp session directory that is renamed into place, and every writer fsyncs the tmp file before the rename and the directory after, because atif-duck reads the corpus with no locks and no journal (packages/atif-corpus/src/atif_corpus/infrastructure/atomic.py:5). Conversion arrives through the ConverterPort Protocol, typed to the contract’s artifact shapes rather than converter internals since this member may never import atif-converter (packages/atif-corpus/src/atif_corpus/domain/ports.py:41).

convert_and_audit returns a conversion result paired with a loss report — the trajectory plus an accounting of what upstream dropped (packages/atif-converter/src/atif_converter/application/convert_and_audit.py:105). The conversion is one private harbor call, ClaudeCode._convert_events_to_trajectory, confined to a single adapter module behind a startup assertion (packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:112), which is why the dependency is ceilinged at harbor>=0.22.0,<0.23 (packages/atif-converter/pyproject.toml:23). The seven known conversion gaps are types rather than prose — FidelityGap enumerates them (packages/atif-converter/src/atif_converter/domain/fidelity.py:44) and a pure enrichment pass repairs three by re-running harbor’s deterministic normalization order over the raw records (packages/atif-converter/src/atif_converter/domain/enrichment.py:198). A fingerprint snapshot is taken before harbor reads and re-checked afterwards, so a session that resumes writing mid-conversion fails instead of yielding mutually inconsistent artifacts (packages/atif-converter/src/atif_converter/infrastructure/raw_records.py:110).

A deliberately narrow seam: a system prompt, a user prompt, and a pydantic schema in; a validated instance of that schema out (packages/atif-models/src/atif_models/domain/ports.py:116). Its only in-repo consumer is atif-analytics — the single edge the forbidden contract leaves open (pyproject.toml:520) — and the registry is the only place in the workspace where a Bedrock model id is written down, so a pipeline names a family and a size alias and lets resolve pick the id (packages/atif-models/src/atif_models/domain/registry.py:6, :109). The default adapter posts an OpenAI chat-completions body to invoke_model in strict json_schema mode, dispatching the blocking call through anyio.to_thread under a capacity limiter with tenacity owning the retry loop and botocore’s own retries disabled (packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py:142). Strict mode demands additionalProperties: false at every object level and every property listed in required, which one pure transform enforces before the schema reaches the wire (packages/atif-models/src/atif_models/domain/schema.py:40).