@nitra/cursor 1.13.83 → 1.13.85
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/.claude-template/commands/n-check.md +2 -2
- package/CHANGELOG.md +56 -27
- package/README.md +10 -10
- package/bin/n-cursor.js +40 -60
- package/package.json +1 -1
- package/rules/abie/abie.mdc +2 -2
- package/rules/abie/fix.mjs +5 -1
- package/rules/adr/fix.mjs +5 -1
- package/rules/adr/js/hooks/check.mjs +1 -1
- package/rules/bun/bun.mdc +1 -1
- package/rules/bun/fix.mjs +5 -1
- package/rules/bun/js/layout/check.mjs +1 -1
- package/rules/capacitor/fix.mjs +5 -1
- package/rules/capacitor/policy/package_json/package_json.rego +3 -3
- package/rules/changelog/changelog.mdc +1 -1
- package/rules/changelog/fix.mjs +5 -1
- package/rules/ci4/ci4.mdc +1 -1
- package/rules/ci4/fix.mjs +5 -1
- package/rules/docker/docker.mdc +6 -6
- package/rules/docker/fix.mjs +5 -1
- package/rules/docker/lint/lint.mjs +10 -3
- package/rules/docker/policy/package_json/package_json.rego +1 -1
- package/rules/efes/efes.mdc +1 -1
- package/rules/efes/fix.mjs +5 -1
- package/rules/feedback/feedback.mdc +2 -2
- package/rules/feedback/fix.mjs +5 -1
- package/rules/ga/fix.mjs +5 -1
- package/rules/ga/lint/lint.mjs +5 -5
- package/rules/ga/policy/workflow_common/workflow_common.rego +1 -1
- package/rules/graphql/fix.mjs +5 -1
- package/rules/graphql/policy/vscode_extensions/vscode_extensions.rego +2 -2
- package/rules/hasura/fix.mjs +5 -1
- package/rules/image-avif/fix.mjs +5 -1
- package/rules/image-avif/image-avif.mdc +1 -1
- package/rules/image-avif/js/avif_generation/check.mjs +1 -1
- package/rules/image-compress/fix.mjs +5 -1
- package/rules/image-compress/js/package_setup/check.mjs +1 -1
- package/rules/js-bun-db/fix.mjs +5 -1
- package/rules/js-bun-redis/fix.mjs +5 -1
- package/rules/js-lint/fix.mjs +5 -1
- package/rules/js-lint/js/tooling/check.mjs +2 -2
- package/rules/js-lint/js-lint.mdc +1 -1
- package/rules/js-mssql/fix.mjs +5 -1
- package/rules/js-mssql/policy/package_json/package_json.rego +2 -2
- package/rules/js-run/fix.mjs +5 -1
- package/rules/js-run/js/runtime/check.mjs +3 -3
- package/rules/k8s/fix.mjs +5 -1
- package/rules/k8s/js/manifests/check.mjs +1 -1
- package/rules/k8s/k8s.mdc +12 -12
- package/rules/k8s/lint/lint.mjs +12 -5
- package/rules/k8s/policy/base_kustomization/base_kustomization.rego +3 -3
- package/rules/k8s/policy/base_manifest/base_manifest.rego +2 -2
- package/rules/k8s/policy/gateway/gateway.rego +2 -2
- package/rules/k8s/policy/hasura_configmap/hasura_configmap.rego +3 -3
- package/rules/k8s/policy/hasura_httproute/hasura_httproute.rego +1 -1
- package/rules/k8s/policy/hpa_pdb/hpa_pdb.rego +1 -1
- package/rules/k8s/policy/kustomization/kustomization.rego +2 -2
- package/rules/k8s/policy/manifest/manifest.rego +4 -4
- package/rules/k8s/policy/svc_hl_yaml/svc_hl_yaml.rego +2 -2
- package/rules/k8s/policy/svc_yaml/svc_yaml.rego +2 -2
- package/rules/nginx-default-tpl/fix.mjs +5 -1
- package/rules/nginx-default-tpl/policy/vscode_extensions/vscode_extensions.rego +2 -2
- package/rules/nginx-default-tpl/policy/vscode_settings/vscode_settings.rego +1 -1
- package/rules/npm-module/fix.mjs +5 -1
- package/rules/npm-module/js/package_structure/check.mjs +2 -2
- package/rules/php/fix.mjs +5 -1
- package/rules/php/js/tooling/check.mjs +2 -2
- package/rules/rego/fix.mjs +5 -1
- package/rules/rego/lint/lint.mjs +12 -2
- package/rules/security/fix.mjs +5 -1
- package/rules/security/security.mdc +1 -1
- package/rules/style-lint/fix.mjs +5 -1
- package/rules/style-lint/js/tooling/check.mjs +2 -2
- package/rules/tauri/fix.mjs +5 -1
- package/rules/tauri/js/tooling/check.mjs +1 -1
- package/rules/tauri/policy/vscode_extensions/vscode_extensions.rego +2 -2
- package/rules/test/fix.mjs +5 -1
- package/rules/test/test.mdc +1 -1
- package/rules/text/fix.mjs +5 -1
- package/rules/text/js/formatting/check.mjs +2 -2
- package/rules/text/lint/lint.mjs +9 -2
- package/rules/text/text.mdc +1 -1
- package/rules/vue/fix.mjs +5 -1
- package/rules/vue/js/packages/check.mjs +1 -1
- package/rules/vue/policy/package_json/package_json.rego +1 -1
- package/rules/vue/vue.mdc +1 -1
- package/schemas/n-cursor.json +1 -1
- package/scripts/build-agents-commands.mjs +1 -1
- package/scripts/claude-stop-hook.mjs +1 -1
- package/scripts/utils/discover-check-rules-from-cursor.mjs +1 -1
- package/scripts/utils/read-n-cursor-config-lite.mjs +59 -0
- package/scripts/utils/run-rule-cli.mjs +37 -0
- package/scripts/utils/run-standard-rule.mjs +12 -3
- package/scripts/utils/with-lock.mjs +2 -2
- package/skills/fix/SKILL.md +5 -5
- package/skills/lint/SKILL.md +1 -1
|
@@ -28,14 +28,14 @@ export function check() {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
if (existsSync('package.json')) {
|
|
31
|
-
pass('package.json є (наявність lint-php перевіряє npx @nitra/cursor
|
|
31
|
+
pass('package.json є (наявність lint-php перевіряє npx @nitra/cursor fix → php.package_json)')
|
|
32
32
|
} else {
|
|
33
33
|
fail('package.json не знайдено в корені — додай (php.mdc)')
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
const wfPath = '.github/workflows/lint-php.yml'
|
|
37
37
|
if (existsSync(wfPath)) {
|
|
38
|
-
pass(`${wfPath} є (структуру перевіряє npx @nitra/cursor
|
|
38
|
+
pass(`${wfPath} є (структуру перевіряє npx @nitra/cursor fix → php.lint_php_yml)`)
|
|
39
39
|
} else {
|
|
40
40
|
fail(`${wfPath} не існує — створи згідно php.mdc`)
|
|
41
41
|
}
|
package/rules/rego/fix.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import { runStandardRule } from '../../scripts/utils/run-standard-rule.mjs'
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Запускає правило: applies → JS-concerns → policy → mdc-refs (через runStandardRule).
|
|
5
|
+
* Library mode: викликається CLI orchestration через `import + run(ctx)`.
|
|
5
6
|
* @param {import('../../scripts/utils/run-standard-rule.mjs').RuleContext} [ctx] контекст прогону (walkCache тощо)
|
|
6
7
|
* @returns {Promise<number>} 0 — OK, 1 — порушення
|
|
7
8
|
*/
|
|
@@ -10,6 +11,9 @@ export function run(ctx) {
|
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
if (import.meta.main) {
|
|
14
|
+
// Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
|
|
15
|
+
// (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
|
|
16
|
+
const { runRuleCli } = await import('../../scripts/utils/run-rule-cli.mjs')
|
|
13
17
|
// eslint-disable-next-line unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
|
|
14
|
-
process.exit(await
|
|
18
|
+
process.exit(await runRuleCli(import.meta.dirname))
|
|
15
19
|
}
|
package/rules/rego/lint/lint.mjs
CHANGED
|
@@ -29,6 +29,7 @@ import { resolve } from 'node:path'
|
|
|
29
29
|
|
|
30
30
|
import { isRunAsCli } from '../../../scripts/cli-entry.mjs'
|
|
31
31
|
import { resolveCmd } from '../../../scripts/utils/resolve-cmd.mjs'
|
|
32
|
+
import { withLock } from '../../../scripts/utils/with-lock.mjs'
|
|
32
33
|
|
|
33
34
|
/** Шляхи з Rego-полісі (відносно cwd). Існують не всі на ранніх стадіях — фільтруємо нижче. */
|
|
34
35
|
const LINT_TARGETS = ['npm/rules']
|
|
@@ -89,10 +90,13 @@ function runStep(bin, args, cwd) {
|
|
|
89
90
|
/**
|
|
90
91
|
* Запускає `opa check --strict` і `regal lint` по існуючих цілях. Якщо жодної цілі немає —
|
|
91
92
|
* пропускає лінт із кодом 0. Якщо хоча б один preflight не пройшов — exit 1 ще до запусків.
|
|
93
|
+
*
|
|
94
|
+
* Внутрішня форма без локу — для тестів, які працюють у тимчасових каталогах і мають
|
|
95
|
+
* можливість запускати fresh без дедуплікації проти попереднього прогону.
|
|
92
96
|
* @param {string} [cwd] робочий каталог (за замовчуванням `process.cwd()`)
|
|
93
97
|
* @returns {number} 0 — OK або skip; інакше код виходу першого кроку, що впав
|
|
94
98
|
*/
|
|
95
|
-
export function
|
|
99
|
+
export function runLintRegoSteps(cwd = process.cwd()) {
|
|
96
100
|
const root = resolve(cwd)
|
|
97
101
|
const opa = resolveCmd('opa')
|
|
98
102
|
const regal = resolveCmd('regal')
|
|
@@ -130,6 +134,12 @@ export function runLintRego(cwd = process.cwd()) {
|
|
|
130
134
|
return runStep(conftest, ['verify', ...targets.flatMap(t => ['-p', t])], root)
|
|
131
135
|
}
|
|
132
136
|
|
|
137
|
+
/**
|
|
138
|
+
* Публічна CLI-форма: серіалізує через `withLock('lint-rego')` + дедуп за станом git-дерева.
|
|
139
|
+
* @returns {Promise<number>} код виходу
|
|
140
|
+
*/
|
|
141
|
+
export const runLintRego = () => withLock('lint-rego', () => runLintRegoSteps())
|
|
142
|
+
|
|
133
143
|
if (isRunAsCli()) {
|
|
134
|
-
process.exitCode = runLintRego()
|
|
144
|
+
process.exitCode = await runLintRego()
|
|
135
145
|
}
|
package/rules/security/fix.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import { runStandardRule } from '../../scripts/utils/run-standard-rule.mjs'
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Запускає правило: applies → JS-concerns → policy → mdc-refs (через runStandardRule).
|
|
5
|
+
* Library mode: викликається CLI orchestration через `import + run(ctx)`.
|
|
5
6
|
* @param {import('../../scripts/utils/run-standard-rule.mjs').RuleContext} [ctx] контекст прогону (walkCache тощо)
|
|
6
7
|
* @returns {Promise<number>} 0 — OK, 1 — порушення
|
|
7
8
|
*/
|
|
@@ -10,6 +11,9 @@ export function run(ctx) {
|
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
if (import.meta.main) {
|
|
14
|
+
// Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
|
|
15
|
+
// (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
|
|
16
|
+
const { runRuleCli } = await import('../../scripts/utils/run-rule-cli.mjs')
|
|
13
17
|
// eslint-disable-next-line unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
|
|
14
|
-
process.exit(await
|
|
18
|
+
process.exit(await runRuleCli(import.meta.dirname))
|
|
15
19
|
}
|
package/rules/style-lint/fix.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import { runStandardRule } from '../../scripts/utils/run-standard-rule.mjs'
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Запускає правило: applies → JS-concerns → policy → mdc-refs (через runStandardRule).
|
|
5
|
+
* Library mode: викликається CLI orchestration через `import + run(ctx)`.
|
|
5
6
|
* @param {import('../../scripts/utils/run-standard-rule.mjs').RuleContext} [ctx] контекст прогону (walkCache тощо)
|
|
6
7
|
* @returns {Promise<number>} 0 — OK, 1 — порушення
|
|
7
8
|
*/
|
|
@@ -10,6 +11,9 @@ export function run(ctx) {
|
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
if (import.meta.main) {
|
|
14
|
+
// Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
|
|
15
|
+
// (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
|
|
16
|
+
const { runRuleCli } = await import('../../scripts/utils/run-rule-cli.mjs')
|
|
13
17
|
// eslint-disable-next-line unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
|
|
14
|
-
process.exit(await
|
|
18
|
+
process.exit(await runRuleCli(import.meta.dirname))
|
|
15
19
|
}
|
|
@@ -44,7 +44,7 @@ async function checkStylelintConfigPresence(reporter) {
|
|
|
44
44
|
// `.vscode/extensions.json` (`stylelint.vscode-stylelint`) і `.vscode/settings.json`
|
|
45
45
|
// (`css.validate`/`scss.validate`/`less.validate: false`) — у rego-пакетах
|
|
46
46
|
// `style_lint.vscode_extensions` і `style_lint.vscode_settings`, прогоняє
|
|
47
|
-
// `npx @nitra/cursor
|
|
47
|
+
// `npx @nitra/cursor fix`. JS-копії видалено, щоб не було двох джерел істини.
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
50
|
* Перевіряє відповідність проєкту правилам style-lint.mdc
|
|
@@ -64,7 +64,7 @@ export async function check() {
|
|
|
64
64
|
|
|
65
65
|
const wfPath = '.github/workflows/lint-style.yml'
|
|
66
66
|
if (existsSync(wfPath)) {
|
|
67
|
-
pass(`${wfPath} є (структуру перевіряє npx @nitra/cursor
|
|
67
|
+
pass(`${wfPath} є (структуру перевіряє npx @nitra/cursor fix → style_lint.lint_style_yml)`)
|
|
68
68
|
} else {
|
|
69
69
|
fail(`${wfPath} не існує — створи його`)
|
|
70
70
|
}
|
package/rules/tauri/fix.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import { runStandardRule } from '../../scripts/utils/run-standard-rule.mjs'
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Запускає правило: applies → JS-concerns → policy → mdc-refs (через runStandardRule).
|
|
5
|
+
* Library mode: викликається CLI orchestration через `import + run(ctx)`.
|
|
5
6
|
* @param {import('../../scripts/utils/run-standard-rule.mjs').RuleContext} [ctx] контекст прогону (walkCache тощо)
|
|
6
7
|
* @returns {Promise<number>} 0 — OK, 1 — порушення
|
|
7
8
|
*/
|
|
@@ -10,6 +11,9 @@ export function run(ctx) {
|
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
if (import.meta.main) {
|
|
14
|
+
// Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
|
|
15
|
+
// (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
|
|
16
|
+
const { runRuleCli } = await import('../../scripts/utils/run-rule-cli.mjs')
|
|
13
17
|
// eslint-disable-next-line unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
|
|
14
|
-
process.exit(await
|
|
18
|
+
process.exit(await runRuleCli(import.meta.dirname))
|
|
15
19
|
}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* 3. Інакше — для `.vscode/extensions.json` зробити FS-existence + делегувати
|
|
13
13
|
* content `rego.tauri.vscode_extensions` через `runConftestBatch`.
|
|
14
14
|
*
|
|
15
|
-
* Rego-полісі глобально без `target.json` поруч (не auto-discoverable через `n-cursor
|
|
15
|
+
* Rego-полісі глобально без `target.json` поруч (не auto-discoverable через `n-cursor fix`) — це conditional
|
|
16
16
|
* правило. Plan B: Rego-authoritative + JS-orchestrator з `runConftestBatch`.
|
|
17
17
|
*/
|
|
18
18
|
import { existsSync, statSync } from 'node:fs'
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# Перевірка `.vscode/extensions.json` для tauri (tauri.mdc).
|
|
2
2
|
#
|
|
3
|
-
# Викликається з `
|
|
3
|
+
# Викликається з `rules/tauri/fix.mjs` через `runConftestBatch` лише ПІСЛЯ того,
|
|
4
4
|
# як JS виявив маркер Tauri-проєкту (`src-tauri/` каталог, `tauri.conf.json`
|
|
5
5
|
# у будь-якому пакеті, або залежність `@tauri-apps/*`). Без `target.json` поруч
|
|
6
|
-
# (не auto-discoverable через `n-cursor
|
|
6
|
+
# (не auto-discoverable через `n-cursor fix`) — інакше false-positive порушення на не-Tauri проєктах.
|
|
7
7
|
#
|
|
8
8
|
# Canonical (tauri.mdc): `recommendations` має містити обидва записи —
|
|
9
9
|
# - tauri-apps.tauri-vscode
|
package/rules/test/fix.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import { runStandardRule } from '../../scripts/utils/run-standard-rule.mjs'
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Запускає правило: applies → JS-concerns → policy → mdc-refs (через runStandardRule).
|
|
5
|
+
* Library mode: викликається CLI orchestration через `import + run(ctx)`.
|
|
5
6
|
* @param {import('../../scripts/utils/run-standard-rule.mjs').RuleContext} [ctx] контекст прогону (walkCache тощо)
|
|
6
7
|
* @returns {Promise<number>} 0 — OK, 1 — порушення
|
|
7
8
|
*/
|
|
@@ -10,6 +11,9 @@ export function run(ctx) {
|
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
if (import.meta.main) {
|
|
14
|
+
// Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
|
|
15
|
+
// (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
|
|
16
|
+
const { runRuleCli } = await import('../../scripts/utils/run-rule-cli.mjs')
|
|
13
17
|
// eslint-disable-next-line unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
|
|
14
|
-
process.exit(await
|
|
18
|
+
process.exit(await runRuleCli(import.meta.dirname))
|
|
15
19
|
}
|
package/rules/test/test.mdc
CHANGED
|
@@ -53,7 +53,7 @@ Recursive globs ловлять файли всередині `tests/` так с
|
|
|
53
53
|
|
|
54
54
|
## Що перевіряє правило
|
|
55
55
|
|
|
56
|
-
`npx @nitra/cursor
|
|
56
|
+
`npx @nitra/cursor fix test` (concern `location`) проходить деревом пакета й перевіряє: **кожен `*.test.mjs` файл лежить усередині каталогу з ім'ям `tests`** (точне співпадіння басенейму батьківської директорії). Файли поза цим каталогом репортуються з порадою куди їх перенести.
|
|
57
57
|
|
|
58
58
|
`*_test.rego` перевіркою **не охоплюються** — вони не переміщуються.
|
|
59
59
|
|
package/rules/text/fix.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import { runStandardRule } from '../../scripts/utils/run-standard-rule.mjs'
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Запускає правило: applies → JS-concerns → policy → mdc-refs (через runStandardRule).
|
|
5
|
+
* Library mode: викликається CLI orchestration через `import + run(ctx)`.
|
|
5
6
|
* @param {import('../../scripts/utils/run-standard-rule.mjs').RuleContext} [ctx] контекст прогону (walkCache тощо)
|
|
6
7
|
* @returns {Promise<number>} 0 — OK, 1 — порушення
|
|
7
8
|
*/
|
|
@@ -10,6 +11,9 @@ export function run(ctx) {
|
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
if (import.meta.main) {
|
|
14
|
+
// Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
|
|
15
|
+
// (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
|
|
16
|
+
const { runRuleCli } = await import('../../scripts/utils/run-rule-cli.mjs')
|
|
13
17
|
// eslint-disable-next-line unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
|
|
14
|
-
process.exit(await
|
|
18
|
+
process.exit(await runRuleCli(import.meta.dirname))
|
|
15
19
|
}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* - абзац про український апостроф у `.cursor/rules/n-text.mdc` /
|
|
12
12
|
* `npm/mdc/text.mdc` (markdown-текст, не JSON/YAML);
|
|
13
13
|
* - складна валідація скрипта `lint-text` (cspell, markdownlint, v8r у трьох
|
|
14
|
-
* варіантах, run-
|
|
14
|
+
* варіантах, run-shellrules/text/fix.mjs, обовʼязкові glob-и);
|
|
15
15
|
* - workflow `lint-text.yml` має крок `bun run lint-text` (структура — rego `text.lint_text`).
|
|
16
16
|
*
|
|
17
17
|
* **Що покрила Rego** (`npx \@nitra/cursor check`):
|
|
@@ -111,7 +111,7 @@ function checkTextConfigsExistence(passFn, failFn) {
|
|
|
111
111
|
['.vscode/settings.json', 'text.vscode_settings']
|
|
112
112
|
]) {
|
|
113
113
|
if (existsSync(path)) {
|
|
114
|
-
passFn(`${path} є (структуру перевіряє npx @nitra/cursor
|
|
114
|
+
passFn(`${path} є (структуру перевіряє npx @nitra/cursor fix → ${mdcRef})`)
|
|
115
115
|
} else {
|
|
116
116
|
failFn(`${path} не існує — створи згідно n-text.mdc`)
|
|
117
117
|
}
|
package/rules/text/lint/lint.mjs
CHANGED
|
@@ -18,6 +18,7 @@ import { platform } from 'node:process'
|
|
|
18
18
|
|
|
19
19
|
import { runLintStep } from '../../../scripts/utils/run-lint-step.mjs'
|
|
20
20
|
import { resolveCmd } from '../../../scripts/utils/resolve-cmd.mjs'
|
|
21
|
+
import { withLock } from '../../../scripts/utils/with-lock.mjs'
|
|
21
22
|
import { runDotenvLinter } from './run-dotenv-linter.mjs'
|
|
22
23
|
import { runShellcheckText } from './run-shellcheck.mjs'
|
|
23
24
|
import { runV8rWithGlobs } from './run-v8r.mjs'
|
|
@@ -120,10 +121,10 @@ function preflight(dep) {
|
|
|
120
121
|
}
|
|
121
122
|
|
|
122
123
|
/**
|
|
123
|
-
*
|
|
124
|
+
* Внутрішні кроки `lint-text` без локу.
|
|
124
125
|
* @returns {number} 0 — все OK, інакше — код першого кроку, що впав
|
|
125
126
|
*/
|
|
126
|
-
|
|
127
|
+
function runLintTextSteps() {
|
|
127
128
|
let preflightOk = true
|
|
128
129
|
for (const dep of [SHELLCHECK_PREFLIGHT, PATCH_PREFLIGHT, DOTENV_LINTER_PREFLIGHT]) {
|
|
129
130
|
if (!preflight(dep)) preflightOk = false
|
|
@@ -147,3 +148,9 @@ export function runLintTextCli() {
|
|
|
147
148
|
console.log('\n▶ v8r (schema-валідація json/json5/yaml/yml/toml)')
|
|
148
149
|
return runV8rWithGlobs()
|
|
149
150
|
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Публічна CLI-форма: серіалізує через `withLock('lint-text')` + дедуп за станом git-дерева.
|
|
154
|
+
* @returns {Promise<number>} код виходу
|
|
155
|
+
*/
|
|
156
|
+
export const runLintTextCli = () => withLock('lint-text', () => runLintTextSteps())
|
package/rules/text/text.mdc
CHANGED
|
@@ -260,4 +260,4 @@ kebab-case
|
|
|
260
260
|
|
|
261
261
|
## Перевірка
|
|
262
262
|
|
|
263
|
-
`npx @nitra/cursor
|
|
263
|
+
`npx @nitra/cursor fix text` (охоплює oxfmt, cspell, shellcheck у `lint-text`, markdownlint, v8r, CI для `lint-text`)
|
package/rules/vue/fix.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import { runStandardRule } from '../../scripts/utils/run-standard-rule.mjs'
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Запускає правило: applies → JS-concerns → policy → mdc-refs (через runStandardRule).
|
|
5
|
+
* Library mode: викликається CLI orchestration через `import + run(ctx)`.
|
|
5
6
|
* @param {import('../../scripts/utils/run-standard-rule.mjs').RuleContext} [ctx] контекст прогону (walkCache тощо)
|
|
6
7
|
* @returns {Promise<number>} 0 — OK, 1 — порушення
|
|
7
8
|
*/
|
|
@@ -10,6 +11,9 @@ export function run(ctx) {
|
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
if (import.meta.main) {
|
|
14
|
+
// Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
|
|
15
|
+
// (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
|
|
16
|
+
const { runRuleCli } = await import('../../scripts/utils/run-rule-cli.mjs')
|
|
13
17
|
// eslint-disable-next-line unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
|
|
14
|
-
process.exit(await
|
|
18
|
+
process.exit(await runRuleCli(import.meta.dirname))
|
|
15
19
|
}
|
|
@@ -413,7 +413,7 @@ async function checkVueImportViolations(rootDir, absPackageRoot, ignorePaths, ha
|
|
|
413
413
|
*/
|
|
414
414
|
async function checkVuePackage(rootDir, ignorePaths, fail, passFn) {
|
|
415
415
|
const prefix = `[${packageLabel(rootDir)}] `
|
|
416
|
-
passFn(`${prefix}package.json залежності перевіряє npx @nitra/cursor
|
|
416
|
+
passFn(`${prefix}package.json залежності перевіряє npx @nitra/cursor fix → vue.package_json`)
|
|
417
417
|
|
|
418
418
|
await checkViteClientEnvAndEditorConfig(rootDir, prefix, passFn, fail)
|
|
419
419
|
|
package/rules/vue/vue.mdc
CHANGED
|
@@ -341,4 +341,4 @@ import path from 'node:path'
|
|
|
341
341
|
|
|
342
342
|
## Перевірка
|
|
343
343
|
|
|
344
|
-
`npx @nitra/cursor
|
|
344
|
+
`npx @nitra/cursor fix vue` — перевіряє залежності, `vite.config`, наявність **`src/vite-env.d.ts`** з `/// <reference types="vite/client" />` та **`jsconfig.json`** у корені Vue-пакета; обходить джерела Vue-пакета (`.vue`, `.ts`, `.js` тощо) на заборонені value-імпорти з модуля `vue` (дозволені лише type-only та side-effect `import 'vue'`) і додатково сканує `.vue` SFC на імпорти Node-нативних модулів (`node:*` префікс або bare-ім’я вбудованого модуля Node — `fs`, `path`, `timers/promises` тощо). Імпорти аналізуються через **oxc-parser** (`module.staticImports`); для `.vue` вміст `<script>` витягується з SFC, далі той самий парсер (логіка в `npm/rules/vue/js/packages/vue-forbidden-imports.mjs`).
|
package/schemas/n-cursor.json
CHANGED
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
},
|
|
46
46
|
"ignore": {
|
|
47
47
|
"type": "array",
|
|
48
|
-
"description": "Директорії, що повністю виключаються з обходу check-скриптів CLI (npx @nitra/cursor
|
|
48
|
+
"description": "Директорії, що повністю виключаються з обходу check-скриптів CLI (npx @nitra/cursor fix ...) і AI-модифікацій (AI не редагує, не видаляє, не створює файли). Шляхи відносно кореня репозиторію (posix). Приклади: vendored Helm-чарти, генеровані маніфести, legacy-дерева. Стандартні виключення (node_modules, .git, dist, coverage, .turbo, .next) застосовуються завжди — додавати їх не треба.",
|
|
49
49
|
"items": {
|
|
50
50
|
"type": "string",
|
|
51
51
|
"minLength": 1
|
|
@@ -81,7 +81,7 @@ export async function buildAgentsCommandBulletItems(projectRoot) {
|
|
|
81
81
|
{
|
|
82
82
|
name: `- **Оновити правила та ${AGENTS_MD}** (після змін у правилах/шаблоні CLI): \`npx ${PACKAGE_NAME}\``
|
|
83
83
|
},
|
|
84
|
-
{ name: `- **Перевірки правил (programmatic)**: \`npx ${PACKAGE_NAME}
|
|
84
|
+
{ name: `- **Перевірки правил (programmatic)**: \`npx ${PACKAGE_NAME} fix\`` },
|
|
85
85
|
{ name: `- **knip (невикористані залежності та експорти)**: \`bunx knip\`` }
|
|
86
86
|
)
|
|
87
87
|
|
|
@@ -68,7 +68,7 @@ export async function runStopHookCli() {
|
|
|
68
68
|
const [code] = await once(child, 'exit')
|
|
69
69
|
return code ?? 1
|
|
70
70
|
} catch (error) {
|
|
71
|
-
process.stderr.write(`stop-hook: не вдалося запустити npx @nitra/cursor
|
|
71
|
+
process.stderr.write(`stop-hook: не вдалося запустити npx @nitra/cursor fix — ${error.message}\n`)
|
|
72
72
|
return 1
|
|
73
73
|
}
|
|
74
74
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Визначає список id правил для `npx @nitra/cursor
|
|
2
|
+
* Визначає список id правил для `npx @nitra/cursor fix` без аргументів:
|
|
3
3
|
* зчитує базові імена `*.mdc` у `.cursor/rules/` і залишає лише ті id,
|
|
4
4
|
* для яких у пакеті є programmatic перевірка (JS-концерн або policy з target.json).
|
|
5
5
|
*/
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Light read-only `.n-cursor.json` reader для standalone `fix.mjs` invocation.
|
|
3
|
+
*
|
|
4
|
+
* НЕ робить auto-rules detection, merge, schema sync — це справа повного `readConfig` у CLI.
|
|
5
|
+
* Тут лише: прочитати файл (якщо є), повернути `{ rules: string[], disableRules: string[] }`.
|
|
6
|
+
*
|
|
7
|
+
* Спостереження whitelist:
|
|
8
|
+
* - якщо `.n-cursor.json` НЕМАЄ → правило вважається enabled (поведінка "open by default"),
|
|
9
|
+
* щоб `bun rules/<id>/fix.mjs` з будь-якої тимчасової директорії працювало для debug.
|
|
10
|
+
* - якщо файл є з `rules:[…]`, але правила там немає → правило не enabled.
|
|
11
|
+
* - якщо правило в `disable-rules` → не enabled, навіть якщо у `rules:[…]`.
|
|
12
|
+
*/
|
|
13
|
+
import { existsSync } from 'node:fs'
|
|
14
|
+
import { readFile } from 'node:fs/promises'
|
|
15
|
+
import { join } from 'node:path'
|
|
16
|
+
|
|
17
|
+
const CONFIG_FILE = '.n-cursor.json'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {object} LiteConfig
|
|
21
|
+
* @property {boolean} exists чи існує .n-cursor.json у поточному каталозі
|
|
22
|
+
* @property {string[]} rules id правил з whitelist (порожній якщо файл відсутній)
|
|
23
|
+
* @property {string[]} disableRules id правил, явно вимкнених у `disable-rules`
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {string} [cwd] корінь, у якому шукати .n-cursor.json (default — `process.cwd()`)
|
|
28
|
+
* @returns {Promise<LiteConfig>} стан конфігу
|
|
29
|
+
*/
|
|
30
|
+
export async function readNCursorConfigLite(cwd = process.cwd()) {
|
|
31
|
+
const configPath = join(cwd, CONFIG_FILE)
|
|
32
|
+
if (!existsSync(configPath)) {
|
|
33
|
+
return { exists: false, rules: [], disableRules: [] }
|
|
34
|
+
}
|
|
35
|
+
const raw = await readFile(configPath, 'utf8')
|
|
36
|
+
/** @type {{ rules?: unknown, ['disable-rules']?: unknown }} */
|
|
37
|
+
const parsed = JSON.parse(raw)
|
|
38
|
+
const rules = Array.isArray(parsed.rules) ? parsed.rules.filter(r => typeof r === 'string') : []
|
|
39
|
+
const disableRules = Array.isArray(parsed['disable-rules'])
|
|
40
|
+
? parsed['disable-rules'].filter(r => typeof r === 'string')
|
|
41
|
+
: []
|
|
42
|
+
return { exists: true, rules, disableRules }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Чи активне правило згідно з конфігом.
|
|
47
|
+
* - файл відсутній → true (open by default для debug);
|
|
48
|
+
* - правило явно в `disable-rules` → false;
|
|
49
|
+
* - правило у `rules` → true;
|
|
50
|
+
* - інакше → false.
|
|
51
|
+
* @param {LiteConfig} config розпарсений lite-конфіг
|
|
52
|
+
* @param {string} ruleId id правила (= basename каталогу)
|
|
53
|
+
* @returns {boolean} чи запускати правило
|
|
54
|
+
*/
|
|
55
|
+
export function isRuleEnabled(config, ruleId) {
|
|
56
|
+
if (!config.exists) return true
|
|
57
|
+
if (config.disableRules.includes(ruleId)) return false
|
|
58
|
+
return config.rules.includes(ruleId)
|
|
59
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standalone CLI runner для одного правила. Викликається з `rules/<id>/fix.mjs`
|
|
3
|
+
* у блоці `if (import.meta.main)` — це робить `bun rules/<id>/fix.mjs` повним
|
|
4
|
+
* еквівалентом старого `npx @nitra/cursor fix <id>`: читає `.n-cursor.json`,
|
|
5
|
+
* перевіряє whitelist, друкує summary, повертає aggregated exit-code.
|
|
6
|
+
*
|
|
7
|
+
* Library-mode виклик з CLI orchestration — інше: див. `runStandardRule` + `fix.mjs::run(ctx)`.
|
|
8
|
+
*/
|
|
9
|
+
import { basename } from 'node:path'
|
|
10
|
+
|
|
11
|
+
import { isRuleEnabled, readNCursorConfigLite } from './read-n-cursor-config-lite.mjs'
|
|
12
|
+
import { runStandardRule } from './run-standard-rule.mjs'
|
|
13
|
+
import { getOrCreateWalkCache } from './walk-cache.mjs'
|
|
14
|
+
|
|
15
|
+
const PACKAGE_NAME = '@nitra/cursor'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {string} ruleDir абсолютний шлях до `rules/<id>/`
|
|
19
|
+
* @returns {Promise<number>} 0 — OK або правило не enabled; 1 — порушення
|
|
20
|
+
*/
|
|
21
|
+
export async function runRuleCli(ruleDir) {
|
|
22
|
+
const ruleId = basename(ruleDir)
|
|
23
|
+
const config = await readNCursorConfigLite()
|
|
24
|
+
|
|
25
|
+
if (!isRuleEnabled(config, ruleId)) {
|
|
26
|
+
console.log(`\n🔍 ${PACKAGE_NAME} fix ${ruleId} — правило не в \`.n-cursor.json:rules\`. Пропущено.\n`)
|
|
27
|
+
return 0
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
console.log(`\n🔍 ${PACKAGE_NAME} fix ${ruleId} — перевірка правила\n`)
|
|
31
|
+
|
|
32
|
+
const walkCache = getOrCreateWalkCache()
|
|
33
|
+
const exitCode = await runStandardRule(ruleDir, { walkCache })
|
|
34
|
+
const ok = exitCode === 0
|
|
35
|
+
console.log(`\n✨ Результат: ${ok ? 1 : 0}/1 правил без зауважень\n`)
|
|
36
|
+
return exitCode
|
|
37
|
+
}
|
|
@@ -3,12 +3,19 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Інкапсулює: `discoverOneRule` → `runRule(applies → JS → policy → mdc-refs)`.
|
|
5
5
|
* Локальна логіка в правилах заборонена; розширення поведінки — через `ctx`-опції.
|
|
6
|
+
*
|
|
7
|
+
* Серіалізація: загортає виконання у `withLock('fix-<ruleId>')` — паралельні запуски
|
|
8
|
+
* того самого правила (через `npx @nitra/cursor fix`, прямий `bun rules/<id>/fix.mjs`
|
|
9
|
+
* чи `run(ctx)`-композицію) дедупляться за станом git-дерева; різні правила можуть
|
|
10
|
+
* виконуватись паралельно. Точка інтеграції — тут, щоб не дублювати лок у кожному
|
|
11
|
+
* `fix.mjs`.
|
|
6
12
|
*/
|
|
7
13
|
import { basename, dirname } from 'node:path'
|
|
8
14
|
|
|
9
15
|
import { discoverOneRule } from './discover-checkable-rules.mjs'
|
|
10
16
|
import { runRule } from './run-rule.mjs'
|
|
11
17
|
import { getOrCreateWalkCache } from './walk-cache.mjs'
|
|
18
|
+
import { withLock } from './with-lock.mjs'
|
|
12
19
|
|
|
13
20
|
/**
|
|
14
21
|
* @typedef {object} RuleContext
|
|
@@ -28,7 +35,9 @@ import { getOrCreateWalkCache } from './walk-cache.mjs'
|
|
|
28
35
|
export async function runStandardRule(ruleDir, ctx = {}) {
|
|
29
36
|
const ruleId = basename(ruleDir)
|
|
30
37
|
const bundledRulesDir = dirname(ruleDir)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
38
|
+
return withLock(`fix-${ruleId}`, async () => {
|
|
39
|
+
const rule = await discoverOneRule(ruleDir, ruleId)
|
|
40
|
+
const walkCache = ctx.walkCache ?? getOrCreateWalkCache()
|
|
41
|
+
return runRule(rule, bundledRulesDir, walkCache)
|
|
42
|
+
})
|
|
34
43
|
}
|
|
@@ -40,8 +40,8 @@ export function shouldDedup(result, fingerprint, ttl) {
|
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
42
|
* @param {string} key
|
|
43
|
-
* @param {() => Promise<number>} runFn
|
|
44
|
-
* @param {{ttl?:number, staleThreshold?:number, waitTimeout?:number, pollInterval?:number, cacheDir?:string}} [opts]
|
|
43
|
+
* @param {() => number | Promise<number>} runFn
|
|
44
|
+
* @param {{ttl?:number, staleThreshold?:number, waitTimeout?:number, pollInterval?:number, cacheDir?:string, getFingerprint?:() => string | null}} [opts]
|
|
45
45
|
* @returns {Promise<number>}
|
|
46
46
|
*/
|
|
47
47
|
export async function withLock(key, runFn, opts = {}) {
|
package/skills/fix/SKILL.md
CHANGED
|
@@ -8,14 +8,14 @@ description: >-
|
|
|
8
8
|
|
|
9
9
|
## Scope
|
|
10
10
|
|
|
11
|
-
Цей скіл відповідає **лише за структуру** проєкту: щоб `.cursor/rules/` + `npx @nitra/cursor
|
|
11
|
+
Цей скіл відповідає **лише за структуру** проєкту: щоб `.cursor/rules/` + `npx @nitra/cursor fix` були задоволені (наявність конфігів, залежностей, скриптів, GitHub workflows, відсутність заборонених файлів). **Лінт-порушення у самому коді** (ESLint, oxlint, jscpd, cspell, knip, sonarjs, stylelint тощо) — **поза скоупом**; їх діагностує й виправляє **`/n-lint`** (`bun run lint`). Не запускай `bun run lint` із цього скілу і не намагайся виправляти його порушення тут — це задача `/n-lint`. Якщо `npx @nitra/cursor fix` чистий, а `bun run lint` лишився червоним — запусти `/n-lint` окремо.
|
|
12
12
|
|
|
13
13
|
## Workflow
|
|
14
14
|
|
|
15
|
-
1. **Діагностика** — запусти перевірку (за замовчуванням лише правила з `.cursor/rules/*.mdc`, для яких у пакеті є programmatic check; повний набір — явні аргументи: `npx @nitra/cursor
|
|
15
|
+
1. **Діагностика** — запусти перевірку (за замовчуванням лише правила з `.cursor/rules/*.mdc`, для яких у пакеті є programmatic check; повний набір — явні аргументи: `npx @nitra/cursor fix bun ga …`):
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
npx @nitra/cursor
|
|
18
|
+
npx @nitra/cursor fix
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
2. **Аналіз** — зчитай вивід, знайди всі `❌` та визнач які правила порушено
|
|
@@ -43,7 +43,7 @@ oxfmt .
|
|
|
43
43
|
6. **Верифікація** — перевір що все виправлено:
|
|
44
44
|
|
|
45
45
|
```bash
|
|
46
|
-
npx @nitra/cursor
|
|
46
|
+
npx @nitra/cursor fix
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
7. **Результат** — всі `❌` від `npx @nitra/cursor
|
|
49
|
+
7. **Результат** — всі `❌` від `npx @nitra/cursor fix` мають стати `✅`. Якщо залишились `❌` — повтори кроки 3-6. Лінт-помилки від `bun run lint` тут **не виправляй** — вони на скіл `/n-lint`.
|
package/skills/lint/SKILL.md
CHANGED
|
@@ -114,4 +114,4 @@ bun run lint
|
|
|
114
114
|
|
|
115
115
|
## Примітка
|
|
116
116
|
|
|
117
|
-
Цей скіл **не** замінює **`npx @nitra/cursor
|
|
117
|
+
Цей скіл **не** замінює **`npx @nitra/cursor fix`**: **`lint`** перевіряє лінтери/формат у **`package.json`**, а **`check`** — програмні правила пакета **`@nitra/cursor`**. За потреби запускай обидва.
|