@gian-tiaga/eda 3.1.0 → 3.2.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 CHANGED
@@ -71,7 +71,7 @@ docs/
71
71
 
72
72
  Версия npm-пакета и версия схемы настроек независимы: в eda 3.1 актуальным остаётся `docs/settings.yaml` с `version: 3`.
73
73
 
74
- Установщик по-прежнему сохраняет полный валидный v3 байт-в-байт. Старый, неизвестный или неполный YAML нормализуется: известные валидные значения переносятся, а отсутствующие спрашиваются в TTY или получают defaults без TTY.
74
+ Установщик по-прежнему сохраняет полный валидный v3 байт-в-байт — кроме случая, когда в файле остались устаревшие ключи: тогда он перезаписывает файл без вопросов и печатает, что именно убрал. Старый, неизвестный или неполный YAML нормализуется: известные валидные значения переносятся, а отсутствующие спрашиваются в TTY или получают defaults без TTY.
75
75
 
76
76
  В `eda update-all` режим `configure` один раз спрашивает общий профиль v3 и записывает его во все проекты. Режим `skip` сохраняет полный v3, индивидуально переносит остальные конфиги и создаёт defaults только при отсутствии файла.
77
77
 
@@ -80,8 +80,8 @@ docs/
80
80
  Что в 3.1 изменится в уже настроенном проекте:
81
81
 
82
82
  - **Удалены двенадцать агентов `eda-plan-review-*`.** Проверку плана целиком выполняет один субагент по фиксированному чек-листу. `eda update` стирает файлы этих агентов из `.claude/agents/` и `.codex/agents/`.
83
- - **Секция `plan-review.agents` больше не читается** и при нормализации настроек отбрасывается. Дефолт `plan-review.threshold` — `100`, `plan-polish.limit` — `2`.
84
- - **У `eda-polish` больше нет порога.** Цикл завершается только при нуле находок и останавливается досрочно, если за круг число открытых находок не сократилось. Существующий валидный `docs/settings.yaml` установщик не переписывает, поэтому аргумент вида `args: "threshold 90 limit 2"` в шаге оркестратора останется в файле и будет игнорироваться — убери из него `threshold N` вручную.
83
+ - **Секция `plan-review.agents` больше не читается.** Начиная с 3.1.1 `eda update` находит её в готовом файле настроек и убирает вместе с другими устаревшими ключами — `review-check`, `review.strict`, `review.include_code_quality`. Дефолт `plan-review.threshold` — `100`, `plan-polish.limit` — `2`.
84
+ - **У `eda-polish` больше нет порога.** Цикл завершается только при нуле находок и останавливается досрочно, если за круг число открытых находок не сократилось. Аргумент вида `args: "threshold 90 limit 2"` в шаге оркестратора `eda update` приводит к виду `args: "limit 2"`.
85
85
  - **Формат ревью дополнен.** У каждой находки появилось поле `repro` — чем проблема воспроизводится, — а в отчёт добавлен раздел «Отклонено» с причинами отклонения. `eda-fix-by-review` может отклонить находку, но только фактически неверную и с обоснованием в отчёте. Старые файлы ревью читаются как есть.
86
86
 
87
87
  ## Проверка миграции
@@ -92,6 +92,7 @@ docs/
92
92
  - `docs/business.md`, `docs/business/`, `docs/references.md`, `docs/references/`, `docs/arch.md` и `docs/rules.md` остались на месте;
93
93
  - `docs/settings.yaml` содержит `version: 3`;
94
94
  - `eda --version` выводит версию `3.1.x`;
95
+ - в `docs/settings.yaml` нет секции `plan-review.agents` и аргумента `threshold N` у шага `eda-polish`;
95
96
  - в `.claude/agents/` и `.codex/agents/` не осталось файлов `eda-plan-review-*`;
96
97
  - в `.claude/` или `.codex/` установлены обновлённые скилы и агенты.
97
98
 
package/README.md CHANGED
@@ -58,7 +58,7 @@ eda update-all ~/Code
58
58
  | `eda-manual-test` | Делает ручную smoke/acceptance-проверку после изменений: основной чеклист строит по выполненной задаче и связанному плану/issue/review/fix-отчёту, затем сверяет фактический diff. Применимая business-карточка может добавить только относящийся к задаче инвариант или сценарий; несвязанные карточки не загружаются. Не правит код, не пишет автотесты и не коммитит | `docs/artifacts/manual-tests/` |
59
59
  | `eda-polish` | Оркестрирует доводку изменений: запускает полное `eda-review`, затем `eda-fix-by-review` и повторяет цикл до `clean`, отсутствия находок и оценки не ниже 95/100 либо до лимита 5 итераций. Все находки исправляются. Каждый шаг идёт в изолированном нативном субагенте. Не коммитит | `docs/artifacts/reviews/`, `docs/artifacts/review-fixes/` |
60
60
  | `eda-send-review` | На `sonnet` / `gpt-5.6-terra` отправляет ревью на GitHub PR через `gh` всегда в едином формате: общий комментарий-сводка плюс комментарии к строкам кода. Без явно указанного типа использует `comment`; `approve` и `request-changes` берёт только из контекста текущего вызова. По настройке может скрыть предыдущие сводки eda-send-review и зарезолвить их inline-треды. По умолчанию шлёт все пункты. Claude-модель закрепляется при установке; Codex-модель хранится как рекомендуемый будущий override | (комментарий в PR) |
61
- | `eda-commit` | Работает как лёгкий оркестратор одного `haiku` / `gpt-5.6-luna` агента: тот сам собирает git-контекст, выбирает один логический набор файлов, пишет сообщение, точечно коммитит и выполняет явно заданные push/PR/merge-действия. В Claude Code сам оркестратор закреплён за `haiku`; в Codex остаётся на активной модели сессии из-за отсутствия per-skill override. При неоднозначном составе или вероятном секрете агент блокируется до индексации. Если продолжение не задано, предлагает применимые варианты | (git, опционально GitHub PR) |
61
+ | `eda-commit` | Работает как лёгкий оркестратор одного `haiku` / `gpt-5.6-luna` агента: тот сам собирает git-контекст, выбирает один логический набор файлов, пишет сообщение, точечно коммитит и выполняет явно заданные push/PR/merge-действия. В Claude Code сам оркестратор закреплён за `haiku`; в Codex остаётся на активной модели сессии из-за отсутствия per-skill override. При неоднозначном составе или вероятном секрете агент блокируется до индексации. После любого push всегда возвращает ссылку — на существующий PR или на его создание — и спрашивает, создавать ли PR, если его нет. Если продолжение не задано, предлагает применимые варианты | (git, опционально GitHub PR) |
62
62
  | `eda-worktree` | На `haiku` / `gpt-5.6-luna` создаёт через пакетного агента git worktree рядом с основным проектом в папке `{name}-work-{n}` и одноимённую ветку. Базу берёт из аргумента или спрашивает | соседняя папка `{name}-work-{n}` |
63
63
  | `eda-merge-worktree` | На `haiku` / `gpt-5.6-luna` мержит через пакетного агента ветку из соседнего worktree в текущую ветку. Принимает номер `1`, короткое имя `work-1` или полное имя `{name}-work-1`. Worktree и ветку после merge не удаляет | (git) |
64
64
  | `eda-prepare-ai` | Выполняет технический bootstrap нового или существующего проекта: проектирует архитектуру, строгие правила и профиль инструментов, проходит полный каталог автоматических проверок, подбирает применимые AI-скилы, роли и MCP, затем создаёт AI-документацию. Подтверждённые требования, стек и архитектурные драйверы из `eda-new-project` принимает как обязательный вход и не переоткрывает без обнаруженного конфликта. Для пустого проекта не выдумывает reference-карточки и не выдаёт неустановленные инструменты за доступные | `docs/rules.md`, `docs/arch.md`, `docs/references.md`, `docs/references/`, `AGENTS.md`, `CLAUDE.md` |
@@ -34,7 +34,7 @@
34
34
  2. Прочитай последние 5–10 коммитов и определи наблюдаемый стиль сообщений.
35
35
  3. Получи текущую ветку, upstream, remotes и основную ветку из `origin/HEAD`.
36
36
  4. Прочитай `AGENTS.md`, `CLAUDE.md`, `docs/rules.md`, `docs/arch.md` и связанные артефакты из `docs/**`, если они есть и применимы к изменениям.
37
- 5. Проверяй существующий PR текущей ветки только когда `USER_REQUEST` или `REQUESTED_ACTIONS` упоминает PR.
37
+ 5. Проверяй существующий PR текущей ветки, когда `USER_REQUEST` или `REQUESTED_ACTIONS` упоминает PR или push.
38
38
  6. Определи один логический набор файлов, соответствующий запросу. Отдели несвязанные пользовательские изменения и не включай их.
39
39
  7. Проверь выбранные изменения на вероятные секреты, токены, `.env` и локальные артефакты.
40
40
 
@@ -57,6 +57,9 @@
57
57
  - Для push используй существующий upstream, иначе `git push -u origin <branch>`.
58
58
  - Для PR определи base по `origin/HEAD`, сначала проверь текущую ветку через `gh pr view`; существующий PR не дублируй.
59
59
  - Если PR отсутствует, создай его через `gh pr create`, затем получи фактические номер и URL через `gh`; не угадывай ссылку.
60
+ - После любого выполненного push всегда возвращай ссылку. Если PR текущей ветки есть — `pr.status: existing` и его фактический URL. Если PR нет — `pr.status: absent` и `pr.create_url`: ссылка на создание PR, собранная из фактического URL репозитория (`gh repo view`), base из `origin/HEAD` и имени ветки.
61
+ - Если PR нет, а его создание не запрошено, сам PR не создавай: верни `completed`, `pr.create_url`, вопрос «Создать PR?» и варианты «создать PR» / «оставить ссылку на создание».
62
+ - Если репозиторий не на GitHub или `gh` недоступен, верни `pr.create_url: null` и назови причину в `error`.
60
63
  - Force-push всегда требует отдельного подтверждения. Force-push в `main` или `master` запрещён.
61
64
  - Если continuation не задан, ничего после коммита не выполняй и верни применимые варианты для вопроса пользователя.
62
65
 
@@ -79,9 +82,10 @@ push:
79
82
  remote: <remote | null>
80
83
  branch: <branch | null>
81
84
  pr:
82
- status: skipped | existing | created | failed
85
+ status: skipped | existing | created | absent | failed
83
86
  number: <number | null>
84
87
  url: <url | null>
88
+ create_url: <url | null>
85
89
  merge:
86
90
  status: skipped | completed | failed
87
91
  current_branch: <branch>
@@ -98,6 +102,6 @@ options: []
98
102
 
99
103
  - `empty` — в рабочем дереве нет изменений для коммита;
100
104
  - `committed` — коммит создан, но в `REQUESTED_ACTIONS` не было продолжения; верни применимые варианты;
101
- - `completed` — вся явно запрошенная цепочка выполнена либо пользователь после коммита выбрал «ничего больше»;
105
+ - `completed` — вся явно запрошенная цепочка выполнена либо пользователь после коммита выбрал «ничего больше»; при `pr.status: absent` после push здесь остаётся вопрос про создание PR;
102
106
  - `partial` — часть разрешённой цепочки выполнена, следующая операция завершилась ошибкой без нового вопроса;
103
107
  - `blocked` — для безопасного продолжения нужен один ответ пользователя.
package/lib/install.js CHANGED
@@ -64,6 +64,12 @@ const RETIRED_AGENTS = [
64
64
  'eda-plan-review-performance',
65
65
  'eda-plan-review-previous'
66
66
  ];
67
+ const RETIRED_SETTINGS_PATHS = [
68
+ 'plan-review.agents',
69
+ 'review-check',
70
+ 'review.strict',
71
+ 'review.include_code_quality'
72
+ ];
67
73
  const MANIFEST_FILE = 'eda-manifest.json';
68
74
  const SETTINGS_VERSION = 3;
69
75
  const REVIEW_AGENT_DEFINITIONS = [
@@ -536,11 +542,13 @@ export async function updateAll({
536
542
  if (resolvedSettingsMode === 'skip' && await fileExists(projectSettingsPath)) {
537
543
  const existingContent = await fs.readFile(projectSettingsPath, 'utf8');
538
544
  const migrated = migrateSettingsContent(existingContent);
539
- if (migrated.version === SETTINGS_VERSION && migrated.missingKeys.size === 0) {
545
+ if (migrated.version === SETTINGS_VERSION && migrated.missingKeys.size === 0 && migrated.retiredPaths.length === 0) {
540
546
  output.write(`Полный файл настроек сохранён: ${SETTINGS_RELATIVE_PATH}\n`);
541
547
  } else {
542
548
  await writeSettingsContent(projectDir, formatSettings(migrated.settings));
543
- output.write(`Старый или неполный файл настроек перенесён на version: ${SETTINGS_VERSION}: ${SETTINGS_RELATIVE_PATH}\n`);
549
+ output.write(migrated.retiredPaths.length > 0
550
+ ? `Из файла настроек убраны устаревшие ключи (${migrated.retiredPaths.join(', ')}): ${SETTINGS_RELATIVE_PATH}\n`
551
+ : `Старый или неполный файл настроек перенесён на version: ${SETTINGS_VERSION}: ${SETTINGS_RELATIVE_PATH}\n`);
544
552
  }
545
553
  } else {
546
554
  await writeSettingsContent(projectDir, sharedSettingsContent);
@@ -1000,12 +1008,18 @@ async function ensureSettings(cwd, { input = process.stdin, output = process.std
1000
1008
  }
1001
1009
 
1002
1010
  const migrated = migrateSettingsContent(existingContent);
1003
- if (migrated.version === SETTINGS_VERSION && migrated.missingKeys.size === 0) {
1011
+ const needsMigration = migrated.version !== SETTINGS_VERSION || migrated.missingKeys.size > 0;
1012
+ if (!needsMigration && migrated.retiredPaths.length === 0) {
1004
1013
  output.write(`Настройки уже есть: ${SETTINGS_RELATIVE_PATH}\n`);
1005
1014
  return;
1006
1015
  }
1007
1016
 
1008
- output.write(`Переношу ${SETTINGS_RELATIVE_PATH} на version: ${SETTINGS_VERSION}; спрашиваются только отсутствующие значения.\n`);
1017
+ if (migrated.retiredPaths.length > 0) {
1018
+ output.write(`Убираю устаревшие настройки: ${migrated.retiredPaths.join(', ')}.\n`);
1019
+ }
1020
+ if (needsMigration) {
1021
+ output.write(`Переношу ${SETTINGS_RELATIVE_PATH} на version: ${SETTINGS_VERSION}; спрашиваются только отсутствующие значения.\n`);
1022
+ }
1009
1023
  const settings = await askSettings({
1010
1024
  input,
1011
1025
  output,
@@ -1028,12 +1042,18 @@ async function configureSettings(cwd, { input = process.stdin, output = process.
1028
1042
  }
1029
1043
 
1030
1044
  const migrated = migrateSettingsContent(existingContent);
1031
- if (migrated.version === SETTINGS_VERSION && migrated.missingKeys.size === 0) {
1045
+ const needsMigration = migrated.version !== SETTINGS_VERSION || migrated.missingKeys.size > 0;
1046
+ if (!needsMigration && migrated.retiredPaths.length === 0) {
1032
1047
  output.write(`Настройки ${SETTINGS_RELATIVE_PATH} версии ${SETTINGS_VERSION} полные — вопросы не требуются.\n`);
1033
1048
  return;
1034
1049
  }
1035
1050
 
1036
- output.write(`Переношу ${SETTINGS_RELATIVE_PATH} на version: ${SETTINGS_VERSION}; спрашиваются только отсутствующие значения.\n`);
1051
+ if (migrated.retiredPaths.length > 0) {
1052
+ output.write(`Убираю устаревшие настройки: ${migrated.retiredPaths.join(', ')}.\n`);
1053
+ }
1054
+ if (needsMigration) {
1055
+ output.write(`Переношу ${SETTINGS_RELATIVE_PATH} на version: ${SETTINGS_VERSION}; спрашиваются только отсутствующие значения.\n`);
1056
+ }
1037
1057
  const settings = await askSettings({
1038
1058
  input,
1039
1059
  output,
@@ -1159,6 +1179,7 @@ function migrateSettingsContent(content) {
1159
1179
  return {
1160
1180
  settings,
1161
1181
  missingKeys,
1182
+ retiredPaths: findRetiredSettingsPaths(raw),
1162
1183
  version: Number.isInteger(raw.version) ? raw.version : null
1163
1184
  };
1164
1185
  }
@@ -1179,6 +1200,12 @@ function transferAgentSettings({ rawAgents, settingsAgents, definitions, prefix,
1179
1200
  }
1180
1201
  }
1181
1202
 
1203
+ // У eda-polish больше нет порога: цикл завершается по нулю находок.
1204
+ function stripRetiredStepArgs(skill, args) {
1205
+ if (skill !== 'eda-polish') return args;
1206
+ return args.replace(/\bthreshold\s+\d+\s*/gi, '').replace(/\s+/g, ' ').trim();
1207
+ }
1208
+
1182
1209
  function normalizeStoredOrhestraSteps(value) {
1183
1210
  if (!Array.isArray(value) || value.length === 0) return null;
1184
1211
  const steps = [];
@@ -1192,7 +1219,7 @@ function normalizeStoredOrhestraSteps(value) {
1192
1219
  id: step.id,
1193
1220
  skill: step.skill,
1194
1221
  enabled: step.enabled !== false,
1195
- args: step.args ?? ''
1222
+ args: stripRetiredStepArgs(step.skill, step.args ?? '')
1196
1223
  };
1197
1224
  const onFailure = objectValue(step.on_failure);
1198
1225
  if (Object.keys(onFailure).length > 0) {
@@ -1216,6 +1243,23 @@ function objectValue(value) {
1216
1243
  return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
1217
1244
  }
1218
1245
 
1246
+ function findRetiredSettingsPaths(raw) {
1247
+ const found = [];
1248
+ for (const settingPath of RETIRED_SETTINGS_PATHS) {
1249
+ let current = raw;
1250
+ let present = true;
1251
+ for (const segment of settingPath.split('.')) {
1252
+ if (!current || typeof current !== 'object' || Array.isArray(current) || !(segment in current)) {
1253
+ present = false;
1254
+ break;
1255
+ }
1256
+ current = current[segment];
1257
+ }
1258
+ if (present && current !== undefined) found.push(settingPath);
1259
+ }
1260
+ return found;
1261
+ }
1262
+
1219
1263
  async function writeFileAtomically(filePath, content) {
1220
1264
  const currentStat = await fs.stat(filePath);
1221
1265
  const tempPath = path.join(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gian-tiaga/eda",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "Пакет скилов и специализированных агентов eda-* для Claude Code и Codex CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -66,14 +66,13 @@ REQUESTED_ACTIONS:
66
66
  - `empty` — сообщи, что коммитить нечего, и закончи;
67
67
  - `blocked` — задай только конкретный вопрос агента и продолжи его после ответа;
68
68
  - `committed` — если продолжение не было задано, задай вопрос из следующего этапа;
69
- - `completed` или `partial` — переходи к финалу.
69
+ - `completed` — если push выполнен, а PR нет, задай вопрос из следующего этапа, иначе переходи к финалу;
70
+ - `partial` — переходи к финалу.
70
71
 
71
72
  При продолжении передай тому же агенту исходный `$USER_REQUEST`, актуальные `$REQUESTED_ACTIONS`, полный предыдущий результат и новый ответ пользователя. Не теряй commit hash и уже выполненные действия.
72
73
 
73
74
  ### 2. Решить, нужен ли вопрос после коммита
74
75
 
75
- Если `$REQUESTED_ACTIONS` не пуст и агент выполнил всю цепочку, сразу переходи к финалу.
76
-
77
76
  Если `$REQUESTED_ACTIONS` пуст и статус `committed`, спроси пользователя, используя только применимые варианты, которые вернул executor:
78
77
 
79
78
  - push;
@@ -81,6 +80,10 @@ REQUESTED_ACTIONS:
81
80
  - merge в основную ветку, переключение и удаление текущей ветки;
82
81
  - ничего больше.
83
82
 
83
+ Если push выполнен, а PR текущей ветки нет (`pr.status: absent`), спроси один раз, создавать ли PR: «создать PR» или «оставить ссылку на создание». Не спрашивай, если PR уже есть или пользователь его запретил.
84
+
85
+ Если других вопросов нет и вся запрошенная цепочка выполнена, сразу переходи к финалу.
86
+
84
87
  Передай выбор тому же `eda-commit-executor` вместе с полным предыдущим результатом. Основной агент команды не выполняет.
85
88
 
86
89
  ### 3. Финал
@@ -93,6 +96,8 @@ REQUESTED_ACTIONS:
93
96
  - текущую ветку;
94
97
  - оставшиеся незакоммиченные файлы.
95
98
 
99
+ Если push выполнен, ссылка обязательна всегда: URL существующего или созданного PR, иначе `pr.create_url` — ссылку на создание PR. Если executor вернул обе ссылки пустыми, прямо скажи, что ссылку получить не удалось, и назови причину.
100
+
96
101
  Для `partial` сначала назови выполненные действия, затем ошибку и следующий безопасный шаг.
97
102
 
98
103
  ## Чего НЕ делать
@@ -102,5 +107,6 @@ REQUESTED_ACTIONS:
102
107
  - Пересказывать предыдущий результат вместо передачи полного YAML при продолжении.
103
108
  - Терять начало `$USER_REQUEST` из-за последующего вопроса или ответа.
104
109
  - Спрашивать про push или PR, если пользователь уже явно приказал выполнить их.
110
+ - Заканчивать выполненный push без ссылки на PR или на его создание.
105
111
  - Отключать hooks, использовать `--amend`, делать force-push в `main`/`master` или создавать дубликат PR.
106
112
  - Делать ревью или править логику кода — это другие скилы.