@7n/rules 1.5.1 → 1.7.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 +12 -0
- package/bin/n-rules.js +83 -21
- package/package.json +1 -2
- package/rules/abie/main.mdc +0 -6
- package/rules/js/check/docs/main.md +1 -1
- package/rules/js/check/main.mjs +6 -10
- package/rules/js/main.mdc +0 -1
- package/rules/js/vscode_extensions/template/extensions.json.snippet.json +4 -1
- package/rules/npm-module/main.mdc +0 -2
- package/rules/php/tooling/docs/main.md +1 -1
- package/rules/php/tooling/main.mjs +2 -7
- package/rules/python/tooling/docs/main.md +1 -1
- package/rules/python/tooling/main.mjs +2 -7
- package/rules/rego/lib/docs/run-external-tool.md +1 -2
- package/rules/rego/lib/run-external-tool.mjs +4 -1
- package/rules/security/main.mdc +0 -1
- package/rules/style/main.mdc +0 -1
- package/rules/style/tooling/docs/main.md +1 -1
- package/rules/style/tooling/main.mjs +2 -7
- package/rules/tauri/release/release.mdc +1 -1
- package/rules/text/formatting/docs/main.md +1 -1
- package/rules/text/formatting/main.mjs +11 -11
- package/rules/text/main.mdc +0 -1
- package/scripts/docs/skills-cli.md +1 -1
- package/scripts/lib/acp-runner.mjs +26 -33
- package/scripts/lib/docs/acp-runner.md +5 -5
- package/scripts/lib/docs/mirror-parity.md +1 -1
- package/scripts/lib/mirror-parity.mjs +49 -16
- package/scripts/skills-cli.mjs +35 -26
- package/rules/abie/clean_merged_ignore_branches/clean_merged_ignore_branches.mdc +0 -19
- package/rules/abie/clean_merged_ignore_branches/clean_merged_ignore_branches.rego +0 -81
- package/rules/abie/clean_merged_ignore_branches/concern.json +0 -11
- package/rules/abie/clean_merged_ignore_branches/docs/fix-clean_merged_ignore_branches.md +0 -31
- package/rules/abie/clean_merged_ignore_branches/docs/index.md +0 -9
- package/rules/abie/clean_merged_ignore_branches/fix-clean_merged_ignore_branches.mjs +0 -8
- package/rules/abie/clean_merged_ignore_branches/template/clean-merged-branch.yml.snippet.yml +0 -9
- package/rules/docker/lint_docker_yml/concern.json +0 -12
- package/rules/docker/lint_docker_yml/docs/fix-lint_docker_yml.md +0 -31
- package/rules/docker/lint_docker_yml/docs/index.md +0 -9
- package/rules/docker/lint_docker_yml/fix-lint_docker_yml.mjs +0 -5
- package/rules/docker/lint_docker_yml/lint_docker_yml.mdc +0 -14
- package/rules/docker/lint_docker_yml/lint_docker_yml.rego +0 -79
- package/rules/docker/lint_docker_yml/template/lint-docker.yml.snippet.yml +0 -41
- package/rules/js/lint_js_yml/concern.json +0 -12
- package/rules/js/lint_js_yml/docs/fix-lint_js_yml.md +0 -28
- package/rules/js/lint_js_yml/docs/index.md +0 -9
- package/rules/js/lint_js_yml/fix-lint_js_yml.mjs +0 -5
- package/rules/js/lint_js_yml/lint_js_yml.mdc +0 -14
- package/rules/js/lint_js_yml/lint_js_yml.rego +0 -117
- package/rules/js/lint_js_yml/template/lint-js.yml.snippet.yml +0 -40
- package/rules/k8s/lint_k8s_yml/concern.json +0 -12
- package/rules/k8s/lint_k8s_yml/docs/fix-lint_k8s_yml.md +0 -30
- package/rules/k8s/lint_k8s_yml/docs/index.md +0 -9
- package/rules/k8s/lint_k8s_yml/fix-lint_k8s_yml.mjs +0 -5
- package/rules/k8s/lint_k8s_yml/lint_k8s_yml.rego +0 -57
- package/rules/k8s/lint_k8s_yml/template/lint-k8s.yml.snippet.yml +0 -43
- package/rules/npm-module/npm_publish_yml/concern.json +0 -13
- package/rules/npm-module/npm_publish_yml/docs/fix-npm_publish_yml.md +0 -28
- package/rules/npm-module/npm_publish_yml/docs/index.md +0 -9
- package/rules/npm-module/npm_publish_yml/fix-npm_publish_yml.mjs +0 -8
- package/rules/npm-module/npm_publish_yml/template/npm-publish.yml.snippet.yml +0 -49
- package/rules/php/lint_php_yml/concern.json +0 -12
- package/rules/php/lint_php_yml/docs/fix-lint_php_yml.md +0 -28
- package/rules/php/lint_php_yml/docs/index.md +0 -9
- package/rules/php/lint_php_yml/fix-lint_php_yml.mjs +0 -5
- package/rules/php/lint_php_yml/lint_php_yml.mdc +0 -23
- package/rules/php/lint_php_yml/lint_php_yml.rego +0 -34
- package/rules/php/lint_php_yml/template/lint-php.yml.snippet.yml +0 -47
- package/rules/python/lint_python_yml/concern.json +0 -12
- package/rules/python/lint_python_yml/docs/fix-lint_python_yml.md +0 -27
- package/rules/python/lint_python_yml/docs/index.md +0 -9
- package/rules/python/lint_python_yml/fix-lint_python_yml.mjs +0 -5
- package/rules/python/lint_python_yml/lint_python_yml.mdc +0 -12
- package/rules/python/lint_python_yml/lint_python_yml.rego +0 -68
- package/rules/python/lint_python_yml/template/lint-python.yml.snippet.yml +0 -41
- package/rules/rust/lint_rust_yml/concern.json +0 -14
- package/rules/rust/lint_rust_yml/docs/fix-lint_rust_yml.md +0 -28
- package/rules/rust/lint_rust_yml/docs/index.md +0 -9
- package/rules/rust/lint_rust_yml/fix-lint_rust_yml.mjs +0 -5
- package/rules/rust/lint_rust_yml/lint_rust_yml.mdc +0 -17
- package/rules/rust/lint_rust_yml/lint_rust_yml.rego +0 -79
- package/rules/rust/lint_rust_yml/template/lint-rust.yml.snippet.yml +0 -44
- package/rules/rust/toolchain_cache/concern.json +0 -14
- package/rules/rust/toolchain_cache/docs/fix-toolchain_cache.md +0 -35
- package/rules/rust/toolchain_cache/docs/index.md +0 -12
- package/rules/rust/toolchain_cache/docs/main.md +0 -57
- package/rules/rust/toolchain_cache/fix-toolchain_cache.mjs +0 -117
- package/rules/rust/toolchain_cache/main.mjs +0 -177
- package/rules/rust/toolchain_cache/toolchain_cache.mdc +0 -23
- package/rules/security/lint_security_yml/concern.json +0 -14
- package/rules/security/lint_security_yml/docs/fix-lint_security_yml.md +0 -31
- package/rules/security/lint_security_yml/docs/index.md +0 -9
- package/rules/security/lint_security_yml/fix-lint_security_yml.mjs +0 -8
- package/rules/security/lint_security_yml/lint_security_yml.mdc +0 -7
- package/rules/security/lint_security_yml/lint_security_yml.rego +0 -33
- package/rules/security/lint_security_yml/template/lint-security.yml.snippet.yml +0 -31
- package/rules/style/lint_style_yml/concern.json +0 -12
- package/rules/style/lint_style_yml/docs/fix-lint_style_yml.md +0 -31
- package/rules/style/lint_style_yml/docs/index.md +0 -9
- package/rules/style/lint_style_yml/fix-lint_style_yml.mjs +0 -5
- package/rules/style/lint_style_yml/lint_style_yml.mdc +0 -13
- package/rules/style/lint_style_yml/lint_style_yml.rego +0 -38
- package/rules/style/lint_style_yml/template/lint-style.yml.snippet.yml +0 -39
- package/rules/text/lint_text/concern.json +0 -11
- package/rules/text/lint_text/docs/fix-lint_text.md +0 -28
- package/rules/text/lint_text/docs/index.md +0 -9
- package/rules/text/lint_text/fix-lint_text.mjs +0 -5
- package/rules/text/lint_text/lint_text.mdc +0 -19
- package/rules/text/lint_text/lint_text.rego +0 -104
- package/rules/text/lint_text/template/lint-text.yml.snippet.yml +0 -61
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
/** @see ./docs/fix-toolchain_cache.md */
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* T0-autofix для `rust/toolchain_cache` — детерміновано вставляє
|
|
5
|
-
* `Swatinem/rust-cache@v2` одразу після кожного `dtolnay/rust-toolchain@…` кроку,
|
|
6
|
-
* якому його бракує у своєму job-і, і дописує `with.workspaces` у Tauri-job-ах,
|
|
7
|
-
* де Cargo.toml не в корені репо. Текстові splice-и (як `ga/workflows/fix-workflows.mjs`) —
|
|
8
|
-
* зберігають коментарі/формат, мінімальний diff. Ідемпотентно: `scanToolchainSteps`
|
|
9
|
-
* заново перевіряє стан файла на кожному прогоні.
|
|
10
|
-
*/
|
|
11
|
-
import { applyToFiles } from '../../../scripts/utils/apply-to-files.mjs'
|
|
12
|
-
|
|
13
|
-
import { MISSING_RUST_CACHE, MISSING_RUST_CACHE_WORKSPACES, scanToolchainSteps } from './main.mjs'
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Індекс першого рядка після step-блоку, що починається на `stepLine`
|
|
17
|
-
* (dash-колонка `dashCol`) — перший рядок з відступом не більшим за `dashCol`
|
|
18
|
-
* (сусідній крок того самого рівня або dedent), або EOF.
|
|
19
|
-
* @param {string[]} lines усі рядки
|
|
20
|
-
* @param {number} stepLine індекс рядка кроку (`- uses: …`)
|
|
21
|
-
* @param {number} dashCol колонка dash-а кроку
|
|
22
|
-
* @returns {number} індекс вставки (кінець блоку кроку)
|
|
23
|
-
*/
|
|
24
|
-
function stepBlockEnd(lines, stepLine, dashCol) {
|
|
25
|
-
let j = stepLine + 1
|
|
26
|
-
while (j < lines.length) {
|
|
27
|
-
const line = lines[j]
|
|
28
|
-
if (line.trim() !== '' && line.length - line.trimStart().length <= dashCol) break
|
|
29
|
-
j++
|
|
30
|
-
}
|
|
31
|
-
return j
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Вставляє `Swatinem/rust-cache@v2` (з опційним `with.workspaces`) одразу після
|
|
36
|
-
* кожного `dtolnay/rust-toolchain@…` кроку без cache-кроку в тому самому job-і.
|
|
37
|
-
* @param {string} content вміст workflow-файла
|
|
38
|
-
* @param {string} [workspaceDir] відносний шлях workspace-а для `with.workspaces` (опційно)
|
|
39
|
-
* @returns {string|null} новий вміст або null, якщо нічого не змінилось
|
|
40
|
-
*/
|
|
41
|
-
export function insertRustCache(content, workspaceDir) {
|
|
42
|
-
const lines = content.split('\n')
|
|
43
|
-
const missing = scanToolchainSteps(content).filter(s => !s.hasCache)
|
|
44
|
-
if (missing.length === 0) return null
|
|
45
|
-
|
|
46
|
-
/** @type {Array<{ at: number, text: string[] }>} */
|
|
47
|
-
const inserts = []
|
|
48
|
-
for (const step of missing) {
|
|
49
|
-
const at = stepBlockEnd(lines, step.line, step.dashCol)
|
|
50
|
-
const ind = ' '.repeat(step.dashCol)
|
|
51
|
-
const text = [`${ind}- uses: Swatinem/rust-cache@v2`]
|
|
52
|
-
if (workspaceDir && step.jobHasTauriAction) {
|
|
53
|
-
text.push(`${ind} with:`, `${ind} workspaces: ${workspaceDir}`)
|
|
54
|
-
}
|
|
55
|
-
inserts.push({ at, text })
|
|
56
|
-
}
|
|
57
|
-
inserts.sort((a, b) => b.at - a.at) // згори вниз — індекси не зсуваються під час splice
|
|
58
|
-
for (const ins of inserts) lines.splice(ins.at, 0, ...ins.text)
|
|
59
|
-
return lines.join('\n')
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Дописує `with: workspaces: <dir>` у кожен уже наявний `Swatinem/rust-cache@…`
|
|
64
|
-
* крок Tauri-job-а (`tauri-apps/tauri-action`), якому бракує `workspaces`.
|
|
65
|
-
* @param {string} content вміст workflow-файла
|
|
66
|
-
* @param {string} workspaceDir відносний шлях workspace-а
|
|
67
|
-
* @returns {string|null} новий вміст або null, якщо нічого не змінилось
|
|
68
|
-
*/
|
|
69
|
-
export function addCacheWorkspaces(content, workspaceDir) {
|
|
70
|
-
const lines = content.split('\n')
|
|
71
|
-
const targets = scanToolchainSteps(content).filter(s => s.hasCache && s.jobHasTauriAction && !s.cacheHasWorkspaces)
|
|
72
|
-
if (targets.length === 0) return null
|
|
73
|
-
|
|
74
|
-
/** @type {Array<{ at: number, text: string[] }>} */
|
|
75
|
-
const inserts = []
|
|
76
|
-
for (const step of targets) {
|
|
77
|
-
const cacheLine = lines[step.cacheLine]
|
|
78
|
-
const usesCol = cacheLine.indexOf('uses:')
|
|
79
|
-
const ind = ' '.repeat(usesCol)
|
|
80
|
-
const at = stepBlockEnd(lines, step.cacheLine, usesCol - 2)
|
|
81
|
-
inserts.push({ at, text: [`${ind}with:`, `${ind} workspaces: ${workspaceDir}`] })
|
|
82
|
-
}
|
|
83
|
-
inserts.sort((a, b) => b.at - a.at)
|
|
84
|
-
for (const ins of inserts) lines.splice(ins.at, 0, ...ins.text)
|
|
85
|
-
return lines.join('\n')
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/** @type {import('../../../scripts/lib/lint-surface/types.mjs').T0Pattern[]} */
|
|
89
|
-
export const patterns = [
|
|
90
|
-
{
|
|
91
|
-
id: 'rust-toolchain-cache-insert',
|
|
92
|
-
test: violations => violations.some(v => v.data?.kind === MISSING_RUST_CACHE && v.file),
|
|
93
|
-
apply: (violations, ctx) => {
|
|
94
|
-
const targets = violations.filter(v => v.data?.kind === MISSING_RUST_CACHE && v.file)
|
|
95
|
-
const wsTargets = violations.filter(v => v.data?.kind === MISSING_RUST_CACHE_WORKSPACES)
|
|
96
|
-
const workspaceDir = wsTargets.find(v => typeof v.data?.workspaceDir === 'string')?.data?.workspaceDir
|
|
97
|
-
const touchedFiles = applyToFiles(targets, ctx, () => content => insertRustCache(content, workspaceDir))
|
|
98
|
-
return touchedFiles.length > 0
|
|
99
|
-
? { touchedFiles, message: `Swatinem/rust-cache@v2 → ${touchedFiles.length} workflow(s)` }
|
|
100
|
-
: { touchedFiles: [] }
|
|
101
|
-
}
|
|
102
|
-
},
|
|
103
|
-
{
|
|
104
|
-
id: 'rust-toolchain-cache-workspaces',
|
|
105
|
-
test: violations => violations.some(v => v.data?.kind === MISSING_RUST_CACHE_WORKSPACES && v.file),
|
|
106
|
-
apply: (violations, ctx) => {
|
|
107
|
-
const targets = violations.filter(v => v.data?.kind === MISSING_RUST_CACHE_WORKSPACES && v.file)
|
|
108
|
-
const touchedFiles = applyToFiles(targets, ctx, () => content => {
|
|
109
|
-
const workspaceDir = targets.find(v => typeof v.data?.workspaceDir === 'string')?.data?.workspaceDir
|
|
110
|
-
return workspaceDir ? addCacheWorkspaces(content, workspaceDir) : null
|
|
111
|
-
})
|
|
112
|
-
return touchedFiles.length > 0
|
|
113
|
-
? { touchedFiles, message: `with.workspaces → ${touchedFiles.length} workflow(s)` }
|
|
114
|
-
: { touchedFiles: [] }
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
]
|
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @see ./docs/main.md
|
|
3
|
-
*
|
|
4
|
-
* Read-only detector: у кожному `.github/workflows/*.yml` кожен job, що ставить
|
|
5
|
-
* Rust toolchain через `dtolnay/rust-toolchain@stable`, повинен мати
|
|
6
|
-
* `Swatinem/rust-cache@v2` десь пізніше у тому самому job-і (lint, coverage,
|
|
7
|
-
* Tauri release/build — незалежно від job-а чи файлу; rust.mdc). Якщо job також
|
|
8
|
-
* запускає `tauri-apps/tauri-action` і Cargo.toml лежить не в корені репо, а під
|
|
9
|
-
* `src-tauri/`, кеш-крок повинен мати `with.workspaces` на цей каталог.
|
|
10
|
-
*
|
|
11
|
-
* Текстовий (не YAML-AST) аналіз — навмисно, як `ga/workflows/main.mjs`: мінімізує
|
|
12
|
-
* diff і не залежить від того, чи canonical formatter зберігає коментарі при
|
|
13
|
-
* round-trip через YAML-парсер. Job-межа визначається через indentation:
|
|
14
|
-
* будь-який рядок з відступом меншим за відступ dash-а кроку означає вихід
|
|
15
|
-
* зі step-list-а (наступний job або кінець `jobs:`).
|
|
16
|
-
*/
|
|
17
|
-
import { existsSync } from 'node:fs'
|
|
18
|
-
import { readdir, readFile } from 'node:fs/promises'
|
|
19
|
-
import { join, relative } from 'node:path'
|
|
20
|
-
|
|
21
|
-
import { createViolationReporter } from '../../../scripts/lib/lint-surface/violation-reporter.mjs'
|
|
22
|
-
|
|
23
|
-
export const MISSING_RUST_CACHE = 'missing-rust-cache'
|
|
24
|
-
export const MISSING_RUST_CACHE_WORKSPACES = 'missing-rust-cache-workspaces'
|
|
25
|
-
|
|
26
|
-
export const TOOLCHAIN_RE = /uses:\s*dtolnay\/rust-toolchain@/u
|
|
27
|
-
export const CACHE_RE = /uses:\s*Swatinem\/rust-cache@/u
|
|
28
|
-
const TAURI_ACTION_RE = /uses:\s*tauri-apps\/tauri-action@/u
|
|
29
|
-
const WORKSPACES_KEY_RE = /^\s*workspaces\s*:/u
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Відступ рядка (кількість пробілів перед першим непробільним символом).
|
|
33
|
-
* @param {string} line рядок файла
|
|
34
|
-
* @returns {number} кількість пробілів відступу
|
|
35
|
-
*/
|
|
36
|
-
function indentOf(line) {
|
|
37
|
-
return line.length - line.trimStart().length
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Дашова колонка кроку (`- uses: …`) з колонки `uses:`. Захист від негативного
|
|
42
|
-
* значення для нетипового форматування (dash не на тому ж рядку).
|
|
43
|
-
* @param {number} usesCol колонка підрядка `uses:`
|
|
44
|
-
* @returns {number} колонка dash-а (не менше 0)
|
|
45
|
-
*/
|
|
46
|
-
function dashColFor(usesCol) {
|
|
47
|
-
return Math.max(usesCol - 2, 0)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Один запис аналізу `dtolnay/rust-toolchain` кроку в межах його job-а (обмеженого
|
|
52
|
-
* indentation-dedent-ом, без явного YAML-парсу job-структури).
|
|
53
|
-
* @typedef {object} ToolchainStepScan
|
|
54
|
-
* @property {number} line індекс рядка кроку `dtolnay/rust-toolchain@…`
|
|
55
|
-
* @property {number} dashCol колонка dash-а кроку (рівень step-list-а job-а)
|
|
56
|
-
* @property {boolean} hasCache чи є `Swatinem/rust-cache@…` пізніше в тому самому job-і
|
|
57
|
-
* @property {number} cacheLine індекс рядка кеш-кроку (−1, якщо відсутній)
|
|
58
|
-
* @property {boolean} cacheHasWorkspaces чи кеш-крок вже має ключ `workspaces`
|
|
59
|
-
* @property {boolean} jobHasTauriAction чи job також викликає `tauri-apps/tauri-action`
|
|
60
|
-
*/
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Сканує job від рядка ОДРАЗУ ПІСЛЯ toolchain-кроку до dedent-у: шукає перший
|
|
64
|
-
* `Swatinem/rust-cache@…` крок і чи job також викликає `tauri-apps/tauri-action`.
|
|
65
|
-
* @param {string[]} lines усі рядки файла
|
|
66
|
-
* @param {number} fromLine рядок, з якого починати сканування (i + 1)
|
|
67
|
-
* @param {number} dashCol колонка dash-а step-list-а job-а (межа dedent-у)
|
|
68
|
-
* @returns {{hasCache: boolean, cacheLine: number, jobHasTauriAction: boolean}} результат сканування job-а
|
|
69
|
-
*/
|
|
70
|
-
function scanJobForCache(lines, fromLine, dashCol) {
|
|
71
|
-
let hasCache = false
|
|
72
|
-
let cacheLine = -1
|
|
73
|
-
let jobHasTauriAction = false
|
|
74
|
-
for (let j = fromLine; j < lines.length; j++) {
|
|
75
|
-
const line = lines[j]
|
|
76
|
-
if (line.trim() === '') continue
|
|
77
|
-
if (indentOf(line) < dashCol) break // dedent → вийшли зі step-list-а цього job-а
|
|
78
|
-
if (!hasCache && CACHE_RE.test(line)) {
|
|
79
|
-
hasCache = true
|
|
80
|
-
cacheLine = j
|
|
81
|
-
}
|
|
82
|
-
if (TAURI_ACTION_RE.test(line)) jobHasTauriAction = true
|
|
83
|
-
}
|
|
84
|
-
return { hasCache, cacheLine, jobHasTauriAction }
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Чи кеш-крок (`cacheLine`) уже має ключ `with.workspaces` у своєму блоці (до dedent-у).
|
|
89
|
-
* @param {string[]} lines усі рядки файла
|
|
90
|
-
* @param {number} cacheLine рядок кеш-кроку
|
|
91
|
-
* @param {number} dashCol колонка dash-а step-list-а job-а (межа dedent-у)
|
|
92
|
-
* @returns {boolean} true — ключ `workspaces` уже є
|
|
93
|
-
*/
|
|
94
|
-
function cacheStepHasWorkspaces(lines, cacheLine, dashCol) {
|
|
95
|
-
for (let j = cacheLine + 1; j < lines.length; j++) {
|
|
96
|
-
const line = lines[j]
|
|
97
|
-
if (line.trim() === '') continue
|
|
98
|
-
if (indentOf(line) < dashCol) break
|
|
99
|
-
if (WORKSPACES_KEY_RE.test(line)) return true
|
|
100
|
-
}
|
|
101
|
-
return false
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Сканує вміст workflow-файла й повертає по одному запису на кожен
|
|
106
|
-
* `dtolnay/rust-toolchain@…` крок, з інформацією про cache-крок і tauri-action
|
|
107
|
-
* у тому самому job-і (обмежено indentation-dedent-ом).
|
|
108
|
-
* @param {string} content вміст workflow-файла
|
|
109
|
-
* @returns {ToolchainStepScan[]} записи аналізу
|
|
110
|
-
*/
|
|
111
|
-
export function scanToolchainSteps(content) {
|
|
112
|
-
const lines = content.split('\n')
|
|
113
|
-
/** @type {ToolchainStepScan[]} */
|
|
114
|
-
const out = []
|
|
115
|
-
for (let i = 0; i < lines.length; i++) {
|
|
116
|
-
const usesCol = lines[i].indexOf('uses:')
|
|
117
|
-
if (usesCol === -1 || !TOOLCHAIN_RE.test(lines[i])) continue
|
|
118
|
-
const dashCol = dashColFor(usesCol)
|
|
119
|
-
const { hasCache, cacheLine, jobHasTauriAction } = scanJobForCache(lines, i + 1, dashCol)
|
|
120
|
-
const cacheHasWorkspaces = hasCache && cacheStepHasWorkspaces(lines, cacheLine, dashCol)
|
|
121
|
-
out.push({ line: i, dashCol, hasCache, cacheLine, cacheHasWorkspaces, jobHasTauriAction })
|
|
122
|
-
}
|
|
123
|
-
return out
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Каталог Rust-workspace-а для `Swatinem/rust-cache` `with.workspaces`, якщо
|
|
128
|
-
* `Cargo.toml` не в корені репо, а під `src-tauri/` (типовий Tauri-layout).
|
|
129
|
-
* `undefined`, якщо корінь репо вже є workspace-коренем (окремий крок не потрібен).
|
|
130
|
-
* @param {string} cwd корінь проєкту
|
|
131
|
-
* @returns {string|undefined} відносний шлях workspace-а або `undefined`
|
|
132
|
-
*/
|
|
133
|
-
export function tauriWorkspaceDir(cwd) {
|
|
134
|
-
if (existsSync(join(cwd, 'Cargo.toml'))) return
|
|
135
|
-
return existsSync(join(cwd, 'src-tauri', 'Cargo.toml')) ? 'src-tauri' : undefined
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст лінт-прогону
|
|
140
|
-
* @returns {Promise<import('../../../scripts/lib/lint-surface/types.mjs').LintResult>} результат перевірки
|
|
141
|
-
*/
|
|
142
|
-
export async function lint(ctx) {
|
|
143
|
-
const cwd = ctx.cwd
|
|
144
|
-
const reporter = createViolationReporter(ctx)
|
|
145
|
-
const wfDir = join(cwd, '.github', 'workflows')
|
|
146
|
-
if (!existsSync(wfDir)) return reporter.result()
|
|
147
|
-
|
|
148
|
-
const workspaceDir = tauriWorkspaceDir(cwd)
|
|
149
|
-
const entries = await readdir(wfDir)
|
|
150
|
-
const workflowFiles = entries.filter(f => f.endsWith('.yml') || f.endsWith('.yaml'))
|
|
151
|
-
|
|
152
|
-
for (const name of workflowFiles) {
|
|
153
|
-
const abs = join(wfDir, name)
|
|
154
|
-
const rel = relative(cwd, abs)
|
|
155
|
-
const content = await readFile(abs, 'utf8')
|
|
156
|
-
for (const step of scanToolchainSteps(content)) {
|
|
157
|
-
if (!step.hasCache) {
|
|
158
|
-
reporter.fail(
|
|
159
|
-
`${rel}: job зі \`dtolnay/rust-toolchain@stable\` потребує \`Swatinem/rust-cache@v2\` одразу після (rust.mdc)`,
|
|
160
|
-
{ reason: MISSING_RUST_CACHE, file: rel, data: { kind: MISSING_RUST_CACHE } }
|
|
161
|
-
)
|
|
162
|
-
continue
|
|
163
|
-
}
|
|
164
|
-
if (workspaceDir && step.jobHasTauriAction && !step.cacheHasWorkspaces) {
|
|
165
|
-
reporter.fail(
|
|
166
|
-
`${rel}: Swatinem/rust-cache@v2 у Tauri-job-і потребує \`with.workspaces: ${workspaceDir}\` (rust.mdc)`,
|
|
167
|
-
{
|
|
168
|
-
reason: MISSING_RUST_CACHE_WORKSPACES,
|
|
169
|
-
file: rel,
|
|
170
|
-
data: { kind: MISSING_RUST_CACHE_WORKSPACES, workspaceDir }
|
|
171
|
-
}
|
|
172
|
-
)
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
return reporter.result()
|
|
177
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
## Rust-cache після rust-toolchain — у будь-якому workflow
|
|
2
|
-
|
|
3
|
-
Concern: `rust/toolchain_cache` (JS-detector, `main.mjs` — не rego)
|
|
4
|
-
|
|
5
|
-
Цільові файли: `.github/workflows/*.yml`, `.github/workflows/*.yaml` (кожен файл окремо)
|
|
6
|
-
|
|
7
|
-
У кожному job-і, що ставить Rust toolchain через `dtolnay/rust-toolchain@stable`
|
|
8
|
-
(або будь-яку іншу версію), одразу після цього кроку має бути `Swatinem/rust-cache@v2` —
|
|
9
|
-
незалежно від того, lint це, coverage чи release/build job, що збирає Tauri-додаток
|
|
10
|
-
через `tauri-apps/tauri-action`. Без кешу CI щоразу заново качає весь cargo registry.
|
|
11
|
-
|
|
12
|
-
Це узагальнення перевірки `rust/lint_rust_yml` (яка звіряє канонічний
|
|
13
|
-
`.github/workflows/lint-rust.yml` цілком) — той concern лишається specific до
|
|
14
|
-
lint-workflow, цей — universal per-job перевірка для решти workflow-файлів
|
|
15
|
-
(coverage, release, будь-який кастомний).
|
|
16
|
-
|
|
17
|
-
Якщо job також викликає `tauri-apps/tauri-action`, а `Cargo.toml` лежить не в
|
|
18
|
-
корені репо, а під `<ws>/src-tauri/` (типовий Tauri-layout) — крок
|
|
19
|
-
`Swatinem/rust-cache@v2` має мати `with.workspaces: <ws>/src-tauri`.
|
|
20
|
-
|
|
21
|
-
Автофікс (`fix-toolchain_cache.mjs`, T0, `fixability: config`) вставляє відсутній
|
|
22
|
-
крок одразу після `dtolnay/rust-toolchain@…` (і `with.workspaces`, де потрібно)
|
|
23
|
-
текстовим splice-ом — зберігає формат/коментарі, ідемпотентно.
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://unpkg.com/@7n/rules/schemas/concern.json",
|
|
3
|
-
"fixability": "config",
|
|
4
|
-
"policy": {
|
|
5
|
-
"files": {
|
|
6
|
-
"single": ".github/workflows/lint-security.yml",
|
|
7
|
-
"required": true
|
|
8
|
-
},
|
|
9
|
-
"missingMessage": ".github/workflows/lint-security.yml не знайдено — створи за каноном security.mdc"
|
|
10
|
-
},
|
|
11
|
-
"requires": {
|
|
12
|
-
"capability": "ci:github"
|
|
13
|
-
}
|
|
14
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
type: JS Module
|
|
3
|
-
title: fix-lint_security_yml.mjs
|
|
4
|
-
resource: npm/rules/security/lint_security_yml/fix-lint_security_yml.mjs
|
|
5
|
-
docgen:
|
|
6
|
-
crc: cb66ccb7
|
|
7
|
-
model: openai-codex/gpt-5.5
|
|
8
|
-
tier: cloud-avg
|
|
9
|
-
score: 100
|
|
10
|
-
issues: judge:inaccurate:0.99
|
|
11
|
-
judgeModel: openai-codex/gpt-5.4-mini
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
## Огляд
|
|
15
|
-
|
|
16
|
-
`patterns` визначає межі пошуку в кодовій базі та свідомо оминає `.github` і `.git`, щоб перевірка не зачіпала службові каталоги. Правило працює в read-only режимі й не записує зміни у файлову систему або бази даних.
|
|
17
|
-
|
|
18
|
-
## Поведінка
|
|
19
|
-
|
|
20
|
-
1. `patterns` визначає правило автоматичного приведення workflow перевірки безпеки до еталонного шаблону.
|
|
21
|
-
|
|
22
|
-
2. Правило орієнтується на файл `.github/workflows/lint-security.yml`, щоб забезпечити наявність узгодженого процесу security lint у CI.
|
|
23
|
-
|
|
24
|
-
3. Під час підбору файлів для загальної обробки свідомо пропускаються службові шляхи `.github` і `.git`, щоб не аналізувати інфраструктурні каталоги як звичайний код.
|
|
25
|
-
|
|
26
|
-
4. Зміни не записуються напряму у файлову систему або зовнішні сховища: `patterns` лише описує доступне виправлення для подальшого застосування інструментом.
|
|
27
|
-
|
|
28
|
-
## Гарантії поведінки
|
|
29
|
-
|
|
30
|
-
- Read-only: не виконує операцій запису (ФС/БД).
|
|
31
|
-
- Свідомо пропускає шляхи: `.github`, `.git`.
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
type: Directory Index
|
|
3
|
-
title: npm/rules/security/lint_security_yml
|
|
4
|
-
resource: npm/rules/security/lint_security_yml/
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
| Файл | Тип |
|
|
8
|
-
| ----------------------------------------------------- | --------- |
|
|
9
|
-
| [fix-lint_security_yml.mjs](fix-lint_security_yml.md) | JS Module |
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
## Наявність кроку TruffleHog у CI workflow
|
|
2
|
-
|
|
3
|
-
Rego-пакет: `security.lint_security_yml`
|
|
4
|
-
|
|
5
|
-
Цільові файли: `.github/workflows/lint-security.yml`
|
|
6
|
-
|
|
7
|
-
Перевіряє, що серед `uses:` у workflow присутній крок `trufflesecurity/trufflehog@main`. Очікуваний перелік action-refs (не-`actions/*`) береться з `--data` через [lint-security.yml.snippet.yml](./template/lint-security.yml.snippet.yml). Універсальні кроки (`actions/*`) перевіряє `ga.workflow_common`.
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# Перевірка `.github/workflows/lint-security.yml` (security.mdc).
|
|
2
|
-
#
|
|
3
|
-
# Канон надходить через --data: { "template": { "snippet": ... } }, побудоване
|
|
4
|
-
# з template/lint-security.yml.snippet.yml. З нього збирається перелік
|
|
5
|
-
# `uses:` action-refs security-job (виключаючи універсальні `actions/*`, які
|
|
6
|
-
# валідує `ga.workflow_common`).
|
|
7
|
-
#
|
|
8
|
-
# Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
|
|
9
|
-
package security.lint_security_yml
|
|
10
|
-
|
|
11
|
-
import rego.v1
|
|
12
|
-
|
|
13
|
-
# Очікувані action-uses із template (не-`actions/*`).
|
|
14
|
-
expected_uses_blob := concat("\n", [u |
|
|
15
|
-
some step in data.template.snippet.jobs.security.steps
|
|
16
|
-
u := object.get(step, "uses", "")
|
|
17
|
-
u != ""
|
|
18
|
-
not startswith(u, "actions/")
|
|
19
|
-
])
|
|
20
|
-
|
|
21
|
-
# Усі `uses:` із input workflow.
|
|
22
|
-
all_uses_text := concat("\n", [u |
|
|
23
|
-
some job in object.get(input, "jobs", {})
|
|
24
|
-
some step in object.get(job, "steps", [])
|
|
25
|
-
u := object.get(step, "uses", "")
|
|
26
|
-
u != ""
|
|
27
|
-
])
|
|
28
|
-
|
|
29
|
-
deny contains msg if {
|
|
30
|
-
expected_uses_blob != ""
|
|
31
|
-
not contains(all_uses_text, expected_uses_blob)
|
|
32
|
-
msg := sprintf("lint-security.yml: відсутній крок з uses %q (security.mdc)", [expected_uses_blob])
|
|
33
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
name: Lint Security
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches:
|
|
6
|
-
- dev
|
|
7
|
-
- main
|
|
8
|
-
|
|
9
|
-
pull_request:
|
|
10
|
-
branches:
|
|
11
|
-
- dev
|
|
12
|
-
- main
|
|
13
|
-
|
|
14
|
-
concurrency:
|
|
15
|
-
group: ${{ github.ref }}-${{ github.workflow }}
|
|
16
|
-
cancel-in-progress: true
|
|
17
|
-
|
|
18
|
-
jobs:
|
|
19
|
-
security:
|
|
20
|
-
runs-on: ubuntu-latest
|
|
21
|
-
permissions:
|
|
22
|
-
contents: read
|
|
23
|
-
steps:
|
|
24
|
-
- uses: actions/checkout@v6
|
|
25
|
-
with:
|
|
26
|
-
persist-credentials: false
|
|
27
|
-
fetch-depth: 0
|
|
28
|
-
|
|
29
|
-
- uses: trufflesecurity/trufflehog@main
|
|
30
|
-
with:
|
|
31
|
-
extra_args: --results=verified,unknown
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
type: JS Module
|
|
3
|
-
title: fix-lint_style_yml.mjs
|
|
4
|
-
resource: npm/rules/style/lint_style_yml/fix-lint_style_yml.mjs
|
|
5
|
-
docgen:
|
|
6
|
-
crc: fc52d912
|
|
7
|
-
model: openai-codex/gpt-5.5
|
|
8
|
-
tier: cloud-avg
|
|
9
|
-
score: 100
|
|
10
|
-
issues: judge:inaccurate:0.99
|
|
11
|
-
judgeModel: openai-codex/gpt-5.4-mini
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
## Огляд
|
|
15
|
-
|
|
16
|
-
Публічна функція `patterns` надає шаблони для зовнішньої перевірки очікуваного стану коду. Модуль працює як read-only джерело правил: не змінює ФС чи БД і свідомо пропускає службові шляхи `.github` та `.git`.
|
|
17
|
-
|
|
18
|
-
## Поведінка
|
|
19
|
-
|
|
20
|
-
1. `patterns` визначає набір правил автоматичного виправлення для стандартного workflow перевірки стилю.
|
|
21
|
-
|
|
22
|
-
2. `patterns` спрямовує виправлення на файл `.github/workflows/lint-style.yml`, щоб підтримувати єдиний шаблон CI-перевірки стилю в проєкті.
|
|
23
|
-
|
|
24
|
-
3. `patterns` не виконує запис у файлову систему чи інші сховища самостійно, а лише описує очікувану зміну для зовнішнього механізму застосування.
|
|
25
|
-
|
|
26
|
-
4. Під час роботи свідомо не обробляються службові шляхи `.github` і `.git`, окрім цільового workflow-файлу, для якого визначено шаблонне виправлення.
|
|
27
|
-
|
|
28
|
-
## Гарантії поведінки
|
|
29
|
-
|
|
30
|
-
- Read-only: не виконує операцій запису (ФС/БД).
|
|
31
|
-
- Свідомо пропускає шляхи: `.github`, `.git`.
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
type: Directory Index
|
|
3
|
-
title: npm/rules/style/lint_style_yml
|
|
4
|
-
resource: npm/rules/style/lint_style_yml/
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
| Файл | Тип |
|
|
8
|
-
| ----------------------------------------------- | --------- |
|
|
9
|
-
| [fix-lint_style_yml.mjs](fix-lint_style_yml.md) | JS Module |
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
## GitHub Actions workflow: lint-style.yml
|
|
2
|
-
|
|
3
|
-
Rego-пакет: `style_lint.lint_style_yml`
|
|
4
|
-
|
|
5
|
-
Цільовий файл: `.github/workflows/lint-style.yml`
|
|
6
|
-
|
|
7
|
-
Перевіряє, що у workflow є крок `run`, який містить команду `n-rules lint style --no-fix` (substring-match по всіх кроках усіх jobs). Канонічний текст береться з template через `--data`.
|
|
8
|
-
|
|
9
|
-
Канонічний template: [lint-style.yml.snippet.yml](./template/lint-style.yml.snippet.yml)
|
|
10
|
-
|
|
11
|
-
**✓ Правильно** — крок `run: n-rules lint style --no-fix` присутній у jobs.stylelint.steps.
|
|
12
|
-
|
|
13
|
-
**✗ Неправильно** — крок відсутній або містить лише `echo nothing` / `npx stylelint` без канонічного рядка.
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
# Перевірка `lint-style.yml` (style.mdc).
|
|
2
|
-
#
|
|
3
|
-
# Канон надходить через --data: { "template": { "snippet": ... } }
|
|
4
|
-
# Структура --data сформована з template/lint-style.yml.snippet.yml.
|
|
5
|
-
# Маркер `run:` (npx stylelint substring) збирається з template's stylelint-job steps.
|
|
6
|
-
# Універсальні workflow-перевірки — у `ga.workflow_common`.
|
|
7
|
-
package style.lint_style_yml
|
|
8
|
-
|
|
9
|
-
import rego.v1
|
|
10
|
-
|
|
11
|
-
expected_run_blob := concat("\n", [r |
|
|
12
|
-
some step in data.template.snippet.jobs.stylelint.steps
|
|
13
|
-
r := object.get(step, "run", "")
|
|
14
|
-
r != ""
|
|
15
|
-
])
|
|
16
|
-
|
|
17
|
-
all_run_text := concat("\n", [run_text |
|
|
18
|
-
some job in object.get(input, "jobs", {})
|
|
19
|
-
some step in object.get(job, "steps", [])
|
|
20
|
-
run_text := step_run_to_text(step)
|
|
21
|
-
])
|
|
22
|
-
|
|
23
|
-
deny contains msg if {
|
|
24
|
-
expected_run_blob != ""
|
|
25
|
-
not contains(all_run_text, expected_run_blob)
|
|
26
|
-
msg := sprintf("lint-style.yml: жоден крок run не містить %q (style.mdc)", [expected_run_blob])
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
deny contains msg if {
|
|
30
|
-
contains(all_run_text, "bunx stylelint")
|
|
31
|
-
msg := "lint-style.yml: знайдено bunx stylelint — використовуй n-rules lint style --no-fix (style.mdc)"
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
step_run_to_text(step) := step.run if is_string(step.run)
|
|
35
|
-
|
|
36
|
-
else := concat("\n", [s | some s in step.run]) if is_array(step.run)
|
|
37
|
-
|
|
38
|
-
else := ""
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
name: StyleLint
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches:
|
|
6
|
-
- dev
|
|
7
|
-
- main
|
|
8
|
-
paths:
|
|
9
|
-
- '**/*.css'
|
|
10
|
-
- '**/*.scss'
|
|
11
|
-
- '**/*.vue'
|
|
12
|
-
|
|
13
|
-
pull_request:
|
|
14
|
-
branches:
|
|
15
|
-
- dev
|
|
16
|
-
- main
|
|
17
|
-
paths:
|
|
18
|
-
- '**/*.css'
|
|
19
|
-
- '**/*.scss'
|
|
20
|
-
- '**/*.vue'
|
|
21
|
-
|
|
22
|
-
concurrency:
|
|
23
|
-
group: ${{ github.ref }}-${{ github.workflow }}
|
|
24
|
-
cancel-in-progress: true
|
|
25
|
-
|
|
26
|
-
jobs:
|
|
27
|
-
stylelint:
|
|
28
|
-
runs-on: ubuntu-latest
|
|
29
|
-
permissions:
|
|
30
|
-
contents: read
|
|
31
|
-
steps:
|
|
32
|
-
- uses: actions/checkout@v6
|
|
33
|
-
with:
|
|
34
|
-
persist-credentials: false
|
|
35
|
-
|
|
36
|
-
- uses: ./.github/actions/setup-bun-deps
|
|
37
|
-
|
|
38
|
-
- name: StyleLint
|
|
39
|
-
run: bunx n-rules lint style --no-fix
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
type: JS Module
|
|
3
|
-
title: fix-lint_text.mjs
|
|
4
|
-
resource: npm/rules/text/lint_text/fix-lint_text.mjs
|
|
5
|
-
docgen:
|
|
6
|
-
crc: cefae829
|
|
7
|
-
model: openai-codex/gpt-5.4-mini
|
|
8
|
-
tier: cloud-min
|
|
9
|
-
score: 100
|
|
10
|
-
issues: judge:inaccurate:0.99
|
|
11
|
-
judgeModel: openai-codex/gpt-5.4-mini
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
## Огляд
|
|
15
|
-
|
|
16
|
-
`patterns` визначає, які текстові зміни вважаються придатними для узгодження з еталонною структурою workflow. Вона потрібна, щоб перевірка зосереджувалась на змісті потрібних файлів і не торкалась службових шляхів `.github` і `.git`.
|
|
17
|
-
|
|
18
|
-
## Поведінка
|
|
19
|
-
|
|
20
|
-
1. `patterns` формує набір правил для узгодження текстової перевірки з еталонною структурою workflow.
|
|
21
|
-
2. Підготовлює виправлення для цільового файлу `.github/workflows/lint-text.yml`.
|
|
22
|
-
3. Свідомо обходить службові шляхи `.github` і `.git`, щоб не зачіпати внутрішню інфраструктуру репозиторію.
|
|
23
|
-
4. Працює лише на читання: не змінює файлову систему і не виконує запис у БД.
|
|
24
|
-
|
|
25
|
-
## Гарантії поведінки
|
|
26
|
-
|
|
27
|
-
- Read-only: не виконує операцій запису (ФС/БД).
|
|
28
|
-
- Свідомо пропускає шляхи: `.github`, `.git`.
|