@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
@@ -0,0 +1,335 @@
1
+ # level.mjs
2
+
3
+ ## Огляд
4
+
5
+ Модуль `level.mjs` реалізує **scale-adaptive детекцію рівня складності й рівня ризику** задачі за її текстовим описом. Ідея запозичена з BMAD (project-levels / risk-profile) і адаптована до внутрішніх термінів проєкту.
6
+
7
+ Призначення модуля у контексті dispatcher:
8
+
9
+ - На етапі `init` (ініціалізація задачі/флоу) функція виклику аналізує текстовий опис задачі і відносить її до одного з чотирьох рівнів складності `L0..L3` та одного з трьох рівнів ризику `low | med | high`.
10
+ - Поєднання `level` + `risk` _right-size_-ить (масштабує) подальший пайплайн: зокрема, скільки adversarial-рецензентів спавнить команда `flow review` і яка її глибина/фокус, а також які фази робочого процесу рекомендовані як контракт.
11
+ - Усі рішення детермінуються пошуком ключових слів у нижньому регістрі без використання регулярних виразів — це свідомий вибір для уникнення slow-regex та проблем зі словесними межами для кириличних символів.
12
+
13
+ Файл є **чистою бібліотекою без побічних ефектів**: не звертається до файлової системи, мережі, не пише в `stdout/stderr`, не залежить від часу. Усі експорти — детерміновані функції від рядкового опису (або числа/рядка).
14
+
15
+ ## Експорти / API
16
+
17
+ Модуль експортує чотири іменовані функції (усі — `export function`):
18
+
19
+ | Експорт | Сигнатура | Призначення |
20
+ | ------------------- | -------------------------------------------- | --------------------------------------------------------------------------- |
21
+ | `detectLevel` | `(desc: string) => 0 \| 1 \| 2 \| 3` | Визначає рівень складності задачі за описом. |
22
+ | `reviewersForLevel` | `(level: number) => number` | Кількість adversarial-рецензентів на основі рівня (1..3). |
23
+ | `detectRisk` | `(desc: string) => 'low' \| 'med' \| 'high'` | Визначає рівень ризику задачі за описом. |
24
+ | `reviewersForRisk` | `(risk: string) => number` | Кількість рецензентів на основі ризику (1..3). |
25
+ | `reviewersFor` | `(level: number, risk?: string) => number` | Підсумкова кількість рецензентів: максимум вимог рівня й ризику, з капом 3. |
26
+
27
+ Внутрішні (НЕ експортовані) допоміжні функції: `isAsciiAlnum`, `hasWord`.
28
+
29
+ Внутрішні константи з наборами ключових слів: `L3_KEYS`, `L0_WORD_KEYS`, `L0_SUBSTR_KEYS`, `L2_KEYS`, `HIGH_RISK_KEYS`, `MED_RISK_KEYS`.
30
+
31
+ ## Константи (внутрішні)
32
+
33
+ ### `L3_KEYS`
34
+
35
+ Ключові слова, що сигналізують про **архітектурні/великі/міграційні** задачі (рівень L3).
36
+
37
+ ```text
38
+ ['platform', 'migration', 'rewrite', 'architecture', 'enterprise',
39
+ 'редизайн', 'міграц', 'переписат']
40
+ ```
41
+
42
+ Збігаються звичайним `indexOf` (підрядок, case-insensitive після приведення опису до lowercase). Кириличні корені `міграц` / `переписат` спеціально вкорочені до основи слова (стемінг), щоб ловити будь-які закінчення (`міграція`, `міграційний`, `переписати`, `переписану`).
43
+
44
+ ### `L0_WORD_KEYS`
45
+
46
+ ASCII-дієслова, що сигналізують про **тривіальні** задачі (рівень L0). Матчаться **тільки як ціле слово** через `hasWord` — щоб `fix` не ловило `prefix` / `fixture`.
47
+
48
+ ```text
49
+ ['fix', 'typo', 'bump', 'rename', 'hotfix']
50
+ ```
51
+
52
+ ### `L0_SUBSTR_KEYS`
53
+
54
+ Кириличні ключі для L0, матчаться **підрядком** (стемінг: `перейменув` ловить `перейменування`, `перейменувати` тощо).
55
+
56
+ ```text
57
+ ['опечат', 'перейменув']
58
+ ```
59
+
60
+ ### `L2_KEYS`
61
+
62
+ Ключові слова для **багатофайлових фіч/рефакторів** (рівень L2). Матчаться підрядком.
63
+
64
+ ```text
65
+ ['feature', 'epic', 'refactor', 'рефактор', 'фіча']
66
+ ```
67
+
68
+ ### `HIGH_RISK_KEYS`
69
+
70
+ Ключі **високого** ризику (безпека, гроші, доступи). Матчаються підрядком.
71
+
72
+ ```text
73
+ ['security', 'auth', 'crypto', 'payment', 'secret', 'token',
74
+ 'permission', 'password', 'безпек']
75
+ ```
76
+
77
+ ### `MED_RISK_KEYS`
78
+
79
+ Ключі **середнього** ризику (дані, незворотність). Матчаться підрядком.
80
+
81
+ ```text
82
+ ['data', ' db', 'database', 'migration', 'delete', 'gateway',
83
+ 'міграц', 'видален']
84
+ ```
85
+
86
+ Зверніть увагу: ключ `' db'` навмисно записаний з провідним пробілом — це дешевий спосіб відрізнити окреме слово `db` від випадкових підрядків (наприклад, у `dbg`, `dbus`), не вдаючись до пошуку зі словесними межами.
87
+
88
+ ## Функції
89
+
90
+ ### `isAsciiAlnum(ch)` _(internal)_
91
+
92
+ **Сигнатура:** `function isAsciiAlnum(ch: string | undefined): boolean`
93
+
94
+ **Параметри:**
95
+
96
+ - `ch` — один символ (або `undefined`, що моделює "край рядка" перед нульовою позицією чи за останньою).
97
+
98
+ **Повертає:** `true`, якщо `ch` — ASCII-літера в нижньому регістрі (`a..z`) або ASCII-цифра (`0..9`). Інакше `false`. Для `undefined` повертає `false` (край рядка не вважається символом alnum).
99
+
100
+ **Side effects:** немає.
101
+
102
+ **Використання:** як перевірка межі слова в `hasWord`. Свідомо охоплює лише lowercase ASCII (бо вхід вже зведено до `.toLowerCase()` у `detectLevel`/`detectRisk`).
103
+
104
+ ### `hasWord(text, word)` _(internal)_
105
+
106
+ **Сигнатура:** `function hasWord(text: string, word: string): boolean`
107
+
108
+ **Параметри:**
109
+
110
+ - `text` — основний текст (передбачається lowercase після `.toLowerCase()`).
111
+ - `word` — шукане ASCII-слово (lowercase).
112
+
113
+ **Повертає:** `true`, якщо в `text` зустрічається `word` і обидві його межі (символ безпосередньо до й безпосередньо після) **не є** ASCII-alnum (тобто це справді ціле слово); інакше `false`.
114
+
115
+ **Алгоритм:**
116
+
117
+ 1. Шукає перше входження `word` через `text.indexOf(word)`.
118
+ 2. Поки знайдено (`i !== -1`):
119
+ - Перевіряє ліву межу: `text[i - 1]` (або `undefined`, якщо `i === 0`).
120
+ - Перевіряє праву межу: `text[i + word.length]` (або `undefined`, якщо це кінець рядка).
121
+ - Якщо **жодна** з них не є ASCII-alnum — повертає `true`.
122
+ - Інакше шукає наступне входження через `text.indexOf(word, i + 1)`.
123
+ 3. Якщо всі входження виявилися підрядками всередині більших слів — повертає `false`.
124
+
125
+ **Side effects:** немає.
126
+
127
+ **Чому без regex:** конвенція файлу — уникати `RegExp` для запобігання slow-regex атакам і неоднозначностям `\b` на кириличних межах слів (де `\b` в JS-regex поводиться непередбачувано). Ручний прохід через `indexOf` дешевший і явніший.
128
+
129
+ ### `detectLevel(desc)`
130
+
131
+ **Експорт:** `export function detectLevel(desc): 0 | 1 | 2 | 3`
132
+
133
+ **Параметри:**
134
+
135
+ - `desc` — опис задачі (будь-який тип; усередині нормалізується через `String(desc ?? '')`).
136
+
137
+ **Повертає:** один із літералів `0 | 1 | 2 | 3` — рівень складності.
138
+
139
+ **Side effects:** немає.
140
+
141
+ **Алгоритм (з пріоритетом L3 > L0 > L2 > L1):**
142
+
143
+ 1. Нормалізує `desc` до рядка: `String(desc ?? '')` (значення `null`/`undefined` стають порожнім рядком).
144
+ 2. Зводить рядок у нижній регістр: `const d = ...toLowerCase()`.
145
+ 3. Створює локальний хелпер `has = keys => keys.some(k => d.includes(k))` (підрядковий пошук).
146
+ 4. Обчислює `isL0`:
147
+ - `L0_WORD_KEYS.some(k => hasWord(d, k))` — ASCII-дієслова як ціле слово, **АБО**
148
+ - `L0_SUBSTR_KEYS.some(k => d.includes(k))` — кириличні підрядки.
149
+ 5. Послідовно перевіряє:
150
+ - Якщо `has(L3_KEYS)` → `3`.
151
+ - Інакше якщо `isL0` → `0`.
152
+ - Інакше якщо `has(L2_KEYS)` → `2`.
153
+ - Інакше → дефолт `1`.
154
+
155
+ **Семантика пріоритетів:**
156
+
157
+ - L3 виграє у всіх: навіть якщо опис містить і `migration`, і `fix`, рівень буде `3` — велика міграція важливіша за згадку дрібного фіксу.
158
+ - L0 наступний: тривіальні зміни не повинні маскуватися під L2-фічу через випадковий збіг.
159
+ - L2 — middle ground для багатофайлових задач.
160
+ - L1 — дефолт, коли жоден сигнал не зловлено.
161
+
162
+ **Приклади:**
163
+
164
+ - `'Fix typo in README'` → L0 (`fix` як слово; `typo`).
165
+ - `'Add database migration for users'` → L3 (через `migration`, що в L3_KEYS).
166
+ - `'Refactor auth module'` → L2 (`refactor`; `auth` тут не змінює рівень — це сигнал ризику, не рівня).
167
+ - `'Update copy on landing'` → L1 (нічого не зловлено).
168
+ - `'Prefix new fields with role_'` → L1 (`prefix` НЕ ловиться L0_WORD_KEYS через цілословний матч `fix`).
169
+
170
+ ### `reviewersForLevel(level)`
171
+
172
+ **Експорт:** `export function reviewersForLevel(level: number): number`
173
+
174
+ **Параметри:**
175
+
176
+ - `level` — числовий рівень (зазвичай `0..3`, але приймає будь-яке число).
177
+
178
+ **Повертає:** ціле в діапазоні `1..3` — рекомендована кількість adversarial-рецензентів суто на основі рівня.
179
+
180
+ **Side effects:** немає.
181
+
182
+ **Таблиця рішень:**
183
+
184
+ | `level` | Повертає |
185
+ | -------------------------- | -------- |
186
+ | `>= 3` | `3` |
187
+ | `=== 2` | `2` |
188
+ | решта (`0`, `1`, від'ємні) | `1` |
189
+
190
+ **Семантика:** глибина review корелює з масштабом задачі.
191
+
192
+ ### `detectRisk(desc)`
193
+
194
+ **Експорт:** `export function detectRisk(desc): 'low' | 'med' | 'high'`
195
+
196
+ **Параметри:**
197
+
198
+ - `desc` — опис задачі (будь-який тип; усередині нормалізується через `String(desc ?? '')`).
199
+
200
+ **Повертає:** літерал `'low' | 'med' | 'high'`.
201
+
202
+ **Side effects:** немає.
203
+
204
+ **Алгоритм:**
205
+
206
+ 1. Нормалізує: `const d = String(desc ?? '').toLowerCase()`.
207
+ 2. `has = keys => keys.some(k => d.includes(k))` — суто підрядковий пошук (для ризику немає різниці ASCII vs кирилиця: всі ключі матчаться як підрядки).
208
+ 3. Якщо `has(HIGH_RISK_KEYS)` → `'high'`.
209
+ 4. Інакше якщо `has(MED_RISK_KEYS)` → `'med'`.
210
+ 5. Інакше → `'low'`.
211
+
212
+ **Семантика пріоритетів:** `high > med > low`. Якщо опис одночасно містить і high-, і med-сигнал — виграє `high`.
213
+
214
+ **Приклади:**
215
+
216
+ - `'Add password reset flow'` → `'high'` (`password`).
217
+ - `'Migrate user data to new schema'` → `'high'` (`migration`/`міграц` — НІ, але `data` в MED_RISK_KEYS… насправді тут `migration` теж у MED_RISK_KEYS, а high-ключів немає → `'med'`).
218
+ - `'Bump dep version'` → `'low'`.
219
+
220
+ ### `reviewersForRisk(risk)`
221
+
222
+ **Експорт:** `export function reviewersForRisk(risk: string): number`
223
+
224
+ **Параметри:**
225
+
226
+ - `risk` — рядок (очікувано `'low' | 'med' | 'high'`; інші значення трактуються як low).
227
+
228
+ **Повертає:** ціле `1..3` — рекомендована кількість рецензентів суто за ризиком.
229
+
230
+ **Side effects:** немає.
231
+
232
+ **Таблиця рішень:**
233
+
234
+ | `risk` | Повертає |
235
+ | ----------------------------------------------------- | -------- |
236
+ | `'high'` | `3` |
237
+ | `'med'` | `2` |
238
+ | решта (включно з `'low'`, `undefined`, неочікуваними) | `1` |
239
+
240
+ ### `reviewersFor(level, risk)`
241
+
242
+ **Експорт:** `export function reviewersFor(level: number, risk?: string): number`
243
+
244
+ **Параметри:**
245
+
246
+ - `level` — числовий рівень складності (`0..3`).
247
+ - `risk` — необов'язковий рядок ризику (`'low' | 'med' | 'high'`).
248
+
249
+ **Повертає:** ціле `1..3` — підсумкова кількість рецензентів, **максимум** вимог за рівнем і за ризиком, з верхнім капом `3`.
250
+
251
+ **Side effects:** немає.
252
+
253
+ **Формула:**
254
+
255
+ ```text
256
+ Math.min(3, Math.max(reviewersForLevel(level), reviewersForRisk(risk)))
257
+ ```
258
+
259
+ **Семантика:**
260
+
261
+ - Високий ризик піднімає глибину review навіть для L0/L1.
262
+ - Велика задача (L3) гарантує максимум рецензентів незалежно від ризику.
263
+ - Кап `3` страхує від екзотичних вхідних значень `level > 3` (не виходимо за межі узгодженого діапазону).
264
+
265
+ **Приклади (level, risk → reviewers):**
266
+
267
+ | `level` | `risk` | Результат |
268
+ | ------- | ----------- | --------------- |
269
+ | `0` | `'low'` | `max(1, 1) = 1` |
270
+ | `0` | `'high'` | `max(1, 3) = 3` |
271
+ | `2` | `'low'` | `max(2, 1) = 2` |
272
+ | `3` | `'med'` | `max(3, 2) = 3` |
273
+ | `1` | `undefined` | `max(1, 1) = 1` |
274
+
275
+ ## Залежності
276
+
277
+ **Зовнішні (npm/Node.js):** немає. Файл повністю самодостатній.
278
+
279
+ **Стандартні API:** використовує лише вбудовані операції рядка (`String`, `toLowerCase`, `indexOf`, доступ за індексом, `length`) та `Math.min` / `Math.max`.
280
+
281
+ **Внутрішні (з інших модулів проєкту):** немає `import`/`require`.
282
+
283
+ **Хто залежить від цього модуля:** очікувано — модулі команди `init` (ініціалізація флоу) та `flow review` (вибір кількості рецензентів). Інші частини dispatcher можуть викликати `reviewersFor` як єдину точку входу для розрахунку review-глибини.
284
+
285
+ ## Потік виконання / Використання
286
+
287
+ Типовий потік:
288
+
289
+ 1. **Ініціалізація задачі (`init`).** Викликач має текстовий опис задачі (наприклад, з ADR-чернетки або з CLI-аргументу). Імпортує `detectLevel` і `detectRisk`:
290
+
291
+ ```js
292
+ import { detectLevel, detectRisk, reviewersFor } from './level.mjs'
293
+
294
+ const level = detectLevel(taskDescription) // 0 | 1 | 2 | 3
295
+ const risk = detectRisk(taskDescription) // 'low' | 'med' | 'high'
296
+ ```
297
+
298
+ 2. **Запис у стан флоу.** Значення `level` і `risk` зберігаються у стейті задачі (state-store / spec / артефакти), щоб бути доступними наступним командам.
299
+
300
+ 3. **Розрахунок глибини review (`flow review`).** Перед спавном adversarial-рецензентів викликається `reviewersFor`:
301
+
302
+ ```js
303
+ import { reviewersFor } from './level.mjs'
304
+
305
+ const reviewers = reviewersFor(level, risk) // 1..3
306
+ for (let i = 0; i < reviewers; i++) {
307
+ spawnAdversarialReviewer({ index: i, level, risk })
308
+ }
309
+ ```
310
+
311
+ 4. **Рекомендовані фази (контракт).** Рівень і ризик також впливають на те, які фази робочого процесу обов'язкові (наприклад, для L3/high — повний цикл з планом, ревью, gate; для L0/low — спрощений шлях). Це використання вже виходить за межі цього файлу, але `detectLevel` + `detectRisk` — точка входу для таких рішень.
312
+
313
+ **Інваріанти й гарантії:**
314
+
315
+ - Усі функції чисті й детерміновані: однаковий вхід → однаковий вихід.
316
+ - `detectLevel` / `detectRisk` стійкі до `null` / `undefined` / нерядкових входів (приведення через `String(x ?? '')`).
317
+ - Жодна функція не кидає виключень для типових входів.
318
+ - Жодна функція не виконує I/O.
319
+
320
+ **Обмеження детекції:**
321
+
322
+ - Простий пошук підрядків може давати false-positive на природній мові (наприклад, фраза "no migration needed" все одно тригерить L3). Це свідомий компроміс на користь швидкості і детермінізму.
323
+ - ASCII-дієслова L0 потребують матчу цілим словом; кириличні L0 матчаться підрядком — це асиметрія, продиктована особливостями токенізації двох алфавітів.
324
+
325
+ ## Rebuild Test
326
+
327
+ Документація достатня для відтворення модуля з нуля без перегляду оригіналу:
328
+
329
+ - Усі експорти, їхні сигнатури, параметри, тип повертального значення й семантика описані.
330
+ - Внутрішні константи (списки ключових слів) наведені дослівно, з поясненням, чому окремі ключі сформовані саме так (стемінг, провідний пробіл у `' db'`).
331
+ - Алгоритми `detectLevel` / `detectRisk` / `hasWord` / `isAsciiAlnum` описані покроково з порядком пріоритетів і дефолтами.
332
+ - Формула `reviewersFor` з капом і максимумом наведена явно.
333
+ - Зафіксовано принципову відмову від regex з обґрунтуванням.
334
+
335
+ За цією специфікацією модуль `level.mjs` можна повністю відтворити, і його поведінка на типових входах буде ідентичною оригіналу.
@@ -0,0 +1,181 @@
1
+ # plan-panel.mjs
2
+
3
+ ## Огляд
4
+
5
+ Модуль `plan-panel.mjs` реалізує **agent↔agent brainstorm** — багатоперсональну панель субагентів, які формують спільну відповідь на задачу. Концептуально це поєднання двох підходів:
6
+
7
+ - **bmad party-mode** — кілька «персон» одночасно дивляться на проблему з різних кутів;
8
+ - **superpowers dispatching** — окремий суддя-субагент синтезує думки персон в єдину фінальну відповідь.
9
+
10
+ Панель є спільним механізмом для двох фаз життєвого циклу задачі:
11
+
12
+ - `mode: 'spec'` — генерує 2–3 текстові підходи до розв’язання у форматі Markdown (для фази специфікації);
13
+ - `mode: 'plan'` — генерує покроковий JSON-план (для фази планування).
14
+
15
+ Модуль повторно використовує **runner-інтерфейс Фасада B**, який очікує об’єкт із методом `runStep(prompt, opts) => { ok, output }` (контракт, аналогічний `planner.mjs` і `active.mjs` у тому ж каталозі).
16
+
17
+ Принципово важлива поведінкова характеристика — **HITL (human-in-the-loop)**: панель тільки **повертає синтез** і ніколи не зберігає артефакт самостійно. Апрув людини та фіксація результату належать до контракту `flow.mdc` і виконуються окремими командами (`flow spec` / `flow plan`).
18
+
19
+ ## Експорти / API
20
+
21
+ Модуль має один публічний експорт:
22
+
23
+ - `runPanel(input)` — асинхронна функція, що проводить панель і повертає синтез або `null` у разі провалу.
24
+
25
+ Внутрішні артефакти модуля (не експортуються):
26
+
27
+ - константа `PERSONAS` — список персон панелі;
28
+ - функція `judgePrompt(mode, proposals, task)` — формує промпт для судді залежно від режиму.
29
+
30
+ ## Функції
31
+
32
+ ### `runPanel({ task, cwd, runner, log, mode })`
33
+
34
+ **Сигнатура:**
35
+
36
+ ```
37
+ async function runPanel({
38
+ task, // string — опис задачі для персон і судді
39
+ cwd, // string — робочий каталог, передається в runner.runStep як opts.cwd
40
+ runner, // { runStep: (prompt, opts?) => { ok, output } | Promise<{ ok, output }> }
41
+ log, // (msg: string) => void — логер помилок; за замовчуванням console.error
42
+ mode // 'spec' | 'plan' — режим синтезу; за замовчуванням 'plan'
43
+ }) => Promise<
44
+ | { task: string, acceptance?: string }[] // коли mode === 'plan' і JSON парситься
45
+ | string // коли mode === 'spec'
46
+ | null // у разі будь-якого фейлу
47
+ >
48
+ ```
49
+
50
+ **Параметри:**
51
+
52
+ - `task` — текстовий опис задачі. Підставляється в системний промпт кожної персони (`<sys>\n\nЗадача: <task>`) та в промпт судді (рядок `Задача: <task>` у кінці).
53
+ - `cwd` — робоча директорія; модуль не використовує її напряму, лише передає у `runner.runStep` через `{ cwd }`.
54
+ - `runner` — обов’язкова ін’єкція з методом `runStep`. Якщо відсутній, функція логуює повідомлення `panel: нема runner — режим --panel недоступний` і повертає `null`.
55
+ - `log` — функція логування непомилкового рівня (фактично використовується тільки для діагностичних повідомлень про помилки). За замовчуванням — `console.error`.
56
+ - `mode` — `'spec'` або `'plan'`. За замовчуванням `'plan'`. Впливає на:
57
+ - інструкцію в `judgePrompt`;
58
+ - формат повернення (текст або розпарсений JSON-масив).
59
+
60
+ **Повертає:**
61
+
62
+ - Для `mode === 'plan'`: масив об’єктів `{ task: string, acceptance?: string }` після успішного `JSON.parse` фрагмента між першим `[` і останнім `]` у відповіді судді.
63
+ - Для `mode === 'spec'`: рядок `judge.output` (Markdown від судді) як є.
64
+ - `null` — у будь-якому з провальних сценаріїв (див. далі).
65
+
66
+ **Сценарії повернення `null`:**
67
+
68
+ 1. Не передано `runner` → лог `panel: нема runner — режим --panel недоступний`.
69
+ 2. Виклик судді завершився `{ ok: false }` → лог `panel: суддя-синтез завершився помилкою`.
70
+ 3. Режим `plan`, але у виводі судді не знайдено JSON-меж (`[` / `]`) або `end < start` → лог `panel: суддя не повернув JSON-план`.
71
+ 4. Режим `plan`, JSON знайдено, але `JSON.parse` кинув виняток → лог `panel: невалідний JSON синтезу`. Виняток ловиться `catch {}` і не пробрасується далі.
72
+
73
+ **Алгоритм (по кроках):**
74
+
75
+ 1. Перевірити наявність `runner`; якщо нема — залогувати й вийти з `null`.
76
+ 2. Паралельно через `Promise.all` опитати всі персони з `PERSONAS`:
77
+ - для кожної персони викликати `runner.runStep(<sys>\n\nЗадача: <task>, { cwd })`;
78
+ - сформувати рядок `### <name>\n<output або "(порожньо)" якщо !ok>`.
79
+ 3. Склеїти всі думки персон через `\n\n` і викликати суддю промптом, побудованим у `judgePrompt(mode, proposals, task)`.
80
+ 4. Якщо суддя `!ok` — лог і `null`.
81
+ 5. Якщо `mode === 'spec'` — повернути `judge.output` без обробки.
82
+ 6. Інакше (plan): знайти межі JSON-масиву (`indexOf('[')` та `lastIndexOf(']')`), валідувати їх; розпарсити `slice(start, end + 1)` і повернути; у разі помилки парсингу — лог і `null`.
83
+
84
+ **Side effects:**
85
+
86
+ - **Зовнішні виклики моделей через `runner.runStep`**: персон — `PERSONAS.length` (зараз 3), плюс один виклик судді. Усього 4 запити на один прогін `runPanel`.
87
+ - **Логування**: виключно через переданий `log` (за замовчуванням `console.error`). Інших звернень до stdout/stderr нема.
88
+ - **Файлову систему не чіпає**, мережу безпосередньо не використовує (опосередковано — через `runner`), артефактів не зберігає (HITL — фіксацію робить агент за `flow.mdc`).
89
+ - **Конкурентність**: запити персон виконуються паралельно (`Promise.all` + `map(async …)`); виклик судді — послідовно після завершення всіх персон.
90
+
91
+ ### `judgePrompt(mode, proposals, task)`
92
+
93
+ **Сигнатура:**
94
+
95
+ ```
96
+ function judgePrompt(
97
+ mode: 'spec' | 'plan',
98
+ proposals: string,
99
+ task: string
100
+ ): string
101
+ ```
102
+
103
+ **Параметри:**
104
+
105
+ - `mode` — режим синтезу. Визначає інструкцію в «голові» промпту:
106
+ - `'plan'` — три рядки інструкції: вимога ОДНОГО покрокового плану, обмеження ≤ 5 хв на крок із критерієм приймання, вимога повернути **ЛИШЕ JSON-масив без коментарів** у форматі `[{ "task": "...", "acceptance": "..." }, ...]`.
107
+ - `'spec'` (та будь-яке інше значення) — дві інструкції: 2–3 підходи з рекомендацією й коротким дизайном, повернути людино-читабельний Markdown.
108
+ - `proposals` — попередньо склеєні думки персон (зазвичай `proposals.join('\n\n')` із масиву `### name\noutput`).
109
+ - `task` — оригінальний опис задачі (рядок `Задача: <task>` додається в кінець промпту).
110
+
111
+ **Повертає:** рядок промпту, склеєний через `\n` у вигляді:
112
+
113
+ ```
114
+ <head-line-1>
115
+ <head-line-2>
116
+ [<head-line-3 — лише для plan>]
117
+
118
+ <proposals>
119
+
120
+ Задача: <task>
121
+ ```
122
+
123
+ **Side effects:** чиста функція без побічних ефектів і зовнішніх викликів.
124
+
125
+ ## Залежності
126
+
127
+ ### Зовнішні модулі / пакети
128
+
129
+ - **Жодних `import`** із Node.js core, npm-пакетів чи інших файлів проєкту. Модуль повністю самодостатній.
130
+
131
+ ### Глобали
132
+
133
+ - `console.error` — використовується як **дефолтне** значення параметра `log`. Якщо викликач передасть власний `log`, `console` не використовується.
134
+ - `Promise`, `JSON`, `Array.isArray` (не використовується), `String.prototype.indexOf` / `lastIndexOf` / `slice` — стандартні JS-примітиви.
135
+
136
+ ### Контрактні (поведінкові) залежності
137
+
138
+ - **Runner-інтерфейс Фасада B**: `runner.runStep(prompt: string, opts?: { cwd: string }) => { ok: boolean, output: string } | Promise<{ ok, output }>`. Цей контракт реалізують сусідні модулі — наприклад, `planner.mjs` і `active.mjs` у каталозі `lib/`.
139
+ - **HITL за `flow.mdc`**: правила Flow вимагають, щоб фіксацію артефакту (`flow spec` / `flow plan`) виконував агент окремою командою; панель сама артефактів не пише.
140
+
141
+ ### Внутрішні константи
142
+
143
+ - `PERSONAS` — масив із трьох записів `[name, systemPrompt]`:
144
+ - `architect` — «Запропонуй найчистішу архітектуру розв’язання. Стисло, по суті.»
145
+ - `skeptic` — «Назви ризики, граничні випадки і що може піти не так. Стисло.»
146
+ - `tester` — «Опиши, які тести доведуть коректність. Стисло.»
147
+
148
+ ## Потік виконання / Використання
149
+
150
+ ### Типовий потік виклику
151
+
152
+ 1. Викликач (наприклад, CLI-команда фази `spec` чи `plan`) отримує `runner`, що інкапсулює запуск субагента (модель + транспорт), і поточний `cwd`.
153
+ 2. Викликач формулює `task` і обирає `mode` (`'spec'` або `'plan'`).
154
+ 3. Викликає `await runPanel({ task, cwd, runner, mode, log? })`.
155
+ 4. Внутрішньо панель:
156
+ - паралельно опитує `architect`, `skeptic`, `tester` — кожна повертає текстову думку (`### <name>\n<output>` або `(порожньо)` у разі `!ok`);
157
+ - формує промпт судді через `judgePrompt`, який отримує всі думки + оригінальне завдання;
158
+ - запускає суддю одним викликом `runner.runStep`;
159
+ - на основі `mode` повертає або сирий Markdown (`spec`), або розпарсений JSON-масив кроків (`plan`).
160
+ 5. Викликач отримує результат і:
161
+ - для `null` — повідомляє користувача та припиняє/повторює фазу;
162
+ - для `spec` (рядок) — показує користувачу й чекає `flow spec` для фіксації;
163
+ - для `plan` (масив кроків) — передає далі (виконавцю/планеру) і чекає `flow plan` для фіксації.
164
+
165
+ ### Очікувані формати виводу судді
166
+
167
+ - **`plan`**: модель повинна повернути рядок, у якому міститься JSON-масив об’єктів вигляду `{ "task": "...", "acceptance": "..." }`. Парсер толерантний до «обгортки» (текст до першого `[` і після останнього `]` ігнорується), але самий JSON має бути валідним.
168
+ - **`spec`**: довільний Markdown-текст; жодних обмежень формату на стороні модуля.
169
+
170
+ ### Гарантії та обмеження
171
+
172
+ - **Ніколи не кидає виняток назовні**: усі провальні шляхи завершуються `null` + лог через `log()`. `try/catch` навколо `JSON.parse` ловить навіть synthax-помилки парсингу.
173
+ - **Паралелізм персон**: усі персони запускаються одночасно — це впливає на навантаження `runner` (зараз 3 паралельні виклики моделі) та сумарну затримку (≈ max(persona-latency) + judge-latency, а не сума).
174
+ - **Не зберігає стан** між викликами; кожен виклик `runPanel` повністю самодостатній.
175
+ - **Не валідує внутрішню структуру** елементів JSON-плану — тип `{ task: string, acceptance?: string }[]` зазначений у JSDoc, але рантайм-перевірки полів немає. Викликач відповідає за подальшу валідацію.
176
+
177
+ ### Розширюваність
178
+
179
+ - Додати персону: дописати запис у масив `PERSONAS`. Жодних інших змін не потрібно — обхід йде через `map`.
180
+ - Додати режим: розширити умову в `judgePrompt` (та, за потреби, гілку обробки виводу у `runPanel`).
181
+ - Підмінити модель/транспорт: реалізувати інший `runner` із тим самим контрактом `runStep` — модуль працює з будь-яким об’єктом, що задовольняє інтерфейс.