@nitra/cursor 12.8.8 → 12.9.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.
Files changed (132) hide show
  1. package/CHANGELOG.md +14 -1
  2. package/bin/n-cursor.js +18 -10
  3. package/package.json +5 -5
  4. package/rules/abie/docs/index.md +0 -1
  5. package/rules/abie/lib/docs/http-route.md +11 -12
  6. package/rules/abie/lib/http-route.mjs +3 -0
  7. package/rules/abie/policy/health_check_policy/health_check_policy.mdc +3 -1
  8. package/rules/abie/policy/health_check_policy/health_check_policy.rego +27 -0
  9. package/rules/adr/docs/index.md +0 -1
  10. package/rules/adr/js/madr_format.mdc +13 -1
  11. package/rules/bun/docs/index.md +0 -1
  12. package/rules/bun/policy/package_json/package_json.rego +12 -0
  13. package/rules/capacitor/docs/index.md +0 -1
  14. package/rules/changelog/docs/index.md +0 -1
  15. package/rules/ci4/docs/index.md +0 -1
  16. package/rules/doc-files/docs/index.md +0 -1
  17. package/rules/doc-files/docs/main.md +7 -9
  18. package/rules/doc-files/main.mjs +2 -3
  19. package/rules/docker/docs/index.md +0 -1
  20. package/rules/efes/docs/index.md +0 -1
  21. package/rules/feedback/docs/index.md +0 -1
  22. package/rules/ga/docs/index.md +0 -1
  23. package/rules/graphql/docs/index.md +0 -1
  24. package/rules/hasura/docs/index.md +0 -1
  25. package/rules/hasura/js/docs/index.md +3 -2
  26. package/rules/hasura/js/docs/migrations.md +30 -0
  27. package/rules/hasura/js/migrations.mjs +47 -0
  28. package/rules/image-avif/docs/index.md +0 -1
  29. package/rules/image-compress/docs/index.md +0 -1
  30. package/rules/js/docs/index.md +0 -1
  31. package/rules/js/js/dep-policy.mjs +87 -0
  32. package/rules/js/js/docs/dep-policy.md +36 -0
  33. package/rules/js/js/docs/index.md +1 -0
  34. package/rules/js/policy/package_json/package_json.rego +16 -0
  35. package/rules/js-bun-db/docs/index.md +0 -1
  36. package/rules/js-bun-redis/docs/index.md +0 -1
  37. package/rules/js-mssql/docs/index.md +0 -1
  38. package/rules/js-run/docs/index.md +0 -1
  39. package/rules/k8s/docs/index.md +0 -1
  40. package/rules/nginx-default-tpl/docs/index.md +0 -1
  41. package/rules/npm-module/docs/index.md +0 -1
  42. package/rules/php/docs/index.md +0 -1
  43. package/rules/python/docs/index.md +0 -1
  44. package/rules/rego/docs/index.md +0 -1
  45. package/rules/rego/js/docs/index.md +3 -3
  46. package/rules/rego/js/docs/tooling.md +28 -0
  47. package/rules/rego/js/tooling.mjs +24 -0
  48. package/rules/rego/policy/package_json/package_json.rego +21 -0
  49. package/rules/rego/policy/package_json/target.json +4 -0
  50. package/rules/release/docs/index.md +0 -1
  51. package/rules/rust/docs/index.md +0 -1
  52. package/rules/rust/policy/lint_rust_yml/lint_rust_yml.rego +24 -0
  53. package/rules/rust/policy/package_json/package_json.rego +20 -0
  54. package/rules/rust/policy/package_json/target.json +4 -0
  55. package/rules/security/docs/index.md +0 -1
  56. package/rules/style/docs/index.md +0 -1
  57. package/rules/style/js/docs/index.md +2 -3
  58. package/rules/style/js/docs/tooling.md +14 -10
  59. package/rules/style/js/tooling.mjs +8 -2
  60. package/rules/style/policy/lint_style_yml/lint_style_yml.rego +5 -0
  61. package/rules/tauri/docs/index.md +0 -1
  62. package/rules/test/docs/index.md +0 -1
  63. package/rules/test/js/docs/index.md +2 -0
  64. package/rules/test/js/docs/no-console-store-restore.md +32 -0
  65. package/rules/test/js/docs/sandbox-aware-test.md +32 -0
  66. package/rules/test/js/no-console-store-restore.mjs +88 -0
  67. package/rules/test/js/sandbox-aware-test.mjs +89 -0
  68. package/rules/text/docs/index.md +0 -1
  69. package/rules/tool-surface/docs/index.md +0 -1
  70. package/rules/vue/docs/index.md +0 -1
  71. package/rules/worktree/docs/index.md +0 -1
  72. package/scripts/docs/hook.md +29 -0
  73. package/scripts/docs/index.md +1 -2
  74. package/scripts/hook.mjs +72 -0
  75. package/scripts/lib/docs/index.md +35 -36
  76. package/scripts/lib/docs/rule-meta.md +10 -9
  77. package/scripts/lib/docs/run-lint.md +9 -8
  78. package/scripts/lib/docs/run-rule.md +7 -7
  79. package/scripts/lib/fix/docs/index.md +0 -1
  80. package/scripts/lib/rule-meta.mjs +2 -1
  81. package/scripts/lib/run-lint.mjs +54 -3
  82. package/scripts/lib/run-rule.mjs +1 -2
  83. package/skills/adr-normalize/SKILL.md +1 -0
  84. package/skills/coverage-fix/SKILL.md +1 -0
  85. package/skills/doc-aggregate/SKILL.md +1 -0
  86. package/skills/doc-files/SKILL.md +1 -0
  87. package/skills/lint/SKILL.md +24 -19
  88. package/skills/llm-patch/SKILL.md +1 -0
  89. package/skills/publish-telegram/SKILL.md +1 -0
  90. package/skills/start-check/SKILL.md +1 -0
  91. package/skills/taze/SKILL.md +3 -2
  92. package/types/bin/n-cursor.d.ts +1 -1
  93. package/rules/abie/docs/fix.md +0 -37
  94. package/rules/adr/docs/fix.md +0 -37
  95. package/rules/bun/docs/fix.md +0 -30
  96. package/rules/capacitor/docs/fix.md +0 -36
  97. package/rules/changelog/docs/fix.md +0 -37
  98. package/rules/ci4/docs/fix.md +0 -32
  99. package/rules/doc-files/docs/fix.md +0 -29
  100. package/rules/docker/docs/fix.md +0 -35
  101. package/rules/efes/docs/fix.md +0 -37
  102. package/rules/feedback/docs/fix.md +0 -30
  103. package/rules/ga/docs/fix.md +0 -30
  104. package/rules/graphql/docs/fix.md +0 -37
  105. package/rules/hasura/docs/fix.md +0 -39
  106. package/rules/image-avif/docs/fix.md +0 -28
  107. package/rules/image-compress/docs/fix.md +0 -27
  108. package/rules/js/docs/fix.md +0 -37
  109. package/rules/js-bun-db/docs/fix.md +0 -30
  110. package/rules/js-bun-redis/docs/fix.md +0 -32
  111. package/rules/js-mssql/docs/fix.md +0 -30
  112. package/rules/js-run/docs/fix.md +0 -36
  113. package/rules/k8s/docs/fix.md +0 -31
  114. package/rules/nginx-default-tpl/docs/fix.md +0 -35
  115. package/rules/npm-module/docs/fix.md +0 -34
  116. package/rules/php/docs/fix.md +0 -35
  117. package/rules/python/docs/fix.md +0 -38
  118. package/rules/rego/docs/fix.md +0 -31
  119. package/rules/release/docs/fix.md +0 -28
  120. package/rules/rust/docs/fix.md +0 -32
  121. package/rules/security/docs/fix.md +0 -33
  122. package/rules/style/docs/fix.md +0 -28
  123. package/rules/tauri/docs/fix.md +0 -39
  124. package/rules/test/docs/fix.md +0 -31
  125. package/rules/text/docs/fix.md +0 -37
  126. package/rules/tool-surface/docs/fix.md +0 -32
  127. package/rules/vue/docs/fix.md +0 -32
  128. package/rules/worktree/docs/fix.md +0 -40
  129. package/scripts/docs/post-tool-use-fix.md +0 -32
  130. package/scripts/docs/worktree-cli.md +0 -27
  131. package/scripts/lib/docs/worktree.md +0 -42
  132. package/scripts/lib/fix/docs/run-fix-check.md +0 -33
@@ -0,0 +1,89 @@
1
+ /** @see ./docs/sandbox-aware-test.md */
2
+ import { readFile } from 'node:fs/promises'
3
+ import { basename, relative } from 'node:path'
4
+
5
+ import { createCheckReporter } from '../../../scripts/lib/check-reporter.mjs'
6
+ import { loadCursorIgnorePaths } from '../../../scripts/lib/load-cursor-config.mjs'
7
+ import { walkDir } from '../../../scripts/utils/walkDir.mjs'
8
+
9
+ /**
10
+ * Чи файл — JS-тест (`*.test.mjs` / `*.test.js`).
11
+ * @param {string} absPath абсолютний шлях
12
+ * @returns {boolean} `true` для `.test.{mjs,js}` файлів
13
+ */
14
+ function isTestFile(absPath) {
15
+ const name = basename(absPath)
16
+ return name.endsWith('.test.mjs') || name.endsWith('.test.js')
17
+ }
18
+
19
+ /**
20
+ * Чи файл містить `import.meta.dirname`/`import.meta.url`-навігацію з ≥4 `..`-рівнів.
21
+ * Для кожного вживання `import.meta.dirname|url` рахує `'..'`/`".."` у вікні 400 символів.
22
+ * @param {string} body вміст файлу
23
+ * @returns {boolean} `true` якщо знайдено глибоку навігацію
24
+ */
25
+ function hasDeepMetaNavigation(body) {
26
+ const RE = /import\.meta\.(?:dirname|url)\b/gu
27
+ let match
28
+ while ((match = RE.exec(body)) !== null) {
29
+ const chunk = body.slice(match.index, match.index + 400)
30
+ const dots = (chunk.match(/'\.\.'|"\.\."/gu) ?? []).length
31
+ if (dots >= 4) return true
32
+ }
33
+ return false
34
+ }
35
+
36
+ /** Захист через тимчасову пісочницю — `withTmpDir`. */
37
+ const WITH_TMP_DIR_RE = /\bwithTmpDir\b/u
38
+
39
+ /** Захист через явний skip у Stryker-sandbox (`test.skipIf`). */
40
+ const SKIP_IF_STRYKER_RE = /\btest\.skipIf\s*\(\s*(?:env|process\.env)\.STRYKER_MUTATOR_WORKER\b/u
41
+
42
+ /**
43
+ * Перевіряє, що `*.test.{mjs,js}` з глибокою `import.meta`-навігацією (≥4 `..`-рівнів)
44
+ * захищені `withTmpDir` або `test.skipIf(env.STRYKER_MUTATOR_WORKER)`.
45
+ * Без ізоляції Stryker-sandbox (`reports/stryker/.tmp/sandbox-XXX/`) не має `.git/`,
46
+ * тому git-операції у таких тестах падають і мутаційний прогін не стартує.
47
+ * @param {string} [cwdParam] корінь репозиторію
48
+ * @returns {Promise<number>} 0 — чисто, 1 — є порушення
49
+ */
50
+ export async function check(cwdParam = process.cwd()) {
51
+ const reporter = createCheckReporter()
52
+ const { pass, fail } = reporter
53
+
54
+ const cwd = cwdParam
55
+ const ignorePaths = await loadCursorIgnorePaths(cwd)
56
+
57
+ /** @type {string[]} */
58
+ const testFiles = []
59
+ await walkDir(
60
+ cwd,
61
+ absPath => {
62
+ if (isTestFile(absPath)) testFiles.push(absPath)
63
+ },
64
+ ignorePaths
65
+ )
66
+
67
+ /** @type {string[]} */
68
+ const offenders = []
69
+ for (const absPath of testFiles) {
70
+ const body = await readFile(absPath, 'utf8')
71
+ if (!hasDeepMetaNavigation(body)) continue
72
+ if (WITH_TMP_DIR_RE.test(body) || SKIP_IF_STRYKER_RE.test(body)) continue
73
+ offenders.push(relative(cwd, absPath))
74
+ }
75
+
76
+ if (offenders.length === 0) {
77
+ pass(`Усі ${testFiles.length} тестові файли sandbox-aware (test.mdc)`)
78
+ return reporter.getExitCode()
79
+ }
80
+
81
+ for (const file of offenders) {
82
+ fail(
83
+ `${file}: import.meta deep navigation (≥4 рівні ..) без ізоляції — ` +
84
+ `оберни у withTmpDir() або захисти test.skipIf(env.STRYKER_MUTATOR_WORKER) (test.mdc, sandbox-aware-test)`
85
+ )
86
+ }
87
+
88
+ return reporter.getExitCode()
89
+ }
@@ -8,5 +8,4 @@ resource: npm/rules/text/
8
8
 
9
9
  | Файл | Тип |
10
10
  | ------------------- | --------- |
11
- | [fix.mjs](fix.md) | JS Module |
12
11
  | [main.mjs](main.md) | JS Module |
@@ -8,5 +8,4 @@ resource: npm/rules/tool-surface/
8
8
 
9
9
  | Файл | Тип |
10
10
  | ------------------- | --------- |
11
- | [fix.mjs](fix.md) | JS Module |
12
11
  | [main.mjs](main.md) | JS Module |
@@ -8,5 +8,4 @@ resource: npm/rules/vue/
8
8
 
9
9
  | Файл | Тип |
10
10
  | ------------------- | --------- |
11
- | [fix.mjs](fix.md) | JS Module |
12
11
  | [main.mjs](main.md) | JS Module |
@@ -8,5 +8,4 @@ resource: npm/rules/worktree/
8
8
 
9
9
  | Файл | Тип |
10
10
  | ------------------- | --------- |
11
- | [fix.mjs](fix.md) | JS Module |
12
11
  | [main.mjs](main.md) | JS Module |
@@ -0,0 +1,29 @@
1
+ ---
2
+ type: JS Module
3
+ title: hook.mjs
4
+ resource: npm/scripts/hook.mjs
5
+ docgen:
6
+ crc: 8a19fee4
7
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
+ score: 90
9
+ ---
10
+
11
+ ## Огляд
12
+
13
+ Модуль є точкою входу для хуків Claude Code. Він зчитує контекст, отримуючи шлях до файлу з вхідного потоку або визначаючи його через Git. У режимі `--post-tool-use` шлях до файлу зчитується з JSON, що надходить у stdin. У режимі `--stop` визначається робоче дерево проти HEAD (`git diff HEAD` + untracked). Після зчитування контексту, він делегує виконання перевірки за допомогою `runLint`. Код виходу, отриманий від перевірки, перекодовується у відповідний протокол хука (1 $\rightarrow$ 2).
14
+
15
+ ## Поведінка
16
+
17
+ extractFilePath: Витягує шлях до файлу з JSON, отриманого з вхідного потоку.
18
+ runHookCli: Виконує логіку хука, визначаючи файли для перевірки на основі аргументів командного рядка, запускає перевірку та повертає відповідний код виходу.
19
+
20
+ ## Публічний API
21
+
22
+ extractFilePath — витягує шлях до файлу з вхідних даних JSON хука Claude Code PostToolUse.
23
+ runHookCli — виконує команду для хука n-cursor.
24
+
25
+ ## Гарантії поведінки
26
+
27
+ - Read-only: не виконує операцій запису (ФС/БД).
28
+ - Перехоплює помилки і не пропускає винятків назовні (fail-safe).
29
+ - За певних помилок повертає порожнє значення (напр. `null`) замість винятку.
@@ -15,11 +15,10 @@ resource: npm/scripts/
15
15
  | [coverage-fix-extract.mjs](coverage-fix-extract.md) | JS Module |
16
16
  | [coverage-fix.mjs](coverage-fix.md) | JS Module |
17
17
  | [ensure-nitra-cursor-dev-dependencies.mjs](ensure-nitra-cursor-dev-dependencies.md) | JS Module |
18
+ | [hook.mjs](hook.md) | JS Module |
18
19
  | [post-tool-use-check.mjs](post-tool-use-check.md) | JS Module |
19
- | [post-tool-use-fix.mjs](post-tool-use-fix.md) | JS Module |
20
20
  | [rename-yaml-extensions.mjs](rename-yaml-extensions.md) | JS Module |
21
21
  | [skills-cli.mjs](skills-cli.md) | JS Module |
22
22
  | [sync-claude-config.mjs](sync-claude-config.md) | JS Module |
23
23
  | [sync-setup-bun-deps-action.mjs](sync-setup-bun-deps-action.md) | JS Module |
24
24
  | [upgrade-nitra-cursor-and-install.mjs](upgrade-nitra-cursor-and-install.md) | JS Module |
25
- | [worktree-cli.mjs](worktree-cli.md) | JS Module |
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Thin hook entrypoint для Claude Code hooks: зчитує контекст (stdin / git),
3
+ * делегує в `runLint({ readOnly: true })`, перекодовує exit-код у hook-протокол (1 → 2).
4
+ *
5
+ * Режими:
6
+ * --post-tool-use PostToolUse: file_path зі stdin JSON Claude Code.
7
+ * --stop Stop: робоче дерево vs HEAD (`git diff HEAD` + untracked).
8
+ */
9
+ import { once } from 'node:events'
10
+ import { cwd as processCwd } from 'node:process'
11
+
12
+ import { runLint } from './lib/run-lint.mjs'
13
+ import { collectChangedFiles } from './lib/changed-files.mjs'
14
+
15
+ /**
16
+ * @returns {Promise<string>} вміст stdin або '' на TTY
17
+ */
18
+ async function readStdin() {
19
+ if (process.stdin.isTTY) return ''
20
+ process.stdin.setEncoding('utf8')
21
+ const chunks = []
22
+ process.stdin.on('data', c => chunks.push(c))
23
+ try {
24
+ await once(process.stdin, 'end')
25
+ } catch {
26
+ // error на stdin — повертаємо що встигли
27
+ }
28
+ return chunks.join('')
29
+ }
30
+
31
+ /**
32
+ * Дістає `tool_input.file_path` зі stdin JSON Claude Code PostToolUse hook.
33
+ * @param {string} json сирий stdin
34
+ * @returns {string|null}
35
+ */
36
+ export function extractFilePath(json) {
37
+ if (!json) return null
38
+ try {
39
+ const fp = JSON.parse(json)?.tool_input?.file_path
40
+ return typeof fp === 'string' && fp !== '' ? fp : null
41
+ } catch {
42
+ return null
43
+ }
44
+ }
45
+
46
+ /**
47
+ * CLI для `n-cursor hook`.
48
+ * @param {string[]} argv аргументи після 'hook'
49
+ * @returns {Promise<number>} exit-код (0 — чисто; 2 — є порушення hook-протокол)
50
+ */
51
+ export async function runHookCli(argv) {
52
+ const cwd = processCwd()
53
+ const postToolUse = argv.includes('--post-tool-use')
54
+ const stop = argv.includes('--stop')
55
+
56
+ if (!postToolUse && !stop) {
57
+ process.stderr.write('hook: потрібен --post-tool-use або --stop\n')
58
+ return 1
59
+ }
60
+
61
+ let files
62
+ if (postToolUse) {
63
+ const fp = extractFilePath(await readStdin())
64
+ if (!fp) return 0
65
+ files = [fp]
66
+ } else {
67
+ files = collectChangedFiles(cwd)
68
+ }
69
+
70
+ const code = await runLint({ files, readOnly: true, cwd })
71
+ return code !== 0 ? 2 : 0
72
+ }
@@ -6,40 +6,39 @@ resource: npm/scripts/lib/
6
6
 
7
7
  # npm/scripts/lib
8
8
 
9
- | Файл | Тип |
10
- | --------------------------------------------------------------------------- | --------- |
11
- | [assert-project-root.mjs](assert-project-root.md) | JS Module |
12
- | [changed-files.mjs](changed-files.md) | JS Module |
13
- | [check-mdc-template-refs.mjs](check-mdc-template-refs.md) | JS Module |
14
- | [check-reporter.mjs](check-reporter.md) | JS Module |
15
- | [diff-added-lines.mjs](diff-added-lines.md) | JS Module |
9
+ | Файл | Тип |
10
+ |---|---|
11
+ | [assert-project-root.mjs](assert-project-root.md) | JS Module |
12
+ | [changed-files.mjs](changed-files.md) | JS Module |
13
+ | [check-mdc-template-refs.mjs](check-mdc-template-refs.md) | JS Module |
14
+ | [check-reporter.mjs](check-reporter.md) | JS Module |
15
+ | [diff-added-lines.mjs](diff-added-lines.md) | JS Module |
16
16
  | [discover-check-rules-from-cursor.mjs](discover-check-rules-from-cursor.md) | JS Module |
17
- | [discover-checkable-rules.mjs](discover-checkable-rules.md) | JS Module |
18
- | [ensure-tool.mjs](ensure-tool.md) | JS Module |
19
- | [generated-markdown.mjs](generated-markdown.md) | JS Module |
20
- | [gha-workflow.mjs](gha-workflow.md) | JS Module |
21
- | [inline-template-links.mjs](inline-template-links.md) | JS Module |
22
- | [list-project-rules-mdc.mjs](list-project-rules-mdc.md) | JS Module |
23
- | [list-rule-ids.mjs](list-rule-ids.md) | JS Module |
24
- | [load-cursor-config.mjs](load-cursor-config.md) | JS Module |
25
- | [mirror-parity.mjs](mirror-parity.md) | JS Module |
26
- | [read-n-cursor-config-lite.mjs](read-n-cursor-config-lite.md) | JS Module |
27
- | [resolve-target-files.mjs](resolve-target-files.md) | JS Module |
28
- | [root-notice.mjs](root-notice.md) | JS Module |
29
- | [rule-meta-helpers.mjs](rule-meta-helpers.md) | JS Module |
30
- | [rule-meta.mjs](rule-meta.md) | JS Module |
31
- | [rule-predicates.mjs](rule-predicates.md) | JS Module |
32
- | [run-conftest-batch.mjs](run-conftest-batch.md) | JS Module |
33
- | [run-lint-step.mjs](run-lint-step.md) | JS Module |
34
- | [run-lint.mjs](run-lint.md) | JS Module |
35
- | [run-rule-cli.mjs](run-rule-cli.md) | JS Module |
36
- | [run-rule.mjs](run-rule.md) | JS Module |
37
- | [run-standard-lint.mjs](run-standard-lint.md) | JS Module |
38
- | [run-standard-rule.mjs](run-standard-rule.md) | JS Module |
39
- | [skill-meta.mjs](skill-meta.md) | JS Module |
40
- | [sync-gitignore-worktree.mjs](sync-gitignore-worktree.md) | JS Module |
41
- | [template.mjs](template.md) | JS Module |
42
- | [timing-summary.mjs](timing-summary.md) | JS Module |
43
- | [workspaces.mjs](workspaces.md) | JS Module |
44
- | [worktree-notice.mjs](worktree-notice.md) | JS Module |
45
- | [worktree.mjs](worktree.md) | JS Module |
17
+ | [discover-checkable-rules.mjs](discover-checkable-rules.md) | JS Module |
18
+ | [ensure-tool.mjs](ensure-tool.md) | JS Module |
19
+ | [generated-markdown.mjs](generated-markdown.md) | JS Module |
20
+ | [gha-workflow.mjs](gha-workflow.md) | JS Module |
21
+ | [inline-template-links.mjs](inline-template-links.md) | JS Module |
22
+ | [list-project-rules-mdc.mjs](list-project-rules-mdc.md) | JS Module |
23
+ | [list-rule-ids.mjs](list-rule-ids.md) | JS Module |
24
+ | [load-cursor-config.mjs](load-cursor-config.md) | JS Module |
25
+ | [mirror-parity.mjs](mirror-parity.md) | JS Module |
26
+ | [read-n-cursor-config-lite.mjs](read-n-cursor-config-lite.md) | JS Module |
27
+ | [resolve-target-files.mjs](resolve-target-files.md) | JS Module |
28
+ | [root-notice.mjs](root-notice.md) | JS Module |
29
+ | [rule-meta-helpers.mjs](rule-meta-helpers.md) | JS Module |
30
+ | [rule-meta.mjs](rule-meta.md) | JS Module |
31
+ | [rule-predicates.mjs](rule-predicates.md) | JS Module |
32
+ | [run-conftest-batch.mjs](run-conftest-batch.md) | JS Module |
33
+ | [run-lint-step.mjs](run-lint-step.md) | JS Module |
34
+ | [run-lint.mjs](run-lint.md) | JS Module |
35
+ | [run-rule-cli.mjs](run-rule-cli.md) | JS Module |
36
+ | [run-rule.mjs](run-rule.md) | JS Module |
37
+ | [run-standard-lint.mjs](run-standard-lint.md) | JS Module |
38
+ | [run-standard-rule.mjs](run-standard-rule.md) | JS Module |
39
+ | [skill-meta.mjs](skill-meta.md) | JS Module |
40
+ | [sync-gitignore-worktree.mjs](sync-gitignore-worktree.md) | JS Module |
41
+ | [template.mjs](template.md) | JS Module |
42
+ | [timing-summary.mjs](timing-summary.md) | JS Module |
43
+ | [workspaces.mjs](workspaces.md) | JS Module |
44
+ | [worktree-notice.mjs](worktree-notice.md) | JS Module |
@@ -3,29 +3,30 @@ type: JS Module
3
3
  title: rule-meta.mjs
4
4
  resource: npm/scripts/lib/rule-meta.mjs
5
5
  docgen:
6
- crc: 8eca71d2
6
+ crc: dd8fe34a
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  score: 100
9
9
  ---
10
10
 
11
11
  ## Огляд
12
12
 
13
- Файл зчитує метадані правил з `npm/rules/<id>/main.json`. Він нормалізує специфікації активації (`auto`) та області застосування (`lint`). Специфікація `main.json.auto` може мати чотири форми: константу `RULE_ALWAYS` ("завжди"), масив залежних правил, об'єкт з шаблоном `glob` або об'єкт з предикатом. Це забезпечує механізм для визначення умов активації правила на основі конфігурації `main.json`.
13
+ Парсер метаданих правил з `npm/rules/<id>/main.json` інтерпретує специфікації активації (`auto`) та області дії (`lint`). Він нормалізує логіку, визначену у `main.json.auto`, яка може бути константою `RULE_ALWAYS="завжди"`, списком залежностей, об'єктом з шаблоном `glob`, або предикатом. Код лише зчитує дані та не виконує операцій з файловою системою чи базами даних. При виникненні помилок він перехоплює їх, повертаючи безпечне значення замість винятків.
14
14
 
15
15
  ## Поведінка
16
16
 
17
17
  RULE_ALWAYS — Константа, що позначає безумовну активацію правила.
18
18
  parseRuleAutoSpec — Нормалізує значення поля `auto` з `main.json` у дискриміновану специфікацію активації правила.
19
- parseRuleLintSpec — Нормалізує значення поля `lint` з `main.json` у специфікацію області застосування детектора.
20
- readRuleMetaRaw — Зчитує та парсить вміст `main.json` з каталогу правила, повертаючи його як об'єкт або `null` у разі помилки чи відсутності файлу.
19
+ parseRuleLintSpec — Нормалізує значення поля `lint` з `main.json` у специфікацію області дії детектора.
20
+ readRuleMetaRaw — Зчитує та парсить вміст `main.json` з каталогу правила, повертаючи його як об'єкт або `null`.
21
21
 
22
22
  ## Публічний API
23
23
 
24
- RULE_ALWAYS — позначка для безумовної активації.
25
- parseRuleAutoSpec — перетворює налаштування автоматичного визначення правил з `main.json` у структурований формат.
26
- parseRuleLintSpec — перетворює налаштування лінтера з `main.json` у визначення області дії детектора.
27
- "per-file" — визначає, що детектор аналізує зміни на рівні окремих файлів (порівнює відмінності з оригіналом).
28
- "full" — визначає, що детектор аналізує весь проєкт одночасно (активується лише при використанні прапорця `--full` або в CI).
24
+ RULE_ALWAYS — константа, що вказує на безумовну активацію.
25
+ parseRuleAutoSpec — перетворює налаштування автоматичного сканування з `main.json` у структурований формат.
26
+ parseRuleLintSpec — перетворює налаштування лінтування з `main.json` у визначення області дії детектора.
27
+ "per-file" — визначає, що детектор аналізує лише змінені файли (порівнюючи їх з оригіналом).
28
+ "full" — визначає, що детектор сканує весь репозиторій; у режимі змін тригериться лише при перетині змінених файлів з шаблоном `auto.glob`.
29
+ Об'єктна форма `{scope, ci}` скасована — оскільки режим CI (`--read-only --full`) сканує весь репозиторій, окремі перевизначення CI для кожного правила не потрібні.
29
30
  readRuleMetaRaw — зчитує та розбирає метадані одного правила з `main.json`.
30
31
 
31
32
  ## Гарантії поведінки
@@ -3,27 +3,28 @@ type: JS Module
3
3
  title: run-lint.mjs
4
4
  resource: npm/scripts/lib/run-lint.mjs
5
5
  docgen:
6
- crc: 24827b04
6
+ crc: f574f097
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  score: 100
9
9
  ---
10
10
 
11
11
  ## Огляд
12
12
 
13
- Модуль ініціалізує та керує процесом лінтування коду. Він вибирає та сортує набір правил для перевірки, використовуючи конфігурації `meta.json` та `.n-cursor.json`. Публічна функція `selectLintRules` виконує вибір правил. Публічна функція `runLint` ініціює повний цикл лінтування, який може включати перевірку змінених файлів або всього репозиторію.
13
+ Модуль керує процесом лінтування коду. Він визначає набір активних правил лінтування, використовуючи конфігурації з `meta.json` та `.n-cursor.json`. Модуль надає можливість вибрати ці правила за допомогою `selectLintRules` та ініціювати запуск перевірки коду за допомогою `runLint`.
14
14
 
15
15
  ## Поведінка
16
16
 
17
- selectLintRules вибирає і алфавітно сортує ідентифікатори правил для лінтування, ґрунтуючись на наявності в конфігурації та заданому обсязі перевірки.
18
- runLint запускає повну оркестрацію лінтування, виконуючи перевірку змінених файлів або весь репозиторій, залежно від параметрів, і може включати фази конформності та форматування.
17
+ selectLintRules визначає, які правила лінтуються на основі їхньої конфігурації та активності в `.n-cursor.json`, повертаючи відсортований список ID.
18
+ runLint запускає лінтер-оркестрацію залежно від наданих опцій: виконує прогін для конкретних правил, перевіряє лише змінені файли, виконує повний прогін репозиторію або форматування.
19
19
 
20
20
  ## Публічний API
21
21
 
22
- selectLintRules — обирає ідентифікатори правил для контексту, розташовуючи їх в алфавітному порядку.
22
+ selectLintRules — вибирає ідентифікатори правил для контексту в алфавітному порядку.
23
23
  runLint — ініціює процес лінтування.
24
- full — сканує весь репозиторій, порівнюючи його з початковим станом.
25
- readOnly — лише виявляє проблеми, не вносячи змін, порівнюючи з початковим станом.
26
- rules — виконує повне сканування лише для вказаних правил у заданому обсязі.
24
+ full — аналізує весь репозиторій, порівнюючи поточний стан із початковим.
25
+ readOnly — лише виявляє проблеми, не вносячи змін.
26
+ rules — виконує повний прогін лише для заданого набору правил у вказаному контексті.
27
+ files — виконує перевірку лише для перелічених файлів у режимі хука.
27
28
 
28
29
  ## Гарантії поведінки
29
30
 
@@ -3,25 +3,25 @@ type: JS Module
3
3
  title: run-rule.mjs
4
4
  resource: npm/scripts/lib/run-rule.mjs
5
5
  docgen:
6
- crc: 7d0585e1
6
+ crc: c9b164c7
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
- score: 90
8
+ score: 100
9
9
  ---
10
10
 
11
11
  ## Огляд
12
12
 
13
- Файл оркеструє виконання одного правила під CLI `fix`. Він послідовно застосовує гейт `applies` з `js/applies.mjs` для визначення придатності правила. Якщо гейт повертає `false`, правило не застосовується. Далі виконуються JS-концерни та Policy-концерни в алфавітному порядку. Резолвер `resolveTargetFiles` ділить кеш між концернами. Кожен concern створює власний репортер, а їхні exit-коди OR-уються в єдиний контракт, що забезпечує 0/1 результат для правила. Оркестратор спирається на конфігурації `target.json` та `.n-cursor.json`.
13
+ Файл оркеструє виконання одного правила під CLI `fix`. Він послідовно застосовує `applies`-гейт, а потім виконує JS-концерни та Policy-концерни. Резолвер ділить кеш між концернами, а кожен concern має власний механізм звітності, що об'єднується в єдиний exit-код правила. Процес спирається на конфігураційні файли, зокрема `target.json` та `.n-cursor.json`.
14
14
 
15
15
  ## Поведінка
16
16
 
17
- runTemplateSubsetConcern виконує перевірку концерну, де канон визначається сніпетом у `target.json`, звіряючи його з актуальними файлами-таргетами.
17
+ runTemplateSubsetConcern виконує перевірку концерну, де канон визначено у `target.json` як `template`, звіряючи вміст файлів-таргетів з шаблоном, визначеним у відповідному каталозі.
18
18
 
19
- runRule оркеструє виконання одного правила, послідовно застосовуючи applies-гейт, виконуючи JS-концерни та запускаючи policy-концерни, а також перевіряючи відсутність markdown-посилань.
19
+ runRule оркеструє виконання одного правила, послідовно застосовуючи `applies`-гейт, виконуючи JS-концерни, запускаючи policy-концерни та перевіряючи відсутність markdown-посилань у `main.mdc`.
20
20
 
21
21
  ## Публічний API
22
22
 
23
- runTemplateSubsetConcern — Порівнює фактичний файл із канонічним шаблоном (з `target.json`) для перевірки, чи всі обов'язкові елементи присутні, дозволяючи додаткові.
24
- runRule — Виконує окреме правило, яке проходить через перевірки застосовності, JavaScript-концерни та політики.
23
+ runTemplateSubsetConcern — Порівнює фактичний файл з канонічним шаблоном (`target.json:"check":"template"`), визначаючи, чи всі обов'язкові елементи з шаблону присутні у файлі.
24
+ runRule — Виконує окреме правило, перевіряючи його відповідність через гейт, JavaScript-концерни та політики.
25
25
 
26
26
  ## Гарантії поведінки
27
27
 
@@ -15,5 +15,4 @@ resource: npm/scripts/lib/fix/
15
15
  | [llm-worker.mjs](llm-worker.md) | JS Module |
16
16
  | [orchestrator.mjs](orchestrator.md) | JS Module |
17
17
  | [run-conformance-check.mjs](run-conformance-check.md) | JS Module |
18
- | [run-fix-check.mjs](run-fix-check.md) | JS Module |
19
18
  | [t0.mjs](t0.md) | JS Module |
@@ -54,7 +54,8 @@ const LINT_SCOPES = new Set(['per-file', 'full'])
54
54
  /**
55
55
  * Нормалізує значення `main.json.lint` у scope детектора.
56
56
  * - `"per-file"` — детектор декомпозується на змінені файли (дельта vs origin);
57
- * - `"full"` — нероздільно крос-файловий (лише `--full` / CI).
57
+ * - `"full"` — крос-файловий: у `--full` ганяє весь репо; у delta-режимі тригериться
58
+ * лише якщо змінені файли перетинаються з `auto.glob` правила (whole-repo scan).
58
59
  * Об'єктна форма `{scope, ci}` скасована: CI=`--read-only --full` ганяє все повністю,
59
60
  * тож per-rule CI-override не потрібен (spec 2026-06-14-lint-rule-consolidation §3-А).
60
61
  * @param {unknown} value значення поля `lint`
@@ -5,7 +5,9 @@ import { fileURLToPath } from 'node:url'
5
5
  import { cwd as processCwd } from 'node:process'
6
6
  import { spawnSync } from 'node:child_process'
7
7
 
8
- import { parseRuleLintSpec, readRuleMetaRaw } from './rule-meta.mjs'
8
+ import picomatch from 'picomatch'
9
+
10
+ import { parseRuleAutoSpec, parseRuleLintSpec, readRuleMetaRaw } from './rule-meta.mjs'
9
11
  import { collectChangedFilesSince, resolveChangedBase } from './changed-files.mjs'
10
12
  import { resolveCmd } from '../utils/resolve-cmd.mjs'
11
13
  import { isRuleEnabled, readNCursorConfigLite } from './read-n-cursor-config-lite.mjs'
@@ -78,6 +80,30 @@ export function selectLintRules(metaById, full, enabledRuleIds) {
78
80
  return out.toSorted((a, b) => a.localeCompare(b))
79
81
  }
80
82
 
83
+ /**
84
+ * Визначає `full`-scope правила для запуску у delta-режимі.
85
+ * Правило включається, якщо хоча б один зі змінених файлів відповідає `auto.glob` правила.
86
+ * Предикат-auto (repo-level сигнал, не file-level) — пропускається.
87
+ * @param {Record<string, Record<string, unknown>>} metaById
88
+ * @param {string[]} changed змінені файли (posix-відносні від кореня)
89
+ * @param {string[]} enabledRuleIds активні rule-id
90
+ * @returns {string[]} відсортовані id
91
+ */
92
+ function selectFullRulesForDelta(metaById, changed, enabledRuleIds) {
93
+ if (changed.length === 0) return []
94
+ const enabled = new Set(enabledRuleIds)
95
+ const out = []
96
+ for (const [id, raw] of Object.entries(metaById)) {
97
+ if (!enabled.has(id)) continue
98
+ if (parseRuleLintSpec(raw?.lint) !== 'full') continue
99
+ const autoSpec = parseRuleAutoSpec(raw?.auto)
100
+ if (!autoSpec || !('glob' in autoSpec)) continue
101
+ const isMatch = picomatch(autoSpec.glob, { dot: true })
102
+ if (changed.some(f => isMatch(f))) out.push(id)
103
+ }
104
+ return out.toSorted((a, b) => a.localeCompare(b))
105
+ }
106
+
81
107
  /**
82
108
  * Активні правила для unscoped linter-фази. `.n-cursor.json` — єдине джерело
83
109
  * whitelist/disable, `meta.json#lint` нижче використовується лише як scope (`per-file`/`full`).
@@ -211,16 +237,18 @@ async function runScopedRules(rules, ctx) {
211
237
 
212
238
  /**
213
239
  * Запускає lint-оркестрацію.
214
- * @param {{ full?: boolean, readOnly?: boolean, rules?: string[], cwd?: string, rulesDir?: string, log?: (s: string) => void }} [opts] параметри
240
+ * @param {{ full?: boolean, readOnly?: boolean, rules?: string[], files?: string[], cwd?: string, rulesDir?: string, log?: (s: string) => void }} [opts] параметри
215
241
  * - `full` — весь репо (`true`) проти дельти vs origin (`false`, default);
216
242
  * - `readOnly` — лише детект без мутацій (`true`) проти fix (`false`, default);
217
- * - `rules` — непорожній scope → повний прогін лише цих правил (лінтер + конформність, whole-repo).
243
+ * - `rules` — непорожній scope → повний прогін лише цих правил (лінтер + конформність, whole-repo);
244
+ * - `files` — явний список файлів (hook-режим): per-file правила без conformance/format/delta-full.
218
245
  * @returns {Promise<number>} exit code
219
246
  */
220
247
  export async function runLint(opts = {}) {
221
248
  const full = opts.full === true
222
249
  const readOnly = opts.readOnly === true
223
250
  const rules = Array.isArray(opts.rules) ? opts.rules : []
251
+ const explicitFiles = Array.isArray(opts.files) ? opts.files : null
224
252
  const cwd = opts.cwd ?? processCwd()
225
253
  const rulesDir = opts.rulesDir ?? RULES_DIR
226
254
  const log = opts.log ?? (s => process.stdout.write(s))
@@ -230,6 +258,17 @@ export async function runLint(opts = {}) {
230
258
  return runScopedRules(rules, { cwd, readOnly, rulesDir, conformance: opts.rulesDir === undefined, log })
231
259
  }
232
260
 
261
+ // Hook-режим (явний список файлів): per-file правила, без conformance/format/delta-full.
262
+ // Правила отримують точний список файлів; пусті files (Stop без змін) — правила однаково
263
+ // викликаються (orphan-детект у doc-files не залежить від списку джерел).
264
+ if (explicitFiles !== null) {
265
+ const metaById = readAllMeta(rulesDir)
266
+ const enabledRuleIds = await readEnabledLintRuleIds(metaById, cwd)
267
+ const ids = selectLintRules(metaById, false, enabledRuleIds)
268
+ const perFile = await runPerFileRules(ids, { rulesDir, changed: explicitFiles, cwd, readOnly, metaById, log })
269
+ return perFile.stop ? perFile.code : perFile.code
270
+ }
271
+
233
272
  // Default scope — дельта vs origin (merge-base main/origin/main); `--full` — весь репо.
234
273
  const changed = full ? undefined : collectChangedFilesSince(resolveChangedBase(cwd), cwd)
235
274
  if (!full && changed.length === 0) {
@@ -244,6 +283,18 @@ export async function runLint(opts = {}) {
244
283
  if (perFile.stop) return perFile.code
245
284
  let worst = perFile.code
246
285
 
286
+ // Delta-режим: `full`-scope правила, чиї glob-и перетинаються з changed.
287
+ // Запускаємо з `changed=undefined` (whole-repo scan як зазвичай) — так уникаємо
288
+ // прогону docker/ga/k8s… коли жоден їхній файл не змінився.
289
+ if (!full && changed.length > 0) {
290
+ const fullIds = selectFullRulesForDelta(metaById, changed, enabledRuleIds)
291
+ if (fullIds.length > 0) {
292
+ const fullResult = await runPerFileRules(fullIds, { rulesDir, changed: undefined, cwd, readOnly, metaById, log })
293
+ if (fullResult.stop) return fullResult.code
294
+ if (fullResult.code !== 0) worst = fullResult.code
295
+ }
296
+ }
297
+
247
298
  // Конформність-фаза: whole-repo, лише у `--full`. Кастомний rulesDir (юніт-тести
248
299
  // селектора) — реальний пакет недоступний, тож пропускаємо.
249
300
  if (full && opts.rulesDir === undefined) {
@@ -117,8 +117,7 @@ async function runPolicyConcern(bundledRulesDir, ruleId, concernName, walkCache)
117
117
  if (files.length === 0) {
118
118
  if (target.files.required && target.files.single) {
119
119
  const msg =
120
- target.missingMessage ??
121
- `${target.files.single} не існує — створи згідно main.mdc (${ruleId}.${concernName})`
120
+ target.missingMessage ?? `${target.files.single} не існує — створи згідно main.mdc (${ruleId}.${concernName})`
122
121
  reporter.fail(msg)
123
122
  }
124
123
  return reporter.getExitCode()
@@ -3,6 +3,7 @@ name: n-adr-normalize
3
3
  description: >-
4
4
  Ручний запуск ADR-нормалізації — обхід порогу й min-interval, прогон одного
5
5
  батчу чернеток через LLM, перегляд результату через git diff
6
+ version: '1.0'
6
7
  ---
7
8
 
8
9
  # n-adr-normalize — ручна нормалізація ADR-чернеток
@@ -2,6 +2,7 @@
2
2
  name: n-coverage-fix
3
3
  description: >-
4
4
  Автономна команда: запускає n-cursor coverage → читає вцілілих мутантів → ітеративно пише тести до конвергенції (max 3 ітерації)
5
+ version: '1.0'
5
6
  ---
6
7
 
7
8
  # n-coverage-fix — підвищення mutation score
@@ -2,6 +2,7 @@
2
2
  name: doc-aggregate
3
3
  description: >-
4
4
  Агрегуюча документація за запитом: module-summary на кожен логічний модуль (docs/ARCHITECTURE.md) і доменні доки бізнес-процесів у кореневій docs/ — синтез поверх готових файлових док (doc-files), батч-диспатч субагентів у worktree
5
+ version: '1.0'
5
6
  ---
6
7
 
7
8
  # doc-aggregate — агрегуюча документація (за запитом)
@@ -2,6 +2,7 @@
2
2
  name: doc-files
3
3
  description: >-
4
4
  Обовʼязковий крок задачі (як lint): для кожного зміненого/нового кодового файлу (js/mjs/ts/vue/py) JS-оркестрована генерація лаконічної поведінкової української md-документації у теку docs/ поряд із кодом, зі звіркою застарілості за CRC у frontmatter
5
+ version: '1.0'
5
6
  ---
6
7
 
7
8
  # doc-files — файлова документація (обовʼязковий крок)