@nitra/cursor 5.3.4 → 5.4.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/.claude-template/settings.template.json +2 -2
- package/.pi-template/extensions/n-cursor-adr/docs/index.md +13 -24
- package/CHANGELOG.md +11 -0
- package/bin/n-cursor.js +43 -22
- package/lib/docs/models.md +29 -18
- package/lib/docs/omlx-trace.md +51 -0
- package/lib/docs/omlx.md +31 -15
- package/lib/omlx.mjs +2 -5
- package/package.json +1 -1
- package/rules/abie/docs/fix.md +17 -11
- package/rules/adr/docs/fix.md +25 -140
- package/rules/bun/docs/fix.md +18 -151
- package/rules/capacitor/docs/fix.md +16 -13
- package/rules/capacitor/js/docs/platforms.md +31 -43
- package/rules/changelog/docs/fix.md +25 -169
- package/rules/ci4/docs/fix.md +11 -14
- package/rules/doc-files/doc-files.mdc +60 -0
- package/rules/doc-files/docs/fix.md +31 -0
- package/rules/doc-files/fix.mjs +19 -0
- package/{skills → rules}/doc-files/js/docgen-extract.mjs +42 -19
- package/{skills → rules}/doc-files/js/docgen-ignore.mjs +2 -1
- package/{skills → rules}/doc-files/js/docgen-scan.mjs +9 -1
- package/{skills → rules}/doc-files/js/docs/docgen-crc.md +1 -1
- package/{skills → rules}/doc-files/js/docs/docgen-extract-anchors.md +1 -1
- package/{skills → rules}/doc-files/js/docs/docgen-extract.md +2 -2
- package/{skills → rules}/doc-files/js/docs/docgen-files-batch.md +1 -1
- package/{skills → rules}/doc-files/js/docs/docgen-gen.md +1 -1
- package/{skills → rules}/doc-files/js/docs/docgen-ignore.md +4 -4
- package/rules/doc-files/js/docs/docgen-prompts.md +39 -0
- package/rules/doc-files/js/docs/docgen-scan.md +54 -0
- package/rules/doc-files/js/docs/lint.md +36 -0
- package/rules/doc-files/js/docs/units-js.md +31 -0
- package/rules/doc-files/js/docs/units-rs.md +35 -0
- package/rules/doc-files/js/docs/units.md +30 -0
- package/rules/doc-files/js/lint.mjs +96 -0
- package/{skills → rules}/doc-files/js/units-rs.mjs +37 -17
- package/rules/doc-files/lint/docs/lint.md +37 -0
- package/rules/doc-files/lint/lint.mjs +105 -0
- package/rules/doc-files/meta.json +1 -0
- package/rules/docker/docs/fix.md +21 -161
- package/rules/efes/docs/fix.md +23 -194
- package/rules/feedback/docs/fix.md +10 -8
- package/rules/ga/docs/fix.md +10 -5
- package/rules/graphql/docs/fix.md +23 -119
- package/rules/hasura/docs/fix.md +19 -5
- package/rules/hasura/js/docs/internal_urls.md +34 -307
- package/rules/image-avif/docs/fix.md +16 -127
- package/rules/image-compress/docs/fix.md +20 -141
- package/rules/image-compress/js/docs/package_setup.md +22 -182
- package/rules/js-bun-db/docs/fix.md +23 -139
- package/rules/js-bun-db/js/docs/safety.md +33 -221
- package/rules/js-bun-redis/docs/fix.md +25 -114
- package/rules/js-bun-redis/js/docs/imports.md +18 -166
- package/rules/js-lint/docs/fix.md +30 -108
- package/rules/js-lint/js/docs/lint-findings.md +37 -17
- package/rules/js-lint/js/docs/lint.md +22 -238
- package/rules/js-lint/js/docs/tooling.md +34 -331
- package/rules/js-lint-ci/docs/fix.md +16 -149
- package/rules/js-lint-ci/js/docs/lint.md +16 -136
- package/rules/js-mssql/docs/fix.md +18 -123
- package/rules/js-mssql/js/docs/deps.md +28 -251
- package/rules/js-run/docs/fix.md +23 -138
- package/rules/js-run/js/docs/runtime.md +24 -378
- package/rules/k8s/docs/fix.md +18 -123
- package/rules/nginx-default-tpl/docs/fix.md +22 -118
- package/rules/nginx-default-tpl/js/docs/template.md +38 -360
- package/rules/npm-module/docs/fix.md +27 -89
- package/rules/npm-module/js/docs/header_doc_pointer.md +15 -15
- package/rules/npm-module/js/docs/package_structure.md +36 -258
- package/rules/npm-module/js/docs/rule_meta.md +25 -127
- package/rules/npm-module/js/docs/skill_meta.md +18 -180
- package/rules/php/docs/fix.md +21 -98
- package/rules/php/js/docs/tooling.md +20 -143
- package/rules/python/docs/fix.md +25 -157
- package/rules/python/js/docs/applies.md +20 -98
- package/rules/python/js/docs/tooling.md +27 -144
- package/rules/rego/docs/fix.md +24 -112
- package/rules/rego/js/docs/applies.md +20 -164
- package/rules/rego/js/docs/lint.md +15 -110
- package/rules/release/docs/fix.md +16 -114
- package/rules/rust/docs/fix.md +24 -119
- package/rules/rust/js/docs/applies.md +20 -129
- package/rules/security/docs/fix.md +21 -78
- package/rules/security/js/docs/sample_secret.md +23 -182
- package/rules/security/js/docs/trufflehog.md +19 -128
- package/rules/style-lint/docs/fix.md +16 -150
- package/rules/style-lint/js/docs/lint.md +21 -172
- package/rules/style-lint/js/docs/tooling.md +19 -184
- package/rules/tauri/docs/fix.md +26 -152
- package/rules/tauri/js/docs/cargo_mutants_config.md +21 -159
- package/rules/tauri/js/docs/tooling.md +20 -217
- package/rules/test/docs/fix.md +19 -127
- package/rules/test/js/data/stryker_config/docs/stryker.config.baseline.md +15 -127
- package/rules/test/js/data/stryker_config/docs/stryker.config.vue.baseline.md +17 -153
- package/rules/test/js/docs/cargo_mutants_config.md +24 -164
- package/rules/test/js/docs/location.md +24 -126
- package/rules/test/js/docs/no-process-chdir.md +20 -151
- package/rules/test/js/docs/no-relative-fs-path.md +24 -261
- package/rules/test/js/docs/stryker_config.md +48 -148
- package/rules/test/js/docs/vitest-config-pool-forks.md +21 -164
- package/rules/text/docs/fix.md +25 -113
- package/rules/text/js/docs/forbidden-prettier.md +21 -132
- package/rules/text/js/docs/formatting.md +60 -251
- package/rules/text/js/docs/lint.md +17 -114
- package/rules/vue/docs/fix.md +25 -118
- package/rules/vue/js/docs/packages.md +25 -323
- package/rules/worktree/docs/fix.md +31 -150
- package/scripts/coverage-classify/docs/index.md +23 -209
- package/scripts/coverage-classify/docs/verdict-schema.md +14 -159
- package/scripts/dispatcher/docs/trace.md +35 -0
- package/scripts/docs/auto-rules.md +37 -361
- package/scripts/docs/lint-cli.md +12 -13
- package/scripts/docs/post-tool-use-fix.md +16 -15
- package/scripts/docs/skills-cli.md +26 -23
- package/scripts/docs/sync-claude-config.md +94 -34
- package/scripts/docs/worktree-cli.md +11 -34
- package/scripts/lib/docs/assert-project-root.md +14 -16
- package/scripts/lib/docs/changed-files.md +24 -139
- package/scripts/lib/docs/discover-check-rules-from-cursor.md +14 -146
- package/scripts/lib/docs/rule-predicates.md +20 -17
- package/scripts/lib/docs/run-rule-cli.md +14 -18
- package/scripts/lib/docs/run-rule.md +13 -20
- package/scripts/lib/docs/run-standard-rule.md +12 -15
- package/scripts/lib/docs/sync-gitignore-worktree.md +15 -18
- package/scripts/lib/rule-predicates.mjs +1 -1
- package/scripts/sync-claude-config.mjs +4 -1
- package/scripts/utils/docs/with-lock.md +19 -12
- package/scripts/utils/with-lock.mjs +4 -2
- package/skills/doc-aggregate/SKILL.md +2 -2
- package/skills/doc-aggregate/js/docgen-ignore.mjs +6 -6
- package/skills/doc-aggregate/js/docs/docgen-ignore.md +1 -1
- package/skills/doc-aggregate/js/docs/docgen-scan.md +78 -0
- package/skills/doc-files/.changes/260612-0031.md +5 -0
- package/skills/doc-files/.changes/260612-0036.md +5 -0
- package/skills/doc-files/.changes/260612-0114.md +5 -0
- package/skills/doc-files/SKILL.md +6 -6
- package/skills/fix/js/docs/llm-worker.md +17 -15
- package/skills/fix/js/docs/orchestrator.md +30 -23
- package/skills/fix/js/docs/t0.md +26 -16
- package/skills/start-check/js/docs/check.md +26 -22
- package/skills/taze/js/docs/diff.md +44 -20
- package/skills/doc-files/js/docs/docgen-prompts.md +0 -32
- package/skills/doc-files/js/docs/docgen-scan.md +0 -25
- package/skills/doc-files/js/docs/units-rs.md +0 -35
- /package/{skills → rules}/doc-files/js/docgen-crc.mjs +0 -0
- /package/{skills → rules}/doc-files/js/docgen-extract-anchors.mjs +0 -0
- /package/{skills → rules}/doc-files/js/docgen-files-batch.mjs +0 -0
- /package/{skills → rules}/doc-files/js/docgen-gen.mjs +0 -0
- /package/{skills → rules}/doc-files/js/docgen-prompts.mjs +0 -0
- /package/{skills → rules}/doc-files/js/units-js.mjs +0 -0
- /package/{skills → rules}/doc-files/js/units.mjs +0 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
docgen:
|
|
3
|
+
source: npm/rules/doc-files/js/docgen-scan.mjs
|
|
4
|
+
crc: 46f11827
|
|
5
|
+
score: 100
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# docgen-scan.mjs
|
|
9
|
+
|
|
10
|
+
## Огляд
|
|
11
|
+
|
|
12
|
+
isSourceFile перевіряє, чи є файл кодовим джерелом.
|
|
13
|
+
docPathForSource обчислює шлях md-документа для кодового файлу.
|
|
14
|
+
isDocCandidate перевіряє, чи підлягає файл документуванню.
|
|
15
|
+
describeFile описує кодовий файл, включаючи шлях доки та стан застарілості.
|
|
16
|
+
scanForDocFiles рекурсивно обходить дерево, повертаючи кандидати з інформацією про старілість.
|
|
17
|
+
resolveRoot парсить аргументи, щоб визначити абсолютний корінь.
|
|
18
|
+
runDocFilesScanCli сканує дерево і друкує JSON-масив усіх кодових файлів зі станом застарілості.
|
|
19
|
+
runDocFilesCheckCli детектує застарілість для хук'ів, гейтів або інших режимів, повертаючи код виходу.
|
|
20
|
+
|
|
21
|
+
## Поведінка
|
|
22
|
+
|
|
23
|
+
isSourceFile Обчислює, чи є файл кодовим джерелом.
|
|
24
|
+
docPathForSource Обчислює шлях md-документа для кодового файлу.
|
|
25
|
+
isDocCandidate Перевіряє, чи підлягає файл документуванню.
|
|
26
|
+
describeFile Описує кодовий файл, включаючи шлях доки та стан застарілості.
|
|
27
|
+
scanForDocFiles Рекурсивно обходить дерево, повертаючи кандидати з інформацією про старілість.
|
|
28
|
+
resolveRoot Парсить аргументи, щоб визначити абсолютний корінь.
|
|
29
|
+
runDocFilesScanCli Сканує дерево і друкує JSON-масив усіх кодових файлів зі станом застарілості.
|
|
30
|
+
runDocFilesCheckCli Детектує застарілість для хук'ів, гейтів або інших режимів, повертаючи код виходу.
|
|
31
|
+
|
|
32
|
+
## Публічний API
|
|
33
|
+
|
|
34
|
+
isSourceFile — перевіряє, чи є файл коду для документування.
|
|
35
|
+
docPathForSource — обчислює шлях до документа для кодового файлу, розміщуючи його в теці `docs/` поруч із джерелом. Якщо шлях відносний, документ також відносний; якщо абсолютний, документ залишається абсолютним.
|
|
36
|
+
isDocCandidate — визначає, чи підлягає кодовий файл документуванню: має правильне розширення, не є тестом, не знаходиться в ігнорованому списку, і не є кореневим системним документуванням.
|
|
37
|
+
describeFile — надає опис кодового файлу: шлях до джерела, шлях до документа та стан застарілості за CRC.
|
|
38
|
+
scanForDocFiles — рекурсивно переглядає дерево від заданого кореня і повертає кодові файли разом зі станом застарілості.
|
|
39
|
+
resolveRoot — парсить аргумент `--root <dir>` з командного рядка; за замовчуванням використовує поточну робочу директорію.
|
|
40
|
+
runDocFilesScanCli — сканує дерево і виводить JSON-масив усіх кодових файлів із зазначенням їхнього стану застарілості.
|
|
41
|
+
runDocFilesCheckCli — виконує перевірку застарілості для хуків та командного рядка через інструмент `doc-files check`.
|
|
42
|
+
Режими — це способи виконання:
|
|
43
|
+
--hook — бере шлях до файлу з вводу JSON і перевіряє один файл.
|
|
44
|
+
--git — перевіряє різницю в Git (`git diff --name-only HEAD`) з урахуванням порогу `--max` (за замовчуванням 50); якщо застарілості більше, не блокує (виходить з кодом 0 з попередженням).
|
|
45
|
+
--degraded — генерує інформаційний звіт про документи, які мають оцінку нижче встановленого порогу (виходить з кодом 0).
|
|
46
|
+
<paths…> — використовується для визначення явних шляхів до джерел.
|
|
47
|
+
Exit 2 — повертається, якщо знайдено застарілі дані; повертається 0, якщо дані свіжі або пройдено перевищення порогу.
|
|
48
|
+
|
|
49
|
+
## Гарантії поведінки
|
|
50
|
+
|
|
51
|
+
- Read-only: файл не виконує операцій запису у файлову систему.
|
|
52
|
+
- Перехоплює помилки і не пропускає винятків назовні (fail-safe).
|
|
53
|
+
- За невдачі повертає значення помилки (`false`/`null`/`Err`) замість генерування винятку чи паніки.
|
|
54
|
+
- Не звертається до мережі.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
docgen:
|
|
3
|
+
source: npm/rules/doc-files/js/lint.mjs
|
|
4
|
+
crc: f25a3bbe
|
|
5
|
+
score: 100
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# lint.mjs
|
|
9
|
+
|
|
10
|
+
## Огляд
|
|
11
|
+
|
|
12
|
+
Адаптер правила doc-files до агрегатора `n-cursor lint`. Дає агрегатору відповідь на одне
|
|
13
|
+
питання: чи має кожен дотичний кодовий файл актуальну файлову документацію поряд із собою.
|
|
14
|
+
|
|
15
|
+
## Поведінка
|
|
16
|
+
|
|
17
|
+
У quick-фазі отримує список змінених файлів і зводить його до набору джерел для перевірки в
|
|
18
|
+
**обидва** боки: змінене джерело перевіряється проти своєї доки, а змінена або видалена дока
|
|
19
|
+
(`<dir>/docs/<stem>.md`) повертається до відповідного джерела з тим самим іменем у каталозі над
|
|
20
|
+
`docs/`. У ci-фазі (списку немає) обходить усе дерево.
|
|
21
|
+
|
|
22
|
+
Порушенням вважається відсутня дока (`missing`) або розбіжність контрольної суми джерела з тією,
|
|
23
|
+
що записана у frontmatter доки (`crc-mismatch`). Документ із низькою якістю, але свіжою сумою —
|
|
24
|
+
не порушення.
|
|
25
|
+
|
|
26
|
+
## Публічний API
|
|
27
|
+
|
|
28
|
+
`lint` — перевіряє документацію для переданого набору змінених файлів (або всього репозиторію,
|
|
29
|
+
якщо набір не задано); повертає код виходу `1`, якщо є застарілі чи відсутні доки, інакше `0`.
|
|
30
|
+
Список проблемних файлів друкується у stderr із підказкою регенерувати.
|
|
31
|
+
|
|
32
|
+
## Гарантії поведінки
|
|
33
|
+
|
|
34
|
+
- Детермінованість: жодного виклику мовної моделі, рішення лише за контрольною сумою.
|
|
35
|
+
- Реверс-мапінг доки до джерела перебирає лише кодові розширення і повертає наявний файл-кандидат.
|
|
36
|
+
- Порожній набір змінених файлів дає код `0` без обходу дерева.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
docgen:
|
|
3
|
+
source: npm/rules/doc-files/js/units-js.mjs
|
|
4
|
+
crc: 58b898cc
|
|
5
|
+
score: 100
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# units-js.mjs
|
|
9
|
+
|
|
10
|
+
## Огляд
|
|
11
|
+
|
|
12
|
+
Файл парсить програму, ітеруючи по її елементах для збору юнітів. Для кожної декларації визначається опис. Для функцій та класів збираються виклики інших юнітів через ребра викликів.
|
|
13
|
+
|
|
14
|
+
## Поведінка
|
|
15
|
+
|
|
16
|
+
1. Парсинг програми.
|
|
17
|
+
2. Ітерація по тілу програми.
|
|
18
|
+
3. Збір юнітів.
|
|
19
|
+
4. Для кожної декларації визначається опис.
|
|
20
|
+
5. Для функцій та класів збираються виклики інших юнітів.
|
|
21
|
+
6. Вибираються виклики інших юнітів з ребрами викликів.
|
|
22
|
+
|
|
23
|
+
## Публічний API
|
|
24
|
+
|
|
25
|
+
extractUnitsJs — Юніт-шар для js/mjs/ts: top-level функції/класи/const-функції з тілом, JSDoc, прапором експорту і ребрами call-graph (виклики ІНШИХ юнітів у тілі).
|
|
26
|
+
|
|
27
|
+
## Гарантії поведінки
|
|
28
|
+
|
|
29
|
+
- Read-only: файл не виконує операцій запису у файлову систему.
|
|
30
|
+
- За невдачі повертає значення помилки (`false`/`null`/`Err`) замість генерування винятку чи паніки.
|
|
31
|
+
- Не звертається до мережі.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
docgen:
|
|
3
|
+
source: npm/rules/doc-files/js/units-rs.mjs
|
|
4
|
+
crc: 114e9a0b
|
|
5
|
+
score: 100
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# units-rs.mjs
|
|
9
|
+
|
|
10
|
+
## Огляд
|
|
11
|
+
|
|
12
|
+
Файл аналізує структуру коду для вилучення інформації про юніти. Процес включає сканування рядків, ітерацію для визначення глибини блоків та позиції закриваючих лапок. Обробляються рядкові літерали для визначення межі блоків та підраховуються глибини блоків для визначення видимості. Обробляються декларації для визначення публічності. Витягується тіло функцій, структур, перерахувань або трейтів. Визначається експонування функцій через атрибути. Збирається документація перед декларацією. Формується об'єкт юніту з назвою, типом, публічністю, ім'я impl та діапазон. Визначається область видимості тіла за допомогою пошуку закриваючої фігу. Збираються виклики інших юнітів у тілі функції.
|
|
13
|
+
|
|
14
|
+
## Поведінка
|
|
15
|
+
|
|
16
|
+
1. Скан файлу по рядках.
|
|
17
|
+
2. Ітерація по рядках для визначення глибини блоків.
|
|
18
|
+
3. Обробка рядкових літералів для визначення позиції закриваючого лапки.
|
|
19
|
+
4. Підрахунок глибини блоків для визначення видимості.
|
|
20
|
+
5. Обробка декларацій для визначення публічності.
|
|
21
|
+
6. Витягнення тіла функції, структури, перерахування або трейта.
|
|
22
|
+
7. Визначення експонування функції через атрибути.
|
|
23
|
+
8. Збір документації перед декларацією.
|
|
24
|
+
9. Формування об'єкта юніту з назвою, типом, публічністю, ім'ям impl та діапазоном.
|
|
25
|
+
10. Визначення області видимості тіла за допомогою пошуку закриваючої фігу.
|
|
26
|
+
11. Збір викликів інших юнітів у тілі функції.
|
|
27
|
+
|
|
28
|
+
## Публічний API
|
|
29
|
+
|
|
30
|
+
extractUnitsRs — Визначає top-level і impl-методи через підрахунок дужок по рядках. Обмеження: рядкові літерали з `{`/`}` всередині `{}` можуть дати хибну глибину.
|
|
31
|
+
|
|
32
|
+
## Гарантії поведінки
|
|
33
|
+
|
|
34
|
+
- Read-only: файл не виконує операцій запису у файлову систему.
|
|
35
|
+
- Не звертається до мережі.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
docgen:
|
|
3
|
+
source: npm/rules/doc-files/js/units.mjs
|
|
4
|
+
crc: af91bd0d
|
|
5
|
+
score: 100
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# units.mjs
|
|
9
|
+
|
|
10
|
+
## Огляд
|
|
11
|
+
|
|
12
|
+
Файл витягує одиниці з вмісту файлу залежно від розширення шляху. Якщо розширення належить до набору JS_EXT, використовується extractUnitsJs. Якщо розширення дорівнює 'rs', використовується extractUnitsRs. В інших випадках функція повертає null.
|
|
13
|
+
|
|
14
|
+
## Поведінка
|
|
15
|
+
|
|
16
|
+
1. extractUnits приймає вміст файлу та шлях файлу.
|
|
17
|
+
2. Перевіряє розширення шляху.
|
|
18
|
+
3. Якщо розширення знаходиться у наборі JS_EXT, викликає extractUnitsJs.
|
|
19
|
+
4. Якщо розширення дорівнює 'rs', викликає extractUnitsRs.
|
|
20
|
+
5. У інших випадках повертає null.
|
|
21
|
+
|
|
22
|
+
## Публічний API
|
|
23
|
+
|
|
24
|
+
extractUnits — Визначає тип парсингу залежно від розширення файлу: js/mjs/ts використовує oxc AST, rs використовує regex+brace-counting, vue/py повертає null (повний шлях).
|
|
25
|
+
|
|
26
|
+
## Гарантії поведінки
|
|
27
|
+
|
|
28
|
+
- Read-only: файл не виконує операцій запису у файлову систему.
|
|
29
|
+
- За невдачі повертає значення помилки (`false`/`null`/`Err`) замість генерування винятку чи паніки.
|
|
30
|
+
- Не звертається до мережі.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Адаптер агрегатора `n-cursor lint` для правила doc-files.
|
|
3
|
+
*
|
|
4
|
+
* Quick-фаза отримує список змінених файлів і мапить їх у пари в **обидва** боки:
|
|
5
|
+
* - змінене **джерело** (`.js/.mjs/.ts/.vue/.py/.rs`) → перевірка його доки `<dir>/docs/<stem>.md`;
|
|
6
|
+
* - змінена/видалена **дока** (`<dir>/docs/<stem>.md`) → перевірка відповідного джерела
|
|
7
|
+
* (той самий stem у каталозі над текою `docs`).
|
|
8
|
+
* Ci-фаза (files === undefined) проганяє повний скан дерева.
|
|
9
|
+
*
|
|
10
|
+
* Порушення — `missing` ∪ `crc-mismatch` (детермінований CRC-детект, 0 LLM-токенів);
|
|
11
|
+
* degraded не блокує. Exit 1 — є stale; 0 — все свіже (конвенція агрегатора).
|
|
12
|
+
*/
|
|
13
|
+
import { join, dirname, basename, extname } from 'node:path'
|
|
14
|
+
import { existsSync, readdirSync } from 'node:fs'
|
|
15
|
+
|
|
16
|
+
import { describeFile, isDocCandidate, isSourceFile, scanForDocFiles } from './docgen-scan.mjs'
|
|
17
|
+
|
|
18
|
+
/** Дока живе у `<dir>/docs/<stem>.md`; повертає `<dir>/<stem>` для реверс-мапінгу. */
|
|
19
|
+
const DOC_MD_RE = /(?:^|\/)docs\/[^/]+\.md$/u
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Реверс-мапінг доки → джерело: для `<dir>/docs/<stem>.md` шукає у `<dir>` файл
|
|
23
|
+
* `<stem>.<ext>` із кодовим розширенням, що існує і є кандидатом на доку.
|
|
24
|
+
* @param {string} cwd корінь репо
|
|
25
|
+
* @param {string} docRel posix-шлях доки від кореня
|
|
26
|
+
* @returns {string|null} posix-шлях джерела або null
|
|
27
|
+
*/
|
|
28
|
+
function sourceForDoc(cwd, docRel) {
|
|
29
|
+
const docsDir = dirname(docRel) // `<dir>/docs`
|
|
30
|
+
const srcDir = dirname(docsDir) // `<dir>`
|
|
31
|
+
const stem = basename(docRel, '.md')
|
|
32
|
+
let entries
|
|
33
|
+
try {
|
|
34
|
+
entries = readdirSync(join(cwd, srcDir), { withFileTypes: true })
|
|
35
|
+
} catch {
|
|
36
|
+
return null
|
|
37
|
+
}
|
|
38
|
+
for (const e of entries) {
|
|
39
|
+
if (!e.isFile() || !isSourceFile(e.name)) continue
|
|
40
|
+
if (basename(e.name, extname(e.name)) !== stem) continue
|
|
41
|
+
const rel = srcDir === '.' ? e.name : `${srcDir}/${e.name}`
|
|
42
|
+
if (isDocCandidate(cwd, rel)) return rel
|
|
43
|
+
}
|
|
44
|
+
return null
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Зводить список змінених файлів у множину джерел-кандидатів для перевірки доки.
|
|
49
|
+
* @param {string[]} files змінені шляхи (posix або нативні)
|
|
50
|
+
* @param {string} cwd корінь репо
|
|
51
|
+
* @returns {string[]} унікальні posix-шляхи джерел
|
|
52
|
+
*/
|
|
53
|
+
function sourcesFromChanged(files, cwd) {
|
|
54
|
+
const out = new Set()
|
|
55
|
+
for (const raw of files) {
|
|
56
|
+
const rel = raw.split('\\').join('/')
|
|
57
|
+
if (DOC_MD_RE.test(rel)) {
|
|
58
|
+
const src = sourceForDoc(cwd, rel)
|
|
59
|
+
if (src) out.add(src)
|
|
60
|
+
} else if (isDocCandidate(cwd, rel) && existsSync(join(cwd, rel))) {
|
|
61
|
+
out.add(rel)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return [...out]
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Друкує список stale і повертає exit-код.
|
|
69
|
+
* @param {Array<{sourcePath:string, reason:string|null}>} stale застарілі описи
|
|
70
|
+
* @returns {number} 1 — є stale; 0 — немає
|
|
71
|
+
*/
|
|
72
|
+
function reportStale(stale) {
|
|
73
|
+
if (stale.length === 0) return 0
|
|
74
|
+
const list = stale.map(f => ` - ${f.sourcePath} (${f.reason})`).join('\n')
|
|
75
|
+
process.stderr.write(
|
|
76
|
+
`✗ doc-files: документація застаріла/відсутня для ${stale.length} файл(ів):\n${list}\n→ перегенеруй: npx @nitra/cursor fix-doc-files\n`
|
|
77
|
+
)
|
|
78
|
+
return 1
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Крок агрегатора lint для doc-files.
|
|
83
|
+
* @param {string[] | undefined} files quick: лише ці файли; undefined: весь репозиторій
|
|
84
|
+
* @param {string} [cwd] корінь репо
|
|
85
|
+
* @returns {Promise<number>} 0 — OK, 1 — є застарілі доки
|
|
86
|
+
*/
|
|
87
|
+
export function lint(files, cwd = process.cwd()) {
|
|
88
|
+
if (files === undefined) {
|
|
89
|
+
const stale = scanForDocFiles(cwd).filter(f => f.stale)
|
|
90
|
+
return Promise.resolve(reportStale(stale))
|
|
91
|
+
}
|
|
92
|
+
const sources = sourcesFromChanged(files, cwd)
|
|
93
|
+
if (sources.length === 0) return Promise.resolve(0)
|
|
94
|
+
const stale = sources.map(src => describeFile(cwd, src)).filter(f => f.stale)
|
|
95
|
+
return Promise.resolve(reportStale(stale))
|
|
96
|
+
}
|
|
@@ -9,7 +9,10 @@
|
|
|
9
9
|
function skipString(src, i) {
|
|
10
10
|
i++ // відкриваючий "
|
|
11
11
|
while (i < src.length) {
|
|
12
|
-
if (src[i] === '\\') {
|
|
12
|
+
if (src[i] === '\\') {
|
|
13
|
+
i += 2
|
|
14
|
+
continue
|
|
15
|
+
}
|
|
13
16
|
if (src[i] === '"') return i + 1
|
|
14
17
|
i++
|
|
15
18
|
}
|
|
@@ -38,8 +41,15 @@ function findClosingBrace(src, start) {
|
|
|
38
41
|
i = end === -1 ? src.length : end + 2
|
|
39
42
|
continue
|
|
40
43
|
}
|
|
41
|
-
if (ch === '"') {
|
|
42
|
-
|
|
44
|
+
if (ch === '"') {
|
|
45
|
+
i = skipString(src, i)
|
|
46
|
+
continue
|
|
47
|
+
}
|
|
48
|
+
if (ch === '{') {
|
|
49
|
+
depth++
|
|
50
|
+
i++
|
|
51
|
+
continue
|
|
52
|
+
}
|
|
43
53
|
if (ch === '}') {
|
|
44
54
|
depth--
|
|
45
55
|
if (depth === 0) return i
|
|
@@ -73,12 +83,17 @@ function docBefore(lines, lineIdx) {
|
|
|
73
83
|
return doc.join(' ').trim()
|
|
74
84
|
}
|
|
75
85
|
|
|
76
|
-
// Pub-items
|
|
86
|
+
// Pub-items матчаться у два кроки по trim-нутому рядку (прості регекспи без
|
|
87
|
+
// бектрекінгу): спершу опційний pub(...)-префікс, потім сама декларація.
|
|
77
88
|
// Також ловить fn без pub (для localSymbols і impl-методів)
|
|
78
|
-
const
|
|
89
|
+
const PUB_PREFIX_RE = /^pub(?:\([^)]*\))?\s+/
|
|
90
|
+
const ITEM_DECL_RE = /^(?:async\s+)?(?:unsafe\s+)?(fn|struct|enum|trait|type)\s+(\w+)/
|
|
79
91
|
|
|
80
|
-
// impl Type { або impl<T> Trait for Type {
|
|
81
|
-
|
|
92
|
+
// impl Type { або impl<T> Trait for Type { — теж двокроково: голова `impl<...>`,
|
|
93
|
+
// далі тип після `for` (trait-impl) або перше слово (inherent impl)
|
|
94
|
+
const IMPL_HEAD_RE = /^impl(?:<[^>]*>)?\s+/
|
|
95
|
+
const IMPL_FOR_TYPE_RE = /\bfor\s+(\w+)/
|
|
96
|
+
const TYPE_NAME_RE = /^(\w+)/
|
|
82
97
|
|
|
83
98
|
// Підозрілі exposure-атрибути, що роблять непуб-fn фактично публічними
|
|
84
99
|
const EXPOSURE_ATTR_RE = /#\[(?:tauri::command|wasm_bindgen|uniffi::export|pyo3::pyfunction|napi)/
|
|
@@ -93,7 +108,7 @@ const CALL_RE = /\b([a-z_]\w*)\s*\(/g
|
|
|
93
108
|
* хибну глибину (рідкісно в реальному Rust-коді з rustfmt).
|
|
94
109
|
* @param {string} src вміст файлу
|
|
95
110
|
* @param {string} [_relPath] резервний (не використовується)
|
|
96
|
-
* @returns {Array<{name:string, kind:string, exported:boolean, implName:string|null, span:{start:number,end:number}, body:string, calls:string[], doc:string}>|null}
|
|
111
|
+
* @returns {Array<{name:string, kind:string, exported:boolean, implName:string|null, span:{start:number,end:number}, body:string, calls:string[], doc:string}>|null} юніти файлу (fn та impl-методи) або `null`, якщо юнітів не знайдено
|
|
97
112
|
*/
|
|
98
113
|
export function extractUnitsRs(src, _relPath) {
|
|
99
114
|
const lines = src.split('\n')
|
|
@@ -129,7 +144,7 @@ export function extractUnitsRs(src, _relPath) {
|
|
|
129
144
|
}
|
|
130
145
|
|
|
131
146
|
// Закриті impl-блоки прибираємо зі стека
|
|
132
|
-
while (implStack.length > 0 && implStack
|
|
147
|
+
while (implStack.length > 0 && implStack.at(-1).openDepth > depth) {
|
|
133
148
|
implStack.pop()
|
|
134
149
|
}
|
|
135
150
|
|
|
@@ -140,22 +155,27 @@ export function extractUnitsRs(src, _relPath) {
|
|
|
140
155
|
nextFnExposed = true
|
|
141
156
|
}
|
|
142
157
|
|
|
158
|
+
const trimmed = line.trimStart()
|
|
159
|
+
|
|
143
160
|
// Impl-декларація (зазвичай глибина 0, але може бути в mod)
|
|
144
161
|
if (depthAtStart <= 1) {
|
|
145
|
-
const
|
|
146
|
-
if (
|
|
147
|
-
|
|
162
|
+
const headM = trimmed.match(IMPL_HEAD_RE)
|
|
163
|
+
if (headM && line.includes('{')) {
|
|
164
|
+
const rest = trimmed.slice(headM[0].length)
|
|
165
|
+
const typeM = rest.match(IMPL_FOR_TYPE_RE) ?? rest.match(TYPE_NAME_RE)
|
|
166
|
+
if (typeM) implStack.push({ typeName: typeM[1], openDepth: depth })
|
|
148
167
|
}
|
|
149
168
|
}
|
|
150
169
|
|
|
151
170
|
// Елементи на глибині 0 (top-level) і 1 (всередині impl)
|
|
152
171
|
if (depthAtStart <= 1) {
|
|
153
|
-
const
|
|
172
|
+
const pubM = trimmed.match(PUB_PREFIX_RE)
|
|
173
|
+
const m = (pubM ? trimmed.slice(pubM[0].length) : trimmed).match(ITEM_DECL_RE)
|
|
154
174
|
if (m) {
|
|
155
|
-
const isPub = Boolean(
|
|
156
|
-
if (m[
|
|
157
|
-
const kind = m[
|
|
158
|
-
const name = m[
|
|
175
|
+
const isPub = Boolean(pubM) || (m[1] === 'fn' && nextFnExposed)
|
|
176
|
+
if (m[1] === 'fn') nextFnExposed = false
|
|
177
|
+
const kind = m[1]
|
|
178
|
+
const name = m[2]
|
|
159
179
|
const doc = docBefore(lines, li)
|
|
160
180
|
|
|
161
181
|
// Витягуємо тіло через findClosingBrace для fn/struct/enum/trait
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
docgen:
|
|
3
|
+
source: npm/rules/doc-files/lint/lint.mjs
|
|
4
|
+
crc: a30dd81f
|
|
5
|
+
score: 100
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# lint.mjs
|
|
9
|
+
|
|
10
|
+
## Огляд
|
|
11
|
+
|
|
12
|
+
CLI-команда `lint-doc-files` — детермінований детектор застарілості файлових док. Працює без
|
|
13
|
+
мовної моделі скрізь: локально, в hook'ах і в CI.
|
|
14
|
+
|
|
15
|
+
## Поведінка
|
|
16
|
+
|
|
17
|
+
Без прапорців або з переліком шляхів робить повний чи точковий детект і повертає код `1`, якщо є
|
|
18
|
+
застарілі/відсутні доки. `--missing-only` звужує до самих відсутніх. `--json` друкує машинний
|
|
19
|
+
лістинг усіх кандидатів зі станом і завершується кодом `0`. Режими `--hook`, `--git` і
|
|
20
|
+
`--degraded` делегуються детектору hook-протоколу (PostToolUse за одним файлом зі stdin, Stop-гейт
|
|
21
|
+
за зміненими у задачі джерелами з порогом, інформаційний звіт про доки нижчої якості).
|
|
22
|
+
|
|
23
|
+
Повний прогін серіалізується спільним локом під ключем, виведеним зі шляху каталогу, тож паралельні
|
|
24
|
+
запуски не накладаються; hook-форми навмисно виконуються без локу заради завжди-свіжого вердикту.
|
|
25
|
+
|
|
26
|
+
## Публічний API
|
|
27
|
+
|
|
28
|
+
`runLintDocFilesCli` — точка входу команди: маршрутизує між делегатом hook-протоколу, JSON-лістингом
|
|
29
|
+
і повним/точковим детектом; повертає код виходу.
|
|
30
|
+
`runLintDocFilesSteps` — сама робота повного чи точкового детекту, придатна для прямого виклику з
|
|
31
|
+
тестів без локу; повертає `1` за наявності застарілих док, інакше `0`.
|
|
32
|
+
|
|
33
|
+
## Гарантії поведінки
|
|
34
|
+
|
|
35
|
+
- Неіснуючий корінь дає зрозумілу помилку і код `1`.
|
|
36
|
+
- Повний прогін — код `1` (конвенція `lint-*`); hook/git — код `2` (blocking feedback для Claude Code).
|
|
37
|
+
- Жодних викликів мовної моделі.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI-обгортка канонічного `lint-doc-files` (doc-files.mdc): детермінований детектор
|
|
3
|
+
* застарілості файлових док (`<dir>/docs/<stem>.md`) — 0 викликів LLM, працює будь-де.
|
|
4
|
+
*
|
|
5
|
+
* Режими (мапа команд у doc-files.mdc / спеці 2026-06-12):
|
|
6
|
+
* - (без прапорців) / `[paths…]` — повний або точковий детект; **exit 1**, якщо є stale.
|
|
7
|
+
* - `--missing-only` — звужує до `missing` (без `crc-mismatch`); exit 1.
|
|
8
|
+
* - `--json` — JSON-лістинг усіх кандидатів зі станом (= старий `scan`); exit 0.
|
|
9
|
+
* - `--hook` / `--git` / `--degraded` — делегат у `runDocFilesCheckCli` (hook-протокол: exit 2/0).
|
|
10
|
+
*
|
|
11
|
+
* Серіалізація: повний прогін — через `runStandardLint` (ключ `lint-doc-files`,
|
|
12
|
+
* виводиться зі шляху каталогу). Hook/git/degraded форми — **без локу** (швидкі точкові
|
|
13
|
+
* перевірки в hook-протоколі потребують завжди-свіжого вердикту) — канон scripts.mdc.
|
|
14
|
+
*/
|
|
15
|
+
import { existsSync, statSync } from 'node:fs'
|
|
16
|
+
import { join, relative, resolve, sep, isAbsolute } from 'node:path'
|
|
17
|
+
|
|
18
|
+
import { isRunAsCli } from '../../../scripts/cli-entry.mjs'
|
|
19
|
+
import { runStandardLint } from '../../../scripts/lib/run-standard-lint.mjs'
|
|
20
|
+
import {
|
|
21
|
+
describeFile,
|
|
22
|
+
isDocCandidate,
|
|
23
|
+
resolveRoot,
|
|
24
|
+
runDocFilesCheckCli,
|
|
25
|
+
runDocFilesScanCli,
|
|
26
|
+
scanForDocFiles
|
|
27
|
+
} from '../js/docgen-scan.mjs'
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Нормалізує шлях-кандидат до posix-шляху від кореня (null поза деревом).
|
|
31
|
+
* @param {string} root абсолютний корінь
|
|
32
|
+
* @param {string} candidate шлях-кандидат
|
|
33
|
+
* @returns {string|null} posix-шлях від кореня або null
|
|
34
|
+
*/
|
|
35
|
+
function toRelSource(root, candidate) {
|
|
36
|
+
const rel = relative(root, resolve(root, candidate))
|
|
37
|
+
if (rel.startsWith('..') || isAbsolute(rel)) return null
|
|
38
|
+
return rel.split(sep).join('/')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Витягує позиційні шляхи з argv (не прапорці й не значення `--root`).
|
|
43
|
+
* @param {string[]} argv аргументи
|
|
44
|
+
* @returns {string[]} позиційні шляхи
|
|
45
|
+
*/
|
|
46
|
+
function positionalPaths(argv) {
|
|
47
|
+
const rootIdx = argv.indexOf('--root')
|
|
48
|
+
const skip = rootIdx !== -1 ? rootIdx + 1 : -1
|
|
49
|
+
return argv.filter((a, i) => !a.startsWith('--') && i !== skip)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Реальна робота повного / точкового детекту. Exit 1 — є stale, 0 — все свіже.
|
|
54
|
+
* @param {string[]} argv аргументи після назви команди
|
|
55
|
+
* @returns {number} exit-код
|
|
56
|
+
*/
|
|
57
|
+
export function runLintDocFilesSteps(argv) {
|
|
58
|
+
const root = resolveRoot(argv)
|
|
59
|
+
if (!existsSync(root) || !statSync(root).isDirectory()) {
|
|
60
|
+
console.error(`lint-doc-files: корінь не існує або не є директорією: ${root}`)
|
|
61
|
+
return 1
|
|
62
|
+
}
|
|
63
|
+
const missingOnly = argv.includes('--missing-only')
|
|
64
|
+
const paths = positionalPaths(argv)
|
|
65
|
+
|
|
66
|
+
const described = paths.length
|
|
67
|
+
? paths
|
|
68
|
+
.map(p => toRelSource(root, p))
|
|
69
|
+
.filter(rel => rel && isDocCandidate(root, rel) && existsSync(join(root, rel)))
|
|
70
|
+
.map(rel => describeFile(root, /** @type {string} */ (rel)))
|
|
71
|
+
: scanForDocFiles(root)
|
|
72
|
+
|
|
73
|
+
let stale = described.filter(f => f.stale)
|
|
74
|
+
if (missingOnly) stale = stale.filter(f => f.reason === 'missing')
|
|
75
|
+
|
|
76
|
+
if (stale.length === 0) {
|
|
77
|
+
console.log('✓ doc-files: усі файлові доки актуальні.')
|
|
78
|
+
return 0
|
|
79
|
+
}
|
|
80
|
+
const list = stale.map(f => ` - ${f.sourcePath} (${f.reason})`).join('\n')
|
|
81
|
+
console.error(
|
|
82
|
+
`✗ doc-files: документація застаріла/відсутня для ${stale.length} файл(ів):\n${list}\n→ перегенеруй: npx @nitra/cursor fix-doc-files`
|
|
83
|
+
)
|
|
84
|
+
return 1
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Публічна CLI-форма `lint-doc-files`. Hook/git/degraded — делегат без локу; `--json` —
|
|
89
|
+
* scan; решта — повний/точковий детект під `runStandardLint` (ключ `lint-doc-files`).
|
|
90
|
+
* @param {string[]} [argv] аргументи після назви команди
|
|
91
|
+
* @returns {Promise<number>} exit-код
|
|
92
|
+
*/
|
|
93
|
+
export function runLintDocFilesCli(argv = process.argv.slice(3)) {
|
|
94
|
+
if (argv.includes('--hook') || argv.includes('--git') || argv.includes('--degraded')) {
|
|
95
|
+
return runDocFilesCheckCli(argv)
|
|
96
|
+
}
|
|
97
|
+
if (argv.includes('--json')) {
|
|
98
|
+
return Promise.resolve(runDocFilesScanCli(argv))
|
|
99
|
+
}
|
|
100
|
+
return runStandardLint(import.meta.dirname, () => runLintDocFilesSteps(argv))
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (isRunAsCli(import.meta.url)) {
|
|
104
|
+
process.exitCode = await runLintDocFilesCli(process.argv.slice(2))
|
|
105
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "auto": "завжди", "lint": "quick" }
|