@gian-tiaga/eda 0.5.0 → 0.5.2

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/README.md CHANGED
@@ -19,7 +19,7 @@ npm install -g @gian-tiaga/eda
19
19
  eda init
20
20
  ```
21
21
 
22
- Установщик покажет чекбоксы: стрелками выбираешь среду, пробелом отмечаешь, Enter продолжает. Можно поставить Claude Code, Codex CLI или обе среды сразу. Если `docs/settings.yaml` ещё нет, установщик также предложит выбрать настройки скилов и создаст этот файл.
22
+ Установщик покажет чекбоксы: стрелками выбираешь среду, пробелом отмечаешь, Enter продолжает. Можно поставить Claude Code, Codex CLI или обе среды сразу. Если `docs/settings.yaml` ещё нет, установщик также предложит выбрать настройки скилов и создаст этот файл. В конце `eda init` пишет, какие скилы реально установлены или изменились.
23
23
 
24
24
  ---
25
25
 
@@ -65,22 +65,29 @@ eda init
65
65
  version: 1
66
66
 
67
67
  defaults:
68
+ # Включает strict-режим по умолчанию для eda-explore, eda-plan и eda-review.
68
69
  # true | false
69
70
  strict: false
71
+ # Задаёт размер плана по умолчанию для eda-plan.
70
72
  # normal | short | ask_each_time
71
73
  plan_size: normal
74
+ # Определяет, как eda-explore и eda-plan принимают существенные решения.
72
75
  # autonomous | recommend_and_ask | ask_each_time
73
76
  decision_mode: recommend_and_ask
77
+ # Задаёт стратегию тестов по умолчанию для eda-plan.
74
78
  # after_each_phase | tdd_each_phase | end_of_plan | ask_each_time
75
79
  test_strategy: ask_each_time
80
+ # Задаёт стратегию логирования по умолчанию для eda-plan.
76
81
  # debug_precise | standard | ask_each_time
77
82
  logging_strategy: ask_each_time
78
83
 
79
84
  automate:
85
+ # Добавляет docs/plans/ в обычный запуск eda-automate.
80
86
  # true | false
81
87
  include_plans: false
82
88
 
83
89
  review:
90
+ # Добавляет в eda-review проверку качества кода и meta-reviewer quality-check.
84
91
  # true | false
85
92
  include_code_quality: true
86
93
  ```
@@ -130,7 +137,7 @@ automate может запускаться от review, fix-by-review или fix
130
137
 
131
138
  ```bash
132
139
  eda init # выбрать Claude Code / Codex / обе среды и установить скилы
133
- eda update # обновить скилы и создать docs/settings.yaml, если его ещё нет
140
+ eda update # обновить скилы, показать реально изменившиеся и создать docs/settings.yaml, если его ещё нет
134
141
  eda --version # показать версию установленного пакета
135
142
  eda --help # справка
136
143
  ```
@@ -153,7 +160,7 @@ eda update # синхронизировать скилы в текущ
153
160
  | **Claude Code** | `<project>/.claude/skills/<skill>/SKILL.md` — отдельная папка на каждый скил |
154
161
  | **Codex CLI** | `<project>/.codex/skills/<skill>/SKILL.md` — отдельная папка на каждый скил |
155
162
 
156
- `eda update` идемпотентно перезаписывает файлы в обеих папках. При обновлении старые файлы Codex-формата `<project>/.codex/skills/<skill>.md`, созданные версиями `eda` до этой структуры, удаляются. Если ты хочешь подправить скил локально — лучше копию сделай рядом, а не прямо в `.claude/skills/` или `.codex/skills/`, иначе при следующем `update` твои правки потеряются.
163
+ `eda update` идемпотентно перезаписывает файлы в обеих папках и в конце пишет только те скилы, содержимое которых реально изменилось относительно установленной копии. При обновлении старые файлы Codex-формата `<project>/.codex/skills/<skill>.md`, созданные версиями `eda` до этой структуры, удаляются. Если ты хочешь подправить скил локально — лучше копию сделай рядом, а не прямо в `.claude/skills/` или `.codex/skills/`, иначе при следующем `update` твои правки потеряются.
157
164
 
158
165
  `AGENTS.md` (как и любые другие файлы в корне проекта) установщик **не трогает**. Если хочешь, чтобы Codex автоматически подгружал скилы, дай ему знать сам — например, одной строкой в `AGENTS.md`: «следуй инструкциям из `.codex/skills/`».
159
166
 
package/lib/install.js CHANGED
@@ -108,7 +108,7 @@ export async function init({ cwd, input = process.stdin, output = process.stdout
108
108
  return;
109
109
  }
110
110
  await ensureSettings(cwd, { input, output });
111
- await syncSkills(cwd, targets, output);
111
+ await syncSkills(cwd, targets, output, { action: 'install' });
112
112
  }
113
113
 
114
114
  export async function update({ cwd, input = process.stdin, output = process.stdout }) {
@@ -119,7 +119,7 @@ export async function update({ cwd, input = process.stdin, output = process.stdo
119
119
  }
120
120
  output.write(`Найдены установленные среды: ${targets.join(', ')}\n`);
121
121
  await ensureSettings(cwd, { input, output });
122
- await syncSkills(cwd, targets, output);
122
+ await syncSkills(cwd, targets, output, { action: 'update' });
123
123
  }
124
124
 
125
125
  export async function askTargets({ input = process.stdin, output = process.stdout } = {}) {
@@ -211,17 +211,29 @@ async function detectTargets(cwd) {
211
211
  return targets;
212
212
  }
213
213
 
214
- async function syncSkills(cwd, targets, output = process.stdout) {
214
+ async function syncSkills(cwd, targets, output = process.stdout, { action = 'update' } = {}) {
215
215
  const skills = await listSkills();
216
216
  if (skills.length === 0) {
217
217
  throw new Error(`В пакете нет скилов (искал в ${SKILLS_SRC}).`);
218
218
  }
219
219
  output.write(`Скилы для установки: ${skills.map(s => s.name).join(', ')}\n`);
220
220
 
221
- if (targets.includes('claude')) await installToClaude(cwd, skills, output);
222
- if (targets.includes('codex')) await installToCodex(cwd, skills, output);
221
+ const changedSkills = new Set();
222
+ if (targets.includes('claude')) {
223
+ const result = await installToClaude(cwd, skills, output);
224
+ for (const skillName of result.changedSkills) changedSkills.add(skillName);
225
+ }
226
+ if (targets.includes('codex')) {
227
+ const result = await installToCodex(cwd, skills, output);
228
+ for (const skillName of result.changedSkills) changedSkills.add(skillName);
229
+ }
223
230
  await removeRetiredSkills(cwd, targets);
224
231
 
232
+ const actionLabel = action === 'install' ? 'Установлено' : 'Обновлено';
233
+ const changedSkillNames = skills
234
+ .map(skill => skill.name)
235
+ .filter(skillName => changedSkills.has(skillName));
236
+ output.write(formatChangedSkills(actionLabel, changedSkillNames));
225
237
  output.write('\nГотово.\n');
226
238
  }
227
239
 
@@ -252,22 +264,29 @@ function formatSettings(settings) {
252
264
  return `version: 1
253
265
 
254
266
  defaults:
267
+ # Включает strict-режим по умолчанию для eda-explore, eda-plan и eda-review.
255
268
  # true | false
256
269
  strict: ${settings.strict ? 'true' : 'false'}
270
+ # Задаёт размер плана по умолчанию для eda-plan.
257
271
  # normal | short | ask_each_time
258
272
  plan_size: ${settings.planSize}
273
+ # Определяет, как eda-explore и eda-plan принимают существенные решения.
259
274
  # autonomous | recommend_and_ask | ask_each_time
260
275
  decision_mode: ${settings.decisionMode}
276
+ # Задаёт стратегию тестов по умолчанию для eda-plan.
261
277
  # after_each_phase | tdd_each_phase | end_of_plan | ask_each_time
262
278
  test_strategy: ${settings.testStrategy}
279
+ # Задаёт стратегию логирования по умолчанию для eda-plan.
263
280
  # debug_precise | standard | ask_each_time
264
281
  logging_strategy: ${settings.loggingStrategy}
265
282
 
266
283
  automate:
284
+ # Добавляет docs/plans/ в обычный запуск eda-automate.
267
285
  # true | false
268
286
  include_plans: ${settings.includePlans ? 'true' : 'false'}
269
287
 
270
288
  review:
289
+ # Добавляет в eda-review проверку качества кода и meta-reviewer quality-check.
271
290
  # true | false
272
291
  include_code_quality: ${settings.includeCodeQuality ? 'true' : 'false'}
273
292
  `;
@@ -276,26 +295,64 @@ review:
276
295
  async function installToClaude(cwd, skills, output = process.stdout) {
277
296
  const dst = path.join(cwd, '.claude/skills');
278
297
  await fs.mkdir(dst, { recursive: true });
298
+ const changedSkills = [];
279
299
  for (const skill of skills) {
280
300
  const skillDir = path.join(dst, skill.name);
281
301
  await fs.mkdir(skillDir, { recursive: true });
282
302
  const content = await fs.readFile(skill.path, 'utf-8');
283
- await fs.writeFile(path.join(skillDir, 'SKILL.md'), content);
303
+ const changed = await writeSkillFile(path.join(skillDir, 'SKILL.md'), content);
304
+ if (changed) changedSkills.push(skill.name);
284
305
  }
285
- output.write(` ✓ Claude Code: ${dst}\n`);
306
+ output.write(` ✓ Claude Code: ${dst} (изменилось ${formatSkillCount(changedSkills.length)})\n`);
307
+ return { changedSkills };
286
308
  }
287
309
 
288
310
  async function installToCodex(cwd, skills, output = process.stdout) {
289
311
  const dst = path.join(cwd, '.codex/skills');
290
312
  await fs.mkdir(dst, { recursive: true });
313
+ const changedSkills = [];
291
314
  for (const skill of skills) {
292
315
  const skillDir = path.join(dst, skill.name);
293
316
  await fs.mkdir(skillDir, { recursive: true });
294
317
  const content = await fs.readFile(skill.path, 'utf-8');
295
- await fs.writeFile(path.join(skillDir, 'SKILL.md'), content);
318
+ const changed = await writeSkillFile(path.join(skillDir, 'SKILL.md'), content);
319
+ if (changed) changedSkills.push(skill.name);
296
320
  await removeObsoleteCodexFile(dst, skill.name);
297
321
  }
298
- output.write(` ✓ Codex CLI: ${dst}\n`);
322
+ output.write(` ✓ Codex CLI: ${dst} (изменилось ${formatSkillCount(changedSkills.length)})\n`);
323
+ return { changedSkills };
324
+ }
325
+
326
+ async function writeSkillFile(filePath, content) {
327
+ const previousContent = await readFileIfExists(filePath);
328
+ await fs.writeFile(filePath, content);
329
+ return previousContent !== content;
330
+ }
331
+
332
+ async function readFileIfExists(filePath) {
333
+ try {
334
+ return await fs.readFile(filePath, 'utf-8');
335
+ } catch (err) {
336
+ if (err?.code !== 'ENOENT') throw err;
337
+ return null;
338
+ }
339
+ }
340
+
341
+ function formatChangedSkills(actionLabel, skillNames) {
342
+ if (skillNames.length === 0) return `${actionLabel} 0 скилов.\n`;
343
+ return `${actionLabel} ${formatSkillCount(skillNames.length)}: ${skillNames.join(', ')}.\n`;
344
+ }
345
+
346
+ function formatSkillCount(count) {
347
+ return `${count} ${pluralizeSkill(count)}`;
348
+ }
349
+
350
+ function pluralizeSkill(count) {
351
+ const mod10 = count % 10;
352
+ const mod100 = count % 100;
353
+ if (mod10 === 1 && mod100 !== 11) return 'скил';
354
+ if (mod10 >= 2 && mod10 <= 4 && (mod100 < 12 || mod100 > 14)) return 'скила';
355
+ return 'скилов';
299
356
  }
300
357
 
301
358
  async function removeObsoleteCodexFile(dst, skillName) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gian-tiaga/eda",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Набор скилов eda-* для Claude Code и Codex CLI: roadmap, explore, plan, execute, fix, review, fix-by-review, send-review, commit, docs, automate",
5
5
  "type": "module",
6
6
  "bin": {