@nitra/cursor 5.3.4 → 6.0.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 (173) hide show
  1. package/.claude-template/settings.template.json +2 -2
  2. package/.pi-template/extensions/n-cursor-adr/docs/index.md +13 -24
  3. package/CHANGELOG.md +21 -0
  4. package/bin/n-cursor.js +47 -24
  5. package/lib/docs/models.md +29 -18
  6. package/lib/docs/omlx-trace.md +51 -0
  7. package/lib/docs/omlx.md +31 -15
  8. package/lib/omlx.mjs +2 -5
  9. package/package.json +1 -1
  10. package/rules/abie/docs/fix.md +17 -11
  11. package/rules/adr/docs/fix.md +25 -140
  12. package/rules/bun/docs/fix.md +18 -151
  13. package/rules/capacitor/docs/fix.md +16 -13
  14. package/rules/capacitor/js/docs/platforms.md +31 -43
  15. package/rules/changelog/docs/fix.md +25 -169
  16. package/rules/ci4/docs/fix.md +11 -14
  17. package/rules/doc-files/doc-files.mdc +60 -0
  18. package/rules/doc-files/docs/fix.md +31 -0
  19. package/rules/doc-files/fix.mjs +19 -0
  20. package/{skills → rules}/doc-files/js/docgen-extract.mjs +42 -19
  21. package/{skills → rules}/doc-files/js/docgen-files-batch.mjs +18 -5
  22. package/{skills → rules}/doc-files/js/docgen-gen.mjs +46 -5
  23. package/{skills → rules}/doc-files/js/docgen-ignore.mjs +2 -1
  24. package/{skills → rules}/doc-files/js/docgen-scan.mjs +11 -3
  25. package/{skills → rules}/doc-files/js/docs/docgen-crc.md +1 -1
  26. package/{skills → rules}/doc-files/js/docs/docgen-extract-anchors.md +1 -1
  27. package/{skills → rules}/doc-files/js/docs/docgen-extract.md +2 -2
  28. package/{skills → rules}/doc-files/js/docs/docgen-files-batch.md +2 -2
  29. package/{skills → rules}/doc-files/js/docs/docgen-gen.md +2 -2
  30. package/{skills → rules}/doc-files/js/docs/docgen-ignore.md +4 -4
  31. package/rules/doc-files/js/docs/docgen-prompts.md +39 -0
  32. package/rules/doc-files/js/docs/docgen-scan.md +54 -0
  33. package/rules/doc-files/js/docs/lint.md +36 -0
  34. package/rules/doc-files/js/docs/units-js.md +31 -0
  35. package/rules/doc-files/js/docs/units-rs.md +35 -0
  36. package/rules/doc-files/js/docs/units.md +30 -0
  37. package/rules/doc-files/js/lint.mjs +96 -0
  38. package/{skills → rules}/doc-files/js/units-rs.mjs +37 -17
  39. package/rules/doc-files/lint/docs/lint.md +37 -0
  40. package/rules/doc-files/lint/lint.mjs +105 -0
  41. package/rules/doc-files/meta.json +1 -0
  42. package/rules/docker/docs/fix.md +21 -161
  43. package/rules/efes/docs/fix.md +23 -194
  44. package/rules/feedback/docs/fix.md +10 -8
  45. package/rules/ga/docs/fix.md +10 -5
  46. package/rules/ga/meta.json +1 -1
  47. package/rules/graphql/docs/fix.md +23 -119
  48. package/rules/hasura/docs/fix.md +19 -5
  49. package/rules/hasura/js/docs/internal_urls.md +34 -307
  50. package/rules/image-avif/docs/fix.md +16 -127
  51. package/rules/image-compress/docs/fix.md +20 -141
  52. package/rules/image-compress/js/docs/package_setup.md +22 -182
  53. package/rules/js-bun-db/docs/fix.md +23 -139
  54. package/rules/js-bun-db/js/docs/safety.md +33 -221
  55. package/rules/js-bun-redis/docs/fix.md +25 -114
  56. package/rules/js-bun-redis/js/docs/imports.md +18 -166
  57. package/rules/js-lint/docs/fix.md +30 -108
  58. package/rules/js-lint/js/docs/lint-findings.md +37 -17
  59. package/rules/js-lint/js/docs/lint.md +22 -238
  60. package/rules/js-lint/js/docs/tooling.md +34 -331
  61. package/rules/js-lint/js/lint.mjs +19 -12
  62. package/rules/js-lint/js-lint.mdc +1 -1
  63. package/rules/js-lint/meta.json +1 -1
  64. package/rules/js-lint-ci/docs/fix.md +16 -149
  65. package/rules/js-lint-ci/js/docs/lint.md +16 -136
  66. package/rules/js-lint-ci/js-lint-ci.mdc +1 -1
  67. package/rules/js-lint-ci/meta.json +1 -1
  68. package/rules/js-mssql/docs/fix.md +18 -123
  69. package/rules/js-mssql/js/docs/deps.md +28 -251
  70. package/rules/js-run/docs/fix.md +23 -138
  71. package/rules/js-run/js/docs/runtime.md +24 -378
  72. package/rules/k8s/docs/fix.md +18 -123
  73. package/rules/nginx-default-tpl/docs/fix.md +22 -118
  74. package/rules/nginx-default-tpl/js/docs/template.md +38 -360
  75. package/rules/npm-module/docs/fix.md +27 -89
  76. package/rules/npm-module/js/docs/header_doc_pointer.md +15 -15
  77. package/rules/npm-module/js/docs/package_structure.md +36 -258
  78. package/rules/npm-module/js/docs/rule_meta.md +25 -127
  79. package/rules/npm-module/js/docs/skill_meta.md +18 -180
  80. package/rules/npm-module/js/rule_meta.mjs +3 -3
  81. package/rules/php/docs/fix.md +21 -98
  82. package/rules/php/js/docs/tooling.md +20 -143
  83. package/rules/python/docs/fix.md +25 -157
  84. package/rules/python/js/docs/applies.md +20 -98
  85. package/rules/python/js/docs/tooling.md +27 -144
  86. package/rules/rego/docs/fix.md +24 -112
  87. package/rules/rego/js/docs/applies.md +20 -164
  88. package/rules/rego/js/docs/lint.md +15 -110
  89. package/rules/rego/meta.json +1 -1
  90. package/rules/release/docs/fix.md +16 -114
  91. package/rules/rust/docs/fix.md +24 -119
  92. package/rules/rust/js/docs/applies.md +20 -129
  93. package/rules/security/docs/fix.md +21 -78
  94. package/rules/security/js/docs/sample_secret.md +23 -182
  95. package/rules/security/js/docs/trufflehog.md +19 -128
  96. package/rules/security/meta.json +1 -1
  97. package/rules/style-lint/docs/fix.md +16 -150
  98. package/rules/style-lint/js/docs/lint.md +21 -172
  99. package/rules/style-lint/js/docs/tooling.md +19 -184
  100. package/rules/style-lint/js/lint.mjs +4 -3
  101. package/rules/style-lint/meta.json +1 -1
  102. package/rules/tauri/docs/fix.md +26 -152
  103. package/rules/tauri/js/docs/cargo_mutants_config.md +21 -159
  104. package/rules/tauri/js/docs/tooling.md +20 -217
  105. package/rules/test/docs/fix.md +19 -127
  106. package/rules/test/js/data/stryker_config/docs/stryker.config.baseline.md +15 -127
  107. package/rules/test/js/data/stryker_config/docs/stryker.config.vue.baseline.md +17 -153
  108. package/rules/test/js/docs/cargo_mutants_config.md +24 -164
  109. package/rules/test/js/docs/location.md +24 -126
  110. package/rules/test/js/docs/no-process-chdir.md +20 -151
  111. package/rules/test/js/docs/no-relative-fs-path.md +24 -261
  112. package/rules/test/js/docs/stryker_config.md +48 -148
  113. package/rules/test/js/docs/vitest-config-pool-forks.md +21 -164
  114. package/rules/text/docs/fix.md +25 -113
  115. package/rules/text/js/docs/forbidden-prettier.md +21 -132
  116. package/rules/text/js/docs/formatting.md +60 -251
  117. package/rules/text/js/docs/lint.md +17 -114
  118. package/rules/text/js/lint.mjs +5 -3
  119. package/rules/text/lint/docs/lint.md +1 -1
  120. package/rules/text/lint/docs/run-dotenv-linter.md +1 -1
  121. package/rules/text/lint/docs/run-shellcheck.md +1 -1
  122. package/rules/text/lint/lint.mjs +13 -9
  123. package/rules/text/lint/run-dotenv-linter.mjs +13 -10
  124. package/rules/text/lint/run-shellcheck.mjs +10 -6
  125. package/rules/text/meta.json +1 -1
  126. package/rules/vue/docs/fix.md +25 -118
  127. package/rules/vue/js/docs/packages.md +25 -323
  128. package/rules/worktree/docs/fix.md +31 -150
  129. package/scripts/coverage-classify/docs/index.md +23 -209
  130. package/scripts/coverage-classify/docs/verdict-schema.md +14 -159
  131. package/scripts/dispatcher/docs/trace.md +35 -0
  132. package/scripts/docs/auto-rules.md +37 -361
  133. package/scripts/docs/lint-cli.md +12 -13
  134. package/scripts/docs/post-tool-use-fix.md +16 -15
  135. package/scripts/docs/skills-cli.md +26 -23
  136. package/scripts/docs/sync-claude-config.md +94 -34
  137. package/scripts/docs/worktree-cli.md +11 -34
  138. package/scripts/lib/docs/assert-project-root.md +14 -16
  139. package/scripts/lib/docs/changed-files.md +24 -139
  140. package/scripts/lib/docs/discover-check-rules-from-cursor.md +14 -146
  141. package/scripts/lib/docs/rule-meta.md +1 -1
  142. package/scripts/lib/docs/rule-predicates.md +20 -17
  143. package/scripts/lib/docs/run-rule-cli.md +14 -18
  144. package/scripts/lib/docs/run-rule.md +13 -20
  145. package/scripts/lib/docs/run-standard-rule.md +12 -15
  146. package/scripts/lib/docs/sync-gitignore-worktree.md +15 -18
  147. package/scripts/lib/rule-meta.mjs +10 -6
  148. package/scripts/lib/rule-predicates.mjs +1 -1
  149. package/scripts/lint-cli.mjs +28 -20
  150. package/scripts/sync-claude-config.mjs +4 -1
  151. package/scripts/utils/docs/with-lock.md +19 -12
  152. package/scripts/utils/with-lock.mjs +4 -2
  153. package/skills/doc-aggregate/SKILL.md +2 -2
  154. package/skills/doc-aggregate/js/docgen-ignore.mjs +6 -6
  155. package/skills/doc-aggregate/js/docs/docgen-ignore.md +1 -1
  156. package/skills/doc-aggregate/js/docs/docgen-scan.md +78 -0
  157. package/skills/doc-files/.changes/260612-0031.md +5 -0
  158. package/skills/doc-files/.changes/260612-0036.md +5 -0
  159. package/skills/doc-files/.changes/260612-0114.md +5 -0
  160. package/skills/doc-files/SKILL.md +6 -6
  161. package/skills/fix/js/docs/llm-worker.md +17 -15
  162. package/skills/fix/js/docs/orchestrator.md +30 -23
  163. package/skills/fix/js/docs/t0.md +26 -16
  164. package/skills/start-check/js/docs/check.md +26 -22
  165. package/skills/taze/js/docs/diff.md +44 -20
  166. package/skills/doc-files/js/docs/docgen-prompts.md +0 -32
  167. package/skills/doc-files/js/docs/docgen-scan.md +0 -25
  168. package/skills/doc-files/js/docs/units-rs.md +0 -35
  169. /package/{skills → rules}/doc-files/js/docgen-crc.mjs +0 -0
  170. /package/{skills → rules}/doc-files/js/docgen-extract-anchors.mjs +0 -0
  171. /package/{skills → rules}/doc-files/js/docgen-prompts.mjs +0 -0
  172. /package/{skills → rules}/doc-files/js/units-js.mjs +0 -0
  173. /package/{skills → rules}/doc-files/js/units.mjs +0 -0
@@ -1,183 +1,39 @@
1
1
  ---
2
2
  docgen:
3
3
  source: npm/rules/changelog/fix.mjs
4
- crc: 12fc1644
4
+ crc: 38cf876b
5
+ score: 100
5
6
  ---
6
7
 
7
- # fix.mjs — точка входу правила `changelog`
8
+ # fix.mjs
8
9
 
9
10
  ## Огляд
10
11
 
11
- Файл `npm/rules/changelog/fix.mjs` тонкий **entry-point** для правила `changelog` у складі пакета `@nitra/cursor`. Він виконує дві ролі одночасно:
12
+ Виконує застосування JS-занепокоєних на наданому контексті прогону. Застосовує визначену політику та генерує посилання MDC. Повертає результат прогону.
12
13
 
13
- 1. **Library mode** — експортує функцію `run(ctx)`, яку викликає зовнішній CLI-оркестратор (`scripts/cli/fix.mjs` або аналог), коли користувач запускає `npx @nitra/cursor fix changelog` (чи прогін усіх правил `fix`). У цьому режимі `runStandardRule` отримує контекст із кешем обходу файлової системи (`walkCache`), shared summary тощо.
14
- 2. **Standalone mode** — якщо файл запущено напряму (`bun npm/rules/changelog/fix.mjs`), виконується повноцінний CLI-сценарій із завантаженням конфігу, whitelist-фільтрацією й friendly summary — тобто еквівалент `npx @nitra/cursor fix changelog`.
14
+ ## Поведінка
15
15
 
16
- Сам файл **не містить бізнес-логіки** правила: вся перевірка/автофікс ділиться між суб-модулями (`applies` → `JS-concerns` → `policy` → `mdc-refs`), які підтягуються конвенційно через `runStandardRule(import.meta.dirname, ctx)`. Поведінка повністю детермінована директорією, в якій лежить `fix.mjs` (`import.meta.dirname` вказує на `npm/rules/changelog/`).
16
+ 1. Запуск правила.
17
+ * Приймає контекст прогону.
18
+ * Виконує застосування JS-занепокоєних.
19
+ * Застосовує політику.
20
+ * Генерує посилання MDC.
21
+ * Повертає результат прогону.
22
+ 2. Виконання у режимі CLI.
23
+ * Виконується як окремий скрипт.
24
+ * Еквівалент команди `npx @nitra/cursor fix <id>`.
25
+ * Виконує завантаження конфігурації.
26
+ * Виконує перевірку дозволених елементів.
27
+ * Генерує зведену інформацію.
28
+ * Виконує повний еквівалент `fix.mjs`.
17
29
 
18
- Це шаблонний файл-обгортка — він повторюється для **кожного** правила в `npm/rules/<rule-id>/fix.mjs`. Зміни тут зазвичай небажані; натомість конкретна перевірка живе у сусідніх теках (`js/`, `lib/`, `meta.json`, `changelog.mdc`).
30
+ ## Публічний API
19
31
 
20
- ## Експорти / API
32
+ run запускає правило: applies → JS-concerns → policy → mdc-refs (через runStandardRule).
33
+ Library mode — викликається CLI orchestration через `import + run`.
21
34
 
22
- | Експорт | Тип | Призначення |
23
- | ------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
24
- | `run` | `function (ctx?: RuleContext) => Promise<number>` | Library-функція для виклику з зовнішнього CLI; повертає exit-code `0` (OK) або `1` (є порушення). |
35
+ ## Гарантії поведінки
25
36
 
26
- Сторонніх іменованих експортів немає. Default export відсутній.
27
-
28
- ### Side-effect при імпорті
29
-
30
- Файл містить **top-level умовний блок**:
31
-
32
- ```js
33
- if (isRunAsCli(import.meta.url)) {
34
- process.exit(await runRuleCli(import.meta.dirname))
35
- }
36
- ```
37
-
38
- — якщо модуль виконується як точка входу процесу (`process.argv[1]` відповідає `import.meta.url`), він **завершить процес** через `process.exit(...)`. У звичайному library-імпорті (`import { run } from '.../fix.mjs'`) `isRunAsCli` повертає `false`, отже `process.exit` НЕ викликається — імпорт безпечний.
39
-
40
- Зверніть увагу на `await` на top-level — файл потребує підтримки top-level await (ESM, Node ≥ 14.8 / Bun).
41
-
42
- ## Функції
43
-
44
- ### `run(ctx)`
45
-
46
- ```js
47
- /**
48
- *
49
- */
50
- export function run(ctx) {
51
- return runStandardRule(import.meta.dirname, ctx)
52
- }
53
- ```
54
-
55
- - **Сигнатура:** `run(ctx?: RuleContext): Promise<number>`
56
- - **Параметри:**
57
- - `ctx` _(optional)_ — об'єкт `RuleContext`, тип якого імпортується з `../../scripts/lib/run-standard-rule.mjs`. Зазвичай несе спільний стан між кількома правилами: кеш обходу файлів (`walkCache`), accumulator для summary, прапорці dry-run/auto-fix тощо. Якщо `ctx` не передано — `runStandardRule` створить дефолтний контекст всередині.
58
- - **Повертає:** `Promise<number>` — exit-code правила:
59
- - `0` — порушень немає (або всі автоматично виправлені);
60
- - `1` — є невиправні порушення / помилки.
61
- - **Side effects:**
62
- - Делегує всю роботу до `runStandardRule(dir, ctx)`. Це може включати: обхід файлової системи (`walkdir`), читання/запис файлів-учасників правила (auto-fix), вивід у `stdout`/`stderr` через спільний логер, мутацію `ctx.walkCache` тощо.
63
- - **Не** викликає `process.exit` напряму.
64
- - **Помилки:** будь-який reject від `runStandardRule` пробрасується нагору (caller відповідає за `try/catch`).
65
-
66
- ### Анонімний CLI-блок (top-level)
67
-
68
- ```js
69
- if (isRunAsCli(import.meta.url)) {
70
- process.exit(await runRuleCli(import.meta.dirname))
71
- }
72
- ```
73
-
74
- - **Тригер:** виконується **лише** коли файл — точка входу процесу. Використовує `isRunAsCli(import.meta.url)` для надійного визначення (порівняння `import.meta.url` з `process.argv[1]` через `pathToFileURL`).
75
- - **Дія:** делегує до `runRuleCli(import.meta.dirname)` — повноцінного CLI-обгортача, який:
76
- - завантажує конфіг (`.cursor/cursor.json` чи аналог);
77
- - застосовує whitelist/blacklist;
78
- - збирає friendly summary;
79
- - запускає `runStandardRule` всередині.
80
- - **Завершення:** `process.exit(<exit-code>)` — пробрасує код у shell для CI/IDE-інтеграцій.
81
- - **ESLint disables:**
82
- - `n/no-process-exit` та `unicorn/no-process-exit` свідомо вимкнено коментарем — standalone entry-point **зобов'язаний** повертати exit-code для CI/IDE.
83
-
84
- ## Залежності
85
-
86
- ### Внутрішні (relative imports)
87
-
88
- | Модуль | Що використано | Призначення |
89
- | ----------------------------------------- | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
90
- | `../../scripts/lib/run-rule-cli.mjs` | `isRunAsCli`, `runRuleCli` | Хелпери standalone-режиму: детект "запущено як CLI" та повноцінна CLI-обгортка. |
91
- | `../../scripts/lib/run-standard-rule.mjs` | `runStandardRule` | Універсальний раннер, що виконує "стандартну" послідовність етапів правила: `applies → JS-concerns → policy → mdc-refs`. Також експортує тип `RuleContext` (через JSDoc-`import`). |
92
-
93
- Шлях `../../` веде з `npm/rules/changelog/` до `npm/scripts/lib/`.
94
-
95
- ### Сусідні артефакти правила `changelog`
96
-
97
- Не імпортуються напряму з цього файлу, але **використовуються `runStandardRule`** конвенційно (за іменами файлів у тій самій теці):
98
-
99
- - `npm/rules/changelog/meta.json` — метадані правила (id, опис, теги, прапорці `worktree`/`policy`).
100
- - `npm/rules/changelog/changelog.mdc` — людинозрозумілий зміст правила (Cursor MDC).
101
- - `npm/rules/changelog/js/` — JS-concerns (перевірки/фікси для коду).
102
- - `npm/rules/changelog/lib/` — допоміжні модулі правила.
103
-
104
- ### Зовнішні (Node/runtime)
105
-
106
- - `import.meta.url` — стандарт ESM, для детекту CLI-режиму.
107
- - `import.meta.dirname` — потребує Node ≥ 20.11 або Bun (Bun підтримує). Передається в обидва раннери як корінь правила.
108
- - `process.exit` — глобал Node/Bun.
109
- - Top-level `await` — ESM-фіча, потребує сумісного runtime.
110
-
111
- ## Потік виконання / Використання
112
-
113
- ### Library mode (типовий — виклик з оркестратора `fix`)
114
-
115
- ```text
116
- npx @nitra/cursor fix
117
-
118
-
119
- scripts/cli/fix.mjs (orchestrator)
120
- │ для кожного правила з whitelist:
121
-
122
- import { run } from 'npm/rules/<id>/fix.mjs'
123
-
124
-
125
- run(ctx)
126
-
127
-
128
- runStandardRule(import.meta.dirname, ctx)
129
-
130
-
131
- applies → JS-concerns → policy → mdc-refs
132
-
133
-
134
- Promise<0 | 1>
135
- ```
136
-
137
- Приклад програмного виклику:
138
-
139
- ```js
140
- import { run } from '@nitra/cursor/npm/rules/changelog/fix.mjs'
141
-
142
- const code = await run({ walkCache: new Map(), summary: [] })
143
- if (code !== 0) {
144
- // є порушення — обробити в caller
145
- }
146
- ```
147
-
148
- ### Standalone mode (ручний запуск/debug)
149
-
150
- ```bash
151
- bun npm/rules/changelog/fix.mjs
152
- # еквівалент:
153
- npx @nitra/cursor fix changelog
154
- ```
155
-
156
- Послідовність:
157
-
158
- 1. ESM-модуль завантажується як entry-point.
159
- 2. Виконуються імпорти.
160
- 3. Експорт `run` реєструється (але ніхто не викликає).
161
- 4. Виконується умова `isRunAsCli(import.meta.url)` → `true`.
162
- 5. `await runRuleCli(import.meta.dirname)` — завантажує конфіг, фільтрує whitelist (тут — лише поточне правило, бо `dirname` фіксує id), запускає `runStandardRule`, друкує summary.
163
- 6. `process.exit(<code>)` — повертає exit-code у shell.
164
-
165
- ### Чому два режими в одному файлі?
166
-
167
- - **DRY** — не дублювати entry-point на кожне правило.
168
- - **DevEx** — розробник може дебажити одне правило: `bun npm/rules/<id>/fix.mjs`.
169
- - **CI** — оркестратор `fix.mjs` імпортує `run` гуртом, ділить кеш обходу між правилами для прискорення.
170
-
171
- ## Rebuild Test (контекстна незалежність)
172
-
173
- Файл можна відтворити, маючи лише цю документацію:
174
-
175
- 1. Створіть `npm/rules/changelog/fix.mjs`.
176
- 2. Імпортуйте `isRunAsCli` та `runRuleCli` з `../../scripts/lib/run-rule-cli.mjs`.
177
- 3. Імпортуйте `runStandardRule` з `../../scripts/lib/run-standard-rule.mjs`.
178
- 4. Експортуйте функцію `run(ctx)`, яка повертає результат `runStandardRule(import.meta.dirname, ctx)`.
179
- 5. Додайте JSDoc до `run` з типом `RuleContext` (через `import('../../scripts/lib/run-standard-rule.mjs').RuleContext`) і `Promise<number>` на повернення.
180
- 6. Внизу додайте умовний блок `if (isRunAsCli(import.meta.url)) { process.exit(await runRuleCli(import.meta.dirname)) }`.
181
- 7. Поряд із `process.exit(...)` додайте eslint-disable коментар для `n/no-process-exit` та `unicorn/no-process-exit` із обґрунтуванням "standalone entry-point має повертати exit-code для CI/IDE".
182
-
183
- Результат має бути ідентичним за поведінкою оригіналу: library-імпорт безпечний, standalone-запуск завершує процес коректним exit-code.
37
+ - Read-only: файл не виконує операцій запису у файлову систему.
38
+ - Кешує результати в межах одного прогону.
39
+ - Не звертається до мережі.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  docgen:
3
3
  source: npm/rules/ci4/fix.mjs
4
- crc: 12fc1644
4
+ crc: 38cf876b
5
5
  score: 100
6
6
  ---
7
7
 
@@ -9,26 +9,23 @@ docgen:
9
9
 
10
10
  ## Огляд
11
11
 
12
- Модуль виконує бізнес-процес трансформації даних. Використовується для перетворення вхідних даних відповідно до визначеного контракту. Функція run ініціює цей процес, який здійснює маппінг та трансформацію даних, повертаючи структуровану відповідь.
12
+ Огляд
13
+ Файл надає функціонал для виконання визначених правил. Публічні методи забезпечують запуск цих правил, повертаючи отримані результати. Дозволяє запускати правила у режимі командного рядка, керуючи виходом процесу на основі отриманих даних.
13
14
 
14
15
  ## Поведінка
15
16
 
16
- 1. Виклик функції run передає контекст прогону для виконання правила.
17
+ 1. Запуск правила.
18
+ * Виклик runStandardRule з контекстом.
19
+ * Повернення результату.
17
20
 
18
- 2. Виконання правила відбувається через стандартний механізм перевірки.
19
-
20
- 3. У режимі бібліотеки функція викликає основну логіку перевірки, яка включає застосування правил, перевірку конфігурації та формування посилань.
21
-
22
- 4. Якщо виконання відбувається у режимі командного рядка, функція виконує повний еквівалент інструментального прогону.
23
-
24
- 5. У режимі командного рядка функція виконує команду, яка включає завантаження конфігурації, перевірку дозволених елементів та формування зведення.
25
-
26
- 6. У режимі командного рядка результат виконання передається для негайного завершення процесу, що використовується для інструментальних середовищ.
21
+ 2. Запуск правила у режимі CLI.
22
+ * Виклик runRuleCli з директорією модуля.
23
+ * Вихід з процесом залежно від результату.
27
24
 
28
25
  ## Публічний API
29
26
 
30
- run — Запускає послідовність перевірок: applies до mdc-refs через policy та JS-concerns.
31
- Library mode — Викликається через CLI оркестрацію за допомогою імпорту та функції run.
27
+ run — запускає правило: applies JS-concerns policy mdc-refs (через runStandardRule).
28
+ Library mode — викликається CLI orchestration через `import + run`.
32
29
 
33
30
  ## Гарантії поведінки
34
31
 
@@ -0,0 +1,60 @@
1
+ ---
2
+ description: Файлова документація — обовʼязковий крок задачі (як lint); детермінований детектор застарілості за CRC джерела (lint-doc-files), генерація local-only конвеєром (fix-doc-files)
3
+ globs: "**/*.{js,mjs,ts,vue,py,rs},**/docs/**"
4
+ alwaysApply: true
5
+ version: '1.0'
6
+ ---
7
+
8
+ Кожен кодовий файл (`.js .mjs .ts .vue .py .rs`, крім тестів і `.d.ts`) має **актуальну**
9
+ файлову доку поряд: `<dir>/docs/<stem>.md`. Це **обовʼязковий крок кожної задачі**, нарівні
10
+ з lint. Актуальність детермінується за **CRC** джерела, записаним у frontmatter доки.
11
+
12
+ ## Дві відповідальності — дві команди
13
+
14
+ - **`lint-doc-files`** — детермінований **детектор**: знаходить кодові файли без актуальної
15
+ доки. **0 викликів LLM**, працює локально, в hook'ах і в CI.
16
+ - **`fix-doc-files`** — **генератор**: створює/оновлює доки local-only конвеєром (omlx) зі
17
+ штампом CRC. Потребує локальної моделі; у CI **не** запускається.
18
+
19
+ Це та сама пара, що `/n-lint` (детект) проти `/n-fix` (закриття) у решті проєкту.
20
+
21
+ ## Stale = `missing` ∪ `crc-mismatch`
22
+
23
+ Дока застаріла, якщо її **немає** (`missing`) або `crc(джерело) ≠ crc` у frontmatter
24
+ (`crc-mismatch`). **Degraded** (низький score, але CRC свіжий) — **не** stale; це борг, видимий
25
+ через `lint-doc-files --degraded` і закривається `fix-doc-files --retry-degraded`.
26
+
27
+ Алгоритм детекту (кандидати, ignore-дерево, CRC, реверс-мапінг доки→джерело) повністю в
28
+ `js/docgen-scan.mjs` / `js/docgen-crc.mjs` / `js/docgen-ignore.mjs` і адаптері `js/lint.mjs`;
29
+ тут — лише людинозрозумілий контракт, без дублювання логіки.
30
+
31
+ ## Мапа команд
32
+
33
+ | Команда | Exit | Семантика |
34
+ | --- | --- | --- |
35
+ | `lint-doc-files` | 1 — є stale | повний детект, людиночитний список stale |
36
+ | `lint-doc-files [paths…]` | 1 | точковий детект заданих джерел |
37
+ | `lint-doc-files --missing-only` | 1 | лише `missing`, без `crc-mismatch` |
38
+ | `lint-doc-files --json` | 0 | JSON-лістинг усіх кандидатів зі станом |
39
+ | `lint-doc-files --hook` | 2 — stale | PostToolUse: stdin JSON, один файл |
40
+ | `lint-doc-files --git [--max N]` | 2 — stale | Stop-гейт: `git diff` HEAD; понад поріг (`N_CURSOR_DOC_FILES_GATE_MAX`, дефолт 50) — warn + exit 0 |
41
+ | `lint-doc-files --degraded` | 0 | звіт про доки зі score < порогу |
42
+ | `fix-doc-files [--limit/--from/--overwrite/--retry-degraded]` | 0/1 | local-only генерація зі штампом CRC |
43
+ | `fix-doc-files --stamp` | 0/1 | детерміноване перештампування `source`+`crc` без LLM |
44
+
45
+ Exit-коди: повний прогін — **1** (конвенція `lint-*`); `--hook`/`--git` — **2** (hook-протокол
46
+ Claude Code: blocking feedback).
47
+
48
+ ## Hook'и
49
+
50
+ PostToolUse (`lint-doc-files --hook`) **сигналить** про дрейф після правки кодового файлу;
51
+ Stop-гейт (`lint-doc-files --git`) **блокує завершення** задачі за наявності застарілих док
52
+ (виняток — масовий прогін понад поріг). Регенерація — `/doc-files` (JS-оркестрована, не диспатч
53
+ субагентів). Агрегуюча дока (module-summary, доменні) — окремий скіл `/doc-aggregate`.
54
+
55
+ ## Серіалізація
56
+
57
+ Повний `lint-doc-files` і `fix-doc-files` серіалізуються через `runStandardLint` /
58
+ `runStandardRule` (ключі `lint-doc-files` / `fix-doc-files`, виводяться зі шляху каталогу).
59
+ `--hook` / `--git` — окрема форма **без локу** (швидкий точковий вердикт). Деталі —
60
+ `.cursor/rules/scripts.mdc`, секція «Серіалізація важких CLI-команд».
@@ -0,0 +1,31 @@
1
+ ---
2
+ docgen:
3
+ source: npm/rules/doc-files/fix.mjs
4
+ crc: 9df3e88d
5
+ score: 100
6
+ ---
7
+
8
+ # fix.mjs
9
+
10
+ ## Огляд
11
+
12
+ Точка входу правила doc-files для каналу `n-cursor fix doc-files`. Перевіряє структурні вимоги
13
+ правила (наявність потрібного GA-workflow і скрипта в `package.json` через policy-канал), не
14
+ чіпаючи вміст самих док.
15
+
16
+ ## Поведінка
17
+
18
+ Делегує стандартному оркестратору правил: applies → JS-concerns → policy → перевірка
19
+ markdown-посилань. Контентні порушення (відсутні чи застарілі файлові доки) свідомо поза цим
20
+ каналом — їх закриває окрема команда генерації `fix-doc-files`, а не загальний `fix`.
21
+
22
+ ## Публічний API
23
+
24
+ `run` — виконує перевірку правила і повертає код виходу (`0` — без зауважень, `1` — є порушення).
25
+ При прямому запуску файлу працює як повний еквівалент `npx @nitra/cursor fix doc-files` із
26
+ завантаженням конфігу та підсумком.
27
+
28
+ ## Гарантії поведінки
29
+
30
+ - Серіалізується спільним локом, тож паралельні запуски того самого правила дедуплікуються.
31
+ - Не виконує генерацію документації — лише структурну перевірку.
@@ -0,0 +1,19 @@
1
+ import { isRunAsCli, runRuleCli } from '../../scripts/lib/run-rule-cli.mjs'
2
+ import { runStandardRule } from '../../scripts/lib/run-standard-rule.mjs'
3
+
4
+ /**
5
+ * Запускає правило doc-files: applies → JS-concerns → policy → mdc-refs (через runStandardRule).
6
+ * Структурні concerns (наявність workflow lint-doc-files.yml, скрипт у package.json) закриває
7
+ * policy-канал; контентні порушення (відсутні/застарілі доки) — поза `n-cursor fix`, їх закриває
8
+ * `fix-doc-files` (генерація). Library mode: викликається через `import + run(ctx)`.
9
+ * @param {import('../../scripts/lib/run-standard-rule.mjs').RuleContext} [ctx] контекст прогону
10
+ * @returns {Promise<number>} 0 — OK, 1 — порушення
11
+ */
12
+ export function run(ctx) {
13
+ return runStandardRule(import.meta.dirname, ctx)
14
+ }
15
+
16
+ if (isRunAsCli(import.meta.url)) {
17
+ // Standalone: bun rules/doc-files/fix.mjs — еквівалент `npx @nitra/cursor fix doc-files`.
18
+ process.exitCode = await runRuleCli(import.meta.dirname)
19
+ }
@@ -213,26 +213,32 @@ function extractMarkers(src) {
213
213
 
214
214
  // ── Rust-екстрактор ──────────────────────────────────────────────────────────
215
215
 
216
- // Модульний //! doc-коментар (inner doc)
217
- const RS_MODULE_DOC_RE = /^(?:[ \t]*\/\/![ \t]?(.*)\n)*/m
218
-
219
216
  // pub fn / pub struct / pub enum / pub trait (та fn із exposure-атрибутом)
220
- const RS_PUB_ITEM_RE = /^[ \t]*(pub(?:\([^)]*\))?\s+)?(?:async\s+)?(?:unsafe\s+)?(fn|struct|enum|trait|type)\s+(\w+)/gm
217
+ // матчаться у два кроки по trim-нутому рядку — прості регекспи без бектрекінгу:
218
+ // спершу опційний pub(...)-префікс, потім сама декларація
219
+ const RS_PUB_PREFIX_RE = /^pub(?:\([^)]*\))?\s+/
220
+ const RS_ITEM_DECL_RE = /^(?:async\s+)?(?:unsafe\s+)?(fn|struct|enum|trait|type)\s+(\w+)/
221
+
222
+ // fn-декларація у trim-нутому рядку одразу після exposure-атрибута
223
+ const RS_FN_AFTER_ATTR_RE = /^(?:pub\s+)?(?:async\s+)?(?:unsafe\s+)?fn\s+/
224
+
225
+ // Будь-яка fn-декларація без pub (для приватних localSymbols)
226
+ const RS_PRIVATE_FN_RE = /^[ \t]*(?:async\s+)?(?:unsafe\s+)?fn\s+(\w+)/
221
227
 
222
228
  // Exposure-атрибути (#[tauri::command] тощо)
223
229
  const RS_EXPOSURE_ATTR_RE = /#\[(?:tauri::command|wasm_bindgen|uniffi::export|pyo3::pyfunction|napi)/gm
224
230
 
225
- // /// line-doc перед елементом
226
- const RS_LINE_DOC_RE = /(?:[ \t]*\/\/\/[ \t]?.*\n)*/
227
-
228
231
  // use crate::module::{A, B} або use std::..;
229
232
  const RS_USE_RE = /^[ \t]*use\s+([\w:]+(?:::\{[^}]+\})?(?:::\*)?(?:::\w+)?)\s*;/gm
230
233
 
231
234
  // Файловий запис: fs::write / File::create / remove_file / create_dir / write_all
232
235
  const RS_WRITE_RE = /fs::write|File::create|remove_file|create_dir|BufWriter::new|OpenOptions[^;]*\.write\s*\(\s*true/
233
236
 
234
- // Обробка помилок (але не просто `?`)
235
- const RS_CATCH_RE = /\.unwrap_or(?:_else|_default)?|if\s+let\s+Err\s*\(|match\s+\S+.*\{\s*[\s\S]*?Err\s*\(|\.map_err\s*\(|\.ok\s*\(\)/
237
+ // Обробка помилок (але не просто `?`): прості маркери; випадок
238
+ // «match з Err(-гілкою» — віконним обходом рядків у rsHasMatchWithErrArm
239
+ const RS_CATCH_SIMPLE_RES = [/\.unwrap_or(?:_else|_default)?/, /if\s+let\s+Err\s*\(/, /\.map_err\s*\(/, /\.ok\s*\(\)/]
240
+ const RS_MATCH_KW_RE = /\bmatch\s/
241
+ const RS_ERR_ARM_RE = /\bErr\s*\(/
236
242
 
237
243
  // Функції, що повертають Result або Option
238
244
  const RS_RESULT_RE = /->\s*(?:Result|Option)\s*</
@@ -243,6 +249,21 @@ const RS_NETWORK_RE = /reqwest|hyper::|TcpStream|UdpSocket|tokio::net/
243
249
  // Кешування
244
250
  const RS_CACHE_RE = /\bcache\b|\bCache\b|lazy_static!|OnceCell|OnceLock|DashMap/i
245
251
 
252
+ /**
253
+ * Чи містить джерело `match`-вираз із `Err(`-гілкою неподалік (вікно 12 рядків).
254
+ * Замінює бектрекінг-вразливу регулярку детермінованим обходом рядків.
255
+ * @param {string[]} srcLines рядки файлу
256
+ * @returns {boolean} true, якщо за `match` слідує `Err(`
257
+ */
258
+ function rsHasMatchWithErrArm(srcLines) {
259
+ for (let i = 0; i < srcLines.length; i++) {
260
+ if (!RS_MATCH_KW_RE.test(srcLines[i])) continue
261
+ const end = Math.min(i + 12, srcLines.length)
262
+ for (let j = i; j < end; j++) if (RS_ERR_ARM_RE.test(srcLines[j])) return true
263
+ }
264
+ return false
265
+ }
266
+
246
267
  /**
247
268
  * Видобуває `///` doc-рядки перед рядком `lineIdx` (назад через `#[...]` та пусті рядки).
248
269
  * @param {string[]} lines рядки файлу
@@ -254,8 +275,9 @@ function rsDocBefore(lines, lineIdx) {
254
275
  for (let i = lineIdx - 1; i >= 0; i--) {
255
276
  const t = lines[i].trim()
256
277
  if (t.startsWith('///')) doc.unshift(t.slice(3).trim())
257
- else if (t.startsWith('#[') || t.startsWith('#![') || t === '') { /* skip */ }
258
- else break
278
+ else if (t.startsWith('#[') || t.startsWith('#![') || t === '') {
279
+ /* skip */
280
+ } else break
259
281
  }
260
282
  return doc.join(' ').trim()
261
283
  }
@@ -289,7 +311,7 @@ function extractFactsRust(src, relPath) {
289
311
  for (let nli = li + 1; nli < Math.min(li + 5, srcLines.length); nli++) {
290
312
  const t = srcLines[nli].trim()
291
313
  if (t.startsWith('#[') || t === '') continue
292
- if (/^(?:pub\s+)?(?:async\s+)?(?:unsafe\s+)?fn\s+/.test(t)) exposedLineSet.add(nli)
314
+ if (RS_FN_AFTER_ATTR_RE.test(t)) exposedLineSet.add(nli)
293
315
  break
294
316
  }
295
317
  break
@@ -303,12 +325,14 @@ function extractFactsRust(src, relPath) {
303
325
  let lineOffset = 0
304
326
  for (let li = 0; li < srcLines.length; li++) {
305
327
  const line = srcLines[li]
306
- const m = line.match(/^[ \t]*(pub(?:\([^)]*\))?\s+)?(?:async\s+)?(?:unsafe\s+)?(fn|struct|enum|trait|type)\s+(\w+)/)
328
+ const trimmed = line.trimStart()
329
+ const pubM = trimmed.match(RS_PUB_PREFIX_RE)
330
+ const m = (pubM ? trimmed.slice(pubM[0].length) : trimmed).match(RS_ITEM_DECL_RE)
307
331
  if (m) {
308
- const isPub = Boolean(m[1]) || exposedLineSet.has(li)
332
+ const isPub = Boolean(pubM) || exposedLineSet.has(li)
309
333
  if (isPub) {
310
334
  const desc = rsDocBefore(srcLines, li)
311
- exports.push({ name: m[3], kind: m[2], desc })
335
+ exports.push({ name: m[2], kind: m[1], desc })
312
336
  }
313
337
  }
314
338
  lineOffset += line.length + 1
@@ -316,9 +340,8 @@ function extractFactsRust(src, relPath) {
316
340
 
317
341
  // localSymbols — приватні fn (не pub і не exposed) — не документуємо як публічний API
318
342
  const localSymbols = []
319
- for (let li = 0; li < srcLines.length; li++) {
320
- const line = srcLines[li]
321
- const m = line.match(/^[ \t]*(?:async\s+)?(?:unsafe\s+)?fn\s+(\w+)/)
343
+ for (const line of srcLines) {
344
+ const m = line.match(RS_PRIVATE_FN_RE)
322
345
  if (m && !exports.some(e => e.name === m[1])) localSymbols.push(m[1])
323
346
  }
324
347
 
@@ -336,7 +359,7 @@ function extractFactsRust(src, relPath) {
336
359
  // markers
337
360
  const markers = {
338
361
  readOnly: !RS_WRITE_RE.test(src),
339
- catchesErrors: RS_CATCH_RE.test(src),
362
+ catchesErrors: RS_CATCH_SIMPLE_RES.some(re => re.test(src)) || rsHasMatchWithErrArm(srcLines),
340
363
  returnsFalsyOnFail: RS_RESULT_RE.test(src),
341
364
  network: RS_NETWORK_RE.test(src),
342
365
  caches: RS_CACHE_RE.test(src),
@@ -90,6 +90,19 @@ function modeSuffix({ overwrite, retryDegraded }) {
90
90
  return ''
91
91
  }
92
92
 
93
+ /**
94
+ * Рядок таймінгу одного файлу: загальний час, час у LLM (і кількість викликів)
95
+ * та залишок — оркестрація (екстракт фактів, скоринг, парсинг, IO). Дає зрозуміти,
96
+ * скільки коштує сама модель проти JS-оркестрації.
97
+ * @param {{ ms: number, llmMs?: number, llmCalls?: number }} r результат generateDoc
98
+ * @returns {string} напр. `12.3s (llm 11.8s/7 calls, orch 0.5s)`
99
+ */
100
+ function fmtTiming(r) {
101
+ const s = ms => `${(ms / 1000).toFixed(1)}s`
102
+ const llmMs = r.llmMs ?? 0
103
+ return `${s(r.ms)} (llm ${s(llmMs)}/${r.llmCalls ?? 0} calls, orch ${s(r.ms - llmMs)})`
104
+ }
105
+
93
106
  /**
94
107
  * Генерує й штампує доку для одного файлу, оновлюючи лічильники й прогрес.
95
108
  * @param {object} file елемент scanForDocFiles
@@ -114,9 +127,9 @@ async function generateOne(file, root, progress, stats) {
114
127
  stats.ok++
115
128
  if (result.degraded) {
116
129
  stats.degraded++
117
- process.stdout.write(`⚠ degraded score=${result.score} crc=${crc}\n`)
130
+ process.stdout.write(`⚠ degraded score=${result.score} crc=${crc} ${fmtTiming(result)}\n`)
118
131
  } else {
119
- process.stdout.write(`✓ score=${result.score ?? '—'} crc=${crc}\n`)
132
+ process.stdout.write(`✓ score=${result.score ?? '—'} crc=${crc} ${fmtTiming(result)}\n`)
120
133
  }
121
134
  } catch (error) {
122
135
  stats.err++
@@ -137,7 +150,7 @@ function reportStats(stats) {
137
150
  for (const e of stats.errors) console.log(` - ${e}`)
138
151
  }
139
152
  if (stats.degraded > 0) {
140
- console.log(`Degraded-доки перегенеровуються пізніше: npx @nitra/cursor doc-files gen --retry-degraded`)
153
+ console.log(`Degraded-доки перегенеровуються пізніше: npx @nitra/cursor fix-doc-files --retry-degraded`)
141
154
  }
142
155
  }
143
156
 
@@ -164,7 +177,7 @@ export async function runDocFilesGenCli(argv) {
164
177
 
165
178
  const problem = preflightProblem()
166
179
  if (problem) {
167
- console.error(`✗ doc-files gen: ${problem}`)
180
+ console.error(`✗ fix-doc-files: ${problem}`)
168
181
  return 1
169
182
  }
170
183
 
@@ -201,7 +214,7 @@ export function runDocFilesStampCli(argv) {
201
214
  writeFileSync(docAbs, stampDoc(md, file.sourcePath, crc, score === null ? null : { score, issues }))
202
215
  stamped++
203
216
  }
204
- console.log(`✓ doc-files stamp: оновлено frontmatter у ${stamped} доці(ах).`)
217
+ console.log(`✓ fix-doc-files --stamp: оновлено frontmatter у ${stamped} доці(ах).`)
205
218
  return 0
206
219
  }
207
220
 
@@ -4,7 +4,7 @@ import { basename } from 'node:path'
4
4
  import { env } from 'node:process'
5
5
  import { resolveModel } from '../../../lib/models.mjs'
6
6
  import { DEFAULT_OMLX_MODEL } from '../../../lib/omlx.mjs'
7
- import { callLlm } from '../../../lib/llm.mjs'
7
+ import { callLlm as callLlmRaw } from '../../../lib/llm.mjs'
8
8
  import { isRunAsCli } from '../../../scripts/cli-entry.mjs'
9
9
  import { docPathForSource } from './docgen-scan.mjs'
10
10
  import { extractFacts } from './docgen-extract.mjs'
@@ -19,6 +19,26 @@ import {
19
19
  guaranteesFromMarkers
20
20
  } from './docgen-prompts.mjs'
21
21
 
22
+ /** Облік LLM-викликів і часу в них у межах однієї генерації (скидається на старті generateDoc). */
23
+ let llmMeter = { calls: 0, ms: 0 }
24
+
25
+ /**
26
+ * Обгортка callLlm з обліком: лічить кількість викликів і сумарний час у них.
27
+ * callLlm синхронний (spawnSync/curl), генерація одного файлу послідовна — лічильник без гонок.
28
+ * Усі виклики `callLlm(...)` у цьому модулі йдуть через неї автоматично (імпорт як callLlmRaw).
29
+ * @param {...any} args ті самі аргументи, що й у callLlm з lib/llm.mjs
30
+ * @returns {string} відповідь моделі
31
+ */
32
+ function callLlm(...args) {
33
+ const started = Date.now()
34
+ try {
35
+ return callLlmRaw(...args)
36
+ } finally {
37
+ llmMeter.calls += 1
38
+ llmMeter.ms += Date.now() - started
39
+ }
40
+ }
41
+
22
42
  const FENCE_OPEN_RE = /^```[a-z]*\n?/
23
43
  const FENCE_CLOSE_RE = /\n?```\s*$/
24
44
  const LEADING_HEADING_RE = /^#{1,6}[ \t]{1,8}[^\n]{0,400}\n{1,8}/
@@ -360,12 +380,13 @@ export const DEFAULT_LOCAL_MODEL = env.N_CURSOR_DOCGEN_MODEL ?? (resolveModel('m
360
380
  * позначається `degraded`, рішення про перегенерацію приймає batch/користувач.
361
381
  * @param {string} file абсолютний шлях джерела
362
382
  * @param {{ model?: string, threshold?: number, existingMd?: string|null }} [opts] model-id, поріг degraded, наявна дока (для збереження захищеної секції)
363
- * @returns {{ md: string, ms: number, score: number|null, issues: string[], degraded: boolean, model: string }} документ і метадані генерації
383
+ * @returns {{ md: string, ms: number, llmMs: number, llmCalls: number, score: number|null, issues: string[], degraded: boolean, model: string }} документ і метадані генерації (ms — увесь файл; llmMs/llmCalls — лише LLM; решта ms — оркестрація)
364
384
  */
365
385
  export function generateDoc(file, { model = DEFAULT_LOCAL_MODEL, threshold = QUALITY_THRESHOLD, existingMd = null } = {}) {
366
386
  const src = readFileSync(file, 'utf8')
367
387
  const facts = extractFacts(src, file)
368
388
  const t0 = Date.now()
389
+ llmMeter = { calls: 0, ms: 0 }
369
390
 
370
391
  // Варіант B: захищена секція «Призначення» з наявної доки — зберегти й подати як контекст
371
392
  const intent = existingMd ? splitProtected(existingMd).body : null
@@ -376,7 +397,16 @@ export function generateDoc(file, { model = DEFAULT_LOCAL_MODEL, threshold = QUA
376
397
 
377
398
  // unsupported (vue/py до юніт-шару): скорер не застосовний — score=null, не degraded
378
399
  if (facts.unsupported) {
379
- return { ...r, ms: Date.now() - t0, score: null, issues: [], degraded: false, model }
400
+ return {
401
+ ...r,
402
+ ms: Date.now() - t0,
403
+ llmMs: llmMeter.ms,
404
+ llmCalls: llmMeter.calls,
405
+ score: null,
406
+ issues: [],
407
+ degraded: false,
408
+ model
409
+ }
380
410
  }
381
411
 
382
412
  // Stage 2.5: детермінований скоринг (0 токенів)
@@ -399,7 +429,16 @@ export function generateDoc(file, { model = DEFAULT_LOCAL_MODEL, threshold = QUA
399
429
  }
400
430
  }
401
431
 
402
- return { ...r, ms: Date.now() - t0, score, issues, degraded: score < threshold, model }
432
+ return {
433
+ ...r,
434
+ ms: Date.now() - t0,
435
+ llmMs: llmMeter.ms,
436
+ llmCalls: llmMeter.calls,
437
+ score,
438
+ issues,
439
+ degraded: score < threshold,
440
+ model
441
+ }
403
442
  }
404
443
 
405
444
  // CLI: node docgen-gen.mjs <file> [--model <m>]
@@ -416,6 +455,8 @@ if (isRunAsCli(import.meta.url)) {
416
455
  const existingMd = existsSync(docPath) ? readFileSync(docPath, 'utf8') : null
417
456
  const r = generateDoc(file, { model, existingMd })
418
457
  const issuesTxt = r.issues?.length ? ` issues=${r.issues.join(',')}` : ''
419
- process.stderr.write(`[local ${r.model}] ${r.ms}ms / score=${r.score}${r.degraded ? ' DEGRADED' : ''}${issuesTxt}\n`)
458
+ process.stderr.write(
459
+ `[local ${r.model}] ${r.ms}ms (llm ${r.llmMs}ms/${r.llmCalls} calls, orch ${r.ms - r.llmMs}ms) / score=${r.score}${r.degraded ? ' DEGRADED' : ''}${issuesTxt}\n`
460
+ )
420
461
  process.stdout.write(r.md)
421
462
  }