From d8d086855c8a390d7ac8221f6dafc8d848ce5c66 Mon Sep 17 00:00:00 2001 From: jana-selva Date: Thu, 30 Jul 2026 11:33:51 +0530 Subject: [PATCH 1/6] PTB skill initial commiit --- doc/changes/unreleased.md | 4 +- .../skills/exasol-python-toolbox/SKILL.md | 53 +++++ .../exasol-python-toolbox/agents/openai.yaml | 4 + .../references/coding-guidelines.md | 58 +++++ .../references/common-workflows.md | 134 ++++++++++++ .../references/nox-sessions.md | 100 +++++++++ .../references/source-routing.md | 87 ++++++++ pyproject.toml | 3 +- .../exasol-python-toolbox/eval_cases.yml | 103 +++++++++ test/unit/skills_test.py | 202 ++++++++++++++++++ 10 files changed, 746 insertions(+), 2 deletions(-) create mode 100644 exasol/toolbox/skills/exasol-python-toolbox/SKILL.md create mode 100644 exasol/toolbox/skills/exasol-python-toolbox/agents/openai.yaml create mode 100644 exasol/toolbox/skills/exasol-python-toolbox/references/coding-guidelines.md create mode 100644 exasol/toolbox/skills/exasol-python-toolbox/references/common-workflows.md create mode 100644 exasol/toolbox/skills/exasol-python-toolbox/references/nox-sessions.md create mode 100644 exasol/toolbox/skills/exasol-python-toolbox/references/source-routing.md create mode 100644 test/resources/skills/exasol-python-toolbox/eval_cases.yml create mode 100644 test/unit/skills_test.py diff --git a/doc/changes/unreleased.md b/doc/changes/unreleased.md index 28ee15466..fac471d90 100644 --- a/doc/changes/unreleased.md +++ b/doc/changes/unreleased.md @@ -3,11 +3,13 @@ ## Summary This release documents how to discover and use PTB's Nox sessions in the user -guide and removes the unused Modules section from the developer guide. +guide, adds an agent skill for PTB work, and removes the unused Modules section +from the developer guide. ## Documentation * #456: Documented how to discover PTB nox sessions in the user guide +* #933: Added an agent skill for PTB work ## Refactoring diff --git a/exasol/toolbox/skills/exasol-python-toolbox/SKILL.md b/exasol/toolbox/skills/exasol-python-toolbox/SKILL.md new file mode 100644 index 000000000..cbb87df19 --- /dev/null +++ b/exasol/toolbox/skills/exasol-python-toolbox/SKILL.md @@ -0,0 +1,53 @@ +--- +name: exasol-python-toolbox +description: Use this skill in Exasol Python projects that use exasol-toolbox/PTB. Use it for PTB setup, nox sessions, code checks, GitHub workflows, updates, releases, and PTB configuration. Use it when an agent must not replace PTB automation. +--- + +# Exasol Python Toolbox + +Use PTB as the task interface for Exasol Python repositories. Use the nox +sessions, configuration, templates, and hooks from the installed +`exasol-toolbox` package. Do not replace them with manual commands. Use direct +tool commands only to isolate a failure. + +Use controlled technical English. Use short sentences. Put one instruction in +one sentence when possible. Use the same term for the same item. + +## Operating rules + +1. Confirm that the project uses PTB before you act: + - `pyproject.toml` contains a dependency on `exasol-toolbox`. + - `noxfile.py` imports from `exasol.toolbox.nox.tasks`. + - `noxconfig.py` defines `PROJECT_CONFIG`. +2. Run commands with Poetry. If the project gives a different command, use it. +3. Use `references/nox-sessions.md` for session names and command syntax. +4. Before you edit generated workflow files, use PTB workflow sessions, + `.workflow-patcher.yml`, `PROJECT_CONFIG`, or PTB hooks. +5. Do not run Black, isort, Ruff, Pylint, Mypy, Pytest, Coverage, Sphinx, + Poetry build, Twine, or zizmor directly when a PTB nox session does the same + work. Use direct tool commands only for fault isolation. + +## Task routing + +Read only the reference that the task needs: + +- Source files and resource files for exact PTB operation: + `references/source-routing.md` +- Setup, code changes, CI fixes, releases, PTB updates, dependency updates, and + workflow maintenance: `references/common-workflows.md` +- Nox session names, arguments, outputs, and limits: + `references/nox-sessions.md` +- Coding rules, PTB design rules, and rules for PTB extension: + `references/coding-guidelines.md` + +## Source rule + +The skill does not copy PTB source code. Source copies can become old. Use this +skill to find the correct file. Read the file from the repository before you +change or explain exact PTB operation. + +## Release safety + +`release:trigger` creates git tags. It pushes the tags. Run it only when the +user asks you to trigger a release. Check the repository state first. +Use `references/common-workflows.md` for the release workflow. diff --git a/exasol/toolbox/skills/exasol-python-toolbox/agents/openai.yaml b/exasol/toolbox/skills/exasol-python-toolbox/agents/openai.yaml new file mode 100644 index 000000000..b830f708e --- /dev/null +++ b/exasol/toolbox/skills/exasol-python-toolbox/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Exasol Python Toolbox" + short_description: "Use PTB nox sessions" + default_prompt: "Use $exasol-python-toolbox to select the correct PTB nox session." diff --git a/exasol/toolbox/skills/exasol-python-toolbox/references/coding-guidelines.md b/exasol/toolbox/skills/exasol-python-toolbox/references/coding-guidelines.md new file mode 100644 index 000000000..8aef02001 --- /dev/null +++ b/exasol/toolbox/skills/exasol-python-toolbox/references/coding-guidelines.md @@ -0,0 +1,58 @@ +# Coding Guidelines for PTB Work + +PTB is a development dependency. PTB is also a task interface. Keep project +automation aligned with PTB conventions. Change the convention only if the +project has a clear reason. + +Use controlled technical English in user-facing text. Use short sentences. Put +one instruction in one sentence when possible. Use consistent terms. + +## Sources to read + +- `doc/design.rst`: PTB design rules and task boundaries. +- `doc/user_guide/getting_started.rst`: setup and integration. +- `doc/user_guide/features/formatting_code/index.rst`: format tools and format + sessions. +- `doc/user_guide/features/creating_a_release.rst`: release procedure. +- Exasol Python styleguide tooling reference: + `https://exasol.github.io/python-styleguide/guides/tooling.html` + +## Design rules to keep + +- Treat PTB as development tooling. +- Do not import PTB from production package code. +- Use conventions first. +- Add configuration only when a convention is not sufficient. +- Use `pyproject.toml` for static tool configuration. +- Use `noxconfig.py` and `PROJECT_CONFIG` for dynamic project configuration. +- Use plugin hooks or PTB extension points for project-specific work. +- Make project-specific work operate with standard sessions. +- Keep GitHub workflows as orchestration. +- Put shared task logic in Python functions or nox sessions. +- Do not put shared task logic directly in workflow YAML. +- Do not make nox sessions notify other nox sessions for shared task logic. +- Put shared nox logic in functions that receive the nox `Session`. +- Add a PTB feature only when a real project needs it. +- Move logic into PTB when more than one project needs it. + +## Code change rules + +Use PTB sessions to format and validate code. + +When you change public operation, update the related tests. Update +`doc/changes/unreleased.md` when the project requires a changelog entry. + +For workflow-template changes, check generated workflows. Generate workflows +only when you intend to change them. Use `nox-sessions.md` for command syntax. + +## Project-specific differences + +If a standard PTB session is not correct for a project: + +1. Check whether existing `BaseConfig` or `PROJECT_CONFIG` fields solve the + issue. +2. Check whether a PTB plugin hook applies. +3. Use `.workflow-patcher.yml` for supported workflow changes. +4. Use a local nox override only as a short-term migration step. +5. If multiple projects need the same change, consider a PTB template, + configuration field, or hook. diff --git a/exasol/toolbox/skills/exasol-python-toolbox/references/common-workflows.md b/exasol/toolbox/skills/exasol-python-toolbox/references/common-workflows.md new file mode 100644 index 000000000..fb68e8b74 --- /dev/null +++ b/exasol/toolbox/skills/exasol-python-toolbox/references/common-workflows.md @@ -0,0 +1,134 @@ +# Common PTB Workflows + +Use these workflows in an Exasol Python repository that uses PTB. +Use `nox-sessions.md` for nox command syntax. + +## Set up PTB + +For a new project, use the PTB Cookiecutter template. + +```bash +cookiecutter https://github.com/exasol/python-toolbox.git \ + --checkout --directory project-template \ + --overwrite-if-exists +``` + +Use a released tag. This gives the same setup result each time. + +After you create the project, run these commands: + +```bash +poetry env use +poetry install +``` + +For an existing project: + +1. Add PTB as a development dependency: + `poetry add --group dev exasol-toolbox` +2. Add or update `noxconfig.py`. +3. Define `PROJECT_CONFIG` in `noxconfig.py`. +4. Import the standard sessions in `noxfile.py`: + `from exasol.toolbox.nox.tasks import *` +5. Move tool configuration to `pyproject.toml` where PTB expects it. +6. Run `poetry install`. +7. List the available nox sessions. +8. Add optional integrations only when the project needs them. Examples are + pre-commit hooks, documentation, Sonar, and GitHub workflows. + +If a project cannot use a PTB session without changes, use a local override +only as a short-term migration step. For a permanent solution, use +configuration, plugin hooks, or PTB extension points. + +## Make a daily code change + +1. Read `noxconfig.py`. +2. Read the related tool sections in `pyproject.toml`. +3. Make the requested code change, document change, or configuration change. +4. Run the smallest PTB session that checks the change. +5. Run `project:check` when the change affects shared operation. +6. Run `project:check` when the change affects release readiness. +7. Run `project:check` when the change affects CI operation. + +When you remove or rename a documentation file, update each Sphinx `toctree` +that points to it. Then run `docs:build`. + +Put more Pytest arguments after `--`. +Use `nox-sessions.md` for examples. + +## Fix lint or format findings + +Run the PTB formatter first. Then run the checks that match the failure. +Use `nox-sessions.md` for the exact session commands. + +Do not adjust code manually to imitate Black, isort, or Ruff. Let PTB run these +tools. Use PTB configuration to exclude a file or directory. + +## Prepare a release + +Prepare a release with `release:prepare`. + +This session does these actions: + +1. It updates the version. +2. It moves content from `doc/changes/unreleased.md` to a versioned changes file. +3. It updates `doc/changes/changelog.md`. +4. It runs configured release hooks. +5. It commits the changes if you do not use `--no-add`. +6. It opens a PR if you do not use `--no-pr`. + +Useful flags: + +- `--no-branch`: Do not create or switch to a release branch. +- `--no-add`: Do not add or commit changes. +- `--no-pr`: Do not create a pull request. + +If dependencies change after release preparation, update the versioned changes +file with `release:update`. + +After the release PR is merged, apply the release safety rule from `SKILL.md`. + +`release:trigger` checks out the default branch. It pulls the default branch. It +creates a version tag. It pushes the tag. It can update a `v` tag if +`PROJECT_CONFIG` enables this function. + +## Update PTB or dependencies + +When you update `exasol-toolbox` in a project: + +1. Read the PTB changelog for migration notes. +2. Update the dependency with Poetry. Example: + `poetry update exasol-toolbox` +3. Generate or check PTB-managed workflows. +4. Run necessary validation sessions. +5. Update `doc/changes/unreleased.md` when the project requires a changelog + entry. + +For dependency updates that fix vulnerabilities, use the PTB vulnerability +sessions. + +## Maintain GitHub workflows + +PTB ships workflow templates. Generate workflows instead of editing generated +files manually. + +Use the PTB workflow sessions. + +Use `.workflow-patcher.yml` for supported project-specific workflow changes. If +multiple Exasol Python projects need the same change, consider a PTB template, +configuration field, or hook. + +When you change workflow templates, custom workflows, or `.workflow-patcher.yml`, +check these items after generation: + +- No `needs` entry points to a removed job. +- Required root-level and job-level `permissions` stay in the generated + workflow. +- Scalar permission forms, for example `read-all` and `write-all`, are handled + or rejected with a clear error. +- Release guards, for example tag checks, still run before release or extension + jobs. +- Self-release workflows use an action reference that exists before the first + release tag is pushed. +- Project tools run in the Poetry environment when the project dependencies are + installed there. diff --git a/exasol/toolbox/skills/exasol-python-toolbox/references/nox-sessions.md b/exasol/toolbox/skills/exasol-python-toolbox/references/nox-sessions.md new file mode 100644 index 000000000..262b816e0 --- /dev/null +++ b/exasol/toolbox/skills/exasol-python-toolbox/references/nox-sessions.md @@ -0,0 +1,100 @@ +# PTB Nox Sessions + +Always list the sessions in the current project: + +```bash +poetry run -- nox -l +``` + +The sessions below match the PTB version that includes this skill. + +## Project and quality sessions + +| Session | Use | Notes | +| --- | --- | --- | +| `project:check` | Run large local checks or CI checks. | It runs format check, Pylint, Mypy, and coverage for unit and integration tests. | +| `format:fix` | Apply Python format changes. | It runs pyupgrade, Ruff fixes, isort, and Black. | +| `format:check` | Check Python format. | It runs Ruff check, isort check, and Black check. | +| `lint:code` | Run static code analysis. | It runs Pylint on `PROJECT_CONFIG.source_code_path` and writes `.lint.json`. | +| `lint:typing` | Run type checks. | It runs Mypy on filtered project Python files. | +| `lint:security` | Run security lint. | It runs Bandit and writes `.security.json`. | +| `lint:dependencies` | Check legacy dependency sources. | This session is deprecated. It is scheduled for removal on 2026-10-08. Do not add new use. | + +## Test sessions + +| Session | Use | Notes | +| --- | --- | --- | +| `test:unit` | Run unit tests. | It runs `pytest -v test/unit`. Put Pytest arguments after `--`. | +| `test:integration` | Run integration tests. | It runs `pytest -v test/integration`. It supports PTB integration test hooks. | +| `test:coverage` | Run all tests with coverage. | It runs unit and integration tests with coverage. It prints `coverage report -m`. | + +Test command examples: + +```bash +poetry run -- nox -s test:unit +poetry run -- nox -s test:unit -- --coverage +poetry run -- nox -s test:unit -- test/unit/path_test.py -k scenario +poetry run -- nox -s test:integration -- --db-version 8.34.0 +``` + +## Documentation and changelog sessions + +| Session | Use | Notes | +| --- | --- | --- | +| `docs:build` | Build current documentation. | It runs Sphinx HTML build. It makes warnings errors. | +| `docs:multiversion` | Build multiversion documentation. | It runs `sphinx-multiversion` and creates `.nojekyll`. | +| `docs:open` | Open built documentation in a browser. | It requires `.html-documentation`. It can require GUI approval. | +| `docs:clean` | Remove generated documentation. | It deletes `.html-documentation`. | +| `links:list` | List documentation links. | It uses Sphinx linkcheck. It ignores link failures. | +| `links:check` | Validate documentation links. | It can write JSON output with `-- -o `. | +| `changelog:updated` | Check that the changelog changed. | It fails if `doc/changes` did not change when compared with `origin/main`. | + +## Release sessions + +| Session | Use | Notes | +| --- | --- | --- | +| `release:prepare` | Prepare a release PR. | It requires `-- --type major`, `minor`, or `patch`. It creates a branch, commit, and PR if flags do not disable these actions. | +| `release:update` | Update a prepared release changelog. | It updates the latest versioned changelog after dependency changes. | +| `release:trigger` | Trigger a release from the default branch. | It creates and pushes git tags. | + +Release command examples: + +```bash +poetry run -- nox -s release:prepare -- --type patch +poetry run -- nox -s release:prepare -- --type minor --no-pr --no-branch --no-add +poetry run -- nox -s release:update +``` + +## Workflow sessions + +| Session | Use | Notes | +| --- | --- | --- | +| `workflow:check` | Check generated workflows. | It requires one workflow name or `all`. It fails when files differ from PTB templates. | +| `workflow:generate` | Generate or update workflows. | It requires one workflow name or `all`. | +| `workflow:audit` | Audit workflows and actions. | It runs zizmor with PTB configuration. It sends more zizmor arguments to zizmor. | + +Workflow command examples: + +```bash +poetry run -- nox -s workflow:check -- all +poetry run -- nox -s workflow:generate -- checks +poetry run -- nox -s workflow:audit -- --fix=safe +``` + +## Dependency, artifact, package, and matrix sessions + +| Session | Use | Notes | +| --- | --- | --- | +| `dependency:licenses` | Report dependency licenses. | It prints a Markdown license report. | +| `dependency:audit` | Report vulnerabilities. | It prints known vulnerabilities as JSON. | +| `vulnerabilities:update` | Update vulnerable dependencies. | It can write a JSON report file inside the repository. | +| `vulnerabilities:resolved` | Report vulnerabilities that are resolved since the latest tag. | Use it during release preparation. | +| `dependency:sbom` | Generate SBOM files. | It writes `bom.cdx.json` and `bom.spdx.json`. | +| `artifacts:validate` | Validate CI artifacts. | It checks `.coverage`, `.lint.json`, and `.security.json`. | +| `artifacts:copy` | Copy and combine CI artifacts. | It requires an artifact directory argument. | +| `sonar:check` | Upload artifacts to Sonar. | It uses `SONAR_TOKEN` and prepares `ci-coverage.xml`. | +| `package:check` | Validate the package long description. | It runs `poetry build` and `twine check`. | +| `matrix:generate` | Print selected config values as JSON arrays. | Use this session for matrix output. | +| `matrix:python` | Print legacy Python matrix output. | This session is deprecated. It is scheduled for removal on 2026-09-15. | +| `matrix:exasol` | Print legacy Exasol matrix output. | This session is deprecated. It is scheduled for removal on 2026-09-15. | +| `matrix:all` | Print legacy Python and Exasol matrix output. | This session is deprecated. It is scheduled for removal on 2026-09-15. | diff --git a/exasol/toolbox/skills/exasol-python-toolbox/references/source-routing.md b/exasol/toolbox/skills/exasol-python-toolbox/references/source-routing.md new file mode 100644 index 000000000..1a3c15889 --- /dev/null +++ b/exasol/toolbox/skills/exasol-python-toolbox/references/source-routing.md @@ -0,0 +1,87 @@ +# Source Routing + +Use this reference to find the source file for exact PTB operation. +Read the source file before you change code. +Read the source file before you explain a detailed rule. + +## Project entry points + +- `pyproject.toml`: package data, dependencies, tool configuration, and + packaged resources. +- `noxfile.py`: local nox entry point. +- `noxconfig.py`: local `PROJECT_CONFIG` and release hooks. +- `README.rst`: project overview. +- `doc/design.rst`: PTB design rules. + +## PTB configuration + +- `exasol/toolbox/config.py`: `BaseConfig`, computed configuration fields, + supported versions, paths, workflow variables, and plugin validation. +- `exasol/toolbox/nox/plugin.py`: nox plugin hooks. + +## PTB nox sessions + +- `exasol/toolbox/nox/tasks.py`: exported standard sessions and `project:check`. +- `exasol/toolbox/nox/_format.py`: `format:fix` and `format:check`. +- `exasol/toolbox/nox/_lint.py`: `lint:code`, `lint:typing`, + `lint:security`, and deprecated `lint:dependencies`. +- `exasol/toolbox/nox/_test.py`: `test:unit`, `test:integration`, and + `test:coverage`. +- `exasol/toolbox/nox/_documentation.py`: documentation, link check, and + changelog sessions. +- `exasol/toolbox/nox/_release.py`: release preparation, release update, and + release trigger sessions. +- `exasol/toolbox/nox/_workflow.py`: workflow check, generation, and audit + sessions. +- `exasol/toolbox/nox/_dependencies.py`: dependency audit, license, + vulnerability, and SBOM sessions. +- `exasol/toolbox/nox/_artifacts.py`: artifact validation, artifact copy, and + Sonar upload sessions. +- `exasol/toolbox/nox/_matrix.py`: matrix output sessions. +- `exasol/toolbox/nox/_package.py`: package validation. +- `exasol/toolbox/nox/_shared.py`: shared nox helpers. + +## PTB tools + +- `exasol/toolbox/tools/tbx.py`: CLI root. +- `exasol/toolbox/tools/template.py`: template list, show, diff, install, and + update helpers. +- `exasol/toolbox/tools/issue.py`: issue template CLI. +- `exasol/toolbox/tools/security.py`: security issue conversion and creation. +- `exasol/toolbox/tools/replace_version.py`: version replacement helpers. + +## Workflow templates and helpers + +- `exasol/toolbox/templates/github/workflows/`: packaged GitHub workflow + templates. +- `exasol/toolbox/templates/github/`: packaged GitHub templates and zizmor + configuration. +- `exasol/toolbox/util/workflows/`: workflow rendering, patching, validation, + and custom workflow extraction. +- `.workflow-patcher.yml`: PTB workflow patch configuration for this repository. +- `.github/workflows/`: generated repository workflows. +- `.github/actions/`: repository GitHub actions. + +## Release and dependency helpers + +- `exasol/toolbox/util/release/`: changelog, release notes, and Cookiecutter + version helpers. +- `exasol/toolbox/util/dependencies/`: dependency audit, license report, + dependency change, vulnerability tracking, and dependency update helpers. +- `exasol/toolbox/util/version.py`: version parsing and version upgrade logic. +- `exasol/toolbox/util/git.py`: git helper functions. + +## Documentation and project template + +- `doc/user_guide/`: user tasks and feature documentation. +- `doc/developer_guide/`: developer and plugin documentation. +- `doc/api/`: API documentation entry points. +- `doc/changes/`: changelog files. +- `project-template/`: Cookiecutter template for new PTB projects. + +## Tests + +- `test/unit/`: unit tests for PTB operation. +- `test/integration/`: integration tests. +- `test/integration/project-template/`: tests for the Cookiecutter project + template. diff --git a/pyproject.toml b/pyproject.toml index e5d1a28cb..e42826c30 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -83,7 +83,8 @@ include = [ "README.rst", "doc/changes/changelog.md", "LICENSE", - "exasol/toolbox/templates/**/*" + "exasol/toolbox/templates/**/*", + "exasol/toolbox/skills/**/*" ] [project.urls] diff --git a/test/resources/skills/exasol-python-toolbox/eval_cases.yml b/test/resources/skills/exasol-python-toolbox/eval_cases.yml new file mode 100644 index 000000000..31c107e04 --- /dev/null +++ b/test/resources/skills/exasol-python-toolbox/eval_cases.yml @@ -0,0 +1,103 @@ +version: 1 +skill: "exasol-python-toolbox" +cases: + - id: "setup-existing-project" + category: "setup" + prompt: "An existing Python project must start to use PTB. Tell me the correct setup steps." + expected: + must_include: + - "poetry add --group dev exasol-toolbox" + - "noxconfig.py" + - "PROJECT_CONFIG" + - "noxfile.py" + - "from exasol.toolbox.nox.tasks import *" + must_not_include: + - "copy PTB source code" + - "replace PTB automation" + + - id: "fix-format-failure" + category: "quality" + prompt: "A PTB project has format check failures. Tell me the correct first action." + expected: + must_include: + - "format:fix" + - "nox-sessions.md" + must_not_include: + - "run Black directly" + - "run isort directly" + + - id: "run-targeted-unit-test" + category: "test" + prompt: "I changed one unit test file. Tell me how to run a targeted unit test." + expected: + must_include: + - "test:unit" + - "--" + - "nox-sessions.md" + must_not_include: + - "run Pytest directly" + - "project:check first" + + - id: "prepare-release" + category: "release" + prompt: "Prepare a patch release for a PTB project. Tell me the safe workflow." + expected: + must_include: + - "release:prepare" + - "--type patch" + - "review" + - "changelog" + must_not_include: + - "release:trigger" + - "push tags" + + - id: "trigger-release-safety" + category: "release" + prompt: "The release PR is merged. Tell me the safety checks before I trigger the release." + expected: + must_include: + - "release:trigger" + - "Check the repository state" + - "user asks" + must_not_include: + - "run without approval" + - "skip repository checks" + + - id: "workflow-template-update" + category: "workflow" + prompt: "A generated or patched GitHub workflow changed in a PTB project. Tell me what to do." + expected: + must_include: + - "workflow:check" + - "workflow:generate" + - ".workflow-patcher.yml" + - "needs" + - "permissions" + must_not_include: + - "edit generated workflow files first" + - "manual workflow edit first" + + - id: "update-ptb-dependency" + category: "update" + prompt: "Update exasol-toolbox in a PTB project. Tell me the validation steps." + expected: + must_include: + - "poetry update exasol-toolbox" + - "workflow:generate" + - "workflow:check" + - "doc/changes/unreleased.md" + must_not_include: + - "ignore migration notes" + - "skip validation" + + - id: "exact-source-behavior" + category: "source-routing" + prompt: "I need exact behavior for release preparation. Tell me which source file to read." + expected: + must_include: + - "source-routing.md" + - "exasol/toolbox/nox/_release.py" + - "read the source file" + must_not_include: + - "guess" + - "copy PTB source code" diff --git a/test/unit/skills_test.py b/test/unit/skills_test.py new file mode 100644 index 000000000..b1fda913e --- /dev/null +++ b/test/unit/skills_test.py @@ -0,0 +1,202 @@ +import tomllib +from ast import literal_eval +from pathlib import Path + +PROJECT_ROOT = Path(__file__).parents[2] +SKILL = PROJECT_ROOT / "exasol" / "toolbox" / "skills" / "exasol-python-toolbox" +EVAL_CASES = ( + PROJECT_ROOT + / "test" + / "resources" + / "skills" + / "exasol-python-toolbox" + / "eval_cases.yml" +) + + +def _parse_quoted_value(value: str) -> str: + return literal_eval(value.strip()) + + +def _load_eval_cases_without_yaml_parser() -> dict: + result: dict = {"cases": []} + current_case: dict | None = None + current_list: list[str] | None = None + + for raw_line in EVAL_CASES.read_text(encoding="utf-8").splitlines(): + line = raw_line.rstrip() + stripped = line.strip() + if not stripped: + continue + if stripped.startswith("version:"): + result["version"] = int(stripped.split(":", maxsplit=1)[1].strip()) + continue + if stripped.startswith("skill:"): + result["skill"] = _parse_quoted_value(stripped.split(":", maxsplit=1)[1]) + continue + if stripped == "cases:": + continue + if stripped.startswith("- id:"): + current_case = {"expected": {"must_include": [], "must_not_include": []}} + result["cases"].append(current_case) + current_case["id"] = _parse_quoted_value(stripped.split(":", maxsplit=1)[1]) + current_list = None + continue + if current_case is None: + continue + if stripped.startswith("category:"): + current_case["category"] = _parse_quoted_value( + stripped.split(":", maxsplit=1)[1] + ) + continue + if stripped.startswith("prompt:"): + current_case["prompt"] = _parse_quoted_value( + stripped.split(":", maxsplit=1)[1] + ) + continue + if stripped == "expected:": + continue + if stripped == "must_include:": + current_list = current_case["expected"]["must_include"] + continue + if stripped == "must_not_include:": + current_list = current_case["expected"]["must_not_include"] + continue + if stripped.startswith("- "): + assert current_list is not None + current_list.append(_parse_quoted_value(stripped[2:])) + + return result + + +def _load_eval_cases() -> dict: + try: + from ruamel.yaml import YAML + except ModuleNotFoundError: + return _load_eval_cases_without_yaml_parser() + + return YAML(typ="safe").load(EVAL_CASES) + + +def test_ptb_skill_resources_are_available(): + expected_files = [ + "SKILL.md", + "agents/openai.yaml", + "references/coding-guidelines.md", + "references/common-workflows.md", + "references/nox-sessions.md", + "references/source-routing.md", + ] + + for expected in expected_files: + assert (SKILL / expected).is_file() + + +def test_ptb_skill_resources_are_packaged(): + pyproject = tomllib.loads((PROJECT_ROOT / "pyproject.toml").read_text()) + includes = pyproject["tool"]["poetry"]["include"] + + assert "exasol/toolbox/skills/**/*" in includes + + +def test_ptb_skill_frontmatter_is_complete(): + content = (SKILL / "SKILL.md").read_text(encoding="utf-8") + frontmatter = content.split("---", maxsplit=2)[1] + + assert "name: exasol-python-toolbox" in frontmatter + assert "description: Use this skill in Exasol Python projects" in frontmatter + assert "[TODO" not in content + + +def test_ptb_skill_has_no_main_branch_metadata(): + forbidden = [ + "main-branch", + "main branch", + "inventory", + "source-map", + ] + content = "\n".join( + path.read_text(encoding="utf-8") for path in SKILL.rglob("*") if path.is_file() + ).lower() + + for term in forbidden: + assert term not in content + + +def test_ptb_skill_has_no_duplicate_markdown_lines(): + for path in SKILL.rglob("*.md"): + seen = {} + for line_number, line in enumerate( + path.read_text(encoding="utf-8").splitlines(), 1 + ): + normalized = line.strip().lower() + if ( + not normalized + or normalized in {"---", "```bash", "```"} + or normalized.startswith("|") + ): + continue + assert normalized not in seen, ( + f"{path} duplicates line {seen[normalized]} at line {line_number}: " + f"{line}" + ) + seen[normalized] = line_number + + +def test_nox_command_syntax_is_only_in_nox_session_reference(): + nox_reference = SKILL / "references" / "nox-sessions.md" + for path in SKILL.rglob("*"): + if not path.is_file() or path == nox_reference: + continue + + content = path.read_text(encoding="utf-8") + assert "poetry run -- nox -s" not in content + assert "poetry run -- nox -l" not in content + + +def test_ptb_skill_eval_cases_are_valid(): + eval_cases = _load_eval_cases() + + assert eval_cases["version"] == 1 + assert eval_cases["skill"] == "exasol-python-toolbox" + assert 6 <= len(eval_cases["cases"]) <= 8 + + ids = [case["id"] for case in eval_cases["cases"]] + assert len(ids) == len(set(ids)) + + for case in eval_cases["cases"]: + assert case["id"] + assert case["category"] + assert case["prompt"] + assert case["expected"]["must_include"] + assert case["expected"]["must_not_include"] + + +def test_ptb_skill_eval_cases_cover_ticket_scope(): + eval_cases = _load_eval_cases() + categories = {case["category"] for case in eval_cases["cases"]} + + assert { + "setup", + "quality", + "release", + "update", + "workflow", + "source-routing", + }.issubset(categories) + + +def test_ptb_skill_eval_cases_do_not_define_llm_ci_execution(): + content = EVAL_CASES.read_text(encoding="utf-8").lower() + + forbidden = [ + "model:", + "api_key", + "openai", + "chatgpt", + "codex", + "temperature", + ] + + for term in forbidden: + assert term not in content From 5d1164cb688b5b98d93d6d6611fc3398a19ea462 Mon Sep 17 00:00:00 2001 From: jana-selva Date: Thu, 30 Jul 2026 11:39:28 +0530 Subject: [PATCH 2/6] Remove optional skill UI metadata --- .../toolbox/skills/exasol-python-toolbox/agents/openai.yaml | 4 ---- test/unit/skills_test.py | 1 - 2 files changed, 5 deletions(-) delete mode 100644 exasol/toolbox/skills/exasol-python-toolbox/agents/openai.yaml diff --git a/exasol/toolbox/skills/exasol-python-toolbox/agents/openai.yaml b/exasol/toolbox/skills/exasol-python-toolbox/agents/openai.yaml deleted file mode 100644 index b830f708e..000000000 --- a/exasol/toolbox/skills/exasol-python-toolbox/agents/openai.yaml +++ /dev/null @@ -1,4 +0,0 @@ -interface: - display_name: "Exasol Python Toolbox" - short_description: "Use PTB nox sessions" - default_prompt: "Use $exasol-python-toolbox to select the correct PTB nox session." diff --git a/test/unit/skills_test.py b/test/unit/skills_test.py index b1fda913e..30b18294e 100644 --- a/test/unit/skills_test.py +++ b/test/unit/skills_test.py @@ -81,7 +81,6 @@ def _load_eval_cases() -> dict: def test_ptb_skill_resources_are_available(): expected_files = [ "SKILL.md", - "agents/openai.yaml", "references/coding-guidelines.md", "references/common-workflows.md", "references/nox-sessions.md", From df3ab2d7c5bcab5d6c9dbd4f6128e2adcd5953a2 Mon Sep 17 00:00:00 2001 From: jana-selva Date: Thu, 30 Jul 2026 11:52:51 +0530 Subject: [PATCH 3/6] tomllib for python versions --- test/unit/skills_test.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/unit/skills_test.py b/test/unit/skills_test.py index 30b18294e..ca03f3702 100644 --- a/test/unit/skills_test.py +++ b/test/unit/skills_test.py @@ -1,7 +1,11 @@ -import tomllib from ast import literal_eval from pathlib import Path +try: + import tomllib +except ModuleNotFoundError: + import tomli as tomllib + PROJECT_ROOT = Path(__file__).parents[2] SKILL = PROJECT_ROOT / "exasol" / "toolbox" / "skills" / "exasol-python-toolbox" EVAL_CASES = ( From 405d9ec7dfb8fffc610c0b3f394a33f7d498cc48 Mon Sep 17 00:00:00 2001 From: jana-selva Date: Fri, 31 Jul 2026 16:31:19 +0530 Subject: [PATCH 4/6] Add PTB agent skill guidance and review fixes(only few fixes.) --- .../references/coding-guidelines.md | 21 ++++++- .../references/common-workflows.md | 63 ++++++++----------- .../references/source-routing.md | 4 +- pyproject.toml | 4 +- test/unit/skills_test.py | 59 +++++++++++------ 5 files changed, 88 insertions(+), 63 deletions(-) diff --git a/exasol/toolbox/skills/exasol-python-toolbox/references/coding-guidelines.md b/exasol/toolbox/skills/exasol-python-toolbox/references/coding-guidelines.md index 8aef02001..a47746a14 100644 --- a/exasol/toolbox/skills/exasol-python-toolbox/references/coding-guidelines.md +++ b/exasol/toolbox/skills/exasol-python-toolbox/references/coding-guidelines.md @@ -14,8 +14,22 @@ one instruction in one sentence when possible. Use consistent terms. - `doc/user_guide/features/formatting_code/index.rst`: format tools and format sessions. - `doc/user_guide/features/creating_a_release.rst`: release procedure. +- `doc/user_guide/features/github_workflows/index.rst`: workflow types, + maintained workflows, and custom workflows. +- `doc/user_guide/features/github_workflows/create_and_update.rst`: workflow + generation, update, and check commands. +- `doc/user_guide/features/github_workflows/workflow_patcher.rst`: supported + `.workflow-patcher.yml` configuration. +- `doc/user_guide/features/github_workflows/workflow_variables.rst`: workflow + template values, custom workflow secrets, permissions, and matrix behavior. +- PEP 8: Python style guidance: + `https://peps.python.org/pep-0008/` +- Google Python Style Guide: Python coding guidance: + `https://google.github.io/styleguide/pyguide.html` - Exasol Python styleguide tooling reference: - `https://exasol.github.io/python-styleguide/guides/tooling.html` + `https://exasol.github.io/python-styleguide/guides/tooling.html`. Use it as + a PTB tooling reference. Do not use it as the final official Exasol coding + guideline until the team defines and maintains one. ## Design rules to keep @@ -39,8 +53,9 @@ one instruction in one sentence when possible. Use consistent terms. Use PTB sessions to format and validate code. -When you change public operation, update the related tests. Update -`doc/changes/unreleased.md` when the project requires a changelog entry. +When you change public operation, update the related tests. For almost all +non-automated changes, add an entry to `doc/changes/unreleased.md`. The usual +exception is a dependency update PR that CI creates automatically. For workflow-template changes, check generated workflows. Generate workflows only when you intend to change them. Use `nox-sessions.md` for command syntax. diff --git a/exasol/toolbox/skills/exasol-python-toolbox/references/common-workflows.md b/exasol/toolbox/skills/exasol-python-toolbox/references/common-workflows.md index fb68e8b74..aa886fbf7 100644 --- a/exasol/toolbox/skills/exasol-python-toolbox/references/common-workflows.md +++ b/exasol/toolbox/skills/exasol-python-toolbox/references/common-workflows.md @@ -39,6 +39,8 @@ For an existing project: If a project cannot use a PTB session without changes, use a local override only as a short-term migration step. For a permanent solution, use configuration, plugin hooks, or PTB extension points. +When you add a temporary local override, create an issue in the project to +replace it with configuration, a plugin hook, or a PTB extension point later. ## Make a daily code change @@ -53,16 +55,14 @@ configuration, plugin hooks, or PTB extension points. When you remove or rename a documentation file, update each Sphinx `toctree` that points to it. Then run `docs:build`. -Put more Pytest arguments after `--`. -Use `nox-sessions.md` for examples. - ## Fix lint or format findings Run the PTB formatter first. Then run the checks that match the failure. Use `nox-sessions.md` for the exact session commands. Do not adjust code manually to imitate Black, isort, or Ruff. Let PTB run these -tools. Use PTB configuration to exclude a file or directory. +tools. To exclude a Python file or directory, use +`PROJECT_CONFIG.add_to_excluded_python_paths` in `noxconfig.py`. ## Prepare a release @@ -71,16 +71,19 @@ Prepare a release with `release:prepare`. This session does these actions: 1. It updates the version. -2. It moves content from `doc/changes/unreleased.md` to a versioned changes file. -3. It updates `doc/changes/changelog.md`. -4. It runs configured release hooks. -5. It commits the changes if you do not use `--no-add`. -6. It opens a PR if you do not use `--no-pr`. +2. It creates a release branch and sets it as the current Git branch unless you + use `--no-branch` or `--no-add`. +3. It moves content from `doc/changes/unreleased.md` to a versioned changes file. +4. It updates `doc/changes/changelog.md`. +5. It runs configured release hooks. +6. It commits the changes if you do not use `--no-add`. +7. It opens a PR if you do not use `--no-pr`. Useful flags: -- `--no-branch`: Do not create or switch to a release branch. -- `--no-add`: Do not add or commit changes. +- `--no-branch`: Do not create a release branch and do not set it as current. +- `--no-add`: Do not add or commit changes. This also prevents release branch + creation. - `--no-pr`: Do not create a pull request. If dependencies change after release preparation, update the versioned changes @@ -89,8 +92,8 @@ file with `release:update`. After the release PR is merged, apply the release safety rule from `SKILL.md`. `release:trigger` checks out the default branch. It pulls the default branch. It -creates a version tag. It pushes the tag. It can update a `v` tag if -`PROJECT_CONFIG` enables this function. +creates a version tag. It pushes the tag. It updates a `v` tag if +`PROJECT_CONFIG.create_major_version_tags` is `True`. ## Update PTB or dependencies @@ -109,26 +112,14 @@ sessions. ## Maintain GitHub workflows -PTB ships workflow templates. Generate workflows instead of editing generated -files manually. - -Use the PTB workflow sessions. - -Use `.workflow-patcher.yml` for supported project-specific workflow changes. If -multiple Exasol Python projects need the same change, consider a PTB template, -configuration field, or hook. - -When you change workflow templates, custom workflows, or `.workflow-patcher.yml`, -check these items after generation: - -- No `needs` entry points to a removed job. -- Required root-level and job-level `permissions` stay in the generated - workflow. -- Scalar permission forms, for example `read-all` and `write-all`, are handled - or rejected with a clear error. -- Release guards, for example tag checks, still run before release or extension - jobs. -- Self-release workflows use an action reference that exists before the first - release tag is pushed. -- Project tools run in the Poetry environment when the project dependencies are - installed there. +PTB has two workflow groups: + +- PTB-provided workflows: Do not edit generated files by hand. Use + `.workflow-patcher.yml` for supported project-specific changes. Use + `PROJECT_CONFIG`, a PTB template, or a hook for shared behavior. Then run + `workflow:generate` and `workflow:check`. +- Custom workflows: The project owns these files. Edit the custom file directly. + Put GitHub `permissions` in the jobs that need them. Declare reusable-workflow + secrets under `on.workflow_call.secrets`. + +After workflow changes, run `workflow:audit`. diff --git a/exasol/toolbox/skills/exasol-python-toolbox/references/source-routing.md b/exasol/toolbox/skills/exasol-python-toolbox/references/source-routing.md index 1a3c15889..f95ef7734 100644 --- a/exasol/toolbox/skills/exasol-python-toolbox/references/source-routing.md +++ b/exasol/toolbox/skills/exasol-python-toolbox/references/source-routing.md @@ -23,8 +23,8 @@ Read the source file before you explain a detailed rule. - `exasol/toolbox/nox/tasks.py`: exported standard sessions and `project:check`. - `exasol/toolbox/nox/_format.py`: `format:fix` and `format:check`. -- `exasol/toolbox/nox/_lint.py`: `lint:code`, `lint:typing`, - `lint:security`, and deprecated `lint:dependencies`. +- `exasol/toolbox/nox/_lint.py`: `lint:code`, `lint:typing`, and + `lint:security`. - `exasol/toolbox/nox/_test.py`: `test:unit`, `test:integration`, and `test:coverage`. - `exasol/toolbox/nox/_documentation.py`: documentation, link check, and diff --git a/pyproject.toml b/pyproject.toml index e42826c30..f5a92a47a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,9 +82,7 @@ packages = [ include = [ "README.rst", "doc/changes/changelog.md", - "LICENSE", - "exasol/toolbox/templates/**/*", - "exasol/toolbox/skills/**/*" + "LICENSE" ] [project.urls] diff --git a/test/unit/skills_test.py b/test/unit/skills_test.py index ca03f3702..86f3aca2a 100644 --- a/test/unit/skills_test.py +++ b/test/unit/skills_test.py @@ -1,13 +1,17 @@ from ast import literal_eval from pathlib import Path - -try: - import tomllib -except ModuleNotFoundError: - import tomli as tomllib +from subprocess import run +from zipfile import ZipFile PROJECT_ROOT = Path(__file__).parents[2] SKILL = PROJECT_ROOT / "exasol" / "toolbox" / "skills" / "exasol-python-toolbox" +SKILL_FILES = [ + "SKILL.md", + "references/coding-guidelines.md", + "references/common-workflows.md", + "references/nox-sessions.md", + "references/source-routing.md", +] EVAL_CASES = ( PROJECT_ROOT / "test" @@ -83,23 +87,40 @@ def _load_eval_cases() -> dict: def test_ptb_skill_resources_are_available(): - expected_files = [ - "SKILL.md", - "references/coding-guidelines.md", - "references/common-workflows.md", - "references/nox-sessions.md", - "references/source-routing.md", - ] - - for expected in expected_files: + for expected in SKILL_FILES: assert (SKILL / expected).is_file() -def test_ptb_skill_resources_are_packaged(): - pyproject = tomllib.loads((PROJECT_ROOT / "pyproject.toml").read_text()) - includes = pyproject["tool"]["poetry"]["include"] - - assert "exasol/toolbox/skills/**/*" in includes +def test_ptb_skill_resources_are_packaged(tmp_path): + build_output = tmp_path / "dist" + result = run( + [ + "poetry", + "build", + "--project", + str(PROJECT_ROOT), + "--format", + "wheel", + "--output", + str(build_output), + ], + check=False, + capture_output=True, + text=True, + ) + + assert result.returncode == 0, result.stdout + result.stderr + + wheels = list(build_output.glob("*.whl")) + assert len(wheels) == 1 + + with ZipFile(wheels[0]) as wheel: + wheel_files = set(wheel.namelist()) + + expected_files = { + f"exasol/toolbox/skills/exasol-python-toolbox/{path}" for path in SKILL_FILES + } + assert expected_files <= wheel_files def test_ptb_skill_frontmatter_is_complete(): From 77ccd62558bae7b1f346c9eb6609231e31e1708a Mon Sep 17 00:00:00 2001 From: jana-selva Date: Fri, 31 Jul 2026 16:34:24 +0530 Subject: [PATCH 5/6] removed lint:dependencies --- .../exasol-python-toolbox/references/nox-sessions.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/exasol/toolbox/skills/exasol-python-toolbox/references/nox-sessions.md b/exasol/toolbox/skills/exasol-python-toolbox/references/nox-sessions.md index 262b816e0..fb83afab7 100644 --- a/exasol/toolbox/skills/exasol-python-toolbox/references/nox-sessions.md +++ b/exasol/toolbox/skills/exasol-python-toolbox/references/nox-sessions.md @@ -18,13 +18,12 @@ The sessions below match the PTB version that includes this skill. | `lint:code` | Run static code analysis. | It runs Pylint on `PROJECT_CONFIG.source_code_path` and writes `.lint.json`. | | `lint:typing` | Run type checks. | It runs Mypy on filtered project Python files. | | `lint:security` | Run security lint. | It runs Bandit and writes `.security.json`. | -| `lint:dependencies` | Check legacy dependency sources. | This session is deprecated. It is scheduled for removal on 2026-10-08. Do not add new use. | ## Test sessions | Session | Use | Notes | | --- | --- | --- | -| `test:unit` | Run unit tests. | It runs `pytest -v test/unit`. Put Pytest arguments after `--`. | +| `test:unit` | Run unit tests. | It runs `pytest -v test/unit`. Put Pytest options after `--`; use `-k` to select tests by name. | | `test:integration` | Run integration tests. | It runs `pytest -v test/integration`. It supports PTB integration test hooks. | | `test:coverage` | Run all tests with coverage. | It runs unit and integration tests with coverage. It prints `coverage report -m`. | @@ -33,7 +32,7 @@ Test command examples: ```bash poetry run -- nox -s test:unit poetry run -- nox -s test:unit -- --coverage -poetry run -- nox -s test:unit -- test/unit/path_test.py -k scenario +poetry run -- nox -s test:unit -- -k scenario poetry run -- nox -s test:integration -- --db-version 8.34.0 ``` @@ -71,7 +70,7 @@ poetry run -- nox -s release:update | --- | --- | --- | | `workflow:check` | Check generated workflows. | It requires one workflow name or `all`. It fails when files differ from PTB templates. | | `workflow:generate` | Generate or update workflows. | It requires one workflow name or `all`. | -| `workflow:audit` | Audit workflows and actions. | It runs zizmor with PTB configuration. It sends more zizmor arguments to zizmor. | +| `workflow:audit` | Audit workflows and actions. | It runs zizmor with PTB configuration. It can pass `--fix` to zizmor to apply automatic fixes. | Workflow command examples: From 649543cf38c2a14f25ce6dce8a4a030bd10c2390 Mon Sep 17 00:00:00 2001 From: jana-selva Date: Fri, 31 Jul 2026 17:44:03 +0530 Subject: [PATCH 6/6] Address PTB skill review comments --- .../skills/exasol-python-toolbox/SKILL.md | 13 ++- .../references/coding-guidelines.md | 2 + .../references/common-workflows.md | 28 +++++-- .../references/nox-sessions.md | 7 +- .../references/source-routing.md | 45 ++++++---- exasol/toolbox/util/skills.py | 29 +++++++ .../exasol-python-toolbox/eval_cases.yml | 25 ++++-- test/unit/skills_test.py | 82 ++++--------------- 8 files changed, 128 insertions(+), 103 deletions(-) create mode 100644 exasol/toolbox/util/skills.py diff --git a/exasol/toolbox/skills/exasol-python-toolbox/SKILL.md b/exasol/toolbox/skills/exasol-python-toolbox/SKILL.md index cbb87df19..e9d6ee49a 100644 --- a/exasol/toolbox/skills/exasol-python-toolbox/SKILL.md +++ b/exasol/toolbox/skills/exasol-python-toolbox/SKILL.md @@ -49,5 +49,16 @@ change or explain exact PTB operation. ## Release safety `release:trigger` creates git tags. It pushes the tags. Run it only when the -user asks you to trigger a release. Check the repository state first. +user asks you to trigger a release. + +Before you run `release:trigger`: + +1. Confirm that `release:prepare` ran and its PR is merged. +2. Confirm that the current branch is the default branch. +3. Confirm that the current branch is at the latest remote commit. +4. Confirm that the current project version has no git tag and no GitHub + release. +5. Confirm that the current project version is newer than the latest release, + unless the user confirms a different release plan. + Use `references/common-workflows.md` for the release workflow. diff --git a/exasol/toolbox/skills/exasol-python-toolbox/references/coding-guidelines.md b/exasol/toolbox/skills/exasol-python-toolbox/references/coding-guidelines.md index a47746a14..9d66d84d1 100644 --- a/exasol/toolbox/skills/exasol-python-toolbox/references/coding-guidelines.md +++ b/exasol/toolbox/skills/exasol-python-toolbox/references/coding-guidelines.md @@ -14,6 +14,8 @@ one instruction in one sentence when possible. Use consistent terms. - `doc/user_guide/features/formatting_code/index.rst`: format tools and format sessions. - `doc/user_guide/features/creating_a_release.rst`: release procedure. +- `doc/user_guide/features/git_hooks/index.rst`: pre-commit and pre-push + hooks. - `doc/user_guide/features/github_workflows/index.rst`: workflow types, maintained workflows, and custom workflows. - `doc/user_guide/features/github_workflows/create_and_update.rst`: workflow diff --git a/exasol/toolbox/skills/exasol-python-toolbox/references/common-workflows.md b/exasol/toolbox/skills/exasol-python-toolbox/references/common-workflows.md index aa886fbf7..f08b2569e 100644 --- a/exasol/toolbox/skills/exasol-python-toolbox/references/common-workflows.md +++ b/exasol/toolbox/skills/exasol-python-toolbox/references/common-workflows.md @@ -55,6 +55,16 @@ replace it with configuration, a plugin hook, or a PTB extension point later. When you remove or rename a documentation file, update each Sphinx `toctree` that points to it. Then run `docs:build`. +## Use git hooks + +If `.pre-commit-config.yaml` exists, the project can run hooks on commit and +push. + +- On commit, pre-commit hooks can apply format fixes. Add the changed files and + commit again. +- On push, pre-push hooks can run checks such as type checks and lint checks. + Fix failures and push again. + ## Fix lint or format findings Run the PTB formatter first. Then run the checks that match the failure. @@ -100,11 +110,14 @@ creates a version tag. It pushes the tag. It updates a `v` tag if When you update `exasol-toolbox` in a project: 1. Read the PTB changelog for migration notes. -2. Update the dependency with Poetry. Example: +2. Check the `exasol-toolbox` version range in `pyproject.toml`. +3. Update the dependency with Poetry. Example: `poetry update exasol-toolbox` -3. Generate or check PTB-managed workflows. -4. Run necessary validation sessions. -5. Update `doc/changes/unreleased.md` when the project requires a changelog +4. If Poetry does not update PTB, adjust the version range intentionally. This + is usually necessary for a new PTB major version. +5. Generate or check PTB-managed workflows. +6. Run necessary validation sessions. +7. Update `doc/changes/unreleased.md` when the project requires a changelog entry. For dependency updates that fix vulnerabilities, use the PTB vulnerability @@ -119,7 +132,10 @@ PTB has two workflow groups: `PROJECT_CONFIG`, a PTB template, or a hook for shared behavior. Then run `workflow:generate` and `workflow:check`. - Custom workflows: The project owns these files. Edit the custom file directly. - Put GitHub `permissions` in the jobs that need them. Declare reusable-workflow - secrets under `on.workflow_call.secrets`. + Prefer workflow extension files that PTB can call from a PTB-provided + workflow. Use a separate workflow trigger only when the project owns the merge + protection decision for that workflow. Put GitHub `permissions` in the jobs + that need them. Declare reusable-workflow secrets under + `on.workflow_call.secrets`. After workflow changes, run `workflow:audit`. diff --git a/exasol/toolbox/skills/exasol-python-toolbox/references/nox-sessions.md b/exasol/toolbox/skills/exasol-python-toolbox/references/nox-sessions.md index fb83afab7..c22662e9a 100644 --- a/exasol/toolbox/skills/exasol-python-toolbox/references/nox-sessions.md +++ b/exasol/toolbox/skills/exasol-python-toolbox/references/nox-sessions.md @@ -80,7 +80,7 @@ poetry run -- nox -s workflow:generate -- checks poetry run -- nox -s workflow:audit -- --fix=safe ``` -## Dependency, artifact, package, and matrix sessions +## Dependency, package, and matrix sessions | Session | Use | Notes | | --- | --- | --- | @@ -89,11 +89,6 @@ poetry run -- nox -s workflow:audit -- --fix=safe | `vulnerabilities:update` | Update vulnerable dependencies. | It can write a JSON report file inside the repository. | | `vulnerabilities:resolved` | Report vulnerabilities that are resolved since the latest tag. | Use it during release preparation. | | `dependency:sbom` | Generate SBOM files. | It writes `bom.cdx.json` and `bom.spdx.json`. | -| `artifacts:validate` | Validate CI artifacts. | It checks `.coverage`, `.lint.json`, and `.security.json`. | -| `artifacts:copy` | Copy and combine CI artifacts. | It requires an artifact directory argument. | | `sonar:check` | Upload artifacts to Sonar. | It uses `SONAR_TOKEN` and prepares `ci-coverage.xml`. | | `package:check` | Validate the package long description. | It runs `poetry build` and `twine check`. | | `matrix:generate` | Print selected config values as JSON arrays. | Use this session for matrix output. | -| `matrix:python` | Print legacy Python matrix output. | This session is deprecated. It is scheduled for removal on 2026-09-15. | -| `matrix:exasol` | Print legacy Exasol matrix output. | This session is deprecated. It is scheduled for removal on 2026-09-15. | -| `matrix:all` | Print legacy Python and Exasol matrix output. | This session is deprecated. It is scheduled for removal on 2026-09-15. | diff --git a/exasol/toolbox/skills/exasol-python-toolbox/references/source-routing.md b/exasol/toolbox/skills/exasol-python-toolbox/references/source-routing.md index f95ef7734..1c9559cf1 100644 --- a/exasol/toolbox/skills/exasol-python-toolbox/references/source-routing.md +++ b/exasol/toolbox/skills/exasol-python-toolbox/references/source-routing.md @@ -21,34 +21,43 @@ Read the source file before you explain a detailed rule. ## PTB nox sessions -- `exasol/toolbox/nox/tasks.py`: exported standard sessions and `project:check`. +- `exasol/toolbox/nox/_artifacts.py`: artifact validation, artifact copy, and + Sonar upload sessions. +- `exasol/toolbox/nox/_dependencies.py`: dependency audit, license, + vulnerability, and SBOM sessions. +- `exasol/toolbox/nox/_documentation.py`: documentation, link check, and + changelog sessions. - `exasol/toolbox/nox/_format.py`: `format:fix` and `format:check`. - `exasol/toolbox/nox/_lint.py`: `lint:code`, `lint:typing`, and `lint:security`. -- `exasol/toolbox/nox/_test.py`: `test:unit`, `test:integration`, and - `test:coverage`. -- `exasol/toolbox/nox/_documentation.py`: documentation, link check, and - changelog sessions. +- `exasol/toolbox/nox/_matrix.py`: matrix output sessions for CI usage. +- `exasol/toolbox/nox/_package.py`: package validation. - `exasol/toolbox/nox/_release.py`: release preparation, release update, and release trigger sessions. +- `exasol/toolbox/nox/_test.py`: `test:unit`, `test:integration`, and + `test:coverage`. - `exasol/toolbox/nox/_workflow.py`: workflow check, generation, and audit sessions. -- `exasol/toolbox/nox/_dependencies.py`: dependency audit, license, - vulnerability, and SBOM sessions. -- `exasol/toolbox/nox/_artifacts.py`: artifact validation, artifact copy, and - Sonar upload sessions. -- `exasol/toolbox/nox/_matrix.py`: matrix output sessions. -- `exasol/toolbox/nox/_package.py`: package validation. -- `exasol/toolbox/nox/_shared.py`: shared nox helpers. + +## PTB nox support files + +- `exasol/toolbox/nox/_shared.py`: old shared nox helpers. Prefer + `exasol/toolbox/util/` for new code that more than one session uses. +- `exasol/toolbox/nox/tasks.py`: exported nox session list. Import new nox + session modules here so projects that use PTB can import them with + `from exasol.toolbox.nox.tasks import *`. Do not add new session code here. ## PTB tools +Do not add new functions under `exasol/toolbox/tools/`. Put reusable code in +`exasol/toolbox/util/`. Prefer a nox session for new user-facing operations. + +- `exasol/toolbox/tools/issue.py`: issue template CLI. +- `exasol/toolbox/tools/replace_version.py`: version replacement helpers. +- `exasol/toolbox/tools/security.py`: security issue conversion and creation. - `exasol/toolbox/tools/tbx.py`: CLI root. - `exasol/toolbox/tools/template.py`: template list, show, diff, install, and update helpers. -- `exasol/toolbox/tools/issue.py`: issue template CLI. -- `exasol/toolbox/tools/security.py`: security issue conversion and creation. -- `exasol/toolbox/tools/replace_version.py`: version replacement helpers. ## Workflow templates and helpers @@ -64,12 +73,12 @@ Read the source file before you explain a detailed rule. ## Release and dependency helpers -- `exasol/toolbox/util/release/`: changelog, release notes, and Cookiecutter - version helpers. - `exasol/toolbox/util/dependencies/`: dependency audit, license report, dependency change, vulnerability tracking, and dependency update helpers. -- `exasol/toolbox/util/version.py`: version parsing and version upgrade logic. - `exasol/toolbox/util/git.py`: git helper functions. +- `exasol/toolbox/util/release/`: changelog, release notes, and Cookiecutter + version helpers. +- `exasol/toolbox/util/version.py`: version parsing and version upgrade logic. ## Documentation and project template diff --git a/exasol/toolbox/util/skills.py b/exasol/toolbox/util/skills.py new file mode 100644 index 000000000..e18c83b87 --- /dev/null +++ b/exasol/toolbox/util/skills.py @@ -0,0 +1,29 @@ +from collections.abc import Mapping +from pathlib import Path +from typing import Final + +import importlib_resources as resources + +SKILLS_DIRECTORY: Final = "exasol.toolbox.skills" +PTB_SKILL_NAME: Final = "exasol-python-toolbox" + + +def get_skill_path(skill_name: str = PTB_SKILL_NAME) -> Path: + """ + Return the path to a packaged skill. + """ + return Path(str(resources.files(SKILLS_DIRECTORY) / skill_name)) + + +def get_skill_files(skill_name: str = PTB_SKILL_NAME) -> Mapping[str, Path]: + """ + Return packaged skill files. + + The keys are paths relative to the skill root. + """ + skill_path = get_skill_path(skill_name) + return { + str(path.relative_to(skill_path)): path + for path in skill_path.rglob("*") + if path.is_file() + } diff --git a/test/resources/skills/exasol-python-toolbox/eval_cases.yml b/test/resources/skills/exasol-python-toolbox/eval_cases.yml index 31c107e04..42b2d0e68 100644 --- a/test/resources/skills/exasol-python-toolbox/eval_cases.yml +++ b/test/resources/skills/exasol-python-toolbox/eval_cases.yml @@ -26,17 +26,19 @@ cases: - "run Black directly" - "run isort directly" - - id: "run-targeted-unit-test" - category: "test" - prompt: "I changed one unit test file. Tell me how to run a targeted unit test." + - id: "run-local-quality-check" + category: "quality" + prompt: "I changed code in a PTB project. Tell me the local checks before commit and push." expected: must_include: - - "test:unit" - - "--" + - ".pre-commit-config.yaml" + - "pre-commit" + - "pre-push" + - "project:check" - "nox-sessions.md" must_not_include: - "run Pytest directly" - - "project:check first" + - "test/unit/path_test.py" - id: "prepare-release" category: "release" @@ -57,7 +59,11 @@ cases: expected: must_include: - "release:trigger" - - "Check the repository state" + - "default branch" + - "latest remote commit" + - "no git tag" + - "no GitHub release" + - "latest release" - "user asks" must_not_include: - "run without approval" @@ -68,11 +74,13 @@ cases: prompt: "A generated or patched GitHub workflow changed in a PTB project. Tell me what to do." expected: must_include: + - "PTB-provided workflows" + - "Custom workflows" - "workflow:check" - "workflow:generate" - ".workflow-patcher.yml" - - "needs" - "permissions" + - "on.workflow_call.secrets" must_not_include: - "edit generated workflow files first" - "manual workflow edit first" @@ -82,6 +90,7 @@ cases: prompt: "Update exasol-toolbox in a PTB project. Tell me the validation steps." expected: must_include: + - "pyproject.toml" - "poetry update exasol-toolbox" - "workflow:generate" - "workflow:check" diff --git a/test/unit/skills_test.py b/test/unit/skills_test.py index 86f3aca2a..56c4980b5 100644 --- a/test/unit/skills_test.py +++ b/test/unit/skills_test.py @@ -1,10 +1,17 @@ -from ast import literal_eval from pathlib import Path from subprocess import run from zipfile import ZipFile +from ruamel.yaml import YAML + +from exasol.toolbox.util.skills import ( + PTB_SKILL_NAME, + get_skill_files, + get_skill_path, +) + PROJECT_ROOT = Path(__file__).parents[2] -SKILL = PROJECT_ROOT / "exasol" / "toolbox" / "skills" / "exasol-python-toolbox" +SKILL = get_skill_path(PTB_SKILL_NAME) SKILL_FILES = [ "SKILL.md", "references/coding-guidelines.md", @@ -22,73 +29,16 @@ ) -def _parse_quoted_value(value: str) -> str: - return literal_eval(value.strip()) - - -def _load_eval_cases_without_yaml_parser() -> dict: - result: dict = {"cases": []} - current_case: dict | None = None - current_list: list[str] | None = None - - for raw_line in EVAL_CASES.read_text(encoding="utf-8").splitlines(): - line = raw_line.rstrip() - stripped = line.strip() - if not stripped: - continue - if stripped.startswith("version:"): - result["version"] = int(stripped.split(":", maxsplit=1)[1].strip()) - continue - if stripped.startswith("skill:"): - result["skill"] = _parse_quoted_value(stripped.split(":", maxsplit=1)[1]) - continue - if stripped == "cases:": - continue - if stripped.startswith("- id:"): - current_case = {"expected": {"must_include": [], "must_not_include": []}} - result["cases"].append(current_case) - current_case["id"] = _parse_quoted_value(stripped.split(":", maxsplit=1)[1]) - current_list = None - continue - if current_case is None: - continue - if stripped.startswith("category:"): - current_case["category"] = _parse_quoted_value( - stripped.split(":", maxsplit=1)[1] - ) - continue - if stripped.startswith("prompt:"): - current_case["prompt"] = _parse_quoted_value( - stripped.split(":", maxsplit=1)[1] - ) - continue - if stripped == "expected:": - continue - if stripped == "must_include:": - current_list = current_case["expected"]["must_include"] - continue - if stripped == "must_not_include:": - current_list = current_case["expected"]["must_not_include"] - continue - if stripped.startswith("- "): - assert current_list is not None - current_list.append(_parse_quoted_value(stripped[2:])) - - return result - - def _load_eval_cases() -> dict: - try: - from ruamel.yaml import YAML - except ModuleNotFoundError: - return _load_eval_cases_without_yaml_parser() - return YAML(typ="safe").load(EVAL_CASES) def test_ptb_skill_resources_are_available(): + skill_files = get_skill_files(PTB_SKILL_NAME) + for expected in SKILL_FILES: - assert (SKILL / expected).is_file() + assert expected in skill_files + assert skill_files[expected].is_file() def test_ptb_skill_resources_are_packaged(tmp_path): @@ -118,7 +68,7 @@ def test_ptb_skill_resources_are_packaged(tmp_path): wheel_files = set(wheel.namelist()) expected_files = { - f"exasol/toolbox/skills/exasol-python-toolbox/{path}" for path in SKILL_FILES + f"exasol/toolbox/skills/{PTB_SKILL_NAME}/{path}" for path in SKILL_FILES } assert expected_files <= wheel_files @@ -136,6 +86,8 @@ def test_ptb_skill_has_no_main_branch_metadata(): forbidden = [ "main-branch", "main branch", + "master-branch", + "master branch", "inventory", "source-map", ] @@ -183,6 +135,8 @@ def test_ptb_skill_eval_cases_are_valid(): assert eval_cases["version"] == 1 assert eval_cases["skill"] == "exasol-python-toolbox" + # Keep enough cases to cover the ticket scope, but not so many that the + # deterministic eval file becomes hard to review. assert 6 <= len(eval_cases["cases"]) <= 8 ids = [case["id"] for case in eval_cases["cases"]]