@gian-tiaga/eda 1.0.4 → 1.0.5
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 +2 -2
- package/README.md +7 -5
- package/bin/cli.js +2 -2
- package/lib/install.js +231 -84
- package/package.json +1 -1
package/MIGRATION.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Миграция на eda 1.0
|
|
2
2
|
|
|
3
|
-
Версия 1.0 переводит `eda` с набора файлов-скилов на пакет с каноническими скилами и специализированными агентами для Claude Code и Codex CLI.
|
|
3
|
+
Версия 1.0 переводит `eda` с набора файлов-скилов на пакет с каноническими скилами и специализированными агентами для Claude Code и Codex CLI. `eda update` сохраняет полный `docs/settings.yaml` версии 2 без вопросов, а в неполном v2 спрашивает и атомарно дописывает только отсутствующие разделы перед обновлением компонентов.
|
|
4
4
|
|
|
5
5
|
## Короткий путь
|
|
6
6
|
|
|
@@ -26,7 +26,7 @@ eda update
|
|
|
26
26
|
eda update-all /path/to/projects
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
`update-all` ищет проекты на глубине до двух
|
|
29
|
+
`update-all` ищет проекты на глубине до двух уровней и предлагает два режима. `configure` один раз спрашивает полный профиль и записывает одинаковый `docs/settings.yaml` версии 2 во все найденные проекты. `skip` не задаёт вопросов, сохраняет существующие конфиги и создаёт профиль defaults только в проектах без файла. В non-TTY запуске используется `skip`.
|
|
30
30
|
|
|
31
31
|
После обновления перезапусти Claude Code или Codex CLI, чтобы среда перечитала установленные скилы и агентов.
|
|
32
32
|
|
package/README.md
CHANGED
|
@@ -23,13 +23,15 @@ eda init
|
|
|
23
23
|
|
|
24
24
|
Установщик покажет чекбоксы: стрелками выбираешь среду, пробелом отмечаешь, Enter продолжает. Можно поставить Claude Code, Codex CLI или обе среды сразу. Устанавливаются и скилы, и агенты. Если `docs/settings.yaml` ещё нет, установщик предложит настройки скилов, а для каждой review-проверки отдельно спросит режим и модели Claude/Codex. В неинтерактивном запуске записывается сбалансированный профиль. В конце `eda init` отдельно пишет, какие скилы и агенты реально установлены или изменились.
|
|
25
25
|
|
|
26
|
+
`eda update` не перенастраивает полный v2-конфиг: если все обязательные разделы уже есть, команда обновляет пакет без вопросов и сохраняет `docs/settings.yaml` байт-в-байт. Если разделов не хватает, спрашиваются только они и атомарно дописываются в существующий файл; без TTY для них используются defaults. Для отсутствующего файла или версии, отличной от 2, собирается полный v2-профиль.
|
|
27
|
+
|
|
26
28
|
Если пакет уже стоит во многих проектах, можно обновить его одной командой:
|
|
27
29
|
|
|
28
30
|
```bash
|
|
29
31
|
eda update-all ~/Code
|
|
30
32
|
```
|
|
31
33
|
|
|
32
|
-
`eda update-all` ищет проекты в указанной директории, её прямых подпапках и подпапках второго уровня. Проект считается найденным, если в нём уже есть установленный скил, агент или manifest `eda` для Claude/Codex. Перед обновлением команда
|
|
34
|
+
`eda update-all` ищет проекты в указанной директории, её прямых подпапках и подпапках второго уровня. Проект считается найденным, если в нём уже есть установленный скил, агент или manifest `eda` для Claude/Codex. Перед обновлением команда предлагает настроить один общий профиль для всех проектов или выбрать `skip`. В режиме `skip` вопросы не задаются, существующие `docs/settings.yaml` сохраняются байт-в-байт, а полный профиль defaults создаётся только в проектах без файла. Без TTY автоматически используется `skip`.
|
|
33
35
|
|
|
34
36
|
---
|
|
35
37
|
|
|
@@ -346,7 +348,7 @@ discover-automations:
|
|
|
346
348
|
|
|
347
349
|
Поддерживается только `version: 2`. Для старого v2 без `review.agents` новый review использует сбалансированные defaults и предупреждает, что устаревшие `review.strict`, `review.include_code_quality` и `review-check` игнорируются. Установщик существующий файл не переписывает.
|
|
348
350
|
|
|
349
|
-
`eda init` создаёт `docs/settings.yaml`, только если файла ещё нет; существующий v1 автоматически мигрируется в v2, а v2 и неизвестные версии сохраняются. `eda update`
|
|
351
|
+
`eda init` создаёт `docs/settings.yaml`, только если файла ещё нет; существующий v1 автоматически мигрируется в v2, а v2 и неизвестные версии сохраняются. `eda update` сохраняет полный v2-файл без вопросов, а в неполный v2 атомарно дописывает только отсутствующие разделы после вопросов только по ним; без TTY используются defaults недостающих разделов. Для отсутствующего файла или другой версии команда заново собирает полный v2-профиль. `eda update-all` предлагает общий профиль с перезаписью или `skip`, который сохраняет существующие файлы и создаёт defaults только при их отсутствии. Без TTY используется `skip`.
|
|
350
352
|
|
|
351
353
|
---
|
|
352
354
|
|
|
@@ -421,8 +423,8 @@ discover-automations может запускаться от review, fix-by-revie
|
|
|
421
423
|
|
|
422
424
|
```bash
|
|
423
425
|
eda init # выбрать Claude Code / Codex / обе среды и установить пакет
|
|
424
|
-
eda update #
|
|
425
|
-
eda update-all [dir] #
|
|
426
|
+
eda update # дополнить недостающие настройки и обновить скилы и агенты
|
|
427
|
+
eda update-all [dir] # сохранить конфиги или настроить общий и обновить проекты на глубине 2
|
|
426
428
|
eda --version # показать версию установленного пакета
|
|
427
429
|
eda --help # справка
|
|
428
430
|
```
|
|
@@ -455,7 +457,7 @@ eda update-all ~/Code
|
|
|
455
457
|
| Агент | `.claude/agents/<agent>.md` | `.codex/agents/<agent>.toml` |
|
|
456
458
|
| Manifest владения | `.claude/eda-manifest.json` | `.codex/eda-manifest.json` |
|
|
457
459
|
|
|
458
|
-
`eda update`
|
|
460
|
+
`eda update` сохраняет полный v2-конфиг как есть или дополняет только отсутствующие разделы, затем идемпотентно синхронизирует обе группы компонентов и отдельно показывает реально изменившиеся скилы и агенты. Manifest хранит только идентификаторы компонентов, которыми владеет `eda`: благодаря этому retired-компоненты удаляются, а чужие скилы и агенты остаются нетронутыми. `eda update-all [dir]` либо один раз собирает и записывает общий конфиг, либо в режиме `skip` сохраняет существующие настройки и создаёт defaults только при отсутствии файла; после этого синхронизирует компоненты каждого проекта. Старые Codex-файлы `.codex/skills/<skill>.md` также удаляются при миграции.
|
|
459
461
|
|
|
460
462
|
Установленные копии не являются источником правды. Локальные изменения внутри `.claude/skills/eda-*`, `.codex/skills/eda-*`, `.claude/agents/eda-*` или `.codex/agents/eda-*` будут заменены следующим обновлением.
|
|
461
463
|
|
package/bin/cli.js
CHANGED
|
@@ -10,8 +10,8 @@ const HELP = `eda — установка и обновление скилов и
|
|
|
10
10
|
|
|
11
11
|
Использование:
|
|
12
12
|
eda init — выбрать целевые среды (Claude / Codex / обе) и установить пакет
|
|
13
|
-
eda update —
|
|
14
|
-
eda update-all [dir] —
|
|
13
|
+
eda update — дополнить недостающие настройки и обновить текущий проект
|
|
14
|
+
eda update-all [dir] — сохранить конфиги или настроить общий и обновить проекты внутри dir
|
|
15
15
|
eda --version — показать версию
|
|
16
16
|
eda --help — показать эту справку
|
|
17
17
|
`;
|
package/lib/install.js
CHANGED
|
@@ -229,28 +229,43 @@ const DEFAULT_SETTINGS = {
|
|
|
229
229
|
includePlans: false
|
|
230
230
|
}
|
|
231
231
|
};
|
|
232
|
+
const SETTINGS_SECTION_NAMES = [
|
|
233
|
+
'orhestra',
|
|
234
|
+
'aim',
|
|
235
|
+
'explore',
|
|
236
|
+
'plan',
|
|
237
|
+
'plan-polish',
|
|
238
|
+
'review',
|
|
239
|
+
'send-review',
|
|
240
|
+
'discover-automations'
|
|
241
|
+
];
|
|
232
242
|
const SETTINGS_CHOICES = [
|
|
233
243
|
{
|
|
244
|
+
section: 'explore',
|
|
234
245
|
value: 'exploreStrict',
|
|
235
246
|
name: 'Strict по умолчанию для eda-explore',
|
|
236
247
|
checked: DEFAULT_SETTINGS.explore.strict
|
|
237
248
|
},
|
|
238
249
|
{
|
|
250
|
+
section: 'plan',
|
|
239
251
|
value: 'planStrict',
|
|
240
252
|
name: 'Strict по умолчанию для eda-plan',
|
|
241
253
|
checked: DEFAULT_SETTINGS.plan.strict
|
|
242
254
|
},
|
|
243
255
|
{
|
|
256
|
+
section: 'plan-polish',
|
|
244
257
|
value: 'planPolishStrict',
|
|
245
258
|
name: 'Strict по умолчанию для eda-plan-polish',
|
|
246
259
|
checked: DEFAULT_SETTINGS.planPolish.strict
|
|
247
260
|
},
|
|
248
261
|
{
|
|
262
|
+
section: 'send-review',
|
|
249
263
|
value: 'sendReviewClosePreviousReviews',
|
|
250
264
|
name: 'Закрывать предыдущие ревью eda-send-review',
|
|
251
265
|
checked: DEFAULT_SETTINGS.sendReview.closePreviousReviews
|
|
252
266
|
},
|
|
253
267
|
{
|
|
268
|
+
section: 'discover-automations',
|
|
254
269
|
value: 'discoverAutomationsIncludePlans',
|
|
255
270
|
name: 'Анализировать планы в eda-discover-automations по умолчанию',
|
|
256
271
|
checked: DEFAULT_SETTINGS.discoverAutomations.includePlans
|
|
@@ -355,6 +370,16 @@ const CODEX_REVIEW_MODEL_CHOICES = [
|
|
|
355
370
|
{ value: 'gpt-5.6-terra', name: 'gpt-5.6-terra — сильная проверка кода' },
|
|
356
371
|
{ value: 'gpt-5.6-sol', name: 'gpt-5.6-sol — максимальная глубина' }
|
|
357
372
|
];
|
|
373
|
+
const UPDATE_ALL_SETTINGS_MODE_CHOICES = [
|
|
374
|
+
{
|
|
375
|
+
value: 'configure',
|
|
376
|
+
name: 'Настроить один общий профиль и записать его во все проекты'
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
value: 'skip',
|
|
380
|
+
name: 'Skip — сохранить существующие конфиги, defaults создать только там, где файла нет'
|
|
381
|
+
}
|
|
382
|
+
];
|
|
358
383
|
|
|
359
384
|
export async function init({ cwd, input = process.stdin, output = process.stdout }) {
|
|
360
385
|
const targets = await askTargets({ input, output });
|
|
@@ -381,7 +406,8 @@ export async function updateAll({
|
|
|
381
406
|
root = process.cwd(),
|
|
382
407
|
input = process.stdin,
|
|
383
408
|
output = process.stdout,
|
|
384
|
-
maxDepth = UPDATE_ALL_MAX_DEPTH
|
|
409
|
+
maxDepth = UPDATE_ALL_MAX_DEPTH,
|
|
410
|
+
settingsMode
|
|
385
411
|
} = {}) {
|
|
386
412
|
if (!Number.isInteger(maxDepth) || maxDepth < 0) {
|
|
387
413
|
throw new Error('Глубина поиска должна быть неотрицательным целым числом.');
|
|
@@ -409,9 +435,20 @@ export async function updateAll({
|
|
|
409
435
|
}
|
|
410
436
|
|
|
411
437
|
output.write(`Найдено ${formatProjectCount(projects.length)}: ${projects.map(project => formatProjectPath(rootDir, project)).join(', ')}\n`);
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
438
|
+
const resolvedSettingsMode = settingsMode ?? await askUpdateAllSettingsMode({ input, output });
|
|
439
|
+
if (!['configure', 'skip'].includes(resolvedSettingsMode)) {
|
|
440
|
+
throw new Error(`Неизвестный режим настроек update-all: ${resolvedSettingsMode}.`);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
let sharedSettingsContent;
|
|
444
|
+
if (resolvedSettingsMode === 'configure') {
|
|
445
|
+
output.write('Настройки будут запрошены один раз и записаны одинаково во все найденные проекты.\n');
|
|
446
|
+
const sharedSettings = await askSettings({ input, output });
|
|
447
|
+
sharedSettingsContent = formatSettings(sharedSettings);
|
|
448
|
+
} else {
|
|
449
|
+
output.write(`Режим skip: существующие ${SETTINGS_RELATIVE_PATH} сохраняются, defaults создаются только для проектов без файла.\n`);
|
|
450
|
+
sharedSettingsContent = formatSettings(structuredClone(DEFAULT_SETTINGS));
|
|
451
|
+
}
|
|
415
452
|
|
|
416
453
|
const updatedProjects = [];
|
|
417
454
|
const skippedProjects = [];
|
|
@@ -430,8 +467,15 @@ export async function updateAll({
|
|
|
430
467
|
}
|
|
431
468
|
|
|
432
469
|
output.write(`Найдены установленные среды: ${targets.join(', ')}\n`);
|
|
433
|
-
|
|
434
|
-
|
|
470
|
+
const projectSettingsPath = path.join(projectDir, SETTINGS_RELATIVE_PATH);
|
|
471
|
+
if (resolvedSettingsMode === 'skip' && await fileExists(projectSettingsPath)) {
|
|
472
|
+
output.write(`Существующий файл настроек сохранён: ${SETTINGS_RELATIVE_PATH}\n`);
|
|
473
|
+
} else {
|
|
474
|
+
await writeSettingsContent(projectDir, sharedSettingsContent);
|
|
475
|
+
output.write(resolvedSettingsMode === 'skip'
|
|
476
|
+
? `Создан файл настроек с defaults: ${SETTINGS_RELATIVE_PATH}\n`
|
|
477
|
+
: `Записан общий файл настроек: ${SETTINGS_RELATIVE_PATH}\n`);
|
|
478
|
+
}
|
|
435
479
|
const result = await syncPackage(projectDir, targets, output, {
|
|
436
480
|
action: 'update',
|
|
437
481
|
writeDone: false
|
|
@@ -459,6 +503,7 @@ export async function updateAll({
|
|
|
459
503
|
return {
|
|
460
504
|
root: rootDir,
|
|
461
505
|
maxDepth,
|
|
506
|
+
settingsMode: resolvedSettingsMode,
|
|
462
507
|
projects,
|
|
463
508
|
updatedProjects,
|
|
464
509
|
skippedProjects,
|
|
@@ -486,45 +531,79 @@ export async function askTargets({ input = process.stdin, output = process.stdou
|
|
|
486
531
|
});
|
|
487
532
|
}
|
|
488
533
|
|
|
489
|
-
export async function
|
|
534
|
+
export async function askUpdateAllSettingsMode({
|
|
535
|
+
input = process.stdin,
|
|
536
|
+
output = process.stdout,
|
|
537
|
+
selectPrompt = select
|
|
538
|
+
} = {}) {
|
|
490
539
|
if (!input.isTTY || !output.isTTY) {
|
|
491
|
-
output.write(`Нет интерактивного терминала — использую
|
|
492
|
-
return
|
|
540
|
+
output.write(`Нет интерактивного терминала — использую режим skip: сохраняю существующие ${SETTINGS_RELATIVE_PATH}, defaults создаю только при отсутствии файла.\n`);
|
|
541
|
+
return 'skip';
|
|
493
542
|
}
|
|
494
543
|
|
|
495
|
-
|
|
496
|
-
message:
|
|
497
|
-
|
|
498
|
-
|
|
544
|
+
return selectPrompt({
|
|
545
|
+
message: `Как обновить ${SETTINGS_RELATIVE_PATH} в найденных проектах?`,
|
|
546
|
+
choices: UPDATE_ALL_SETTINGS_MODE_CHOICES,
|
|
547
|
+
default: 'configure'
|
|
499
548
|
}, {
|
|
500
549
|
input,
|
|
501
550
|
output
|
|
502
551
|
});
|
|
552
|
+
}
|
|
503
553
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
554
|
+
export async function askSettings({
|
|
555
|
+
input = process.stdin,
|
|
556
|
+
output = process.stdout,
|
|
557
|
+
sections = SETTINGS_SECTION_NAMES,
|
|
558
|
+
checkboxPrompt = checkbox,
|
|
559
|
+
selectPrompt = select
|
|
560
|
+
} = {}) {
|
|
561
|
+
if (!input.isTTY || !output.isTTY) {
|
|
562
|
+
output.write(`Нет интерактивного терминала — использую настройки ${SETTINGS_RELATIVE_PATH} по умолчанию.\n`);
|
|
563
|
+
return structuredClone(DEFAULT_SETTINGS);
|
|
564
|
+
}
|
|
512
565
|
|
|
513
|
-
const
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
566
|
+
const requestedSections = new Set(sections);
|
|
567
|
+
const booleanChoices = SETTINGS_CHOICES.filter(choice => requestedSections.has(choice.section));
|
|
568
|
+
const selected = booleanChoices.length > 0
|
|
569
|
+
? await checkboxPrompt({
|
|
570
|
+
message: 'Какие настройки включить?',
|
|
571
|
+
instructions: 'Стрелки — выбрать, Space — отметить, Enter — продолжить',
|
|
572
|
+
choices: booleanChoices.map(({ section, ...choice }) => choice)
|
|
573
|
+
}, {
|
|
574
|
+
input,
|
|
575
|
+
output
|
|
576
|
+
})
|
|
577
|
+
: [];
|
|
578
|
+
|
|
579
|
+
const orhestraMode = requestedSections.has('orhestra')
|
|
580
|
+
? await selectPrompt({
|
|
581
|
+
message: 'Как eda-orhestra должен вести полный цикл по умолчанию?',
|
|
582
|
+
choices: ORHESTRA_MODE_CHOICES,
|
|
583
|
+
default: DEFAULT_SETTINGS.orhestra.mode
|
|
584
|
+
}, {
|
|
585
|
+
input,
|
|
586
|
+
output
|
|
587
|
+
})
|
|
588
|
+
: DEFAULT_SETTINGS.orhestra.mode;
|
|
589
|
+
|
|
590
|
+
const enabledOrhestraSteps = requestedSections.has('orhestra')
|
|
591
|
+
? await checkboxPrompt({
|
|
592
|
+
message: 'Какие этапы eda-orhestra включить по умолчанию?',
|
|
593
|
+
instructions: 'Порядок можно изменить вручную в docs/settings.yaml',
|
|
594
|
+
choices: ORHESTRA_STEP_DEFINITIONS.map(step => ({
|
|
595
|
+
name: step.name,
|
|
596
|
+
value: step.skill,
|
|
597
|
+
checked: step.enabled
|
|
598
|
+
}))
|
|
599
|
+
}, {
|
|
600
|
+
input,
|
|
601
|
+
output
|
|
602
|
+
})
|
|
603
|
+
: DEFAULT_ORHESTRA_STEPS.filter(step => step.enabled).map(step => step.skill);
|
|
525
604
|
|
|
526
|
-
const polishLimit = enabledOrhestraSteps.includes('eda-polish')
|
|
527
|
-
? await
|
|
605
|
+
const polishLimit = requestedSections.has('orhestra') && enabledOrhestraSteps.includes('eda-polish')
|
|
606
|
+
? await selectPrompt({
|
|
528
607
|
message: 'Сколько итераций разрешить eda-polish внутри eda-orhestra?',
|
|
529
608
|
choices: ORHESTRA_POLISH_LIMIT_CHOICES,
|
|
530
609
|
default: 5
|
|
@@ -540,61 +619,75 @@ export async function askSettings({ input = process.stdin, output = process.stdo
|
|
|
540
619
|
...(step.skill === 'eda-polish' ? { args: `limit ${polishLimit}` } : {})
|
|
541
620
|
}));
|
|
542
621
|
|
|
543
|
-
const aimMode =
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
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;
|
|
551
632
|
|
|
552
|
-
const exploreDecisionMode =
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
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;
|
|
560
643
|
|
|
561
|
-
const planSize =
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
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;
|
|
569
654
|
|
|
570
|
-
const planDecisionMode =
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
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;
|
|
578
665
|
|
|
579
|
-
const testStrategy =
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
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;
|
|
587
676
|
|
|
588
|
-
const loggingStrategy =
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
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;
|
|
596
687
|
|
|
597
|
-
const reviewAgents =
|
|
688
|
+
const reviewAgents = requestedSections.has('review')
|
|
689
|
+
? await askReviewAgentSettings({ input, output, selectPrompt })
|
|
690
|
+
: structuredClone(DEFAULT_REVIEW_AGENTS);
|
|
598
691
|
|
|
599
692
|
return {
|
|
600
693
|
orhestra: {
|
|
@@ -802,6 +895,26 @@ async function ensureSettings(cwd, { input = process.stdin, output = process.std
|
|
|
802
895
|
}
|
|
803
896
|
|
|
804
897
|
async function configureSettings(cwd, { input = process.stdin, output = process.stdout } = {}) {
|
|
898
|
+
const settingsPath = path.join(cwd, SETTINGS_RELATIVE_PATH);
|
|
899
|
+
const existingContent = await readFileIfExists(settingsPath);
|
|
900
|
+
if (existingContent !== null && readTopLevelYamlScalar(existingContent, 'version') === '2') {
|
|
901
|
+
const presentSections = readTopLevelYamlKeys(existingContent);
|
|
902
|
+
const missingSections = SETTINGS_SECTION_NAMES.filter(section => !presentSections.has(section));
|
|
903
|
+
if (missingSections.length === 0) {
|
|
904
|
+
output.write(`Настройки ${SETTINGS_RELATIVE_PATH} версии 2 полные — вопросы не требуются.\n`);
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
output.write(`Дополняю отсутствующие разделы ${SETTINGS_RELATIVE_PATH}: ${missingSections.join(', ')}.\n`);
|
|
909
|
+
const settings = await askSettings({ input, output, sections: missingSections });
|
|
910
|
+
const missingContent = extractSettingsSections(formatSettings(settings), missingSections);
|
|
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`);
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
|
|
805
918
|
output.write(`Настраиваю ${SETTINGS_RELATIVE_PATH} заново.\n`);
|
|
806
919
|
const settings = await askSettings({ input, output });
|
|
807
920
|
await writeSettingsContent(cwd, formatSettings(settings));
|
|
@@ -923,6 +1036,40 @@ function readTopLevelYamlScalar(content, key) {
|
|
|
923
1036
|
return null;
|
|
924
1037
|
}
|
|
925
1038
|
|
|
1039
|
+
function readTopLevelYamlKeys(content) {
|
|
1040
|
+
const keys = new Set();
|
|
1041
|
+
for (const line of content.replace(/^\uFEFF/, '').split(/\r?\n/)) {
|
|
1042
|
+
const match = line.match(/^([A-Za-z0-9_-]+):(?:\s.*)?$/);
|
|
1043
|
+
if (match) keys.add(match[1]);
|
|
1044
|
+
}
|
|
1045
|
+
return keys;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
function extractSettingsSections(content, sectionNames) {
|
|
1049
|
+
const requested = new Set(sectionNames);
|
|
1050
|
+
const lines = content.split(/\r?\n/);
|
|
1051
|
+
const sectionStarts = [];
|
|
1052
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
1053
|
+
const match = lines[index].match(/^([A-Za-z0-9_-]+):(?:\s.*)?$/);
|
|
1054
|
+
if (match) sectionStarts.push({ name: match[1], index });
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
const blocks = [];
|
|
1058
|
+
for (let index = 0; index < sectionStarts.length; index += 1) {
|
|
1059
|
+
const section = sectionStarts[index];
|
|
1060
|
+
if (!requested.has(section.name)) continue;
|
|
1061
|
+
const end = sectionStarts[index + 1]?.index ?? lines.length;
|
|
1062
|
+
blocks.push(lines.slice(section.index, end).join('\n').trimEnd());
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
const found = new Set(blocks.map(block => block.match(/^([A-Za-z0-9_-]+):/)?.[1]));
|
|
1066
|
+
const missing = sectionNames.filter(section => !found.has(section));
|
|
1067
|
+
if (missing.length > 0) {
|
|
1068
|
+
throw new Error(`Не удалось сформировать разделы ${SETTINGS_RELATIVE_PATH}: ${missing.join(', ')}.`);
|
|
1069
|
+
}
|
|
1070
|
+
return blocks.join('\n\n');
|
|
1071
|
+
}
|
|
1072
|
+
|
|
926
1073
|
function readNestedYamlScalar(content, section, key) {
|
|
927
1074
|
let currentSection = null;
|
|
928
1075
|
for (const line of content.replace(/^\uFEFF/, '').split(/\r?\n/)) {
|