@gian-tiaga/eda 3.14.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/MIGRATION.md +23 -2
  2. package/README.md +131 -223
  3. package/agents/eda-aim-executor/prompt.md +2 -0
  4. package/agents/eda-aim-verifier/prompt.md +2 -0
  5. package/agents/eda-review-api/prompt.md +4 -4
  6. package/agents/eda-review-architecture/prompt.md +4 -4
  7. package/agents/eda-review-business/prompt.md +5 -12
  8. package/agents/eda-review-code-quality/prompt.md +4 -4
  9. package/agents/eda-review-correctness/prompt.md +3 -3
  10. package/agents/eda-review-database/prompt.md +4 -4
  11. package/agents/eda-review-documentation/prompt.md +4 -4
  12. package/agents/eda-review-frontend/prompt.md +4 -4
  13. package/agents/eda-review-infra/agent.json +14 -0
  14. package/agents/eda-review-infra/prompt.md +36 -0
  15. package/agents/eda-review-performance/prompt.md +7 -7
  16. package/agents/eda-review-plan-alignment/prompt.md +4 -4
  17. package/agents/eda-review-previous-reviews/prompt.md +5 -4
  18. package/agents/eda-review-references/prompt.md +4 -4
  19. package/agents/eda-review-rules/prompt.md +4 -4
  20. package/agents/eda-review-security/prompt.md +4 -4
  21. package/agents/eda-review-seo/agent.json +14 -0
  22. package/agents/eda-review-seo/prompt.md +36 -0
  23. package/agents/eda-review-tests/prompt.md +4 -4
  24. package/agents/eda-review-triage/agent.json +14 -0
  25. package/agents/eda-review-triage/prompt.md +38 -0
  26. package/lib/catalog.js +28 -1
  27. package/lib/install.js +221 -590
  28. package/lib/prompts.js +53 -0
  29. package/lib/renderers/codex-agent.js +3 -1
  30. package/lib/renderers/codex-skill.js +21 -1
  31. package/lib/renderers/opencode-skill.js +2 -2
  32. package/package.json +2 -3
  33. package/skills/_shared/done.md +15 -0
  34. package/skills/_shared/questions.md +9 -0
  35. package/skills/eda-aim/SKILL.md +2 -3
  36. package/skills/eda-business/SKILL.md +5 -6
  37. package/skills/eda-commit/SKILL.md +2 -3
  38. package/skills/eda-discover-automations/SKILL.md +6 -7
  39. package/skills/eda-explore/SKILL.md +28 -15
  40. package/skills/eda-fix/SKILL.md +7 -7
  41. package/skills/eda-fix-by-review/SKILL.md +4 -3
  42. package/skills/eda-just-do-it/SKILL.md +4 -4
  43. package/skills/eda-manual-test/SKILL.md +7 -5
  44. package/skills/eda-merge-worktree/SKILL.md +2 -3
  45. package/skills/eda-new-project/SKILL.md +3 -5
  46. package/skills/eda-orhestra/SKILL.md +3 -4
  47. package/skills/eda-plan/SKILL.md +126 -168
  48. package/skills/eda-plan-execute/SKILL.md +72 -92
  49. package/skills/eda-plan-polish/SKILL.md +14 -14
  50. package/skills/eda-plan-review/SKILL.md +25 -10
  51. package/skills/eda-plan-review-fix/SKILL.md +10 -7
  52. package/skills/eda-polish/SKILL.md +9 -6
  53. package/skills/eda-prepare-ai/SKILL.md +35 -15
  54. package/skills/eda-prod-check/SKILL.md +129 -0
  55. package/skills/eda-review/SKILL.md +92 -64
  56. package/skills/eda-review/references/checks/api.md +18 -0
  57. package/skills/eda-review/references/checks/architecture.md +16 -0
  58. package/skills/eda-review/references/checks/business.md +13 -0
  59. package/skills/eda-review/references/checks/code-quality.md +18 -0
  60. package/skills/eda-review/references/checks/correctness.md +19 -0
  61. package/skills/eda-review/references/checks/database.md +17 -0
  62. package/skills/eda-review/references/checks/documentation.md +13 -0
  63. package/skills/eda-review/references/checks/frontend.md +19 -0
  64. package/skills/eda-review/references/checks/infra.md +16 -0
  65. package/skills/eda-review/references/checks/performance.md +21 -0
  66. package/skills/eda-review/references/checks/plan-alignment.md +16 -0
  67. package/skills/eda-review/references/checks/previous-reviews.md +9 -0
  68. package/skills/eda-review/references/checks/references.md +11 -0
  69. package/skills/eda-review/references/checks/rules.md +16 -0
  70. package/skills/eda-review/references/checks/security.md +26 -0
  71. package/skills/eda-review/references/checks/seo.md +16 -0
  72. package/skills/eda-review/references/checks/tests.md +18 -0
  73. package/skills/eda-review/references/defects.md +24 -0
  74. package/skills/eda-review/references/triage.md +23 -0
  75. package/skills/eda-roadmap/SKILL.md +2 -4
  76. package/skills/eda-send-review/SKILL.md +3 -5
  77. package/skills/eda-worktree/SKILL.md +2 -3
  78. package/skills/eda-review/references/review-matrix.md +0 -72
package/lib/install.js CHANGED
@@ -1,12 +1,11 @@
1
- import checkbox from '@inquirer/checkbox';
2
- import select from '@inquirer/select';
3
1
  import { randomUUID } from 'node:crypto';
4
2
  import fs from 'node:fs/promises';
5
3
  import path from 'node:path';
6
4
  import { fileURLToPath } from 'node:url';
7
5
  import { parse as parseYaml } from 'yaml';
8
6
 
9
- import { listAgents, listSkills } from './catalog.js';
7
+ import { expandIncludes, listAgents, listSkills } from './catalog.js';
8
+ import { checkboxPrompt as checkbox, finishPrompts, selectPrompt as select, startPrompts } from './prompts.js';
10
9
  import { renderClaudeAgent } from './renderers/claude-agent.js';
11
10
  import { renderClaudeSkill } from './renderers/claude-skill.js';
12
11
  import { renderCodexAgent } from './renderers/codex-agent.js';
@@ -42,6 +41,8 @@ const TARGET_CHOICES = [
42
41
  { value: 'codex', label: 'Codex CLI', dir: '.agents/skills/' },
43
42
  { value: 'opencode', label: 'OpenCode', dir: '.opencode/skills/' }
44
43
  ];
44
+ // В вопросах платформы показываются в этом порядке; установка идёт в порядке TARGET_CHOICES.
45
+ const TARGET_PROMPT_ORDER = ['claude', 'opencode', 'codex'];
45
46
  const UPDATE_ALL_MAX_DEPTH = 2;
46
47
  const UPDATE_ALL_SKIP_DIRS = new Set(['.git', '.agents', '.claude', '.codex', '.opencode', 'node_modules']);
47
48
  const RETIRED_SKILLS = [
@@ -68,6 +69,8 @@ const RETIRED_AGENTS = [
68
69
  'eda-plan-review-previous'
69
70
  ];
70
71
  const RETIRED_SETTINGS_PATHS = [
72
+ 'plan.review',
73
+ 'plan.meta_review',
71
74
  'plan-review.agents',
72
75
  'review-check',
73
76
  'review.strict',
@@ -75,138 +78,30 @@ const RETIRED_SETTINGS_PATHS = [
75
78
  ];
76
79
  const MANIFEST_FILE = 'eda-manifest.json';
77
80
  const SETTINGS_VERSION = 3;
78
- const REVIEW_AGENT_DEFINITIONS = [
79
- {
80
- key: 'correctness',
81
- description: 'Проверяет ошибки в логике, крайние случаи и обработку ошибок.',
82
- auto: 'Эта базовая проверка по умолчанию запускается всегда.',
83
- mode: 'always',
84
- claude: 'sonnet',
85
- codex: 'gpt-5.6-terra'
86
- },
87
- {
88
- key: 'architecture',
89
- description: 'Проверяет архитектурные границы, зависимости и разделение ответственности.',
90
- auto: 'В auto запускается при изменении модулей, зависимостей, публичных контрактов или границ системы.',
91
- mode: 'auto',
92
- claude: 'opus',
93
- codex: 'gpt-5.6-sol'
94
- },
95
- {
96
- key: 'rules',
97
- description: 'Проверяет соблюдение AGENTS.md, CLAUDE.md и docs/rules.md.',
98
- auto: 'Эта базовая проверка по умолчанию запускается всегда.',
99
- mode: 'always',
100
- claude: 'haiku',
101
- codex: 'gpt-5.6-luna'
102
- },
103
- {
104
- key: 'references',
105
- description: 'Проверяет соответствие применимым карточкам из docs/references/.',
106
- auto: 'В auto запускается только когда для изменений выбраны применимые карточки.',
107
- mode: 'auto',
108
- claude: 'haiku',
109
- codex: 'gpt-5.6-luna'
110
- },
111
- {
112
- key: 'business',
113
- description: 'Проверяет изменённое поведение по применимым карточкам из docs/business/.',
114
- auto: 'В auto запускается только когда изменения затрагивают поведение с применимой business-карточкой.',
115
- mode: 'auto',
116
- claude: 'sonnet',
117
- codex: 'gpt-5.6-terra'
118
- },
119
- {
120
- key: 'plan_alignment',
121
- description: 'Проверяет, что реализация соответствует указанному или однозначно найденному плану.',
122
- auto: 'В auto запускается только когда у ревью есть конкретный план.',
123
- mode: 'auto',
124
- claude: 'sonnet',
125
- codex: 'gpt-5.6-terra'
126
- },
127
- {
128
- key: 'code_quality',
129
- description: 'Проверяет читаемость, сложность, дублирование и поддерживаемость кода.',
130
- auto: 'Эта базовая проверка по умолчанию запускается всегда.',
131
- mode: 'always',
132
- claude: 'sonnet',
133
- codex: 'gpt-5.6-terra'
134
- },
135
- {
136
- key: 'tests',
137
- description: 'Проверяет достаточность, корректность и устойчивость тестов.',
138
- auto: 'Эта базовая проверка по умолчанию запускается всегда.',
139
- mode: 'always',
140
- claude: 'sonnet',
141
- codex: 'gpt-5.6-terra'
142
- },
143
- {
144
- key: 'security',
145
- description: 'Проверяет уязвимости, права доступа, валидацию и работу с чувствительными данными.',
146
- auto: 'В auto запускается для auth, permissions, внешнего ввода, файлов, сети, секретов и криптографии.',
147
- mode: 'auto',
148
- claude: 'opus',
149
- codex: 'gpt-5.6-sol'
150
- },
151
- {
152
- key: 'performance',
153
- description: 'Проверяет запросы, циклы, память, конкурентность и другие риски производительности.',
154
- auto: 'В auto запускается при изменении запросов, пакетной обработки, кешей, конкурентности или горячих путей.',
155
- mode: 'auto',
156
- claude: 'sonnet',
157
- codex: 'gpt-5.6-terra'
158
- },
159
- {
160
- key: 'frontend',
161
- description: 'Проверяет фронтенд-код, UI, UX, адаптивность и состояния интерфейса.',
162
- auto: 'В auto обязательно запускается при изменении компонентов, стилей, клиентских маршрутов или состояния UI.',
163
- mode: 'auto',
164
- claude: 'sonnet',
165
- codex: 'gpt-5.6-terra'
166
- },
167
- {
168
- key: 'api',
169
- description: 'Проверяет API-контракты, совместимость, валидацию и обработку ошибок.',
170
- auto: 'В auto запускается при изменении маршрутов, handlers, DTO, схем API, GraphQL или RPC.',
171
- mode: 'auto',
172
- claude: 'sonnet',
173
- codex: 'gpt-5.6-terra'
174
- },
175
- {
176
- key: 'database',
177
- description: 'Проверяет схемы, миграции, запросы, индексы и целостность данных.',
178
- auto: 'В auto запускается при изменении миграций, моделей хранения, SQL, схем или транзакций.',
179
- mode: 'auto',
180
- claude: 'opus',
181
- codex: 'gpt-5.6-sol'
182
- },
183
- {
184
- key: 'documentation',
185
- description: 'Проверяет, обновлена ли документация для изменившегося пользовательского поведения.',
186
- auto: 'В auto запускается при изменении публичного API, CLI, настроек или документируемого поведения.',
187
- mode: 'auto',
188
- claude: 'haiku',
189
- codex: 'gpt-5.6-luna'
190
- },
191
- {
192
- key: 'previous_reviews',
193
- description: 'Перепроверяет незакрытые замечания предыдущих ревью для удалённого PR или MR.',
194
- auto: 'В auto запускается только для ссылки на PR/MR или другой цели с доступными удалёнными обсуждениями.',
195
- mode: 'auto',
196
- claude: 'haiku',
197
- codex: 'gpt-5.6-luna'
198
- }
81
+ // Состав проверок выбирает eda-review-triage по содержимому ревью. В настройках у роли одна строка:
82
+ // default — модель установленного агента — или provider/model#variant, где provider и variant необязательны.
83
+ const REVIEW_AGENT_KEYS = [
84
+ 'triage',
85
+ 'correctness',
86
+ 'architecture',
87
+ 'rules',
88
+ 'references',
89
+ 'business',
90
+ 'plan_alignment',
91
+ 'code_quality',
92
+ 'tests',
93
+ 'security',
94
+ 'performance',
95
+ 'frontend',
96
+ 'api',
97
+ 'database',
98
+ 'infra',
99
+ 'seo',
100
+ 'documentation',
101
+ 'previous_reviews'
199
102
  ];
200
- const DEFAULT_REVIEW_AGENTS = Object.fromEntries(REVIEW_AGENT_DEFINITIONS.map(agent => [
201
- agent.key,
202
- {
203
- mode: agent.mode,
204
- model: {
205
- claude: agent.claude,
206
- codex: agent.codex
207
- }
208
- }
209
- ]));
103
+ const DEFAULT_REVIEW_AGENTS = Object.fromEntries(REVIEW_AGENT_KEYS.map(key => [key, 'default']));
104
+ const REVIEW_AGENT_MODEL_PATTERN = /^[\w.:-]+(?:\/[\w.:-]+)*(?:#[\w.-]+)?$/;
210
105
  const ORHESTRA_STEP_DEFINITIONS = [
211
106
  {
212
107
  id: 'plan',
@@ -266,20 +161,19 @@ const DEFAULT_SETTINGS = {
266
161
  },
267
162
  plan: {
268
163
  strict: false,
269
- review: true,
270
164
  size: 'normal',
271
165
  decisionMode: 'recommend_and_ask',
272
- testStrategy: 'ask_each_time',
273
- loggingStrategy: 'ask_each_time'
166
+ testStrategy: 'phase_tests_final_checks',
167
+ loggingStrategy: 'standard'
274
168
  },
275
169
  planReview: {
276
170
  threshold: 100
277
171
  },
278
172
  planPolish: {
279
- limit: 1
173
+ limit: 5
280
174
  },
281
175
  planExecute: {
282
- mode: 'auto'
176
+ mode: 'main'
283
177
  },
284
178
  polish: {
285
179
  execution: 'subagents'
@@ -288,249 +182,83 @@ const DEFAULT_SETTINGS = {
288
182
  depth: 'full'
289
183
  },
290
184
  review: {
291
- execution: 'subagents',
185
+ execution: 'main',
292
186
  agents: DEFAULT_REVIEW_AGENTS
293
187
  },
294
188
  sendReview: {
295
- closePreviousReviews: false
189
+ closePreviousReviews: true
296
190
  },
297
191
  discoverAutomations: {
298
192
  includePlans: false
299
193
  }
300
194
  };
195
+ // Разделы идут по ходу работы: исследование, план, его проверка, выполнение, ревью,
196
+ // ручные тесты; оркестраторы, которые собирают остальные скилы, — в конце.
301
197
  const SETTINGS_SECTION_NAMES = [
302
- 'orhestra',
303
- 'aim',
304
198
  'explore',
305
199
  'plan',
306
200
  'plan-review',
307
201
  'plan-polish',
308
202
  'plan-execute',
203
+ 'review',
309
204
  'polish',
310
205
  'manual-test',
311
- 'review',
312
206
  'send-review',
313
- 'discover-automations'
314
- ];
315
- const SETTINGS_CHOICES = [
316
- {
317
- section: 'explore',
318
- key: 'explore.strict',
319
- value: 'exploreStrict',
320
- name: 'Strict по умолчанию для eda-explore',
321
- checked: DEFAULT_SETTINGS.explore.strict
322
- },
323
- {
324
- section: 'plan',
325
- key: 'plan.strict',
326
- value: 'planStrict',
327
- name: 'Strict по умолчанию для eda-plan',
328
- checked: DEFAULT_SETTINGS.plan.strict
329
- },
330
- {
331
- section: 'plan',
332
- key: 'plan.review',
333
- value: 'planReview',
334
- name: 'Запускать eda-plan-review внутри eda-plan',
335
- checked: DEFAULT_SETTINGS.plan.review
336
- },
337
- {
338
- section: 'send-review',
339
- key: 'sendReview.closePreviousReviews',
340
- value: 'sendReviewClosePreviousReviews',
341
- name: 'Закрывать предыдущие ревью eda-send-review',
342
- checked: DEFAULT_SETTINGS.sendReview.closePreviousReviews
343
- },
344
- {
345
- section: 'discover-automations',
346
- key: 'discoverAutomations.includePlans',
347
- value: 'discoverAutomationsIncludePlans',
348
- name: 'Анализировать планы в eda-discover-automations по умолчанию',
349
- checked: DEFAULT_SETTINGS.discoverAutomations.includePlans
350
- }
351
- ];
352
- const ORHESTRA_MODE_CHOICES = [
353
- {
354
- value: 'automatic',
355
- name: 'Автоматически пройти план, выполнение, ревью и ручные тесты'
356
- },
357
- {
358
- value: 'manual',
359
- name: 'Писать план вместе с человеком и передавать ему вопросы'
360
- }
361
- ];
362
- const ORHESTRA_EXECUTION_CHOICES = [
363
- {
364
- value: 'subagents',
365
- name: 'Каждый этап выполнять отдельным изолированным субагентом'
366
- },
367
- {
368
- value: 'main',
369
- name: 'Все этапы выполнять основным агентом без передачи режима внутрь'
370
- },
371
- {
372
- value: 'main_propagated',
373
- name: 'Все этапы выполнять основным агентом и передавать main совместимым скилам'
374
- }
375
- ];
376
- const AIM_MODE_CHOICES = [
377
- {
378
- value: 'automatic',
379
- name: 'Автоматически отвечать на безопасные рабочие вопросы'
380
- },
381
- {
382
- value: 'manual',
383
- name: 'Передавать рабочие вопросы человеку'
384
- }
385
- ];
386
- const ORHESTRA_POLISH_LIMIT_CHOICES = [1, 2, 3, 5, 10].map(value => ({
387
- value,
388
- name: `${value} ${value === 1 ? 'итерация' : value < 5 ? 'итерации' : 'итераций'}`
389
- }));
390
- const PLAN_REVIEW_THRESHOLD_CHOICES = [80, 90, 100].map(value => ({
391
- value,
392
- name: `${value}/100`
393
- }));
394
- const PLAN_POLISH_LIMIT_CHOICES = [1, 2, 3].map(value => ({
395
- value,
396
- name: `${value} ${value === 1 ? 'итерация' : value < 5 ? 'итерации' : 'итераций'}`
397
- }));
398
- const PLAN_EXECUTE_MODE_CHOICES = [
399
- {
400
- value: 'auto',
401
- name: 'Решать по плану: один контекст для простого плана, субагенты для остальных'
402
- },
403
- {
404
- value: 'subagents',
405
- name: 'Каждую фазу выполнять отдельным изолированным субагентом'
406
- },
407
- {
408
- value: 'main',
409
- name: 'Выполнять весь план основным агентом без субагентов'
410
- }
411
- ];
412
- const POLISH_EXECUTION_CHOICES = [
413
- {
414
- value: 'subagents',
415
- name: 'Каждое review и исправление выполнять отдельным изолированным субагентом'
416
- },
417
- {
418
- value: 'main',
419
- name: 'Review и исправления выполнять основным агентом без субагентов'
420
- }
207
+ 'discover-automations',
208
+ 'orhestra',
209
+ 'aim'
421
210
  ];
211
+ // Варианты ответа: первым — значение по умолчанию, дальше — от ближайшего к дальнему,
212
+ // «Спрашивать каждый раз» всегда последним.
422
213
  const PLAN_SIZE_CHOICES = [
423
- {
424
- value: 'normal',
425
- name: 'Обычный план по умолчанию'
426
- },
427
- {
428
- value: 'short',
429
- name: 'Короткий plan short по умолчанию'
430
- },
431
- {
432
- value: 'ask_each_time',
433
- name: 'Спрашивать размер плана каждый раз'
434
- }
435
- ];
436
- const MANUAL_TEST_DEPTH_CHOICES = [
437
- {
438
- value: 'full',
439
- name: 'Полная проверка всех областей: API, база, консоль, интерфейс, алгоритмы'
440
- },
441
- {
442
- value: 'smoke',
443
- name: 'Только smoke: запуск и основной сценарий изменённой области'
444
- },
445
- {
446
- value: 'ask_each_time',
447
- name: 'Спрашивать глубину проверки каждый раз'
448
- }
214
+ { value: 'normal', name: 'normal', description: 'обычный план до 40 КБ' },
215
+ { value: 'short', name: 'short', description: 'до 12 КБ и 2 фаз' },
216
+ { value: 'ask_each_time', name: 'Спрашивать каждый раз' }
449
217
  ];
450
218
  const DECISION_MODE_CHOICES = [
451
- {
452
- value: 'recommend_and_ask',
453
- name: 'Рекомендовать вариант и спрашивать по важным развилкам'
454
- },
455
- {
456
- value: 'autonomous',
457
- name: 'Самостоятельно выбирать по коду, правилам и рискам'
458
- },
459
- {
460
- value: 'ask_each_time',
461
- name: 'Спрашивать по каждой значимой развилке'
462
- }
219
+ { value: 'recommend_and_ask', name: 'Рекомендовать и спрашивать', description: 'вопрос только по существенным развилкам' },
220
+ { value: 'autonomous', name: 'Самостоятельно', description: 'агент выбирает сам и записывает причину' },
221
+ { value: 'ask_each_time', name: 'Спрашивать по каждой развилке' }
463
222
  ];
464
223
  const TEST_STRATEGY_CHOICES = [
465
- {
466
- value: 'after_each_phase',
467
- name: 'Писать тесты и запускать все проверки после каждой фазы'
468
- },
469
- {
470
- value: 'phase_tests_final_checks',
471
- name: 'Писать тесты в фазах, запускать только их, остальные проверки — в конце'
472
- },
473
- {
474
- value: 'tdd_each_phase',
475
- name: 'В каждой фазе сначала тесты, затем код и все проверки фазы'
476
- },
477
- {
478
- value: 'end_of_plan',
479
- name: 'Писать тесты и запускать все проверки в конце плана'
480
- },
481
- {
482
- value: 'ask_each_time',
483
- name: 'Спрашивать стратегию тестов и проверок каждый раз'
484
- }
224
+ { value: 'phase_tests_final_checks', name: 'Тесты в фазах, проверки в конце', description: 'в фазе — только её тесты, остальные проверки в конце плана' },
225
+ { value: 'tdd_each_phase', name: 'TDD', description: 'в каждой фазе сначала тесты, затем код и все проверки фазы' },
226
+ { value: 'after_each_phase', name: 'После каждой фазы', description: 'тесты и все проверки после каждой фазы' },
227
+ { value: 'end_of_plan', name: 'В конце плана', description: 'тесты и все проверки один раз в конце' },
228
+ { value: 'ask_each_time', name: 'Спрашивать каждый раз' }
485
229
  ];
486
- const LOGGING_STRATEGY_CHOICES = [
487
- {
488
- value: 'standard',
489
- name: 'Стандартные info / warning / error по необходимости'
490
- },
491
- {
492
- value: 'debug_precise',
493
- name: 'Подробные debug-логи на важных шагах'
494
- },
495
- {
496
- value: 'ask_each_time',
497
- name: 'Спрашивать стратегию логирования каждый раз'
498
- }
499
- ];
500
- const REVIEW_MODE_CHOICES = [
501
- { value: 'always', name: 'always — запускать при каждом ревью' },
502
- { value: 'auto', name: 'auto — запускать только когда проверка применима' },
503
- { value: 'off', name: 'off — отключить проверку' }
230
+ const PLAN_EXECUTE_MODE_CHOICES = [
231
+ { value: 'main', name: 'Основной агент', description: 'весь план одним контекстом' },
232
+ { value: 'auto', name: 'auto', description: 'по плану: небольшой — одним контекстом, остальные — субагентами' },
233
+ { value: 'subagents', name: 'Субагенты', description: 'каждая фаза — отдельный субагент с независимой проверкой' }
504
234
  ];
505
235
  const REVIEW_EXECUTION_CHOICES = [
506
- {
507
- value: 'subagents',
508
- name: 'Каждую review-проверку выполнять отдельным специализированным субагентом'
509
- },
510
- {
511
- value: 'main',
512
- name: 'Все review-проверки выполнять основным агентом без субагентов'
513
- }
236
+ { value: 'main', name: 'Основной агент', description: 'все проверки в одном контексте' },
237
+ { value: 'subagents', name: 'Субагенты', description: 'разведчик и отдельный агент на каждую проверку' }
238
+ ];
239
+ const POLISH_EXECUTION_CHOICES = [
240
+ { value: 'subagents', name: 'Субагенты', description: 'каждое ревью и исправление — отдельный субагент' },
241
+ { value: 'main', name: 'Основной агент', description: 'ревью и исправления в одном контексте' }
514
242
  ];
515
- const CLAUDE_REVIEW_MODEL_CHOICES = [
516
- { value: 'haiku', name: 'haiku — быстрая проверка' },
517
- { value: 'sonnet', name: 'sonnet — сильная проверка кода' },
518
- { value: 'opus', name: 'opus — максимальная глубина' }
243
+ const MANUAL_TEST_DEPTH_CHOICES = [
244
+ { value: 'full', name: 'Полная', description: 'API, БД, консоль, интерфейс, алгоритмы, фон, права, логи, регрессии' },
245
+ { value: 'smoke', name: 'Smoke', description: 'запуск и основной сценарий изменённой области' },
246
+ { value: 'ask_each_time', name: 'Спрашивать каждый раз' }
247
+ ];
248
+ const ORHESTRA_MODE_CHOICES = [
249
+ { value: 'automatic', name: 'automatic', description: 'сам отвечает на безопасные рабочие вопросы' },
250
+ { value: 'manual', name: 'manual', description: 'план пишется с человеком, вопросы передаются ему' }
519
251
  ];
520
- const CODEX_REVIEW_MODEL_CHOICES = [
521
- { value: 'gpt-5.6-luna', name: 'gpt-5.6-luna — быстрая проверка' },
522
- { value: 'gpt-5.6-terra', name: 'gpt-5.6-terra — сильная проверка кода' },
523
- { value: 'gpt-5.6-sol', name: 'gpt-5.6-sol — максимальная глубина' }
252
+ const ORHESTRA_EXECUTION_CHOICES = [
253
+ { value: 'subagents', name: 'Субагенты', description: 'каждый этап — отдельный субагент' },
254
+ { value: 'main', name: 'Основной агент', description: 'цепочку ведёт текущий агент, вложенные скилы — по своим настройкам' },
255
+ { value: 'main_propagated', name: 'Основной агент везде', description: 'plan-execute, polish и review тоже без субагентов' }
524
256
  ];
257
+ // План и выполнение в оркестраторе включены всегда; спрашиваются только необязательные этапы.
258
+ const ORHESTRA_OPTIONAL_STEPS = ['eda-plan-polish', 'eda-polish', 'eda-manual-test'];
525
259
  const UPDATE_ALL_SETTINGS_MODE_CHOICES = [
526
- {
527
- value: 'configure',
528
- name: 'Настроить один общий профиль и записать его во все проекты'
529
- },
530
- {
531
- value: 'skip',
532
- name: 'Skip — сохранить полный v3, остальные конфиги перенести с defaults'
533
- }
260
+ { value: 'configure', name: 'Настроить общий профиль', description: 'ответить один раз и записать во все проекты' },
261
+ { value: 'skip', name: 'Skip', description: 'сохранить актуальные конфиги, старые перенести с defaults' }
534
262
  ];
535
263
 
536
264
  export async function init({ cwd, input = process.stdin, output = process.stdout }) {
@@ -630,6 +358,12 @@ export async function updateAll({
630
358
  output.write(`Полный файл настроек сохранён: ${SETTINGS_RELATIVE_PATH}\n`);
631
359
  } else {
632
360
  await writeSettingsContent(projectDir, rendered);
361
+ if (migrated.legacyReviewAgents) {
362
+ output.write(`review.agents переведён на формат 4.0, все роли — default: ${SETTINGS_RELATIVE_PATH}\n`);
363
+ }
364
+ if (migrated.resetReviewAgents.length > 0) {
365
+ output.write(`Недопустимые модели review.agents заменены на default (${migrated.resetReviewAgents.join(', ')}): ${SETTINGS_RELATIVE_PATH}\n`);
366
+ }
633
367
  output.write(migrated.retiredPaths.length > 0
634
368
  ? `Из файла настроек убраны устаревшие ключи (${migrated.retiredPaths.join(', ')}): ${SETTINGS_RELATIVE_PATH}\n`
635
369
  : migrated.normalizedPaths.length > 0
@@ -685,18 +419,20 @@ export async function askTargets({ input = process.stdin, output = process.stdou
685
419
  return TARGET_CHOICES.map(choice => choice.value);
686
420
  }
687
421
 
688
- return checkbox({
689
- message: 'Куда устанавливать пакет eda?',
690
- instructions: 'Стрелки — выбрать, Space — отметить, Enter — продолжить',
691
- choices: TARGET_CHOICES.map(choice => ({
692
- name: `${choice.label} (${choice.dir})`,
422
+ const selected = await checkbox({
423
+ message: 'Куда установить eda?',
424
+ choices: targetPromptChoices().map(choice => ({
425
+ name: choice.label,
693
426
  value: choice.value,
694
- checked: true
695
- }))
427
+ description: choice.dir,
428
+ checked: false
429
+ })),
430
+ required: true
696
431
  }, {
697
432
  input,
698
433
  output
699
434
  });
435
+ return TARGET_CHOICES.map(choice => choice.value).filter(target => selected.includes(target));
700
436
  }
701
437
 
702
438
  export async function askUpdateTargets({
@@ -707,22 +443,25 @@ export async function askUpdateTargets({
707
443
  }) {
708
444
  if (!input.isTTY || !output.isTTY) return [...installedTargets];
709
445
 
446
+ // Установленные платформы обновляются всегда: снятая с них отметка ничего не удаляет.
710
447
  const selected = await checkboxPrompt({
711
- message: 'Какие платформы добавить? Установленные уже отмечены и будут обновлены.',
712
- instructions: 'Space — добавить платформу, Enter — обновить выбранные; установленные снимать нельзя',
713
- choices: TARGET_CHOICES.map(choice => ({
714
- name: `${choice.label} (${choice.dir})${installedTargets.includes(choice.value) ? ' — установлена' : ''}`,
448
+ message: 'Какие платформы обновить? Установленные уже отмечены.',
449
+ choices: targetPromptChoices().map(choice => ({
450
+ name: choice.label,
715
451
  value: choice.value,
452
+ description: installedTargets.includes(choice.value) ? 'установлена — обновится в любом случае' : choice.dir,
716
453
  checked: installedTargets.includes(choice.value)
717
- })),
718
- validate: choices => installedTargets.every(target => choices.some(choice => choice.value === target))
719
- || 'Установленные платформы нужно оставить отмеченными: update их не удаляет.'
454
+ }))
720
455
  }, { input, output });
721
456
 
722
457
  return TARGET_CHOICES.map(choice => choice.value)
723
458
  .filter(target => installedTargets.includes(target) || selected.includes(target));
724
459
  }
725
460
 
461
+ function targetPromptChoices() {
462
+ return TARGET_PROMPT_ORDER.map(value => TARGET_CHOICES.find(choice => choice.value === value));
463
+ }
464
+
726
465
  export async function askUpdateAllSettingsMode({
727
466
  input = process.stdin,
728
467
  output = process.stdout,
@@ -743,6 +482,8 @@ export async function askUpdateAllSettingsMode({
743
482
  });
744
483
  }
745
484
 
485
+ // Спрашиваются только основные настройки в порядке работы. Редкие получают defaults и
486
+ // меняются вручную в docs/settings.yaml, как и модели review.agents.
746
487
  export async function askSettings({
747
488
  input = process.stdin,
748
489
  output = process.stdout,
@@ -759,157 +500,52 @@ export async function askSettings({
759
500
  }
760
501
 
761
502
  const requestedSections = new Set(sections);
762
- const shouldAsk = key => missingKeys === null || missingKeys.has(key);
763
- const booleanChoices = SETTINGS_CHOICES.filter(choice => (
764
- requestedSections.has(choice.section) && shouldAsk(choice.key)
765
- ));
766
- const selected = booleanChoices.length > 0
767
- ? await checkboxPrompt({
768
- message: 'Какие настройки включить?',
769
- instructions: 'Стрелки — выбрать, Space — отметить, Enter — продолжить',
770
- choices: booleanChoices.map(({ section, key, ...choice }) => choice)
771
- }, { input, output })
772
- : [];
773
-
774
- if (requestedSections.has('explore') && shouldAsk('explore.strict')) {
775
- settings.explore.strict = selected.includes('exploreStrict');
776
- }
777
- if (requestedSections.has('plan') && shouldAsk('plan.strict')) {
778
- settings.plan.strict = selected.includes('planStrict');
779
- }
780
- if (requestedSections.has('plan') && shouldAsk('plan.review')) {
781
- settings.plan.review = selected.includes('planReview');
782
- }
783
- if (requestedSections.has('send-review') && shouldAsk('sendReview.closePreviousReviews')) {
784
- settings.sendReview.closePreviousReviews = selected.includes('sendReviewClosePreviousReviews');
785
- }
786
- if (requestedSections.has('discover-automations') && shouldAsk('discoverAutomations.includePlans')) {
787
- settings.discoverAutomations.includePlans = selected.includes('discoverAutomationsIncludePlans');
788
- }
789
-
790
- if (requestedSections.has('orhestra') && shouldAsk('orhestra.mode')) {
791
- settings.orhestra.mode = await selectPrompt({
792
- message: 'Как eda-orhestra должен вести полный цикл по умолчанию?',
793
- choices: ORHESTRA_MODE_CHOICES,
794
- default: settings.orhestra.mode
795
- }, { input, output });
503
+ const shouldAsk = (section, key) => requestedSections.has(section) && (missingKeys === null || missingKeys.has(key));
504
+ const scalarPrompts = [
505
+ ['plan', 'plan.size', settings.plan, 'size', 'eda-plan · размер плана', PLAN_SIZE_CHOICES],
506
+ ['plan', 'plan.decisionMode', settings.plan, 'decisionMode', 'eda-plan · как принимать существенные решения', DECISION_MODE_CHOICES],
507
+ ['plan', 'plan.testStrategy', settings.plan, 'testStrategy', 'eda-plan · когда писать тесты и запускать проверки', TEST_STRATEGY_CHOICES],
508
+ ['plan-execute', 'planExecute.mode', settings.planExecute, 'mode', 'eda-plan-execute · где выполнять фазы плана', PLAN_EXECUTE_MODE_CHOICES],
509
+ ['review', 'review.execution', settings.review, 'execution', 'eda-review · где выполнять проверки', REVIEW_EXECUTION_CHOICES],
510
+ ['polish', 'polish.execution', settings.polish, 'execution', 'eda-polish · где выполнять ревью и исправления', POLISH_EXECUTION_CHOICES],
511
+ ['manual-test', 'manualTest.depth', settings.manualTest, 'depth', 'eda-manual-test · глубина ручной проверки', MANUAL_TEST_DEPTH_CHOICES],
512
+ ['orhestra', 'orhestra.mode', settings.orhestra, 'mode', 'eda-orhestra · как вести полный цикл', ORHESTRA_MODE_CHOICES]
513
+ ].filter(([section, key]) => shouldAsk(section, key));
514
+ const askSteps = shouldAsk('orhestra', 'orhestra.steps');
515
+ const askOrhestraExecution = shouldAsk('orhestra', 'orhestra.execution');
516
+ if (scalarPrompts.length === 0 && !askSteps && !askOrhestraExecution) return settings;
517
+
518
+ startPrompts(`Настройки ${SETTINGS_RELATIVE_PATH}`, { output });
519
+ for (const [, , target, property, message, choices] of scalarPrompts) {
520
+ target[property] = await selectPrompt({ message, choices, default: target[property] }, { input, output });
796
521
  }
797
-
798
- if (requestedSections.has('orhestra') && shouldAsk('orhestra.steps')) {
522
+ if (askSteps) {
799
523
  const enabledSteps = await checkboxPrompt({
800
- message: 'Какие этапы eda-orhestra включить по умолчанию?',
801
- instructions: 'Порядок можно изменить вручную в docs/settings.yaml',
802
- choices: ORHESTRA_STEP_DEFINITIONS.map(step => ({
803
- name: step.name,
804
- value: step.skill,
805
- checked: settings.orhestra.steps.find(item => item.skill === step.skill)?.enabled !== false
806
- }))
524
+ message: 'eda-orhestra · какие этапы включить, кроме плана и выполнения',
525
+ choices: ORHESTRA_STEP_DEFINITIONS
526
+ .filter(step => ORHESTRA_OPTIONAL_STEPS.includes(step.skill))
527
+ .map(step => ({
528
+ name: step.name,
529
+ value: step.skill,
530
+ checked: settings.orhestra.steps.find(item => item.skill === step.skill)?.enabled !== false
531
+ }))
807
532
  }, { input, output });
808
- const codePolishLimit = enabledSteps.includes('eda-polish')
809
- ? await selectPrompt({
810
- message: 'Сколько итераций разрешить eda-polish внутри eda-orhestra?',
811
- choices: ORHESTRA_POLISH_LIMIT_CHOICES,
812
- default: 5
813
- }, { input, output })
814
- : 5;
815
533
  settings.orhestra.steps = DEFAULT_ORHESTRA_STEPS.map(step => ({
816
534
  ...step,
817
- enabled: enabledSteps.includes(step.skill),
818
- ...(step.skill === 'eda-polish' ? { args: `limit ${codePolishLimit}` } : {})
535
+ enabled: !ORHESTRA_OPTIONAL_STEPS.includes(step.skill) || enabledSteps.includes(step.skill)
819
536
  }));
820
537
  }
821
-
822
- const scalarPrompts = [
823
- ['orhestra', 'orhestra.execution', settings.orhestra, 'execution', 'Как eda-orhestra должен выполнять этапы по умолчанию?', ORHESTRA_EXECUTION_CHOICES],
824
- ['aim', 'aim.mode', settings.aim, 'mode', 'Как eda-aim должен отвечать на рабочие вопросы по умолчанию?', AIM_MODE_CHOICES],
825
- ['explore', 'explore.decisionMode', settings.explore, 'decisionMode', 'Как принимать важные решения в eda-explore?', DECISION_MODE_CHOICES],
826
- ['plan', 'plan.size', settings.plan, 'size', 'Какой размер плана eda-plan использовать по умолчанию?', PLAN_SIZE_CHOICES],
827
- ['plan', 'plan.decisionMode', settings.plan, 'decisionMode', 'Как принимать важные решения в eda-plan?', DECISION_MODE_CHOICES],
828
- ['plan', 'plan.testStrategy', settings.plan, 'testStrategy', 'Какую стратегию тестов и проверок eda-plan использовать по умолчанию?', TEST_STRATEGY_CHOICES],
829
- ['plan', 'plan.loggingStrategy', settings.plan, 'loggingStrategy', 'Какую стратегию логирования eda-plan использовать по умолчанию?', LOGGING_STRATEGY_CHOICES],
830
- ['plan-review', 'planReview.threshold', settings.planReview, 'threshold', 'Какой порог готовности использовать в eda-plan-review?', PLAN_REVIEW_THRESHOLD_CHOICES],
831
- ['plan-polish', 'planPolish.limit', settings.planPolish, 'limit', 'Сколько итераций «проверка + исправление» разрешить eda-plan-polish?', PLAN_POLISH_LIMIT_CHOICES],
832
- ['plan-execute', 'planExecute.mode', settings.planExecute, 'mode', 'Как eda-plan-execute должен выполнять фазы по умолчанию?', PLAN_EXECUTE_MODE_CHOICES],
833
- ['polish', 'polish.execution', settings.polish, 'execution', 'Как eda-polish должен выполнять review и исправления по умолчанию?', POLISH_EXECUTION_CHOICES],
834
- ['manual-test', 'manualTest.depth', settings.manualTest, 'depth', 'Какую глубину ручной проверки использовать в eda-manual-test по умолчанию?', MANUAL_TEST_DEPTH_CHOICES],
835
- ['review', 'review.execution', settings.review, 'execution', 'Как eda-review должен выполнять проверки по умолчанию?', REVIEW_EXECUTION_CHOICES]
836
- ];
837
- for (const [section, key, target, property, message, choices] of scalarPrompts) {
838
- if (!requestedSections.has(section) || !shouldAsk(key)) continue;
839
- target[property] = await selectPrompt({ message, choices, default: target[property] }, { input, output });
840
- }
841
-
842
- if (requestedSections.has('review')) {
843
- settings.review.agents = await askReviewAgentSettings({
844
- input,
845
- output,
846
- selectPrompt,
847
- seedAgents: settings.review.agents,
848
- missingKeys,
849
- skipModels: settings.review.execution === 'main'
850
- });
538
+ if (askOrhestraExecution) {
539
+ settings.orhestra.execution = await selectPrompt({
540
+ message: 'eda-orhestra · где выполнять этапы',
541
+ choices: ORHESTRA_EXECUTION_CHOICES,
542
+ default: settings.orhestra.execution
543
+ }, { input, output });
851
544
  }
545
+ finishPrompts(`Остальные настройки — в ${SETTINGS_RELATIVE_PATH}`, { output });
852
546
  return settings;
853
547
  }
854
548
 
855
- export async function askReviewAgentSettings({
856
- input = process.stdin,
857
- output = process.stdout,
858
- selectPrompt = select,
859
- seedAgents = DEFAULT_REVIEW_AGENTS,
860
- missingKeys = null,
861
- skipModels = false
862
- } = {}) {
863
- return askAgentSettings({
864
- input,
865
- output,
866
- selectPrompt,
867
- definitions: REVIEW_AGENT_DEFINITIONS,
868
- seedAgents,
869
- missingKeys,
870
- keyPrefix: 'review.agents',
871
- label: 'review',
872
- skipModels
873
- });
874
- }
875
-
876
- async function askAgentSettings({ input, output, selectPrompt, definitions, seedAgents, missingKeys, keyPrefix, label, skipModels = false }) {
877
- const configuredAgents = structuredClone(seedAgents);
878
- const shouldAsk = key => missingKeys === null || missingKeys.has(key);
879
- for (const agent of definitions) {
880
- const modeKey = `${keyPrefix}.${agent.key}.mode`;
881
- const claudeKey = `${keyPrefix}.${agent.key}.model.claude`;
882
- const codexKey = `${keyPrefix}.${agent.key}.model.codex`;
883
- let mode = configuredAgents[agent.key].mode;
884
- if (shouldAsk(modeKey)) {
885
- mode = await selectPrompt({
886
- message: `Когда запускать ${label}-проверку ${agent.key}?`,
887
- choices: REVIEW_MODE_CHOICES,
888
- default: mode
889
- }, { input, output });
890
- }
891
-
892
- let claude = configuredAgents[agent.key].model.claude;
893
- let codex = configuredAgents[agent.key].model.codex;
894
- if (!skipModels && mode !== 'off' && shouldAsk(claudeKey)) {
895
- claude = await selectPrompt({
896
- message: `Какой моделью Claude проверять ${agent.key}?`,
897
- choices: CLAUDE_REVIEW_MODEL_CHOICES,
898
- default: claude
899
- }, { input, output });
900
- }
901
- if (!skipModels && mode !== 'off' && shouldAsk(codexKey)) {
902
- codex = await selectPrompt({
903
- message: `Какой моделью Codex проверять ${agent.key}?`,
904
- choices: CODEX_REVIEW_MODEL_CHOICES,
905
- default: codex
906
- }, { input, output });
907
- }
908
- configuredAgents[agent.key] = { mode, model: { claude, codex } };
909
- }
910
- return configuredAgents;
911
- }
912
-
913
549
  async function detectTargets(cwd) {
914
550
  const targets = [];
915
551
  for (const { value } of TARGET_CHOICES) {
@@ -1141,6 +777,12 @@ async function ensureSettings(cwd, { input = process.stdin, output = process.std
1141
777
  if (migrated.retiredPaths.length > 0) {
1142
778
  output.write(`Убираю устаревшие настройки: ${migrated.retiredPaths.join(', ')}.\n`);
1143
779
  }
780
+ if (migrated.legacyReviewAgents) {
781
+ output.write('Перевожу review.agents на формат 4.0: все роли — default, нужные модели впишите вручную.\n');
782
+ }
783
+ if (migrated.resetReviewAgents.length > 0) {
784
+ output.write(`Заменяю недопустимые модели review.agents на default: ${migrated.resetReviewAgents.join(', ')}.\n`);
785
+ }
1144
786
  if (needsMigration) {
1145
787
  output.write(`Переношу ${SETTINGS_RELATIVE_PATH} на version: ${SETTINGS_VERSION}; спрашиваются только отсутствующие значения.\n`);
1146
788
  }
@@ -1179,6 +821,12 @@ async function configureSettings(cwd, { input = process.stdin, output = process.
1179
821
  if (migrated.retiredPaths.length > 0) {
1180
822
  output.write(`Убираю устаревшие настройки: ${migrated.retiredPaths.join(', ')}.\n`);
1181
823
  }
824
+ if (migrated.legacyReviewAgents) {
825
+ output.write('Перевожу review.agents на формат 4.0: все роли — default, нужные модели впишите вручную.\n');
826
+ }
827
+ if (migrated.resetReviewAgents.length > 0) {
828
+ output.write(`Заменяю недопустимые модели review.agents на default: ${migrated.resetReviewAgents.join(', ')}.\n`);
829
+ }
1182
830
  if (needsMigration) {
1183
831
  output.write(`Переношу ${SETTINGS_RELATIVE_PATH} на version: ${SETTINGS_VERSION}; спрашиваются только отсутствующие значения.\n`);
1184
832
  }
@@ -1198,7 +846,9 @@ function resolveSettingsUpdate(existingContent) {
1198
846
  const migrated = migrateSettingsContent(existingContent);
1199
847
  const needsMigration = migrated.version !== SETTINGS_VERSION
1200
848
  || migrated.missingKeys.size > 0
1201
- || migrated.normalizedPaths.length > 0;
849
+ || migrated.normalizedPaths.length > 0
850
+ || migrated.legacyReviewAgents
851
+ || migrated.resetReviewAgents.length > 0;
1202
852
  const rendered = formatSettings(migrated.settings);
1203
853
  return { migrated, needsMigration, rendered, formatChanged: rendered !== existingContent };
1204
854
  }
@@ -1269,7 +919,6 @@ function migrateSettingsContent(content) {
1269
919
 
1270
920
  const plan = objectValue(raw.plan);
1271
921
  transfer('plan.strict', plan.strict ?? legacyStrict, bool, value => { settings.plan.strict = value; });
1272
- transfer('plan.review', plan.review ?? plan.meta_review, bool, value => { settings.plan.review = value; });
1273
922
  transfer(
1274
923
  'plan.size',
1275
924
  plan.size ?? legacyDefaults.plan_size,
@@ -1332,17 +981,7 @@ function migrateSettingsContent(content) {
1332
981
  oneOf(['subagents', 'main']),
1333
982
  value => { settings.review.execution = value; }
1334
983
  );
1335
- transferAgentSettings({
1336
- rawAgents: objectValue(review.agents),
1337
- settingsAgents: settings.review.agents,
1338
- definitions: REVIEW_AGENT_DEFINITIONS,
1339
- prefix: 'review.agents',
1340
- missingKeys
1341
- });
1342
- if (typeof review.include_code_quality === 'boolean' && !objectValue(review.agents).code_quality) {
1343
- settings.review.agents.code_quality.mode = review.include_code_quality ? 'always' : 'off';
1344
- missingKeys.delete('review.agents.code_quality.mode');
1345
- }
984
+ const { legacyReviewAgents, resetReviewAgents } = transferReviewAgents(objectValue(review.agents), settings.review.agents);
1346
985
 
1347
986
  const sendReview = objectValue(raw['send-review']);
1348
987
  transfer(
@@ -1364,25 +1003,27 @@ function migrateSettingsContent(content) {
1364
1003
  settings,
1365
1004
  missingKeys,
1366
1005
  normalizedPaths,
1006
+ legacyReviewAgents,
1007
+ resetReviewAgents,
1367
1008
  retiredPaths: findRetiredSettingsPaths(raw),
1368
1009
  version: Number.isInteger(raw.version) ? raw.version : null
1369
1010
  };
1370
1011
  }
1371
1012
 
1372
- function transferAgentSettings({ rawAgents, settingsAgents, definitions, prefix, missingKeys }) {
1373
- const allowedModes = ['always', 'auto', 'off'];
1374
- const allowedClaudeModels = CLAUDE_REVIEW_MODEL_CHOICES.map(choice => choice.value);
1375
- const allowedCodexModels = CODEX_REVIEW_MODEL_CHOICES.map(choice => choice.value);
1376
- for (const definition of definitions) {
1377
- const configured = objectValue(rawAgents[definition.key]);
1378
- const model = objectValue(configured.model);
1379
- if (allowedModes.includes(configured.mode)) settingsAgents[definition.key].mode = configured.mode;
1380
- else missingKeys.add(`${prefix}.${definition.key}.mode`);
1381
- if (allowedClaudeModels.includes(model.claude)) settingsAgents[definition.key].model.claude = model.claude;
1382
- else missingKeys.add(`${prefix}.${definition.key}.model.claude`);
1383
- if (allowedCodexModels.includes(model.codex)) settingsAgents[definition.key].model.codex = model.codex;
1384
- else missingKeys.add(`${prefix}.${definition.key}.model.codex`);
1385
- }
1013
+ // Формат до 4.0 ({ mode, model } по платформам) не переносится: при мажорном обновлении
1014
+ // все роли получают default. Иное значение не в формате default | provider/model#variant тоже
1015
+ // заменяется на default.
1016
+ function transferReviewAgents(rawAgents, settingsAgents) {
1017
+ const legacyReviewAgents = Object.values(rawAgents).some(value => value && typeof value === 'object');
1018
+ const resetReviewAgents = [];
1019
+ if (legacyReviewAgents) return { legacyReviewAgents, resetReviewAgents };
1020
+ for (const key of REVIEW_AGENT_KEYS) {
1021
+ if (!Object.hasOwn(rawAgents, key)) continue;
1022
+ const value = typeof rawAgents[key] === 'string' ? rawAgents[key].trim() : '';
1023
+ if (REVIEW_AGENT_MODEL_PATTERN.test(value)) settingsAgents[key] = value;
1024
+ else resetReviewAgents.push(key);
1025
+ }
1026
+ return { legacyReviewAgents, resetReviewAgents };
1386
1027
  }
1387
1028
 
1388
1029
  // У eda-polish больше нет порога: цикл завершается по нулю находок.
@@ -1483,66 +1124,36 @@ ${onFailureThen}
1483
1124
  # Строка аргументов передаётся скиллу как часть текущего вызова.
1484
1125
  args: ${JSON.stringify(step.args ?? '')}${onFailure}`;
1485
1126
  }).join('\n');
1486
- const formatAgents = (definitions, configuredAgents, defaults) => definitions.map(agent => {
1487
- const configured = configuredAgents[agent.key] ?? defaults[agent.key];
1488
- return ` # ${agent.description}
1489
- # ${agent.auto}
1490
- ${agent.key}:
1491
- # always — всегда, auto — когда применимо, off — отключено.
1492
- mode: ${configured.mode}
1493
- model:
1494
- # Модель для Claude Code.
1495
- claude: ${configured.model.claude}
1496
- # Модель для Codex.
1497
- codex: ${configured.model.codex}`;
1498
- }).join('\n\n');
1499
- const reviewAgents = formatAgents(REVIEW_AGENT_DEFINITIONS, settings.review.agents, DEFAULT_REVIEW_AGENTS);
1127
+ const reviewAgents = REVIEW_AGENT_KEYS
1128
+ .map(key => ` ${key}: ${settings.review.agents[key] ?? DEFAULT_REVIEW_AGENTS[key]}`)
1129
+ .join('\n');
1500
1130
 
1501
1131
  return `version: ${SETTINGS_VERSION}
1502
1132
 
1503
- orhestra:
1504
- # Режим полного цикла eda-orhestra.
1505
- # automatic | manual
1506
- mode: ${orhestra.mode ?? 'automatic'}
1507
- # Где выполняются этапы и передаётся ли main совместимым вложенным скилам.
1508
- # subagents | main | main_propagated
1509
- execution: ${orhestra.execution ?? 'subagents'}
1510
- # Упорядоченная цепочка. Шаги можно отключать, удалять и переставлять.
1511
- steps:
1512
- ${orhestraSteps}
1513
-
1514
- aim:
1515
- # Режим ответов на рабочие вопросы eda-aim.
1516
- # automatic | manual
1517
- mode: ${aim.mode ?? 'automatic'}
1518
-
1519
1133
  explore:
1520
1134
  # Включает кросс-CLI ревью в eda-explore.
1521
- # true | false
1135
+ # false | true
1522
1136
  strict: ${settings.explore.strict ? 'true' : 'false'}
1523
- # Определяет, как eda-explore ведёт исследовательские развилки.
1524
- # autonomous | recommend_and_ask | ask_each_time
1137
+ # Как eda-explore ведёт исследовательские развилки.
1138
+ # recommend_and_ask | autonomous | ask_each_time
1525
1139
  decision_mode: ${settings.explore.decisionMode}
1526
1140
 
1527
1141
  plan:
1528
- # Включает кросс-CLI ревью в eda-plan.
1529
- # true | false
1530
- strict: ${settings.plan.strict ? 'true' : 'false'}
1531
- # Запускает изолированный цикл eda-plan-polish.
1532
- # true | false
1533
- review: ${settings.plan.review !== false ? 'true' : 'false'}
1534
- # Задаёт размер плана.
1142
+ # Размер плана.
1535
1143
  # normal | short | ask_each_time
1536
1144
  size: ${settings.plan.size}
1537
- # Определяет, как eda-plan принимает существенные решения.
1538
- # autonomous | recommend_and_ask | ask_each_time
1145
+ # Как eda-plan принимает существенные решения.
1146
+ # recommend_and_ask | autonomous | ask_each_time
1539
1147
  decision_mode: ${settings.plan.decisionMode}
1540
- # Задаёт стратегию тестов и автоматических проверок.
1541
- # after_each_phase | phase_tests_final_checks | tdd_each_phase | end_of_plan | ask_each_time
1148
+ # Когда писать тесты и запускать автоматические проверки.
1149
+ # phase_tests_final_checks | tdd_each_phase | after_each_phase | end_of_plan | ask_each_time
1542
1150
  test_strategy: ${settings.plan.testStrategy}
1543
- # Задаёт стратегию логирования.
1544
- # debug_precise | standard | ask_each_time
1151
+ # Стратегия логирования.
1152
+ # standard | debug_precise | ask_each_time
1545
1153
  logging_strategy: ${settings.plan.loggingStrategy}
1154
+ # Включает кросс-CLI ревью в eda-plan.
1155
+ # false | true
1156
+ strict: ${settings.plan.strict ? 'true' : 'false'}
1546
1157
 
1547
1158
  plan-review:
1548
1159
  # Доля закрытых пунктов чек-листа, при которой план считается готовым.
@@ -1556,9 +1167,21 @@ plan-polish:
1556
1167
 
1557
1168
  plan-execute:
1558
1169
  # Где eda-plan-execute выполняет фазы плана.
1559
- # subagents — каждая фаза в отдельном субагенте, main — весь план одним контекстом, auto — решает по плану.
1170
+ # main — весь план одним контекстом, auto — решает по плану, subagents — каждая фаза в отдельном субагенте.
1560
1171
  mode: ${settings.planExecute.mode}
1561
1172
 
1173
+ review:
1174
+ # Где eda-review выполняет выбранные проверки.
1175
+ # main — все проверки основным агентом без субагентов, subagents — разведчик и отдельный агент на проверку.
1176
+ execution: ${settings.review.execution ?? 'main'}
1177
+ # Какие проверки нужны, решает eda-review-triage по содержимому ревью.
1178
+ # Модель роли в subagents: default — модель установленного агента, иначе provider/model#variant,
1179
+ # например anthropic/claude-opus-5-5#high или deepseek/deepseek-flash-4.1; variant задаёт effort.
1180
+ # Если среда не может запустить модель, роль идёт на модели агента с предупреждением в ревью.
1181
+ # В main все проверки выполняет текущая модель.
1182
+ agents:
1183
+ ${reviewAgents}
1184
+
1562
1185
  polish:
1563
1186
  # Где eda-polish выполняет review и исправления цикла полировки.
1564
1187
  # subagents — каждое review и каждое исправление в отдельном изолированном субагенте, main — оба контракта основным агентом.
@@ -1571,14 +1194,6 @@ manual-test:
1571
1194
  # full | smoke | ask_each_time
1572
1195
  depth: ${settings.manualTest.depth}
1573
1196
 
1574
- review:
1575
- # Где eda-review выполняет выбранные проверки.
1576
- # subagents — отдельный специализированный агент на проверку, main — все проверки основным агентом без субагентов.
1577
- execution: ${settings.review.execution ?? 'subagents'}
1578
- # Каждая проверка имеет собственный режим запуска; модели применяются только в subagents.
1579
- agents:
1580
- ${reviewAgents}
1581
-
1582
1197
  send-review:
1583
1198
  # После успешной отправки скрывает предыдущие сводки eda-send-review и резолвит их inline-треды.
1584
1199
  # true | false
@@ -1586,8 +1201,24 @@ send-review:
1586
1201
 
1587
1202
  discover-automations:
1588
1203
  # Добавляет docs/artifacts/plans/ в обычный запуск eda-discover-automations.
1589
- # true | false
1204
+ # false | true
1590
1205
  include_plans: ${settings.discoverAutomations.includePlans ? 'true' : 'false'}
1206
+
1207
+ orhestra:
1208
+ # Режим полного цикла eda-orhestra.
1209
+ # automatic | manual
1210
+ mode: ${orhestra.mode ?? 'automatic'}
1211
+ # Где выполняются этапы и передаётся ли main совместимым вложенным скилам.
1212
+ # subagents | main | main_propagated
1213
+ execution: ${orhestra.execution ?? 'subagents'}
1214
+ # Упорядоченная цепочка. Шаги можно отключать, удалять и переставлять.
1215
+ steps:
1216
+ ${orhestraSteps}
1217
+
1218
+ aim:
1219
+ # Режим ответов на рабочие вопросы eda-aim.
1220
+ # automatic | manual
1221
+ mode: ${aim.mode ?? 'automatic'}
1591
1222
  `;
1592
1223
  }
1593
1224
 
@@ -1662,7 +1293,7 @@ async function buildSkillFiles(skill, target) {
1662
1293
  const entrypoint = files.get('SKILL.md');
1663
1294
  if (!entrypoint) throw new Error(`У скила ${skill.name} нет SKILL.md.`);
1664
1295
 
1665
- const content = entrypoint.toString('utf8');
1296
+ const content = await expandIncludes(`Скил ${skill.name}`, entrypoint.toString('utf8'), PACKAGE_ROOT);
1666
1297
  const renderSkill = { claude: renderClaudeSkill, codex: renderCodexSkill, opencode: renderOpenCodeSkill }[target];
1667
1298
  const rendered = renderSkill(content, skill.config);
1668
1299
  files.set('SKILL.md', Buffer.from(rendered));