@nitra/cursor 3.4.1 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/package.json +1 -1
- package/rules/python/fix.mjs +19 -0
- package/rules/python/js/tooling.mjs +68 -0
- package/rules/python/lint/lint.mjs +116 -0
- package/rules/python/meta.json +1 -0
- package/rules/python/policy/lint_python_yml/lint_python_yml.rego +59 -0
- package/rules/python/policy/lint_python_yml/target.json +4 -0
- package/rules/python/policy/lint_python_yml/template/lint-python.yml.snippet.yml +41 -0
- package/rules/python/policy/package_json/package_json.rego +16 -0
- package/rules/python/policy/package_json/target.json +4 -0
- package/rules/python/policy/package_json/template/package.json.contains.json +5 -0
- package/rules/python/policy/pyproject_toml/pyproject_toml.rego +39 -0
- package/rules/python/policy/pyproject_toml/target.json +4 -0
- package/rules/python/policy/pyproject_toml/template/pyproject.toml.deny.toml +2 -0
- package/rules/python/policy/pyproject_toml/template/pyproject.toml.snippet.toml +12 -0
- package/rules/python/python.mdc +47 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [3.5.0] - 2026-06-01
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- python: нове правило (uv-only, без Poetry) — автоактивація за pyproject.toml, заборона [tool.poetry]/poetry.lock, PEP 621 [project], lint-python (uv lock --check + uv sync --frozen + опц. ruff auto-fix: check --fix & format, mypy), CI workflow з astral-sh/setup-uv
|
|
8
|
+
|
|
3
9
|
## [3.4.1] - 2026-06-01
|
|
4
10
|
|
|
5
11
|
### Fixed
|
package/package.json
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { isRunAsCli, runRuleCli } from '../../scripts/lib/run-rule-cli.mjs'
|
|
2
|
+
import { runStandardRule } from '../../scripts/lib/run-standard-rule.mjs'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Запускає правило: applies → JS-concerns → policy → mdc-refs (через runStandardRule).
|
|
6
|
+
* Library mode: викликається CLI orchestration через `import + run(ctx)`.
|
|
7
|
+
* @param {import('../../scripts/lib/run-standard-rule.mjs').RuleContext} [ctx] контекст прогону (walkCache тощо)
|
|
8
|
+
* @returns {Promise<number>} 0 — OK, 1 — порушення
|
|
9
|
+
*/
|
|
10
|
+
export function run(ctx) {
|
|
11
|
+
return runStandardRule(import.meta.dirname, ctx)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (isRunAsCli(import.meta.url)) {
|
|
15
|
+
// Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
|
|
16
|
+
// (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
|
|
17
|
+
// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
|
|
18
|
+
process.exit(await runRuleCli(import.meta.dirname))
|
|
19
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Перевіряє FS-вимоги правила python.mdc для Python-проєктів на uv.
|
|
3
|
+
*
|
|
4
|
+
* **Що тут лишилося** (FS-existence — не покривається conftest):
|
|
5
|
+
* - наявність `pyproject.toml` у корені (тригер правила);
|
|
6
|
+
* - наявність `uv.lock` поруч (uv-проєкт коммітить lock-файл);
|
|
7
|
+
* - наявність кореневого `package.json` (для `bun run lint-python`);
|
|
8
|
+
* - наявність `.github/workflows/lint-python.yml`;
|
|
9
|
+
* - заборона Poetry-артефактів `poetry.lock` / `poetry.toml` (міграція на uv).
|
|
10
|
+
*
|
|
11
|
+
* **Що покрила Rego** (`npx \@nitra/cursor fix python`):
|
|
12
|
+
* - `python/pyproject_toml/` — заборона `[tool.poetry]` + вимога PEP 621 `[project].name/version`;
|
|
13
|
+
* - `python/package_json/` — наявність скрипта `lint-python` у `package.json`;
|
|
14
|
+
* - `python/lint_python_yml/` — `uses`/`run`-кроки канонічного workflow.
|
|
15
|
+
*
|
|
16
|
+
* `.venv/` навмисно НЕ перевіряється: uv теж створює `.venv`, тож його наявність
|
|
17
|
+
* не є ознакою Poetry й давала б хибні спрацювання.
|
|
18
|
+
*/
|
|
19
|
+
import { existsSync } from 'node:fs'
|
|
20
|
+
import { join } from 'node:path'
|
|
21
|
+
|
|
22
|
+
import { createCheckReporter } from '../../../scripts/lib/check-reporter.mjs'
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Перевіряє відповідність проєкту правилам python.mdc.
|
|
26
|
+
* @param {string} [cwd] корінь репозиторію (`process.cwd()` у звичайному прогоні)
|
|
27
|
+
* @returns {number} 0 — все OK, 1 — є проблеми
|
|
28
|
+
*/
|
|
29
|
+
export function check(cwd = process.cwd()) {
|
|
30
|
+
const reporter = createCheckReporter()
|
|
31
|
+
const { pass, fail } = reporter
|
|
32
|
+
|
|
33
|
+
if (!existsSync(join(cwd, 'pyproject.toml'))) {
|
|
34
|
+
pass('pyproject.toml не знайдено в корені — правило python не застосовне')
|
|
35
|
+
return reporter.getExitCode()
|
|
36
|
+
}
|
|
37
|
+
pass('pyproject.toml існує — застосовую python.mdc')
|
|
38
|
+
|
|
39
|
+
if (existsSync(join(cwd, 'uv.lock'))) {
|
|
40
|
+
pass('uv.lock є')
|
|
41
|
+
} else {
|
|
42
|
+
fail('uv.lock не знайдено — згенеруй `uv lock` (python.mdc, без Poetry)')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Poetry-артефакти заборонені: uv є єдиним пакет-менеджером (python.mdc).
|
|
46
|
+
for (const poetryFile of ['poetry.lock', 'poetry.toml']) {
|
|
47
|
+
if (existsSync(join(cwd, poetryFile))) {
|
|
48
|
+
fail(`${poetryFile} знайдено — прибери Poetry, мігруй на uv (python.mdc)`)
|
|
49
|
+
} else {
|
|
50
|
+
pass(`${poetryFile} відсутній`)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (existsSync(join(cwd, 'package.json'))) {
|
|
55
|
+
pass('package.json є (наявність lint-python перевіряє fix → python.package_json)')
|
|
56
|
+
} else {
|
|
57
|
+
fail('package.json не знайдено в корені — додай для `bun run lint-python` (python.mdc)')
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const wfPath = '.github/workflows/lint-python.yml'
|
|
61
|
+
if (existsSync(join(cwd, wfPath))) {
|
|
62
|
+
pass(`${wfPath} є (структуру перевіряє fix → python.lint_python_yml)`)
|
|
63
|
+
} else {
|
|
64
|
+
fail(`${wfPath} не існує — створи згідно python.mdc`)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return reporter.getExitCode()
|
|
68
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Запуск `lint-python` за правилом python.mdc на базі [uv](https://docs.astral.sh/uv/).
|
|
3
|
+
*
|
|
4
|
+
* Якщо `pyproject.toml` у корені відсутній — вихід 0 без запуску інструментів.
|
|
5
|
+
* Якщо `pyproject.toml` є, але `uv` не знайдено в PATH — це помилка (uv — єдиний
|
|
6
|
+
* пакет-менеджер, без Poetry).
|
|
7
|
+
*
|
|
8
|
+
* Обовʼязкові кроки (uv):
|
|
9
|
+
* - `uv lock --check` — lock-файл актуальний щодо `pyproject.toml`;
|
|
10
|
+
* - `uv sync --frozen` — середовище зібране строго з `uv.lock`.
|
|
11
|
+
*
|
|
12
|
+
* Опційні лінтери запускаються лише якщо доступні через `uv run` (інакше крок
|
|
13
|
+
* пропускається з повідомленням, як optional vendor-tools у php.mdc). `ruff`
|
|
14
|
+
* запускається в auto-fix-режимі (мутує робоче дерево, як `markdownlint-cli2 --fix`
|
|
15
|
+
* у lint-text / `clippy --fix` у lint-rust):
|
|
16
|
+
* - `uv run ruff check --fix .`
|
|
17
|
+
* - `uv run ruff format .`
|
|
18
|
+
* - `uv run mypy .`
|
|
19
|
+
*
|
|
20
|
+
* Канон патерну `lint-*` (серіалізація через `runStandardLint`, без прямого `withLock`) —
|
|
21
|
+
* `.cursor/rules/scripts.mdc`, секція «Серіалізація важких CLI-команд».
|
|
22
|
+
*/
|
|
23
|
+
import { spawnSync } from 'node:child_process'
|
|
24
|
+
import { existsSync } from 'node:fs'
|
|
25
|
+
import { join } from 'node:path'
|
|
26
|
+
|
|
27
|
+
import { isRunAsCli } from '../../../scripts/cli-entry.mjs'
|
|
28
|
+
import { createCheckReporter } from '../../../scripts/lib/check-reporter.mjs'
|
|
29
|
+
import { resolveCmd } from '../../../scripts/utils/resolve-cmd.mjs'
|
|
30
|
+
import { runStandardLint } from '../../../scripts/lib/run-standard-lint.mjs'
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Запускає CLI-крок і репортить результат.
|
|
34
|
+
* @param {string} label назва кроку для повідомлень
|
|
35
|
+
* @param {string} cmd абсолютний шлях до CLI
|
|
36
|
+
* @param {string[]} args аргументи
|
|
37
|
+
* @param {(msg: string) => void} pass callback pass
|
|
38
|
+
* @param {(msg: string) => void} fail callback fail
|
|
39
|
+
* @returns {boolean} true якщо крок успішний
|
|
40
|
+
*/
|
|
41
|
+
function runTool(label, cmd, args, pass, fail) {
|
|
42
|
+
const r = spawnSync(cmd, args, { stdio: 'inherit', shell: false })
|
|
43
|
+
if (r.status === 0) {
|
|
44
|
+
pass(`lint-python: ${label} — OK`)
|
|
45
|
+
return true
|
|
46
|
+
}
|
|
47
|
+
const code = typeof r.status === 'number' ? r.status : 1
|
|
48
|
+
fail(`lint-python: ${label} — помилка (код ${code}, python.mdc)`)
|
|
49
|
+
return false
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Чи доступний інструмент усередині uv-середовища (`uv run --frozen <tool> --version`).
|
|
54
|
+
* @param {string} uv абсолютний шлях до `uv`
|
|
55
|
+
* @param {string} tool назва бінарника (`ruff`, `mypy`)
|
|
56
|
+
* @returns {boolean} true якщо інструмент відповідає на `--version`
|
|
57
|
+
*/
|
|
58
|
+
function uvToolAvailable(uv, tool) {
|
|
59
|
+
const r = spawnSync(uv, ['run', '--frozen', tool, '--version'], { stdio: 'ignore', shell: false })
|
|
60
|
+
return r.status === 0
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Внутрішні кроки `lint-python` без локу.
|
|
65
|
+
* @param {string} [cwd] корінь репозиторію
|
|
66
|
+
* @returns {number} 0 — OK, 1 — є помилки
|
|
67
|
+
*/
|
|
68
|
+
export function runLintPythonSteps(cwd = process.cwd()) {
|
|
69
|
+
const reporter = createCheckReporter()
|
|
70
|
+
const { pass, fail } = reporter
|
|
71
|
+
|
|
72
|
+
if (!existsSync(join(cwd, 'pyproject.toml'))) {
|
|
73
|
+
pass('lint-python: немає pyproject.toml у корені — кроки Python пропущено')
|
|
74
|
+
return reporter.getExitCode()
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const uv = resolveCmd('uv')
|
|
78
|
+
if (!uv) {
|
|
79
|
+
fail('lint-python: `uv` не знайдено в PATH (потрібен при наявному pyproject.toml, python.mdc)')
|
|
80
|
+
return reporter.getExitCode()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!runTool('uv lock --check', uv, ['lock', '--check'], pass, fail)) return reporter.getExitCode()
|
|
84
|
+
if (!runTool('uv sync --frozen', uv, ['sync', '--frozen'], pass, fail)) return reporter.getExitCode()
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Запускає лінтер через `uv run`, якщо він доступний у середовищі.
|
|
88
|
+
* @param {string} tool назва бінарника
|
|
89
|
+
* @param {string} label назва кроку
|
|
90
|
+
* @param {string[]} args аргументи інструмента
|
|
91
|
+
* @returns {boolean} true, якщо крок успішний або пропущений
|
|
92
|
+
*/
|
|
93
|
+
function runOptionalUvTool(tool, label, args) {
|
|
94
|
+
if (!uvToolAvailable(uv, tool)) {
|
|
95
|
+
pass(`lint-python: ${tool} недоступний у uv-середовищі — крок пропущено`)
|
|
96
|
+
return true
|
|
97
|
+
}
|
|
98
|
+
return runTool(label, uv, ['run', '--frozen', tool, ...args], pass, fail)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!runOptionalUvTool('ruff', 'ruff check --fix', ['check', '--fix', '.'])) return reporter.getExitCode()
|
|
102
|
+
if (!runOptionalUvTool('ruff', 'ruff format', ['format', '.'])) return reporter.getExitCode()
|
|
103
|
+
if (!runOptionalUvTool('mypy', 'mypy', ['.'])) return reporter.getExitCode()
|
|
104
|
+
|
|
105
|
+
return reporter.getExitCode()
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Публічна CLI-форма: серіалізує через `withLock('lint-python')` + дедуп за станом git-дерева.
|
|
110
|
+
* @returns {Promise<number>} код виходу
|
|
111
|
+
*/
|
|
112
|
+
export const runLintPython = () => runStandardLint(import.meta.dirname, runLintPythonSteps)
|
|
113
|
+
|
|
114
|
+
if (isRunAsCli(import.meta.url)) {
|
|
115
|
+
process.exitCode = await runLintPython()
|
|
116
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "auto": { "glob": "pyproject.toml" } }
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Перевірка `.github/workflows/lint-python.yml` для правила python (python.mdc).
|
|
2
|
+
#
|
|
3
|
+
# Канон надходить через --data: { "template": { "snippet": ... } }
|
|
4
|
+
# Структура --data сформована з template/lint-python.yml.snippet.yml.
|
|
5
|
+
# Перевіряємо (drift-safe — усе ведеться з template, без inline-літералів):
|
|
6
|
+
# - кожен `uses` з template (підмножина): actions/checkout@v6,
|
|
7
|
+
# ./.github/actions/setup-bun-deps, astral-sh/setup-uv@v8.0.0;
|
|
8
|
+
# - кожен `run` з template має бути присутнім (як substring) серед run-кроків
|
|
9
|
+
# input'а: `uv sync --frozen`, `bun run lint-python`.
|
|
10
|
+
# Заборона Poetry-кроків (snok/install-poetry, `poetry install`) — через відсутність
|
|
11
|
+
# у каноні: правило вимагає uv-кроки, а нав'язаних poetry-кроків у template немає.
|
|
12
|
+
# Універсальні workflow-перевірки (name, concurrency, branches) — у `ga.workflow_common`.
|
|
13
|
+
package python.lint_python_yml
|
|
14
|
+
|
|
15
|
+
import rego.v1
|
|
16
|
+
|
|
17
|
+
# Усі `uses` з канону workflow (по всіх job'ах template).
|
|
18
|
+
expected_uses contains u if {
|
|
19
|
+
some job in data.template.snippet.jobs
|
|
20
|
+
some step in job.steps
|
|
21
|
+
u := object.get(step, "uses", "")
|
|
22
|
+
u != ""
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
# Усі `uses` з input workflow.
|
|
26
|
+
actual_uses contains u if {
|
|
27
|
+
some job in object.get(input, "jobs", {})
|
|
28
|
+
some step in object.get(job, "steps", [])
|
|
29
|
+
u := object.get(step, "uses", "")
|
|
30
|
+
u != ""
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
# Конкатенація всіх `run`-кроків з input workflow.
|
|
34
|
+
all_run_text := concat("\n", [run_text |
|
|
35
|
+
some job in object.get(input, "jobs", {})
|
|
36
|
+
some step in object.get(job, "steps", [])
|
|
37
|
+
run_text := step_run_to_text(step)
|
|
38
|
+
])
|
|
39
|
+
|
|
40
|
+
deny contains msg if {
|
|
41
|
+
some required_use in expected_uses
|
|
42
|
+
not required_use in actual_uses
|
|
43
|
+
msg := sprintf("lint-python.yml: відсутній step з `uses: %s` (python.mdc)", [required_use])
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
deny contains msg if {
|
|
47
|
+
some job in data.template.snippet.jobs
|
|
48
|
+
some step in job.steps
|
|
49
|
+
expected_run := object.get(step, "run", "")
|
|
50
|
+
expected_run != ""
|
|
51
|
+
not contains(all_run_text, expected_run)
|
|
52
|
+
msg := sprintf("lint-python.yml: жоден крок run не містить %q (python.mdc)", [expected_run])
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
step_run_to_text(step) := step.run if is_string(step.run)
|
|
56
|
+
|
|
57
|
+
else := concat("\n", [s | some s in step.run]) if is_array(step.run)
|
|
58
|
+
|
|
59
|
+
else := ""
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
name: Lint Python
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- dev
|
|
7
|
+
- main
|
|
8
|
+
paths:
|
|
9
|
+
- '**/*.py'
|
|
10
|
+
- 'pyproject.toml'
|
|
11
|
+
- 'uv.lock'
|
|
12
|
+
- '.github/workflows/lint-python.yml'
|
|
13
|
+
|
|
14
|
+
pull_request:
|
|
15
|
+
branches:
|
|
16
|
+
- dev
|
|
17
|
+
- main
|
|
18
|
+
|
|
19
|
+
concurrency:
|
|
20
|
+
group: ${{ github.ref }}-${{ github.workflow }}
|
|
21
|
+
cancel-in-progress: true
|
|
22
|
+
|
|
23
|
+
jobs:
|
|
24
|
+
python:
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
permissions:
|
|
27
|
+
contents: read
|
|
28
|
+
steps:
|
|
29
|
+
- uses: actions/checkout@v6
|
|
30
|
+
with:
|
|
31
|
+
persist-credentials: false
|
|
32
|
+
|
|
33
|
+
- uses: ./.github/actions/setup-bun-deps
|
|
34
|
+
|
|
35
|
+
- uses: astral-sh/setup-uv@v8.0.0
|
|
36
|
+
|
|
37
|
+
- name: Install dependencies (uv)
|
|
38
|
+
run: uv sync --frozen
|
|
39
|
+
|
|
40
|
+
- name: Lint Python
|
|
41
|
+
run: bun run lint-python
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Перевірка `package.json` (python.mdc).
|
|
2
|
+
#
|
|
3
|
+
# Канон надходить через --data: { "template": { "contains": ... } }
|
|
4
|
+
# Структура --data сформована з template/package.json.contains.json.
|
|
5
|
+
# FS-перевірки (наявність `package.json` як такого) — у JS.
|
|
6
|
+
package python.package_json
|
|
7
|
+
|
|
8
|
+
import rego.v1
|
|
9
|
+
|
|
10
|
+
deny contains msg if {
|
|
11
|
+
some script_name, needles in data.template.contains.scripts
|
|
12
|
+
actual := object.get(object.get(input, "scripts", {}), script_name, "")
|
|
13
|
+
some needle in needles
|
|
14
|
+
not contains(actual, needle)
|
|
15
|
+
msg := sprintf("package.json: scripts.%s має містити %q (python.mdc)", [script_name, needle])
|
|
16
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Перевірка `pyproject.toml` (python.mdc).
|
|
2
|
+
#
|
|
3
|
+
# Канон надходить через --data: { "template": { "deny": ... } }
|
|
4
|
+
# Структура --data сформована з template/pyproject.toml.deny.toml.
|
|
5
|
+
# FS-перевірки (`poetry.lock`, `poetry.toml`, `uv.lock`, `package.json`) — у JS.
|
|
6
|
+
#
|
|
7
|
+
# Дві групи правил:
|
|
8
|
+
# 1. Заборона Poetry — `[tool.poetry]` (та інші заборонені під-таблиці `tool`)
|
|
9
|
+
# керується deny-template, drift-safe.
|
|
10
|
+
# 2. PEP 621 — `[project].name` і `[project].version` обовʼязкові (структурна
|
|
11
|
+
# вимога без канонічного літералу, тому inline).
|
|
12
|
+
package python.pyproject_toml
|
|
13
|
+
|
|
14
|
+
import rego.v1
|
|
15
|
+
|
|
16
|
+
# ── Заборона Poetry (deny-template керує переліком) ──────────────────────────
|
|
17
|
+
|
|
18
|
+
deny contains msg if {
|
|
19
|
+
some key, reason in object.get(data.template.deny, "tool", {})
|
|
20
|
+
key in object.keys(object.get(input, "tool", {}))
|
|
21
|
+
msg := sprintf("pyproject.toml: [tool.%s] — %s", [key, reason])
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
# ── PEP 621: обовʼязкові [project].name / [project].version ──────────────────
|
|
25
|
+
|
|
26
|
+
deny contains msg if {
|
|
27
|
+
not project_field_set("name")
|
|
28
|
+
msg := "pyproject.toml: відсутній [project].name (PEP 621 — мігруй з [tool.poetry], python.mdc)"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
deny contains msg if {
|
|
32
|
+
not project_field_set("version")
|
|
33
|
+
msg := "pyproject.toml: відсутній статичний [project].version (PEP 621, python.mdc)"
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
project_field_set(key) if {
|
|
37
|
+
value := object.get(object.get(input, "project", {}), key, "")
|
|
38
|
+
value != ""
|
|
39
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Канонічний цільовий вигляд після міграції з Poetry на uv (PEP 621).
|
|
2
|
+
# Метадані — у [project] (а не [tool.poetry]); dev-залежності додавай через
|
|
3
|
+
# `uv add --dev <pkg>` (uv тримає їх у [dependency-groups].dev або [tool.uv]).
|
|
4
|
+
[project]
|
|
5
|
+
name = "your-package"
|
|
6
|
+
version = "0.1.0"
|
|
7
|
+
requires-python = ">=3.12"
|
|
8
|
+
dependencies = []
|
|
9
|
+
|
|
10
|
+
[build-system]
|
|
11
|
+
requires = ["hatchling"]
|
|
12
|
+
build-backend = "hatchling.build"
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Python — пакет-менеджер uv (без Poetry), PEP 621, lint-python
|
|
3
|
+
globs: "**/*.py,pyproject.toml,uv.lock,.github/workflows/lint-python.yml"
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
version: '1.0'
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Python-проєкти ведуться **виключно** на [uv](https://docs.astral.sh/uv/) — єдиний пакет-менеджер і резолвер. **Poetry заборонено.**
|
|
9
|
+
|
|
10
|
+
- Метадані проєкту — у секції **`[project]`** (PEP 621), а **не** в `[tool.poetry]`.
|
|
11
|
+
- Lock-файл — **`uv.lock`** (коммітиться). `poetry.lock` / `poetry.toml` мають бути відсутні.
|
|
12
|
+
- Залежності: `uv add <pkg>`; dev-залежності: `uv add --dev <pkg>`.
|
|
13
|
+
- Середовище: `uv sync --frozen` (строго з `uv.lock`).
|
|
14
|
+
|
|
15
|
+
`uv` / `ruff` / `mypy` **не** додаються в кореневі `devDependencies` споживача — це окремий toolchain і ставиться через `astral-sh/setup-uv` у CI або локально (як `composer` / `regal`).
|
|
16
|
+
|
|
17
|
+
## Міграція з Poetry на uv
|
|
18
|
+
|
|
19
|
+
1. Прибери `[tool.poetry]` і `poetry.lock` / `poetry.toml`.
|
|
20
|
+
2. Перенеси метадані в `[project]` (name, version, requires-python, dependencies) за PEP 621.
|
|
21
|
+
3. Згенеруй lock: `uv lock` → `uv.lock`.
|
|
22
|
+
4. Dev-залежності: `uv add --dev ruff mypy …`.
|
|
23
|
+
|
|
24
|
+
Канонічний цільовий вигляд `pyproject.toml`: [pyproject.toml.snippet.toml](./policy/pyproject_toml/template/pyproject.toml.snippet.toml)
|
|
25
|
+
|
|
26
|
+
Заборонені під-таблиці `[tool.*]` (Poetry): [pyproject.toml.deny.toml](./policy/pyproject_toml/template/pyproject.toml.deny.toml)
|
|
27
|
+
|
|
28
|
+
## lint-python
|
|
29
|
+
|
|
30
|
+
Інструменти uv-екосистеми не мають єдиного CLI, що сам обходить репозиторій, тому `lint-python` делегується у JS-скрипт-обгортку (як `lint-php`, `lint-docker`).
|
|
31
|
+
|
|
32
|
+
- Канон `package.json#scripts.lint-python` (substring requirement): [package.json.contains.json](./policy/package_json/template/package.json.contains.json)
|
|
33
|
+
|
|
34
|
+
Скрипт `rules/python/lint/lint.mjs`:
|
|
35
|
+
|
|
36
|
+
- якщо `pyproject.toml` у корені відсутній — вихід 0 (перевірка пропущена);
|
|
37
|
+
- якщо `pyproject.toml` є, але `uv` не знайдено в PATH — це помилка;
|
|
38
|
+
- `uv lock --check` і `uv sync --frozen` — обовʼязкові;
|
|
39
|
+
- `uv run ruff check --fix .` + `uv run ruff format .` — auto-fix (мутують робоче дерево, як `markdownlint-cli2 --fix` у lint-text);
|
|
40
|
+
- `uv run mypy .` — статична перевірка типів;
|
|
41
|
+
- усі `ruff`/`mypy`-кроки запускаються лише якщо інструмент доступний у середовищі (інакше крок пропускається з повідомленням).
|
|
42
|
+
|
|
43
|
+
## CI: `.github/workflows/lint-python.yml`
|
|
44
|
+
|
|
45
|
+
- Канон: [lint-python.yml.snippet.yml](./policy/lint_python_yml/template/lint-python.yml.snippet.yml)
|
|
46
|
+
|
|
47
|
+
Без кроків `poetry install` / `snok/install-poetry` — лише `astral-sh/setup-uv@v8.0.0` + `uv sync --frozen`.
|