Metadata-Version: 2.4
Name: persona-dsl
Version: 2026.8.4rc156
Summary: Persona DSL - Framework for implementing Screenplay pattern in Python tests
Author-email: Pavel Glyanenko <pglyanenko@me.com>
Requires-Python: >=3.12
Description-Content-Type: text/markdown
Requires-Dist: playwright==1.61.0
Requires-Dist: allure-python-commons<3,>=2.15
Requires-Dist: python-dotenv
Requires-Dist: pyyaml
Requires-Dist: pydantic<3,>=2
Requires-Dist: requests
Requires-Dist: pyhamcrest
Requires-Dist: redis
Requires-Dist: Faker
Requires-Dist: pillow>=12.3.0
Requires-Dist: zeep>=4.3.3
Requires-Dist: pg8000
Requires-Dist: oracledb
Requires-Dist: kafka-python
Requires-Dist: python-qpid-proton<0.41,>=0.40
Requires-Dist: Unidecode>=1.3
Requires-Dist: black
Requires-Dist: ruff<0.16,>=0.15
Requires-Dist: pip-audit
Requires-Dist: vulture
Requires-Dist: libcst
Requires-Dist: python-dateutil
Requires-Dist: xsdata[cli]
Requires-Dist: lxml<7,>=6
Requires-Dist: rich
Requires-Dist: textual
Requires-Dist: tomlkit
Requires-Dist: typing-extensions>=4.10
Requires-Dist: packaging>=24
Requires-Dist: pygls[ws]==2.1.1
Requires-Dist: lsprotocol==2025.0.0
Provides-Extra: dev
Requires-Dist: build==1.5.0; extra == "dev"
Requires-Dist: black; extra == "dev"
Requires-Dist: ruff<0.16,>=0.15; extra == "dev"
Requires-Dist: pip-audit; extra == "dev"
Requires-Dist: pip>=26.1.2; extra == "dev"
Requires-Dist: vulture; extra == "dev"
Requires-Dist: mypy<2.2,>=2.1; extra == "dev"
Requires-Dist: pytest; extra == "dev"
Requires-Dist: allure-pytest<3,>=2.15; extra == "dev"
Requires-Dist: pytest-xdist; extra == "dev"
Requires-Dist: pytest-randomly; extra == "dev"
Requires-Dist: slipcover==1.0.18; extra == "dev"
Requires-Dist: setuptools<84,>=83; extra == "dev"
Requires-Dist: pre-commit; extra == "dev"
Requires-Dist: types-requests; extra == "dev"
Requires-Dist: types-PyYAML; extra == "dev"
Requires-Dist: types-redis; extra == "dev"
Requires-Dist: types-python-dateutil; extra == "dev"
Requires-Dist: lxml-stubs; extra == "dev"
Requires-Dist: pytest-asyncio; extra == "dev"
Requires-Dist: pytest-timeout; extra == "dev"
Requires-Dist: twine<8,>=7; extra == "dev"
Requires-Dist: wheel<0.48,>=0.47; extra == "dev"
Requires-Dist: hypothesis; extra == "dev"
Requires-Dist: click>=8.3.3; extra == "dev"
Provides-Extra: memory-cognee
Requires-Dist: cognee<2,>=1.2; extra == "memory-cognee"

# persona-dsl

`persona-dsl` — Python framework для E2E-, API- и интеграционных тестов на Screenplay-подходе, native runner, discovery, reporting, generators и MCP/read-only tooling.

## Структура

- `src/persona_dsl/` — framework package.
- `tests/` — package tests.
- `scripts/` — package tooling.
- `template/` — официальный starter project.
- `docs/` — правила и инженерные принципы.

## Установка

Новый проект создаётся standalone bootstrap-скриптом из выбранного release tag.
Скрипт проверяет Python `>=3.12`, создаёт project-local `.venv`, устанавливает
выбранную Persona distribution и выполняет полный project reconcile, настройку
агентской среды и doctor. Значение `latest` включает stable, alpha, beta и RC.

POSIX shell:

```bash
PERSONA_RELEASE="<release-tag>"
PERSONA_RELEASE_URL="https://github.com/pglyanenko/persona/releases/download/${PERSONA_RELEASE}"
curl -fLO "${PERSONA_RELEASE_URL}/install-persona.py"
curl -fLO "${PERSONA_RELEASE_URL}/install-persona.py.sha256"
shasum -a 256 -c install-persona.py.sha256
python3 install-persona.py --project-root ./persona-project --version latest
```

PowerShell:

```powershell
$PersonaRelease = "<release-tag>"
$PersonaReleaseUrl = "https://github.com/pglyanenko/persona/releases/download/$PersonaRelease"
Invoke-WebRequest "$PersonaReleaseUrl/install-persona.py" -OutFile install-persona.py
Invoke-WebRequest "$PersonaReleaseUrl/install-persona.py.sha256" -OutFile install-persona.py.sha256
$Expected = (Get-Content install-persona.py.sha256).Split()[0].ToLowerInvariant()
$Actual = (Get-FileHash install-persona.py -Algorithm SHA256).Hash.ToLowerInvariant()
if ($Actual -ne $Expected) { throw "SHA-256 checksum mismatch" }
py install-persona.py --project-root ./persona-project --version latest
```

Для уже созданного проекта единый lifecycle запускается из его корня:

```bash
make install
make agent
```

## Разработка framework

```bash
make install
make checks
make knowledge-check
make package-smoke
```

## Artemis AMQP

`UseArtemis` предоставляет AMQP 1.0 transport для Apache ActiveMQ Artemis.
`persona.make(SendMessage(...))` отправляет bytes, строку или модель с
зарегистрированным schema codec в address и ожидает broker settlement.
`persona.get(MessageInQueue(...))` читает одну доставку из queue или FQQN
`address::queue`, подтверждает её и возвращает metadata, application properties,
JSON envelope либо типизированную XSD/XML-модель через `as_type`.

Environment profile находится в `skills.artemis.<name>` и содержит `url`,
`auth_key`, `timeout`, `heartbeat`, optional `tls_ca_file` и явный
`allow_insecure_auth` для SASL через `amqp://`. Учётные данные хранятся в
`config/auth.yaml` или environment resolver, а не в URL.

## Agent knowledge и browser authoring / Agent knowledge and browser authoring

Project Profile поддерживает источники кода тестируемого приложения: Git URL и
необязательную рабочую ветку для frontend, backend и микросервисов. Репозитории
не скачиваются при project update/init/install/agent launch; on-demand lifecycle
выполняется через `persona project repos status|checkout|update`, а Project Map
анализирует код и записывает commit-bound выводы в Project Memory. Контракт
описан в [docs/tested-application-code-context.md](docs/tested-application-code-context.md).

RU: Любой запрос через `make agent` начинает навигацию с package-owned
`persona_knowledge(action="agent_flows")`. Slash-команда уже подключает нужный
skill и bounded route contract. Для точного route вызов
`persona_knowledge(action="agent_route", route="<route>")` возвращает
копируемые tool payloads, канонический Markdown state, current action,
continuation/recovery и browser contract. Обычные project/file tools доступны
для исходников и чтения возвращённых artifact paths.

Короткий `eN` ref текущего browser state передаётся как
`persona_browser(session_id=<session_id>, action=<action>, ref=<current_eN_ref>)`.
`find` ищет текст, properties и semantic paths в сохранённом index; `inspect`
возвращает точный узел, ancestry, table context и subtree без нового capture.
`persona_session_state` восстанавливает единственное текущее состояние session.
`persona_generate_support(kind="element")` принимает текущий ref, сохраняет
ref-free semantic fragment и мержит элемент в Page structure. Durable
Page/Element/test code хранит semantic identity без ref. Native semantic
observation является основным generation input; Rich snapshot API остаётся
deprecated compatibility-входом на линейке `0.2.x`.

Публичный Python/package contract:

- distribution: `persona-dsl`
- import package: `persona_dsl`
- CLI: `persona`, `persona-page-gen`, `persona-api-gen`, `persona-schema-gen`,
  `persona-mcp`, `persona-lsp`
- Version diagnostics: `persona --version` and `persona-mcp --version`
- Agent bootstrap CLI: `persona agent init`, `persona agent list-profiles`,
  `persona agent doctor`

## Persona Language Server

`persona-lsp` работает рядом с Pyright, Pylance или другим Python language
server. Persona отвечает за framework diagnostics, completion, navigation,
semantic rename, code actions, semantic presentation и безопасные generation
transactions. Python companion отвечает за syntax, imports, typing, generic
refactors и formatting. Сервер использует STDIO и стандартный LSP lifecycle.

```bash
persona-lsp --client-config all
persona-lsp --client-config vscode
persona-lsp --client-config qwen
persona lsp --client-config neovim
persona-lsp --project-root .
```

Typed profiles предусмотрены для VS Code, Neovim, Helix, Zed, Qwen Code CLI и
GigaCode CLI. `make install` добавляет Persona в project-local `.lsp.json` и
включает native LSP для Qwen/GigaCode независимо от MCP. Некорректный
`.lsp.json` сохраняется побайтно в `.persona/local/recovery/client-lsp/`, после
чего Persona entry восстанавливается атомарно. VS Code client собирается через
`make vscode-lsp-package`; [полный контракт](docs/persona-lsp.md) описывает
установку, trust, multi-root, recovery и поддерживаемые клиенты.

## Проверка Persona-проекта / Persona Project Check

RU: `persona check` — packaged analyzer проекта, написанного на Persona, для
before-merge проверки качества. Команда читает `persona.check.yaml`, применяет
выбранный profile и возвращает агрегированный отчёт по стадиям. `persona check`
не запускает authored scenarios, не строит Allure/runtime report UI и не
очищает runtime outputs. Команда сохраняет machine-readable report в
`reports/persona-check/latest.json` и human Markdown report в
`reports/persona-check/latest.md`. Для interactive text runs команда печатает
line-based progress по стадиям в stderr; `--progress always|never` управляет
этим явно.

EN: `persona check` is the packaged before-merge project analyzer for projects
written on Persona. It reads `persona.check.yaml`, applies the selected profile,
and returns an aggregated staged report. `persona check` does not run authored
scenarios, build the Allure/runtime report UI, or clean runtime outputs. It
writes the machine-readable report to `reports/persona-check/latest.json` and
the human Markdown report to `reports/persona-check/latest.md`. Interactive
text runs print line-based stage progress to stderr; use
`--progress always|never` to control it explicitly.

```bash
persona check --project-root . --profile merge
persona check --project-root . --profile strict --warnings-as-errors
persona check --project-root . --profile max
persona check --project-root . --stage scenario-metadata --stage secrets
persona check --project-root . --format json
persona check --project-root . --report-dir reports/persona-check
persona check --project-root . --progress always
persona check --list-stages
```

## Исправления и форматирование Persona-проекта / Persona Project Fixing and Formatting

RU: `persona fix` применяет безопасные packaged автоисправления качества к
проекту. Текущий safe-fix pipeline включает Ruff `check --fix --no-cache` и
Black по существующим code roots `scenarios`, `support` и `scripts`.
`persona format` оставлен как явный алиас formatter pipeline. Команды
принимают `--path` для ограничения области и завершаются с non-zero exit code,
если fixer/formatter недоступен или выбранный путь отсутствует.

EN: `persona fix` applies safe packaged quality autofixes to a project. The
current safe-fix pipeline runs Ruff `check --fix --no-cache` and Black over the
existing `scenarios`, `support` and `scripts` code roots. `persona format`
remains an explicit formatter-pipeline alias. Use repeated `--path` values to
restrict the scope. Commands exit non-zero when a fixer/formatter is unavailable
or a selected path is missing.

```bash
persona fix
persona fix --path scenarios --path support/pages
persona format
persona format --path scenarios --path support/pages
```

Profiles:

- `advisory` — Persona-semantic analyzer без delegated quality/security stages.
- `merge` — default static gate для проекта перед merge.
- `strict` — broad gate with warnings promoted to errors.
- `security` — artifact hygiene, secrets-only scan, Ruff S security rules,
  dependency audit and optional local Semgrep SAST.
- `audit`, `max` и `public-template` — broad packaged profiles for deeper review.

Project-local custom profiles are declared in `persona.check.yaml` under
`profiles.<name>`. A custom profile extends a built-in or another custom profile
and can set `warnings_as_errors`, `max_console_diagnostics`, `excludes` and
stage overrides. The starter includes a `project-max` example profile that
extends built-in `max` and promotes advisory Persona-quality findings to
failures.

### `persona.check.yaml` reference

RU: файл policy опционален. Если `persona.check.yaml` отсутствует, команда
использует профиль `merge`. Корень YAML должен быть mapping; неизвестные ключи,
неверные типы, неизвестный profile и custom profile inheritance cycle дают
ошибку policy до запуска стадий.

EN: this is the public YAML reference for Persona project checks.

Порядок применения effective policy:

1. выбранный built-in profile или `profiles.<name>`;
2. inherited custom profile chain через `extends`;
3. root-level `stages`, `excludes`, `warnings_as_errors`,
   `max_console_diagnostics`;
4. CLI overrides: `--profile`, repeated `--stage`, `--warnings-as-errors`.

Root fields:

| Field | Type | Default | Meaning |
| --- | --- | --- | --- |
| `profile` | non-empty string | `merge` | Built-in или custom profile, который выбирает набор стадий. |
| `warnings_as_errors` | boolean | `false` | Promotes all warning diagnostics in the run to errors. |
| `max_console_diagnostics` | positive integer | selected profile default, usually `20` | Caps console diagnostics. Saved JSON and Markdown reports keep full diagnostics. |
| `excludes` | list of non-empty strings | default source/cache excludes | Extra `fnmatch` globs appended to default excludes for source collection. |
| `profiles` | mapping | `{}` | Project-local named profiles under `profiles.<name>`. |
| `stages` | mapping | `{}` | Root stage overrides applied after selected profile inheritance. |

Custom profile fields under `profiles.<name>`:

| Field | Type | Default | Meaning |
| --- | --- | --- | --- |
| `extends` | non-empty string | `merge` | Built-in or custom parent profile. Names must not override built-in profiles; cycles fail policy validation. |
| `warnings_as_errors` | boolean | inherited | Promotes warnings for this profile. |
| `max_console_diagnostics` | positive integer | inherited | Console diagnostic cap for this profile. |
| `excludes` | list of non-empty strings | inherited/default excludes | Extra source globs appended before root `excludes`. |
| `stages` | mapping | inherited stages | Per-stage overrides for this profile. |

Stage fields under `stages.<id>` or `profiles.<name>.stages.<id>`:

| Field | Type | Default | Meaning |
| --- | --- | --- | --- |
| `title` | non-empty string | built-in title or stage id | Human report title. |
| `kind` | `built-in` or `delegated` | inherited | Built-in stage uses packaged analyzer code; delegated stage runs a command. |
| `enabled` | boolean | profile membership | Selects the stage when no explicit `--stage` is passed. |
| `required` | boolean | inherited, usually `true` | For delegated stages, unavailable/failing required commands fail the run; advisory delegated stages can skip or warn. Built-in stage failure is driven by diagnostics severity. |
| `severity` | `info`, `warning`, `error` | inherited | Default severity for policy-controlled diagnostics emitted by that stage. Hard contract errors remain errors. |
| `warnings_as_errors` | boolean | inherited | Promotes warnings only for this stage. |
| `command` | shell-like string or list of strings | built-in delegated command, if any | Command for delegated stage. Unknown external stage ids must declare `command`. |
| `args` | list of strings | `[]` | Extra arguments appended to `command`. |
| `paths` | list of strings | stage-specific | Stage input roots for built-in stages and built-in delegated commands. Custom commands must include their path arguments in `command` or `args`. |
| `thresholds` | mapping of string to non-negative integer | stage-specific | Numeric knobs read by built-in stages. Project-defined threshold keys are valid YAML, but packaged stages only act on the documented keys listed below. |
| `max_diagnostics` | positive integer | stage-specific or unlimited | Caps console diagnostics for this stage. Saved JSON and Markdown reports keep full diagnostics. |

Built-in profiles:

| Profile | Stages |
| --- | --- |
| `advisory` | Persona semantic and hygiene stages without delegated quality/security stages. |
| `merge` | `format`, `ruff`, `mypy`, Ruff S security rules, pip-audit, Vulture and all semantic/hygiene stages. |
| `strict` | Same stages as `merge`, with stage warnings promoted to errors. |
| `security` | `artifact-hygiene`, `secrets`, Ruff S security rules, pip-audit and optional Semgrep/Gitleaks/OSV stages. |
| `audit` | Same stage set as `merge` plus optional Semgrep, Gitleaks, OSV, dependency hygiene, SBOM, license, shell, CI, Docker, Docker Compose and Markdown audit stages. |
| `max` | Strongest packaged analyzer profile; projects can extend it with local requiredness and warning policy. |
| `public-template` | Same stage set as `merge`; intended for public starter/template checks. |

Built-in stage ids and stage-specific knobs:

| Stage | Checks | Useful fields |
| --- | --- | --- |
| `project-shape` | Required Persona project paths and forbidden legacy paths. | `paths` lists required project paths; default is `config`, `scenarios`, `scenario_data`, `support`. |
| `artifact-hygiene` | Tracked env files, runtime outputs and cache artifacts. | `enabled`, `severity`, `max_diagnostics`. |
| `file-length` | Python file length in selected roots. | `paths`; `thresholds.max_lines`; `thresholds.warning_lines`; `severity`. |
| `importability` | Python source compile/importability check for maintained roots. | `paths`, `required`, `max_diagnostics`. |
| `complexity` | Function-level complexity policy for scenarios/support/scripts. | `paths`; `thresholds.max_complexity`; `thresholds.warning_complexity`; `severity`; `max_diagnostics`. |
| `strict-code` | Inline suppressions, broad typing escapes and forbidden legacy patterns. | `enabled`, `max_diagnostics`. |
| `duplicate-identities` | Duplicate scenario names, traceability ids and public support class names. | `severity`, `max_diagnostics`. |
| `duplicate-code` | Repeated Python code blocks in maintained project roots. | `thresholds.min_duplicate_lines`; `severity`; `max_diagnostics`. |
| `format` | `python -m black --check` over code roots. | `command`, `args`, `paths`, `required`. |
| `ruff` | `python -m ruff check --no-cache` over code roots. | `command`, `args`, `paths`, `required`. |
| `mypy` | `python -m mypy`, using `pyproject.toml` when present. | `command`, `args`, `paths`, `required`. |
| `security-code` | `python -m ruff check --select S --ignore S101 --no-cache` over project code roots. `S101` is excluded because Persona scenarios use pytest-style `assert` as a supported observable check. | `command`, `args`, `paths`, `required`. |
| `dependency-audit` | Pip-audit of the installed virtual environment for `pyproject.toml` projects after `make install`; requirements-only projects are audited through their requirements files. | `command`, `args`, `required`. |
| `semgrep` | Optional external Semgrep SAST through project-pinned local rules. Persona does not install Semgrep into the audited project environment. Default discovery uses `semgrep.yml`, `semgrep.yaml`, `.semgrep.yml`, `.semgrep.yaml`, `rules/semgrep` or `semgrep-rules`; use `command` for another reviewed executable/rules path. | `command`, `args`, `required`; default `required: false`. |
| `gitleaks` | Optional Gitleaks repository secret scan. | `command`, `args`, `required`; default `required: false`. |
| `osv-audit` | Optional OSV dependency vulnerability audit. | `command`, `args`, `required`; default `required: false`. |
| `dependency-hygiene` | Optional dependency/import hygiene through deptry. | `command`, `args`, `required`; default `required: false`. |
| `sbom` | Optional CycloneDX SBOM generation. | `command`, `args`, `required`; default `required: false`. |
| `license-audit` | Optional license report through pip-licenses. | `command`, `args`, `required`; default `required: false`. |
| `dead-code` | Vulture dead-code scan for support/scripts; advisory by default. | `command`, `args`, `required`; default `required: false`. |
| `suite-topology` | `__suite__.py` presence, shape and supported `suite(...)` keywords. | `enabled`, `max_diagnostics`. |
| `scenario-metadata` | Required `@title`, `@story`, `@tag`; traceability advisory metadata. | `thresholds.traceability_warnings`; `severity`; `max_diagnostics`. Set `traceability_warnings: 0` to disable owner/severity/id advisories. |
| `scenario-body-quality` | Sleep calls are hard errors; unasserted `persona.get`, formal/no-op checks, missing observable checks, missing readiness chain and long raw Ops flows are advisory by default and can fail under `strict` or `warnings_as_errors`. | `thresholds.raw_ops_warning`; `thresholds.readiness_ops_warning`; `severity`; `warnings_as_errors`; `max_diagnostics`. |
| `support-quality` | Persona discovery diagnostics for maintained support/page objects. | `severity`, `max_diagnostics`. |
| `data-config` | YAML/JSON/TOML/CSV parse checks under `config` and `scenario_data`. | `enabled`, `max_diagnostics`. |
| `generated-contracts` | Python compilation for `support/contracts`. | `enabled`, `max_diagnostics`. |
| `reference-integrity` | Static lifecycle/retry/resource/scenario-data/config/upload file reference checks from suite and scenario metadata. | `severity`, `max_diagnostics`. |
| `secrets` | Credentials, tokens, private keys, real env/auth files and secret-like assignments. | `enabled`, `max_diagnostics`; branding and organization names are not findings. |
| `shellcheck` | Optional shell script lint. | `command`, `args`, `required`; default `required: false`. |
| `actionlint` | Optional GitHub Actions workflow lint. | `command`, `args`, `required`; default `required: false`. |
| `hadolint` | Optional Dockerfile lint. | `command`, `args`, `required`; default `required: false`. |
| `compose-config` | Optional Docker Compose config validation. | `command`, `args`, `required`; default `required: false`. |
| `markdownlint` | Optional Markdown lint. | `command`, `args`, `required`; default `required: false`. |

Example maximum project profile:

```yaml
profile: merge

profiles:
  project-max:
    extends: max
    warnings_as_errors: true
    max_console_diagnostics: 80
    stages:
      complexity:
        severity: error
        thresholds:
          max_complexity: 10
          warning_complexity: 6
      scenario-metadata:
        severity: error
        max_diagnostics: 80
      scenario-body-quality:
        severity: error
        max_diagnostics: 80
      support-quality:
        severity: error
        max_diagnostics: 80
      dead-code:
        required: true
      semgrep:
        required: true
        command:
          - semgrep
          - scan
          - --config
          - rules/semgrep
          - --quiet
          - .
      gitleaks:
        required: true
      osv-audit:
        required: true
      dependency-hygiene:
        required: true
      sbom:
        required: false
      license-audit:
        required: false
      shellcheck:
        required: false
      actionlint:
        required: false
      hadolint:
        required: false
      compose-config:
        required: false
      markdownlint:
        required: false
```

Example advisory external stage:

```yaml
profiles:
  security-local:
    extends: security
    stages:
      external-sca:
        title: External SCA
        kind: delegated
        command:
          - python
          - -m
          - pip_audit
        required: false
```

### Consolidated report shape

RU: text report сначала показывает semantic summary, затем сводку по стадиям,
диагностические блоки по стадиям и финальную строку `Summary`. Каждая
диагностика содержит stable `PCS-...` code и machine-readable `reason`.
`max_console_diagnostics`
ограничивает только console output. `persona check` сохраняет полный
machine-readable report в `reports/persona-check/latest.json` и полный
human Markdown report в `reports/persona-check/latest.md`. Для CI используйте
`--format json`. В интерактивном text-терминале live progress показывает
обновляемую строку активной стадии в stderr; в non-TTY text runs и при
`--progress always` progress печатается отдельными строками:
`[i/N] START <stage>` и `[i/N] PASS|WARN|FAIL|SKIP <stage> (...)`. При
`--format json` default `auto` progress выключен, чтобы stdout оставался
валидным JSON.

Для исправления diagnostics агенту следует опираться на machine-readable
`code`, `reason`, `stage_id`, `path`, `line` и полный
`reports/persona-check/latest.json`. Локализованный message является
человеческим пояснением, а не единственным repair contract.

EN: text output starts with semantic summary, then grouped stages, diagnostic
blocks and the final summary line. Interactive text terminals render the active
stage as an updating stderr line; non-TTY text runs and `--progress always`
write line-based stage events. `--format json` keeps stdout valid JSON. The
command also writes full JSON and Markdown reports under
`reports/persona-check/`. Agents should use JSON fields such as `code`,
`reason`, `stage_id`, `path`, `line` and `semantic_summary` for remediation;
localized console messages are human explanations.

```text
Persona check: profile=strict status=failed
Project: /workspace/example-project

Semantic summary:
  project: scenarios=42 modules=18 suites=9 support=31
  quality: direct_ops=14 raw_scenarios=3 missing_checks=1
  domains: checkout=18, accounts=12, admin=8, root=4
  suites: Root / Checkout=18, Root / Accounts=12, Root / Admin=8, Root=4
  support: shared=4 domain_owned=23 top=support.steps.login=8, support.facts.account=6, support.expectations.order=5
  metadata: title=100.0%, story=100.0%, tag=97.62%, severity=90.48%, owner=76.19%, identity=83.33%
  references: lifecycle_profiles: declared=4, used=3, missing=0; retry_profiles: declared=2, used=2, missing=0; resources: declared=5, used=4, missing=1; data_files: declared=0, used=3, missing=0; config_paths: declared=0, used=1, missing=0; files: declared=0, used=2, missing=0
  decision: profile=strict status=failed; failing stages: reference-integrity, scenario-metadata, scenario-body-quality

Stages:
  [PASS] project-shape (4 ms, errors=0 warnings=0 info=0)
  [PASS] artifact-hygiene (12 ms, errors=0 warnings=0 info=0)
  [PASS] importability (10 ms, errors=0 warnings=0 info=0)
  [WARN] complexity (17 ms, errors=0 warnings=2 info=0)
  [PASS] format (180 ms, errors=0 warnings=0 info=0)
  [PASS] ruff (145 ms, errors=0 warnings=0 info=0)
  [PASS] mypy (920 ms, errors=0 warnings=0 info=0)
  [FAIL] reference-integrity (11 ms, errors=1 warnings=0 info=0)
  [FAIL] scenario-metadata (32 ms, errors=3 warnings=0 info=0)
  [FAIL] scenario-body-quality (28 ms, errors=1 warnings=0 info=0)
  [PASS] secrets (35 ms, errors=0 warnings=0 info=0)

scenario-metadata: diagnostics
  - error scenarios/checkout/test_checkout.py:42 PCS-MET-004 missing-owner: Сценарий должен иметь owner label для владения.
  - error scenarios/checkout/test_checkout.py:42 PCS-MET-006 missing-id: Сценарий должен иметь id/link/issue/testcase для трассировки.
  - error scenarios/checkout/test_checkout.py:42 PCS-MET-005 missing-severity: Сценарий должен иметь severity для приоритета triage.

scenario-body-quality: diagnostics
  - error scenarios/checkout/test_checkout.py:58 PCS-BDY-006 raw-ops-flow: Сценарий содержит длинную цепочку прямых Ops; для поддерживаемого production flow лучше вынести поток в Step/CombinedStep/Expectation.

reference-integrity: diagnostics
  - error scenarios/checkout/test_checkout.py:42 PCS-REF-003 missing-resource: Resource не найден: checkout.account.

Summary: status=failed exit_code=1 duration=1356 ms
```

JSON report has stable machine-readable keys:

```json
{
  "profile": "strict",
  "project_root": "/workspace/example-project",
  "status": "failed",
  "exit_code": 1,
  "counts": {
    "severity": {"info": 0, "warning": 2, "error": 5},
    "status": {"passed": 8, "warning": 1, "failed": 3, "skipped": 0}
  },
  "excludes": ["reports/**", "artifacts/**"],
  "semantic_summary": {
    "project": {
      "scenarios_total": 42,
      "test_modules_total": 18,
      "suites_total": 9,
      "support_modules_total": 31
    },
    "metadata_coverage": {
      "title": {"present": 42, "missing": 0, "percent": 100.0},
      "story": {"present": 42, "missing": 0, "percent": 100.0},
      "tag": {"present": 41, "missing": 1, "percent": 97.62},
      "severity": {"present": 38, "missing": 4, "percent": 90.48},
      "owner": {"present": 32, "missing": 10, "percent": 76.19},
      "identity": {"present": 35, "missing": 7, "percent": 83.33}
    },
    "scenario_quality": {
      "direct_ops_total": 14,
      "scenarios_with_raw_ops": 3,
      "scenarios_missing_observable_check": 1,
      "readiness_calls_total": 6,
      "formal_checks_total": 1
    },
    "domains": {"checkout": 18, "accounts": 12, "admin": 8, "root": 4},
    "suites": {"Root / Checkout": 18, "Root / Accounts": 12, "Root / Admin": 8, "Root": 4},
    "support": {
      "layers": {"pages": 6, "steps": 10, "expectations": 5, "shared": 4},
      "shared_modules": 4,
      "domain_owned_modules": 23,
      "top_reused_modules": [
        {"module": "support.steps.login", "usages": 8}
      ]
    },
    "references": {
      "lifecycle_profiles": {"declared": 4, "used": 3, "missing": []},
      "retry_profiles": {"declared": 2, "used": 2, "missing": []},
      "resources": {"declared": 5, "used": 4, "missing": ["checkout.account"]},
      "data_files": {"declared": 0, "used": 3, "missing": []},
      "config_paths": {"declared": 0, "used": 1, "missing": []},
      "files": {"declared": 0, "used": 2, "missing": []}
    },
    "top_risky_files": [
      {"path": "scenarios/checkout/test_checkout.py", "score": 7, "errors": 1, "warnings": 1, "info": 0}
    ]
  },
  "stages": [
    {
      "id": "scenario-metadata",
      "title": "Scenario metadata",
      "status": "failed",
      "duration_ms": 32,
      "counts": {"info": 0, "warning": 0, "error": 3},
      "diagnostics": [
        {
          "stage_id": "scenario-metadata",
          "severity": "error",
          "code": "PCS-MET-004",
          "message": "Сценарий должен иметь owner label для владения.",
          "reason": "missing-owner",
          "path": "scenarios/checkout/test_checkout.py",
          "line": 42
        }
      ]
    }
  ]
}
```

Built-in stages cover project shape, tracked artifact hygiene, importability,
complexity, strict code policy, duplicate identities, duplicate code, suite
topology, effective scenario metadata through suite tree, scenario body quality,
support quality diagnostics, data/config parsing, generated contracts,
reference integrity and secrets-only scanning. Delegated stages run Black,
Ruff, Mypy, Ruff S security rules, pip-audit, Vulture, optional audit tools or
configured external commands through policy.

Privacy contract: default scan fails credentials, tokens, private keys, real env
or auth files and secret-like literal assignments. Organization names, product
names and normal project branding are not default findings.

## Локальный Runner / Local Runner

RU: `persona run`, `persona launch ...` и make-цели starter-проекта пишут
выходной слой запуска в `reports/<env>/<launch_id>/`,
`artifacts/<env>/<launch_id>/` и `reports/latest/`.

EN: `persona run`, `persona launch ...`, and starter Make targets write launch
outputs to `reports/<env>/<launch_id>/`, `artifacts/<env>/<launch_id>/`, and
`reports/latest/`.

RU: `persona report info --project-root <project> --env <env> --launch-id
<launch_id>` читает `summary.json` и `journal.jsonl`. Для failed launch команда
печатает `Ошибки сценариев`: work item, название, `Место ошибки` в формате
`path:line[:column] function`, тип и сообщение исключения, фрагмент
пользовательского стека, а также ссылки на traceback, runtime log, stdout и
stderr artifacts. JSON-режим отдаёт те же данные в
`failed_items[].failure_diagnostics`.

EN: `persona report info --project-root <project> --env <env> --launch-id
<launch_id>` reads `summary.json` and `journal.jsonl`. For a failed launch, it
prints `Ошибки сценариев`: work item, title, `Место ошибки` as
`path:line[:column] function`, exception type and message, user stack excerpt,
and links to traceback, runtime log, stdout, and stderr artifacts. JSON mode
returns the same data in `failed_items[].failure_diagnostics`.

RU: `persona.make(...)`, yield-операции внутри `Step` и
`persona.check(actual, expectation)` формируют вложенные шаги canonical report и
Allure projection. При окончательном падении сценария runner прикладывает
`failure-page.html` и `failure-page.png` для активной страницы каждого уже
загруженного browser skill. Перед чтением HTML runner обновляет Persona Runtime
snapshot, поэтому актуальные элементы получают свежие `data-persona-id`. Если
runtime refresh недоступен, сохраняется raw DOM без маскирования исходной ошибки.
Сбор выполняется после исчерпания test-level retry и управляется
`reporting.enabled`, `reporting.pagesource_on_fail` и
`reporting.screenshot_on_fail`; явное `false` отключает соответствующий
артефакт.

EN: `persona.make(...)`, yielded operations inside a `Step`, and
`persona.check(actual, expectation)` produce nested canonical report steps and
Allure projection entries. On a terminal scenario failure, the runner attaches
`failure-page.html` and `failure-page.png` for the active page of every already
loaded browser skill. Before reading HTML, the runner refreshes the Persona
Runtime snapshot so current elements receive fresh `data-persona-id` values. If
runtime refresh is unavailable, raw DOM is still captured without masking the
original failure.
Capture runs after test-level retries are exhausted and is controlled by
`reporting.enabled`, `reporting.pagesource_on_fail`, and
`reporting.screenshot_on_fail`; explicit `false` disables the corresponding
artifact.

RU: `wait_until(..., description="Ожидание статуса READY во внешнем API")`
использует переданный текст как полный заголовок canonical/Allure шага, runtime
events и диагностики таймаута. Значение `None` сохраняет заголовок по имени
класса expectation. Вложенный шаг `Получено значение: ...` и поле
`step_end.result` содержат полное строковое представление результата без
ограничения длины.

EN: `wait_until(..., description="Wait for READY status in the external API")`
uses the supplied text as the complete canonical/Allure step title, runtime
event description, and timeout diagnostic. `None` keeps the expectation class
name title. The nested `Received value: ...` equivalent and `step_end.result`
retain the complete string representation without a length cap.

## Генераторы контрактов

Persona генерирует committed contract packages для проекта:

- `persona-page-gen` — page object из browser/runtime snapshot.
- `persona-api-gen` — OpenAPI API package с Pydantic models, builders, Ops, responses и catalog.
- `persona-schema-gen xsd` — XSD/XML package с models, builders и schema metadata.
- `persona-schema-gen json` — JSON Object Pydantic package из JSON Schema или явно выбранного sample JSON.
- `persona-schema-gen wsdl` — WSDL/SOAP package с models, builders, service/port/operation catalog и generated SOAP Ops.

RU: Page generation сохраняет atomic semantic controls с ARIA role/name как
`Button`/`Link`/`Checkbox`/`Switch`/`Radio`/`Tab(accessible_name=...)`; общий
component `test_id` используется как stable key вместе с семантическим ключом,
scoped owner или `index`. Snapshot ref остаётся временной координатой только
внутри Persona MCP browser session; готовый Page/Element связывается через
semantic identity и `Page.element_path` и не хранит ref.
Heading/accordion wrapper с единственным одноимённым `Link`/`Button` child
генерируется как action-field на owner level.
Безымянные `list`/`listitem` wrappers наследуют semantic hint от единственного
содержательного child: навигация, breadcrumb и pagination получают executable
ids вроде `tips`, `home`, `page_1`, `next`, `tools_list`, а не numeric
wrapper class names. Common chrome (`Header`/`Sidebar`/`Footer`, `Banner`,
`Navigation`, `ContentInfo`) извлекается или переиспользуется как page component
из `support/pages`; для page-specific generation используется explicit target
scope вместо копирования всего сайта в один PageObject.
Повторяющиеся product/card/tile/item контейнеры с пользовательскими полями и
действиями генерируются как composite `Repeated` коллекции. Прототип содержит
стабильные поля вроде `product_image`, `price`, `product_name`, `add_to_cart`,
`view_product`; `item_scope` связывает контейнер с уникальным semantic descendant
через `repeated_item_scope(ancestors=...)`. Генератор сохраняет структурную
глубину схлопнутых layout-обёрток, а runtime refs, CSS structural classes,
item-specific URLs и глобальные индексы не попадают в прототип.
`ElementList`, `Repeated`, `Table.rows`, `Table.body_rows` и `Table.headers`
являются ленивыми locator contracts, а не Python sequences. Прямая итерация
через `for`, `enumerate`, `list` и comprehensions отклоняется до browser-вызова.
Bounded traversal получает размер через
`persona.get(ElementsCount(collection))` и обращается к
`collection.nth(index)` внутри `range(count)`. Значения столбца читаются одним
`persona.get(TableColumnData(table, column))`.
Native `<select multiple>`, ARIA multiple listbox/combobox и Ant/UiKit multiple
select моделируются через Page-owned `MultiSelect`. Сценарий задаёт точный набор
через `persona.make(SelectMultipleOptions(page.tags, values))`, читает состояние
через `persona.get(MultiSelectValues(page.tags))` и очищает control через
`ClearTypedControl`. Методы Page element `select`, `add`, `remove`, `clear` и
`selected_values` остаются typed runtime для собственных Ops. Они используют
полное совпадение option label, связанный popup и наблюдаемое selected-состояние.
Для виртуализированного Ant/UiKit select связь `aria-controls` адресует popup
через служебный listbox, кликабельные options принадлежат virtual list, а
выбранный label читается из содержимого chip независимо от его comment-title.
Успешные `select` и `add` закрывают связанный Ant/UiKit popup через keyboard
owner и ожидают его скрытия. Открытие соседнего MultiSelect сначала закрывает
точно связанный видимый popup, поэтому portal не перехватывает следующий control.
Page generator распознаёт `multiple`, `aria-multiselectable=true` и Ant/UiKit
multiple class markers и генерирует `MultiSelect` с variants.
Native `date`, `datetime-local`, `time` и Ant Design picker моделируются через
Page-owned `DatePicker`, `DateTimePicker`, `DateRangePicker` и `TimePicker`.
Сценарий вызывает `SelectDate`, `SelectDateTime`, `SelectDateRange` и
`SelectTime` через `persona.make`, а значения читает через
`persona.get(PickerValue(...))` или `persona.get(DateRangeValues(...))`.
Операции принимают `date`/`datetime`/`time` либо канонические ISO-строки,
выбирают точную календарную ячейку или time-column value и ожидают наблюдаемое
значение input. Generator сохраняет picker одним полем Page и исключает
календарный popup и его ячейки из PageObject.
После генерации поддерживаемый PageObject проходит refactor pass и доводится до
доменной структуры: classes используют `PascalCase`, Python fields/methods и
Page/Element public metadata разделены. Python fields/methods используют
readable English `snake_case`, protocol keys остаются английскими.
Page class, `PageDefinition.class_name` и element field не локализуются, не
заполняются русским описанием и не записываются транслитом; у Page/Element нет
public `name`, а русское доменное описание страницы или элемента заполняется
через Page/Element `description`, report label или другой human metadata. При
пустом Element `description` runtime использует Python field alias только как
display label для отчётов и диагностики; locator contract остаётся в
`accessible_name`, `text`, `label`, `placeholder`, `test_id`, `locator`,
`html_name`, `title`, `url`, `alt_text` и связанных selector-полях. Static `persona check` сообщает
`missing_element_description` (`PCS-SUP-005`) как support-quality warning.
Если Element задаёт literal `text` и literal `variants`, static
`persona check` сообщает `element_text_not_in_variants` (`PCS-SUP-020`), когда
`text` не входит в список `variants`; runtime импорт PageObject не
останавливается, а явный выбор через `by_variant(...)`/`get_option(...)`
остаётся строгим.
При refactor pass поля с одинаковым effective role в одном owner scope и
пересекающимся `accessible_name`, например `Пароль` / `Новый пароль`, получают
`exact=True`; полностью одинаковые role/name matches требуют более узкий
owner/locator или `index`, потому что `exact=True` их не различает. Custom
element subclasses наследуют role от доказанного Persona base class
(`TextField`, `Button`, ...); если role не доказана, locator-quality не
выдаёт overlap warning. Plain visible message описывается как
`Text(text=..., exact=True)`, а `Status(accessible_name=...)` используется
только для подтверждённого ARIA status node. `Region`/`Main`/`Dialog`
boundaries опираются на реальный DOM/ARIA owner, locator, `test_id` или
структурную семантику; heading text сам по себе остаётся child element.

EN: Page generation keeps atomic semantic controls with ARIA role/name as
`Button`/`Link`/`Checkbox`/`Switch`/`Radio`/`Tab(accessible_name=...)`; a shared
component `test_id` is stable with a semantic key, scoped owner, or `index`, and
runtime resolution uses stable semantic or structural Page contracts. Snapshot
refs are temporary coordinates inside one Persona MCP browser session and are
never persisted in a ready Page/Element.
Heading/accordion wrappers with one same-name `Link`/`Button` child are generated
as owner-level action fields.
Unnamed `list`/`listitem` wrappers inherit a semantic hint from the single
meaningful child: navigation, breadcrumb and pagination generate executable ids
such as `tips`, `home`, `page_1`, `next`, `tools_list`, not numeric wrapper
class names. Common chrome (`Header`/`Sidebar`/`Footer`, `Banner`, `Navigation`,
`ContentInfo`) is extracted or reused as a page component from `support/pages`;
page-specific generation uses an explicit target scope instead of copying the
whole site into one PageObject.
Repeated product/card/tile/item containers with user-facing fields and actions
generate composite `Repeated` collections with stable child fields such as
`product_image`, `price`, `product_name`, `add_to_cart` and `view_product`.
The generated `item_scope` connects the item container to one unique semantic
descendant through `repeated_item_scope(ancestors=...)`, preserving structural
depth without persisting snapshot refs or structural CSS classes.
After generation, maintained PageObjects go through a refactor pass and are
refined into domain structure: classes use `PascalCase`, Python fields/methods
and Page/Element public metadata are separated. Python fields/methods use
readable English `snake_case`, protocol keys stay English. Page class,
`PageDefinition.class_name` and element fields are not localized and are not
transliterated; Page/Element has no public `name`, and localized domain wording goes
into Page/Element `description`, report label or other human metadata. Empty
Element `description` uses the Python field alias only as a runtime display label
for reports and diagnostics; locator semantics stay in explicit selector fields,
and static `persona check` reports `missing_element_description`
(`PCS-SUP-005`) as a support-quality warning. When an Element declares literal
`text` and literal `variants`, static
`persona check` reports `element_text_not_in_variants` (`PCS-SUP-020`) if
`text` is outside the `variants` list; PageObject import remains runtime-safe,
while explicit `by_variant(...)`/`get_option(...)` selection stays strict. During
the refactor pass, same effective-role fields in the same owner scope with
substring-overlapping `accessible_name` values such as `Password` / `New password` get
`exact=True`; equal role/name matches require a narrower owner/locator or
`index` because `exact=True` cannot distinguish them. Custom element subclasses
inherit role from a proven Persona base class (`TextField`, `Button`, ...); when
the role is not proven, locator-quality does not emit an overlap warning. Plain
visible messages are modeled as `Text(text=..., exact=True)`, while
`Status(accessible_name=...)` is reserved for a confirmed ARIA status node.
`Region`/`Main`/`Dialog` boundaries use a real DOM/ARIA owner, locator,
`test_id`, or structural semantics; heading text remains a child element by itself.

RU: Web expectations проверяют составные UI-контролы через их семантические
targets. `ToBeEnabled`/`ToBeDisabled` читают состояние native и Ant/UiKit
`Select`, а `ToBeClickable` проверяет enabled, visibility, stability и получение
pointer events без выполнения клика. Видимое отключённое поле проверяется
композицией `BeVisible(), ToBeDisabled()`, визуально отсутствующее поле —
`WaitForElementState(element, "hidden")`; удалённый DOM-узел имеет состояние
`detached`.

EN: Web expectations evaluate composite controls through semantic targets.
`ToBeEnabled`/`ToBeDisabled` read native and Ant/UiKit `Select` state, while
`ToBeClickable` verifies enabled state, visibility, stability, and pointer-event
reception without clicking. Use `BeVisible(), ToBeDisabled()` for a visible
disabled field, `WaitForElementState(element, "hidden")` for a visually absent
field, and `detached` for a removed DOM node.

### Автоматическая готовность UI-действий

UI-действия работают с ленивыми Playwright locator и сами ожидают готовность
целевого элемента. Structural `Table`, `TableRow`, `TableCell`,
`ColumnHeader`, indexed `Select`, `relative_to(...)` и текущий browser ref
сначала ожидают адресуемый DOM-узел, затем действие использует стандартную
Playwright actionability. `PressKey` ожидает видимый target, `UploadFile` —
прикреплённый file input. Selection и expansion таблицы ожидают соответствующий
checkbox или expander.

`timeout` действия применяется и к structural/semantic resolve, и к самому
Playwright-действию. Ошибка resolve содержит тип и описание target, structural
координаты и ожидаемый UI-контракт. Отдельный `WaitFor*` перед тем же действием
не нужен только ради появления его target.

Явное ожидание остаётся частью сценария, когда требуется самостоятельный
наблюдаемый сигнал: navigation/load state, popup или новая вкладка, network
response, завершение загрузки доменных данных, изменение атрибута либо другое
состояние, которое нельзя вывести из actionability целевого элемента.
`force=True` сохраняет Playwright-семантику и осознанно обходит часть проверок
actionability, но не structural/semantic resolve.

## Конфигурация окружения / Environment Configuration

Persona автоматически определяет ОС процесса: `win32` использует
`platforms.windows`, `linux` — `platforms.linux`, `darwin` —
`platforms.macos`. Параметр платформы в CLI и переменная
`PERSONA_PLATFORM` отсутствуют. Если блок текущей ОС не объявлен, действует
базовый YAML.

Persona detects the process OS automatically: `win32` uses
`platforms.windows`, `linux` uses `platforms.linux`, and `darwin` uses
`platforms.macos`. There is no platform CLI option or
`PERSONA_PLATFORM`. An omitted current-platform block keeps the base YAML.

```yaml
persona:
  base-url: "https://example.test"

skills:
  browser:
    default:
      base-url: "${persona.base-url}"
      type: "chromium"

platforms:
  windows:
    skills:
      browser:
        default:
          channel: "msedge"
  linux:
    skills:
      browser:
        default:
          type: "chromium"
  macos:
    skills:
      browser:
        default:
          channel: "chrome"
```

Окружение `config/{env}.yaml` выбирается в порядке: явный `--env`,
process `PERSONA_ENV`, `PERSONA_ENV` из общего `.env`,
`[tool.persona].default_env`. Значения свойств имеют приоритет:
process environment → `.env.{env}` → общий `.env` → platform overlay →
базовый YAML. Dotenv-файлы читаются отдельно для каждого `Config` и не
изменяют глобальный `os.environ`.

The environment selector order is explicit `--env`, process
`PERSONA_ENV`, common `.env` `PERSONA_ENV`, then
`[tool.persona].default_env`. Property precedence is process environment,
`.env.{env}`, common `.env`, the platform overlay, then base YAML. Dotenv
files are scoped to each `Config` and never mutate global `os.environ`.

Имена свойств используют relaxed binding: точки, дефисы и подчёркивания
преобразуются в одно подчёркивание, повторяющиеся разделители сворачиваются,
регистр переводится в верхний. Поэтому `persona.base-url`,
`persona.base_url` и `persona.base.url` соответствуют
`PERSONA_BASE_URL`; `skills.browser.default.base-url` соответствует
`SKILLS_BROWSER_DEFAULT_BASE_URL`. Env override применяется только к
существующему canonical property. Коллизия canonical имён является ошибкой.

Property names use relaxed binding: dots, hyphens, and underscores become one
underscore, repeated separators collapse, and letters become uppercase.
Therefore `persona.base-url`, `persona.base_url`, and
`persona.base.url` map to `PERSONA_BASE_URL`, while
`skills.browser.default.base-url` maps to
`SKILLS_BROWSER_DEFAULT_BASE_URL`. Environment overrides apply only to an
existing canonical property; canonical collisions are errors.

```dotenv
PERSONA_ENV=dev
PERSONA_BASE_URL=https://stage.example.test
SKILLS_BROWSER_DEFAULT_BASE_URL=https://browser.example.test
SKILLS_BROWSER_DEFAULT_HEADLESS=false
```

Корневые группы `persona`, `service`, `project` и другие custom property
groups допустимы, если хотя бы одно их свойство используется через
`${...}`. Точная ссылка сохраняет тип; ссылка внутри строки принимает scalar.
Env scalars читаются как YAML, списки — только как JSON arrays. Ссылки, циклы,
типы, дубликаты и коллизии диагностируются с файлом, строкой, столбцом и
property path. YAML-наследование `extends` не используется.

Custom root groups such as `persona`, `service`, and `project` are valid
when at least one property is consumed through `${...}`. An exact reference
preserves its type, while string interpolation accepts scalars. Environment
scalars use YAML parsing and lists require JSON arrays. Reference, cycle, type,
duplicate, and collision errors include file, line, column, and property path.
There is no YAML `extends` inheritance.

`config/{env}.yaml` поддерживает batch generation documents:

- `generator.api.documents[]`
- `generator.json.documents[]`
- `generator.xsd.documents[]`
- `generator.soap.documents[]`

XSD document задаёт корневую схему, output routing и необязательные XML
prefixes:

```yaml
generator:
  xsd:
    output_root: "support/contracts"
    documents:
      - name: "arrow_guides"
        schema: "scenario_data/schemas/ArrowGuides.xsd"
        package: "support.contracts.arrow_guides"
        namespace_prefixes:
          ns2: "urn:example:arrow-guides"
```

Генерация выполняется командой
`persona-schema-gen xsd --xsd-document arrow_guides --project-root .`.
`namespace_prefixes` управляет только написанием XML prefix. Namespace
элементов определяется XSD: импортированная схема без `targetNamespace`
остаётся no-namespace, а chameleon `xs:include` наследует namespace
включающей схемы. Generated codec проверяет XML через локальный XSD bundle и
явно отклоняет schema и payload с `DOCTYPE`.

MCP authoring surface использует единый Persona runtime:

- MCP `tools/call` возвращает в `content[0].text` самодостаточный предметный результат: outcome, текущее состояние, diagnostics, точные следующие действия, recovery и переход. `structuredContent` сериализует тот же контракт для машинной обработки. Большие snapshot/history artifacts доступны по возвращённым путям через обычные файловые инструменты и MCP detail routes.
- `persona_knowledge` читает compact guide, guide_search, framework catalog и config catalog; `include_examples=true` добавляет package-owned examples выбранной темы или entity. Guide details/examples, search results, constructor properties, public methods и config fields всегда возвращаются server-owned страницами; продолжение выполняется только через returned `next_page`/`next_pages` и точные `topic_index_offset`, `details_offset`, `examples_offset`, `constructor_offset`, `methods_offset`, `fields_offset` или search `offset`. `framework_search.invocation_modes` выбирает symbols, которые поддерживают хотя бы один указанный live command mode; для web Ops используется точный `query`, затем returned entity route.
- `persona_knowledge(action="agent_problem_resolution")` возвращает единый рабочий цикл основного агента, primary roles и subagents: перечитать активный packet, получить project profile, пройти от memory record к source refs и code repositories, выбрать релевантные retrieval/project/Knowledge/domain/runtime источники, задать один вопрос пользователю и сохранить ответ. Task-local open question не является gap; evidence-backed gap или отдельная внешняя задача появляются только в финальной агрегации нерешённого остатка.
- `persona_project` читает inventory/search/entity/lifecycle/retry данные текущего project-root и обновляет discovery cache при изменении файлов. `inventory(include_ids=true, limit=N, filters={...})` возвращает bounded key id previews, totals и omitted counts; `filters` сужают key ids и facet_counts previews без изменения full summary. `domain_support_map` в inventory является compact overview с layers/status counts/totals/omitted and paged domains; `domain_support_domains_limit`/`domain_support_domains_offset` управляют страницей доменов и возвращают `domains_total`/`domains_omitted`/`domains_offset`; `include_domain_support_details=true` и `domain_support_limit=N` добавляют bounded details от 0 до 10 для small review. `diagnostics_limit=N` управляет inventory diagnostics preview от 0 до 25 и возвращает `diagnostics_total`/`diagnostics_omitted`; `search(filters={support_domains, support_layers, support_statuses}, limit, offset)` используется для paged domain/layer/status drill-down; `search(include_ids=true)` добавляет returned `match_entity_ids`; `search/entity(include_related=true)` возвращают bounded related preview с `related_total`/`related_omitted`; `search/entity` используются для точных сущностей и scoped diagnostics. Page `entity` возвращает `page_ref` и `page_element_ref` на nested elements для typed operation args. Scenario `entity` возвращает bounded executable outline с imports, persona call sequence и referenced page/component ids; generated API operation `entity` возвращает `constructor_signature`, `constructor_parameters`, `response_contract` и `usage_examples`. Diagnostics с `context=support_quality` и `severity=warning` подсвечивают PageObject/support ids, требующие refactor: localized/transliterated Page class names, Step/CombinedStep/Goal and reusable page-component class names, transliteration, numeric/generic/value-derived/overlong fields, missing Page/Element description for generated/refactor-needed support, same-role/same-scope `accessible_name` overlap без `exact=True`, equal role/name matches без scope/index, plain-message `Status(accessible_name=...)` без DOM anchor и label-only owner boundaries. `suggested_identifier_kind` может быть `page_class`, `python_field` или `component_class`; suggestion является review candidate, а не автоматическим rename. EN: scenario and API operation entities expose authoring-critical structure without opening project source files first; support_quality diagnostics mark generated support that must be refactored before production authoring.
- `persona_retrieval` объединяет package core knowledge и discovery corpus текущего проекта. `mode=auto|hybrid` может использовать lexical ranking; structured и compact ответы явно сообщают `effective_mode` и `semantic_provider` с CSS configured/used/status и фактическими model/dimension из query embedding response. `mode=semantic` возвращает только semantic-ranked entries либо `effective_mode=semantic_unavailable` с пустым `entries`, bounded diagnostics и `next_action`. Project index обновляется через `action=refresh, scope=project`, а `action=wait, scope=project, wait_timeout_seconds=1..20` возвращает текущую фазу и числовой прогресс job без остановки фоновой сборки по истечении bounded wait. Core index является package data и обновляется официальной knowledge/package сборкой. `.persona/**` не входит в project discovery corpus, поэтому local runtime и retrieval artifacts не делают собственный index stale. EN: retrieval responses explicitly expose the effective mode and actual CSS provider metadata, semantic mode never substitutes lexical entries, project rebuild jobs expose bounded waits and progress, and the core index is immutable package data.
- `persona_project_memory` управляет памятью проекта: `action=status|ingest|remember|recall|context|assess|retire|delete`, typed records с `memory_type=semantic|episodic|procedural`, evidence refs, validity window, supersede/conflict links, context packs, source/evidence packs and test sufficiency assessment. Agent-facing `recall`/`context` по умолчанию возвращают только active records со статусами `accepted|open`; `proposed|rejected|superseded|resolved` доступны только при явном `include_inactive=true` для аудита. Ошибочные или устаревшие записи переводятся через `action=retire` в `rejected|superseded|resolved` с обязательным `reason`; `action=delete` является hard-delete для явно выбранных `record_ids` и также требует `reason`. `[tool.persona.project_memory].provider = "local"` использует reviewed audit mirror `.persona/project_memory/memory.jsonl`; `provider = "cognee"` подключает optional Cognee library backend под `.persona/project_memory/cognee` без отдельного MCP server. Источники Jira/Confluence/repo/Persona inventory/run/session ingest-ятся как source evidence с content hash и parent/child refs, а не становятся памятью сами по себе. Context/source/evidence packs доступны через MCP resources `persona-memory://...`, включая `persona-memory://sources` and `persona-memory://context/<context_pack_id>`. Human-facing views генерируются вне MCP через `persona memory view`, `persona memory render` или Make targets; HTML graph viewer строит memory/kind/topic/source anchors из records, tags and source audit tree. EN: `persona_project_memory` stores verified active project memory records, keeps inactive audit records out of default agent context, and returns task-specific context packs instead of making the agent reinterpret raw sources on every task.
- `persona_generate_support(page|element)` принимает `payload.description`: для
  page записывает Page `description`, для placed element записывает Element
  `description` выбранного field, не меняя English executable names. MCP
  page/element payload принимает `snapshot_path`, `page_path`,
  `wait_for_state` и `wait_timeout`; `snapshot_path` читает persisted
  RichAriaSnapshot artifact внутри project-root через
  `RichAriaSnapshot.from_yaml_file`, а не через raw `yaml.safe_load`.
  Python-only поля `wait_for_element`, `wait_for_element_state` и
  `wait_for_element_timeout` относятся к `GeneratePageObject`, а не к
  `persona_generate_support`.
- `persona_session_open` по умолчанию входит в единственную подходящую live session той же `task_id`; `new_session=true` создаёт отдельную session для subagent, а `lifecycle=true` выполняет runner lifecycle setup для указанного `scenario_id`. `persona_session_list(task_id=...)` возвращает compact summaries active/suspended/completed sessions, последний snapshot и точный `continuation` с `persona_session_state` либо `persona_session_resume`.
- `persona_session_state` возвращает сохранённый `latest_observation` и свежее состояние session: URL, title, load state, page errors, active element, visible summary, bounded ARIA structure summary, loaded skills/resources, lifecycle state, history summaries, draft, artifact summaries, current-state diagnostics и project fingerprint. `history_tail` для generation-команд содержит bounded `result_summary` с `draft_identifiers`, `generated_elements`, `generated_collections`, `refactor_targets` previews and counts, чтобы агент мог продолжить semantic refactor в той же session без повторной генерации. `browser.aria_snapshot_summary.name_field_meaning="ARIA accessible name"` отделяет поле `name` ARIA/YAML snapshot от PageObject-контракта, где public Page/Element `name` отсутствует; `full_snapshot_operation` подсказывает штатный typed `persona_get` payload для расширенного snapshot.
- Persona MCP session владеет одним current browser state. Публичный ref имеет короткий вид `eN` и используется через `persona_browser(session_id=..., action=..., ref=...)`; `snapshot_id` и `occurrence` не входят в публичный browser action. `find` ищет по тексту, properties, semantic path и необязательной role, а `inspect` читает точный узел и окружение из сохранённого semantic index без нового browser capture. `persona_session_resume` восстанавливает checkpoint; если live state требует повторного наблюдения, Persona снимает current snapshot и выдаёт его `eN` refs. Generated Page/Element, Ant/UiKit Select, Table и Repeated разрешаются по стабильному semantic/structural контракту без ref.
- `persona_make`, `persona_get`, `persona_check` принимают typed `operation` payload из `persona_knowledge`, `persona_project`, generation manifest или support plan и пишут каждый вызов в history. После любой команды, включая произвольные вложенные Web Ops, Step и CombinedStep, ответ содержит revisioned `observation`: bounded `result_summary`, ordered `operations_preview`, artifact refs и при наличии browser page единый `browser.current` с URL/title/load state, snapshot path/revision и actionable refs; `browser.changed_area` содержит текущую область после navigation/tab change либо bounded diff после локального изменения. Отдельный `persona_browser(action="snapshot")` не требуется после каждой команды; полный ref index доступен по пути сохранённого snapshot. Tool schema содержит копируемые `persona_value` examples; например, текущий Python `datetime` передаётся как `{"persona_value":"datetime","preset":"now"}`, а точный — как `{"persona_value":"datetime","iso":"2026-07-17T00:00:00"}`. `persona_value` является строковым discriminator, остальные поля находятся рядом с ним. Project `entity` и ошибочная operation возвращают `accepted_encodings`, ожидаемый Python type и точный `input_path`; postponed annotations разрешаются до materialization. Одна authoring-команда сохраняет единую import identity project Step, generated XSD dataclass, Enum и SchemaBuilder от создания operation до завершения Step, поэтому live MCP и native runner используют один schema contract. Operation `args`/`kwargs` могут ссылаться на PageObject или вложенный element через `{"persona_ref":"page","entity_id":"page::..."}` и `{"persona_ref":"page_element","entity_id":"page::...","path":"form.submit"}`; runtime импортирует страницу внутри, агент не пишет `from support.pages...`. Поле `code` остаётся debug/escape hatch для безопасных Persona/Python expressions, которые ещё не представлены typed operation route. EN: every authoring command returns the current browser projection and bounded changed area independently of the nested operation type.
- `persona_generate_support` пишет `page`, `element`, `api`, `xsd`, `json` и `wsdl` support artifacts в проект, обновляет import caches/discovery и возвращает bounded manifest с `written_paths`, preview/count/omitted полями для `generated_pages`, `generated_elements`, `generated_collections`, `draft_identifiers`, `generated_api_operations`, `generated_models`, `generated_builders`, `project_entities`, а также final-file `imports`, artifact summaries и `diagnostics`; file content читается из записанных project files. `generated_elements` содержит `page_element_ref`, `description_source`, `description_refactor_required`, `identifier_quality` и `owner_boundary_evidence`, чтобы semantic refactor и immediate reuse выполнялись по MCP data без чтения generated source. `draft_identifiers` перечисляет generated generic/value-derived/numeric/transliterated Python fields с `access_path`, `example_ref`, path/support classification и review suggestions. `generated_collections` описывает reusable repeated/list/grid regions через `access_path`, `example_ref`, `example_item_ref`, `page_element_ref`, `prototype_type`, `field_count` и field refs вроде `page.products[0].price`.
- Успешная generation с diagnostic `severity=info`, например `page_component_reused`, возвращает `status=ok`; `partial` означает записанные артефакты с warning/error diagnostics, skipped artifacts или failed sub-operations. EN: successful generation with informational diagnostics is not a partial failure.
- Command diagnostics относятся к текущему tool call. `persona_session_state.diagnostics` показывает active lifecycle/refresh/latest failed-command diagnostics and does not repeat passed generation diagnostics.
- `persona_test_author` редактирует draft поверх history: `state`, `include`, `remove`, `reorder`, `edit`, `render`, `write_file`, `reset`; для semantic support refactor дополнительно принимает `plan_support_file` с выбранными `history_ids`, `support_kind` и `target_path`, возвращает advisory `support_plan` с ролями history, candidates, target classification, `source_operations`, `final_file_imports`, `reuse_operation` и `live_reuse`, затем `write_support_file` с `support_kind`, canonical `support/{actions,steps,combined_steps,goals,facts,expectations}/**/*.py` `target_path` и full module `code`, затем возвращает `written_paths`, final-file `imports`, `project_entities` и `operations`. Для `check` history setup statements остаются setup-кодом, а только финальное expression рендерится как `persona.check(...)`. EN: for `check` history, setup statements stay as setup code and only the final expression is rendered as `persona.check(...)`.
- `persona_test_run` всегда создаёт durable launch и сохраняет UUID `run_id` до запуска процесса. Начальный `action="run"` ждёт результат в пределах `test_run_inline_wait_ms`: быстрый тест возвращает terminal status, item counts и `report_refs` тем же MCP-вызовом; продолжающийся тест возвращает `status="running"`, `run_id`, `launch_id` и точный `continuation` для `action="wait"`. `action="status"` читает сохранённое состояние без ожидания, `action="wait"` продолжает bounded ожидание, `action="cancel"` записывает cancel request. После `running` агент продолжает тот же `run_id` и не запускает тест повторно.
- `persona_session_close(status=passed|failed|partial)` сохраняет browser checkpoint, history, receipts, snapshots и suspension record, переводит task-owned session в `suspended` и оставляет её доступной для `persona_session_resume` с тем же ID. `persona_session_finalize(session_id=...|task_id=..., status=...)` вызывается только при завершении task, выполняет lifecycle close/failure hooks, освобождает runtime resources и сохраняет durable evidence в registry. EN: close suspends a durable session; finalize completes one session or every session owned by the task.
- `persona_make`, `persona_get`, `persona_check` и `persona_browser` поддерживают необязательные `command_id` и `idempotency_key`. Append-only task/session journal фиксирует started/committed events, request digest, history, receipt и artifact hashes; повтор committed payload возвращает прежний результат без повторного действия, а конфликт или незавершённая запись завершается `PERSONA_SESSION_RECOVERY_FAILED`. Browser resume загружает storage state в новый Playwright context, открывает реальный URL и снимает свежий semantic snapshot; сохранённый HTML не исполняется и не подставляется в страницу. EN: durable command identities provide at-most-once retry, while browser recovery restores native context state without DOM replay.

Generated API operations support request-level API profile binding:
`GetHealth().with_api_profile("bgt").as_response()` uses
`skills.api.bgt`, while raw `JsonExchange(..., api_profile="bgt")` uses the same
profile selector. `persona.skill("api.bgt")` is accepted as shorthand for
`persona.skill("api", "bgt")`.

Element generation принимает live `target` и explicit `placement`: `action`
`add|replace|extract`, `scope` `page|section|dialog|table|component`,
`field_name`/`field_path` и owner/class context. Package-owned knowledge
surface для генераторов проверяется командами:

```bash
make knowledge-generate
make knowledge-check
```

## Миграция Page DSL name / Page DSL name migration

RU: `persona migrate page-names` сканирует Page DSL-файлы проекта и формирует
перенос literal `name=...` в `description`, `accessible_name`, `text` или
`locator` по явным правилам. Команда по умолчанию работает в dry-run режиме;
запись изменений включается через `--apply`. `--path`, `--snapshot-root` и
`--report` задают область сканирования, RichAriaSnapshot confidence gate и путь
отчёта. Мигратор распознаёт direct Page DSL classes and local component
subclasses, declared in the same page file. Неперенесённые случаи остаются в
исходном файле и попадают в report warnings с координатами.
Для `Text`/`Paragraph` с уже заданным `variants` literal `name=...` переносится
только в `description`, чтобы не создавать конфликт `text` и `variants`.

EN: `persona migrate page-names` scans project Page DSL files and prepares a
literal `name=...` migration to `description`, `accessible_name`, `text`, or
`locator` through explicit rules. The command defaults to dry-run mode; `--apply`
enables writes. `--path`, `--snapshot-root` and `--report` configure the scan
scope, RichAriaSnapshot confidence gate and report path. The migrator recognizes
direct Page DSL classes and local component subclasses declared in the same page
file. Unmigrated cases stay in source files and are listed in report warnings
with coordinates.
For `Text`/`Paragraph` elements that already declare `variants`, literal
`name=...` migrates only to `description` so the migration does not create a
`text`/`variants` conflict.

```bash
persona migrate page-names --project-root . --report reports/page-name-migration-report.txt
persona migrate page-names --project-root . --snapshot-root artifacts/snapshots --apply
```

RU: starter `.venv/bin/persona migrate page-names --project-root .` является thin wrapper над этой Persona CLI
командой. EN: starter `.venv/bin/persona migrate page-names --project-root .` is a thin wrapper over this
Persona CLI command.

## Миграция snapshot refs / Snapshot ref migration

RU: `persona migrate page-refs` сканирует production Page DSL и удаляет
snapshot-scoped `aria_ref`, `header_ref` и `Strategy.REF`. Элементы со
стабильной semantic/structural strategy переписываются напрямую. Ref-only
элемент материализуется из scoped project-owned snapshots: объявленный
`static_aria_snapshot_path` имеет приоритет, затем проверяются локальные
`support/pages/aria/<page>_*.yaml`, а project-wide snapshots используются как
последняя область для вынесенного holder-файла. Persona проверяет SHA-256,
выбирает element subtree либо полный table subtree для `header_ref`,
последовательно запускает штатный generator/merge и атомарно пишет ref-free
Page и связанные Python component updates. Входные snapshots остаются
неизменяемым evidence:
миграция не сериализует выбранный subtree в новый YAML и не меняет объявленный
`static_aria_snapshot_path`. Одинаковая semantic identity в нескольких
состояниях дедуплицируется; разные identities одного legacy ref дают
диагностируемую `snapshot_identity_ambiguous` только внутри выбранной
Page-области. Совпавший snapshot-scoped ref другой страницы не конкурирует с
объявленным snapshot. Объявленные и локальные snapshots страницы валидируются
всегда; project-wide fallback читается только для оставшихся legacy refs, а
нерелевантный повреждённый snapshot другой страницы не блокирует миграцию.

Команда не требует Persona MCP session, task ID или session ID. При отсутствии
generated state snapshots apply сохраняет все доказанно безопасные rewrite,
materialization и удаления `no_known_usages`, а unresolved used/unknown поля
оставляет с точными coordinates, usages и typed remediation action. После
сохранения недостающего состояния Page повторяется та же команда. Generated и
мигрированные Generic, Ant/UiKit, Table и Repeated элементы используют stable
semantics и specialized structural resolvers; runtime ref в готовый Page или
тест не переносится. Для безымянных Ant Select мигратор восстанавливает
локальный `index` внутри доказанного owner-контейнера и сохраняет объявленные
enum/options. Generator result без доказуемой semantic/structural strategy не
считается materialization и не заменяет используемое Page field.
Project reference graph строится один раз на invocation, различает прямое
использование, `by_variant(...)` и composition holder через дочерний путь.
Ref поля, которое во всех usages выбирается через `by_variant(...)`, удаляется
только когда вариант сам задаёт полную semantic identity; прямой resolve того
же `Text(variants=...)` остаётся unresolved. Один apply удаляет все доказанно
неиспользуемые поля, включая поля вложенных generated-классов, и записывает
проект одной атомарной транзакцией. Удаление другого unused-поля не стирает
reason, snapshot paths и usage evidence оставшегося unresolved элемента.
Одинаковые имена вложенных классов различаются по exact legacy ref; при
реальной неоднозначности ошибка перечисляет файл, source coordinates и все
candidate assignments.
CLI по умолчанию немедленно печатает line-oriented progress в stderr:
discovery Page-файлов, текущий Page `N/total`, чтение snapshot, parse/usages
каждого Python-файла при построении reference index, атомарную запись и
финальные счётчики. Jenkins и перенаправленный shell log показывают активную
фазу без смешивания с итоговым Markdown в stdout. `--progress never` отключает
только progress и сохраняет отчёт и exit code. Каждый project snapshot
проверяется и парсится один раз на invocation, затем его typed artifact
переиспользуется для остальных Page. Согласованные отступы 2 или 4 пробела в
persisted Rich ARIA snapshots сохраняют одну и ту же иерархию; конфликтующий
отступ прямых потомков остаётся ошибкой структуры. Беззначные boolean
properties `checked`, `disabled`, `expanded`, `hidden`, `pressed` и `selected`
записываются как `- /<property>`; остальные properties требуют форму
`- /<property>: "<value>"`.
Итоговый Markdown группирует снятые refs, восстановленные элементы, удалённые
unused-поля и unresolved-действия по project-relative файлам. Восстановленное
поле ссылается на конкретный snapshot evidence path; абсолютные пути и
дублирующие технические action-строки в отчёт не выводятся.

EN: `persona migrate page-refs` replaces production snapshot refs using a
Page-scoped priority: declared `static_aria_snapshot_path`, local Page state
snapshots, then project-wide fallback for a separate holder module. It verifies
every artifact, resolves refs across separate Page/Elements modules, and
regenerates exact element or table subtrees through the normal structural merge
without an MCP session. A ref reused by another Page cannot make the declared
snapshot ambiguous. Input snapshots remain immutable evidence: migration never
persists the selected subtree as a new YAML artifact or replaces the Page's
declared `static_aria_snapshot_path`. Declared and local Page snapshots are
always validated; project-wide fallback is loaded only for unresolved legacy
refs, so an unrelated malformed snapshot from another Page cannot block the
migration.
Unnamed Ant Select controls receive an owner-local `index` when the verified
snapshot proves their container, while authored enum/options remain intact.
An unresolved generator result is retained as a typed remediation action and
never replaces a used Page field.
The project reference graph is built once per invocation and distinguishes
direct, `by_variant(...)`, and composition-path usage. Variant-bound fields are
rewritten only when the selected variant supplies a complete semantic identity;
direct resolution remains unresolved. A single apply removes every
proven-unused field, including fields in nested generated classes, and writes
the project through one atomic transaction. Existing reason, snapshot paths,
and usage evidence survive coordinate-changing removals. Exact legacy refs
disambiguate repeated nested class names; genuine ambiguity reports the file,
source coordinates, and every candidate assignment.
The CLI streams Page, snapshot, per-file reference-index, atomic-write and
summary progress to stderr by default, including in CI and redirected logs.
The final Markdown report remains isolated in stdout; `--progress never`
disables only progress output. Each project snapshot is parsed once per
invocation and its typed artifact is reused across Page files. Persisted Rich
ARIA snapshots may use a consistent two- or four-space child indentation;
conflicting direct-child indentation remains a structural error. Bare
`checked`, `disabled`, `expanded`, `hidden`, `pressed`, and `selected`
properties represent `true`; every other property requires
`- /<property>: "<value>"`.
The report groups rewrites, materializations, unused removals, and unresolved
actions under project-relative paths. Every materialized field links to its
snapshot evidence path; absolute paths and duplicate raw action lines are not
emitted.

```bash
persona migrate page-refs --project-root .
persona migrate page-refs --project-root . --apply
persona migrate page-refs --project-root . --apply --progress never
make migrate-page-refs APPLY=1
make migrate-page-refs
```

## Пользовательский проект

Официальный starter расположен в `template/`.

- `pyproject.toml`
- `config/<env>.yaml`
- `config/auth.yaml`
- `scenarios/**/test_*.py`
- `scenarios/**/__suite__.py`
- `scenario_data/`
- `support/`

Сценарий или ветка suite может быть отключена через `disabled(reason)` на
`test_*` или `suite(disabled=True, disabled_reason=...)` в `__suite__.py`.
Такие сценарии остаются в discovery/catalog с причиной, но не попадают в
обычный `persona run` / `make test`; контролируемый запуск требует
`--include-disabled`.

Команды starter:

```bash
cd template
make install
make checks
make redis
make redis action=status
```

## Создание и обновление Persona-проекта

RU: `persona project init` создаёт пустой Persona-проект из template,
упакованного внутрь установленного `persona-dsl` wheel/sdist. Команда по
умолчанию работает в текущей директории; `--project-root <path>` указывает
другой каталог. Пакетный template содержит только production skeleton:
`Makefile`, `pyproject.toml`, `.gitignore`, `persona.check.yaml`, минимальный
`config/`, пустые `scenarios/` и `support/`, а также managed Make targets в
`.persona/make/persona.mk`. Starter tests, starter data, учебные сценарии,
reports, caches, `.venv` и egg-info не входят в project bootstrap.

`persona project init` владеет только package-owned scaffold и lifecycle state;
команда не создаёт `.venv`. Package-owned runtime phase для интеграционных
клиентов запускается managed Python без Make и без standalone-скрипта:

```bash
<MANAGED_PYTHON> -m persona_dsl.cli project install-runtime \
  --project-root <PROJECT_ROOT> --json
```

Эквивалентная команда установленного managed executable: `persona project
install-runtime --project-root <PROJECT_ROOT> --json`. Она создаёт project
`.venv`, устанавливает build/runtime/dev requirements проекта без преждевременного
разрешения Persona requirement и закрепляет package-owned безопасный минимум
`pip` для локального dependency audit, последней закрепляет distribution и точную
версию Persona, которой принадлежит managed runtime, устанавливает editable
project metadata с `--no-deps`, затем проверяет project Python, Persona CLI, MCP
и LSP. Имя distribution и версия не принимаются от consumer, поэтому public и
corporate builds используют identity фактически запущенного package.

Install source также принадлежит producer. Retained local source или wheel из
`direct_url.json` преобразуется в проверенный PEP 508 direct reference и входит
в content fingerprint; путь не публикуется в machine payload. При отсутствии
доступного local artifact используется exact `distribution==version` через
настроенный pip registry. Изменение retained source меняет fingerprint и
запускает повторную установку той же development version.

В machine mode stderr содержит последовательные JSONL-события
`persona.project-runtime-install-progress`, а stdout содержит один итоговый
payload `persona.project-runtime-install`. Progress использует закрытые phase
`preflight`, `environment`, `dependencies`, `distribution`, `metadata`,
`verification`. Успех возвращает `installed` или `noop` и exit code `0`; ошибка
возвращает `error`, bounded diagnostic и exit code `1`; ошибка синтаксиса CLI
использует exit code `2`. Receipt содержит exact distribution/version,
проверенные executables, dependency fingerprint и путь к durable local state.
Повторный вызов из проверенного project runtime возвращает исходный receipt со
статусом `noop` без install phases. Вызов из managed runtime продолжает
сравнивать provider source fingerprint и выполняет установку при его изменении.

После runtime phase Qompanion получает preview от project `.venv`, сохраняет
`plan_digest` и применяет только этот plan:

```bash
<PROJECT_ROOT>/.venv/bin/persona project reconcile \
  --project-root <PROJECT_ROOT> \
  --persona-package-name <RECEIPT_DISTRIBUTION> \
  --persona-version-spec ==<RECEIPT_VERSION> \
  --dependency-mode add --json
<PROJECT_ROOT>/.venv/bin/persona project reconcile \
  --project-root <PROJECT_ROOT> \
  --persona-package-name <RECEIPT_DISTRIBUTION> \
  --persona-version-spec ==<RECEIPT_VERSION> \
  --dependency-mode add --approved-plan-digest <PLAN_DIGEST> --yes --json
```

`stale_plan` требует нового preview; consumer не применяет изменившийся plan по
старому digest. Direct CLI lifecycle остаётся доступен через `make install` и
standalone `install-persona.py`, но они не являются runtime API Qompanion.

RU: `persona project update` обновляет только Persona-owned scaffolding
существующего проекта с `pyproject.toml`. Для пустой директории используется
`persona project init`; update не создаёт проект и не добавляет отсутствующие
README, config, `scenarios/`, `support/`, `persona.check.yaml` или
`.gitattributes`. Update синхронизирует Persona sections в существующем
`pyproject.toml`, восстанавливает отсутствующий `.gitignore`, синхронизирует
один Persona-managed блок в существующем `.gitignore` и поддерживает managed
Make infrastructure `.persona/make/persona.mk` с include в корневом Makefile.
Пользовательские правила до и после bounded-блока `.gitignore` сохраняются;
повреждённые или неоднозначные границы блока возвращают conflict. Команда
показывает plan. В интерактивном терминале
каждый create/update пункт показывает line-numbered review: замены сгруппированы
в явные блоки `Было` и `Станет`, добавления и удаления имеют
отдельные названия, а неизменённые строки показаны как локальный контекст. `y`
применяет пункт, `n` пропускает пункт, `d` или пустой Enter повторяет review
этого пункта, `q`
выходит без применения оставшихся пунктов, `o`/`ours` оставляет текущий файл
проекта, `t`/`theirs` принимает Persona-managed вариант, `m` показывает
auto-merge candidate и спрашивает отдельное подтверждение. В non-interactive
среде plan использует тот же readable review и требует `--yes` для применения;
`--diff-only` печатает standard unified diff для внешних diff/patch tools и
никогда не пишет файлы. Для зарегистрированного packaged Makefile review
показывает подтверждённый registry-owned input, синхронизацию актуальных targets
в `.persona/make/persona.mk` и итоговый однострочный root include; include нельзя принять
без выбранного или уже актуального managed-файла. Изменённый или неизвестный
Makefile с Persona targets получает conflict вместо перезаписи. Review самого
package-owned `persona.mk` показывает назначение и old/new line count; полный
контент доступен через `--diff-only`. Update
сохраняет `[project].name`, `[project].version`, пользовательские Make targets,
бизнес-тесты и данные. Corporate package задаётся явно через
`--persona-package-name` и `--persona-version-spec`; Persona dependency не
угадывается и не переписывается молча. `persona project update` предоставляет
отдельный diff-first CLI, а `make update-project` является переходным алиасом
полного `make install`.

## Настройка agent-клиентов

`make install` является единым lifecycle Persona-проекта. Команда создаёт
`.venv`, устанавливает зависимости, применяет content-addressed project plan,
материализует package-owned agent surface, настраивает локальные интеграции и
завершает работу через `persona agent doctor`. `make init` и
`make update-project` являются алиасами того же пути. Установка package и
согласование изменившейся project surface завершаются в одном вызове без
последующей отдельной команды.

Project-local surface содержит полный version-matched набор Persona:

- `.qwen` или `.gigacode` с context, slash-командами, primary roles и subagents;
- package-owned skills и workflow templates;
- Persona-owned fragments client settings и lifecycle hooks;
- `.lsp.json` с portable Persona LSP;
- Persona MCP с точным project/worktree root для каждой сессии;
- `.persona/project_memory` и `.persona/workflows` как durable state проекта.

Ownership manifest `.persona/project/ownership.json` различает managed files,
bounded fragments и durable state. Reconcile обновляет только Persona-owned
данные и сохраняет пользовательские client settings. Устаревший client-local
Persona skill с доказанным происхождением из managed extension source или
завершённого migration receipt архивируется byte-identically в
`.persona/local/recovery/legacy-agent-surface/<client>/<sha256>/` и заменяется
актуальным package-owned skill в той же транзакции. Файл без доказанного
Persona provenance остаётся конфликтом и не перезаписывается. Повторный
`make install` для актуального проекта завершается без повторной
материализации и без registry-операций.

Выбор встроенного agent profile задаётся при сборке Persona. Дефолт объявлен в
`pyproject.toml`:

```toml
[tool.persona.agent_distribution]
default = "gigacode"
```

CI/release сборка может выпустить другой distribution без изменения
template-проекта:

```bash
PERSONA_AGENT_DISTRIBUTION=qwen make build
PERSONA_AGENT_DISTRIBUTION=qwen make package-smoke
```

```toml
default_profile = "gigacode"
enabled_profiles = ["gigacode"]

[[profiles]]
id = "gigacode"
adapter = "gigacode"
display_name = "GigaCode Persona Agent"
scope = "project"
mcp_servers = ["persona"]
workflows = [
    "project-profile",
    "project-map",
    "test-plan",
    "test-sufficiency",
    "author-test",
    "test-run",
    "test-integration",
    "acceptance",
    "run-analysis",
    "memory-refresh",
    "quality-refactor",
]
client_command = "gigacode"
```

Starter user flow:

```bash
make install
make agent
```

`make install` выполняет interactive setup для локального checkout, сохраняет
typed integration settings вне Git и рендерит surface выбранного клиента.
Каждый запуск агента проверяет current lifecycle, binding, client runtime,
project-local surface, Persona MCP и LSP. После обновления Persona повторный
`make install` применяет только изменившийся package-owned контракт.

`make agent` launches the selected client with the current project `.venv`
first in `PATH` and with `VIRTUAL_ENV` set. The agent can call `persona`,
`python` and rendered skill scripts without activating the environment or
probing executables. `make agent-continue`, `make agent-resume` and every
stage shortcut use the same runtime contract. Slash commands provide the exact
initial Persona CLI invocation with the user's natural-language scope. Each
successful response returns `task_id`, packet/state paths and one named next
action; continuation uses the printed high-level route without shell JSON.

Parallel tasks use isolated Git worktrees and isolated Python environments:

```bash
make agent worktree=test-login
make agent-worktree-list
make agent worktree=test-login
make agent worktree=api-refactor worktree_root=../task-worktrees
make agent-worktree-close worktree=test-login
make agent-worktree-close worktree=test-login force=true
make agent-worktree-close worktree=test-login delete_branch=true
```

The first command creates branch `persona/test-login` from the current `HEAD`,
then materializes the base checkout staged, unstaged and non-ignored untracked
files before dependency installation. Git staging is preserved. The snapshot
base, digest and paths are stored under ignored `.persona/local`; a reused
worktree rejects a different dirty source snapshot instead of overwriting task
work. Each worktree owns its `.venv`, and `make install` runs when its dependency
fingerprint changes. The worktree `.venv/bin/persona` renders its own
project-local surface and proves the high-level route contract before the model
client starts.
Project-local settings and runtime files are synchronized separately:
`.persona/local/agent-bootstrap.local.json`, `config/auth.yaml`, `.env`,
`.env.*` and `config/auth.*.yaml`; `*.example*` files are excluded.
`make agent-worktree-list` prints registered Persona worktrees with path, branch
and clean status. `make agent-worktree-close worktree=<name>` runs
`git worktree remove` for a clean Persona-owned worktree and then
`git worktree prune`. Dirty worktrees stop with a warning; `force=true` removes
the working directory through `git worktree remove --force`. `delete_branch=true`
also removes the local `persona/<name>` branch; with `force=true` it uses
`git branch -D`.

Client runtime lifecycle is distribution-owned. A required Qwen profile writes
private settings to
`.persona/local/client-runtime/<profile-id>/settings.json` and passes the file
through `QWEN_CODE_SYSTEM_SETTINGS_PATH`; Persona does not rewrite user or
project Qwen settings. Qwen `SessionStart`, `PreCompact`, `Stop`, `StopFailure`,
`SessionEnd` and scoped tool-failure hooks bind the exact client session and
attach the current task, flow, route, packet, finding, knowledge lookup and
continuation. Hook diagnostics are context and do not block model, tool,
command or delegation execution. `persona agent client-status`
renders the read-only task/stage/gap/context status line. Managed Qwen automatic,
team and dream memory are disabled because Persona project memory owns durable
project knowledge.

The tracked `.qwenignore` excludes local runtime/cache/view paths from Qwen file
discovery without hiding durable workflow packets, reviewed memory or project
sources. `persona agent doctor --project-root .` validates lifecycle state,
client version, binding, project-local inventory, settings ownership, MCP, LSP
and context policy.

Direct CLI flow:

```bash
persona agent init --project-root .
persona agent launch --project-root . --mode new
persona agent launch --project-root . --mode continue
persona agent launch --project-root . --mode resume --session-id <session-id>
```

`make agent` delegates to the same typed launcher and starts the selected client
without choosing a workflow. A raw `qwen` or `gigacode` command remains an
ordinary non-Persona session. The user selects a concrete route with
`/persona.<route>` inside the Persona session.
Long-running role entrypoints keep separate commands because they select a
package-owned primary prompt rather than a route.

Long-running test work can start with a primary role without choosing a route,
task or packet:

```bash
make agent-test-analysis
make agent-test-author
```

`agent-test-analysis` starts the current client as the test-analysis owner and
waits for `/persona.test-sufficiency ...`. `agent-test-author` starts it as the
test-authoring owner and waits for `/persona.author-test ...`. The slash command
binds the concrete route and task inside the open session; the current agent
executes that route directly. Files under `agents/persona-*.md` define bounded
child-agent assignments.

Qwen и GigaCode получают одинаковый capability catalog: route-команды, четыре
bundled skill, route reference, system prompt, workflow packet templates,
primary roles и subagent assignments. Renderer размещает эти файлы в namespace
конкретного проекта, а ownership manifest фиксирует digest каждого managed
artifact. Project path, credentials и runtime settings не встраиваются в
package-owned тексты.

`persona agent init` проверяет current project lifecycle, настраивает private
client runtime и binding, проверяет MCP/LSP и выполняет resumable migration
старых project-specific registry entries. `persona agent legacy-scan` доступен
только для аудита legacy installations. Registry entry удаляется после успешной
проверки project-local surface и launch; старый generated source удаляется при
полном совпадении ownership hashes. Доказанный managed source с
client-generated cache сохраняется в content-addressed private recovery
`.persona/local/recovery/legacy-agent-extensions/` и удаляется из
`.persona/agent_extensions`; изменённый или не подтверждённый source остаётся
на месте с явным conflict.

Typed launcher читает system prompt из `.<client>/persona`, проверяет обычные
project MCP definitions и запускает vendor CLI без shell, extension flags и
временного MCP config. Slash-команда выбирает workflow внутри открытой сессии.
Route использует durable Markdown packet, project-memory receipts и обязательные
views; stdout агента не является workflow evidence.
Локальные интеграции настраиваются во время интерактивного `make install` или
точными recovery-командами `persona agent init --project-root .
--configure-atlassian`, `--configure-css` и `--configure-zephyr`. Значения
Atlassian, CSS и Zephyr хранятся в git-ignored
`.persona/local/agent-bootstrap.local.json`; process environment проецируется в
git-ignored `.env` с сохранением пользовательских переменных.

Atlassian runtime находится в
`.persona/local/atlassian-mcp/.venv`, содержит закреплённые версии
`mcp-atlassian`, `redis` и `Authlib` и запускается с `READ_ONLY_MODE=false` и
`TOOLSETS=all`. Zephyr использует `persona-zephyr-mcp` из project `.venv`,
project-local журнал `.persona/mcp/zephyr-mcp.log` и `trust=false`. Credentials
не попадают в tracked client settings, workflow packets или MCP definition.

`make install` добавляет Persona и настроенные Atlassian/Zephyr integrations в
обычный project-local `.qwen/settings.json` или `.gigacode/settings.json`.
Definitions содержат переносимые команды, cwd, timeout и trust без credentials.
Приватные значения хранятся в `.persona/local/agent-bootstrap.local.json`,
проецируются в `.env` и передаются дочернему client process через environment.
`make agent` проверяет executable каждого настроенного сервера и передаёт
клиенту точный список разрешённых имён отдельными значениями
`--allowed-mcp-server-names`. Persona MCP получает фактический project/worktree
root. Обычный vendor CLI, запущенный в проекте, использует ту же project MCP
поверхность без global extension.

Для transport-диагностики `.venv/bin/persona-zephyr-mcp --project-root .`
запускает long-lived server в foreground. Журнал содержит JSON-RPC request
start/end, safe issue/test keys, status, diagnostic и elapsed time; PAT,
Authorization и protocol bodies не записываются. `zephyr_test_case_get` читает
до 50 шагов на страницу, а большие значения продолжает через точный JSON
Pointer в `zephyr_test_case_value`.

Durable project state включает Persona-managed client surface,
`.persona/make/persona.mk`, `.persona/project/ownership.json`,
`.persona/project_memory/memory.jsonl`, `.persona/project_memory/sources/**` и
`.persona/workflows/**`. Binding, local settings, runtime projections, context
packs, rendered views, retrieval indexes, MCP logs, dependencies, databases,
locks и временные файлы игнорируются Git. `.gitattributes` назначает
`merge=union` для memory JSONL; derived views пересобираются через
`make memory action=render`.

Повторный `make install` сверяет dependency fingerprint, report runtime,
content-addressed project plan, ownership, client binding, MCP/LSP и doctor.
Совпадающий контракт возвращает `noop`; изменённый Persona-owned файл создаёт
точный conflict, а устаревший package-owned artifact обновляется одной project
transaction. Локальные secrets при reconcile не переписываются.

<!-- persona-agent-flow-contract:start -->
## Сценарии Persona Agent

`task_id` идентифицирует один flow, объявленный в `Identity.flow`. Route видит packets и receipts этого flow. Project-discovery передаёт другим flows принятую project memory, но не свои packets.

| Flow | Тип | Entry | Completion | Routes |
|---|---|---|---|---|
| `project-discovery` | composed | `project-profile`, `project-map` | `project-profile`, `project-map` | `project-profile`, `project-map` |
| `memory-maintenance` | atomic | `memory-refresh` | `memory-refresh` | `memory-refresh` |
| `new-test` | composed | `test-plan` | `test-integration`, `acceptance` | `test-plan`, `test-sufficiency`, `author-test`, `test-run`, `run-analysis`, `test-integration`, `acceptance` |
| `manual-case-automation` | composed | `test-sufficiency`, `author-test` | `test-integration`, `acceptance` | `test-sufficiency`, `author-test`, `test-run`, `run-analysis`, `test-integration`, `acceptance` |
| `test-execution` | composed | `test-run`, `run-analysis`, `acceptance` | `test-run`, `test-integration`, `acceptance` | `test-run`, `run-analysis`, `author-test`, `test-integration`, `acceptance` |
| `quality-campaign` | atomic | `quality-refactor` | `quality-refactor` | `quality-refactor` |

Индекс: `persona_knowledge(action="agent_flows")`. Полный flow: `persona_knowledge(action="agent_flow", flow_id="<flow>")`. Точный route: `persona_knowledge(action="agent_route", route="<route>")`.
<!-- persona-agent-flow-contract:end -->

Bundled skills are domain playbooks, not extra slash-command stages:

- `persona-project-memory` is used by project-map, memory-refresh and
  source-backed test planning. It owns scoped source evidence, Confluence
  parent/child checklists, reviewed memory records, gaps, context packs and
  view refresh decisions.
- `persona-test-authoring` is used by live test-sufficiency, author-test and
  code-changing test-integration. In sufficiency mode it owns runtime traversal
  evidence without code writes; authoring mode owns support generation,
  Page/Step/Fact/Expectation structure, `persona_test_author` draft/write and
  runner verification.
- `persona-run-evidence` is used by test-run, run-analysis and acceptance. It
  owns UUID `run_id`, session/run binding, report refs, failure taxonomy,
  acceptance packets and post-run memory updates.
- `persona-quality-refactor` is used by quality-refactor. It owns the strict
  baseline, structure/guides/suppression reviews, dependency-ordered root-cause
  plan, bounded remediation checkpoints, repeated gates and final evidence.

Slash commands remain the user workflow; skills are the reusable professional
behavior the launched agent applies inside that workflow.

Atlassian-scoped project-map examples:

```bash
/persona.project-map agent_args="source=confluence:SPACE-123 include_children=true"
/persona.project-map agent_args="source=confluence:987654 include_children=false"
/persona.test-plan jira=PROJ-123
```

Inside GigaCode the same parameters are passed directly to slash workflows:

```text
/persona.project-map task_id=auth-map source=confluence:SPACE-123 include_children=true
/persona.project-map task_id=repo-map source=repo:pyproject.toml
/persona.test-plan jira=PROJ-123 source=confluence:987654
/persona.project-map составь карту по Confluence page 987654 с дочерними страницами
/persona.test-plan подготовь план по Jira PROJ-123 и Confluence 987654
```

`include_children=true` means the agent must build a Source Checklist for the
parent Confluence page and each discovered child before extraction. Missing
Atlassian access or unreadable child pages become project-memory gap records.
Natural-language slash input is accepted when it contains a concrete Jira key,
Confluence page id or scope ref; the agent canonicalizes it to the same scoped
arguments and must ask for the missing identifier instead of broad searching.

Cognee-backed project memory is an optional package extra and project config:

```bash
pip install "persona-dsl[memory-cognee]"
```

```toml
[tool.persona.project_memory]
provider = "cognee"
dataset_name = "persona_project"
top_k = 15
use_domain_graph_model = true
self_improvement = false
include_backend_context = true
```

Persona keeps one MCP server. GigaCode/Qwen calls `persona_project_memory`; Persona
ingests raw sources into Cognee as the project-local graph/vector backend and
keeps reviewed records in `.persona/project_memory/memory.jsonl`.

```bash
persona memory status --project-root .
persona memory view --project-root .
persona memory serve --project-root . --host 127.0.0.1 --port 8776
```

## Runtime Event Sink

Persona публикует runtime-события во внешний sink через нейтральный contract:

- `PERSONA_EVENT_SINK=redis_stream`
- `PERSONA_EVENT_REDIS_URL=redis://localhost:6379/0`
- `PERSONA_EVENT_STREAM_KEY=persona:{launch_id}:events`
- `PERSONA_EVENT_STREAM_MAXLEN=10000`
- `PERSONA_EVENT_STRICT_CONNECT=false`

При отключённом sink локальные runtime/report artifacts продолжают писаться в проектные директории.
Starter поднимает локальный Redis через `template/compose.yaml`:

```bash
cd template
make redis
make redis action=status
```

## Data Pools

`runtime.data_pools` описывает эксклюзивные элементы для параллельных запусков.
Типизированный resource объявляется обычным классом в `support/resources`:

```python
from persona_dsl import Persona, leased_resource


@leased_resource("parallel.user", pool="parallel_users")
class ParallelUser:
    item_id: str
    username: str
    password: str


def test_parallel_login(persona: Persona) -> None:
    user = persona.resource(ParallelUser)
    login(user.username, user.password)
```

`@leased_resource` сам создаёт strict frozen/slotted dataclass. `item_id`
берётся из lease envelope, остальные поля класса — из одноимённых ключей
`payload`. Лишние, отсутствующие и несовместимые по типу поля завершают
получение ресурса диагностируемой ошибкой. Runtime берёт lease при фактическом
`persona.resource(ParallelUser)` и освобождает его через lifecycle test-scoped
resource. `define_lease_resource(...)` остаётся для ресурсов с пользовательской
фабрикой, преобразованием или cleanup.

`config/dev.yaml` связывает pool с отдельным YAML-файлом:

```yaml
runtime:
  data_pools:
    parallel_users:
      backend: "local_sqlite"
      acquire_timeout_sec: 30
      lease_ttl_sec: 300
      heartbeat_interval_sec: 30
      items_source:
        path: "scenario_data/data_pools/parallel_users.yaml"
        format: "yaml"
```

`scenario_data/data_pools/parallel_users.yaml` содержит элементы пула:

```yaml
- item_id: "parallel_user_01"
  payload:
    username: "test-user-01"
    password: "credential-placeholder-01"
```

Элемент пула требует только `item_id`. `auth_key` задаётся для ресурсов, которые
читают credentials через `persona.auth(...)`; несекретные значения хранятся в
`payload`.

Redis backend для data pools читает URL из env-переменной, указанной в
`runtime.data_pools.<pool_id>.redis.url_env`. Starter содержит `env=redis` и
пример `.env.redis.example`:

- `PERSONA_DATA_POOL_REDIS_URL=redis://localhost:6379/0`
- `PERSONA_EVENT_REDIS_URL=redis://localhost:6379/0`

`dev` и `staging` используют `local_sqlite`; Redis-режим выбирается явно через
`env=redis`.

## Knowledge

Package-owned knowledge включает guide, framework/config catalogs, retrieval corpus и build-time completeness inventory. Проверка выполняется через:

```bash
make knowledge-generate
make knowledge-check
```
