@nitra/cursor 5.0.3 → 5.2.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 (206) hide show
  1. package/.claude-template/settings.template.json +22 -0
  2. package/.pi-template/extensions/n-cursor-adr/docs/index.md +15 -9
  3. package/CHANGELOG.md +18 -1
  4. package/bin/n-cursor.js +73 -16
  5. package/docs/stryker.config.md +6 -0
  6. package/docs/vitest.config.md +6 -0
  7. package/lib/docs/llm.md +29 -0
  8. package/lib/docs/omlx.md +32 -0
  9. package/lib/llm.mjs +137 -0
  10. package/lib/models.mjs +9 -1
  11. package/lib/omlx.mjs +147 -0
  12. package/package.json +1 -1
  13. package/rules/abie/docs/fix.md +6 -0
  14. package/rules/abie/js/docs/applies.md +6 -0
  15. package/rules/abie/js/docs/env_dns.md +25 -22
  16. package/rules/abie/js/docs/firebase_hosting.md +6 -0
  17. package/rules/abie/js/docs/hc_pairing.md +21 -25
  18. package/rules/abie/js/docs/ua_http_route.md +27 -19
  19. package/rules/abie/js/docs/ua_node_selector.md +24 -19
  20. package/rules/abie/lib/docs/enabled.md +13 -7
  21. package/rules/abie/lib/docs/env-dns.md +9 -3
  22. package/rules/abie/lib/docs/hc-yaml.md +6 -0
  23. package/rules/abie/lib/docs/http-route.md +6 -0
  24. package/rules/abie/lib/docs/k8s-tree.md +6 -0
  25. package/rules/abie/lib/docs/kustomization-patches.md +6 -0
  26. package/rules/abie/lib/docs/overlay-paths.md +6 -0
  27. package/rules/abie/lib/docs/yaml.md +6 -0
  28. package/rules/adr/docs/fix.md +6 -0
  29. package/rules/adr/js/docs/hooks.md +29 -244
  30. package/rules/bun/docs/fix.md +6 -0
  31. package/rules/bun/js/docs/layout.md +37 -375
  32. package/rules/capacitor/docs/fix.md +22 -108
  33. package/rules/capacitor/js/docs/platforms.md +62 -268
  34. package/rules/changelog/docs/fix.md +6 -0
  35. package/rules/changelog/lib/docs/package-manifest.md +6 -0
  36. package/rules/ci4/docs/fix.md +23 -165
  37. package/rules/ci4/js/docs/marksman_config.md +9 -1
  38. package/rules/docker/docs/fix.md +6 -0
  39. package/rules/docker/js/docs/lint.md +55 -239
  40. package/rules/docker/lib/docs/docker-hadolint.md +6 -0
  41. package/rules/docker/lib/docs/docker-mirror.md +6 -0
  42. package/rules/docker/lib/docs/docker-native-addon.md +6 -0
  43. package/rules/docker/lib/docs/docker-nginx-user.md +6 -0
  44. package/rules/docker/lint/docs/lint.md +9 -1
  45. package/rules/efes/docs/fix.md +6 -0
  46. package/rules/ga/lint/docs/lint.md +6 -0
  47. package/rules/graphql/docs/fix.md +6 -0
  48. package/rules/graphql/lib/docs/graphql-gql-scan.md +6 -0
  49. package/rules/image-avif/docs/fix.md +6 -0
  50. package/rules/image-avif/js/docs/avif_generation.md +6 -0
  51. package/rules/js-bun-db/lib/docs/bun-sql-scan.md +9 -3
  52. package/rules/js-bun-redis/lib/docs/redis-imports.md +6 -0
  53. package/rules/js-lint/js/docs/utils_imports.md +6 -0
  54. package/rules/js-lint-ci/docs/fix.md +7 -1
  55. package/rules/js-mssql/docs/fix.md +6 -0
  56. package/rules/js-mssql/lib/docs/mssql-pool-scan.md +6 -0
  57. package/rules/js-run/docs/fix.md +6 -0
  58. package/rules/js-run/lib/docs/bunyan-imports.md +6 -0
  59. package/rules/js-run/lib/docs/check-env-scan.md +6 -0
  60. package/rules/js-run/lib/docs/conn-file-rules.md +6 -0
  61. package/rules/js-run/lib/docs/conn-imports-scan.md +6 -0
  62. package/rules/js-run/lib/docs/promise-settimeout-scan.md +6 -0
  63. package/rules/js-run/lib/docs/temporal-scan.md +6 -0
  64. package/rules/k8s/docs/fix.md +6 -0
  65. package/rules/k8s/lint/docs/lint.md +6 -0
  66. package/rules/nginx-default-tpl/docs/fix.md +6 -0
  67. package/rules/npm-module/js/docs/header_doc_pointer.md +7 -0
  68. package/rules/npm-module/js/header_doc_pointer.mjs +2 -8
  69. package/rules/php/docs/fix.md +6 -0
  70. package/rules/php/lint/docs/lint.md +6 -0
  71. package/rules/python/docs/fix.md +6 -0
  72. package/rules/python/lint/docs/lint.md +6 -0
  73. package/rules/rego/lint/docs/lint.md +6 -0
  74. package/rules/release/docs/change.md +6 -0
  75. package/rules/release/docs/fix.md +6 -0
  76. package/rules/release/docs/release.md +6 -0
  77. package/rules/release/lib/docs/aggregate.md +6 -0
  78. package/rules/release/lib/docs/change-file.md +6 -0
  79. package/rules/release/lib/docs/fallback.md +6 -0
  80. package/rules/rust/lib/docs/has-cargo-toml.md +6 -0
  81. package/rules/security/docs/fix.md +7 -1
  82. package/rules/security/js/docs/lint.md +6 -0
  83. package/rules/style-lint/docs/fix.md +6 -0
  84. package/rules/tauri/docs/fix.md +6 -0
  85. package/rules/test/docs/fix.md +6 -0
  86. package/rules/test/js/data/stryker_config/docs/stryker-vue-macros-ignorer.md +6 -0
  87. package/rules/test/js/data/stryker_config/docs/stryker.config.baseline.md +6 -0
  88. package/rules/test/js/data/stryker_config/docs/stryker.config.vue.baseline.md +6 -0
  89. package/rules/test/js/data/vitest_config/docs/vitest.config.baseline.md +6 -0
  90. package/rules/text/docs/fix.md +6 -0
  91. package/rules/text/lint/docs/lint.md +6 -0
  92. package/rules/text/lint/docs/run-dotenv-linter.md +6 -0
  93. package/rules/text/lint/docs/run-shellcheck.md +6 -0
  94. package/rules/text/lint/docs/run-v8r.md +6 -0
  95. package/rules/vue/lib/docs/vue-forbidden-imports.md +6 -0
  96. package/scripts/coverage-classify/cache.mjs +1 -1
  97. package/scripts/coverage-classify/docs/apply.md +6 -0
  98. package/scripts/coverage-classify/docs/cache.md +6 -0
  99. package/scripts/coverage-classify/docs/prompt.md +6 -0
  100. package/scripts/coverage-classify/docs/verdict-schema.md +6 -0
  101. package/scripts/coverage-classify/index.mjs +24 -15
  102. package/scripts/coverage-classify/prompt.mjs +1 -1
  103. package/scripts/coverage-fix-extract.mjs +1 -1
  104. package/scripts/coverage-fix.mjs +2 -1
  105. package/scripts/docs/auto-skills.md +6 -0
  106. package/scripts/docs/build-agents-commands.md +7 -1
  107. package/scripts/docs/cli-entry.md +6 -0
  108. package/scripts/docs/coverage-fix-extract.md +6 -0
  109. package/scripts/docs/coverage-fix.md +6 -0
  110. package/scripts/docs/ensure-nitra-cursor-dev-dependencies.md +6 -0
  111. package/scripts/docs/lint-cli.md +6 -0
  112. package/scripts/docs/post-tool-use-fix.md +6 -0
  113. package/scripts/docs/rename-yaml-extensions.md +6 -0
  114. package/scripts/docs/skills-cli.md +6 -0
  115. package/scripts/docs/sync-setup-bun-deps-action.md +6 -0
  116. package/scripts/docs/upgrade-nitra-cursor-and-install.md +6 -0
  117. package/scripts/docs/worktree-cli.md +6 -0
  118. package/scripts/lib/docs/assert-project-root.md +6 -0
  119. package/scripts/lib/docs/check-mdc-template-refs.md +6 -0
  120. package/scripts/lib/docs/check-reporter.md +6 -0
  121. package/scripts/lib/docs/diff-added-lines.md +6 -0
  122. package/scripts/lib/docs/discover-check-rules-from-cursor.md +6 -0
  123. package/scripts/lib/docs/discover-checkable-rules.md +6 -0
  124. package/scripts/lib/docs/ensure-tool.md +6 -0
  125. package/scripts/lib/docs/generated-markdown.md +6 -0
  126. package/scripts/lib/docs/gha-workflow.md +6 -0
  127. package/scripts/lib/docs/inline-template-links.md +6 -0
  128. package/scripts/lib/docs/list-rule-ids.md +6 -0
  129. package/scripts/lib/docs/load-cursor-config.md +6 -0
  130. package/scripts/lib/docs/mirror-parity.md +6 -0
  131. package/scripts/lib/docs/read-n-cursor-config-lite.md +6 -0
  132. package/scripts/lib/docs/resolve-target-files.md +6 -0
  133. package/scripts/lib/docs/root-notice.md +6 -0
  134. package/scripts/lib/docs/rule-meta-helpers.md +6 -0
  135. package/scripts/lib/docs/rule-meta.md +6 -0
  136. package/scripts/lib/docs/run-conftest-batch.md +6 -0
  137. package/scripts/lib/docs/run-lint-step.md +6 -0
  138. package/scripts/lib/docs/run-rule-cli.md +6 -0
  139. package/scripts/lib/docs/run-rule.md +6 -0
  140. package/scripts/lib/docs/run-standard-lint.md +6 -0
  141. package/scripts/lib/docs/run-standard-rule.md +6 -0
  142. package/scripts/lib/docs/skill-meta.md +6 -0
  143. package/scripts/lib/docs/template.md +6 -0
  144. package/scripts/lib/docs/timing-summary.md +6 -0
  145. package/scripts/lib/docs/workspaces.md +6 -0
  146. package/scripts/lib/docs/worktree-notice.md +6 -0
  147. package/scripts/lib/docs/worktree.md +6 -0
  148. package/scripts/lib/mirror-parity.mjs +1 -1
  149. package/scripts/lib/root-notice.mjs +1 -1
  150. package/scripts/lib/worktree-notice.mjs +5 -5
  151. package/scripts/lib/worktree.mjs +1 -1
  152. package/scripts/sync-claude-config.mjs +3 -0
  153. package/scripts/utils/docs/ast-scan-utils.md +6 -0
  154. package/scripts/utils/docs/ensure-gitignore-entries.md +6 -0
  155. package/scripts/utils/docs/find-package-json-paths.md +6 -0
  156. package/scripts/utils/docs/lock-cache-dir.md +6 -0
  157. package/scripts/utils/docs/pass.md +6 -0
  158. package/scripts/utils/docs/resolve-cargo-manifest.md +6 -0
  159. package/scripts/utils/docs/resolve-cmd.md +6 -0
  160. package/scripts/utils/docs/resolve-js-root.md +6 -0
  161. package/scripts/utils/docs/test-helpers.md +6 -0
  162. package/scripts/utils/docs/walk-cache.md +6 -0
  163. package/scripts/utils/docs/walkDir.md +6 -0
  164. package/scripts/utils/docs/worktree-fingerprint.md +6 -0
  165. package/scripts/utils/resolve-js-root.mjs +1 -1
  166. package/skills/doc-aggregate/SKILL.md +129 -0
  167. package/skills/doc-aggregate/js/docgen-ignore.mjs +9 -0
  168. package/skills/{docgen → doc-aggregate}/js/docgen-scan.mjs +22 -67
  169. package/skills/doc-aggregate/js/docs/docgen-ignore.md +21 -0
  170. package/skills/doc-files/SKILL.md +100 -0
  171. package/skills/doc-files/js/docgen-crc.mjs +164 -0
  172. package/skills/{docgen → doc-files}/js/docgen-extract-anchors.mjs +24 -15
  173. package/skills/{docgen → doc-files}/js/docgen-extract.mjs +15 -9
  174. package/skills/doc-files/js/docgen-files-batch.mjs +181 -0
  175. package/skills/doc-files/js/docgen-gen.mjs +291 -0
  176. package/skills/{docgen → doc-files}/js/docgen-prompts.mjs +43 -40
  177. package/skills/doc-files/js/docgen-scan.mjs +298 -0
  178. package/skills/doc-files/js/docs/docgen-crc.md +32 -0
  179. package/skills/doc-files/js/docs/docgen-extract-anchors.md +27 -0
  180. package/skills/doc-files/js/docs/docgen-extract.md +29 -0
  181. package/skills/doc-files/js/docs/docgen-files-batch.md +25 -0
  182. package/skills/doc-files/js/docs/docgen-gen.md +30 -0
  183. package/skills/doc-files/js/docs/docgen-prompts.md +32 -0
  184. package/skills/doc-files/js/docs/docgen-scan.md +25 -0
  185. package/skills/doc-files/meta.json +1 -0
  186. package/skills/fix/js/docs/llm-worker.md +6 -0
  187. package/skills/fix/js/docs/orchestrator.md +6 -0
  188. package/skills/fix/js/llm-worker.mjs +23 -14
  189. package/skills/fix/js/orchestrator.mjs +1 -1
  190. package/skills/start-check/js/check.mjs +5 -3
  191. package/skills/start-check/js/docs/check.md +6 -0
  192. package/skills/docgen/SKILL.md +0 -224
  193. package/skills/docgen/bench/etalon/firebase_hosting.md +0 -19
  194. package/skills/docgen/bench/etalon/k8s-tree.md +0 -24
  195. package/skills/docgen/bench/etalon/overlay-paths.md +0 -24
  196. package/skills/docgen/js/docgen-batch-omlx.mjs +0 -82
  197. package/skills/docgen/js/docgen-batch.mjs +0 -95
  198. package/skills/docgen/js/docgen-compare-pi-vs-direct.mjs +0 -95
  199. package/skills/docgen/js/docgen-gen.mjs +0 -339
  200. package/skills/docgen/js/docs/docgen-extract.md +0 -28
  201. package/skills/docgen/js/docs/docgen-gen.md +0 -41
  202. package/skills/docgen/js/docs/docgen-ignore.md +0 -24
  203. package/skills/docgen/js/docs/docgen-prompts.md +0 -24
  204. package/skills/docgen/js/docs/docgen-scan.md +0 -48
  205. /package/skills/{docgen → doc-aggregate}/meta.json +0 -0
  206. /package/skills/{docgen → doc-files}/js/docgen-ignore.mjs +0 -0
@@ -0,0 +1,129 @@
1
+ ---
2
+ name: doc-aggregate
3
+ description: >-
4
+ Агрегуюча документація за запитом: module-summary на кожен логічний модуль (docs/ARCHITECTURE.md) і доменні доки бізнес-процесів у кореневій docs/ — синтез поверх готових файлових док (doc-files), батч-диспатч субагентів у worktree
5
+ ---
6
+
7
+ # doc-aggregate — агрегуюча документація (за запитом)
8
+
9
+ ## Мета
10
+
11
+ Синтезувати документацію вищого рівня поверх **готових файлових док** (їх підтримує
12
+ обовʼязковий скіл `doc-files`). Два рівні, строго послідовно:
13
+
14
+ 1. **Tier 2 — module-summary**: `<module_root>/docs/ARCHITECTURE.md`, субагент на модуль.
15
+ 2. **Tier 3 — доменні доки**: `docs/<домен>.md` у кореневій `docs/`, субагент-синтезатор
16
+ виділяє бізнес-домени й пише файл на кожен домен.
17
+
18
+ Агрегат ніколи не випереджає джерело: Tier 3 — лише після завершення всього Tier 2.
19
+ Цей скіл викликається **за запитом** (не обовʼязковий крок задачі).
20
+
21
+ ## ⚠️ Паралелізм
22
+
23
+ Tier 2 — батчами **по 5** субагентів одночасно (кожен пише свій файл, гонок немає).
24
+ Tier 3 — **один** субагент-синтезатор після завершення всього Tier 2.
25
+
26
+ ## Передумова
27
+
28
+ - Доступний `npx @nitra/cursor`.
29
+ - Файлові доки мають бути свіжі. Перевір і за потреби онови перед агрегацією:
30
+
31
+ ```bash
32
+ npx @nitra/cursor doc-files check --git
33
+ ```
34
+
35
+ Якщо багато застарілих — спершу прожени `npx @nitra/cursor doc-files gen`.
36
+
37
+ ## Крок 1: Tier 2 — module-summary
38
+
39
+ Зібрати список модулів:
40
+
41
+ ```bash
42
+ npx @nitra/cursor doc-aggregate modules
43
+ ```
44
+
45
+ Команда друкує JSON-масив (усі шляхи абсолютні):
46
+
47
+ ```json
48
+ [
49
+ {
50
+ "moduleRoot": "/abs/npm/rules/adr",
51
+ "relRoot": "npm/rules/adr",
52
+ "slug": "npm-rules-adr",
53
+ "docPath": "/abs/npm/rules/adr/docs/ARCHITECTURE.md",
54
+ "members": ["npm/rules/adr/index.mjs"],
55
+ "exists": false
56
+ }
57
+ ]
58
+ ```
59
+
60
+ module-summary **завжди регенерується** (це агрегат — поле `exists` ігноруй). Розбий модулі
61
+ на батчі по 5 і диспатч субагентів. Промпт кожного (підстав `relRoot`, `docPath`, `members`):
62
+
63
+ ```
64
+ Напиши module-summary для одного логічного модуля.
65
+
66
+ МОДУЛЬ: <relRoot>
67
+ ЗАПИСАТИ В: <docPath>
68
+ ФАЙЛИ МОДУЛЯ (members): <members>
69
+
70
+ Кроки:
71
+ 1. Прочитай файлові доки членів модуля. <member> — sourcePath відносно кореня проєкту
72
+ (= поточний CWD); його файлова дока — <CWD>/<dir>/docs/<stem>.md. За потреби зазирни
73
+ в самі файли.
74
+ 2. Створи теку для <docPath>, якщо її немає.
75
+ 3. Запиши markdown у <docPath> за тими ж правилами стилю, що й файлова дока
76
+ (українська, чистий Markdown, контекстна незалежність, без HTML).
77
+
78
+ Секції module-summary:
79
+ ## Огляд модуля — призначення модуля <relRoot>, його роль у проєкті.
80
+ ## Ключові файли — список із кліковими посиланнями (відносними до розташування цього
81
+ ARCHITECTURE.md) на члени модуля та їхні файлові доки.
82
+ ## Публічний API — що модуль експортує назовні.
83
+ ## Внутрішній потік — як компоненти модуля взаємодіють.
84
+ ## Підмодулі — вкладені модулі, якщо є.
85
+
86
+ Поверни лише підтвердження, що файл <docPath> записано.
87
+ ```
88
+
89
+ ## Крок 2: Tier 3 — доменні доки
90
+
91
+ Після завершення **всіх** module-summary диспатч **одного** субагента-синтезатора.
92
+ У промпт підстав конкретний перелік шляхів module-summary (`<module_root>/docs/ARCHITECTURE.md`
93
+ кожного модуля з виводу `doc-aggregate modules`), а не інструкцію їх шукати. Промпт:
94
+
95
+ ```
96
+ Синтезуй доменну документацію бізнес-процесів проєкту.
97
+
98
+ ДЖЕРЕЛА (module-summary, читай усі): <перелік шляхів ARCHITECTURE.md, підставлений вище>
99
+
100
+ Кроки:
101
+ 1. Прочитай усі module-summary.
102
+ 2. Виділи бізнес-домени та процеси (можуть перетинати межі модулів). Доменів може бути багато.
103
+ 3. Для КОЖНОГО домену запиши окремий файл docs/<домен>.md у кореневій docs/:
104
+ - назва файлу — короткий kebab-slug домену;
105
+ - не перезаписуй файлові доки кореневих файлів у docs/ (напр. app.md, eslint.config.md):
106
+ якщо слаґ домену збігається з іменем такого файлу — додай суфікс -domain
107
+ (напр. app-domain.md). Інакше пиши docs/<домен>.md як є;
108
+ - опиши бізнес-процес домену з кліковими відносними посиланнями на module-summary, конкретні файли й директорії.
109
+
110
+ Правила стилю — ті ж (українська, чистий Markdown, контекстна незалежність, без HTML).
111
+
112
+ Поверни перелік створених файлів docs/<домен>.md.
113
+ ```
114
+
115
+ ## Крок 3: Підсумок
116
+
117
+ ```
118
+ ✓ doc-aggregate завершено.
119
+ Tier 2 (модулі): <M> module-summary.
120
+ Tier 3 (домени): <D> доменних доків у docs/.
121
+ ```
122
+
123
+ Перелічи файли з помилками (субагент впав або не записав `docPath`), якщо такі є.
124
+ Помилка одного модуля не зупиняє решту.
125
+
126
+ ## Нотатки
127
+
128
+ - Не комітити автоматично — користувач вирішує, коли комітити згенеровану доку.
129
+ - Файлові доки (Tier 1) — окремий обовʼязковий скіл `doc-files`; цей скіл їх не пише, лише агрегує.
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Re-export спільного списку ignore-глобів зі скіла doc-files.
3
+ *
4
+ * Канонічне джерело — `npm/skills/doc-files/js/docgen-ignore.mjs`: обидва скіли
5
+ * (file-level доки і агрегати) мусять бачити однакове дерево кодових файлів,
6
+ * інакше агрегат посилатиметься на файли без док (або навпаки). Залежність
7
+ * спрямована doc-aggregate → doc-files за ADR про розбиття docgen.
8
+ */
9
+ export * from '../../doc-files/js/docgen-ignore.mjs'
@@ -13,9 +13,7 @@ const SOURCE_EXTENSIONS = new Set(['.js', '.mjs', '.ts', '.vue', '.py'])
13
13
  const TEST_FILE_RE = /\.(?:test|spec)\.[^.]+$/u
14
14
 
15
15
  /**
16
- * Чи корінь має system-wide docs layout.
17
- * Такий корінь зарезервований під репозиторні docs/adr, docs/explanation тощо,
18
- * тому file-level docs у нього не пишемо.
16
+ * Чи корінь має system-wide docs layout (зарезервований під repo docs/adr тощо).
19
17
  * @param {string} root абсолютний корінь обходу
20
18
  * @returns {boolean} true — корінь system-wide docs
21
19
  */
@@ -26,7 +24,7 @@ function isSystemWideDocsRoot(root) {
26
24
  /**
27
25
  * Чи є файл кодовим джерелом для документування.
28
26
  * @param {string} fileName базове ім'я файлу
29
- * @returns {boolean} true — документуємо; false — пропускаємо
27
+ * @returns {boolean} true — документуємо
30
28
  */
31
29
  export function isSourceFile(fileName) {
32
30
  if (fileName.endsWith('.d.ts')) return false
@@ -35,30 +33,14 @@ export function isSourceFile(fileName) {
35
33
  }
36
34
 
37
35
  /**
38
- * Обчислює шлях md-документа для кодового файлу: тека `docs/` поряд із джерелом.
39
- * Якщо `sourcePath` відносний, `docPath` теж відносний; якщо абсолютний — абсолютний.
40
- * @param {string} sourcePath шлях до джерела (відносний або абсолютний)
41
- * @returns {string} шлях до `<dir>/docs/<stem>.md` у тому ж просторі шляхів
42
- */
43
- export function docPathForSource(sourcePath) {
44
- const dir = path.dirname(sourcePath)
45
- const stem = path.basename(sourcePath, path.extname(sourcePath))
46
- return path.join(dir, 'docs', `${stem}.md`)
47
- }
48
-
49
- /**
50
- * Рекурсивно обходить дерево від `root`, повертає кодові файли для документування.
51
- * Синхронний `readdirSync` — детермінований порядок і простий рекурсивний обхід без
52
- * гонок; обсяг дерева проєкту це дозволяє.
36
+ * Рекурсивно збирає кодові файли проєкту (posix-шляхи від кореня).
53
37
  * @param {string} root абсолютний корінь обходу
54
- * @returns {Array<{sourcePath:string, docPath:string, exists:boolean}>} список кандидатів з відносними шляхами
38
+ * @returns {string[]} sourcePath-и
55
39
  */
56
- export function scanForDocgen(root) {
40
+ export function scanSourceFiles(root) {
57
41
  const results = []
58
42
 
59
- /**
60
- * @param {string} dir поточний каталог обходу
61
- */
43
+ /** @param {string} dir поточний каталог обходу */
62
44
  function walk(dir) {
63
45
  let entries
64
46
  try {
@@ -76,12 +58,7 @@ export function scanForDocgen(root) {
76
58
  if (isSystemWideDocsRoot(root) && path.dirname(relPath) === '.') continue
77
59
  const sourcePath = relPath.split(path.sep).join('/')
78
60
  if (isDocgenIgnored(sourcePath)) continue
79
- const docPath = docPathForSource(sourcePath)
80
- results.push({
81
- sourcePath,
82
- docPath,
83
- exists: existsSync(path.join(root, docPath))
84
- })
61
+ results.push(sourcePath)
85
62
  }
86
63
  }
87
64
  }
@@ -98,7 +75,6 @@ export function scanForDocgen(root) {
98
75
  */
99
76
  export function slugForModule(root, moduleRoot) {
100
77
  const rel = path.relative(root, moduleRoot)
101
- // корінь репо: фіксований sentinel 'root'
102
78
  if (rel === '') return 'root'
103
79
  return rel
104
80
  .split(path.sep)
@@ -108,7 +84,6 @@ export function slugForModule(root, moduleRoot) {
108
84
 
109
85
  /**
110
86
  * Знаходить корені модулів — теки з `package.json` (корінь завжди модуль).
111
- * Ті ж ignore-glob правила, тож `package.json` у службових деревах не враховується.
112
87
  * @param {string} root абсолютний корінь обходу
113
88
  * @returns {string[]} абсолютні шляхи коренів модулів
114
89
  */
@@ -159,17 +134,17 @@ export function nearestModuleRoot(filePath, moduleRoots) {
159
134
  * Лістить логічні модулі проєкту з членами-файлами і docPath module-summary.
160
135
  * Модулі без кодових файлів пропускаються.
161
136
  * @param {string} root абсолютний корінь обходу
162
- * @returns {Array<{moduleRoot:string, relRoot:string, slug:string, docPath:string, members:string[], exists:boolean}>} модулі (members — sourcePath-и, відносні від root)
137
+ * @returns {Array<{moduleRoot:string, relRoot:string, slug:string, docPath:string, members:string[], exists:boolean}>} модулі (members — sourcePath від root)
163
138
  */
164
139
  export function scanForModules(root) {
165
- const files = scanForDocgen(root)
140
+ const files = scanSourceFiles(root)
166
141
  const moduleRoots = findModuleRoots(root)
167
142
  const byRoot = new Map()
168
- for (const file of files) {
169
- const moduleRoot = nearestModuleRoot(path.join(root, file.sourcePath), moduleRoots)
143
+ for (const sourcePath of files) {
144
+ const moduleRoot = nearestModuleRoot(path.join(root, sourcePath), moduleRoots)
170
145
  if (moduleRoot === null) continue
171
146
  if (!byRoot.has(moduleRoot)) byRoot.set(moduleRoot, [])
172
- byRoot.get(moduleRoot).push(file.sourcePath)
147
+ byRoot.get(moduleRoot).push(sourcePath)
173
148
  }
174
149
 
175
150
  const results = []
@@ -190,7 +165,7 @@ export function scanForModules(root) {
190
165
  }
191
166
 
192
167
  /**
193
- * Парсить `--root <dir>` з argv; default — cwd.
168
+ * Парсить `--root <dir>`; default — cwd.
194
169
  * @param {string[]} argv аргументи після підкоманди
195
170
  * @returns {string} абсолютний корінь
196
171
  */
@@ -200,42 +175,22 @@ export function resolveRoot(argv) {
200
175
  }
201
176
 
202
177
  /**
203
- * Парсить `--root <dir>` (default cwd), сканує і друкує JSON-масив у stdout.
204
- * @param {string[]} argv аргументи після назви субкоманди (наприклад ['--root', '<dir>'])
205
- * @returns {Promise<number>} exit-код: 0 — успіх, 1 — корінь не існує
206
- */
207
- export async function runDocgenScanCli(argv) {
208
- const root = resolveRoot(argv)
209
-
210
- if (!existsSync(root) || !statSync(root).isDirectory()) {
211
- console.error(`docgen scan: корінь не існує або не є директорією: ${root}`)
212
- return 1
213
- }
214
-
215
- const items = await scanForDocgen(root)
216
- console.log(JSON.stringify(items, null, 2))
217
- return 0
218
- }
219
-
220
- /**
221
- * Парсить `--root`, сканує модулі і друкує JSON-масив у stdout.
222
- * @param {string[]} argv аргументи після назви субкоманди (наприклад ['--root', '<dir>'])
223
- * @returns {Promise<number>} exit-код: 0 — успіх, 1 — корінь не існує
178
+ * `doc-aggregate modules` — сканує модулі і друкує JSON-масив у stdout.
179
+ * @param {string[]} argv аргументи після назви субкоманди
180
+ * @returns {number} exit-код: 0 — успіх, 1 — корінь не існує
224
181
  */
225
- export async function runDocgenModulesCli(argv) {
182
+ export function runDocAggregateModulesCli(argv) {
226
183
  const root = resolveRoot(argv)
227
-
228
184
  if (!existsSync(root) || !statSync(root).isDirectory()) {
229
- console.error(`docgen modules: корінь не існує або не є директорією: ${root}`)
185
+ console.error(`doc-aggregate modules: корінь не існує або не є директорією: ${root}`)
230
186
  return 1
231
187
  }
232
-
233
- const items = await scanForModules(root)
234
- console.log(JSON.stringify(items, null, 2))
188
+ console.log(JSON.stringify(scanForModules(root), null, 2))
235
189
  return 0
236
190
  }
237
191
 
238
192
  if (isRunAsCli(import.meta.url)) {
239
- // Прямий запуск: `node skills/docgen/js/docgen-scan.mjs --root <dir>`
240
- process.exitCode = await runDocgenScanCli(process.argv.slice(2))
193
+ // Прямий запуск: `node skills/doc-aggregate/js/docgen-scan.mjs modules --root <dir>`
194
+ const [sub, ...rest] = process.argv.slice(2)
195
+ process.exitCode = runDocAggregateModulesCli(sub === 'modules' ? rest : process.argv.slice(2))
241
196
  }
@@ -0,0 +1,21 @@
1
+ ---
2
+ docgen:
3
+ source: npm/skills/doc-aggregate/js/docgen-ignore.mjs
4
+ crc: 8821af65
5
+ ---
6
+
7
+ # docgen-ignore
8
+
9
+ ## Огляд
10
+
11
+ Re-export спільного списку ignore-глобів зі скіла doc-files: обидва скіли документації (пофайлові доки й агрегати) мусять бачити однакове дерево кодових файлів, інакше агрегат посилатиметься на файли без док або навпаки.
12
+
13
+ ## Поведінка
14
+
15
+ 1. Модуль не має власної логіки: повністю делегує канонічному джерелу в doc-files і повторно експортує його API (список глобів і перевірку належності шляху до ігнорованих).
16
+ 2. Напрям залежності — doc-aggregate → doc-files, відповідно до рішення про розбиття docgen на два скіли.
17
+
18
+ ## Гарантії поведінки
19
+
20
+ - Списки ignore-глобів двох скілів не можуть розійтися — джерело одне.
21
+ - Read-only, без мережі й побічних ефектів.
@@ -0,0 +1,100 @@
1
+ ---
2
+ name: doc-files
3
+ description: >-
4
+ Обовʼязковий крок задачі (як lint): для кожного зміненого/нового кодового файлу (js/mjs/ts/vue/py) JS-оркестрована генерація лаконічної поведінкової української md-документації у теку docs/ поряд із кодом, зі звіркою застарілості за CRC у frontmatter
5
+ ---
6
+
7
+ # doc-files — файлова документація (обовʼязковий крок)
8
+
9
+ ## Мета
10
+
11
+ Для кожного кодового файлу проєкту тримати **актуальну** лаконічну поведінкову `.md`-документацію
12
+ у теці `docs/` **поряд із самим файлом** (`<dir>/docs/<stem>.md`). Це **обовʼязковий крок кожної
13
+ задачі** — як `lint`: після зміни коду його дока має бути перегенерована.
14
+
15
+ Застарілість визначається **детерміновано за CRC**: кожна дока несе у frontmatter контрольну
16
+ суму байтів джерела на момент генерації. Дока **застаріла**, якщо її немає або
17
+ `crc(поточне джерело) ≠ crc у frontmatter`.
18
+
19
+ ```markdown
20
+ ---
21
+ docgen:
22
+ source: src/lib/foo.js
23
+ crc: a3f1c9e0
24
+ ---
25
+
26
+ ## Огляд
27
+
28
+
29
+ ```
30
+
31
+ ## Оркестрацію веде JS, не модель; конвеєр — local-only
32
+
33
+ Уся важка робота — черга, батчинг, виклики LLM і штамп CRC — живе в JS-команді
34
+ `doc-files gen`. **Ти не диспатчиш субагентів і не тримаєш сотні файлів у контексті**
35
+ — тому навіть масовий перший прогін усього репо не «заморює». Цей скіл **тонкий**: твоє завдання —
36
+ запустити генерацію і прочитати підсумок.
37
+
38
+ Конвеєр **суто локальний** (ADR `260610-2228`): будь-який файл генерується локальною
39
+ моделлю (`omlx/…` напряму), хмарних ескалацій немає. Якщо det-оцінка нижча за поріг —
40
+ дока все одно пишеться з **degraded-маркером** (`score`/`issues` у frontmatter, CRC свіжий),
41
+ а перегенерація таких док — окремою командою пізніше.
42
+
43
+ ## Передумова
44
+
45
+ - Поточна директорія — корінь проєкту (`requireRoot`), не worktree.
46
+ - Доступний `npx @nitra/cursor`.
47
+
48
+ ## Workflow
49
+
50
+ ### Крок 1: Генерація застарілих/відсутніх док
51
+
52
+ ```bash
53
+ npx @nitra/cursor doc-files gen
54
+ ```
55
+
56
+ Команда сама: перевіряє omlx (preflight: «сервер лежить» / «модель не влазить у пам'ять
57
+ зайнятої машини» / «потрібен API-ключ» → одна зрозуміла зупинка замість лавини «✗») →
58
+ сканує проєкт → фільтрує застарілі (`stale`) → генерує локальною моделлю → пише доку зі
59
+ **свіжим CRC** (і degraded-маркером, якщо не дотягнула) → друкує прогрес і підсумок.
60
+
61
+ - Для дуже великого прогону можна порціями: `--from N --limit M`.
62
+ - Перегенерувати **всі** доки (не лише застарілі): `--overwrite`.
63
+ - Перегенерувати лише degraded (свіжі за CRC, score < порогу): `--retry-degraded`.
64
+
65
+ ### Крок 2: Підтвердження
66
+
67
+ Дочекайся підсумку `✓ OK: <N> ⚠ degraded: <D> ✗ Err: <E>`. Якщо є помилки — перелічи
68
+ проблемні файли. Exit-код `1` означає, що хоча б один файл не згенерувався (або не пройшов
69
+ preflight). Degraded — не помилка: дока існує, борг видно у `check --degraded`.
70
+
71
+ ### Крок 3 (за потреби): перевірка перед завершенням
72
+
73
+ ```bash
74
+ npx @nitra/cursor doc-files check --git
75
+ ```
76
+
77
+ Перевіряє змінені у задачі джерела (`git diff --name-only HEAD`) проти CRC їхніх док.
78
+ Цю ж перевірку виконує **Stop-hook** як твердий гейт: завершити задачу зі застарілими
79
+ доками не можна (виняток — масовий прогін понад поріг `N_CURSOR_DOC_FILES_GATE_MAX`, дефолт 50).
80
+ Degraded-доки гейт **не** блокує (CRC свіжий); їх список — `doc-files check --degraded`.
81
+
82
+ ## Правила стилю документа (за adr/ci4)
83
+
84
+ - Мова — **УКРАЇНСЬКА** для всього тексту. Code identifiers, шляхи, імена API, команди — як у коді.
85
+ - **Чистий Markdown.** Жодних HTML-обгорток. Єдиний виняток — машинний `docgen:`-frontmatter із CRC.
86
+ - **Фокус на ПОВЕДІНЦІ, не реалізації.** ЩО і НАВІЩО, а не як саме зроблено.
87
+ - Не перелічуй модулі стандартної бібліотеки і внутрішні назви допоміжних функцій.
88
+ - Кожна секція самодостатня (без «як вище», «ця функція»).
89
+ - Секції (лише доречні): `## Огляд`, `## Поведінка`, `## Публічний API`, `## Де використовується`,
90
+ `## Гарантії поведінки`; для `.vue` — `## Інтерфейс компонента`.
91
+ - Не вигадуй деталей, яких немає в коді.
92
+
93
+ ## Нотатки
94
+
95
+ - Не комітити автоматично — користувач вирішує, коли комітити згенеровану доку.
96
+ - Scanner ігнорує `node_modules`, `dist`, `.git`, `__pycache__`, `coverage`, `.cursor`, `.claude`,
97
+ усі теки `docs/`, а також `*.test.*` / `*.spec.*` / `*.d.ts`. Кореневий repo `docs/` —
98
+ system-wide only: file-level docs туди не пишуться. Список glob-ів — `docgen-ignore.mjs`.
99
+ - Агрегуюча документація (module-summary, доменні доки) — окремий скіл `doc-aggregate`, за запитом.
100
+ - Для наявних док без CRC одноразово: `npx @nitra/cursor doc-files stamp` (штампує frontmatter без LLM).
@@ -0,0 +1,164 @@
1
+ /**
2
+ * CRC32 джерела + YAML-frontmatter файлової документації.
3
+ *
4
+ * Кожна файлова дока несе у frontmatter контрольну суму байтів джерела на момент
5
+ * генерації. Це детермінований маркер застарілості: `crc32(поточне джерело)` звіряється
6
+ * з `crc` у доці — розбіжність (або відсутня дока) означає, що дока відстала від коду.
7
+ * CRC не залежить від git-стану (rebase, незакомічене, гілки), тож придатний і для
8
+ * per-edit hook (бачить лише змінений файл), і для повного сканування.
9
+ *
10
+ * Degraded-маркер (ADR 260610-2228): якщо локальний конвеєр не дотягнув до порогу
11
+ * якості, дока все одно пишеться, а frontmatter додатково несе `score` (det-оцінка)
12
+ * та `issues` (коди проблем). CRC при цьому свіжий — Stop-гейт не блокує задачі через
13
+ * слабкість моделі; борг видимий через `check --degraded` і адресно перегенеровується
14
+ * через `gen --retry-degraded`.
15
+ *
16
+ * Frontmatter — єдиний дозволений виняток із правила «чистий Markdown без HTML»:
17
+ * це машинні метадані, не контент. Формат:
18
+ *
19
+ * ---
20
+ * docgen:
21
+ * source: src/lib/foo.js
22
+ * crc: a3f1c9e0
23
+ * score: 55
24
+ * issues: short-behavior,internal-name:bar
25
+ * ---
26
+ */
27
+ import { existsSync, readFileSync } from 'node:fs'
28
+ import { crc32 as zlibCrc32 } from 'node:zlib'
29
+ import { env } from 'node:process'
30
+
31
+ /** Поріг degraded: дока зі `score` нижче вважається неякісною. */
32
+ export const QUALITY_THRESHOLD = Number(env.N_CURSOR_DOC_FILES_THRESHOLD ?? 70) || 70
33
+
34
+ /**
35
+ * CRC32 вмісту у hex (8 символів, з провідними нулями). Делегує у нативний
36
+ * `node:zlib.crc32` — без ручної бітової арифметики.
37
+ * @param {string|Buffer} input текст або байти джерела
38
+ * @returns {string} CRC32 у hex
39
+ */
40
+ export function crc32(input) {
41
+ const buf = typeof input === 'string' ? Buffer.from(input, 'utf8') : input
42
+ return zlibCrc32(buf).toString(16).padStart(8, '0')
43
+ }
44
+
45
+ /** Провідний YAML-frontmatter-блок `---\n…\n---`. */
46
+ const FRONTMATTER_RE = /^---\n([\s\S]*?)\n---\n?/u
47
+ const SOURCE_RE = /^[ \t]{0,8}source:[ \t]{0,8}(.+)$/mu
48
+ const CRC_RE = /^[ \t]{0,8}crc:[ \t]{0,8}(.+)$/mu
49
+ const SCORE_RE = /^[ \t]{0,8}score:[ \t]{0,8}(\d+)$/mu
50
+ const ISSUES_RE = /^[ \t]{0,8}issues:[ \t]{0,8}(.+)$/mu
51
+ const LEADING_NEWLINES_RE = /^\n+/u
52
+ const ISSUE_CODE_TAIL_RE = /[,:]$/u
53
+
54
+ /**
55
+ * Парсить frontmatter файлової доки. Без блоку — `data:null` і `body` дорівнює входу.
56
+ * Поля `score`/`issues` опційні (back-compat зі старими доками): без них —
57
+ * `score:null`, `issues:[]`.
58
+ * @param {string} md вміст md-файлу
59
+ * @returns {{ data: { source: string|null, crc: string|null, score: number|null, issues: string[] }|null, body: string }} метадані + тіло без frontmatter
60
+ */
61
+ export function parseDocFrontmatter(md) {
62
+ const match = md.match(FRONTMATTER_RE)
63
+ if (!match) return { data: null, body: md }
64
+ const block = match[1]
65
+ const scoreRaw = block.match(SCORE_RE)?.[1]
66
+ const issuesRaw = block.match(ISSUES_RE)?.[1]
67
+ return {
68
+ data: {
69
+ source: block.match(SOURCE_RE)?.[1].trim() ?? null,
70
+ crc: block.match(CRC_RE)?.[1].trim() ?? null,
71
+ score: scoreRaw === undefined ? null : Number(scoreRaw),
72
+ issues: issuesRaw
73
+ ? issuesRaw
74
+ .split(',')
75
+ .map(s => s.trim())
76
+ .filter(Boolean)
77
+ : []
78
+ },
79
+ body: md.slice(match[0].length)
80
+ }
81
+ }
82
+
83
+ /** Максимум кодів issues у frontmatter — це маркер, а не повний лог. */
84
+ const MAX_ISSUE_CODES = 8
85
+
86
+ /**
87
+ * Нормалізує issues до YAML-безпечних кодів: бере фрагмент до першого пробілу
88
+ * (зрізає людиночитні хвости помилок), відкидає порожні, обмежує кількість.
89
+ * @param {string[]} issues сирі issue-рядки від скорера
90
+ * @returns {string[]} коди без пробілів
91
+ */
92
+ function issueCodes(issues) {
93
+ return issues
94
+ .map(i => String(i).split(' ')[0].replace(ISSUE_CODE_TAIL_RE, ''))
95
+ .filter(Boolean)
96
+ .slice(0, MAX_ISSUE_CODES)
97
+ }
98
+
99
+ /**
100
+ * Будує frontmatter-блок із шляхом джерела, CRC і (опційно) якістю.
101
+ * @param {string} source відносний шлях джерела
102
+ * @param {string} crc CRC32 джерела у hex
103
+ * @param {{ score: number, issues?: string[] }|null} [quality] det-оцінка доки; null — без полів якості
104
+ * @returns {string} рядок `---\ndocgen:\n source: …\n crc: …[\n score: …][\n issues: …]\n---\n`
105
+ */
106
+ export function buildDocFrontmatter(source, crc, quality = null) {
107
+ const lines = [`source: ${source}`, `crc: ${crc}`]
108
+ if (quality && typeof quality.score === 'number') {
109
+ lines.push(`score: ${quality.score}`)
110
+ const codes = issueCodes(quality.issues ?? [])
111
+ if (codes.length > 0) lines.push(`issues: ${codes.join(',')}`)
112
+ }
113
+ const indented = lines.map(l => ' ' + l).join('\n')
114
+ return `---\ndocgen:\n${indented}\n---\n`
115
+ }
116
+
117
+ /**
118
+ * (Пере)штампує frontmatter у md-доку: знімає наявний блок і додає свіжий.
119
+ * @param {string} md тіло доки (з frontmatter або без)
120
+ * @param {string} source відносний шлях джерела
121
+ * @param {string} crc CRC32 джерела у hex
122
+ * @param {{ score: number, issues?: string[] }|null} [quality] det-оцінка доки
123
+ * @returns {string} md зі свіжим frontmatter
124
+ */
125
+ export function stampDoc(md, source, crc, quality = null) {
126
+ const { body } = parseDocFrontmatter(md)
127
+ return `${buildDocFrontmatter(source, crc, quality)}\n${body.replace(LEADING_NEWLINES_RE, '')}`
128
+ }
129
+
130
+ /**
131
+ * CRC, збережений у frontmatter доки; `null` — доки немає або CRC відсутній.
132
+ * @param {string} docAbsPath абсолютний шлях md-доки
133
+ * @returns {string|null} CRC32 з frontmatter або null
134
+ */
135
+ export function readDocCrc(docAbsPath) {
136
+ if (!existsSync(docAbsPath)) return null
137
+ return parseDocFrontmatter(readFileSync(docAbsPath, 'utf8')).data?.crc ?? null
138
+ }
139
+
140
+ /**
141
+ * Якість, збережена у frontmatter доки.
142
+ * @param {string} docAbsPath абсолютний шлях md-доки
143
+ * @returns {{ score: number|null, issues: string[] }} `score:null` — доки немає або поле відсутнє
144
+ */
145
+ export function readDocQuality(docAbsPath) {
146
+ if (!existsSync(docAbsPath)) return { score: null, issues: [] }
147
+ const data = parseDocFrontmatter(readFileSync(docAbsPath, 'utf8')).data
148
+ return { score: data?.score ?? null, issues: data?.issues ?? [] }
149
+ }
150
+
151
+ /**
152
+ * Стан застарілості доки відносно її джерела.
153
+ * `missing` — доки немає; `crc-mismatch` — CRC джерела ≠ CRC у доці; інакше свіжа.
154
+ * @param {string} sourceAbsPath абсолютний шлях джерела
155
+ * @param {string} docAbsPath абсолютний шлях md-доки
156
+ * @returns {{ stale: boolean, reason: 'missing'|'crc-mismatch'|null }} стан застарілості
157
+ */
158
+ export function staleness(sourceAbsPath, docAbsPath) {
159
+ const docCrc = readDocCrc(docAbsPath)
160
+ if (docCrc === null) return { stale: true, reason: 'missing' }
161
+ const srcCrc = crc32(readFileSync(sourceAbsPath))
162
+ if (srcCrc !== docCrc) return { stale: true, reason: 'crc-mismatch' }
163
+ return { stale: false, reason: null }
164
+ }