@gian-tiaga/eda 1.0.4 → 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 +116 -33
- 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 +601 -298
- 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
|
|
@@ -229,28 +359,49 @@ const DEFAULT_SETTINGS = {
|
|
|
229
359
|
includePlans: false
|
|
230
360
|
}
|
|
231
361
|
};
|
|
362
|
+
const SETTINGS_SECTION_NAMES = [
|
|
363
|
+
'orhestra',
|
|
364
|
+
'aim',
|
|
365
|
+
'explore',
|
|
366
|
+
'plan',
|
|
367
|
+
'plan-review',
|
|
368
|
+
'plan-polish',
|
|
369
|
+
'review',
|
|
370
|
+
'send-review',
|
|
371
|
+
'discover-automations'
|
|
372
|
+
];
|
|
232
373
|
const SETTINGS_CHOICES = [
|
|
233
374
|
{
|
|
375
|
+
section: 'explore',
|
|
376
|
+
key: 'explore.strict',
|
|
234
377
|
value: 'exploreStrict',
|
|
235
378
|
name: 'Strict по умолчанию для eda-explore',
|
|
236
379
|
checked: DEFAULT_SETTINGS.explore.strict
|
|
237
380
|
},
|
|
238
381
|
{
|
|
382
|
+
section: 'plan',
|
|
383
|
+
key: 'plan.strict',
|
|
239
384
|
value: 'planStrict',
|
|
240
385
|
name: 'Strict по умолчанию для eda-plan',
|
|
241
386
|
checked: DEFAULT_SETTINGS.plan.strict
|
|
242
387
|
},
|
|
243
388
|
{
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
389
|
+
section: 'plan',
|
|
390
|
+
key: 'plan.review',
|
|
391
|
+
value: 'planReview',
|
|
392
|
+
name: 'Запускать eda-plan-review внутри eda-plan',
|
|
393
|
+
checked: DEFAULT_SETTINGS.plan.review
|
|
247
394
|
},
|
|
248
395
|
{
|
|
396
|
+
section: 'send-review',
|
|
397
|
+
key: 'sendReview.closePreviousReviews',
|
|
249
398
|
value: 'sendReviewClosePreviousReviews',
|
|
250
399
|
name: 'Закрывать предыдущие ревью eda-send-review',
|
|
251
400
|
checked: DEFAULT_SETTINGS.sendReview.closePreviousReviews
|
|
252
401
|
},
|
|
253
402
|
{
|
|
403
|
+
section: 'discover-automations',
|
|
404
|
+
key: 'discoverAutomations.includePlans',
|
|
254
405
|
value: 'discoverAutomationsIncludePlans',
|
|
255
406
|
name: 'Анализировать планы в eda-discover-automations по умолчанию',
|
|
256
407
|
checked: DEFAULT_SETTINGS.discoverAutomations.includePlans
|
|
@@ -280,6 +431,14 @@ const ORHESTRA_POLISH_LIMIT_CHOICES = [1, 2, 3, 5, 10].map(value => ({
|
|
|
280
431
|
value,
|
|
281
432
|
name: `${value} ${value === 1 ? 'итерация' : value < 5 ? 'итерации' : 'итераций'}`
|
|
282
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
|
+
}));
|
|
283
442
|
const PLAN_SIZE_CHOICES = [
|
|
284
443
|
{
|
|
285
444
|
value: 'normal',
|
|
@@ -355,6 +514,16 @@ const CODEX_REVIEW_MODEL_CHOICES = [
|
|
|
355
514
|
{ value: 'gpt-5.6-terra', name: 'gpt-5.6-terra — сильная проверка кода' },
|
|
356
515
|
{ value: 'gpt-5.6-sol', name: 'gpt-5.6-sol — максимальная глубина' }
|
|
357
516
|
];
|
|
517
|
+
const UPDATE_ALL_SETTINGS_MODE_CHOICES = [
|
|
518
|
+
{
|
|
519
|
+
value: 'configure',
|
|
520
|
+
name: 'Настроить один общий профиль и записать его во все проекты'
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
value: 'skip',
|
|
524
|
+
name: 'Skip — сохранить полный v3, остальные конфиги перенести с defaults'
|
|
525
|
+
}
|
|
526
|
+
];
|
|
358
527
|
|
|
359
528
|
export async function init({ cwd, input = process.stdin, output = process.stdout }) {
|
|
360
529
|
const targets = await askTargets({ input, output });
|
|
@@ -381,7 +550,8 @@ export async function updateAll({
|
|
|
381
550
|
root = process.cwd(),
|
|
382
551
|
input = process.stdin,
|
|
383
552
|
output = process.stdout,
|
|
384
|
-
maxDepth = UPDATE_ALL_MAX_DEPTH
|
|
553
|
+
maxDepth = UPDATE_ALL_MAX_DEPTH,
|
|
554
|
+
settingsMode
|
|
385
555
|
} = {}) {
|
|
386
556
|
if (!Number.isInteger(maxDepth) || maxDepth < 0) {
|
|
387
557
|
throw new Error('Глубина поиска должна быть неотрицательным целым числом.');
|
|
@@ -409,9 +579,20 @@ export async function updateAll({
|
|
|
409
579
|
}
|
|
410
580
|
|
|
411
581
|
output.write(`Найдено ${formatProjectCount(projects.length)}: ${projects.map(project => formatProjectPath(rootDir, project)).join(', ')}\n`);
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
582
|
+
const resolvedSettingsMode = settingsMode ?? await askUpdateAllSettingsMode({ input, output });
|
|
583
|
+
if (!['configure', 'skip'].includes(resolvedSettingsMode)) {
|
|
584
|
+
throw new Error(`Неизвестный режим настроек update-all: ${resolvedSettingsMode}.`);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
let sharedSettingsContent;
|
|
588
|
+
if (resolvedSettingsMode === 'configure') {
|
|
589
|
+
output.write('Настройки будут запрошены один раз и записаны одинаково во все найденные проекты.\n');
|
|
590
|
+
const sharedSettings = await askSettings({ input, output });
|
|
591
|
+
sharedSettingsContent = formatSettings(sharedSettings);
|
|
592
|
+
} else {
|
|
593
|
+
output.write(`Режим skip: полный version: ${SETTINGS_VERSION} сохраняется, старые конфиги переносятся, отсутствующие создаются с defaults.\n`);
|
|
594
|
+
sharedSettingsContent = formatSettings(structuredClone(DEFAULT_SETTINGS));
|
|
595
|
+
}
|
|
415
596
|
|
|
416
597
|
const updatedProjects = [];
|
|
417
598
|
const skippedProjects = [];
|
|
@@ -430,8 +611,22 @@ export async function updateAll({
|
|
|
430
611
|
}
|
|
431
612
|
|
|
432
613
|
output.write(`Найдены установленные среды: ${targets.join(', ')}\n`);
|
|
433
|
-
|
|
434
|
-
|
|
614
|
+
const projectSettingsPath = path.join(projectDir, SETTINGS_RELATIVE_PATH);
|
|
615
|
+
if (resolvedSettingsMode === 'skip' && await fileExists(projectSettingsPath)) {
|
|
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
|
+
}
|
|
624
|
+
} else {
|
|
625
|
+
await writeSettingsContent(projectDir, sharedSettingsContent);
|
|
626
|
+
output.write(resolvedSettingsMode === 'skip'
|
|
627
|
+
? `Создан файл настроек с defaults: ${SETTINGS_RELATIVE_PATH}\n`
|
|
628
|
+
: `Записан общий файл настроек: ${SETTINGS_RELATIVE_PATH}\n`);
|
|
629
|
+
}
|
|
435
630
|
const result = await syncPackage(projectDir, targets, output, {
|
|
436
631
|
action: 'update',
|
|
437
632
|
writeDone: false
|
|
@@ -459,6 +654,7 @@ export async function updateAll({
|
|
|
459
654
|
return {
|
|
460
655
|
root: rootDir,
|
|
461
656
|
maxDepth,
|
|
657
|
+
settingsMode: resolvedSettingsMode,
|
|
462
658
|
projects,
|
|
463
659
|
updatedProjects,
|
|
464
660
|
skippedProjects,
|
|
@@ -486,189 +682,211 @@ export async function askTargets({ input = process.stdin, output = process.stdou
|
|
|
486
682
|
});
|
|
487
683
|
}
|
|
488
684
|
|
|
489
|
-
export async function
|
|
685
|
+
export async function askUpdateAllSettingsMode({
|
|
686
|
+
input = process.stdin,
|
|
687
|
+
output = process.stdout,
|
|
688
|
+
selectPrompt = select
|
|
689
|
+
} = {}) {
|
|
490
690
|
if (!input.isTTY || !output.isTTY) {
|
|
491
|
-
output.write(`Нет интерактивного терминала — использую
|
|
492
|
-
return
|
|
691
|
+
output.write(`Нет интерактивного терминала — использую режим skip: сохраняю полный v3, старые конфиги переношу с defaults.\n`);
|
|
692
|
+
return 'skip';
|
|
493
693
|
}
|
|
494
694
|
|
|
495
|
-
|
|
496
|
-
message:
|
|
497
|
-
|
|
498
|
-
|
|
695
|
+
return selectPrompt({
|
|
696
|
+
message: `Как обновить ${SETTINGS_RELATIVE_PATH} в найденных проектах?`,
|
|
697
|
+
choices: UPDATE_ALL_SETTINGS_MODE_CHOICES,
|
|
698
|
+
default: 'configure'
|
|
499
699
|
}, {
|
|
500
700
|
input,
|
|
501
701
|
output
|
|
502
702
|
});
|
|
703
|
+
}
|
|
503
704
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
705
|
+
export async function askSettings({
|
|
706
|
+
input = process.stdin,
|
|
707
|
+
output = process.stdout,
|
|
708
|
+
sections = SETTINGS_SECTION_NAMES,
|
|
709
|
+
checkboxPrompt = checkbox,
|
|
710
|
+
selectPrompt = select,
|
|
711
|
+
seedSettings = DEFAULT_SETTINGS,
|
|
712
|
+
missingKeys = null
|
|
713
|
+
} = {}) {
|
|
714
|
+
const settings = structuredClone(seedSettings);
|
|
715
|
+
if (!input.isTTY || !output.isTTY) {
|
|
716
|
+
output.write(`Нет интерактивного терминала — использую перенесённые значения и defaults ${SETTINGS_RELATIVE_PATH}.\n`);
|
|
717
|
+
return settings;
|
|
718
|
+
}
|
|
512
719
|
|
|
513
|
-
const
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
720
|
+
const requestedSections = new Set(sections);
|
|
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
|
+
));
|
|
725
|
+
const selected = booleanChoices.length > 0
|
|
726
|
+
? await checkboxPrompt({
|
|
727
|
+
message: 'Какие настройки включить?',
|
|
728
|
+
instructions: 'Стрелки — выбрать, Space — отметить, Enter — продолжить',
|
|
729
|
+
choices: booleanChoices.map(({ section, key, ...choice }) => choice)
|
|
730
|
+
}, { input, output })
|
|
731
|
+
: [];
|
|
732
|
+
|
|
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
|
+
}
|
|
525
756
|
|
|
526
|
-
|
|
527
|
-
|
|
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({
|
|
528
769
|
message: 'Сколько итераций разрешить eda-polish внутри eda-orhestra?',
|
|
529
770
|
choices: ORHESTRA_POLISH_LIMIT_CHOICES,
|
|
530
771
|
default: 5
|
|
531
|
-
}, {
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
enabled: enabledOrhestraSteps.includes(step.skill),
|
|
540
|
-
...(step.skill === 'eda-polish' ? { args: `limit ${polishLimit}` } : {})
|
|
541
|
-
}));
|
|
542
|
-
|
|
543
|
-
const aimMode = await select({
|
|
544
|
-
message: 'Как eda-aim должен отвечать на рабочие вопросы по умолчанию?',
|
|
545
|
-
choices: AIM_MODE_CHOICES,
|
|
546
|
-
default: DEFAULT_SETTINGS.aim.mode
|
|
547
|
-
}, {
|
|
548
|
-
input,
|
|
549
|
-
output
|
|
550
|
-
});
|
|
551
|
-
|
|
552
|
-
const exploreDecisionMode = await select({
|
|
553
|
-
message: 'Как принимать важные решения в eda-explore?',
|
|
554
|
-
choices: DECISION_MODE_CHOICES,
|
|
555
|
-
default: DEFAULT_SETTINGS.explore.decisionMode
|
|
556
|
-
}, {
|
|
557
|
-
input,
|
|
558
|
-
output
|
|
559
|
-
});
|
|
560
|
-
|
|
561
|
-
const planSize = await select({
|
|
562
|
-
message: 'Какой размер плана eda-plan использовать по умолчанию?',
|
|
563
|
-
choices: PLAN_SIZE_CHOICES,
|
|
564
|
-
default: DEFAULT_SETTINGS.plan.size
|
|
565
|
-
}, {
|
|
566
|
-
input,
|
|
567
|
-
output
|
|
568
|
-
});
|
|
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
|
+
}
|
|
569
780
|
|
|
570
|
-
const
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
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
|
+
}
|
|
578
795
|
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
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;
|
|
815
|
+
}
|
|
587
816
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
817
|
+
export async function askReviewAgentSettings({
|
|
818
|
+
input = process.stdin,
|
|
819
|
+
output = process.stdout,
|
|
820
|
+
selectPrompt = select,
|
|
821
|
+
seedAgents = DEFAULT_REVIEW_AGENTS,
|
|
822
|
+
missingKeys = null
|
|
823
|
+
} = {}) {
|
|
824
|
+
return askAgentSettings({
|
|
593
825
|
input,
|
|
594
|
-
output
|
|
826
|
+
output,
|
|
827
|
+
selectPrompt,
|
|
828
|
+
definitions: REVIEW_AGENT_DEFINITIONS,
|
|
829
|
+
seedAgents,
|
|
830
|
+
missingKeys,
|
|
831
|
+
keyPrefix: 'review.agents',
|
|
832
|
+
label: 'review'
|
|
595
833
|
});
|
|
596
|
-
|
|
597
|
-
const reviewAgents = await askReviewAgentSettings({ input, output });
|
|
598
|
-
|
|
599
|
-
return {
|
|
600
|
-
orhestra: {
|
|
601
|
-
mode: orhestraMode,
|
|
602
|
-
steps: orhestraSteps
|
|
603
|
-
},
|
|
604
|
-
aim: {
|
|
605
|
-
mode: aimMode
|
|
606
|
-
},
|
|
607
|
-
explore: {
|
|
608
|
-
strict: selected.includes('exploreStrict'),
|
|
609
|
-
decisionMode: exploreDecisionMode
|
|
610
|
-
},
|
|
611
|
-
plan: {
|
|
612
|
-
strict: selected.includes('planStrict'),
|
|
613
|
-
size: planSize,
|
|
614
|
-
decisionMode: planDecisionMode,
|
|
615
|
-
testStrategy,
|
|
616
|
-
loggingStrategy
|
|
617
|
-
},
|
|
618
|
-
planPolish: {
|
|
619
|
-
strict: selected.includes('planPolishStrict')
|
|
620
|
-
},
|
|
621
|
-
review: {
|
|
622
|
-
agents: reviewAgents
|
|
623
|
-
},
|
|
624
|
-
sendReview: {
|
|
625
|
-
closePreviousReviews: selected.includes('sendReviewClosePreviousReviews')
|
|
626
|
-
},
|
|
627
|
-
discoverAutomations: {
|
|
628
|
-
includePlans: selected.includes('discoverAutomationsIncludePlans')
|
|
629
|
-
}
|
|
630
|
-
};
|
|
631
834
|
}
|
|
632
835
|
|
|
633
|
-
export async function
|
|
836
|
+
export async function askPlanReviewAgentSettings({
|
|
634
837
|
input = process.stdin,
|
|
635
838
|
output = process.stdout,
|
|
636
|
-
selectPrompt = select
|
|
839
|
+
selectPrompt = select,
|
|
840
|
+
seedAgents = DEFAULT_PLAN_REVIEW_AGENTS,
|
|
841
|
+
missingKeys = null
|
|
637
842
|
} = {}) {
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
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
|
+
}
|
|
648
854
|
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
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)) {
|
|
652
874
|
claude = await selectPrompt({
|
|
653
875
|
message: `Какой моделью Claude проверять ${agent.key}?`,
|
|
654
876
|
choices: CLAUDE_REVIEW_MODEL_CHOICES,
|
|
655
|
-
default:
|
|
656
|
-
}, {
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
});
|
|
877
|
+
default: claude
|
|
878
|
+
}, { input, output });
|
|
879
|
+
}
|
|
880
|
+
if (mode !== 'off' && shouldAsk(codexKey)) {
|
|
660
881
|
codex = await selectPrompt({
|
|
661
882
|
message: `Какой моделью Codex проверять ${agent.key}?`,
|
|
662
883
|
choices: CODEX_REVIEW_MODEL_CHOICES,
|
|
663
|
-
default:
|
|
664
|
-
}, {
|
|
665
|
-
input,
|
|
666
|
-
output
|
|
667
|
-
});
|
|
884
|
+
default: codex
|
|
885
|
+
}, { input, output });
|
|
668
886
|
}
|
|
669
|
-
|
|
887
|
+
configuredAgents[agent.key] = { mode, model: { claude, codex } };
|
|
670
888
|
}
|
|
671
|
-
return
|
|
889
|
+
return configuredAgents;
|
|
672
890
|
}
|
|
673
891
|
|
|
674
892
|
async function detectTargets(cwd) {
|
|
@@ -793,19 +1011,58 @@ async function findInstalledProjects(rootDir, maxDepth) {
|
|
|
793
1011
|
}
|
|
794
1012
|
|
|
795
1013
|
async function ensureSettings(cwd, { input = process.stdin, output = process.stdout } = {}) {
|
|
796
|
-
const
|
|
797
|
-
|
|
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
|
+
}
|
|
798
1022
|
|
|
799
|
-
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
|
+
});
|
|
800
1036
|
await writeSettingsContent(cwd, formatSettings(settings));
|
|
801
|
-
output.write(
|
|
1037
|
+
output.write(`Переписан файл настроек: ${SETTINGS_RELATIVE_PATH} → version: ${SETTINGS_VERSION}.\n`);
|
|
802
1038
|
}
|
|
803
1039
|
|
|
804
1040
|
async function configureSettings(cwd, { input = process.stdin, output = process.stdout } = {}) {
|
|
805
|
-
|
|
806
|
-
const
|
|
1041
|
+
const settingsPath = path.join(cwd, SETTINGS_RELATIVE_PATH);
|
|
1042
|
+
const existingContent = await readFileIfExists(settingsPath);
|
|
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
|
+
}
|
|
1050
|
+
|
|
1051
|
+
const migrated = migrateSettingsContent(existingContent);
|
|
1052
|
+
if (migrated.version === SETTINGS_VERSION && migrated.missingKeys.size === 0) {
|
|
1053
|
+
output.write(`Настройки ${SETTINGS_RELATIVE_PATH} версии ${SETTINGS_VERSION} полные — вопросы не требуются.\n`);
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1056
|
+
|
|
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
|
+
});
|
|
807
1064
|
await writeSettingsContent(cwd, formatSettings(settings));
|
|
808
|
-
output.write(
|
|
1065
|
+
output.write(`Переписан файл настроек: ${SETTINGS_RELATIVE_PATH} → version: ${SETTINGS_VERSION}.\n`);
|
|
809
1066
|
}
|
|
810
1067
|
|
|
811
1068
|
async function writeSettingsContent(cwd, content) {
|
|
@@ -818,144 +1075,173 @@ async function writeSettingsContent(cwd, content) {
|
|
|
818
1075
|
await fs.writeFile(settingsPath, content);
|
|
819
1076
|
}
|
|
820
1077
|
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
const version = readTopLevelYamlScalar(content, 'version');
|
|
830
|
-
if (version !== '1') {
|
|
831
|
-
if (reportExisting) {
|
|
832
|
-
output.write(`Настройки уже есть: ${SETTINGS_RELATIVE_PATH}\n`);
|
|
833
|
-
if (version !== '2') {
|
|
834
|
-
output.write('Автомиграция не выполнена: безопасно поддерживается только переход version: 1 → version: 2.\n');
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
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 все отсутствующие значения будут запрошены.
|
|
838
1085
|
}
|
|
839
1086
|
|
|
840
|
-
const
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
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);
|
|
1092
|
+
};
|
|
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
|
+
);
|
|
845
1116
|
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
'
|
|
851
|
-
|
|
852
|
-
['
|
|
853
|
-
|
|
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; }
|
|
854
1143
|
);
|
|
855
|
-
const reviewAgents = structuredClone(DEFAULT_REVIEW_AGENTS);
|
|
856
|
-
reviewAgents.code_quality.mode = readLegacyBoolean(
|
|
857
|
-
content,
|
|
858
|
-
'review',
|
|
859
|
-
'include_code_quality',
|
|
860
|
-
true
|
|
861
|
-
) ? 'always' : 'off';
|
|
862
1144
|
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
content,
|
|
873
|
-
'defaults',
|
|
874
|
-
'plan_size',
|
|
875
|
-
['normal', 'short', 'ask_each_time'],
|
|
876
|
-
DEFAULT_SETTINGS.plan.size
|
|
877
|
-
),
|
|
878
|
-
decisionMode,
|
|
879
|
-
testStrategy: readLegacyEnum(
|
|
880
|
-
content,
|
|
881
|
-
'defaults',
|
|
882
|
-
'test_strategy',
|
|
883
|
-
['after_each_phase', 'tdd_each_phase', 'end_of_plan', 'ask_each_time'],
|
|
884
|
-
DEFAULT_SETTINGS.plan.testStrategy
|
|
885
|
-
),
|
|
886
|
-
loggingStrategy: readLegacyEnum(
|
|
887
|
-
content,
|
|
888
|
-
'defaults',
|
|
889
|
-
'logging_strategy',
|
|
890
|
-
['debug_precise', 'standard', 'ask_each_time'],
|
|
891
|
-
DEFAULT_SETTINGS.plan.loggingStrategy
|
|
892
|
-
)
|
|
893
|
-
},
|
|
894
|
-
planPolish: {
|
|
895
|
-
strict: defaultsStrict
|
|
896
|
-
},
|
|
897
|
-
review: {
|
|
898
|
-
agents: reviewAgents
|
|
899
|
-
},
|
|
900
|
-
discoverAutomations: {
|
|
901
|
-
includePlans: readLegacyBoolean(content, 'automate', 'include_plans', false)
|
|
902
|
-
}
|
|
903
|
-
};
|
|
904
|
-
}
|
|
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
|
+
});
|
|
905
1154
|
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
1155
|
+
const planPolish = objectValue(raw['plan-polish']);
|
|
1156
|
+
transfer('planPolish.limit', planPolish.limit, positiveInt, value => { settings.planPolish.limit = value; });
|
|
1157
|
+
|
|
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');
|
|
1169
|
+
}
|
|
1170
|
+
|
|
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
|
+
);
|
|
912
1186
|
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
1187
|
+
return {
|
|
1188
|
+
settings,
|
|
1189
|
+
missingKeys,
|
|
1190
|
+
version: Number.isInteger(raw.version) ? raw.version : null
|
|
1191
|
+
};
|
|
916
1192
|
}
|
|
917
1193
|
|
|
918
|
-
function
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
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`);
|
|
922
1207
|
}
|
|
923
|
-
return null;
|
|
924
1208
|
}
|
|
925
1209
|
|
|
926
|
-
function
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
if (
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
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
|
+
};
|
|
940
1237
|
}
|
|
941
|
-
|
|
942
|
-
if (currentSection !== section) continue;
|
|
943
|
-
const valueMatch = line.match(/^ {2}([A-Za-z0-9_-]+):\s*(.*?)\s*$/);
|
|
944
|
-
if (valueMatch?.[1] === key) return normalizeYamlScalar(valueMatch[2]);
|
|
1238
|
+
steps.push(normalized);
|
|
945
1239
|
}
|
|
946
|
-
return
|
|
1240
|
+
return steps;
|
|
947
1241
|
}
|
|
948
1242
|
|
|
949
|
-
function
|
|
950
|
-
|
|
951
|
-
if (
|
|
952
|
-
withoutComment.length >= 2
|
|
953
|
-
&& ((withoutComment.startsWith('"') && withoutComment.endsWith('"'))
|
|
954
|
-
|| (withoutComment.startsWith("'") && withoutComment.endsWith("'")))
|
|
955
|
-
) {
|
|
956
|
-
return withoutComment.slice(1, -1);
|
|
957
|
-
}
|
|
958
|
-
return withoutComment;
|
|
1243
|
+
function objectValue(value) {
|
|
1244
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
959
1245
|
}
|
|
960
1246
|
|
|
961
1247
|
async function writeFileAtomically(filePath, content) {
|
|
@@ -996,8 +1282,8 @@ ${onFailureThen}
|
|
|
996
1282
|
# Строка аргументов передаётся скиллу как часть текущего вызова.
|
|
997
1283
|
args: ${JSON.stringify(step.args ?? '')}${onFailure}`;
|
|
998
1284
|
}).join('\n');
|
|
999
|
-
const
|
|
1000
|
-
const configured =
|
|
1285
|
+
const formatAgents = (definitions, configuredAgents, defaults) => definitions.map(agent => {
|
|
1286
|
+
const configured = configuredAgents[agent.key] ?? defaults[agent.key];
|
|
1001
1287
|
return ` # ${agent.description}
|
|
1002
1288
|
# ${agent.auto}
|
|
1003
1289
|
${agent.key}:
|
|
@@ -1009,8 +1295,14 @@ ${onFailureThen}
|
|
|
1009
1295
|
# Модель для Codex.
|
|
1010
1296
|
codex: ${configured.model.codex}`;
|
|
1011
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);
|
|
1012
1304
|
|
|
1013
|
-
return `version:
|
|
1305
|
+
return `version: ${SETTINGS_VERSION}
|
|
1014
1306
|
|
|
1015
1307
|
orhestra:
|
|
1016
1308
|
# Режим полного цикла eda-orhestra.
|
|
@@ -1037,6 +1329,9 @@ plan:
|
|
|
1037
1329
|
# Включает кросс-CLI ревью в eda-plan.
|
|
1038
1330
|
# true | false
|
|
1039
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'}
|
|
1040
1335
|
# Задаёт размер плана.
|
|
1041
1336
|
# normal | short | ask_each_time
|
|
1042
1337
|
size: ${settings.plan.size}
|
|
@@ -1050,10 +1345,18 @@ plan:
|
|
|
1050
1345
|
# debug_precise | standard | ask_each_time
|
|
1051
1346
|
logging_strategy: ${settings.plan.loggingStrategy}
|
|
1052
1347
|
|
|
1348
|
+
plan-review:
|
|
1349
|
+
# План готов только без обязательных findings и при score не ниже этого порога.
|
|
1350
|
+
# 1..100
|
|
1351
|
+
threshold: ${settings.planReview.threshold}
|
|
1352
|
+
# Каждая проверка имеет собственный режим запуска и модели для обеих сред.
|
|
1353
|
+
agents:
|
|
1354
|
+
${planReviewAgents}
|
|
1355
|
+
|
|
1053
1356
|
plan-polish:
|
|
1054
|
-
#
|
|
1055
|
-
#
|
|
1056
|
-
|
|
1357
|
+
# Максимальное число подтверждающих review-итераций.
|
|
1358
|
+
# Положительное целое число.
|
|
1359
|
+
limit: ${settings.planPolish.limit}
|
|
1057
1360
|
|
|
1058
1361
|
review:
|
|
1059
1362
|
# Каждая проверка имеет собственный режим запуска и модели для обеих сред.
|