@7n/rules 1.20.1 → 1.22.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 +16 -0
- package/package.json +1 -1
- package/scripts/lib/docs/resolve-plugins.md +1 -1
- package/skills/taze/js/docs/orchestrate.md +1 -1
- package/skills/taze/js/orchestrate.mjs +175 -64
- package/rules/python/applies/concern.json +0 -4
- package/rules/python/applies/docs/main.md +0 -29
- package/rules/python/applies/main.mjs +0 -24
- package/rules/python/docs/index.md +0 -11
- package/rules/python/lib/docs/index.md +0 -9
- package/rules/python/lib/docs/uv-run.md +0 -31
- package/rules/python/lib/uv-run.mjs +0 -52
- package/rules/python/main.json +0 -1
- package/rules/python/main.mdc +0 -8
- package/rules/python/mypy/concern.json +0 -7
- package/rules/python/mypy/docs/main.md +0 -32
- package/rules/python/mypy/main.mjs +0 -37
- package/rules/python/project/concern.json +0 -6
- package/rules/python/project/docs/main.md +0 -42
- package/rules/python/project/main.mjs +0 -125
- package/rules/python/pyproject_toml/concern.json +0 -9
- package/rules/python/pyproject_toml/pyproject_toml.mdc +0 -29
- package/rules/python/pyproject_toml/pyproject_toml.rego +0 -53
- package/rules/python/pyproject_toml/template/pyproject.toml.deny.toml +0 -2
- package/rules/python/pyproject_toml/template/pyproject.toml.snippet.toml +0 -12
- package/rules/python/ruff/concern.json +0 -7
- package/rules/python/ruff/docs/fix-ruff.md +0 -33
- package/rules/python/ruff/docs/main.md +0 -35
- package/rules/python/ruff/fix-ruff.mjs +0 -65
- package/rules/python/ruff/main.mjs +0 -53
- package/rules/python/tooling/concern.json +0 -15
- package/rules/python/tooling/docs/index.md +0 -11
- package/rules/python/tooling/docs/main.md +0 -35
- package/rules/python/tooling/main.mjs +0 -45
- package/rules/python/tooling/tooling.mdc +0 -22
- package/rules/rust/applies/concern.json +0 -4
- package/rules/rust/applies/docs/index.md +0 -11
- package/rules/rust/applies/docs/main.md +0 -30
- package/rules/rust/applies/main.mjs +0 -28
- package/rules/rust/check/check.mdc +0 -16
- package/rules/rust/check/concern.json +0 -7
- package/rules/rust/check/docs/fix-check.md +0 -30
- package/rules/rust/check/docs/index.md +0 -11
- package/rules/rust/check/docs/main.md +0 -37
- package/rules/rust/check/fix-check.mjs +0 -80
- package/rules/rust/check/main.mjs +0 -87
- package/rules/rust/docs/index.md +0 -11
- package/rules/rust/lib/docs/has-cargo-toml.md +0 -134
- package/rules/rust/lib/docs/index.md +0 -11
- package/rules/rust/lib/has-cargo-toml.mjs +0 -37
- package/rules/rust/main.json +0 -1
- package/rules/rust/main.mdc +0 -8
- package/rules/rust/package_json/concern.json +0 -10
- package/rules/rust/package_json/package_json.mdc +0 -12
- package/rules/rust/package_json/package_json.rego +0 -20
- package/rules/rust/vscode_extensions/concern.json +0 -11
- package/rules/rust/vscode_extensions/docs/fix-vscode_extensions.md +0 -25
- package/rules/rust/vscode_extensions/docs/index.md +0 -11
- package/rules/rust/vscode_extensions/fix-vscode_extensions.mjs +0 -1
- package/rules/rust/vscode_extensions/template/extensions.json.snippet.json +0 -1
- package/rules/rust/vscode_extensions/vscode_extensions.mdc +0 -9
- package/rules/rust/vscode_extensions/vscode_extensions.rego +0 -15
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.22.0] - 2026-07-18
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- taze: по завершенню переносить зміни з автоствореного worktree назад у вихідне дерево (untracked) і прибирає worktree
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- taze: worktree-only гейт сам створює .worktrees/branch-taze і продовжує там замість throw-and-stop
|
|
12
|
+
|
|
13
|
+
## [1.21.0] - 2026-07-18
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- Фаза 3 spec lang-plugins-extraction: lint-правила `rust` і `python` (main.mdc, концерни, rego, шаблони) виїхали з ядра у плагіни `@7n/rules-lang-rust`/`@7n/rules-lang-python` (contributes.rules) — резолвляться через resolveRulesDirs, автодетект/дзеркала/auto-rules працюють без змін для репо з активними плагінами. Mixin-теки CI-плагінів (Rego-gate lint-rust.yml/lint-python.yml) доповнюють правила плагінів-власників, як і раніше
|
|
18
|
+
|
|
3
19
|
## [1.20.1] - 2026-07-18
|
|
4
20
|
|
|
5
21
|
### Changed
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@ type: JS Module
|
|
|
3
3
|
title: resolve-plugins.mjs
|
|
4
4
|
resource: npm/scripts/lib/resolve-plugins.mjs
|
|
5
5
|
docgen:
|
|
6
|
-
crc:
|
|
6
|
+
crc: 17248373
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
Резолв плагінів @7n/rules: визначає, які пакети-плагіни активні у проєкті, де їхні `rules/`-каталоги, які capabilities вони надають і які handlers надають.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/** @see ./docs/orchestrate.md */
|
|
2
2
|
import { spawnSync } from 'node:child_process'
|
|
3
3
|
import { existsSync } from 'node:fs'
|
|
4
|
-
import { copyFile, rm } from 'node:fs/promises'
|
|
5
|
-
import { join } from 'node:path'
|
|
4
|
+
import { copyFile, mkdir, rm } from 'node:fs/promises'
|
|
5
|
+
import { dirname, join } from 'node:path'
|
|
6
6
|
import { pathToFileURL } from 'node:url'
|
|
7
7
|
|
|
8
8
|
import { assertEcosystemProvider } from '../../../scripts/lib/plugin-api.mjs'
|
|
@@ -81,28 +81,122 @@ export async function callRunner(runner, prompt, cwd, deps = {}) {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
/**
|
|
84
|
-
*
|
|
85
|
-
* той самий контракт, що й для інших
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
* не
|
|
91
|
-
*
|
|
84
|
+
* Гарантує, що подальші кроки оркестратора виконуються в ізольованому
|
|
85
|
+
* worktree (`main.json.worktree: true`, той самий контракт, що й для інших
|
|
86
|
+
* worktree-only скілів). Оркестратор не годує SKILL.md жодному агенту, тож
|
|
87
|
+
* замість покладатись на агента, що читає SKILL.md-preflight, сам детерміновано
|
|
88
|
+
* створює worktree конвенцією `<current-branch>-taze` (`npx \@7n/mt worktree
|
|
89
|
+
* create`) і ставить залежності — щоб `bunx taze -w -r latest`/`bun install`
|
|
90
|
+
* ніколи не виконались прямо в основному дереві виклику. `autoCreated: true`
|
|
91
|
+
* сигналить викликачеві, що після завершення прогону варто перенести зміни
|
|
92
|
+
* назад (`bringChangesBackToOriginal`) і прибрати worktree
|
|
93
|
+
* (`removeAutoCreatedWorktree`) — а не лишати сирітське дерево.
|
|
92
94
|
* @param {string} cwd каталог для перевірки
|
|
93
95
|
* @param {typeof spawnSync} spawnFn інжект для тестів
|
|
94
|
-
* @
|
|
96
|
+
* @param {(line: string) => void} log колбек прогресу
|
|
97
|
+
* @returns {{ cwd: string, autoCreated: boolean, branchArg: string|null }} `autoCreated: false` — `cwd` без змін
|
|
98
|
+
* (вже worktree); `autoCreated: true` — `cwd` щойно створеного worktree і `branchArg`, з яким його створено
|
|
95
99
|
*/
|
|
96
|
-
function
|
|
97
|
-
const
|
|
98
|
-
const toplevel =
|
|
100
|
+
function ensureRunningInWorktree(cwd, spawnFn, log) {
|
|
101
|
+
const toplevelResult = spawnFn('git', ['rev-parse', '--show-toplevel'], { cwd, encoding: 'utf8' })
|
|
102
|
+
const toplevel = toplevelResult.status === 0 ? toplevelResult.stdout.trim() : ''
|
|
99
103
|
const segments = new Set(toplevel.replaceAll('\\', '/').split('/'))
|
|
100
|
-
if (
|
|
104
|
+
if (segments.has('.worktrees')) return { cwd, autoCreated: false, branchArg: null }
|
|
105
|
+
|
|
106
|
+
const branchResult = spawnFn('git', ['branch', '--show-current'], { cwd, encoding: 'utf8' })
|
|
107
|
+
const currentBranch = branchResult.status === 0 ? branchResult.stdout.trim() : ''
|
|
108
|
+
if (!currentBranch) {
|
|
101
109
|
throw new Error(
|
|
102
|
-
`taze: "${cwd}" не в ізольованому worktree (git toplevel: "${toplevel || '?'}")
|
|
103
|
-
'
|
|
110
|
+
`taze: "${cwd}" не в ізольованому worktree (git toplevel: "${toplevel || '?'}"), і поточну гілку визначити не вдалось ` +
|
|
111
|
+
'(detached HEAD?) — автоматичне створення worktree за конвенцією `<current-branch>-taze` неможливе. Перейди на гілку вручну.'
|
|
104
112
|
)
|
|
105
113
|
}
|
|
114
|
+
|
|
115
|
+
const branchArg = `${currentBranch}-taze`
|
|
116
|
+
const pathSegment = branchArg.replaceAll('/', '-')
|
|
117
|
+
log(`⚠️ "${cwd}" не в ізольованому worktree — створюю ".worktrees/${pathSegment}"...`)
|
|
118
|
+
runCommand('npx', ['@7n/mt', 'worktree', 'create', branchArg, 'n-taze: worktree-only skill'], cwd, spawnFn)
|
|
119
|
+
|
|
120
|
+
const newCwd = join(cwd, '.worktrees', pathSegment)
|
|
121
|
+
log('📥 bun install (bootstrap нового дерева)...')
|
|
122
|
+
runCommand('bun', ['install'], newCwd, spawnFn)
|
|
123
|
+
return { cwd: newCwd, autoCreated: true, branchArg }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Переносить зміни з автоствореного worktree назад у вихідне дерево як
|
|
128
|
+
* **untracked/незакомічені** правки (просте копіювання файлів, без git
|
|
129
|
+
* merge/cherry-pick) — так само, як їх бачив би користувач, якби сам
|
|
130
|
+
* працював у вихідному дереві. Джерело істини — `git status --porcelain`
|
|
131
|
+
* у worktree: для кожного шляху копіює файл, якщо він існує (модифікація/
|
|
132
|
+
* додавання), або видаляє його у вихідному дереві, якщо existsSync каже,
|
|
133
|
+
* що в worktree його вже нема (видалення). Перейменування (`old -> new` у
|
|
134
|
+
* porcelain) переносять лише нову назву — стара лишається як була, це
|
|
135
|
+
* прийнятний компроміс: taze не перейменовує файли сам, ефект можливий
|
|
136
|
+
* лише як побічний результат LLM-рефакторингу.
|
|
137
|
+
* @param {string} worktreeCwd автостворений worktree, з якого переносимо
|
|
138
|
+
* @param {string} originalCwd вихідне дерево, куди переносимо
|
|
139
|
+
* @param {typeof spawnSync} spawnFn інжект для тестів
|
|
140
|
+
* @param {(line: string) => void} log колбек прогресу
|
|
141
|
+
* @param {{ copyFile?: (src: string, dest: string) => Promise<void>, rm?: (path: string, opts?: object) => Promise<void>, mkdir?: (path: string, opts?: object) => Promise<void> }} [deps] інжекти для тестів
|
|
142
|
+
* @returns {Promise<string[]>} відносні шляхи перенесених файлів
|
|
143
|
+
*/
|
|
144
|
+
export async function bringChangesBackToOriginal(worktreeCwd, originalCwd, spawnFn, log, deps = {}) {
|
|
145
|
+
const copy = deps.copyFile ?? copyFile
|
|
146
|
+
const removeFile = deps.rm ?? rm
|
|
147
|
+
const makeDir = deps.mkdir ?? mkdir
|
|
148
|
+
|
|
149
|
+
const statusResult = spawnFn('git', ['status', '--porcelain'], { cwd: worktreeCwd, encoding: 'utf8' })
|
|
150
|
+
if (statusResult.status !== 0) {
|
|
151
|
+
log(
|
|
152
|
+
`⚠️ Не вдалось прочитати git status у "${worktreeCwd}" — зміни НЕ перенесені назад, worktree лишиться для ручного розбору.`
|
|
153
|
+
)
|
|
154
|
+
return []
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const lines = statusResult.stdout.split('\n').filter(Boolean)
|
|
158
|
+
if (lines.length === 0) {
|
|
159
|
+
log('ℹ️ Worktree без змін — нічого переносити назад.')
|
|
160
|
+
return []
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const brought = []
|
|
164
|
+
for (const line of lines) {
|
|
165
|
+
const rest = line.slice(3)
|
|
166
|
+
const relPath = rest.includes(' -> ') ? rest.split(' -> ', 2)[1] : rest
|
|
167
|
+
const srcPath = join(worktreeCwd, relPath)
|
|
168
|
+
const destPath = join(originalCwd, relPath)
|
|
169
|
+
if (existsSync(srcPath)) {
|
|
170
|
+
await makeDir(dirname(destPath), { recursive: true })
|
|
171
|
+
await copy(srcPath, destPath)
|
|
172
|
+
} else {
|
|
173
|
+
await removeFile(destPath, { force: true })
|
|
174
|
+
}
|
|
175
|
+
brought.push(relPath)
|
|
176
|
+
}
|
|
177
|
+
log(`📤 Перенесено назад у "${originalCwd}" як untracked: ${brought.join(', ')}`)
|
|
178
|
+
return brought
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Прибирає автостворений worktree разом з його ефемерною git-гілкою
|
|
183
|
+
* (`npx \@7n/mt worktree remove <branch>`) — викликати лише ПІСЛЯ
|
|
184
|
+
* `bringChangesBackToOriginal`, інакше зміни згорять разом з деревом.
|
|
185
|
+
* Не кидає при провалі — це прибирання, а не крок, від якого залежить
|
|
186
|
+
* результат прогону; провал лише логується, worktree лишається для
|
|
187
|
+
* ручного розбору.
|
|
188
|
+
* @param {string} branchArg гілка, з якою worktree був створений (з `ensureRunningInWorktree`)
|
|
189
|
+
* @param {string} originalCwd вихідне дерево, звідки виконати `npx \@7n/mt worktree remove`
|
|
190
|
+
* @param {typeof spawnSync} spawnFn інжект для тестів
|
|
191
|
+
* @param {(line: string) => void} log колбек прогресу
|
|
192
|
+
* @returns {void}
|
|
193
|
+
*/
|
|
194
|
+
export function removeAutoCreatedWorktree(branchArg, originalCwd, spawnFn, log) {
|
|
195
|
+
log(`🧹 Прибираю автостворений worktree "${branchArg}"...`)
|
|
196
|
+
const result = spawnFn('npx', ['@7n/mt', 'worktree', 'remove', branchArg], { cwd: originalCwd, encoding: 'utf8' })
|
|
197
|
+
if (result.status !== 0) {
|
|
198
|
+
log(`⚠️ Не вдалось прибрати worktree "${branchArg}" — приберіть вручну (${result.stderr || result.stdout})`)
|
|
199
|
+
}
|
|
106
200
|
}
|
|
107
201
|
|
|
108
202
|
/**
|
|
@@ -329,62 +423,79 @@ export function formatReport({ minorPatch, totalChanged, results, ecosystems = [
|
|
|
329
423
|
* @returns {Promise<{ ok: boolean, report: string, results: Array<object>, ecosystems: Array<object> }>} результат
|
|
330
424
|
*/
|
|
331
425
|
export async function runTazeOrchestrator(options = {}) {
|
|
332
|
-
const cwd = options.cwd ?? process.cwd()
|
|
333
426
|
const runner = options.runner ?? 'pi'
|
|
334
427
|
const log = options.log ?? (line => console.log(line))
|
|
335
428
|
const deps = options.deps ?? {}
|
|
336
429
|
const spawnFn = deps.spawnFn ?? spawnSync
|
|
337
430
|
const call = deps.callRunner ?? callRunner
|
|
338
431
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
// репо `bun install` падає з exit 1, і без цього гейта весь прогін гинув би
|
|
343
|
-
// до екосистемних провайдерів. Той самий принцип «тиші», що й для мовних
|
|
344
|
-
// екосистем: немає сигналу — немає ані кроків, ані згадки у звіті.
|
|
345
|
-
const npmPresent = existsSync(join(cwd, 'package.json'))
|
|
346
|
-
let diff = { major: [], minorPatch: 0, totalChanged: 0 }
|
|
347
|
-
const results = []
|
|
348
|
-
if (npmPresent) {
|
|
349
|
-
log('📦 Бекап package.json...')
|
|
350
|
-
const backedUpWorkspaces = await backupWorkspacePackageFiles(cwd, deps)
|
|
351
|
-
|
|
352
|
-
log('⬆️ bunx taze -w -r latest...')
|
|
353
|
-
runCommand('bunx', ['taze', '-w', '-r', 'latest'], cwd, spawnFn)
|
|
354
|
-
log('📥 bun install...')
|
|
355
|
-
runCommand('bun', ['install'], cwd, spawnFn)
|
|
356
|
-
|
|
357
|
-
const collectDiff = deps.collectTazeDiff ?? collectTazeDiff
|
|
358
|
-
diff = await collectDiff(cwd)
|
|
359
|
-
log(`🔍 diff: ${diff.major.length} major, ${diff.minorPatch} minor/patch`)
|
|
432
|
+
const originalCwd = options.cwd ?? process.cwd()
|
|
433
|
+
const worktree = ensureRunningInWorktree(originalCwd, spawnFn, log)
|
|
434
|
+
const cwd = worktree.cwd
|
|
360
435
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
436
|
+
try {
|
|
437
|
+
// npm/bun-гілка активна лише за кореневим package.json — на чисто-Python/Rust
|
|
438
|
+
// репо `bun install` падає з exit 1, і без цього гейта весь прогін гинув би
|
|
439
|
+
// до екосистемних провайдерів. Той самий принцип «тиші», що й для мовних
|
|
440
|
+
// екосистем: немає сигналу — немає ані кроків, ані згадки у звіті.
|
|
441
|
+
const npmPresent = existsSync(join(cwd, 'package.json'))
|
|
442
|
+
let diff = { major: [], minorPatch: 0, totalChanged: 0 }
|
|
443
|
+
const results = []
|
|
444
|
+
if (npmPresent) {
|
|
445
|
+
log('📦 Бекап package.json...')
|
|
446
|
+
const backedUpWorkspaces = await backupWorkspacePackageFiles(cwd, deps)
|
|
447
|
+
|
|
448
|
+
log('⬆️ bunx taze -w -r latest...')
|
|
449
|
+
runCommand('bunx', ['taze', '-w', '-r', 'latest'], cwd, spawnFn)
|
|
450
|
+
log('📥 bun install...')
|
|
451
|
+
runCommand('bun', ['install'], cwd, spawnFn)
|
|
452
|
+
|
|
453
|
+
const collectDiff = deps.collectTazeDiff ?? collectTazeDiff
|
|
454
|
+
diff = await collectDiff(cwd)
|
|
455
|
+
log(`🔍 diff: ${diff.major.length} major, ${diff.minorPatch} minor/patch`)
|
|
456
|
+
|
|
457
|
+
for (const entry of diff.major) {
|
|
458
|
+
log(`🔧 ${entry.pkg} (${entry.workspace}): ${entry.from} → ${entry.to}...`)
|
|
459
|
+
const outcome = await call(runner, buildDependencyPrompt(entry), cwd, deps)
|
|
460
|
+
results.push({ ...entry, ...outcome })
|
|
461
|
+
log(outcome.ok ? ` ✅ ${entry.pkg}` : ` ❌ ${entry.pkg}: ${outcome.error}`)
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
await cleanupBackups(cwd, backedUpWorkspaces, deps)
|
|
465
|
+
} else {
|
|
466
|
+
log('⏭ npm/bun: кореневого package.json немає — гілка пропущена')
|
|
366
467
|
}
|
|
367
468
|
|
|
368
|
-
await
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
469
|
+
const providers = deps.ecosystemProviders ?? (await loadPluginTazeProviders(cwd, log, deps))
|
|
470
|
+
const ecosystems = []
|
|
471
|
+
for (const provider of providers) {
|
|
472
|
+
ecosystems.push(await runEcosystem(provider, { cwd, runner, log, deps, spawnFn, call }))
|
|
473
|
+
}
|
|
372
474
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
475
|
+
const report = formatReport({
|
|
476
|
+
minorPatch: diff.minorPatch,
|
|
477
|
+
totalChanged: diff.totalChanged,
|
|
478
|
+
results,
|
|
479
|
+
ecosystems,
|
|
480
|
+
npmPresent
|
|
481
|
+
})
|
|
482
|
+
log(report)
|
|
483
|
+
|
|
484
|
+
const ecosystemsOk = ecosystems.every(eco => eco.error === null && eco.results.every(r => r.ok))
|
|
485
|
+
return { ok: results.every(r => r.ok) && ecosystemsOk, report, results, ecosystems }
|
|
486
|
+
} finally {
|
|
487
|
+
// Лише для АВТОстворених worktree — якщо викликач уже сидів у своєму
|
|
488
|
+
// worktree (worktree.autoCreated === false), це не наш worktree і не
|
|
489
|
+
// нам його чіпати/прибирати. `finally` — щоб зміни поверталися і
|
|
490
|
+
// сирітський worktree прибирався навіть при кинутому винятку всередині
|
|
491
|
+
// try (падіння bunx/diff/провайдера), а не лише при успішному прогоні.
|
|
492
|
+
if (worktree.autoCreated) {
|
|
493
|
+
try {
|
|
494
|
+
await bringChangesBackToOriginal(cwd, originalCwd, spawnFn, log, deps)
|
|
495
|
+
} catch (error) {
|
|
496
|
+
log(`⚠️ Перенесення змін назад провалилось: ${error instanceof Error ? error.message : String(error)}`)
|
|
497
|
+
}
|
|
498
|
+
removeAutoCreatedWorktree(worktree.branchArg, originalCwd, spawnFn, log)
|
|
499
|
+
}
|
|
377
500
|
}
|
|
378
|
-
|
|
379
|
-
const report = formatReport({
|
|
380
|
-
minorPatch: diff.minorPatch,
|
|
381
|
-
totalChanged: diff.totalChanged,
|
|
382
|
-
results,
|
|
383
|
-
ecosystems,
|
|
384
|
-
npmPresent
|
|
385
|
-
})
|
|
386
|
-
log(report)
|
|
387
|
-
|
|
388
|
-
const ecosystemsOk = ecosystems.every(eco => eco.error === null && eco.results.every(r => r.ok))
|
|
389
|
-
return { ok: results.every(r => r.ok) && ecosystemsOk, report, results, ecosystems }
|
|
390
501
|
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
type: JS Module
|
|
3
|
-
title: main.mjs
|
|
4
|
-
resource: npm/rules/python/applies/main.mjs
|
|
5
|
-
docgen:
|
|
6
|
-
crc: 871eb5c6
|
|
7
|
-
model: omlx/gemma-4-e4b-it-OptiQ-4bit
|
|
8
|
-
score: 100
|
|
9
|
-
issues: judge:inaccurate:0.97
|
|
10
|
-
judgeModel: openai-codex/gpt-5.4-mini
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## Огляд
|
|
14
|
-
|
|
15
|
-
Перевіряє наявність файлу `pyproject.toml` у корені репозиторію для визначення, чи має застосовуватися правило до проєктів, що використовують Python. Публічні функції `applies` та `main` реалізують цю логіку.
|
|
16
|
-
|
|
17
|
-
## Поведінка
|
|
18
|
-
|
|
19
|
-
applies визначає, чи застосовне правило, перевіряючи наявність файлу `pyproject.toml` у корені репозиторію.
|
|
20
|
-
main повідомляє про успішне знаходження файлу `pyproject.toml`, що підтверджує застосування правила для Python.
|
|
21
|
-
|
|
22
|
-
## Публічний API
|
|
23
|
-
|
|
24
|
-
- applies — визначає загальні налаштування та конфігурацію.
|
|
25
|
-
- main — виводить загальний контекст, тоді як спеціалізовані блоки виконують конкретні перевірки.
|
|
26
|
-
|
|
27
|
-
## Гарантії поведінки
|
|
28
|
-
|
|
29
|
-
- Read-only: не виконує операцій запису (ФС/БД).
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
/** @see ./docs/applies.md */
|
|
2
|
-
import { existsSync } from 'node:fs'
|
|
3
|
-
import { join } from 'node:path'
|
|
4
|
-
|
|
5
|
-
import { createViolationReporter } from '../../../scripts/lib/lint-surface/violation-reporter.mjs'
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* @param {string} [cwd] корінь репозиторію (`process.cwd()` у звичайному прогоні)
|
|
9
|
-
* @returns {Promise<boolean>} `true` — правило застосовне; `false` — пропустити
|
|
10
|
-
*/
|
|
11
|
-
export function applies(cwd = process.cwd()) {
|
|
12
|
-
return Promise.resolve(existsSync(join(cwd, 'pyproject.toml')))
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Друкує короткий context-pass — самі перевірки виконують інші concerns.
|
|
17
|
-
* @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст лінту.
|
|
18
|
-
* @returns {Promise<import('../../../scripts/lib/lint-surface/types.mjs').LintResult>} результат context-pass
|
|
19
|
-
*/
|
|
20
|
-
export function lint(ctx) {
|
|
21
|
-
const reporter = createViolationReporter(ctx)
|
|
22
|
-
reporter.pass('pyproject.toml знайдено в корені — застосовую python.mdc')
|
|
23
|
-
return Promise.resolve(reporter.result())
|
|
24
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
type: JS Module
|
|
3
|
-
title: uv-run.mjs
|
|
4
|
-
resource: npm/rules/python/lib/uv-run.mjs
|
|
5
|
-
docgen:
|
|
6
|
-
crc: 1d697d71
|
|
7
|
-
model: openai-codex/gpt-5.4-mini
|
|
8
|
-
score: 100
|
|
9
|
-
issues: judge:inaccurate:0.97
|
|
10
|
-
judgeModel: openai-codex/gpt-5.4-mini
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## Огляд
|
|
14
|
-
|
|
15
|
-
Спільний preflight для Python-детекторів `mypy` і `ruff`, виділений зі спільного дубльованого коду з `main.mjs`. Використовує `PY_EXT_RE`, щоб залишати лише Python-цілі; для `ctx.files` запускає перевірку по вибраних файлах, а без них — по всьому проєкту через `.`. За наявності `uvToolAvailable` готує запуск обох детекторів через `uv run --frozen`; `preparePythonRun` формує однаковий контракт підготовки для обох інструментів.
|
|
16
|
-
|
|
17
|
-
## Поведінка
|
|
18
|
-
|
|
19
|
-
- `PY_EXT_RE` — визначає, чи файл належить до Python-цілей для delta-лінту.
|
|
20
|
-
- `uvToolAvailable` — перевіряє, чи доступний Python-інструмент у середовищі `uv`.
|
|
21
|
-
- `preparePythonRun` — готує спільний preflight для Python-лінтерів: пропускає без `pyproject.toml`, відсікає нерелевантні файли, перевіряє наявність `uv` і доступність інструменту, після чого повертає готові цілі для запуску через `uv run --frozen`.
|
|
22
|
-
|
|
23
|
-
## Публічний API
|
|
24
|
-
|
|
25
|
-
- PY_EXT_RE — Відібрає лише Python-файли з delta-списку для `lint`.
|
|
26
|
-
- uvToolAvailable — Підтверджує, що `uv` доступний у середовищі.
|
|
27
|
-
- preparePythonRun — Готує запуск Python-цілі: звіряє `pyproject.toml`, визначає delta або full-обсяг, знаходить `uv` і перевіряє, чи є потрібний `tool`; якщо підготовка не вдалася, викликач завершує `reporter.result` раніше.
|
|
28
|
-
|
|
29
|
-
## Гарантії поведінки
|
|
30
|
-
|
|
31
|
-
- Read-only: не виконує операцій запису (ФС/БД).
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Спільний preflight обох python-детекторів (`mypy`, `ruff`) — обидва per-file: приймають
|
|
3
|
-
* `ctx.files`, інакше `.` (весь проєкт), обидва йдуть через `uv run --frozen`. Виділено зі
|
|
4
|
-
* спільного дубльованого коду (jscpd) обох `main.mjs`.
|
|
5
|
-
*/
|
|
6
|
-
import { existsSync } from 'node:fs'
|
|
7
|
-
import { join } from 'node:path'
|
|
8
|
-
|
|
9
|
-
import { resolveCmd } from '../../../scripts/utils/resolve-cmd.mjs'
|
|
10
|
-
import { spawnAsync } from '../../../scripts/utils/spawn-async.mjs'
|
|
11
|
-
|
|
12
|
-
/** Розширення `.py` — фільтр delta-списку файлів у `lint(ctx)`. */
|
|
13
|
-
export const PY_EXT_RE = /\.py$/u
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Async (не блокує event loop) — детектор може виконуватись у parallel lane `detectAll()`
|
|
17
|
-
* (ADR 260716-1354).
|
|
18
|
-
* @param {string} uv шлях до бінарника uv.
|
|
19
|
-
* @param {string} tool ім'я інструменту в uv-середовищі.
|
|
20
|
-
* @returns {Promise<boolean>} true якщо інструмент доступний
|
|
21
|
-
*/
|
|
22
|
-
export async function uvToolAvailable(uv, tool) {
|
|
23
|
-
const r = await spawnAsync(uv, ['run', '--frozen', tool, '--version'])
|
|
24
|
-
return r.exitCode === 0
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Спільний preflight: pyproject.toml, delta/full-цілі, резолв `uv`, доступність `tool` у
|
|
29
|
-
* uv-середовищі. Викликач рано виходить (`reporter.result()`), коли повертається `null` —
|
|
30
|
-
* `fail()` для випадку відсутнього `uv` уже викликаний усередині.
|
|
31
|
-
* Async (не блокує event loop) — детектор може виконуватись у parallel lane `detectAll()`
|
|
32
|
-
* (ADR 260716-1354).
|
|
33
|
-
* @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст лінту.
|
|
34
|
-
* @param {(msg: string, opts: object) => void} fail колбек реєстрації порушення.
|
|
35
|
-
* @param {string} tool ім'я інструменту (`mypy`|`ruff`) для перевірки доступності.
|
|
36
|
-
* @returns {Promise<{uv: string, targets: string[]}|null>} preflight-результат, або null (рано вийти).
|
|
37
|
-
*/
|
|
38
|
-
export async function preparePythonRun(ctx, fail, tool) {
|
|
39
|
-
if (!existsSync(join(ctx.cwd, 'pyproject.toml'))) return null
|
|
40
|
-
|
|
41
|
-
const targets = ctx.files === undefined ? ['.'] : ctx.files.filter(f => PY_EXT_RE.test(f))
|
|
42
|
-
if (targets.length === 0) return null
|
|
43
|
-
|
|
44
|
-
const uv = resolveCmd('uv')
|
|
45
|
-
if (!uv) {
|
|
46
|
-
fail('lint-python: `uv` не знайдено в PATH (потрібен при наявному pyproject.toml, python.mdc)', 'uv-missing')
|
|
47
|
-
return null
|
|
48
|
-
}
|
|
49
|
-
if (!(await uvToolAvailable(uv, tool))) return null // tool недоступний у uv-середовищі → пропущено
|
|
50
|
-
|
|
51
|
-
return { uv, targets }
|
|
52
|
-
}
|
package/rules/python/main.json
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{ "auto": { "glob": "pyproject.toml" } }
|
package/rules/python/main.mdc
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
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 заборонено.** Середовище: `uv sync --frozen` (строго з `uv.lock`).
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
type: JS Module
|
|
3
|
-
title: main.mjs
|
|
4
|
-
resource: npm/rules/python/mypy/main.mjs
|
|
5
|
-
docgen:
|
|
6
|
-
crc: 189c5b15
|
|
7
|
-
model: omlx/gemma-4-e4b-it-OptiQ-4bit
|
|
8
|
-
score: 100
|
|
9
|
-
issues: judge:inaccurate:0.99
|
|
10
|
-
judgeModel: openai-codex/gpt-5.4-mini
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## Огляд
|
|
14
|
-
|
|
15
|
-
Please provide the draft section "overview" that needs revision, according to the context and requirements.
|
|
16
|
-
|
|
17
|
-
## Поведінка
|
|
18
|
-
|
|
19
|
-
1. Перевіряє наявність файлу `pyproject.toml` у кореневій директорії проєкту. Якщо його немає, перевірка не виконується.
|
|
20
|
-
2. Визначає цільові файли для перевірки: або всі `.py` файли у проєкті, або лише ті, що передані в контекст.
|
|
21
|
-
3. Перевіряє наявність у системному шляху інструмента `uv` та доступності `mypy` у середовищі `uv`. Якщо `uv` або `mypy` недоступні, перевірка пропускається.
|
|
22
|
-
4. Запускає `mypy` через `uv run --frozen` для визначених цільових файлів, передаючи поточну робочу директорію проєкту як робочу директорію для виконання.
|
|
23
|
-
5. Якщо `mypy` завершується з ненульовим кодом, фіксується порушення з виводом команди та кодом помилки.
|
|
24
|
-
6. Якщо `mypy` завершується успішно, результати перевірки вважаються позитивними.
|
|
25
|
-
|
|
26
|
-
## Публічний API
|
|
27
|
-
|
|
28
|
-
lint — виявляє помилки в коді Python за допомогою mypy.
|
|
29
|
-
|
|
30
|
-
## Гарантії поведінки
|
|
31
|
-
|
|
32
|
-
- Read-only: не виконує операцій запису (ФС/БД).
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* lint-поверхня python/mypy: read-only detector (`mypy`, через `uv run --frozen`). Per-file:
|
|
3
|
-
* приймає `ctx.files`, інакше `.` (весь проєкт). Виділено з колишнього bundled `python/check`
|
|
4
|
-
* (spec docs/specs/2026-07-02-text-check-per-file-split-design.md "Рішення python/php/rego") —
|
|
5
|
-
* mypy сам транзитивно підвантажує імпортовані модулі (follow-imports), тож дає коректні
|
|
6
|
-
* діагнози й на підмножині переданих файлів. Немає fix capability (mypy — detect-only).
|
|
7
|
-
*/
|
|
8
|
-
import { createViolationReporter } from '../../../scripts/lib/lint-surface/violation-reporter.mjs'
|
|
9
|
-
import { spawnAsync } from '../../../scripts/utils/spawn-async.mjs'
|
|
10
|
-
import { preparePythonRun } from '../lib/uv-run.mjs'
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Detector python/mypy (read-only).
|
|
14
|
-
* Async (не блокує event loop) — детектор може виконуватись у parallel lane `detectAll()`
|
|
15
|
-
* (ADR 260716-1354).
|
|
16
|
-
* @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст лінту.
|
|
17
|
-
* @returns {Promise<import('../../../scripts/lib/lint-surface/types.mjs').LintResult>} результат із порушеннями
|
|
18
|
-
*/
|
|
19
|
-
export async function lint(ctx) {
|
|
20
|
-
const reporter = createViolationReporter(ctx)
|
|
21
|
-
const { fail } = reporter
|
|
22
|
-
const cwd = ctx.cwd
|
|
23
|
-
|
|
24
|
-
const prepared = await preparePythonRun(ctx, fail, 'mypy')
|
|
25
|
-
if (!prepared) return reporter.result()
|
|
26
|
-
const { uv, targets } = prepared
|
|
27
|
-
|
|
28
|
-
const r = await spawnAsync(uv, ['run', '--frozen', 'mypy', ...targets], { cwd })
|
|
29
|
-
if (r.exitCode !== 0) {
|
|
30
|
-
const code = typeof r.exitCode === 'number' ? r.exitCode : 1
|
|
31
|
-
const out = `${r.stdout ?? ''}${r.stderr ?? ''}`.trim().slice(0, 2000)
|
|
32
|
-
const outSuffix = out ? `\n${out}` : ''
|
|
33
|
-
fail(`lint-python: mypy — помилка (код ${code}, python.mdc)${outSuffix}`, 'mypy-violation')
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return reporter.result()
|
|
37
|
-
}
|