@nitra/cursor 3.21.1 → 3.23.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 (231) hide show
  1. package/.pi-template/extensions/n-cursor-adr/docs/index.md +181 -0
  2. package/CHANGELOG.md +37 -3
  3. package/bin/docs/n-cursor.md +636 -0
  4. package/bin/docs/rename-yaml-extensions.md +207 -0
  5. package/bin/n-cursor.js +30 -3
  6. package/package.json +1 -1
  7. package/rules/abie/docs/fix.md +18 -0
  8. package/rules/abie/js/docs/applies.md +26 -0
  9. package/rules/abie/js/docs/env_dns.md +32 -0
  10. package/rules/abie/js/docs/firebase_hosting.md +23 -0
  11. package/rules/abie/js/docs/hc_pairing.md +35 -0
  12. package/rules/abie/js/docs/ua_http_route.md +28 -0
  13. package/rules/abie/js/docs/ua_node_selector.md +28 -0
  14. package/rules/abie/lib/docs/enabled.md +29 -0
  15. package/rules/abie/lib/docs/env-dns.md +35 -0
  16. package/rules/abie/lib/docs/hc-yaml.md +33 -0
  17. package/rules/abie/lib/docs/http-route.md +44 -0
  18. package/rules/abie/lib/docs/k8s-tree.md +40 -0
  19. package/rules/abie/lib/docs/kustomization-patches.md +47 -0
  20. package/rules/abie/lib/docs/overlay-paths.md +38 -0
  21. package/rules/abie/lib/docs/yaml.md +29 -0
  22. package/rules/adr/docs/fix.md +148 -0
  23. package/rules/adr/js/docs/hooks.md +259 -0
  24. package/rules/bun/docs/fix.md +156 -0
  25. package/rules/bun/js/docs/layout.md +393 -0
  26. package/rules/capacitor/docs/fix.md +121 -0
  27. package/rules/capacitor/js/docs/platforms.md +295 -0
  28. package/rules/changelog/changelog.mdc +2 -2
  29. package/rules/changelog/docs/fix.md +174 -0
  30. package/rules/changelog/js/consistency.mjs +114 -13
  31. package/rules/changelog/js/docs/consistency.md +387 -0
  32. package/rules/changelog/lib/docs/package-manifest.md +210 -0
  33. package/rules/ci4/docs/fix.md +179 -0
  34. package/rules/ci4/js/docs/marksman_config.md +128 -0
  35. package/rules/docker/docker.mdc +8 -3
  36. package/rules/docker/docs/fix.md +171 -0
  37. package/rules/docker/js/docs/lint.md +258 -0
  38. package/rules/docker/lib/docs/docker-hadolint.md +184 -0
  39. package/rules/docker/lib/docs/docker-mirror.md +247 -0
  40. package/rules/docker/lib/docs/docker-native-addon.md +170 -0
  41. package/rules/docker/lib/docs/docker-nginx-user.md +219 -0
  42. package/rules/docker/lint/docs/lint.md +193 -0
  43. package/rules/efes/docs/fix.md +203 -0
  44. package/rules/feedback/docs/fix.md +140 -0
  45. package/rules/flow/docs/fix.md +152 -0
  46. package/rules/ga/docs/fix.md +158 -0
  47. package/rules/ga/js/docs/lint.md +100 -0
  48. package/rules/ga/js/docs/workflows.md +217 -0
  49. package/rules/ga/lint/docs/lint.md +209 -0
  50. package/rules/ga/policy/clean_merged_branch/clean_merged_branch.rego +11 -2
  51. package/rules/ga/policy/clean_merged_branch/template/clean-merged-branch.yml.snippet.yml +3 -4
  52. package/rules/graphql/docs/fix.md +126 -0
  53. package/rules/graphql/js/docs/tooling.md +264 -0
  54. package/rules/graphql/lib/docs/graphql-gql-scan.md +219 -0
  55. package/rules/hasura/docs/fix.md +120 -0
  56. package/rules/hasura/hasura.mdc +14 -0
  57. package/rules/hasura/js/docs/internal_urls.md +326 -0
  58. package/rules/image-avif/docs/fix.md +132 -0
  59. package/rules/image-avif/js/docs/avif_generation.md +241 -0
  60. package/rules/image-compress/docs/fix.md +150 -0
  61. package/rules/image-compress/js/docs/package_setup.md +191 -0
  62. package/rules/js-bun-db/docs/fix.md +148 -0
  63. package/rules/js-bun-db/js/docs/safety.md +231 -0
  64. package/rules/js-bun-db/js-bun-db.mdc +42 -13
  65. package/rules/js-bun-db/lib/docs/bun-sql-scan.md +347 -0
  66. package/rules/js-bun-redis/docs/fix.md +123 -0
  67. package/rules/js-bun-redis/js/docs/imports.md +176 -0
  68. package/rules/js-bun-redis/lib/docs/redis-imports.md +223 -0
  69. package/rules/js-lint/docs/fix.md +117 -0
  70. package/rules/js-lint/js/docs/lint.md +250 -0
  71. package/rules/js-lint/js/docs/tooling.md +348 -0
  72. package/rules/js-lint/js/docs/utils_imports.md +207 -0
  73. package/rules/js-lint/js/lint-findings.mjs +110 -0
  74. package/rules/js-lint/js/lint.mjs +86 -15
  75. package/rules/js-lint-ci/docs/fix.md +154 -0
  76. package/rules/js-lint-ci/js/docs/lint.md +144 -0
  77. package/rules/js-mssql/docs/fix.md +128 -0
  78. package/rules/js-mssql/js/docs/deps.md +263 -0
  79. package/rules/js-mssql/lib/docs/mssql-pool-scan.md +367 -0
  80. package/rules/js-run/docs/fix.md +144 -0
  81. package/rules/js-run/js/docs/runtime.md +388 -0
  82. package/rules/js-run/lib/docs/bunyan-imports.md +117 -0
  83. package/rules/js-run/lib/docs/check-env-scan.md +433 -0
  84. package/rules/js-run/lib/docs/conn-file-rules.md +300 -0
  85. package/rules/js-run/lib/docs/conn-imports-scan.md +204 -0
  86. package/rules/js-run/lib/docs/promise-settimeout-scan.md +326 -0
  87. package/rules/k8s/docs/fix.md +129 -0
  88. package/rules/k8s/js/docs/manifests.md +344 -0
  89. package/rules/k8s/js/manifests.mjs +6 -2
  90. package/rules/k8s/k8s.mdc +4 -2
  91. package/rules/k8s/lint/docs/lint.md +411 -0
  92. package/rules/k8s/policy/network_policy/template/deployment.snippet.yaml +2 -0
  93. package/rules/k8s/policy/network_policy/template/stateful-set.snippet.yaml +2 -0
  94. package/rules/nginx-default-tpl/docs/fix.md +124 -0
  95. package/rules/nginx-default-tpl/js/docs/template.md +378 -0
  96. package/rules/npm-module/docs/fix.md +98 -0
  97. package/rules/npm-module/js/docs/package_structure.md +274 -0
  98. package/rules/npm-module/js/docs/rule_meta.md +137 -0
  99. package/rules/npm-module/js/docs/skill_meta.md +190 -0
  100. package/rules/php/docs/fix.md +107 -0
  101. package/rules/php/js/docs/tooling.md +152 -0
  102. package/rules/php/lint/docs/lint.md +215 -0
  103. package/rules/python/docs/fix.md +163 -0
  104. package/rules/python/js/docs/applies.md +108 -0
  105. package/rules/python/js/docs/tooling.md +153 -0
  106. package/rules/python/lint/docs/lint.md +322 -0
  107. package/rules/rego/docs/fix.md +121 -0
  108. package/rules/rego/js/docs/applies.md +174 -0
  109. package/rules/rego/js/docs/lint.md +118 -0
  110. package/rules/rego/lint/docs/lint.md +204 -0
  111. package/rules/release/docs/change.md +185 -0
  112. package/rules/release/docs/fix.md +119 -0
  113. package/rules/release/docs/release.md +222 -0
  114. package/rules/release/lib/docs/aggregate.md +246 -0
  115. package/rules/release/lib/docs/change-file.md +200 -0
  116. package/rules/release/lib/docs/fallback.md +203 -0
  117. package/rules/rust/docs/fix.md +129 -0
  118. package/rules/rust/js/docs/applies.md +140 -0
  119. package/rules/rust/lib/docs/has-cargo-toml.md +130 -0
  120. package/rules/security/docs/fix.md +86 -0
  121. package/rules/security/js/docs/lint.md +171 -0
  122. package/rules/security/js/docs/sample_secret.md +190 -0
  123. package/rules/security/js/docs/trufflehog.md +137 -0
  124. package/rules/security/js/lint.mjs +9 -1
  125. package/rules/style-lint/docs/fix.md +155 -0
  126. package/rules/style-lint/js/docs/lint.md +184 -0
  127. package/rules/style-lint/js/docs/tooling.md +194 -0
  128. package/rules/tauri/docs/fix.md +158 -0
  129. package/rules/tauri/js/docs/cargo_mutants_config.md +168 -0
  130. package/rules/tauri/js/docs/tooling.md +228 -0
  131. package/rules/test/coverage/coverage.mjs +15 -3
  132. package/rules/test/docs/fix.md +132 -0
  133. package/rules/test/js/data/stryker_config/docs/stryker-vue-macros-ignorer.md +138 -0
  134. package/rules/test/js/data/stryker_config/docs/stryker.config.baseline.md +134 -0
  135. package/rules/test/js/data/stryker_config/docs/stryker.config.vue.baseline.md +160 -0
  136. package/rules/test/js/data/vitest_config/docs/vitest.config.baseline.md +195 -0
  137. package/rules/test/js/docs/cargo_mutants_config.md +173 -0
  138. package/rules/test/js/docs/location.md +136 -0
  139. package/rules/test/js/docs/no-process-chdir.md +160 -0
  140. package/rules/test/js/docs/no-relative-fs-path.md +271 -0
  141. package/rules/test/js/docs/stryker_config.md +152 -0
  142. package/rules/test/js/docs/vitest-config-pool-forks.md +174 -0
  143. package/rules/text/docs/fix.md +118 -0
  144. package/rules/text/js/docs/forbidden-prettier.md +143 -0
  145. package/rules/text/js/docs/formatting.md +256 -0
  146. package/rules/text/js/docs/lint.md +122 -0
  147. package/rules/text/lint/docs/lint.md +220 -0
  148. package/rules/text/lint/docs/run-dotenv-linter.md +157 -0
  149. package/rules/text/lint/docs/run-shellcheck.md +212 -0
  150. package/rules/text/lint/docs/run-v8r.md +197 -0
  151. package/rules/vue/docs/fix.md +127 -0
  152. package/rules/vue/js/docs/packages.md +335 -0
  153. package/rules/vue/lib/docs/vue-forbidden-imports.md +261 -0
  154. package/rules/worktree/docs/fix.md +161 -0
  155. package/schemas/rule-meta.json +5 -1
  156. package/scripts/auto-rules.mjs +7 -4
  157. package/scripts/coverage-classify/docs/apply.md +202 -0
  158. package/scripts/coverage-classify/docs/cache.md +203 -0
  159. package/scripts/coverage-classify/docs/index.md +218 -0
  160. package/scripts/coverage-classify/docs/prompt.md +132 -0
  161. package/scripts/coverage-classify/docs/verdict-schema.md +169 -0
  162. package/scripts/coverage-fix-extract.mjs +122 -0
  163. package/scripts/coverage-fix.mjs +1 -1
  164. package/scripts/dispatcher/docs/graph.md +346 -0
  165. package/scripts/dispatcher/docs/index.md +236 -0
  166. package/scripts/dispatcher/docs/trace.md +296 -0
  167. package/scripts/dispatcher/index.mjs +1 -1
  168. package/scripts/dispatcher/lib/active.mjs +4 -8
  169. package/scripts/dispatcher/lib/commands.mjs +7 -11
  170. package/scripts/dispatcher/lib/docs/active.md +348 -0
  171. package/scripts/dispatcher/lib/docs/artifact.md +232 -0
  172. package/scripts/dispatcher/lib/docs/budget.md +167 -0
  173. package/scripts/dispatcher/lib/docs/capability.md +196 -0
  174. package/scripts/dispatcher/lib/docs/commands.md +210 -0
  175. package/scripts/dispatcher/lib/docs/events.md +182 -0
  176. package/scripts/dispatcher/lib/docs/executor.md +190 -0
  177. package/scripts/dispatcher/lib/docs/flow-lock.md +161 -0
  178. package/scripts/dispatcher/lib/docs/flow-resolve.md +267 -0
  179. package/scripts/dispatcher/lib/docs/gate.md +231 -0
  180. package/scripts/dispatcher/lib/docs/level.md +335 -0
  181. package/scripts/dispatcher/lib/docs/plan-panel.md +181 -0
  182. package/scripts/dispatcher/lib/docs/plan.md +200 -0
  183. package/scripts/dispatcher/lib/docs/planner.md +269 -0
  184. package/scripts/dispatcher/lib/docs/review.md +255 -0
  185. package/scripts/dispatcher/lib/docs/reviewer.md +240 -0
  186. package/scripts/dispatcher/lib/docs/snapshot.md +247 -0
  187. package/scripts/dispatcher/lib/docs/spec.md +203 -0
  188. package/scripts/dispatcher/lib/docs/state-store.md +303 -0
  189. package/scripts/dispatcher/lib/docs/subagent-runner.md +173 -0
  190. package/scripts/dispatcher/lib/executor.mjs +6 -1
  191. package/scripts/dispatcher/lib/flow-resolve.mjs +3 -1
  192. package/scripts/dispatcher/lib/level.mjs +29 -3
  193. package/scripts/dispatcher/lib/review.mjs +1 -1
  194. package/scripts/dispatcher/lib/subagent-runner.mjs +5 -3
  195. package/scripts/docs/auto-rules.md +376 -0
  196. package/scripts/docs/auto-skills.md +173 -0
  197. package/scripts/docs/build-agents-commands.md +183 -0
  198. package/scripts/docs/cli-entry.md +153 -0
  199. package/scripts/docs/coverage-fix.md +177 -0
  200. package/scripts/docs/ensure-nitra-cursor-dev-dependencies.md +189 -0
  201. package/scripts/lib/changed-files.mjs +4 -1
  202. package/scripts/lib/diff-added-lines.mjs +85 -0
  203. package/scripts/lib/docs/changed-files.md +149 -0
  204. package/scripts/lib/docs/check-mdc-template-refs.md +222 -0
  205. package/scripts/lib/docs/check-reporter.md +175 -0
  206. package/scripts/lib/docs/discover-check-rules-from-cursor.md +157 -0
  207. package/scripts/lib/docs/discover-checkable-rules.md +165 -0
  208. package/scripts/lib/docs/ensure-tool.md +254 -0
  209. package/scripts/lib/docs/generated-markdown.md +275 -0
  210. package/scripts/lib/docs/gha-workflow.md +326 -0
  211. package/scripts/lib/docs/inline-template-links.md +303 -0
  212. package/scripts/lib/docs/list-rule-ids.md +156 -0
  213. package/scripts/lib/docs/load-cursor-config.md +147 -0
  214. package/scripts/lib/docs/mirror-parity.md +167 -0
  215. package/scripts/lib/worktree.mjs +26 -0
  216. package/scripts/worktree-cli.mjs +12 -2
  217. package/skills/coverage-fix/SKILL.md +34 -45
  218. package/skills/docgen/SKILL.md +44 -23
  219. package/skills/docgen/bench/etalon/firebase_hosting.md +19 -0
  220. package/skills/docgen/bench/etalon/k8s-tree.md +24 -0
  221. package/skills/docgen/bench/etalon/overlay-paths.md +24 -0
  222. package/skills/docgen/js/docgen-ignore.mjs +54 -0
  223. package/skills/docgen/js/docgen-scan.mjs +37 -21
  224. package/skills/llm-patch/SKILL.md +23 -2
  225. package/skills/start-check/SKILL.md +26 -53
  226. package/skills/start-check/js/check.mjs +211 -0
  227. package/skills/taze/SKILL.md +9 -3
  228. package/skills/taze/js/diff.mjs +154 -0
  229. package/types/bin/n-cursor.d.ts +1 -1
  230. package/skills/fix-tests/SKILL.md +0 -119
  231. package/skills/fix-tests/meta.json +0 -1
@@ -0,0 +1,183 @@
1
+ # build-agents-commands.mjs
2
+
3
+ ## Огляд
4
+
5
+ Модуль `build-agents-commands.mjs` формує перелік маркованих елементів (bullet items) для секції «Команди» у файлі `AGENTS.md`, який згенерує CLI `@nitra/cursor` під час синхронізації правил/шаблону.
6
+
7
+ Принципи побудови списку:
8
+
9
+ - Джерело істини — поле `scripts` у `package.json` цільового репозиторію (кореневого, не залежно від workspace).
10
+ - Спочатку додається фіксований рядок про встановлення залежностей: `bun i` (конвенція bun-монорепо).
11
+ - Далі — відомі ключі скриптів у заздалегідь визначеному порядку (`SCRIPT_KEYS_ORDER`), але лише ті, що реально присутні у `package.json` як непорожні рядки.
12
+ - Після них — усі додаткові ключі, що починаються з `lint-` і ще не були додані, у лексикографічному порядку.
13
+ - Наприкінці завжди додаються фіксовані рядки про CLI `@nitra/cursor` (синхронізація правил та `programmatic` перевірки) та про `knip` (пошук невикористаних залежностей/експортів).
14
+
15
+ Модуль повертає масив об'єктів з єдиним полем `name`, який далі споживає функція `expandMustacheSection` під час підставлення в Mustache-шаблон `AGENTS.template.md` (секція `commands`).
16
+
17
+ ## Експорти / API
18
+
19
+ | Експорт | Тип | Призначення |
20
+ | ------------------------------- | ------------------------------------------------------------------ | --------------------------------------------------------------- |
21
+ | `buildAgentsCommandBulletItems` | `async function(projectRoot: string): Promise<{ name: string }[]>` | Будує масив елементів секції `commands` для Mustache-розкриття. |
22
+
23
+ Інших публічних експортів (`default`, ре-експортів) у файлі немає. Внутрішні допоміжні функції та константи (`PACKAGE_NAME`, `AGENTS_MD`, `SCRIPT_KEYS_ORDER`, `readPackageScripts`) не експортуються.
24
+
25
+ ## Функції
26
+
27
+ ### `readPackageScripts(projectRoot)`
28
+
29
+ Внутрішня (не експортована) функція. Безпечно читає поле `scripts` з `package.json` у вказаному корені.
30
+
31
+ - **Сигнатура**: `async function readPackageScripts(projectRoot: string): Promise<Record<string, string>>`
32
+ - **Параметри**:
33
+ - `projectRoot` — абсолютний шлях до кореня репозиторію (де лежить `package.json`).
34
+ - **Повертає**: `Promise<Record<string, string>>` — об'єкт `scripts` із `package.json`. У випадку відсутності файлу, помилки IO, некоректного JSON або відсутності/непридатного типу поля `scripts` — порожній об'єкт `{}`.
35
+ - **Алгоритм**:
36
+ 1. Формує абсолютний шлях `pkgPath = join(projectRoot, 'package.json')`.
37
+ 2. Якщо файл не існує (`existsSync`) — повертає `{}`.
38
+ 3. Інакше — `readFile(pkgPath, 'utf8')`, `JSON.parse(raw)`.
39
+ 4. Перевіряє, що `pkg` — об'єкт, у нього є поле `scripts` типу `object`. Якщо так — повертає його (з JSDoc-кастом до `Record<string, string>`).
40
+ 5. Будь-який кинутий виняток (некоректний JSON, помилка читання) глушиться `try/catch` без логу — секція команд лишається з мінімумом (`bun i` + рядки про CLI).
41
+ - **Side effects**: лише синхронне `existsSync` та асинхронне читання файлу `package.json` із FS. Жодних мутацій глобального стану, жодного `console.*`, жодного `process.exit`.
42
+
43
+ ### `buildAgentsCommandBulletItems(projectRoot)` (експортована)
44
+
45
+ Основний публічний API модуля.
46
+
47
+ - **Сигнатура**: `export async function buildAgentsCommandBulletItems(projectRoot: string): Promise<{ name: string }[]>`
48
+ - **Параметри**:
49
+ - `projectRoot` — абсолютний шлях до кореня репозиторію (зазвичай `process.cwd()` виклику CLI).
50
+ - **Повертає**: `Promise<{ name: string }[]>` — впорядкований список елементів секції `commands`. Кожен елемент — об'єкт з полем `name`, що містить уже готовий рядок маркованого пункту Markdown (починається з `- **<підпис>**: \`<команда>\``).
51
+ - **Алгоритм**:
52
+ 1. Викликає `readPackageScripts(projectRoot)` та зберігає результат у `scripts`.
53
+ 2. Ініціалізує масив `items` одним фіксованим елементом:
54
+ `- **Залежності**: \`bun i\``.
55
+ 3. Створює `Set<string>` під назвою `added` для відстеження уже доданих ключів скриптів (запобігає дублюванню при подальшому fallback-проході).
56
+ 4. Ітерує `SCRIPT_KEYS_ORDER` у заданому порядку: `test`, `lint`, `lint-js`, `lint-text`, `lint-ga`, `lint-k8s`, `lint-docker`, `start`, `dev`, `build`. Для кожного ключа, для якого `scripts[key]` — непорожній рядок, додає у `items` об'єкт `{ name: '- **<key>**: \`bun run <key>\`' }`і фіксує ключ у`added`.
57
+ 5. Збирає масив `lintExtraKeys`: усі ключі `scripts`, які починаються з `lint-`, не входять у `added` і мають значення типу `string`. Сортує лексикографічно через `toSorted((a, b) => a.localeCompare(b))` (іммутабельне сортування — оригінальний масив `Object.keys(scripts)` не змінюється).
58
+ 6. Дописує у `items` пункти для кожного `lintExtraKey` за тим самим шаблоном.
59
+ 7. Додає три фіксовані хвостові пункти:
60
+ - `- **Оновити правила та AGENTS.md** (після змін у правилах/шаблоні CLI): \`npx @nitra/cursor\``
61
+ - `- **Перевірки правил (programmatic)**: \`npx @nitra/cursor fix\``
62
+ - `- **knip (невикористані залежності та експорти)**: \`bunx knip\``
63
+ 8. Повертає `items`.
64
+ - **Контракт для споживача**: масив гарантовано непорожній (мінімум 4 елементи: `bun i` + 3 хвостові), навіть якщо `package.json` відсутній або порожній. Усі рядки вже містять Markdown-формат та зворотні апострофи навколо команд.
65
+ - **Side effects**: тільки опосередковані через `readPackageScripts` (читання `package.json`). Сама функція чиста відносно власних аргументів і повертає новий масив.
66
+
67
+ ## Константи
68
+
69
+ - `PACKAGE_NAME = '@nitra/cursor'` — npm-ім'я CLI, що інтерполюється у хвостові пункти.
70
+ - `AGENTS_MD = 'AGENTS.md'` — ім'я файлу, на який посилається пункт «Оновити правила та AGENTS.md».
71
+ - `SCRIPT_KEYS_ORDER` — JSDoc-кастований до `const` масив із 10 ключів скриптів. Визначає фіксований порядок виводу відомих скриптів. Порядок:
72
+ 1. `test`
73
+ 2. `lint`
74
+ 3. `lint-js`
75
+ 4. `lint-text`
76
+ 5. `lint-ga`
77
+ 6. `lint-k8s`
78
+ 7. `lint-docker`
79
+ 8. `start`
80
+ 9. `dev`
81
+ 10. `build`
82
+
83
+ ## Залежності
84
+
85
+ Усі імпорти — з вбудованих модулів Node.js (`node:`-префіксовані). Жодних сторонніх npm-залежностей.
86
+
87
+ | Імпорт | Джерело | Використання |
88
+ | ------------ | ------------------ | -------------------------------------------------------------------------- |
89
+ | `existsSync` | `node:fs` | Швидка синхронна перевірка наявності `package.json` перед спробою читання. |
90
+ | `readFile` | `node:fs/promises` | Асинхронне читання `package.json` у UTF-8. |
91
+ | `join` | `node:path` | Побудова крос-платформового шляху `projectRoot + 'package.json'`. |
92
+
93
+ Глобальні API: `JSON.parse`, `Object.keys`, `Array.prototype.filter`, `Array.prototype.toSorted` (Node ≥ 20), `String.prototype.localeCompare`, `String.prototype.startsWith`, `Set` (`add`, `has`).
94
+
95
+ Вимога рантайму: Node.js з підтримкою ESM (`import`), `node:`-префіксу та `Array.prototype.toSorted` (Node ≥ 20). Файл — ESM (`.mjs`).
96
+
97
+ ## Потік виконання / Використання
98
+
99
+ Типовий сценарій інтеграції в CLI `@nitra/cursor` (генерація `AGENTS.md` з Mustache-шаблону):
100
+
101
+ 1. CLI отримує `projectRoot` (`process.cwd()` або переданий шлях до репозиторію користувача).
102
+ 2. Викликає `buildAgentsCommandBulletItems(projectRoot)` для отримання масиву елементів секції `commands`.
103
+ 3. Передає результат у функцію розкриття Mustache-секцій (наприклад, `expandMustacheSection(template, 'commands', items)`), яка робить ітерацію по `{{#commands}} {{name}} {{/commands}}` у `AGENTS.template.md`.
104
+ 4. Отриманий Markdown записується у `AGENTS.md` у корені цільового репозиторію.
105
+
106
+ Приклад використання (псевдокод):
107
+
108
+ ```js
109
+ import { buildAgentsCommandBulletItems } from './build-agents-commands.mjs'
110
+
111
+ const items = await buildAgentsCommandBulletItems(process.cwd())
112
+ // items: [
113
+ // { name: '- **Залежності**: `bun i`' },
114
+ // { name: '- **test**: `bun run test`' }, // якщо є у package.json
115
+ // { name: '- **lint**: `bun run lint`' }, // якщо є у package.json
116
+ // ...
117
+ // { name: '- **lint-rego**: `bun run lint-rego`' }, // алфавітний fallback для lint-*
118
+ // { name: '- **Оновити правила та AGENTS.md** (...): `npx @nitra/cursor`' },
119
+ // { name: '- **Перевірки правил (programmatic)**: `npx @nitra/cursor fix`' },
120
+ // { name: '- **knip (невикористані залежності та експорти)**: `bunx knip`' }
121
+ // ]
122
+ ```
123
+
124
+ Граничні випадки:
125
+
126
+ - **`package.json` відсутній** — повертається список лише з `bun i` + 3 хвостових пунктів (4 елементи).
127
+ - **`package.json` некоректний (битий JSON, IO-помилка)** — те саме, що й при відсутності: виняток глушиться, повертається мінімум.
128
+ - **`scripts` відсутній/не об'єкт** — те саме: мінімум 4 елементи.
129
+ - **Скрипт визначений у `package.json` як порожній рядок** — пропускається (умова `scripts[key].length > 0`).
130
+ - **Скрипт визначений не рядком (число/масив/object)** — пропускається (умова `typeof scripts[key] === 'string'`).
131
+ - **Додаткові `lint-*` ключі**, відсутні у `SCRIPT_KEYS_ORDER` (наприклад, `lint-rego`, `lint-vue`, `lint-style`), будуть автоматично додані у відсортованому порядку після основного блоку.
132
+ - **Дублювання** виключено: `added: Set<string>` гарантує, що один і той самий ключ не з'явиться двічі (особливо актуально для `lint-*`, які присутні і в `SCRIPT_KEYS_ORDER`, і в загальному переліку).
133
+
134
+ ## Rebuild Test
135
+
136
+ Сценарій ручної верифікації (за припущенням, що файл імпортується з тестового скрипта):
137
+
138
+ 1. Підготувати тимчасову теку з мінімальним `package.json`:
139
+
140
+ ```json
141
+ {
142
+ "name": "demo",
143
+ "scripts": {
144
+ "test": "bun test",
145
+ "lint": "eslint .",
146
+ "lint-rego": "regal lint",
147
+ "dev": "vite",
148
+ "empty": ""
149
+ }
150
+ }
151
+ ```
152
+
153
+ 2. Виконати:
154
+
155
+ ```js
156
+ import { buildAgentsCommandBulletItems } from '/абсолютний/шлях/до/build-agents-commands.mjs'
157
+ const items = await buildAgentsCommandBulletItems('/абсолютний/шлях/до/тимчасової/теки')
158
+ console.log(items)
159
+ ```
160
+
161
+ 3. Очікуваний результат (за порядком):
162
+ 1. `- **Залежності**: \`bun i\``
163
+ 2. `- **test**: \`bun run test\``(із`SCRIPT_KEYS_ORDER`)
164
+ 3. `- **lint**: \`bun run lint\``(із`SCRIPT_KEYS_ORDER`)
165
+ 4. `- **dev**: \`bun run dev\``(із`SCRIPT_KEYS_ORDER`)
166
+ 5. `- **lint-rego**: \`bun run lint-rego\``(fallback`lint-\*`, відсортовано)
167
+ 6. `- **Оновити правила та AGENTS.md** (після змін у правилах/шаблоні CLI): \`npx @nitra/cursor\``
168
+ 7. `- **Перевірки правил (programmatic)**: \`npx @nitra/cursor fix\``
169
+ 8. `- **knip (невикористані залежності та експорти)**: \`bunx knip\``
170
+
171
+ 4. Перевірити, що:
172
+ - Скрипт `empty` (порожнє значення) пропущено.
173
+ - Ключі з `SCRIPT_KEYS_ORDER`, яких немає у `scripts`, не з'являються.
174
+ - `lint-rego` (не з основного списку) додано у блоці fallback, відсортовано.
175
+ - Жоден ключ не дублюється.
176
+
177
+ 5. Окремо перевірити шлях без `package.json`:
178
+ - Викликати `buildAgentsCommandBulletItems('/неіснуючий/шлях')` або теку без `package.json`.
179
+ - Очікувати рівно 4 елементи: `bun i`, `npx @nitra/cursor`, `npx @nitra/cursor fix`, `bunx knip`.
180
+
181
+ 6. Окремо перевірити битий JSON:
182
+ - Покласти `package.json` зі вмістом `{ "scripts":` (синтаксична помилка).
183
+ - Очікувати ті самі 4 мінімальні елементи (виняток зглушено всередині `readPackageScripts`).
@@ -0,0 +1,153 @@
1
+ # `cli-entry.mjs`
2
+
3
+ ## Огляд
4
+
5
+ Модуль `npm/scripts/cli-entry.mjs` — невеликий утилітарний ESM-модуль (Node.js, `.mjs`), який дає можливість іншому модулю-caller'у з'ясувати, **чи запущено його як точку входу CLI** (тобто прямим викликом `node my-script.mjs` або через `bin`-shim з `package.json`), **чи його просто імпортували** з іншого модуля (наприклад, з юніт-тестів, з фасадного CLI-агрегатора, з devtools).
6
+
7
+ Це класична Node.js-задача: ESM-модулі не мають `require.main === module` ідіоми CommonJS, і canonical-альтернативою служить порівняння `import.meta.url` (URL поточного файлу-модуля) із `process.argv[1]` (шлях, переданий Node.js при запуску). Модуль інкапсулює цю перевірку в одну іменовану функцію `isRunAsCli(metaUrl)` і вирішує три типові проблеми «з полів»:
8
+
9
+ 1. **`import.meta` лексично прив'язаний до файлу, де записаний.** Якщо помістити `import.meta.url` усередину helper-функції тут, у `cli-entry.mjs`, то будь-який модуль, що викличе helper, отримає URL **цього** helper-файлу, а не URL caller-модуля. Тому функція **обов'язково** приймає `metaUrl` як аргумент від caller'а.
10
+ 2. **Symlink-розбіжності.** На macOS `/tmp` — це симлінка на `/private/tmp`; npm/pnpm-bin'и створюють shim-скрипти в `node_modules/.bin/*`, які часто є симлінками або обгортками; pnpm-style content-addressable links теж дають різні поверхневі шляхи. Без нормалізації naive-порівняння рядків дасть `false` навіть для коректного прямого запуску. Тому модуль обертає **обидві** сторони порівняння у `realpathSync()`.
11
+ 3. **Безпечні fallback'и.** Якщо `metaUrl` не передано, або `process.argv[1]` відсутній (наприклад, REPL, embedded-runtime), або `realpathSync` кидає (файл видалено / нема прав / шлях не існує) — функція повертає `false`, а не throw. Семантика: «не впевнені, що CLI → вважаємо, що не CLI».
12
+
13
+ Модуль не має side-effects на рівні top-level (лише imports), не має CLI-shebang, не виконується самостійно. Це чисто бібліотечний експорт для consumer-модулів.
14
+
15
+ ## Експорти / API
16
+
17
+ | Експорт | Тип | Призначення |
18
+ | ------------ | ------------------------- | ---------------------------------------------------- |
19
+ | `isRunAsCli` | `function` (named export) | Перевірити, чи модуль-caller є точкою входу процесу. |
20
+
21
+ Default export відсутній.
22
+
23
+ ## Функції
24
+
25
+ ### `isRunAsCli(metaUrl)`
26
+
27
+ #### Сигнатура
28
+
29
+ ```js
30
+ export function isRunAsCli(metaUrl)
31
+ ```
32
+
33
+ Згідно JSDoc у файлі:
34
+
35
+ ```js
36
+ /**
37
+ * @param {string | URL} [metaUrl] `import.meta.url` модуля-caller'а. Без нього — завжди `false`.
38
+ * @returns {boolean} `true`, якщо файл, з якого передано `metaUrl`, є `process.argv[1]`.
39
+ */
40
+ ```
41
+
42
+ #### Параметри
43
+
44
+ - **`metaUrl`** — `string | URL | undefined`. Очікується значення `import.meta.url` модуля-caller'а. Це може бути або рядок виду `file:///abs/path/to/caller.mjs`, або об'єкт `URL` із тим самим протоколом `file:`. Параметр опціональний: якщо `undefined`/`null`/порожній рядок — функція коротко повертає `false`, не виконуючи решту перевірок.
45
+
46
+ #### Повертає
47
+
48
+ - **`boolean`**:
49
+ - `true` — якщо canonical-шлях файлу caller'а (отриманий через `realpathSync(fileURLToPath(metaUrl))`) **точно дорівнює** canonical-шляху `process.argv[1]` (отриманому через `realpathSync(resolve(entry))`).
50
+ - `false` у всіх інших випадках:
51
+ - `metaUrl` не передано (`!metaUrl`);
52
+ - `process.argv[1]` відсутній/порожній (`!entry`);
53
+ - будь-яка з операцій `realpathSync`/`fileURLToPath`/`resolve` кидає виняток (зокрема `ENOENT`, `EACCES`, некоректний URL-формат, не-`file:` протокол);
54
+ - canonical-шляхи відрізняються (звичайний випадок «модуль імпортовано, а не запущено»).
55
+
56
+ #### Алгоритм (покроково)
57
+
58
+ 1. Early-return: якщо `metaUrl` falsy — `false`.
59
+ 2. Зчитати `entry = process.argv[1]` (другий елемент `argv`: перший — шлях до бінарника `node`, другий — шлях до скрипта-точки входу).
60
+ 3. Early-return: якщо `entry` falsy — `false`.
61
+ 4. У `try`-блоці:
62
+ - Конвертувати `metaUrl` (URL вигляду `file://...`) у звичайний абсолютний шлях через `fileURLToPath(metaUrl)`.
63
+ - Нормалізувати симлінки в цьому шляху через `realpathSync(...)` → `callerPath`.
64
+ - Зробити `resolve(entry)` (на випадок, якщо `argv[1]` був відносним) і теж прогнати через `realpathSync(...)` → `entryPath`.
65
+ - Повернути `callerPath === entryPath` (строге порівняння рядків).
66
+ 5. У `catch` — повернути `false` (без логування, без re-throw). Будь-яка помилка трактується як «не CLI».
67
+
68
+ #### Side effects
69
+
70
+ - **Sync I/O:** `realpathSync` — це **синхронний** виклик файлової системи (двічі за виклик: для caller-шляху й для entry-шляху). Це блокує event loop на час stat-операцій. Для CLI-entry-перевірки, що зазвичай робиться один раз при старті — прийнятно.
71
+ - **Файли:** функція **тільки читає** метадані файлової системи (resolve симлінків). Не пише, не створює, не видаляє.
72
+ - **Глобальний стан:** не змінює `process`, `globalThis`, не реєструє listener'ів.
73
+ - **Кидає:** ніколи (внутрішній `try/catch` ловить усі винятки `realpathSync`).
74
+ - **Детермінізм:** результат залежить від (a) поточного `process.argv[1]`, (b) існування й canonical-шляху обох файлів у ФС на момент виклику. Те саме `metaUrl` за різних `argv[1]` дасть різні відповіді.
75
+
76
+ ## Залежності
77
+
78
+ ### Зовнішні (Node.js core, без npm-залежностей)
79
+
80
+ - **`node:fs`** → `realpathSync` — синхронне розкриття симлінків і нормалізація шляху до canonical-форми.
81
+ - **`node:path`** → `resolve` — приведення `process.argv[1]` до абсолютного шляху (на випадок відносного шляху в `argv[1]`).
82
+ - **`node:url`** → `fileURLToPath` — конвертація URL-форми `file://...` (як у `import.meta.url`) у platform-native шлях ФС (включно з обробкою percent-encoding і Windows-шляхів виду `file:///C:/...`).
83
+
84
+ Усі імпорти — з prefix-формою `node:` (best practice для ESM, явно вказує на built-in-модулі й виключає колізії з npm-пакетами).
85
+
86
+ ### Внутрішні (npm/scripts/...)
87
+
88
+ Немає. Модуль не імпортує жодного локального файлу — чиста утиліта поверх Node.js core.
89
+
90
+ ### Зворотні залежності (хто може його використовувати)
91
+
92
+ Будь-який ESM-скрипт у `npm/scripts/**`, який має «двоїсту» природу: експортує функціонал для тестів/інших модулів **і** може бути запущений напряму як CLI. Типовий патерн:
93
+
94
+ ```js
95
+ import { isRunAsCli } from './cli-entry.mjs' // або відносний шлях
96
+
97
+ export async function runCli(argv) {
98
+ // ...
99
+ }
100
+
101
+ if (isRunAsCli(import.meta.url)) {
102
+ await runCli(process.argv.slice(2))
103
+ }
104
+ ```
105
+
106
+ ## Потік виконання / Використання
107
+
108
+ ### Базовий use-case (типовий CLI-скрипт)
109
+
110
+ ```js
111
+ // my-tool.mjs
112
+ import { isRunAsCli } from '../scripts/cli-entry.mjs'
113
+
114
+ export function doWork(args) {
115
+ /* ... */
116
+ }
117
+
118
+ if (isRunAsCli(import.meta.url)) {
119
+ // Гарантовано: цей файл запущено як `node my-tool.mjs ...`
120
+ // або через bin-shim з package.json.
121
+ const result = doWork(process.argv.slice(2))
122
+ process.exit(result.exitCode ?? 0)
123
+ }
124
+ ```
125
+
126
+ Коли той самий `my-tool.mjs` імпортується в юніт-тесті (`import { doWork } from '.../my-tool.mjs'`), `process.argv[1]` вказуватиме на runner тестів (наприклад, `bun test` або `vitest`), не на `my-tool.mjs`, тому `isRunAsCli(import.meta.url)` поверне `false` і блок CLI-запуску не виконається. Це core-патерн testability для ESM CLI.
127
+
128
+ ### Сценарії, у яких функція повертає `false`
129
+
130
+ 1. **Caller не передав `metaUrl`** — `isRunAsCli()` без аргументу. Логічно: helper не може дізнатися caller'а без явної передачі `import.meta.url`.
131
+ 2. **`process.argv[1]` відсутній** — REPL (`node` без аргументу), embedded-runtime, runtime'и без `argv`.
132
+ 3. **Модуль імпортовано іншим модулем** — `argv[1]` указує на інший файл, canonical-шляхи різні.
133
+ 4. **Файл-caller видалено / нема прав** — `realpathSync` кидає → `catch` → `false`.
134
+ 5. **`metaUrl` не є валідним `file:`-URL** — `fileURLToPath` кидає → `catch` → `false`. Наприклад, `data:`-URL, HTTP-loader-URL, або синтаксично битий рядок.
135
+
136
+ ### Сценарії, у яких функція повертає `true`
137
+
138
+ 1. **Прямий запуск:** `node /abs/path/to/my-tool.mjs` — `argv[1] === '/abs/path/to/my-tool.mjs'`, `metaUrl === 'file:///abs/path/to/my-tool.mjs'`. Після canonicalization — рівні.
139
+ 2. **Запуск через bin-shim:** `npx my-tool` або `bun my-tool` (якщо `package.json` має `"bin": { "my-tool": "./my-tool.mjs" }`). `argv[1]` указує на shim у `node_modules/.bin/`, який є симлінкою на `my-tool.mjs`. `realpathSync` розкриває симлінку, шляхи стають однакові.
140
+ 3. **Запуск з відносним шляхом:** `node ./my-tool.mjs` із CWD = `/abs/path/to/`. `resolve(entry)` приведе до `/abs/path/to/my-tool.mjs`, потім `realpathSync` нормалізує.
141
+ 4. **macOS `/tmp` vs `/private/tmp`:** якщо файл лежить у `/tmp/x.mjs` (тобто `/private/tmp/x.mjs`), а `argv[1]` указує `/tmp/x.mjs` — `realpathSync` обидва зведе до `/private/tmp/x.mjs`.
142
+
143
+ ### Граничні випадки й нюанси
144
+
145
+ - **Windows.** `fileURLToPath` коректно обробляє `file:///C:/...` → `C:\\...`. `realpathSync` працює і на Windows-junction'ах. Функція кросплатформенна.
146
+ - **Workers/child processes.** У worker-thread `process.argv` зазвичай той самий, що в parent (якщо не змінено через `argv`-опцію `Worker`). У child-process через `spawn('node', ['child.mjs'])` `argv[1]` буде `child.mjs` — функція працює очікувано.
147
+ - **Bun.** Bun підтримує ту саму ESM-семантику `import.meta.url` й `process.argv`. Модуль ідентично працює під Bun.
148
+ - **`node --experimental-loader` / custom loaders.** `import.meta.url` залишається `file:`-URL для звичайних модулів. Якщо loader переписує URL на не-`file:` схему — `fileURLToPath` кине, `catch` поверне `false`. Безпечна деградація.
149
+ - **Без розширення / з різним розширенням.** Якщо `argv[1]` без `.mjs`, а Node.js резолвить його з extension lookup — `realpathSync` повертає реальний файл із розширенням, тому порівняння лишається коректним.
150
+
151
+ ### Rebuild test
152
+
153
+ Reading через `import { isRunAsCli } from './cli-entry.mjs'`, виклик `isRunAsCli(import.meta.url)` із caller-скрипта, прогон у двох режимах (прямий запуск `node caller.mjs` → очікуємо `true`; імпорт із іншого скрипта `node other.mjs` → очікуємо `false`) відтворюють поведінку 1-в-1. Жодних схованих залежностей від env-змінних чи глобального стану модуль не має; усі вхідні дані — `metaUrl` (параметр) і `process.argv[1]` (стандарт Node.js).
@@ -0,0 +1,177 @@
1
+ # coverage-fix.mjs
2
+
3
+ ## Огляд
4
+
5
+ Файл `npm/scripts/coverage-fix.mjs` — це ESM-модуль, який реалізує fix-режим команди `n-cursor coverage --fix`. Його призначення — автоматично запустити агента Claude Code (через офіційний SDK `@anthropic-ai/claude-agent-sdk`) і доручити йому дописати unit-тести, які «вб'ють» вцілілих мутантів, знайдених попереднім прогоном Stryker.
6
+
7
+ Модуль не виконує власних мутацій коду й не аналізує покриття самостійно — він є тонким адаптером між інвентарем вцілілих мутантів (структури `SurvivedFileGroup[]`, отримані ззовні) і LLM-агентом: формує rich-промпт з контекстом ±3 рядки навколо кожного мутанта, опційно додає приклад уже існуючого тесту, після чого ітерує по стрімінгу повідомлень агента та виводить його текстові реплики у `stdout`.
8
+
9
+ Файл задумано як єдину публічну точку входу для CLI-флоу `coverage --fix`: викликається з оркестратора `n-cursor coverage`, який групує мутанти Stryker по source-файлах і передає сюди готову структуру разом з абсолютним шляхом до кореня проєкту.
10
+
11
+ ## Експорти / API
12
+
13
+ | Експорт | Тип | Призначення |
14
+ | ------------------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
15
+ | `fixSurvivedMutants(survived, projectRoot)` | `async function` | Публічна функція. Запускає агента, проганяє промпт через `@anthropic-ai/claude-agent-sdk` і друкує текстові повідомлення в `stdout`. |
16
+
17
+ Внутрішні символи (не експортуються):
18
+
19
+ | Символ | Тип | Призначення |
20
+ | --------------------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------- |
21
+ | `buildFixPrompt(survived, projectRoot)` | `async function` | Формує текстовий rich-промпт для агента: список мутантів по файлах, контекст з source, приклади тестів, правила. |
22
+
23
+ JSDoc-типи, оголошені у файлі:
24
+
25
+ - `MutantDetail` — `{ line: number, col: number, mutantType: string, original: string, replacement: string }` — опис одного вцілілого мутанта.
26
+ - `SurvivedFileGroup` — `{ file: string, mutants: MutantDetail[], exampleTest: { testFile: string, code: string | null } | null, recommendationText: string | null }` — група мутантів, об'єднаних спільним source-файлом, разом з опційним прикладом існуючого тесту й текстовою рекомендацією.
27
+
28
+ ## Функції
29
+
30
+ ### `fixSurvivedMutants(survived, projectRoot)`
31
+
32
+ **Сигнатура:** `async function fixSurvivedMutants(survived: SurvivedFileGroup[], projectRoot: string): Promise<void>`
33
+
34
+ **Параметри:**
35
+
36
+ - `survived` — масив груп вцілілих мутантів, згрупованих по source-файлу. Кожна група містить шлях до файлу, перелік мутантів, опційний приклад тесту й опційну текстову рекомендацію.
37
+ - `projectRoot` — абсолютний шлях до кореня проєкту. Використовується одночасно як `cwd` для агента і як база для читання source-файлів при підготовці контексту в промпті.
38
+
39
+ **Що повертає:** `Promise<void>`. Корисний результат — побічний ефект: створені/оновлені тести у файловій системі (їх пише агент через інструменти `Edit`/`Bash`), а також лог у `stdout`.
40
+
41
+ **Алгоритм:**
42
+
43
+ 1. Обчислює загальну кількість мутантів: `survived.reduce((s, g) => s + g.mutants.length, 0)`.
44
+ 2. Якщо загалом 0 мутантів — друкує `✓ Всі мутанти вбиті — доповнення тестів не потрібне` і повертається (без запуску агента, без імпорту SDK).
45
+ 3. Викликає `buildFixPrompt(survived, projectRoot)` і отримує текст промпту.
46
+ 4. Друкує заголовок `🤖 coverage --fix: запускаю агента для N вцілілих мутантів...`.
47
+ 5. Робить **динамічний** `await import('@anthropic-ai/claude-agent-sdk')` (щоб не вимагати пакет у звичайному coverage-прогоні без `--fix`) і дістає функцію `query`.
48
+ 6. Запускає агента викликом `query({ prompt, options: { cwd: projectRoot, maxTurns: 20, allowedTools: ['Read', 'Edit', 'Bash'] } })`.
49
+ 7. Ітерує отриманий `AsyncIterable` через `for await (const msg of …)`. Для повідомлень з `msg.type === 'text'` друкує `msg.text` у `process.stdout` (без додаткового переносу рядка). Інші типи повідомлень (наприклад, tool_use, tool_result) — мовчазно ігноруються.
50
+ 8. Після завершення ітерації друкує фінальний `\n`.
51
+
52
+ **Опції агента:**
53
+
54
+ - `cwd: projectRoot` — поточна тека агента, від якої він читає файли й запускає команди.
55
+ - `maxTurns: 20` — обмеження на кількість turns у діалозі з моделлю, щоб уникнути нескінченних циклів.
56
+ - `allowedTools: ['Read', 'Edit', 'Bash']` — агент має право читати файли, редагувати їх (фактично — тест-файли, бо source заборонено правилом у промпті) і виконувати shell-команди (зокрема `bun test`).
57
+
58
+ **Side effects:**
59
+
60
+ - Запис у `process.stdout` (`console.log` + `process.stdout.write`).
61
+ - Динамічний імпорт зовнішнього пакету `@anthropic-ai/claude-agent-sdk` — у разі його відсутності `await import(…)` кине помилку (її обробка делегована викликачу).
62
+ - Мережеві виклики до API Anthropic (виконує сам SDK).
63
+ - Зміни у файловій системі проєкту: створення/редагування тест-файлів та потенційне виконання тестових команд агентом через інструмент `Bash`.
64
+
65
+ **Граничні випадки:**
66
+
67
+ - `totalMutants === 0` — рання терміновість без імпорту SDK.
68
+ - Помилка `import('@anthropic-ai/claude-agent-sdk')` (пакет не встановлений) — пробрасується нагору; цей файл її не ловить.
69
+ - Помилка з боку стріму `query(...)` — пробрасується нагору; final `\n` не друкується.
70
+
71
+ ### `buildFixPrompt(survived, projectRoot)` (internal)
72
+
73
+ **Сигнатура:** `async function buildFixPrompt(survived: SurvivedFileGroup[], projectRoot: string): Promise<string>`
74
+
75
+ **Параметри:** ті самі семантики, що у `fixSurvivedMutants`.
76
+
77
+ **Що повертає:** текст rich-промпту для агента.
78
+
79
+ **Алгоритм:**
80
+
81
+ 1. Готує пустий масив `sections`.
82
+ 2. Для кожної групи `{ file, mutants, exampleTest }`:
83
+ 1. Намагається прочитати source-файл `join(projectRoot, file)` у `utf8` і розбити на рядки. У разі помилки (`catch {}`) — `srcLines` лишається `[]`, і контекст просто не додасться.
84
+ 2. Для кожного мутанта в групі формує опис:
85
+ - `ctxStart = Math.max(0, m.line - 4)` — індекс початку контексту (0-based, бо рядки в редакторі 1-based; `m.line - 4` дає 3 рядки до мутанта).
86
+ - `ctxEnd = Math.min(srcLines.length, m.line + 3)` — індекс кінця (виключний), 3 рядки після мутанта.
87
+ - `context` — зріз `srcLines.slice(ctxStart, ctxEnd)`, де кожен рядок префіксується абсолютним номером `${ctxStart + i + 1}: ${l}` і з'єднується через `\n`.
88
+ - Опис мутанта — markdown-bullet з полями: рядок, колонка, тип мутації (`m.mutantType` у backticks), оригінал (`m.original`), вцілілий варіант (`m.replacement`), а нижче — fenced code block з контекстом (якщо контекст не порожній). Порожні елементи фільтруються через `.filter(Boolean)`.
89
+ 3. Якщо є `exampleTest.code` — додає окрему секцію `Приклад тесту з \`${exampleTest.testFile}\``з fenced`js`-блоком. Інакше `exampleSection` — порожній рядок.
90
+ 4. Заштовхує у `sections` рядок виду:
91
+ ```
92
+ ### `<file>`<exampleSection>
93
+ <mutantDescriptions>
94
+ ```
95
+ 3. Повертає одну склеєну рядкову відповідь, що складається з:
96
+ - Заголовка-завдання (4 рядки): «Твоє завдання — написати unit-тести…» з конкретною інструкцією: знайти/створити тест-файл, додати кейс, що провалиться при заміні коду на «вцілілий варіант».
97
+ - Розділу `## Вцілілі мутанти` зі склеєними `sections`.
98
+ - Розділу `## Правила`:
99
+ - «Не змінюй source-файли — лише test-файли.»
100
+ - «Використовуй той самий test-фреймворк, що вже в проєкті.»
101
+ - «Запусти `bun test` (або відповідну команду) після кожного файлу — переконайся, що 0 fail.»
102
+ - «Якщо мутант охоплений іншим новим тестом — не дублюй.»
103
+
104
+ **Side effects:** читання source-файлів через `fs/promises.readFile`. Помилки читання (відсутній файл, нечитабельний шлях) тихо проковтуються — функція гарантує, що повертає валідний промпт навіть для повністю відсутніх файлів (просто без контексту).
105
+
106
+ **Граничні випадки:**
107
+
108
+ - `srcLines.length === 0` — `context` стає порожнім рядком, fenced-блок з контекстом не додається до bullet.
109
+ - `m.line` поза межами файлу — `slice` поверне порожній зріз; жодного винятку.
110
+ - `exampleTest === null` або `exampleTest.code === null/undefined` — `exampleSection` залишається порожнім (через `?.code` optional chaining).
111
+ - `mutants.length === 0` у групі — група все одно потрапить у `sections` з заголовком і порожнім тілом (виклична сторона зазвичай не подає пусті групи, бо `fixSurvivedMutants` ще раніше відсіче випадок `totalMutants === 0`).
112
+
113
+ ## Залежності
114
+
115
+ **Зовнішні (npm):**
116
+
117
+ - `@anthropic-ai/claude-agent-sdk` — офіційний SDK Claude Code, експортує функцію `query()` з async-generator-інтерфейсом для стрімінгу повідомлень агента. Завантажується через **dynamic import**, виключно в момент виклику `fixSurvivedMutants` з ненульовим списком мутантів. Декларується у `dependencies` файлу `npm/package.json` (як зазначено у JSDoc-шапці модуля).
118
+
119
+ **Node.js stdlib:**
120
+
121
+ - `node:fs/promises` — використовується `readFile` для читання source-файлів при формуванні контексту в промпті.
122
+ - `node:path` — використовується `join` для зчеплення `projectRoot` з відносним шляхом файлу мутанта.
123
+
124
+ **Глобальні API середовища:**
125
+
126
+ - `console.log` — друк інформаційних рядків (старт/пропуск).
127
+ - `process.stdout.write` — потоковий друк reply-чанків агента (без auto-newline).
128
+
129
+ **Внутрішні залежності проєкту:**
130
+
131
+ - Модуль не імпортує інших файлів з `npm/scripts/` напряму, але формат вхідної структури `SurvivedFileGroup` визначається сусідніми скриптами в каталозі `npm/scripts/` (там, де живе оркестратор `coverage`, який збирає й групує мутантів Stryker і передає сюди готовий масив).
132
+
133
+ ## Потік виконання / Використання
134
+
135
+ Типовий ланцюжок:
136
+
137
+ 1. CLI `n-cursor coverage --fix` (зовнішня обгортка) запускає Stryker і збирає список вцілілих мутантів.
138
+ 2. Оркестратор групує мутантів по `file`, готує `exampleTest` (опційно) і викликає:
139
+
140
+ ```js
141
+ import { fixSurvivedMutants } from './npm/scripts/coverage-fix.mjs'
142
+
143
+ await fixSurvivedMutants(survived, projectRoot)
144
+ ```
145
+
146
+ 3. `fixSurvivedMutants`:
147
+ - Перевіряє, що мутанти взагалі є; якщо ні — друкує success-повідомлення і виходить.
148
+ - Будує промпт через `buildFixPrompt` (читання source-файлів для контексту відбувається саме тут).
149
+ - Динамічно імпортує `@anthropic-ai/claude-agent-sdk`.
150
+ - Стрімить агента з обмеженнями: `cwd = projectRoot`, до 20 turns, інструменти `Read | Edit | Bash`.
151
+ - Друкує всі текстові реплики у `stdout` у реальному часі.
152
+ 4. Агент за правилами промпту читає source-файли (лише для розуміння), знаходить/створює відповідні test-файли, дописує кейси, запускає `bun test` і повторює, доки `maxTurns` або модель не вирішить, що готово.
153
+ 5. Після завершення стріму `fixSurvivedMutants` дописує фінальний `\n` і повертає `void`.
154
+
155
+ **Принципи дизайну, що випливають із коду:**
156
+
157
+ - **Lazy SDK loading.** Імпорт SDK — динамічний, щоб звичайний `coverage` (без `--fix`) не вимагав встановленого `@anthropic-ai/claude-agent-sdk` і не платив за його завантаження.
158
+ - **Ізоляція знань про Stryker.** Цей файл нічого не знає про сам Stryker — він працює з уже нормалізованою структурою `SurvivedFileGroup[]`; уся інтеграція зі Stryker лежить в іншому модулі-оркестраторі.
159
+ - **No source mutation by agent.** Жорстке правило в промпті («Не змінюй source-файли — лише test-файли») задає очікувану поведінку агента; додатково обмежений набір інструментів (`Read | Edit | Bash`) дає змогу йому редагувати тести й запускати тестову команду, але не лазити в систему по непотрібному.
160
+ - **Стрімінг тільки текстових повідомлень.** Інші типи (наприклад, tool_use / tool_result) свідомо не рендеряться, щоб лог у консолі лишався читабельним для людини.
161
+
162
+ **Приклад вхідної структури:**
163
+
164
+ ```js
165
+ const survived = [
166
+ {
167
+ file: 'src/foo.js',
168
+ mutants: [{ line: 42, col: 10, mutantType: 'ConditionalExpression', original: 'a > b', replacement: 'a >= b' }],
169
+ exampleTest: { testFile: 'src/foo.test.js', code: "test('foo', () => { /* ... */ })" },
170
+ recommendationText: null
171
+ }
172
+ ]
173
+
174
+ await fixSurvivedMutants(survived, '/abs/path/to/project')
175
+ ```
176
+
177
+ > Примітка: поле `recommendationText` присутнє в JSDoc-типі `SurvivedFileGroup`, але в поточній реалізації `coverage-fix.mjs` воно **не** використовується при формуванні промпту — резервоване для майбутнього розширення (наприклад, додавання текстових порад поряд із прикладом тесту).