@nitra/cursor 3.22.0 → 3.23.1

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 (229) hide show
  1. package/.pi-template/extensions/n-cursor-adr/docs/index.md +181 -0
  2. package/AGENTS.template.md +4 -0
  3. package/CHANGELOG.md +37 -3
  4. package/bin/docs/n-cursor.md +636 -0
  5. package/bin/docs/rename-yaml-extensions.md +207 -0
  6. package/bin/n-cursor.js +30 -3
  7. package/package.json +1 -1
  8. package/rules/abie/docs/fix.md +18 -0
  9. package/rules/abie/js/docs/applies.md +26 -0
  10. package/rules/abie/js/docs/env_dns.md +32 -0
  11. package/rules/abie/js/docs/firebase_hosting.md +23 -0
  12. package/rules/abie/js/docs/hc_pairing.md +35 -0
  13. package/rules/abie/js/docs/ua_http_route.md +28 -0
  14. package/rules/abie/js/docs/ua_node_selector.md +28 -0
  15. package/rules/abie/lib/docs/enabled.md +29 -0
  16. package/rules/abie/lib/docs/env-dns.md +35 -0
  17. package/rules/abie/lib/docs/hc-yaml.md +33 -0
  18. package/rules/abie/lib/docs/http-route.md +44 -0
  19. package/rules/abie/lib/docs/k8s-tree.md +40 -0
  20. package/rules/abie/lib/docs/kustomization-patches.md +47 -0
  21. package/rules/abie/lib/docs/overlay-paths.md +38 -0
  22. package/rules/abie/lib/docs/yaml.md +29 -0
  23. package/rules/adr/docs/fix.md +148 -0
  24. package/rules/adr/js/docs/hooks.md +259 -0
  25. package/rules/bun/docs/fix.md +156 -0
  26. package/rules/bun/js/docs/layout.md +393 -0
  27. package/rules/capacitor/docs/fix.md +121 -0
  28. package/rules/capacitor/js/docs/platforms.md +295 -0
  29. package/rules/changelog/changelog.mdc +4 -2
  30. package/rules/changelog/docs/fix.md +174 -0
  31. package/rules/changelog/js/consistency.mjs +114 -13
  32. package/rules/changelog/js/docs/consistency.md +387 -0
  33. package/rules/changelog/lib/docs/package-manifest.md +210 -0
  34. package/rules/ci4/docs/fix.md +179 -0
  35. package/rules/ci4/js/docs/marksman_config.md +128 -0
  36. package/rules/docker/docker.mdc +8 -3
  37. package/rules/docker/docs/fix.md +171 -0
  38. package/rules/docker/js/docs/lint.md +258 -0
  39. package/rules/docker/lib/docs/docker-hadolint.md +184 -0
  40. package/rules/docker/lib/docs/docker-mirror.md +247 -0
  41. package/rules/docker/lib/docs/docker-native-addon.md +170 -0
  42. package/rules/docker/lib/docs/docker-nginx-user.md +219 -0
  43. package/rules/docker/lint/docs/lint.md +193 -0
  44. package/rules/efes/docs/fix.md +203 -0
  45. package/rules/feedback/docs/fix.md +140 -0
  46. package/rules/flow/docs/fix.md +152 -0
  47. package/rules/ga/docs/fix.md +158 -0
  48. package/rules/ga/js/docs/lint.md +100 -0
  49. package/rules/ga/js/docs/workflows.md +217 -0
  50. package/rules/ga/lint/docs/lint.md +209 -0
  51. package/rules/ga/policy/clean_merged_branch/clean_merged_branch.rego +11 -2
  52. package/rules/ga/policy/clean_merged_branch/template/clean-merged-branch.yml.snippet.yml +3 -4
  53. package/rules/graphql/docs/fix.md +126 -0
  54. package/rules/graphql/js/docs/tooling.md +264 -0
  55. package/rules/graphql/lib/docs/graphql-gql-scan.md +219 -0
  56. package/rules/hasura/docs/fix.md +120 -0
  57. package/rules/hasura/hasura.mdc +14 -0
  58. package/rules/hasura/js/docs/internal_urls.md +326 -0
  59. package/rules/image-avif/docs/fix.md +132 -0
  60. package/rules/image-avif/js/docs/avif_generation.md +241 -0
  61. package/rules/image-compress/docs/fix.md +150 -0
  62. package/rules/image-compress/js/docs/package_setup.md +191 -0
  63. package/rules/js-bun-db/docs/fix.md +148 -0
  64. package/rules/js-bun-db/js/docs/safety.md +231 -0
  65. package/rules/js-bun-db/js-bun-db.mdc +42 -13
  66. package/rules/js-bun-db/lib/docs/bun-sql-scan.md +347 -0
  67. package/rules/js-bun-redis/docs/fix.md +123 -0
  68. package/rules/js-bun-redis/js/docs/imports.md +176 -0
  69. package/rules/js-bun-redis/lib/docs/redis-imports.md +223 -0
  70. package/rules/js-lint/docs/fix.md +117 -0
  71. package/rules/js-lint/js/docs/lint.md +250 -0
  72. package/rules/js-lint/js/docs/tooling.md +348 -0
  73. package/rules/js-lint/js/docs/utils_imports.md +207 -0
  74. package/rules/js-lint-ci/docs/fix.md +154 -0
  75. package/rules/js-lint-ci/js/docs/lint.md +144 -0
  76. package/rules/js-mssql/docs/fix.md +128 -0
  77. package/rules/js-mssql/js/docs/deps.md +263 -0
  78. package/rules/js-mssql/lib/docs/mssql-pool-scan.md +367 -0
  79. package/rules/js-run/docs/fix.md +144 -0
  80. package/rules/js-run/js/docs/runtime.md +388 -0
  81. package/rules/js-run/lib/docs/bunyan-imports.md +117 -0
  82. package/rules/js-run/lib/docs/check-env-scan.md +433 -0
  83. package/rules/js-run/lib/docs/conn-file-rules.md +300 -0
  84. package/rules/js-run/lib/docs/conn-imports-scan.md +204 -0
  85. package/rules/js-run/lib/docs/promise-settimeout-scan.md +326 -0
  86. package/rules/k8s/docs/fix.md +129 -0
  87. package/rules/k8s/js/docs/manifests.md +344 -0
  88. package/rules/k8s/js/manifests.mjs +6 -2
  89. package/rules/k8s/k8s.mdc +4 -2
  90. package/rules/k8s/lint/docs/lint.md +411 -0
  91. package/rules/k8s/policy/network_policy/template/deployment.snippet.yaml +2 -0
  92. package/rules/k8s/policy/network_policy/template/stateful-set.snippet.yaml +2 -0
  93. package/rules/nginx-default-tpl/docs/fix.md +124 -0
  94. package/rules/nginx-default-tpl/js/docs/template.md +378 -0
  95. package/rules/npm-module/docs/fix.md +98 -0
  96. package/rules/npm-module/js/docs/package_structure.md +274 -0
  97. package/rules/npm-module/js/docs/rule_meta.md +137 -0
  98. package/rules/npm-module/js/docs/skill_meta.md +190 -0
  99. package/rules/php/docs/fix.md +107 -0
  100. package/rules/php/js/docs/tooling.md +152 -0
  101. package/rules/php/lint/docs/lint.md +215 -0
  102. package/rules/python/docs/fix.md +163 -0
  103. package/rules/python/js/docs/applies.md +108 -0
  104. package/rules/python/js/docs/tooling.md +153 -0
  105. package/rules/python/lint/docs/lint.md +322 -0
  106. package/rules/rego/docs/fix.md +121 -0
  107. package/rules/rego/js/docs/applies.md +174 -0
  108. package/rules/rego/js/docs/lint.md +118 -0
  109. package/rules/rego/lint/docs/lint.md +204 -0
  110. package/rules/release/docs/change.md +185 -0
  111. package/rules/release/docs/fix.md +119 -0
  112. package/rules/release/docs/release.md +222 -0
  113. package/rules/release/lib/docs/aggregate.md +246 -0
  114. package/rules/release/lib/docs/change-file.md +200 -0
  115. package/rules/release/lib/docs/fallback.md +203 -0
  116. package/rules/rust/docs/fix.md +129 -0
  117. package/rules/rust/js/docs/applies.md +140 -0
  118. package/rules/rust/lib/docs/has-cargo-toml.md +130 -0
  119. package/rules/security/docs/fix.md +86 -0
  120. package/rules/security/js/docs/lint.md +171 -0
  121. package/rules/security/js/docs/sample_secret.md +190 -0
  122. package/rules/security/js/docs/trufflehog.md +137 -0
  123. package/rules/security/js/lint.mjs +9 -1
  124. package/rules/style-lint/docs/fix.md +155 -0
  125. package/rules/style-lint/js/docs/lint.md +184 -0
  126. package/rules/style-lint/js/docs/tooling.md +194 -0
  127. package/rules/tauri/docs/fix.md +158 -0
  128. package/rules/tauri/js/docs/cargo_mutants_config.md +168 -0
  129. package/rules/tauri/js/docs/tooling.md +228 -0
  130. package/rules/test/coverage/coverage.mjs +15 -3
  131. package/rules/test/docs/fix.md +132 -0
  132. package/rules/test/js/data/stryker_config/docs/stryker-vue-macros-ignorer.md +138 -0
  133. package/rules/test/js/data/stryker_config/docs/stryker.config.baseline.md +134 -0
  134. package/rules/test/js/data/stryker_config/docs/stryker.config.vue.baseline.md +160 -0
  135. package/rules/test/js/data/vitest_config/docs/vitest.config.baseline.md +195 -0
  136. package/rules/test/js/docs/cargo_mutants_config.md +173 -0
  137. package/rules/test/js/docs/location.md +136 -0
  138. package/rules/test/js/docs/no-process-chdir.md +160 -0
  139. package/rules/test/js/docs/no-relative-fs-path.md +271 -0
  140. package/rules/test/js/docs/stryker_config.md +152 -0
  141. package/rules/test/js/docs/vitest-config-pool-forks.md +174 -0
  142. package/rules/text/docs/fix.md +118 -0
  143. package/rules/text/js/docs/forbidden-prettier.md +143 -0
  144. package/rules/text/js/docs/formatting.md +256 -0
  145. package/rules/text/js/docs/lint.md +122 -0
  146. package/rules/text/lint/docs/lint.md +220 -0
  147. package/rules/text/lint/docs/run-dotenv-linter.md +157 -0
  148. package/rules/text/lint/docs/run-shellcheck.md +212 -0
  149. package/rules/text/lint/docs/run-v8r.md +197 -0
  150. package/rules/vue/docs/fix.md +127 -0
  151. package/rules/vue/js/docs/packages.md +335 -0
  152. package/rules/vue/lib/docs/vue-forbidden-imports.md +261 -0
  153. package/rules/worktree/docs/fix.md +161 -0
  154. package/schemas/rule-meta.json +5 -1
  155. package/scripts/auto-rules.mjs +7 -4
  156. package/scripts/coverage-classify/docs/apply.md +202 -0
  157. package/scripts/coverage-classify/docs/cache.md +203 -0
  158. package/scripts/coverage-classify/docs/index.md +218 -0
  159. package/scripts/coverage-classify/docs/prompt.md +132 -0
  160. package/scripts/coverage-classify/docs/verdict-schema.md +169 -0
  161. package/scripts/coverage-fix-extract.mjs +122 -0
  162. package/scripts/coverage-fix.mjs +1 -1
  163. package/scripts/dispatcher/docs/graph.md +346 -0
  164. package/scripts/dispatcher/docs/index.md +236 -0
  165. package/scripts/dispatcher/docs/trace.md +296 -0
  166. package/scripts/dispatcher/index.mjs +1 -1
  167. package/scripts/dispatcher/lib/active.mjs +4 -8
  168. package/scripts/dispatcher/lib/commands.mjs +7 -11
  169. package/scripts/dispatcher/lib/docs/active.md +348 -0
  170. package/scripts/dispatcher/lib/docs/artifact.md +232 -0
  171. package/scripts/dispatcher/lib/docs/budget.md +167 -0
  172. package/scripts/dispatcher/lib/docs/capability.md +196 -0
  173. package/scripts/dispatcher/lib/docs/commands.md +210 -0
  174. package/scripts/dispatcher/lib/docs/events.md +182 -0
  175. package/scripts/dispatcher/lib/docs/executor.md +190 -0
  176. package/scripts/dispatcher/lib/docs/flow-lock.md +161 -0
  177. package/scripts/dispatcher/lib/docs/flow-resolve.md +267 -0
  178. package/scripts/dispatcher/lib/docs/gate.md +231 -0
  179. package/scripts/dispatcher/lib/docs/level.md +335 -0
  180. package/scripts/dispatcher/lib/docs/plan-panel.md +181 -0
  181. package/scripts/dispatcher/lib/docs/plan.md +200 -0
  182. package/scripts/dispatcher/lib/docs/planner.md +269 -0
  183. package/scripts/dispatcher/lib/docs/review.md +255 -0
  184. package/scripts/dispatcher/lib/docs/reviewer.md +240 -0
  185. package/scripts/dispatcher/lib/docs/snapshot.md +247 -0
  186. package/scripts/dispatcher/lib/docs/spec.md +203 -0
  187. package/scripts/dispatcher/lib/docs/state-store.md +303 -0
  188. package/scripts/dispatcher/lib/docs/subagent-runner.md +173 -0
  189. package/scripts/dispatcher/lib/executor.mjs +6 -1
  190. package/scripts/dispatcher/lib/flow-resolve.mjs +3 -1
  191. package/scripts/dispatcher/lib/level.mjs +29 -3
  192. package/scripts/dispatcher/lib/review.mjs +1 -1
  193. package/scripts/dispatcher/lib/subagent-runner.mjs +5 -3
  194. package/scripts/docs/auto-rules.md +376 -0
  195. package/scripts/docs/auto-skills.md +173 -0
  196. package/scripts/docs/build-agents-commands.md +183 -0
  197. package/scripts/docs/cli-entry.md +153 -0
  198. package/scripts/docs/coverage-fix.md +177 -0
  199. package/scripts/docs/ensure-nitra-cursor-dev-dependencies.md +189 -0
  200. package/scripts/lib/changed-files.mjs +4 -1
  201. package/scripts/lib/docs/changed-files.md +149 -0
  202. package/scripts/lib/docs/check-mdc-template-refs.md +222 -0
  203. package/scripts/lib/docs/check-reporter.md +175 -0
  204. package/scripts/lib/docs/discover-check-rules-from-cursor.md +157 -0
  205. package/scripts/lib/docs/discover-checkable-rules.md +165 -0
  206. package/scripts/lib/docs/ensure-tool.md +254 -0
  207. package/scripts/lib/docs/generated-markdown.md +275 -0
  208. package/scripts/lib/docs/gha-workflow.md +326 -0
  209. package/scripts/lib/docs/inline-template-links.md +303 -0
  210. package/scripts/lib/docs/list-rule-ids.md +156 -0
  211. package/scripts/lib/docs/load-cursor-config.md +147 -0
  212. package/scripts/lib/docs/mirror-parity.md +167 -0
  213. package/scripts/lib/worktree.mjs +26 -0
  214. package/scripts/worktree-cli.mjs +12 -2
  215. package/skills/coverage-fix/SKILL.md +34 -45
  216. package/skills/docgen/SKILL.md +44 -23
  217. package/skills/docgen/bench/etalon/firebase_hosting.md +19 -0
  218. package/skills/docgen/bench/etalon/k8s-tree.md +24 -0
  219. package/skills/docgen/bench/etalon/overlay-paths.md +24 -0
  220. package/skills/docgen/js/docgen-ignore.mjs +54 -0
  221. package/skills/docgen/js/docgen-scan.mjs +37 -21
  222. package/skills/llm-patch/SKILL.md +23 -2
  223. package/skills/start-check/SKILL.md +26 -53
  224. package/skills/start-check/js/check.mjs +211 -0
  225. package/skills/taze/SKILL.md +9 -3
  226. package/skills/taze/js/diff.mjs +154 -0
  227. package/types/bin/n-cursor.d.ts +1 -1
  228. package/skills/fix-tests/SKILL.md +0 -119
  229. package/skills/fix-tests/meta.json +0 -1
@@ -21,80 +21,53 @@ description: >-
21
21
 
22
22
  ### 1. Зібрати список воркспейсів
23
23
 
24
- Прочитай поле `workspaces` з кореневого `package.json`. Воно може містити glob-патерни (`packages/*`, `apps/*`) розгорни кожен у конкретні директорії. Для кожної директорії прочитай її `package.json`.
25
-
26
- ### 2. Відфільтрувати воркспейси зі `start`-скриптом
27
-
28
- Воркспейс перевіряється, лише якщо в його `package.json` є `scripts.start`. Решту — пропусти й познач у звіті як `SKIP (немає start)`.
29
-
30
- ### 3. Класифікувати `start` і зафіксувати стан репо
31
-
32
- Скіл прогоняє **кожен** воркспейс зі `start` — запуск не пропускається. Але `start` буває двох типів, і це впливає і на трактування коду виходу, і на ризик побічних ефектів:
33
-
34
- - **Запуск сервера/застосунку** (`vite`, `next dev`, `nuxt dev`, `node`/`bun <server>`, `nodemon` тощо) — успіх визначається за живучістю процесу.
35
- - **CLI/разова дія** (зокрема ті, що мутують репо — sync, генерація коду, міграції, розгортання, перезапис конфігів) — успіх визначається за кодом виходу; такий `start` може **виконати реальну роботу** й лишити зміни в репозиторії.
36
-
37
- Щоб прогін мутаційного `start` не лишив сміття, зафіксуй стан робочого дерева **перед** запусками:
24
+ > **Не парси `package.json`/glob вручну.** Розгортання воркспейсів і класифікацію `start` несе CLI.
38
25
 
39
26
  ```bash
40
- git status --porcelain > /tmp/n-start-check.before
27
+ n-cursor start-check scan
41
28
  ```
42
29
 
43
- Цей знімокбаза, щоб відкотити побічні ефекти (крок 5).
44
-
45
- ### 4. Прогнати `start` послідовно
30
+ Друкує JSON `[{ workspace, name, hasStart, startCmd, type }]` для root + усіх воркспейсів. `type` `server` (dev-сервер/демон) чи `cli` (разова дія). Воркспейси з `hasStart:false` — `SKIP (немає start)` у звіті; решту прогоняй послідовно (крок 2).
46
31
 
47
- Запускай воркспейси **по черзі, НЕ паралельно**: dev-сервери конфліктують за портами, а послідовний прогон дає однозначну причину краху.
32
+ ### 2. Прогнати `start` кожного воркспейсу по черзі
48
33
 
49
- `start` буває двох типів скіл має їх розрізняти:
34
+ > **Не запускай `perl alarm` і не інтерпретуй exit-коди вручну.** CLI запускає процес із grace-таймаутом (крос-платформно через `spawnSync`), класифікує OK/FAIL за типом і парсить лог.
50
35
 
51
- - **Довгий процес** (dev-сервер, демон): успіх = процес живий через grace-період без краху. Бажано дочекатися рядка готовності в логу (`ready`, `listening`, `Local:`, `started` тощо).
52
- - **Короткий процес** (CLI, разова дія): успіх = вихід із кодом `0`.
53
-
54
- macOS не має `timeout` як стандартної утиліти. Обмеж час через `perl` + `alarm`: `alarm` зводить SIGALRM, а `exec` зберігає той самий PID, тож таймер спрацьовує на справжньому процесі й знімає його після grace-періоду:
36
+ Для кожного воркспейсу з `hasStart:true` (**по одному, НЕ паралельно** dev-сервери конфліктують за портами):
55
37
 
56
38
  ```bash
57
- cd <workspace-dir>
58
- perl -e 'alarm shift; exec @ARGV' 12 bun run start > /tmp/n-start-check.log 2>&1
59
- CODE=$?
39
+ n-cursor start-check run <workspace> # напр. demo (--grace <ms>, дефолт 12000)
60
40
  ```
61
41
 
62
- Інтерпретація `CODE`:
63
-
64
- - `142` — процес дожив до кінця grace-періоду й був знятий SIGALRM (`128 + 14`). Для **довгого процесу** (dev-сервер) це **успіх** — стартував і не впав.
65
- - `0` — процес завершився сам без помилки. Для **короткого процесу** (CLI, разова дія) це **успіх**.
66
- - будь-що інше — **FAIL**: крах або ненульовий вихід ще до кінця grace-періоду.
67
-
68
- Звір також лог: для dev-сервера — рядок готовності (`ready`, `listening`, `Local:`); для CLI — відсутність трас помилок.
69
-
70
- Альтернатива `cd` — `bun run --filter '<package-name>' start` (запуск за іменем пакета).
42
+ Друкує JSON:
71
43
 
72
- `bun run start` прибирає свій дочірній процес при завершенні; якщо `start` у репо форкає демонів окремо — переконайся, що після перевірки нічого не лишилось висіти.
73
-
74
- ### 5. Відкотити побічні ефекти прогону
44
+ ```
45
+ { workspace, type, exitCode, timedOut, status, ready, firstError, logTail, sideEffects }
46
+ ```
75
47
 
76
- Після кожного запуску звір поточний `git status --porcelain` зі знімком `/tmp/n-start-check.before` і відкоти **лише те, що з'явилося через прогін**:
48
+ - `status: "OK"` server дожив до кінця grace (`timedOut`) або встиг віддати рядок готовності; cli вийшов із кодом `0`.
49
+ - `status: "FAIL"` — крах/ненульовий код до grace. `firstError` + `logTail` — для діагностики.
50
+ - `sideEffects: { newFiles, changedTracked }` — read-only git-diff проти стану до запуску (для кроку 3).
77
51
 
78
- - нові невідстежувані файли/директорії (яких не було у знімку) — видали;
79
- - відстежувані файли, що стали зміненими, а у знімку були чисті — `git checkout -- <path>`;
80
- - усе, що було брудним **до** прогону, — НЕ чіпай: це зміни користувача, не пов'язані з прогоном.
52
+ Розрізняй **помилку коду** (синтаксис, відсутній імпорт, падіння на ініціалізації) і **середовищний збій** (немає `.env`, недоступна БД/порт зайнятий) — останній не означає, що проєкт зламаний.
81
53
 
82
- Gitignored-артефакти (кеші, `node_modules`, логи) у `git status` не з'являються — їх чіпати не треба. Коли побічні ефекти прибрано, дерево повертається до стану знімка, тож наступний воркспейс стартує з чистої бази.
54
+ ### 3. Відкотити побічні ефекти прогону
83
55
 
84
- ### 6. Зафіксувати результат
56
+ CLI **не** мутує дерево сам (відкат — деструктивний, лишається явним). Якщо `run` повернув непорожній `sideEffects`, відкоти **лише те, що зʼявилося через прогін**, перш ніж запускати наступний воркспейс:
85
57
 
86
- Для кожного воркспейсу статус і примітка:
58
+ - `sideEffects.newFiles`нові невідстежувані файли/директорії → видали (`rm`);
59
+ - `sideEffects.changedTracked` — відстежувані файли, що стали зміненими → `git checkout -- <path>`.
87
60
 
88
- - `OK` стартував (живий dev-сервер або вихід `0`).
89
- - `FAIL` — крах або ненульовий код. Додай витяг із логу: останні значущі рядки з помилкою.
90
- - `SKIP` — у воркспейсі немає `start`-скрипта (крок 2). Сам запуск `start` не пропускається.
61
+ Усе, що було брудним **до** прогону, у `sideEffects` не потрапляє (CLI рахує лише різницю) — зміни користувача не чіпай. Gitignored-артефакти (кеші, `node_modules`) у git-diff не зʼявляються.
91
62
 
92
- Якщо мутаційний `start` лишив побічні ефекти (крок 5) — познач це в примітці: проєкт стартує, але `start` не є чистим запуском сервера.
63
+ ### 4. Звіт
93
64
 
94
- Розрізняй у звіті **помилку коду** (синтаксис, відсутній імпорт, падіння при ініціалізації) і **середовищний збій** (немає `.env`, недоступна БД/сервіс, зайнятий порт) — останній не означає, що проєкт зламаний.
65
+ Підсумкова таблиця: `воркспейс | статус | примітка`, з даних `scan` + `run`:
95
66
 
96
- ### 7. Звіт
67
+ - `OK` — `status:"OK"` (живий dev-сервер або вихід `0`).
68
+ - `FAIL` — `status:"FAIL"`; додай `firstError` / `logTail`.
69
+ - `SKIP` — `hasStart:false`.
97
70
 
98
- Підсумкова таблиця: `воркспейс | статус | примітка`. Якщо є хоч один `FAIL` — винеси його окремо й покажи помилку повністю.
71
+ Якщо `run` повернув непорожній `sideEffects` познач у примітці: проєкт стартує, але `start` не є чистим запуском (мутує репо). Якщо є хоч один `FAIL` — винеси його окремо з повною помилкою.
99
72
 
100
73
  Скіл лише **діагностує** запуск. Виправлення коду — поза скоупом; якщо причина FAIL очевидна, познач її у звіті, але не правь, поки про це не попросять окремо.
@@ -0,0 +1,211 @@
1
+ /**
2
+ * `n-cursor start-check scan|run` — детермінована частина smoke-перевірки
3
+ * bun-монорепо для скілу `n-start-check`.
4
+ *
5
+ * Мотивація: скіл раніше казав LLM-агенту вручну розгортати glob-воркспейси,
6
+ * читати кожен `package.json`, класифікувати `start` (сервер vs CLI), запускати
7
+ * процес із таймаутом через `perl alarm`, інтерпретувати exit-коди й парсити лог
8
+ * на рядки готовності/помилки. Усе це детерміновано — скрипт робить це надійно й
9
+ * крос-платформно (`spawnSync` з `timeout`), а агент лишається тільки з
10
+ * діагностикою: **чому** саме воркспейс упав.
11
+ *
12
+ * `scan` — `[{workspace, name, hasStart, startCmd, type}]`.
13
+ * `run <ws>` — `{workspace, type, exitCode, timedOut, status, ready, firstError,
14
+ * logTail, sideEffects}` (sideEffects — read-only git-diff проти стану до запуску;
15
+ * власне відкат лишається явним кроком скіла).
16
+ */
17
+ import { spawnSync } from 'node:child_process'
18
+ import { existsSync } from 'node:fs'
19
+ import { readFile } from 'node:fs/promises'
20
+ import { join } from 'node:path'
21
+
22
+ import { getMonorepoPackageRootDirs } from '../../../scripts/lib/workspaces.mjs'
23
+
24
+ /** Маркери довгого процесу (dev-сервер/демон) у команді `start`. */
25
+ const SERVER_CMD_RE = /\b(vite|next|nuxt|nodemon|serve|astro|remix|webpack-dev-server|http-server)\b|\bdev\b|--watch/
26
+ /** Рядки готовності dev-сервера в логу (`\b` лише де треба — щоб «already» не матчив «ready»). */
27
+ const READY_RE = /\bready\b|\blistening\b|\bstarted\b|\bcompiled\b|server running|local:/i
28
+ /** Сигнатури помилок у логу. */
29
+ const ERROR_RE = /(error|exception|fatal|cannot find|module not found|unhandled|panic|traceback)/i
30
+ /** Скільки останніх рядків логу повертати. */
31
+ const LOG_TAIL_LINES = 15
32
+
33
+ /**
34
+ * Класифікує `start`-команду: довгий процес (сервер) чи разова дія (CLI).
35
+ * @param {string} startCmd значення `scripts.start`
36
+ * @returns {'server'|'cli'} тип процесу
37
+ */
38
+ export function classifyStartType(startCmd) {
39
+ return typeof startCmd === 'string' && SERVER_CMD_RE.test(startCmd) ? 'server' : 'cli'
40
+ }
41
+
42
+ /**
43
+ * Зчитує `package.json` воркспейсу або повертає null.
44
+ * @param {string} dir абсолютний шлях до каталогу воркспейсу
45
+ * @returns {Promise<object|null>} розпарсений package.json або null
46
+ */
47
+ async function readPkg(dir) {
48
+ const path = join(dir, 'package.json')
49
+ if (!existsSync(path)) return null
50
+ try {
51
+ return JSON.parse(await readFile(path, 'utf8'))
52
+ } catch {
53
+ return null
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Сканує монорепо: для кожного воркспейсу — чи є `start`, його команда і тип.
59
+ * @param {string} cwd корінь репозиторію
60
+ * @returns {Promise<Array<{workspace:string, name:string|null, hasStart:boolean, startCmd:string|null, type:('server'|'cli'|null)}>>} список воркспейсів
61
+ */
62
+ export async function scanStartWorkspaces(cwd) {
63
+ const roots = await getMonorepoPackageRootDirs(cwd)
64
+ const out = []
65
+ for (const ws of roots) {
66
+ const pkg = await readPkg(join(cwd, ws))
67
+ const startCmd = pkg?.scripts?.start ?? null
68
+ const hasStart = typeof startCmd === 'string' && startCmd.length > 0
69
+ out.push({
70
+ workspace: ws,
71
+ name: pkg?.name ?? null,
72
+ hasStart,
73
+ startCmd: hasStart ? startCmd : null,
74
+ type: hasStart ? classifyStartType(startCmd) : null
75
+ })
76
+ }
77
+ return out
78
+ }
79
+
80
+ /**
81
+ * Парсить лог процесу: готовність (сервер), перша помилка, хвіст.
82
+ * @param {string} log обʼєднаний stdout+stderr
83
+ * @returns {{ready:boolean, firstError:string|null, logTail:string}} витяг
84
+ */
85
+ export function parseStartLog(log) {
86
+ const text = log ?? ''
87
+ const lines = text.split('\n')
88
+ const firstError = lines.find(l => ERROR_RE.test(l))?.trim() ?? null
89
+ const logTail = lines
90
+ .filter(l => l.trim() !== '')
91
+ .slice(-LOG_TAIL_LINES)
92
+ .join('\n')
93
+ return { ready: READY_RE.test(text), firstError, logTail }
94
+ }
95
+
96
+ /**
97
+ * Read-only знімок `git status --porcelain` як множина рядків.
98
+ * @param {string} cwd корінь репозиторію
99
+ * @returns {Set<string>} рядки porcelain (статус + шлях)
100
+ */
101
+ function gitPorcelain(cwd) {
102
+ const res = spawnSync('git', ['status', '--porcelain'], { cwd, encoding: 'utf8' })
103
+ if (res.status !== 0 || typeof res.stdout !== 'string') return new Set()
104
+ return new Set(res.stdout.split('\n').filter(l => l.trim() !== ''))
105
+ }
106
+
107
+ /**
108
+ * Обчислює побічні ефекти прогону як різницю git-станів до/після.
109
+ * @param {Set<string>} before знімок до
110
+ * @param {Set<string>} after знімок після
111
+ * @returns {{newFiles:string[], changedTracked:string[]}} нові untracked і ново-змінені tracked шляхи
112
+ */
113
+ function diffSideEffects(before, after) {
114
+ const newFiles = []
115
+ const changedTracked = []
116
+ for (const line of after) {
117
+ if (before.has(line)) continue
118
+ const path = line.slice(3)
119
+ if (line.startsWith('??')) newFiles.push(path)
120
+ else changedTracked.push(path)
121
+ }
122
+ return { newFiles, changedTracked }
123
+ }
124
+
125
+ /**
126
+ * Запускає `start` одного воркспейсу з grace-таймаутом і класифікує результат.
127
+ * @param {string} cwd корінь репозиторію
128
+ * @param {string} workspace відносний шлях воркспейсу
129
+ * @param {{graceMs?:number, type?:('server'|'cli'), spawnImpl?:Function}} [opts] grace-період, тип (інакше з package.json), інʼєкція spawn для тестів
130
+ * @returns {Promise<{workspace:string, type:string, exitCode:number|null, timedOut:boolean, status:('OK'|'FAIL'), ready:boolean, firstError:string|null, logTail:string, sideEffects:{newFiles:string[], changedTracked:string[]}}>} результат прогону
131
+ */
132
+ export async function runWorkspaceStart(cwd, workspace, opts = {}) {
133
+ const { graceMs = 12_000, spawnImpl = spawnSync } = opts
134
+ const dir = join(cwd, workspace)
135
+ const pkg = await readPkg(dir)
136
+ const startCmd = pkg?.scripts?.start
137
+ if (typeof startCmd !== 'string' || startCmd.length === 0) {
138
+ throw new Error(`У воркспейсі ${workspace} немає scripts.start`)
139
+ }
140
+ const type = opts.type ?? classifyStartType(startCmd)
141
+
142
+ const before = gitPorcelain(cwd)
143
+ const res = spawnImpl('bun', ['run', 'start'], {
144
+ cwd: dir,
145
+ encoding: 'utf8',
146
+ timeout: graceMs,
147
+ killSignal: 'SIGTERM'
148
+ })
149
+ const timedOut = res.error?.code === 'ETIMEDOUT' || res.signal === 'SIGTERM'
150
+ const exitCode = typeof res.status === 'number' ? res.status : null
151
+ const { ready, firstError, logTail } = parseStartLog(`${res.stdout ?? ''}${res.stderr ?? ''}`)
152
+
153
+ // server: успіх = дожив до кінця grace (timedOut) або встиг віддати рядок готовності.
154
+ // cli: успіх = чистий вихід 0 у межах grace.
155
+ const status = type === 'server' ? (timedOut || ready ? 'OK' : 'FAIL') : exitCode === 0 ? 'OK' : 'FAIL'
156
+
157
+ return {
158
+ workspace,
159
+ type,
160
+ exitCode,
161
+ timedOut,
162
+ status,
163
+ ready,
164
+ firstError,
165
+ logTail,
166
+ sideEffects: diffSideEffects(before, gitPorcelain(cwd))
167
+ }
168
+ }
169
+
170
+ const USAGE = 'Usage: n-cursor start-check <scan | run <workspace> [--grace <ms>]>'
171
+
172
+ /**
173
+ * CLI: `scan` друкує список воркспейсів зі `start`; `run <ws>` запускає один і
174
+ * друкує класифікований результат. Обидва — JSON у stdout.
175
+ * @param {string[]} args аргументи після `start-check`
176
+ * @param {string} [cwd] корінь репозиторію (інʼєкція для тестів)
177
+ * @returns {Promise<number>} exit code
178
+ */
179
+ export async function runStartCheckCli(args, cwd = process.cwd()) {
180
+ const sub = args[0]
181
+
182
+ if (sub === 'scan') {
183
+ process.stdout.write(`${JSON.stringify(await scanStartWorkspaces(cwd))}\n`)
184
+ return 0
185
+ }
186
+
187
+ if (sub === 'run') {
188
+ const workspace = args[1] && !args[1].startsWith('--') ? args[1] : undefined
189
+ if (!workspace) {
190
+ console.error(USAGE)
191
+ return 1
192
+ }
193
+ const graceAt = args.indexOf('--grace')
194
+ const graceMs = graceAt === -1 ? undefined : Number(args[graceAt + 1])
195
+ if (graceAt !== -1 && (!Number.isFinite(graceMs) || graceMs <= 0)) {
196
+ console.error('✗ --grace очікує додатнє число (мс)')
197
+ return 1
198
+ }
199
+ try {
200
+ const result = await runWorkspaceStart(cwd, workspace, graceMs ? { graceMs } : {})
201
+ process.stdout.write(`${JSON.stringify(result)}\n`)
202
+ return 0
203
+ } catch (error) {
204
+ console.error(`✗ ${error.message}`)
205
+ return 1
206
+ }
207
+ }
208
+
209
+ console.error(USAGE)
210
+ return 1
211
+ }
@@ -43,9 +43,15 @@ bun install
43
43
 
44
44
  ### 3. Виявити major-оновлення
45
45
 
46
- Порівняти `package.json.taze-bak` з оновленим `package.json` також `bun.lock.taze-bak` ↔ `bun.lock` для транзитивних). Для кожної залежності, у якої змінилась перша значуща цифра semver (`1.x.x 2.x.x`, `0.4.x → 0.5.x`, `0.0.x → 0.1.x`) додати в список «потребує перевірки».
46
+ > **Не порівнюй `package.json` вручну.** Класифікацію semver несе CLIдетерміновано, по всіх воркспейсах.
47
47
 
48
- Minor/patch — пропускати, їх вважаємо сумісними.
48
+ ```bash
49
+ n-cursor taze diff
50
+ ```
51
+
52
+ Друкує компактний JSON: `{ "major": [{workspace, pkg, from, to}], "minorPatch": <N>, "totalChanged": <N> }`. `major` — список залежностей, у яких змінилась найлівіша ненульова компонента semver (`1.x→2.x`, `0.4.x→0.5.x`, `0.0.3→0.0.4`); саме він іде в кроки 4–6. `minorPatch` — лічба сумісних (для звіту в кроці 8).
53
+
54
+ Покриває **прямі** залежності з `package.json` (root + воркспейси). Транзитивні major-стрибки (`bun.lock`) — за потреби переглянь окремо; основний ризик breaking-змін — у прямих.
49
55
 
50
56
  ### 4. Зібрати breaking changes по кожному major-оновленню
51
57
 
@@ -94,7 +100,7 @@ rm package.json.taze-bak bun.lock.taze-bak
94
100
 
95
101
  Коротко в одному повідомленні:
96
102
 
97
- - **Оновлено (minor/patch):** кількість пакетів, без деталей.
103
+ - **Оновлено (minor/patch):** кількість пакетів (`minorPatch` із кроку 3), без деталей.
98
104
  - **Major-оновлення:** список `<name>: <old> → <new>` з посиланням на release notes.
99
105
  - **Зрефакторено автоматично:** список файлів і коротко що саме змінено.
100
106
  - **Потребує ручного втручання:** список TODO з причиною (нетривіальна міграція / неоднозначність / падіння тестів).
@@ -0,0 +1,154 @@
1
+ /**
2
+ * `n-cursor taze diff` — read-only детермінований diff версій залежностей між
3
+ * `package.json` і його бекапом `package.json.taze-bak` (root + усі воркспейси
4
+ * монорепо), із класифікацією кожної зміни за semver.
5
+ *
6
+ * Мотивація: скіл `n-taze` раніше казав LLM-агенту вручну порівнювати backup із
7
+ * новим `package.json` по всіх воркспейсах і вирішувати, де «змінилась перша
8
+ * значуща цифра semver» (major). Це детермінований JSON-diff + semver-логіка —
9
+ * скрипт робить це за мілісекунди й без помилок, а агент отримує готовий список
10
+ * major-оновлень для справді когнітивної роботи (читання CHANGELOG, рефакторинг).
11
+ *
12
+ * «Breaking» (major) рахуємо за caret-семантикою — змінилась найлівіша ненульова
13
+ * компонента: `1.x→2.x`, `0.4.x→0.5.x`, `0.0.3→0.0.4`. Minor/patch вважаємо
14
+ * сумісними.
15
+ */
16
+ import { existsSync } from 'node:fs'
17
+ import { readFile } from 'node:fs/promises'
18
+ import { join } from 'node:path'
19
+
20
+ import { getMonorepoPackageRootDirs } from '../../../scripts/lib/workspaces.mjs'
21
+
22
+ /** Поля package.json із залежностями, які порівнюємо. */
23
+ const DEP_FIELDS = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies']
24
+
25
+ /** Дефолтний суфікс бекапу, який створює крок 1 скіла (`cp package.json package.json.taze-bak`). */
26
+ const DEFAULT_BACKUP_SUFFIX = '.taze-bak'
27
+
28
+ // Заякорено на початок (після можливих range-операторів `^~>=<`, пробілів, `v`),
29
+ // щоб НЕ ловити версію всередині protocol-specifier-ів (`workspace:1.0.0`, `npm:x@1.2.3`).
30
+ const SEMVER_RE = /^[\s~^>=<v]*(\d+)\.(\d+)\.(\d+)/
31
+
32
+ /**
33
+ * Парсить semver-ядро зі specifier-а (ігнорує range-префікси `^`/`~`/`>=` тощо).
34
+ * @param {string} spec версійний specifier із package.json
35
+ * @returns {{major:number, minor:number, patch:number}|null} ядро або null для не-semver (`workspace:*`, git-url, `*`)
36
+ */
37
+ export function parseVersion(spec) {
38
+ if (typeof spec !== 'string') return null
39
+ const m = SEMVER_RE.exec(spec)
40
+ if (!m) return null
41
+ return { major: Number(m[1]), minor: Number(m[2]), patch: Number(m[3]) }
42
+ }
43
+
44
+ /**
45
+ * Чи є перехід `from → to` breaking за caret-семантикою (змінилась найлівіша
46
+ * ненульова компонента).
47
+ * @param {{major:number,minor:number,patch:number}} from стара версія
48
+ * @param {{major:number,minor:number,patch:number}} to нова версія
49
+ * @returns {boolean} true — major/breaking
50
+ */
51
+ export function isBreaking(from, to) {
52
+ if (from.major !== to.major) return true
53
+ if (from.major > 0) return false
54
+ if (from.minor !== to.minor) return true
55
+ if (from.minor > 0) return false
56
+ return from.patch !== to.patch
57
+ }
58
+
59
+ /**
60
+ * Порівнює два package.json-обʼєкти й повертає зміни залежностей.
61
+ * @param {object} oldPkg розпарсений старий package.json (бекап)
62
+ * @param {object} newPkg розпарсений новий package.json
63
+ * @param {string} workspace мітка воркспейсу (`.` для кореня)
64
+ * @returns {{major: Array<{workspace:string, pkg:string, from:string, to:string}>, minorPatch:number}} зміни
65
+ */
66
+ export function diffPackageJson(oldPkg, newPkg, workspace) {
67
+ const major = []
68
+ let minorPatch = 0
69
+ for (const field of DEP_FIELDS) {
70
+ const oldDeps = oldPkg?.[field]
71
+ const newDeps = newPkg?.[field]
72
+ if (!oldDeps || !newDeps) continue
73
+ for (const [pkg, from] of Object.entries(oldDeps)) {
74
+ const to = newDeps[pkg]
75
+ if (to === undefined || to === from) continue
76
+ const fromV = parseVersion(from)
77
+ const toV = parseVersion(to)
78
+ if (fromV && toV && isBreaking(fromV, toV)) {
79
+ major.push({ workspace, pkg, from, to })
80
+ } else {
81
+ minorPatch += 1
82
+ }
83
+ }
84
+ }
85
+ return { major, minorPatch }
86
+ }
87
+
88
+ /**
89
+ * Читає JSON-файл або повертає null, якщо файл відсутній / невалідний.
90
+ * @param {string} path абсолютний шлях
91
+ * @returns {Promise<object|null>} розпарсений обʼєкт або null
92
+ */
93
+ async function readJsonOrNull(path) {
94
+ if (!existsSync(path)) return null
95
+ try {
96
+ return JSON.parse(await readFile(path, 'utf8'))
97
+ } catch {
98
+ return null
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Збирає diff по всьому монорепо: для кожного воркспейсу порівнює
104
+ * `<ws>/package.json` з `<ws>/package.json<backupSuffix>`.
105
+ * @param {string} cwd корінь репозиторію
106
+ * @param {string} [backupSuffix] суфікс бекап-файлу
107
+ * @returns {Promise<{major: Array<{workspace:string, pkg:string, from:string, to:string}>, minorPatch:number, totalChanged:number, comparedWorkspaces:number}>} агрегований diff
108
+ */
109
+ export async function collectTazeDiff(cwd, backupSuffix = DEFAULT_BACKUP_SUFFIX) {
110
+ const roots = await getMonorepoPackageRootDirs(cwd)
111
+ const major = []
112
+ let minorPatch = 0
113
+ let comparedWorkspaces = 0
114
+ for (const ws of roots) {
115
+ const dir = join(cwd, ws)
116
+ const oldPkg = await readJsonOrNull(join(dir, `package.json${backupSuffix}`))
117
+ const newPkg = await readJsonOrNull(join(dir, 'package.json'))
118
+ if (!oldPkg || !newPkg) continue
119
+ comparedWorkspaces += 1
120
+ const res = diffPackageJson(oldPkg, newPkg, ws)
121
+ major.push(...res.major)
122
+ minorPatch += res.minorPatch
123
+ }
124
+ return { major, minorPatch, totalChanged: major.length + minorPatch, comparedWorkspaces }
125
+ }
126
+
127
+ const USAGE = 'Usage: n-cursor taze diff [--backup-suffix <suffix>]'
128
+
129
+ /**
130
+ * CLI: `n-cursor taze diff` друкує компактний JSON зі списком major-оновлень і
131
+ * лічбою minor/patch. Read-only.
132
+ * @param {string[]} args аргументи після `taze`
133
+ * @param {string} [cwd] корінь репозиторію (ін'єкція для тестів)
134
+ * @returns {Promise<number>} exit code
135
+ */
136
+ export async function runTazeCli(args, cwd = process.cwd()) {
137
+ if (args[0] !== 'diff') {
138
+ console.error(USAGE)
139
+ return 1
140
+ }
141
+ const flagAt = args.indexOf('--backup-suffix')
142
+ const backupSuffix = flagAt === -1 ? DEFAULT_BACKUP_SUFFIX : args[flagAt + 1]
143
+ if (!backupSuffix) {
144
+ console.error(USAGE)
145
+ return 1
146
+ }
147
+ const diff = await collectTazeDiff(cwd, backupSuffix)
148
+ if (diff.comparedWorkspaces === 0) {
149
+ console.error(`✗ Не знайдено жодного package.json${backupSuffix} — спершу зроби бекап (крок 1 скіла).`)
150
+ return 1
151
+ }
152
+ process.stdout.write(`${JSON.stringify(diff)}\n`)
153
+ return 0
154
+ }
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- export {};
2
+ export {}
@@ -1,119 +0,0 @@
1
- ---
2
- name: n-fix-tests
3
- description: >-
4
- Ітеративно дописати тести щоб підвищити mutation score — читає вцілілі мутанти з COVERAGE.md і запускає агент до конвергенції
5
- ---
6
-
7
- # n-fix-tests — підвищення mutation score
8
-
9
- ## Мета
10
-
11
- Читає структурований JSON-блок вцілілих мутантів з `COVERAGE.md` і ітеративно дописує тести що їх вловлюють. Зупиняється коли score перестає покращуватись (конвергенція).
12
-
13
- ## Передумови
14
-
15
- - У `COVERAGE.md` є секція `## Вцілілі мутанти` з JSON-блоком
16
- - Залежності встановлені (`bun i`)
17
- - `bun run coverage` (або `n-cursor coverage`) доступний
18
-
19
- ## Workflow
20
-
21
- ### Крок 1: Зчитай вцілілих мутантів
22
-
23
- Прочитай `COVERAGE.md`. Знайди секцію `## Вцілілі мутанти`. Знайди огороджений блок ` ```json ` у цій секції і розбери JSON-масив.
24
-
25
- Якщо секція відсутня або масив порожній — зупинись з повідомленням:
26
- `✓ Жодних вцілілих мутантів — mutation score повний`
27
-
28
- Запамʼятай поточну кількість вцілілих: `prevCount = масив.length`
29
-
30
- ### Крок 2: Знайди test-команду і coverage-команду
31
-
32
- Прочитай `package.json` у кореневій директорії.
33
-
34
- **test-команда** (перша що існує):
35
-
36
- 1. `scripts["test"]` з `package.json`
37
- 2. fallback: `bun test`
38
-
39
- **coverage-команда** (перша що існує):
40
-
41
- 1. `scripts["coverage"]` з `package.json` → виклик: `bun run coverage`
42
- 2. fallback: `n-cursor coverage`
43
-
44
- ### Крок 3: Для кожного файлу — запускає Agent
45
-
46
- Згрупуй мутанти по полю `file`. Для кожної групи виконай:
47
-
48
- **3a. Знайди / визнач test файл (завжди у `tests/` директорії):**
49
-
50
- Цільовий файл завжди: `<dir>/tests/<basename>.test.mjs`
51
- (де `<dir>` — директорія source-файлу, `<basename>` — ім'я без розширення)
52
-
53
- - Source: `<cwd>/<file>` (прочитай вміст)
54
- - Test файл:
55
- 1. Якщо `<dir>/tests/<basename>.test.mjs` існує → використай його
56
- 2. Якщо `<dir>/<basename>.test.js` або `<dir>/<basename>.test.mjs` існує (co-located) →
57
- - Перенеси файл до `<dir>/tests/<basename>.test.mjs`
58
- - Оновити відносні `import` шляхи якщо є (тепер треба `../` рівень вгору)
59
- 3. Якщо жоден не знайдено → буде створено `<dir>/tests/<basename>.test.mjs`
60
-
61
- **3b. Сформуй промпт для Agent:**
62
-
63
- ```
64
- Тобі дані вцілілі мутанти зі Stryker для файлу `<file>`.
65
- Ці мутанти вціліли, тому що наявні тести НЕ вловили конкретні зміни коду.
66
-
67
- **Вихідний код** (`<file>`):
68
- \`\`\`
69
- <зміст source-файлу>
70
- \`\`\`
71
-
72
- **Наявні тести** (`<test-file>`):
73
- \`\`\`
74
- <зміст test-файлу або "файл ще не існує">
75
- \`\`\`
76
-
77
- **Вцілілі мутанти** (кожен — зміна коду що НЕ вловлена):
78
- <для кожного мутанта:>
79
- - Рядок <line>, колонка <col>: `<original>` → `<replacement>` (тип мутації: <mutantType>)
80
-
81
- **Завдання:**
82
- Допиши мінімальні test-cases у файл `<test-file>` які б вловили кожен із перелічених мутантів.
83
- Правила:
84
- - НЕ видаляй і НЕ змінюй наявні тести
85
- - Стиль тестів — відповідно до наявного файлу (той самий фреймворк, той самий стиль describe/test)
86
- - Якщо файл ще не існує — створи `<dir>/tests/<basename>.test.mjs` з правильними імпортами.
87
- Приклад: source `src/services/auth-store.js` → test `src/services/tests/auth-store.test.mjs`,
88
- import: `import { ... } from '../auth-store.js'`
89
- - Після написання запусти: `bun test <test-file>` і переконайся що всі тести проходять (виправ якщо падають)
90
- ```
91
-
92
- **3c. Запусти Agent** з цим промптом і дочекайся завершення.
93
-
94
- ### Крок 4: Перевір що всі тести проходять
95
-
96
- ```bash
97
- bun test # або test-команда з кроку 2
98
- ```
99
-
100
- Якщо тести падають — поверни конкретний Agent (для того файлу) з помилкою і попроси виправити.
101
-
102
- ### Крок 5: Запусти coverage і порівняй
103
-
104
- ```bash
105
- bun run coverage # або coverage-команда з кроку 2
106
- ```
107
-
108
- Прочитай новий `COVERAGE.md`, знайди і розбери JSON-масив вцілілих.
109
- `newCount = новий масив.length`
110
-
111
- **Рішення:**
112
-
113
- - Якщо `newCount < prevCount` → повтор з Кроку 1 з оновленим масивом
114
- - Якщо `newCount >= prevCount` → зупинись:
115
- `✓ Конвергенція: mutation score більше не покращується. Вціліло: <newCount> мутантів.`
116
-
117
- ## Зупинка після конвергенції
118
-
119
- Конвергенція — нормальний результат. Деякі мутанти не можна вбити (захищений зовнішнім станом, недетермінована логіка тощо). Не намагайся виправити те що не змінилось після ітерації.
@@ -1 +0,0 @@
1
- { "auto": ["js-lint"], "worktree": true }