@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 +10 -3
- package/lib/install.js +66 -9
- package/package.json +1 -1
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 # обновить
|
|
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` идемпотентно перезаписывает файлы в обеих
|
|
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
|
-
|
|
222
|
-
if (targets.includes('
|
|
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
|
|
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
|
|
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.
|
|
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": {
|