Metadata-Version: 2.4
Name: persona-dsl
Version: 2026.7.3rc35
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
Requires-Dist: zeep
Requires-Dist: pg8000
Requires-Dist: oracledb
Requires-Dist: kafka-python
Requires-Dist: Unidecode>=1.3
Requires-Dist: black
Requires-Dist: ruff
Requires-Dist: pip-audit
Requires-Dist: vulture
Requires-Dist: libcst
Requires-Dist: python-dateutil
Requires-Dist: xsdata[cli]
Requires-Dist: xmlschema
Requires-Dist: rich
Requires-Dist: textual
Requires-Dist: typing-extensions>=4.10
Provides-Extra: dev
Requires-Dist: black; extra == "dev"
Requires-Dist: ruff; extra == "dev"
Requires-Dist: pip-audit; extra == "dev"
Requires-Dist: vulture; extra == "dev"
Requires-Dist: mypy; extra == "dev"
Requires-Dist: pytest; extra == "dev"
Requires-Dist: allure-pytest<3,>=2.15; extra == "dev"
Requires-Dist: pytest-xdist; extra == "dev"
Requires-Dist: slipcover==1.0.18; 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: pytest-asyncio; extra == "dev"
Requires-Dist: pytest-timeout; extra == "dev"

# 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/` — правила и инженерные принципы.

## Установка

```bash
pip install persona-dsl
python -m playwright install --with-deps
```

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

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

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

- distribution: `persona-dsl`
- import package: `persona_dsl`
- CLI: `persona`, `persona-page-gen`, `persona-api-gen`, `persona-schema-gen`, `persona-mcp`

## Проверка 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` | Dependency vulnerability audit through pip-audit. | `command`, `args`, `required`. |
| `semgrep` | Optional Semgrep SAST through project-pinned local rules. Default discovery uses `semgrep.yml`, `semgrep.yaml`, `.semgrep.yml`, `.semgrep.yaml`, `rules/semgrep` or `semgrep-rules`; use `command` for another local 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`.

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

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`, а runtime выбирает `test_id` раньше `aria_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` коллекции: прототип получает
locator по structural class, стабильные поля вроде `product_image`, `price`,
`product_name`, `add_to_cart`, `view_product`, а runtime refs, item-specific
URLs и глобальные индексы не попадают в прототип.
После генерации поддерживаемый 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`,
`aria_ref` и связанных 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` или
`aria_ref`; 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 chooses `test_id` before `aria_ref`.
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 a structural-class prototype and
stable child fields such as `product_image`, `price`, `product_name`,
`add_to_cart` and `view_product`.
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 `aria_ref`; heading text remains a child element by itself.

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

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

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

- MCP `tools/call` возвращает короткий text summary в `content[0].text` и полный machine-readable payload в `structuredContent`; агенты читают поля, diagnostics, matches, refs и generated manifests из `structuredContent`, а не из text dump. EN: MCP tool calls keep `content[0].text` compact and place the complete payload in `structuredContent`.
- `persona_knowledge` читает compact guide, guide_search, framework catalog и config catalog; `include_examples` и `include_source_refs` включают расширенный authoring context явно. `guide_search`, `framework_search` и `config_search` всегда возвращают bounded page: `limit=0` означает max page, а не unbounded all; продолжение читается через `offset`. `framework_search.invocation_modes` выбирает symbols, которые поддерживают хотя бы один указанный live command mode; для web Ops используйте точный `query` и paged `limit`/`offset`, а catalog audit выполняйте постранично.
- `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_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; `lifecycle=true` выполняет runner lifecycle setup для указанного `scenario_id`.
- `persona_session_state` возвращает 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_make`, `persona_get`, `persona_check` принимают typed `operation` payload из `persona_knowledge`, `persona_project`, generation manifest или support plan и пишут каждый вызов в history. 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.
- `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` запускает уже записанный тестовый файл или native `scenario_id` через изолированный native runner path/scenario selector, поэтому final runner check можно выполнять до закрытия live authoring session; ответ содержит `launch_id`, `launch_status`, item counts, `output_summary` и `report_refs`.
- EN: `persona_test_run` returns structured runner evidence; agents use `report_refs` and counts instead of parsing localized stdout.
- `persona_session_close` закрывает session с явным `status=passed|failed|partial`; `failed` запускает failure lifecycle hook, `partial` освобождает exploratory session без `run_on_test_failure`, cleanup and resource release выполняются при закрытии.

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 `make migrate-page-names` является thin wrapper над этой Persona CLI
командой. EN: starter `make migrate-page-names` is a thin wrapper over this
Persona CLI command.

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

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

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

Команды starter:

```bash
cd template
make install
make checks
make redis-up
make redis-status
```

## 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-up
make redis-status
```

## Data Pools

`runtime.data_pools` описывает эксклюзивные элементы для параллельных запусков.
Сценарий получает не пул, а resource:

```python
user = persona.resource("auth.user")
```

Resource объявляется в `support/resources` через
`define_lease_resource("auth.user", pool="ui_users")`. Runtime берёт lease при
фактическом вызове resource и освобождает его через cleanup test-scoped
resource.

Элемент пула требует только `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 surface находится в `src/persona_dsl/knowledge/` и проверяется через:

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