# 0) TL;DR (10–15 строк)

- В `intent_router_v2` графы задаются как строковые `graph_id`: `CodeQAGraph` и `DocsQAGraph` (`app/modules/rag/intent_router_v2/intent/graph_id_resolver.py:4`, класс `GraphIdResolver.resolve`).
- Точка входа роутера: `IntentRouterV2.route(...)` (`app/modules/rag/intent_router_v2/router.py:35`, класс `IntentRouterV2`).
- Отдельные классы/функции с именами `CodeQAGraph` и `DocsQAGraph` в `app/` не найдены (поиск по коду).
- В тестовом CLI-пайплайне выполнение идет через `IntentRouterRagPipelineRunner.run_case(...)` (`tests/pipeline_intent_rag/helpers/pipeline_runner.py:45`) и `PipelineRuntime.run_*` (`tests/pipeline_intent_rag/helpers/runtime.py:31`).
- Контракт роутера возвращается моделью `IntentRouterResult` (`app/modules/rag/intent_router_v2/models.py:139`) и включает `intent`, `retrieval_profile`, `query_plan`, `retrieval_spec`, `retrieval_constraints`, `symbol_resolution`, `evidence_policy`.
- `sub_intent`, `symbol_kind_hint`, `symbol_candidates`, `path_hints`, `doc_scope_hints` находятся внутри `query_plan` (`app/modules/rag/intent_router_v2/models.py:43`).
- Топ-слои retrieval задаются фабрикой `RetrievalSpecFactory` (`app/modules/rag/intent_router_v2/retrieval/retrieval_spec_factory.py:9`).
- Реально индексируемые/используемые слои: `C0..C3`, `D1..D4` (`app/modules/rag/indexing/code/pipeline.py:37`, `app/modules/rag/indexing/docs/pipeline.py:24`, `app/modules/rag/intent_router_v2/retrieval/retrieval_spec_factory.py:10`).
- В enum есть `C4..C6`, но build/retrieval-код для них в `app/modules/rag` (кроме `README` и `enums.py`) не найден (`app/modules/rag/contracts/enums.py:13`).
- Слой `F0` в `app/` и `tests/` не найден (поиск по коду).
- Ранжирование retrieval: `lexical_rank -> test_penalty -> layer_rank -> distance` (`app/modules/rag/persistence/retrieval_statement_builder.py:59`).
- В `pipeline_intent_rag` evidence gate отсутствует; в `chat` есть `CodeExplainEvidenceGate(min_excerpts=2)` (`app/modules/chat/evidence_gate.py:15`).

## 1) High-level pipeline (диаграмма текстом)

### CodeQAGraph

`Router -> Plan -> Retrieval per layer -> Merge/Rank -> Evidence gate -> LLM prompt builder`

- Router: `IntentRouterV2.route` (`app/modules/rag/intent_router_v2/router.py:35`, класс `IntentRouterV2`).
  Определяет `intent`, `graph_id`, `query_plan`, `retrieval_spec`, `retrieval_constraints`, `symbol_resolution`.
- Plan: `QueryPlanBuilder.build` (`app/modules/rag/intent_router_v2/analysis/query_plan_builder.py:50`, класс `QueryPlanBuilder`).
  Извлекает anchors, `sub_intent`, symbol/path/doc hints.
- Retrieval per layer: `RetrievalSpecFactory.build` (`app/modules/rag/intent_router_v2/retrieval/retrieval_spec_factory.py:74`) + `RagDbAdapter.retrieve` (`tests/pipeline_intent_rag/helpers/rag_db_adapter.py:41`).
  Для `CODE_QA` выбираются `C*` слои; выполняется запрос в БД с фильтрами.
- Merge/Rank: `RetrievalStatementBuilder.build_retrieve` (`app/modules/rag/persistence/retrieval_statement_builder.py:9`).
  Все выбранные слои читаются одним SQL и сортируются по `lexical_rank`, `test_penalty`, `layer_rank`, `distance`.
- Evidence gate: в цепочке `tests/pipeline_intent_rag` не найден.
  Для отдельного code-explain чата есть `CodeExplainEvidenceGate.evaluate` (`app/modules/chat/evidence_gate.py:19`).
- LLM prompt builder: в режиме `full_chain` — `GigaChatAnswerer.answer`/`_format_context` (`tests/pipeline_intent_rag/helpers/llm_answerer.py:15`, `:27`).
  Формируется prompt из вопроса и первых RAG-строк.

### DocsQAGraph

`Router -> Plan -> Retrieval per layer -> Merge/Rank -> Evidence gate -> LLM prompt builder`

- Router: `IntentRouterV2.route` (`app/modules/rag/intent_router_v2/router.py:35`).
  Для `DOCS_QA` ставит `retrieval_profile="docs"` (`app/modules/rag/intent_router_v2/router.py:112`).
- Plan: `QueryPlanBuilder.build` (`app/modules/rag/intent_router_v2/analysis/query_plan_builder.py:50`).
  Для `DOCS_QA` принудительно `sub_intent="EXPLAIN"` (`app/modules/rag/intent_router_v2/analysis/query_plan_builder.py:78`).
- Retrieval per layer: `RetrievalSpecFactory.build` (`app/modules/rag/intent_router_v2/retrieval/retrieval_spec_factory.py:74`) + `RagDbAdapter.retrieve` (`tests/pipeline_intent_rag/helpers/rag_db_adapter.py:41`).
  Для docs выбираются `D*` слои; при `path_scope` используется docs-scoped набор (`app/modules/rag/intent_router_v2/retrieval/retrieval_spec_factory.py:97`).
- Merge/Rank: `RetrievalStatementBuilder.build_retrieve` (`app/modules/rag/persistence/retrieval_statement_builder.py:9`).
  Механизм ранжирования тот же, что и для code.
- Evidence gate: для docs в `pipeline_intent_rag` не найден.
- LLM prompt builder: в `full_chain` тот же `GigaChatAnswerer` (`tests/pipeline_intent_rag/helpers/llm_answerer.py:15`).

## 2) Router contract (фактическое API)

### Пример JSON (code)

```json
{
  "intent": "CODE_QA",
  "sub_intent": "EXPLAIN",
  "retrieval_profile": "code",
  "symbol_kind_hint": "class",
  "symbol_candidates": ["Context"],
  "path_hints": [],
  "path_scope": [],
  "doc_scope_hints": [],
  "layers": ["C1_SYMBOL_CATALOG", "C0_SOURCE_CHUNKS", "C2_DEPENDENCY_GRAPH"],
  "retrieval_constraints": {
    "include_globs": ["src/**"],
    "exclude_globs": ["tests/**", "**/test_*.py", "**/*_test.py"],
    "prefer_globs": [],
    "test_file_globs": [],
    "test_symbol_patterns": [],
    "max_candidates": 20,
    "fuzzy_symbol_search": {"enabled": true, "max_distance": 2, "top_k": 5}
  },
  "symbol_resolution": {
    "status": "pending",
    "resolved_symbol": null,
    "alternatives": ["Context"],
    "confidence": 0.0
  }
}
```

### Пример JSON (docs)

```json
{
  "intent": "DOCS_QA",
  "sub_intent": "EXPLAIN",
  "retrieval_profile": "docs",
  "symbol_kind_hint": "unknown",
  "symbol_candidates": [],
  "path_hints": ["README_DEPLOY.md"],
  "path_scope": ["README_DEPLOY.md"],
  "doc_scope_hints": ["README_DEPLOY.md", "README*", "docs/**", "**/*.md"],
  "layers": ["D3_SECTION_INDEX", "D2_FACT_INDEX"],
  "retrieval_constraints": {
    "include_globs": ["README_DEPLOY.md", "docs/**", "README*", "**/*.md"],
    "exclude_globs": [".venv/**", "node_modules/**", "**/*.bin", "**/*.png", "**/*.jpg"],
    "prefer_globs": [],
    "test_file_globs": [],
    "test_symbol_patterns": [],
    "max_candidates": 20,
    "fuzzy_symbol_search": {"enabled": true, "max_distance": 2, "top_k": 5}
  },
  "symbol_resolution": {
    "status": "not_requested",
    "resolved_symbol": null,
    "alternatives": [],
    "confidence": 0.0
  }
}
```

### Поля контракта

- `intent`, `retrieval_profile`: формируются в `IntentRouterV2.route` (`app/modules/rag/intent_router_v2/router.py:35`).
- `sub_intent`, `symbol_kind_hint`, `symbol_candidates`, `path_hints`, `doc_scope_hints`: внутри `QueryPlan` (`app/modules/rag/intent_router_v2/models.py:43`), заполняются в `QueryPlanBuilder.build` (`app/modules/rag/intent_router_v2/analysis/query_plan_builder.py:50`).
- `path_scope`: находится в `retrieval_spec.filters.path_scope`; строится `RetrievalFilterBuilder._path_scope` (`app/modules/rag/intent_router_v2/retrieval/retrieval_filter_builder.py:63`).
- `layers`: отдельного top-level поля в `IntentRouterResult` нет; фактический источник — `retrieval_spec.layer_queries[].layer_id` (`app/modules/rag/intent_router_v2/models.py:121`, `:125`).
- `retrieval_constraints`: модель `RetrievalConstraints` (`app/modules/rag/intent_router_v2/models.py:67`), заполняется в `RetrievalConstraintsFactory.build` (`app/modules/rag/intent_router_v2/retrieval/retrieval_constraints_factory.py:22`).
- `symbol_resolution`: модель `SymbolResolution` (`app/modules/rag/intent_router_v2/models.py:79`), первичный статус задает `_initial_symbol_resolution` (`app/modules/rag/intent_router_v2/router.py:93`).

### `symbol_resolution` статусы и переходы

- Допустимые статусы: `not_requested`, `pending`, `resolved`, `ambiguous`, `not_found` (`app/modules/rag/intent_router_v2/models.py:15`).
- Начальное состояние: `IntentRouterV2._initial_symbol_resolution` (`app/modules/rag/intent_router_v2/router.py:93`).
- Изменение статуса в тестовом router+rag пайплайне: `_resolve_symbol` (`tests/pipeline_intent_rag/helpers/pipeline_runner.py:69`), где `pending` переходит в `resolved`/`ambiguous`/`not_found`.

### Где определены типы/схемы и где валидация

- Типы/схемы: `QueryPlan`, `RetrievalConstraints`, `SymbolResolution`, `RetrievalSpec`, `IntentRouterResult` в `app/modules/rag/intent_router_v2/models.py:43`, `:67`, `:79`, `:121`, `:139`.
- Валидация: pydantic-модели с `ConfigDict(extra="forbid")` (например `app/modules/rag/intent_router_v2/models.py:44`, `:68`, `:80`, `:122`, `:140`), плюс валидаторы confidence (`app/modules/rag/intent_router_v2/models.py:37`, `:208`).

## 3) Layer inventory (что реально индексируется и как)

### 3.1 Code layers

#### C0_SOURCE_CHUNKS

- Назначение: фрагменты исходного кода для цитирования и lexical fallback.
- Единица индексации: chunk.
- Источник данных: AST-разбор (`ast.parse`) с fallback на оконную нарезку (`app/modules/rag/indexing/code/code_text/chunker.py:17`).
- Схема записи:
  - Базовые поля документа: `layer`, `repo_id`, `commit_sha`, `path`, `title`, `text`, `span_start/end`, `metadata`, `embedding` (`app/modules/rag/contracts/documents.py:62`).
  - C0 metadata: `chunk_index`, `chunk_type`, `module_or_unit`, `is_test`, `artifact_type` (`app/modules/rag/indexing/code/code_text/document_builder.py:17`).
  - Колонки БД: `rag_chunks` + `layer/lang/repo_id/commit_sha/title/metadata_json/span_*...` (`app/modules/rag/persistence/schema_repository.py:47`, `:112`).
- Где строится индекс: `CodeIndexingPipeline.index_file` (`app/modules/rag/indexing/code/pipeline.py:37`) + `CodeTextDocumentBuilder.build` (`app/modules/rag/indexing/code/code_text/document_builder.py:9`).
- Где retrieval: `RagDbAdapter.retrieve` (`tests/pipeline_intent_rag/helpers/rag_db_adapter.py:41`) -> `RagRepository.retrieve|retrieve_lexical_code` (`app/modules/rag/persistence/repository.py:62`, `:87`).
- Как ранжируется: `build_retrieve` и `build_lexical_code` (`app/modules/rag/persistence/retrieval_statement_builder.py:9`, `:64`).

#### C1_SYMBOL_CATALOG

- Назначение: каталог символов (class/function/method/import aliases).
- Единица индексации: symbol.
- Источник данных: Python AST visitor (`app/modules/rag/indexing/code/symbols/extractor.py:32`).
- Схема записи:
  - C1 metadata: `symbol_id`, `qname`, `kind`, `signature`, `decorators_or_annotations`, `docstring_or_javadoc`, `parent_symbol_id`, `package_or_module`, `is_entry_candidate`, `is_test`, `lang_payload`, `artifact_type` (`app/modules/rag/indexing/code/symbols/document_builder.py:20`).
- Где строится индекс: `SymbolExtractor.extract` (`app/modules/rag/indexing/code/symbols/extractor.py:24`) + `SymbolDocumentBuilder.build` (`app/modules/rag/indexing/code/symbols/document_builder.py:9`).
- Где retrieval: тот же общий путь SQL retrieval (`app/modules/rag/persistence/retrieval_statement_builder.py:9`).
- Как ранжируется: общий ranking SQL (`app/modules/rag/persistence/retrieval_statement_builder.py:31`, `:39`, `:59`).

#### C2_DEPENDENCY_GRAPH

- Назначение: связи между символами.
- Единица индексации: edge.
- Источник данных: AST visitor по классам/функциям/import/call (`app/modules/rag/indexing/code/edges/extractor.py:33`).
- Схема записи:
  - C2 metadata: `edge_id`, `edge_type`, `src_symbol_id`, `src_qname`, `dst_symbol_id`, `dst_ref`, `resolution`, `is_test`, `lang_payload`, `artifact_type` (`app/modules/rag/indexing/code/edges/document_builder.py:18`).
- Где строится индекс: `EdgeExtractor.extract` (`app/modules/rag/indexing/code/edges/extractor.py:24`) + `EdgeDocumentBuilder.build` (`app/modules/rag/indexing/code/edges/document_builder.py:9`).
- Где retrieval: общий SQL retrieval (`app/modules/rag/persistence/retrieval_statement_builder.py:9`).
- Как ранжируется: общий ranking SQL (`app/modules/rag/persistence/retrieval_statement_builder.py:59`).

Отдельно по `C2_DEPENDENCY_GRAPH`:

- Поддерживаемые `edge_type`: `inherits`, `imports`, `calls` (`app/modules/rag/indexing/code/edges/extractor.py:43`, `:58`, `:72`).
- `dst_ref`: собирается из AST-узла имени/атрибута (`app/modules/rag/indexing/code/edges/extractor.py:107`).
- `dst_symbol_id`: ищется в `qname_map` (`app/modules/rag/indexing/code/edges/extractor.py:89`).
- `resolution`: `"resolved"` если `dst_symbol_id` найден, иначе `"partial"` (`app/modules/rag/indexing/code/edges/extractor.py:102`).

#### C3_ENTRYPOINTS

- Назначение: точки входа (HTTP/CLI).
- Единица индексации: entrypoint.
- Источник данных: эвристики по decorators символов (`FastAPI`, `Flask`, `Typer/Click`) (`app/modules/rag/indexing/code/entrypoints/registry.py:23`).
- Схема записи:
  - C3 metadata: `entry_id`, `entry_type`, `framework`, `route_or_command`, `handler_symbol_id`, `is_test`, `lang_payload`, `artifact_type` (`app/modules/rag/indexing/code/entrypoints/document_builder.py:17`).
- Где строится индекс: detectors `FastApiEntrypointDetector.detect`, `FlaskEntrypointDetector.detect`, `TyperClickEntrypointDetector.detect` (`app/modules/rag/indexing/code/entrypoints/fastapi_detector.py:11`, `flask_detector.py:9`, `typer_click_detector.py:9`).
- Где retrieval: общий SQL retrieval (`app/modules/rag/persistence/retrieval_statement_builder.py:9`).
- Как ранжируется: общий ranking SQL (`app/modules/rag/persistence/retrieval_statement_builder.py:59`), приоритет слоя `C3` в `layer_rank` = 0 (`app/modules/rag/persistence/retrieval_statement_builder.py:41`).

### 3.2 Docs layers

#### D1_MODULE_CATALOG

- Назначение: карточка модуля документации.
- Единица: документ на файл (если есть `id/type/domain` во frontmatter).
- Схема: `module_id`, `type`, `domain`, `status`, `version`, `tags`, `owners`, `links`, `source_path`, `summary_text`, `doc_kind` и поля связей (`calls_api`, `called_by`, ... ) (`app/modules/rag/indexing/docs/document_builder.py:10`).
- Build: `DocsIndexingPipeline.index_file` (`app/modules/rag/indexing/docs/pipeline.py:24`) -> `DocsDocumentBuilder.build_module_catalog` (`app/modules/rag/indexing/docs/document_builder.py:10`).
- Retrieval: общий SQL retrieval (`app/modules/rag/persistence/retrieval_statement_builder.py:9`).

#### D2_FACT_INDEX

- Назначение: атомарные факты.
- Единица: fact.
- Схема: `fact_id`, `subject_id`, `predicate`, `object`, `object_ref`, `anchor`, `tags`, `source_path` (`app/modules/rag/indexing/docs/document_builder.py:86`).
- Build: `DocsIndexingPipeline._extract_facts/_facts_from_table/_facts_from_lists` (`app/modules/rag/indexing/docs/pipeline.py:55`, `:76`, `:118`) -> `build_fact` (`app/modules/rag/indexing/docs/document_builder.py:86`).
- Retrieval: общий SQL retrieval (`app/modules/rag/persistence/retrieval_statement_builder.py:9`).

#### D3_SECTION_INDEX

- Назначение: секции документа.
- Единица: section chunk.
- Схема: `module_id`, `type`, `domain`, `tags`, `section_path`, `section_title`, `order`, `doc_kind`, `source_path`, `artifact_type` (`app/modules/rag/indexing/docs/document_builder.py:42`).
- Build: `MarkdownDocChunker.chunk` (`app/modules/rag/indexing/docs/chunkers/markdown_chunker.py:20`) -> `DocsDocumentBuilder.build_section` (`app/modules/rag/indexing/docs/document_builder.py:42`).
- Retrieval: общий SQL retrieval (`app/modules/rag/persistence/retrieval_statement_builder.py:9`).

#### D4_POLICY_INDEX

- Назначение: policy-документы.
- Единица: policy doc.
- Схема: `policy_id`, `applies_to`, `rules`, `default_behaviors`, `doc_kind`, `section_path`, `source_path` (`app/modules/rag/indexing/docs/document_builder.py:64`).
- Build: только если `frontmatter.type == "policy"` (`app/modules/rag/indexing/docs/pipeline.py:36`) -> `build_policy` (`app/modules/rag/indexing/docs/document_builder.py:64`).
- Retrieval: общий SQL retrieval (`app/modules/rag/persistence/retrieval_statement_builder.py:9`).

## 4) Scoping & filtering (самое важное)

- `repo_id`/workspace scope при индексации:
  - `RagService._resolve_repo_id` берет `project_id` из `rag_session`; fallback — сам `rag_session_id` (`app/modules/rag/services/rag_service.py:161`).
  - `repo_id` пишется в документы/metadata (`app/modules/rag/services/rag_service.py:147`).
- Retrieval scope: SQL всегда фильтрует по `rag_session_id = :sid` (`app/modules/rag/persistence/retrieval_statement_builder.py:23`).
- `include_globs`, `exclude_globs`, `prefer_globs`:
  - Формируются роутером в `RetrievalConstraintsFactory.build` (`app/modules/rag/intent_router_v2/retrieval/retrieval_constraints_factory.py:22`).
  - В `RagDbAdapter.retrieve` применяются `include_globs`/`exclude_globs` через перевод в prefixes/LIKE (`tests/pipeline_intent_rag/helpers/rag_db_adapter.py:46`).
  - Использование `prefer_globs` в retrieval-коде не найдено; найдено только формирование/тесты.
- `path_scope`:
  - Формируется в `RetrievalFilterBuilder._path_scope` (`app/modules/rag/intent_router_v2/retrieval/retrieval_filter_builder.py:63`).
  - В retrieval используется как prefixes: сначала точный scope, затем parent prefix, затем без scope (`tests/pipeline_intent_rag/helpers/rag_db_adapter.py:98`).
- До/после ранжирования:
  - Path/layer/test exclude фильтры попадают в `WHERE` до `ORDER BY` (`app/modules/rag/persistence/retrieval_statement_builder.py:23`, `:58`, `:59`).
- `tests/**` penalty:
  - Penalty вычисляется как `CASE WHEN lower(path) LIKE ... THEN 1 ELSE 0 END` при `prefer_non_tests=True` (`app/modules/rag/persistence/retrieval_statement_builder.py:176`).
  - В `RagDbAdapter` `prefer_non_tests` зависит от `test_policy` (`tests/pipeline_intent_rag/helpers/rag_db_adapter.py:72`).

## 5) Merge policy (как собираются rag_rows)

- Top-K на слой задается в `retrieval_spec.layer_queries` (`app/modules/rag/intent_router_v2/models.py:125`) и заполняется `RetrievalSpecFactory` (`app/modules/rag/intent_router_v2/retrieval/retrieval_spec_factory.py:10`).
- Фактический SQL limit в `pipeline_intent_rag`: `limit = max(top_k)` по всем слоям, не отдельный budget на слой (`tests/pipeline_intent_rag/helpers/rag_db_adapter.py:56`).
- Смешивание слоев: один SQL по списку слоев `layer = ANY(:layers)` + общий `ORDER BY` (`app/modules/rag/persistence/retrieval_statement_builder.py:29`, `:59`).
- Политика merge: interleave/concat как отдельные алгоритмы не найдены; порядок задает SQL sorting.
- Дедупликация по `blob_sha`/`symbol_id` для `rag_rows` в `pipeline_intent_rag` не найдена.
- Общий budget:
  - retrieval rows ограничены `LIMIT :lim` (`app/modules/rag/persistence/retrieval_statement_builder.py:60`);
  - в `full_chain` в prompt уходит максимум 6 `rag_rows`, каждый `content` режется до 1200 символов (`tests/pipeline_intent_rag/helpers/llm_answerer.py:31`, `:34`).

## 6) Prompt builder / evidence packaging

- Формат передачи `rag_rows` в LLM (`pipeline_intent_rag/full_chain`):
  - `GigaChatAnswerer._format_context` делает блоки `- path | title\\ncontent` (`tests/pipeline_intent_rag/helpers/llm_answerer.py:35`),
  - затем `answer` добавляет вопрос+контекст в `user_prompt` (`tests/pipeline_intent_rag/helpers/llm_answerer.py:17`).
- Evidence gate:
  - в `pipeline_intent_rag` gate не найден;
  - в direct code-explain есть `CodeExplainEvidenceGate(min_excerpts=2)` (`app/modules/chat/evidence_gate.py:16`), применяется в `CodeExplainChatService.handle_message` (`app/modules/chat/direct_service.py:46`).
- Шаблоны промптов:
  - code explain: `app/modules/agent/prompts/code_explain_answer_v2.txt:1` (используется в `app/modules/chat/direct_service.py:50`, `app/modules/agent/engine/graphs/project_qa_step_graphs.py:169`);
  - project/docs QA: `app/modules/agent/prompts/project_answer.txt:1` (используется в `app/modules/agent/engine/graphs/project_qa_graph.py:36`);
  - general: `app/modules/agent/prompts/general_answer.txt:1` (используется в `app/modules/agent/engine/graphs/base_graph.py:62`).

## 7) Repro commands

- Построить новый индекс (новая `rag_session`) для локального репозитория:

```bash
python3 -m tests.pipeline_intent_rag reindex --repo-path /abs/path/to/repo
# optional:
python3 -m tests.pipeline_intent_rag reindex --repo-path /abs/path/to/repo --project-id my-project
```

  Источник: `tests/pipeline_intent_rag/cli.py:143`, `:123`; README `tests/pipeline_intent_rag/README.md:54`.

- Прогнать тесткейсы:

```bash
python3 -m tests.pipeline_intent_rag run --mode router_only
python3 -m tests.pipeline_intent_rag run --mode router_rag --rag-session-id <uuid>
python3 -m tests.pipeline_intent_rag run --mode full_chain --rag-session-id <uuid>
```

  Источник: `tests/pipeline_intent_rag/cli.py:135`, `:138`; README `tests/pipeline_intent_rag/README.md:49`.

- Получить `rag_rows` как в логах артефактов:

```bash
python3 -m tests.pipeline_intent_rag run --mode router_rag --rag-session-id <uuid> --test-name rag_probe
ls -t tests/artifacts/rag_probe_*.jsonl | head -n 1
tail -n 50 "$(ls -t tests/artifacts/rag_probe_*.jsonl | head -n 1)"
```

  `rag_rows` сериализуются в JSONL через `PipelineResult.to_record` (`tests/pipeline_intent_rag/helpers/models.py:31`) и `ArtifactWriter` (`tests/pipeline_intent_rag/helpers/artifact_writer.py:20`), имя файла `<test_name>_<YYYYMMDD_HHMMSS>.jsonl` (`tests/pipeline_intent_rag/helpers/artifact_writer.py:16`).

## 8) Appendix: Code pointers

- Entrypoints:
  - Router API: `app/modules/rag/intent_router_v2/router.py:35` (`IntentRouterV2.route`).
  - Graph-id map: `app/modules/rag/intent_router_v2/intent/graph_id_resolver.py:4` (`GraphIdResolver`).
  - CLI: `tests/pipeline_intent_rag/__main__.py:1`, `tests/pipeline_intent_rag/cli.py:178` (`main`).
- Ключевые классы:
  - `IntentRouterV2`: `app/modules/rag/intent_router_v2/router.py:14`.
  - `RagService`: `app/modules/rag/services/rag_service.py:20`.
  - `CodeIndexingPipeline`: `app/modules/rag/indexing/code/pipeline.py:19`.
  - `DocsIndexingPipeline`: `app/modules/rag/indexing/docs/pipeline.py:14`.
  - `RagDbAdapter`: `tests/pipeline_intent_rag/helpers/rag_db_adapter.py:36`.
  - `RetrievalStatementBuilder`: `app/modules/rag/persistence/retrieval_statement_builder.py:8`.
- Конфиги/переменные окружения:
  - Test env: `tests/pipeline_intent_rag/.env.test:3`.
  - Env loader: `tests/pipeline_intent_rag/helpers/env_bootstrap.py:13`.
  - Run config keys: `tests/pipeline_intent_rag/helpers/pipeline_config.py:30`.
- Форматы данных (pydantic/dataclasses):
  - Router schema: `app/modules/rag/intent_router_v2/models.py:43`, `:139`.
  - RAG document dataclass: `app/modules/rag/contracts/documents.py:29`.
  - Pipeline result JSONL model: `tests/pipeline_intent_rag/helpers/models.py:18`.
- Not found:
  - Классы/функции `CodeQAGraph`, `DocsQAGraph` как реальные graph-реализации в `app/` не найдены (строки встречаются как `graph_id` map).
  - `F0` layer не найден в `app/` и `tests/`.
