@gian-tiaga/eda 1.0.5 → 2.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.
- package/MIGRATION.md +58 -80
- package/README.md +113 -32
- package/agents/eda-plan-review-api/agent.json +9 -0
- package/agents/eda-plan-review-api/prompt.md +34 -0
- package/agents/eda-plan-review-architecture/agent.json +9 -0
- package/agents/eda-plan-review-architecture/prompt.md +34 -0
- package/agents/eda-plan-review-database/agent.json +9 -0
- package/agents/eda-plan-review-database/prompt.md +34 -0
- package/agents/eda-plan-review-execution/agent.json +9 -0
- package/agents/eda-plan-review-execution/prompt.md +34 -0
- package/agents/eda-plan-review-feasibility/agent.json +9 -0
- package/agents/eda-plan-review-feasibility/prompt.md +34 -0
- package/agents/eda-plan-review-frontend/agent.json +9 -0
- package/agents/eda-plan-review-frontend/prompt.md +34 -0
- package/agents/eda-plan-review-performance/agent.json +9 -0
- package/agents/eda-plan-review-performance/prompt.md +34 -0
- package/agents/eda-plan-review-previous/agent.json +9 -0
- package/agents/eda-plan-review-previous/prompt.md +28 -0
- package/agents/eda-plan-review-requirements/agent.json +9 -0
- package/agents/eda-plan-review-requirements/prompt.md +34 -0
- package/agents/eda-plan-review-rules/agent.json +9 -0
- package/agents/eda-plan-review-rules/prompt.md +34 -0
- package/agents/eda-plan-review-security/agent.json +9 -0
- package/agents/eda-plan-review-security/prompt.md +34 -0
- package/agents/eda-plan-review-verification/agent.json +9 -0
- package/agents/eda-plan-review-verification/prompt.md +34 -0
- package/agents/eda-review-business/agent.json +15 -0
- package/agents/eda-review-business/prompt.md +46 -0
- package/bin/cli.js +2 -2
- package/lib/install.js +524 -368
- package/package.json +3 -2
- package/skills/eda-aim/SKILL.md +1 -1
- package/skills/eda-business/SKILL.md +147 -0
- package/skills/eda-discover-automations/SKILL.md +4 -2
- package/skills/eda-explore/SKILL.md +7 -6
- package/skills/eda-fix/SKILL.md +5 -4
- package/skills/eda-fix-by-review/SKILL.md +5 -4
- package/skills/eda-manual-test/SKILL.md +13 -5
- package/skills/eda-orhestra/SKILL.md +13 -10
- package/skills/eda-plan/SKILL.md +46 -38
- package/skills/eda-plan-execute/SKILL.md +8 -6
- package/skills/eda-plan-polish/SKILL.md +53 -169
- package/skills/eda-plan-review/SKILL.md +188 -0
- package/skills/eda-plan-review-fix/SKILL.md +105 -0
- package/skills/eda-prepare-ai/SKILL.md +7 -2
- package/skills/eda-review/SKILL.md +13 -5
- package/skills/eda-roadmap/SKILL.md +6 -3
- package/skills/eda-send-review/SKILL.md +1 -1
package/lib/install.js
CHANGED
|
@@ -4,6 +4,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
4
4
|
import fs from 'node:fs/promises';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { parse as parseYaml } from 'yaml';
|
|
7
8
|
|
|
8
9
|
import { listAgents, listSkills } from './catalog.js';
|
|
9
10
|
import { renderClaudeAgent } from './renderers/claude-agent.js';
|
|
@@ -36,6 +37,7 @@ const RETIRED_AGENTS = [
|
|
|
36
37
|
'eda-commit-context'
|
|
37
38
|
];
|
|
38
39
|
const MANIFEST_FILE = 'eda-manifest.json';
|
|
40
|
+
const SETTINGS_VERSION = 3;
|
|
39
41
|
const REVIEW_AGENT_DEFINITIONS = [
|
|
40
42
|
{
|
|
41
43
|
key: 'correctness',
|
|
@@ -69,6 +71,14 @@ const REVIEW_AGENT_DEFINITIONS = [
|
|
|
69
71
|
claude: 'haiku',
|
|
70
72
|
codex: 'gpt-5.6-luna'
|
|
71
73
|
},
|
|
74
|
+
{
|
|
75
|
+
key: 'business',
|
|
76
|
+
description: 'Проверяет изменённое поведение по применимым карточкам из docs/business/.',
|
|
77
|
+
auto: 'В auto запускается только когда изменения затрагивают поведение с применимой business-карточкой.',
|
|
78
|
+
mode: 'auto',
|
|
79
|
+
claude: 'sonnet',
|
|
80
|
+
codex: 'gpt-5.6-terra'
|
|
81
|
+
},
|
|
72
82
|
{
|
|
73
83
|
key: 'plan_alignment',
|
|
74
84
|
description: 'Проверяет, что реализация соответствует указанному или однозначно найденному плану.',
|
|
@@ -160,11 +170,126 @@ const DEFAULT_REVIEW_AGENTS = Object.fromEntries(REVIEW_AGENT_DEFINITIONS.map(ag
|
|
|
160
170
|
}
|
|
161
171
|
}
|
|
162
172
|
]));
|
|
173
|
+
const PLAN_REVIEW_AGENT_DEFINITIONS = [
|
|
174
|
+
{
|
|
175
|
+
key: 'requirements',
|
|
176
|
+
description: 'Проверяет соответствие плана задаче, scope, критериям готовности и применимым business-требованиям.',
|
|
177
|
+
auto: 'Эта базовая проверка по умолчанию запускается всегда.',
|
|
178
|
+
mode: 'always',
|
|
179
|
+
claude: 'sonnet',
|
|
180
|
+
codex: 'gpt-5.6-terra'
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
key: 'rules',
|
|
184
|
+
description: 'Проверяет соблюдение AGENTS.md, CLAUDE.md и docs/rules.md.',
|
|
185
|
+
auto: 'Эта базовая проверка по умолчанию запускается всегда.',
|
|
186
|
+
mode: 'always',
|
|
187
|
+
claude: 'haiku',
|
|
188
|
+
codex: 'gpt-5.6-luna'
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
key: 'architecture',
|
|
192
|
+
description: 'Проверяет архитектурные границы, зависимости и соответствие docs/arch.md.',
|
|
193
|
+
auto: 'Эта базовая проверка по умолчанию запускается всегда.',
|
|
194
|
+
mode: 'always',
|
|
195
|
+
claude: 'opus',
|
|
196
|
+
codex: 'gpt-5.6-sol'
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
key: 'feasibility',
|
|
200
|
+
description: 'Проверяет реализуемость плана относительно фактического кода и отсутствие скрытого исследования.',
|
|
201
|
+
auto: 'Эта базовая проверка по умолчанию запускается всегда.',
|
|
202
|
+
mode: 'always',
|
|
203
|
+
claude: 'opus',
|
|
204
|
+
codex: 'gpt-5.6-sol'
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
key: 'execution',
|
|
208
|
+
description: 'Проверяет связность фаз, ID задач, зависимости и размер работы для одного исполнителя.',
|
|
209
|
+
auto: 'Эта базовая проверка по умолчанию запускается всегда.',
|
|
210
|
+
mode: 'always',
|
|
211
|
+
claude: 'sonnet',
|
|
212
|
+
codex: 'gpt-5.6-terra'
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
key: 'verification',
|
|
216
|
+
description: 'Проверяет тесты, команды проверок, ошибки, логирование, документацию и эксплуатационную готовность.',
|
|
217
|
+
auto: 'Эта базовая проверка по умолчанию запускается всегда.',
|
|
218
|
+
mode: 'always',
|
|
219
|
+
claude: 'sonnet',
|
|
220
|
+
codex: 'gpt-5.6-terra'
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
key: 'api',
|
|
224
|
+
description: 'Проверяет API, DTO, события, очереди, внешние интеграции и совместимость контрактов.',
|
|
225
|
+
auto: 'В auto запускается при изменении API, событий, очередей или внешних интеграций.',
|
|
226
|
+
mode: 'auto',
|
|
227
|
+
claude: 'sonnet',
|
|
228
|
+
codex: 'gpt-5.6-terra'
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
key: 'database',
|
|
232
|
+
description: 'Проверяет схему данных, миграции, backfill, индексы, транзакции и rollback.',
|
|
233
|
+
auto: 'В auto запускается при изменении хранения, моделей, SQL, схем или миграций.',
|
|
234
|
+
mode: 'auto',
|
|
235
|
+
claude: 'opus',
|
|
236
|
+
codex: 'gpt-5.6-sol'
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
key: 'security',
|
|
240
|
+
description: 'Проверяет auth, permissions, внешний ввод, секреты, персональные данные и опасные операции.',
|
|
241
|
+
auto: 'В auto запускается для auth, permissions, внешнего ввода, секретов и чувствительных данных.',
|
|
242
|
+
mode: 'auto',
|
|
243
|
+
claude: 'opus',
|
|
244
|
+
codex: 'gpt-5.6-sol'
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
key: 'frontend',
|
|
248
|
+
description: 'Проверяет пользовательский сценарий, состояния UI, ошибки, accessibility и адаптивность.',
|
|
249
|
+
auto: 'В auto запускается для компонентов, стилей, клиентских маршрутов и состояния UI.',
|
|
250
|
+
mode: 'auto',
|
|
251
|
+
claude: 'sonnet',
|
|
252
|
+
codex: 'gpt-5.6-terra'
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
key: 'performance',
|
|
256
|
+
description: 'Проверяет нагрузку, запросы, конкурентность, кеши и горячие пути.',
|
|
257
|
+
auto: 'В auto запускается для запросов, пакетной обработки, кешей, конкурентности и горячих путей.',
|
|
258
|
+
mode: 'auto',
|
|
259
|
+
claude: 'opus',
|
|
260
|
+
codex: 'gpt-5.6-sol'
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
key: 'previous',
|
|
264
|
+
description: 'Проверяет закрытие findings предыдущей связанной итерации plan-review.',
|
|
265
|
+
auto: 'В auto запускается только при наличии связанной цепочки review/fix для текущего SHA-256 плана.',
|
|
266
|
+
mode: 'auto',
|
|
267
|
+
claude: 'haiku',
|
|
268
|
+
codex: 'gpt-5.6-luna'
|
|
269
|
+
}
|
|
270
|
+
];
|
|
271
|
+
const DEFAULT_PLAN_REVIEW_AGENTS = Object.fromEntries(PLAN_REVIEW_AGENT_DEFINITIONS.map(agent => [
|
|
272
|
+
agent.key,
|
|
273
|
+
{
|
|
274
|
+
mode: agent.mode,
|
|
275
|
+
model: {
|
|
276
|
+
claude: agent.claude,
|
|
277
|
+
codex: agent.codex
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
]));
|
|
163
281
|
const ORHESTRA_STEP_DEFINITIONS = [
|
|
164
282
|
{
|
|
165
283
|
id: 'plan',
|
|
166
284
|
skill: 'eda-plan',
|
|
167
|
-
name: 'Планирование через eda-plan',
|
|
285
|
+
name: 'Планирование без проверок через eda-plan',
|
|
286
|
+
enabled: true,
|
|
287
|
+
args: 'без проверок'
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
id: 'plan-polish',
|
|
291
|
+
skill: 'eda-plan-polish',
|
|
292
|
+
name: 'Полировка плана через eda-plan-polish',
|
|
168
293
|
enabled: true,
|
|
169
294
|
args: ''
|
|
170
295
|
},
|
|
@@ -191,7 +316,7 @@ const ORHESTRA_STEP_DEFINITIONS = [
|
|
|
191
316
|
onFailure: {
|
|
192
317
|
skill: 'eda-fix',
|
|
193
318
|
args: '',
|
|
194
|
-
then: ['
|
|
319
|
+
then: ['manual-test'],
|
|
195
320
|
maxCycles: 5
|
|
196
321
|
}
|
|
197
322
|
}
|
|
@@ -211,13 +336,18 @@ const DEFAULT_SETTINGS = {
|
|
|
211
336
|
},
|
|
212
337
|
plan: {
|
|
213
338
|
strict: false,
|
|
339
|
+
review: true,
|
|
214
340
|
size: 'normal',
|
|
215
341
|
decisionMode: 'recommend_and_ask',
|
|
216
342
|
testStrategy: 'ask_each_time',
|
|
217
343
|
loggingStrategy: 'ask_each_time'
|
|
218
344
|
},
|
|
345
|
+
planReview: {
|
|
346
|
+
threshold: 95,
|
|
347
|
+
agents: DEFAULT_PLAN_REVIEW_AGENTS
|
|
348
|
+
},
|
|
219
349
|
planPolish: {
|
|
220
|
-
|
|
350
|
+
limit: 3
|
|
221
351
|
},
|
|
222
352
|
review: {
|
|
223
353
|
agents: DEFAULT_REVIEW_AGENTS
|
|
@@ -234,6 +364,7 @@ const SETTINGS_SECTION_NAMES = [
|
|
|
234
364
|
'aim',
|
|
235
365
|
'explore',
|
|
236
366
|
'plan',
|
|
367
|
+
'plan-review',
|
|
237
368
|
'plan-polish',
|
|
238
369
|
'review',
|
|
239
370
|
'send-review',
|
|
@@ -242,30 +373,35 @@ const SETTINGS_SECTION_NAMES = [
|
|
|
242
373
|
const SETTINGS_CHOICES = [
|
|
243
374
|
{
|
|
244
375
|
section: 'explore',
|
|
376
|
+
key: 'explore.strict',
|
|
245
377
|
value: 'exploreStrict',
|
|
246
378
|
name: 'Strict по умолчанию для eda-explore',
|
|
247
379
|
checked: DEFAULT_SETTINGS.explore.strict
|
|
248
380
|
},
|
|
249
381
|
{
|
|
250
382
|
section: 'plan',
|
|
383
|
+
key: 'plan.strict',
|
|
251
384
|
value: 'planStrict',
|
|
252
385
|
name: 'Strict по умолчанию для eda-plan',
|
|
253
386
|
checked: DEFAULT_SETTINGS.plan.strict
|
|
254
387
|
},
|
|
255
388
|
{
|
|
256
|
-
section: 'plan
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
389
|
+
section: 'plan',
|
|
390
|
+
key: 'plan.review',
|
|
391
|
+
value: 'planReview',
|
|
392
|
+
name: 'Запускать eda-plan-review внутри eda-plan',
|
|
393
|
+
checked: DEFAULT_SETTINGS.plan.review
|
|
260
394
|
},
|
|
261
395
|
{
|
|
262
396
|
section: 'send-review',
|
|
397
|
+
key: 'sendReview.closePreviousReviews',
|
|
263
398
|
value: 'sendReviewClosePreviousReviews',
|
|
264
399
|
name: 'Закрывать предыдущие ревью eda-send-review',
|
|
265
400
|
checked: DEFAULT_SETTINGS.sendReview.closePreviousReviews
|
|
266
401
|
},
|
|
267
402
|
{
|
|
268
403
|
section: 'discover-automations',
|
|
404
|
+
key: 'discoverAutomations.includePlans',
|
|
269
405
|
value: 'discoverAutomationsIncludePlans',
|
|
270
406
|
name: 'Анализировать планы в eda-discover-automations по умолчанию',
|
|
271
407
|
checked: DEFAULT_SETTINGS.discoverAutomations.includePlans
|
|
@@ -295,6 +431,14 @@ const ORHESTRA_POLISH_LIMIT_CHOICES = [1, 2, 3, 5, 10].map(value => ({
|
|
|
295
431
|
value,
|
|
296
432
|
name: `${value} ${value === 1 ? 'итерация' : value < 5 ? 'итерации' : 'итераций'}`
|
|
297
433
|
}));
|
|
434
|
+
const PLAN_REVIEW_THRESHOLD_CHOICES = [90, 95, 100].map(value => ({
|
|
435
|
+
value,
|
|
436
|
+
name: `${value}/100`
|
|
437
|
+
}));
|
|
438
|
+
const PLAN_POLISH_LIMIT_CHOICES = [1, 2, 3, 5, 10].map(value => ({
|
|
439
|
+
value,
|
|
440
|
+
name: `${value} ${value === 1 ? 'итерация' : value < 5 ? 'итерации' : 'итераций'}`
|
|
441
|
+
}));
|
|
298
442
|
const PLAN_SIZE_CHOICES = [
|
|
299
443
|
{
|
|
300
444
|
value: 'normal',
|
|
@@ -377,7 +521,7 @@ const UPDATE_ALL_SETTINGS_MODE_CHOICES = [
|
|
|
377
521
|
},
|
|
378
522
|
{
|
|
379
523
|
value: 'skip',
|
|
380
|
-
name: 'Skip — сохранить
|
|
524
|
+
name: 'Skip — сохранить полный v3, остальные конфиги перенести с defaults'
|
|
381
525
|
}
|
|
382
526
|
];
|
|
383
527
|
|
|
@@ -446,7 +590,7 @@ export async function updateAll({
|
|
|
446
590
|
const sharedSettings = await askSettings({ input, output });
|
|
447
591
|
sharedSettingsContent = formatSettings(sharedSettings);
|
|
448
592
|
} else {
|
|
449
|
-
output.write(`Режим skip:
|
|
593
|
+
output.write(`Режим skip: полный version: ${SETTINGS_VERSION} сохраняется, старые конфиги переносятся, отсутствующие создаются с defaults.\n`);
|
|
450
594
|
sharedSettingsContent = formatSettings(structuredClone(DEFAULT_SETTINGS));
|
|
451
595
|
}
|
|
452
596
|
|
|
@@ -469,7 +613,14 @@ export async function updateAll({
|
|
|
469
613
|
output.write(`Найдены установленные среды: ${targets.join(', ')}\n`);
|
|
470
614
|
const projectSettingsPath = path.join(projectDir, SETTINGS_RELATIVE_PATH);
|
|
471
615
|
if (resolvedSettingsMode === 'skip' && await fileExists(projectSettingsPath)) {
|
|
472
|
-
|
|
616
|
+
const existingContent = await fs.readFile(projectSettingsPath, 'utf8');
|
|
617
|
+
const migrated = migrateSettingsContent(existingContent);
|
|
618
|
+
if (migrated.version === SETTINGS_VERSION && migrated.missingKeys.size === 0) {
|
|
619
|
+
output.write(`Полный файл настроек сохранён: ${SETTINGS_RELATIVE_PATH}\n`);
|
|
620
|
+
} else {
|
|
621
|
+
await writeSettingsContent(projectDir, formatSettings(migrated.settings));
|
|
622
|
+
output.write(`Старый или неполный файл настроек перенесён на version: ${SETTINGS_VERSION}: ${SETTINGS_RELATIVE_PATH}\n`);
|
|
623
|
+
}
|
|
473
624
|
} else {
|
|
474
625
|
await writeSettingsContent(projectDir, sharedSettingsContent);
|
|
475
626
|
output.write(resolvedSettingsMode === 'skip'
|
|
@@ -537,7 +688,7 @@ export async function askUpdateAllSettingsMode({
|
|
|
537
688
|
selectPrompt = select
|
|
538
689
|
} = {}) {
|
|
539
690
|
if (!input.isTTY || !output.isTTY) {
|
|
540
|
-
output.write(`Нет интерактивного терминала — использую режим skip: сохраняю
|
|
691
|
+
output.write(`Нет интерактивного терминала — использую режим skip: сохраняю полный v3, старые конфиги переношу с defaults.\n`);
|
|
541
692
|
return 'skip';
|
|
542
693
|
}
|
|
543
694
|
|
|
@@ -556,212 +707,186 @@ export async function askSettings({
|
|
|
556
707
|
output = process.stdout,
|
|
557
708
|
sections = SETTINGS_SECTION_NAMES,
|
|
558
709
|
checkboxPrompt = checkbox,
|
|
559
|
-
selectPrompt = select
|
|
710
|
+
selectPrompt = select,
|
|
711
|
+
seedSettings = DEFAULT_SETTINGS,
|
|
712
|
+
missingKeys = null
|
|
560
713
|
} = {}) {
|
|
714
|
+
const settings = structuredClone(seedSettings);
|
|
561
715
|
if (!input.isTTY || !output.isTTY) {
|
|
562
|
-
output.write(`Нет интерактивного терминала — использую
|
|
563
|
-
return
|
|
716
|
+
output.write(`Нет интерактивного терминала — использую перенесённые значения и defaults ${SETTINGS_RELATIVE_PATH}.\n`);
|
|
717
|
+
return settings;
|
|
564
718
|
}
|
|
565
719
|
|
|
566
720
|
const requestedSections = new Set(sections);
|
|
567
|
-
const
|
|
721
|
+
const shouldAsk = key => missingKeys === null || missingKeys.has(key);
|
|
722
|
+
const booleanChoices = SETTINGS_CHOICES.filter(choice => (
|
|
723
|
+
requestedSections.has(choice.section) && shouldAsk(choice.key)
|
|
724
|
+
));
|
|
568
725
|
const selected = booleanChoices.length > 0
|
|
569
726
|
? await checkboxPrompt({
|
|
570
727
|
message: 'Какие настройки включить?',
|
|
571
728
|
instructions: 'Стрелки — выбрать, Space — отметить, Enter — продолжить',
|
|
572
|
-
choices: booleanChoices.map(({ section, ...choice }) => choice)
|
|
573
|
-
}, {
|
|
574
|
-
input,
|
|
575
|
-
output
|
|
576
|
-
})
|
|
729
|
+
choices: booleanChoices.map(({ section, key, ...choice }) => choice)
|
|
730
|
+
}, { input, output })
|
|
577
731
|
: [];
|
|
578
732
|
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
733
|
+
if (requestedSections.has('explore') && shouldAsk('explore.strict')) {
|
|
734
|
+
settings.explore.strict = selected.includes('exploreStrict');
|
|
735
|
+
}
|
|
736
|
+
if (requestedSections.has('plan') && shouldAsk('plan.strict')) {
|
|
737
|
+
settings.plan.strict = selected.includes('planStrict');
|
|
738
|
+
}
|
|
739
|
+
if (requestedSections.has('plan') && shouldAsk('plan.review')) {
|
|
740
|
+
settings.plan.review = selected.includes('planReview');
|
|
741
|
+
}
|
|
742
|
+
if (requestedSections.has('send-review') && shouldAsk('sendReview.closePreviousReviews')) {
|
|
743
|
+
settings.sendReview.closePreviousReviews = selected.includes('sendReviewClosePreviousReviews');
|
|
744
|
+
}
|
|
745
|
+
if (requestedSections.has('discover-automations') && shouldAsk('discoverAutomations.includePlans')) {
|
|
746
|
+
settings.discoverAutomations.includePlans = selected.includes('discoverAutomationsIncludePlans');
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
if (requestedSections.has('orhestra') && shouldAsk('orhestra.mode')) {
|
|
750
|
+
settings.orhestra.mode = await selectPrompt({
|
|
751
|
+
message: 'Как eda-orhestra должен вести полный цикл по умолчанию?',
|
|
752
|
+
choices: ORHESTRA_MODE_CHOICES,
|
|
753
|
+
default: settings.orhestra.mode
|
|
754
|
+
}, { input, output });
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
if (requestedSections.has('orhestra') && shouldAsk('orhestra.steps')) {
|
|
758
|
+
const enabledSteps = await checkboxPrompt({
|
|
759
|
+
message: 'Какие этапы eda-orhestra включить по умолчанию?',
|
|
760
|
+
instructions: 'Порядок можно изменить вручную в docs/settings.yaml',
|
|
761
|
+
choices: ORHESTRA_STEP_DEFINITIONS.map(step => ({
|
|
762
|
+
name: step.name,
|
|
763
|
+
value: step.skill,
|
|
764
|
+
checked: settings.orhestra.steps.find(item => item.skill === step.skill)?.enabled !== false
|
|
765
|
+
}))
|
|
766
|
+
}, { input, output });
|
|
767
|
+
const codePolishLimit = enabledSteps.includes('eda-polish')
|
|
768
|
+
? await selectPrompt({
|
|
607
769
|
message: 'Сколько итераций разрешить eda-polish внутри eda-orhestra?',
|
|
608
770
|
choices: ORHESTRA_POLISH_LIMIT_CHOICES,
|
|
609
771
|
default: 5
|
|
610
|
-
}, {
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
enabled: enabledOrhestraSteps.includes(step.skill),
|
|
619
|
-
...(step.skill === 'eda-polish' ? { args: `limit ${polishLimit}` } : {})
|
|
620
|
-
}));
|
|
621
|
-
|
|
622
|
-
const aimMode = requestedSections.has('aim')
|
|
623
|
-
? await selectPrompt({
|
|
624
|
-
message: 'Как eda-aim должен отвечать на рабочие вопросы по умолчанию?',
|
|
625
|
-
choices: AIM_MODE_CHOICES,
|
|
626
|
-
default: DEFAULT_SETTINGS.aim.mode
|
|
627
|
-
}, {
|
|
628
|
-
input,
|
|
629
|
-
output
|
|
630
|
-
})
|
|
631
|
-
: DEFAULT_SETTINGS.aim.mode;
|
|
632
|
-
|
|
633
|
-
const exploreDecisionMode = requestedSections.has('explore')
|
|
634
|
-
? await selectPrompt({
|
|
635
|
-
message: 'Как принимать важные решения в eda-explore?',
|
|
636
|
-
choices: DECISION_MODE_CHOICES,
|
|
637
|
-
default: DEFAULT_SETTINGS.explore.decisionMode
|
|
638
|
-
}, {
|
|
639
|
-
input,
|
|
640
|
-
output
|
|
641
|
-
})
|
|
642
|
-
: DEFAULT_SETTINGS.explore.decisionMode;
|
|
643
|
-
|
|
644
|
-
const planSize = requestedSections.has('plan')
|
|
645
|
-
? await selectPrompt({
|
|
646
|
-
message: 'Какой размер плана eda-plan использовать по умолчанию?',
|
|
647
|
-
choices: PLAN_SIZE_CHOICES,
|
|
648
|
-
default: DEFAULT_SETTINGS.plan.size
|
|
649
|
-
}, {
|
|
650
|
-
input,
|
|
651
|
-
output
|
|
652
|
-
})
|
|
653
|
-
: DEFAULT_SETTINGS.plan.size;
|
|
654
|
-
|
|
655
|
-
const planDecisionMode = requestedSections.has('plan')
|
|
656
|
-
? await selectPrompt({
|
|
657
|
-
message: 'Как принимать важные решения в eda-plan?',
|
|
658
|
-
choices: DECISION_MODE_CHOICES,
|
|
659
|
-
default: DEFAULT_SETTINGS.plan.decisionMode
|
|
660
|
-
}, {
|
|
661
|
-
input,
|
|
662
|
-
output
|
|
663
|
-
})
|
|
664
|
-
: DEFAULT_SETTINGS.plan.decisionMode;
|
|
665
|
-
|
|
666
|
-
const testStrategy = requestedSections.has('plan')
|
|
667
|
-
? await selectPrompt({
|
|
668
|
-
message: 'Какую стратегию тестов eda-plan использовать по умолчанию?',
|
|
669
|
-
choices: TEST_STRATEGY_CHOICES,
|
|
670
|
-
default: DEFAULT_SETTINGS.plan.testStrategy
|
|
671
|
-
}, {
|
|
672
|
-
input,
|
|
673
|
-
output
|
|
674
|
-
})
|
|
675
|
-
: DEFAULT_SETTINGS.plan.testStrategy;
|
|
676
|
-
|
|
677
|
-
const loggingStrategy = requestedSections.has('plan')
|
|
678
|
-
? await selectPrompt({
|
|
679
|
-
message: 'Какую стратегию логирования eda-plan использовать по умолчанию?',
|
|
680
|
-
choices: LOGGING_STRATEGY_CHOICES,
|
|
681
|
-
default: DEFAULT_SETTINGS.plan.loggingStrategy
|
|
682
|
-
}, {
|
|
683
|
-
input,
|
|
684
|
-
output
|
|
685
|
-
})
|
|
686
|
-
: DEFAULT_SETTINGS.plan.loggingStrategy;
|
|
687
|
-
|
|
688
|
-
const reviewAgents = requestedSections.has('review')
|
|
689
|
-
? await askReviewAgentSettings({ input, output, selectPrompt })
|
|
690
|
-
: structuredClone(DEFAULT_REVIEW_AGENTS);
|
|
772
|
+
}, { input, output })
|
|
773
|
+
: 5;
|
|
774
|
+
settings.orhestra.steps = DEFAULT_ORHESTRA_STEPS.map(step => ({
|
|
775
|
+
...step,
|
|
776
|
+
enabled: enabledSteps.includes(step.skill),
|
|
777
|
+
...(step.skill === 'eda-polish' ? { args: `limit ${codePolishLimit}` } : {})
|
|
778
|
+
}));
|
|
779
|
+
}
|
|
691
780
|
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
781
|
+
const scalarPrompts = [
|
|
782
|
+
['aim', 'aim.mode', settings.aim, 'mode', 'Как eda-aim должен отвечать на рабочие вопросы по умолчанию?', AIM_MODE_CHOICES],
|
|
783
|
+
['explore', 'explore.decisionMode', settings.explore, 'decisionMode', 'Как принимать важные решения в eda-explore?', DECISION_MODE_CHOICES],
|
|
784
|
+
['plan', 'plan.size', settings.plan, 'size', 'Какой размер плана eda-plan использовать по умолчанию?', PLAN_SIZE_CHOICES],
|
|
785
|
+
['plan', 'plan.decisionMode', settings.plan, 'decisionMode', 'Как принимать важные решения в eda-plan?', DECISION_MODE_CHOICES],
|
|
786
|
+
['plan', 'plan.testStrategy', settings.plan, 'testStrategy', 'Какую стратегию тестов eda-plan использовать по умолчанию?', TEST_STRATEGY_CHOICES],
|
|
787
|
+
['plan', 'plan.loggingStrategy', settings.plan, 'loggingStrategy', 'Какую стратегию логирования eda-plan использовать по умолчанию?', LOGGING_STRATEGY_CHOICES],
|
|
788
|
+
['plan-review', 'planReview.threshold', settings.planReview, 'threshold', 'Какой порог готовности использовать в eda-plan-review?', PLAN_REVIEW_THRESHOLD_CHOICES],
|
|
789
|
+
['plan-polish', 'planPolish.limit', settings.planPolish, 'limit', 'Сколько review-итераций разрешить eda-plan-polish?', PLAN_POLISH_LIMIT_CHOICES]
|
|
790
|
+
];
|
|
791
|
+
for (const [section, key, target, property, message, choices] of scalarPrompts) {
|
|
792
|
+
if (!requestedSections.has(section) || !shouldAsk(key)) continue;
|
|
793
|
+
target[property] = await selectPrompt({ message, choices, default: target[property] }, { input, output });
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
if (requestedSections.has('plan-review')) {
|
|
797
|
+
settings.planReview.agents = await askPlanReviewAgentSettings({
|
|
798
|
+
input,
|
|
799
|
+
output,
|
|
800
|
+
selectPrompt,
|
|
801
|
+
seedAgents: settings.planReview.agents,
|
|
802
|
+
missingKeys
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
if (requestedSections.has('review')) {
|
|
806
|
+
settings.review.agents = await askReviewAgentSettings({
|
|
807
|
+
input,
|
|
808
|
+
output,
|
|
809
|
+
selectPrompt,
|
|
810
|
+
seedAgents: settings.review.agents,
|
|
811
|
+
missingKeys
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
return settings;
|
|
724
815
|
}
|
|
725
816
|
|
|
726
817
|
export async function askReviewAgentSettings({
|
|
727
818
|
input = process.stdin,
|
|
728
819
|
output = process.stdout,
|
|
729
|
-
selectPrompt = select
|
|
820
|
+
selectPrompt = select,
|
|
821
|
+
seedAgents = DEFAULT_REVIEW_AGENTS,
|
|
822
|
+
missingKeys = null
|
|
730
823
|
} = {}) {
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
824
|
+
return askAgentSettings({
|
|
825
|
+
input,
|
|
826
|
+
output,
|
|
827
|
+
selectPrompt,
|
|
828
|
+
definitions: REVIEW_AGENT_DEFINITIONS,
|
|
829
|
+
seedAgents,
|
|
830
|
+
missingKeys,
|
|
831
|
+
keyPrefix: 'review.agents',
|
|
832
|
+
label: 'review'
|
|
833
|
+
});
|
|
834
|
+
}
|
|
741
835
|
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
836
|
+
export async function askPlanReviewAgentSettings({
|
|
837
|
+
input = process.stdin,
|
|
838
|
+
output = process.stdout,
|
|
839
|
+
selectPrompt = select,
|
|
840
|
+
seedAgents = DEFAULT_PLAN_REVIEW_AGENTS,
|
|
841
|
+
missingKeys = null
|
|
842
|
+
} = {}) {
|
|
843
|
+
return askAgentSettings({
|
|
844
|
+
input,
|
|
845
|
+
output,
|
|
846
|
+
selectPrompt,
|
|
847
|
+
definitions: PLAN_REVIEW_AGENT_DEFINITIONS,
|
|
848
|
+
seedAgents,
|
|
849
|
+
missingKeys,
|
|
850
|
+
keyPrefix: 'planReview.agents',
|
|
851
|
+
label: 'plan-review'
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
async function askAgentSettings({ input, output, selectPrompt, definitions, seedAgents, missingKeys, keyPrefix, label }) {
|
|
856
|
+
const configuredAgents = structuredClone(seedAgents);
|
|
857
|
+
const shouldAsk = key => missingKeys === null || missingKeys.has(key);
|
|
858
|
+
for (const agent of definitions) {
|
|
859
|
+
const modeKey = `${keyPrefix}.${agent.key}.mode`;
|
|
860
|
+
const claudeKey = `${keyPrefix}.${agent.key}.model.claude`;
|
|
861
|
+
const codexKey = `${keyPrefix}.${agent.key}.model.codex`;
|
|
862
|
+
let mode = configuredAgents[agent.key].mode;
|
|
863
|
+
if (shouldAsk(modeKey)) {
|
|
864
|
+
mode = await selectPrompt({
|
|
865
|
+
message: `Когда запускать ${label}-проверку ${agent.key}?`,
|
|
866
|
+
choices: REVIEW_MODE_CHOICES,
|
|
867
|
+
default: mode
|
|
868
|
+
}, { input, output });
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
let claude = configuredAgents[agent.key].model.claude;
|
|
872
|
+
let codex = configuredAgents[agent.key].model.codex;
|
|
873
|
+
if (mode !== 'off' && shouldAsk(claudeKey)) {
|
|
745
874
|
claude = await selectPrompt({
|
|
746
875
|
message: `Какой моделью Claude проверять ${agent.key}?`,
|
|
747
876
|
choices: CLAUDE_REVIEW_MODEL_CHOICES,
|
|
748
|
-
default:
|
|
749
|
-
}, {
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
});
|
|
877
|
+
default: claude
|
|
878
|
+
}, { input, output });
|
|
879
|
+
}
|
|
880
|
+
if (mode !== 'off' && shouldAsk(codexKey)) {
|
|
753
881
|
codex = await selectPrompt({
|
|
754
882
|
message: `Какой моделью Codex проверять ${agent.key}?`,
|
|
755
883
|
choices: CODEX_REVIEW_MODEL_CHOICES,
|
|
756
|
-
default:
|
|
757
|
-
}, {
|
|
758
|
-
input,
|
|
759
|
-
output
|
|
760
|
-
});
|
|
884
|
+
default: codex
|
|
885
|
+
}, { input, output });
|
|
761
886
|
}
|
|
762
|
-
|
|
887
|
+
configuredAgents[agent.key] = { mode, model: { claude, codex } };
|
|
763
888
|
}
|
|
764
|
-
return
|
|
889
|
+
return configuredAgents;
|
|
765
890
|
}
|
|
766
891
|
|
|
767
892
|
async function detectTargets(cwd) {
|
|
@@ -886,39 +1011,58 @@ async function findInstalledProjects(rootDir, maxDepth) {
|
|
|
886
1011
|
}
|
|
887
1012
|
|
|
888
1013
|
async function ensureSettings(cwd, { input = process.stdin, output = process.stdout } = {}) {
|
|
889
|
-
const
|
|
890
|
-
|
|
1014
|
+
const settingsPath = path.join(cwd, SETTINGS_RELATIVE_PATH);
|
|
1015
|
+
const existingContent = await readFileIfExists(settingsPath);
|
|
1016
|
+
if (existingContent === null) {
|
|
1017
|
+
const settings = await askSettings({ input, output });
|
|
1018
|
+
await writeSettingsContent(cwd, formatSettings(settings));
|
|
1019
|
+
output.write(`Создан файл настроек: ${SETTINGS_RELATIVE_PATH}\n`);
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
891
1022
|
|
|
892
|
-
const
|
|
1023
|
+
const migrated = migrateSettingsContent(existingContent);
|
|
1024
|
+
if (migrated.version === SETTINGS_VERSION && migrated.missingKeys.size === 0) {
|
|
1025
|
+
output.write(`Настройки уже есть: ${SETTINGS_RELATIVE_PATH}\n`);
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
output.write(`Переношу ${SETTINGS_RELATIVE_PATH} на version: ${SETTINGS_VERSION}; спрашиваются только отсутствующие значения.\n`);
|
|
1030
|
+
const settings = await askSettings({
|
|
1031
|
+
input,
|
|
1032
|
+
output,
|
|
1033
|
+
seedSettings: migrated.settings,
|
|
1034
|
+
missingKeys: migrated.missingKeys
|
|
1035
|
+
});
|
|
893
1036
|
await writeSettingsContent(cwd, formatSettings(settings));
|
|
894
|
-
output.write(
|
|
1037
|
+
output.write(`Переписан файл настроек: ${SETTINGS_RELATIVE_PATH} → version: ${SETTINGS_VERSION}.\n`);
|
|
895
1038
|
}
|
|
896
1039
|
|
|
897
1040
|
async function configureSettings(cwd, { input = process.stdin, output = process.stdout } = {}) {
|
|
898
1041
|
const settingsPath = path.join(cwd, SETTINGS_RELATIVE_PATH);
|
|
899
1042
|
const existingContent = await readFileIfExists(settingsPath);
|
|
900
|
-
if (existingContent
|
|
901
|
-
|
|
902
|
-
const
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
1043
|
+
if (existingContent === null) {
|
|
1044
|
+
output.write(`Настраиваю ${SETTINGS_RELATIVE_PATH} заново.\n`);
|
|
1045
|
+
const settings = await askSettings({ input, output });
|
|
1046
|
+
await writeSettingsContent(cwd, formatSettings(settings));
|
|
1047
|
+
output.write(`Записан файл настроек: ${SETTINGS_RELATIVE_PATH}\n`);
|
|
1048
|
+
return;
|
|
1049
|
+
}
|
|
907
1050
|
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
const separator = existingContent.endsWith('\n\n') ? '' : existingContent.endsWith('\n') ? '\n' : '\n\n';
|
|
912
|
-
const updatedContent = `${existingContent}${separator}${missingContent}\n`;
|
|
913
|
-
await writeSettingsContent(cwd, updatedContent);
|
|
914
|
-
output.write(`Добавлены разделы ${SETTINGS_RELATIVE_PATH}: ${missingSections.join(', ')}.\n`);
|
|
1051
|
+
const migrated = migrateSettingsContent(existingContent);
|
|
1052
|
+
if (migrated.version === SETTINGS_VERSION && migrated.missingKeys.size === 0) {
|
|
1053
|
+
output.write(`Настройки ${SETTINGS_RELATIVE_PATH} версии ${SETTINGS_VERSION} полные — вопросы не требуются.\n`);
|
|
915
1054
|
return;
|
|
916
1055
|
}
|
|
917
1056
|
|
|
918
|
-
output.write(
|
|
919
|
-
const settings = await askSettings({
|
|
1057
|
+
output.write(`Переношу ${SETTINGS_RELATIVE_PATH} на version: ${SETTINGS_VERSION}; спрашиваются только отсутствующие значения.\n`);
|
|
1058
|
+
const settings = await askSettings({
|
|
1059
|
+
input,
|
|
1060
|
+
output,
|
|
1061
|
+
seedSettings: migrated.settings,
|
|
1062
|
+
missingKeys: migrated.missingKeys
|
|
1063
|
+
});
|
|
920
1064
|
await writeSettingsContent(cwd, formatSettings(settings));
|
|
921
|
-
output.write(
|
|
1065
|
+
output.write(`Переписан файл настроек: ${SETTINGS_RELATIVE_PATH} → version: ${SETTINGS_VERSION}.\n`);
|
|
922
1066
|
}
|
|
923
1067
|
|
|
924
1068
|
async function writeSettingsContent(cwd, content) {
|
|
@@ -931,178 +1075,173 @@ async function writeSettingsContent(cwd, content) {
|
|
|
931
1075
|
await fs.writeFile(settingsPath, content);
|
|
932
1076
|
}
|
|
933
1077
|
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
const version = readTopLevelYamlScalar(content, 'version');
|
|
943
|
-
if (version !== '1') {
|
|
944
|
-
if (reportExisting) {
|
|
945
|
-
output.write(`Настройки уже есть: ${SETTINGS_RELATIVE_PATH}\n`);
|
|
946
|
-
if (version !== '2') {
|
|
947
|
-
output.write('Автомиграция не выполнена: безопасно поддерживается только переход version: 1 → version: 2.\n');
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
return { exists: true, migrated: false, version };
|
|
1078
|
+
function migrateSettingsContent(content) {
|
|
1079
|
+
let raw = {};
|
|
1080
|
+
try {
|
|
1081
|
+
const parsed = parseYaml(content);
|
|
1082
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) raw = parsed;
|
|
1083
|
+
} catch {
|
|
1084
|
+
// Некорректный YAML пересобирается из defaults; в TTY все отсутствующие значения будут запрошены.
|
|
951
1085
|
}
|
|
952
1086
|
|
|
953
|
-
const
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
function migrateSettingsV1(content) {
|
|
960
|
-
const defaultsStrict = readLegacyBoolean(content, 'defaults', 'strict', false);
|
|
961
|
-
const decisionMode = readLegacyEnum(
|
|
962
|
-
content,
|
|
963
|
-
'defaults',
|
|
964
|
-
'decision_mode',
|
|
965
|
-
['autonomous', 'recommend_and_ask', 'ask_each_time'],
|
|
966
|
-
DEFAULT_SETTINGS.explore.decisionMode
|
|
967
|
-
);
|
|
968
|
-
const reviewAgents = structuredClone(DEFAULT_REVIEW_AGENTS);
|
|
969
|
-
reviewAgents.code_quality.mode = readLegacyBoolean(
|
|
970
|
-
content,
|
|
971
|
-
'review',
|
|
972
|
-
'include_code_quality',
|
|
973
|
-
true
|
|
974
|
-
) ? 'always' : 'off';
|
|
975
|
-
|
|
976
|
-
return {
|
|
977
|
-
...structuredClone(DEFAULT_SETTINGS),
|
|
978
|
-
explore: {
|
|
979
|
-
strict: defaultsStrict,
|
|
980
|
-
decisionMode
|
|
981
|
-
},
|
|
982
|
-
plan: {
|
|
983
|
-
strict: defaultsStrict,
|
|
984
|
-
size: readLegacyEnum(
|
|
985
|
-
content,
|
|
986
|
-
'defaults',
|
|
987
|
-
'plan_size',
|
|
988
|
-
['normal', 'short', 'ask_each_time'],
|
|
989
|
-
DEFAULT_SETTINGS.plan.size
|
|
990
|
-
),
|
|
991
|
-
decisionMode,
|
|
992
|
-
testStrategy: readLegacyEnum(
|
|
993
|
-
content,
|
|
994
|
-
'defaults',
|
|
995
|
-
'test_strategy',
|
|
996
|
-
['after_each_phase', 'tdd_each_phase', 'end_of_plan', 'ask_each_time'],
|
|
997
|
-
DEFAULT_SETTINGS.plan.testStrategy
|
|
998
|
-
),
|
|
999
|
-
loggingStrategy: readLegacyEnum(
|
|
1000
|
-
content,
|
|
1001
|
-
'defaults',
|
|
1002
|
-
'logging_strategy',
|
|
1003
|
-
['debug_precise', 'standard', 'ask_each_time'],
|
|
1004
|
-
DEFAULT_SETTINGS.plan.loggingStrategy
|
|
1005
|
-
)
|
|
1006
|
-
},
|
|
1007
|
-
planPolish: {
|
|
1008
|
-
strict: defaultsStrict
|
|
1009
|
-
},
|
|
1010
|
-
review: {
|
|
1011
|
-
agents: reviewAgents
|
|
1012
|
-
},
|
|
1013
|
-
discoverAutomations: {
|
|
1014
|
-
includePlans: readLegacyBoolean(content, 'automate', 'include_plans', false)
|
|
1015
|
-
}
|
|
1087
|
+
const settings = structuredClone(DEFAULT_SETTINGS);
|
|
1088
|
+
const missingKeys = new Set();
|
|
1089
|
+
const transfer = (key, value, validator, setter) => {
|
|
1090
|
+
if (validator(value)) setter(value);
|
|
1091
|
+
else missingKeys.add(key);
|
|
1016
1092
|
};
|
|
1017
|
-
|
|
1093
|
+
const oneOf = values => value => values.includes(value);
|
|
1094
|
+
const bool = value => typeof value === 'boolean';
|
|
1095
|
+
const positiveInt = value => Number.isInteger(value) && value > 0;
|
|
1096
|
+
const score = value => Number.isInteger(value) && value >= 1 && value <= 100;
|
|
1097
|
+
|
|
1098
|
+
const legacyDefaults = objectValue(raw.defaults);
|
|
1099
|
+
const orhestra = objectValue(raw.orhestra);
|
|
1100
|
+
transfer('orhestra.mode', orhestra.mode, oneOf(['automatic', 'manual']), value => { settings.orhestra.mode = value; });
|
|
1101
|
+
const steps = normalizeStoredOrhestraSteps(orhestra.steps);
|
|
1102
|
+
transfer('orhestra.steps', steps, value => Array.isArray(value), value => { settings.orhestra.steps = value; });
|
|
1103
|
+
|
|
1104
|
+
const aim = objectValue(raw.aim);
|
|
1105
|
+
transfer('aim.mode', aim.mode, oneOf(['automatic', 'manual']), value => { settings.aim.mode = value; });
|
|
1106
|
+
|
|
1107
|
+
const explore = objectValue(raw.explore);
|
|
1108
|
+
const legacyStrict = legacyDefaults.strict;
|
|
1109
|
+
transfer('explore.strict', explore.strict ?? legacyStrict, bool, value => { settings.explore.strict = value; });
|
|
1110
|
+
transfer(
|
|
1111
|
+
'explore.decisionMode',
|
|
1112
|
+
explore.decision_mode ?? legacyDefaults.decision_mode,
|
|
1113
|
+
oneOf(['autonomous', 'recommend_and_ask', 'ask_each_time']),
|
|
1114
|
+
value => { settings.explore.decisionMode = value; }
|
|
1115
|
+
);
|
|
1018
1116
|
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1117
|
+
const plan = objectValue(raw.plan);
|
|
1118
|
+
transfer('plan.strict', plan.strict ?? legacyStrict, bool, value => { settings.plan.strict = value; });
|
|
1119
|
+
transfer('plan.review', plan.review ?? plan.meta_review, bool, value => { settings.plan.review = value; });
|
|
1120
|
+
transfer(
|
|
1121
|
+
'plan.size',
|
|
1122
|
+
plan.size ?? legacyDefaults.plan_size,
|
|
1123
|
+
oneOf(['normal', 'short', 'ask_each_time']),
|
|
1124
|
+
value => { settings.plan.size = value; }
|
|
1125
|
+
);
|
|
1126
|
+
transfer(
|
|
1127
|
+
'plan.decisionMode',
|
|
1128
|
+
plan.decision_mode ?? legacyDefaults.decision_mode,
|
|
1129
|
+
oneOf(['autonomous', 'recommend_and_ask', 'ask_each_time']),
|
|
1130
|
+
value => { settings.plan.decisionMode = value; }
|
|
1131
|
+
);
|
|
1132
|
+
transfer(
|
|
1133
|
+
'plan.testStrategy',
|
|
1134
|
+
plan.test_strategy ?? legacyDefaults.test_strategy,
|
|
1135
|
+
oneOf(['after_each_phase', 'tdd_each_phase', 'end_of_plan', 'ask_each_time']),
|
|
1136
|
+
value => { settings.plan.testStrategy = value; }
|
|
1137
|
+
);
|
|
1138
|
+
transfer(
|
|
1139
|
+
'plan.loggingStrategy',
|
|
1140
|
+
plan.logging_strategy ?? legacyDefaults.logging_strategy,
|
|
1141
|
+
oneOf(['debug_precise', 'standard', 'ask_each_time']),
|
|
1142
|
+
value => { settings.plan.loggingStrategy = value; }
|
|
1143
|
+
);
|
|
1025
1144
|
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1145
|
+
const planReview = objectValue(raw['plan-review']);
|
|
1146
|
+
transfer('planReview.threshold', planReview.threshold, score, value => { settings.planReview.threshold = value; });
|
|
1147
|
+
transferAgentSettings({
|
|
1148
|
+
rawAgents: objectValue(planReview.agents),
|
|
1149
|
+
settingsAgents: settings.planReview.agents,
|
|
1150
|
+
definitions: PLAN_REVIEW_AGENT_DEFINITIONS,
|
|
1151
|
+
prefix: 'planReview.agents',
|
|
1152
|
+
missingKeys
|
|
1153
|
+
});
|
|
1030
1154
|
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
const match = line.match(/^([A-Za-z0-9_-]+):\s*(.*?)\s*$/);
|
|
1034
|
-
if (match?.[1] === key) return normalizeYamlScalar(match[2]);
|
|
1035
|
-
}
|
|
1036
|
-
return null;
|
|
1037
|
-
}
|
|
1155
|
+
const planPolish = objectValue(raw['plan-polish']);
|
|
1156
|
+
transfer('planPolish.limit', planPolish.limit, positiveInt, value => { settings.planPolish.limit = value; });
|
|
1038
1157
|
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1158
|
+
const review = objectValue(raw.review);
|
|
1159
|
+
transferAgentSettings({
|
|
1160
|
+
rawAgents: objectValue(review.agents),
|
|
1161
|
+
settingsAgents: settings.review.agents,
|
|
1162
|
+
definitions: REVIEW_AGENT_DEFINITIONS,
|
|
1163
|
+
prefix: 'review.agents',
|
|
1164
|
+
missingKeys
|
|
1165
|
+
});
|
|
1166
|
+
if (typeof review.include_code_quality === 'boolean' && !objectValue(review.agents).code_quality) {
|
|
1167
|
+
settings.review.agents.code_quality.mode = review.include_code_quality ? 'always' : 'off';
|
|
1168
|
+
missingKeys.delete('review.agents.code_quality.mode');
|
|
1044
1169
|
}
|
|
1045
|
-
return keys;
|
|
1046
|
-
}
|
|
1047
1170
|
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1171
|
+
const sendReview = objectValue(raw['send-review']);
|
|
1172
|
+
transfer(
|
|
1173
|
+
'sendReview.closePreviousReviews',
|
|
1174
|
+
sendReview.close_previous_reviews,
|
|
1175
|
+
bool,
|
|
1176
|
+
value => { settings.sendReview.closePreviousReviews = value; }
|
|
1177
|
+
);
|
|
1178
|
+
const discoverAutomations = objectValue(raw['discover-automations']);
|
|
1179
|
+
const legacyAutomate = objectValue(raw.automate);
|
|
1180
|
+
transfer(
|
|
1181
|
+
'discoverAutomations.includePlans',
|
|
1182
|
+
discoverAutomations.include_plans ?? legacyAutomate.include_plans,
|
|
1183
|
+
bool,
|
|
1184
|
+
value => { settings.discoverAutomations.includePlans = value; }
|
|
1185
|
+
);
|
|
1056
1186
|
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
}
|
|
1187
|
+
return {
|
|
1188
|
+
settings,
|
|
1189
|
+
missingKeys,
|
|
1190
|
+
version: Number.isInteger(raw.version) ? raw.version : null
|
|
1191
|
+
};
|
|
1192
|
+
}
|
|
1064
1193
|
|
|
1065
|
-
|
|
1066
|
-
const
|
|
1067
|
-
|
|
1068
|
-
|
|
1194
|
+
function transferAgentSettings({ rawAgents, settingsAgents, definitions, prefix, missingKeys }) {
|
|
1195
|
+
const allowedModes = ['always', 'auto', 'off'];
|
|
1196
|
+
const allowedClaudeModels = CLAUDE_REVIEW_MODEL_CHOICES.map(choice => choice.value);
|
|
1197
|
+
const allowedCodexModels = CODEX_REVIEW_MODEL_CHOICES.map(choice => choice.value);
|
|
1198
|
+
for (const definition of definitions) {
|
|
1199
|
+
const configured = objectValue(rawAgents[definition.key]);
|
|
1200
|
+
const model = objectValue(configured.model);
|
|
1201
|
+
if (allowedModes.includes(configured.mode)) settingsAgents[definition.key].mode = configured.mode;
|
|
1202
|
+
else missingKeys.add(`${prefix}.${definition.key}.mode`);
|
|
1203
|
+
if (allowedClaudeModels.includes(model.claude)) settingsAgents[definition.key].model.claude = model.claude;
|
|
1204
|
+
else missingKeys.add(`${prefix}.${definition.key}.model.claude`);
|
|
1205
|
+
if (allowedCodexModels.includes(model.codex)) settingsAgents[definition.key].model.codex = model.codex;
|
|
1206
|
+
else missingKeys.add(`${prefix}.${definition.key}.model.codex`);
|
|
1069
1207
|
}
|
|
1070
|
-
return blocks.join('\n\n');
|
|
1071
1208
|
}
|
|
1072
1209
|
|
|
1073
|
-
function
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
if (
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1210
|
+
function normalizeStoredOrhestraSteps(value) {
|
|
1211
|
+
if (!Array.isArray(value) || value.length === 0) return null;
|
|
1212
|
+
const steps = [];
|
|
1213
|
+
for (const step of value) {
|
|
1214
|
+
if (!step || typeof step !== 'object' || Array.isArray(step)) return null;
|
|
1215
|
+
if (typeof step.id !== 'string' || step.id.length === 0) return null;
|
|
1216
|
+
if (typeof step.skill !== 'string' || step.skill.length === 0) return null;
|
|
1217
|
+
if (step.enabled !== undefined && typeof step.enabled !== 'boolean') return null;
|
|
1218
|
+
if (step.args !== undefined && typeof step.args !== 'string') return null;
|
|
1219
|
+
const normalized = {
|
|
1220
|
+
id: step.id,
|
|
1221
|
+
skill: step.skill,
|
|
1222
|
+
enabled: step.enabled !== false,
|
|
1223
|
+
args: step.args ?? ''
|
|
1224
|
+
};
|
|
1225
|
+
const onFailure = objectValue(step.on_failure);
|
|
1226
|
+
if (Object.keys(onFailure).length > 0) {
|
|
1227
|
+
if (typeof onFailure.skill !== 'string' || onFailure.skill.length === 0) return null;
|
|
1228
|
+
if (onFailure.args !== undefined && typeof onFailure.args !== 'string') return null;
|
|
1229
|
+
if (!Array.isArray(onFailure.then) || !onFailure.then.every(item => typeof item === 'string')) return null;
|
|
1230
|
+
if (onFailure.max_cycles !== undefined && (!Number.isInteger(onFailure.max_cycles) || onFailure.max_cycles <= 0)) return null;
|
|
1231
|
+
normalized.onFailure = {
|
|
1232
|
+
skill: onFailure.skill,
|
|
1233
|
+
args: onFailure.args ?? '',
|
|
1234
|
+
then: onFailure.then,
|
|
1235
|
+
maxCycles: onFailure.max_cycles ?? 5
|
|
1236
|
+
};
|
|
1087
1237
|
}
|
|
1088
|
-
|
|
1089
|
-
if (currentSection !== section) continue;
|
|
1090
|
-
const valueMatch = line.match(/^ {2}([A-Za-z0-9_-]+):\s*(.*?)\s*$/);
|
|
1091
|
-
if (valueMatch?.[1] === key) return normalizeYamlScalar(valueMatch[2]);
|
|
1238
|
+
steps.push(normalized);
|
|
1092
1239
|
}
|
|
1093
|
-
return
|
|
1240
|
+
return steps;
|
|
1094
1241
|
}
|
|
1095
1242
|
|
|
1096
|
-
function
|
|
1097
|
-
|
|
1098
|
-
if (
|
|
1099
|
-
withoutComment.length >= 2
|
|
1100
|
-
&& ((withoutComment.startsWith('"') && withoutComment.endsWith('"'))
|
|
1101
|
-
|| (withoutComment.startsWith("'") && withoutComment.endsWith("'")))
|
|
1102
|
-
) {
|
|
1103
|
-
return withoutComment.slice(1, -1);
|
|
1104
|
-
}
|
|
1105
|
-
return withoutComment;
|
|
1243
|
+
function objectValue(value) {
|
|
1244
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
1106
1245
|
}
|
|
1107
1246
|
|
|
1108
1247
|
async function writeFileAtomically(filePath, content) {
|
|
@@ -1143,8 +1282,8 @@ ${onFailureThen}
|
|
|
1143
1282
|
# Строка аргументов передаётся скиллу как часть текущего вызова.
|
|
1144
1283
|
args: ${JSON.stringify(step.args ?? '')}${onFailure}`;
|
|
1145
1284
|
}).join('\n');
|
|
1146
|
-
const
|
|
1147
|
-
const configured =
|
|
1285
|
+
const formatAgents = (definitions, configuredAgents, defaults) => definitions.map(agent => {
|
|
1286
|
+
const configured = configuredAgents[agent.key] ?? defaults[agent.key];
|
|
1148
1287
|
return ` # ${agent.description}
|
|
1149
1288
|
# ${agent.auto}
|
|
1150
1289
|
${agent.key}:
|
|
@@ -1156,8 +1295,14 @@ ${onFailureThen}
|
|
|
1156
1295
|
# Модель для Codex.
|
|
1157
1296
|
codex: ${configured.model.codex}`;
|
|
1158
1297
|
}).join('\n\n');
|
|
1298
|
+
const planReviewAgents = formatAgents(
|
|
1299
|
+
PLAN_REVIEW_AGENT_DEFINITIONS,
|
|
1300
|
+
settings.planReview.agents,
|
|
1301
|
+
DEFAULT_PLAN_REVIEW_AGENTS
|
|
1302
|
+
);
|
|
1303
|
+
const reviewAgents = formatAgents(REVIEW_AGENT_DEFINITIONS, settings.review.agents, DEFAULT_REVIEW_AGENTS);
|
|
1159
1304
|
|
|
1160
|
-
return `version:
|
|
1305
|
+
return `version: ${SETTINGS_VERSION}
|
|
1161
1306
|
|
|
1162
1307
|
orhestra:
|
|
1163
1308
|
# Режим полного цикла eda-orhestra.
|
|
@@ -1184,6 +1329,9 @@ plan:
|
|
|
1184
1329
|
# Включает кросс-CLI ревью в eda-plan.
|
|
1185
1330
|
# true | false
|
|
1186
1331
|
strict: ${settings.plan.strict ? 'true' : 'false'}
|
|
1332
|
+
# Запускает один eda-plan-review и при необходимости один eda-plan-review-fix.
|
|
1333
|
+
# true | false
|
|
1334
|
+
review: ${settings.plan.review !== false ? 'true' : 'false'}
|
|
1187
1335
|
# Задаёт размер плана.
|
|
1188
1336
|
# normal | short | ask_each_time
|
|
1189
1337
|
size: ${settings.plan.size}
|
|
@@ -1197,10 +1345,18 @@ plan:
|
|
|
1197
1345
|
# debug_precise | standard | ask_each_time
|
|
1198
1346
|
logging_strategy: ${settings.plan.loggingStrategy}
|
|
1199
1347
|
|
|
1348
|
+
plan-review:
|
|
1349
|
+
# План готов только без обязательных findings и при score не ниже этого порога.
|
|
1350
|
+
# 1..100
|
|
1351
|
+
threshold: ${settings.planReview.threshold}
|
|
1352
|
+
# Каждая проверка имеет собственный режим запуска и модели для обеих сред.
|
|
1353
|
+
agents:
|
|
1354
|
+
${planReviewAgents}
|
|
1355
|
+
|
|
1200
1356
|
plan-polish:
|
|
1201
|
-
#
|
|
1202
|
-
#
|
|
1203
|
-
|
|
1357
|
+
# Максимальное число подтверждающих review-итераций.
|
|
1358
|
+
# Положительное целое число.
|
|
1359
|
+
limit: ${settings.planPolish.limit}
|
|
1204
1360
|
|
|
1205
1361
|
review:
|
|
1206
1362
|
# Каждая проверка имеет собственный режим запуска и модели для обеих сред.
|