workflow-ai 1.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/README.md +76 -0
- package/agent-templates/CLAUDE.md.tpl +42 -0
- package/agent-templates/QWEN.md.tpl +34 -0
- package/bin/workflow.mjs +3 -0
- package/configs/config.yaml +117 -0
- package/configs/pipeline.yaml +422 -0
- package/package.json +24 -0
- package/src/cli.mjs +131 -0
- package/src/init.mjs +441 -0
- package/src/lib/find-root.mjs +33 -0
- package/src/lib/utils.mjs +66 -0
- package/src/runner.mjs +1466 -0
- package/src/scripts/check-anomalies.js +161 -0
- package/src/scripts/move-ticket.js +228 -0
- package/src/scripts/move-to-ready.js +110 -0
- package/src/scripts/move-to-review.js +88 -0
- package/src/scripts/pick-next-task.js +345 -0
- package/src/skills/analyze-report/SKILL.md +110 -0
- package/src/skills/check-conditions/SKILL.md +140 -0
- package/src/skills/create-plan/SKILL.md +98 -0
- package/src/skills/create-report/SKILL.md +156 -0
- package/src/skills/decompose-gaps/SKILL.md +122 -0
- package/src/skills/decompose-plan/SKILL.md +109 -0
- package/src/skills/execute-task/SKILL.md +117 -0
- package/src/skills/review-result/SKILL.md +274 -0
- package/templates/plan-template.md +116 -0
- package/templates/report-template.md +178 -0
- package/templates/ticket-template.md +103 -0
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* pick-next-task.js - Скрипт для выбора следующего тикета из директории ready/
|
|
5
|
+
*
|
|
6
|
+
* Использование:
|
|
7
|
+
* node pick-next-task.js
|
|
8
|
+
*
|
|
9
|
+
* Выводит результат в формате:
|
|
10
|
+
* ---RESULT---
|
|
11
|
+
* status: found
|
|
12
|
+
* ticket_id: IMPL-001
|
|
13
|
+
* ---RESULT---
|
|
14
|
+
*
|
|
15
|
+
* или если задач нет:
|
|
16
|
+
* ---RESULT---
|
|
17
|
+
* status: empty
|
|
18
|
+
* ---RESULT---
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import fs from 'fs';
|
|
22
|
+
import path from 'path';
|
|
23
|
+
import YAML from 'js-yaml';
|
|
24
|
+
import { findProjectRoot } from '../lib/find-root.mjs';
|
|
25
|
+
import { parseFrontmatter, printResult } from '../lib/utils.mjs';
|
|
26
|
+
|
|
27
|
+
// Корень проекта
|
|
28
|
+
const PROJECT_DIR = findProjectRoot();
|
|
29
|
+
// Базовая директория workflow
|
|
30
|
+
const WORKFLOW_DIR = path.join(PROJECT_DIR, '.workflow');
|
|
31
|
+
const TICKETS_DIR = path.join(WORKFLOW_DIR, 'tickets');
|
|
32
|
+
const READY_DIR = path.join(TICKETS_DIR, 'ready');
|
|
33
|
+
const DONE_DIR = path.join(TICKETS_DIR, 'done');
|
|
34
|
+
const IN_PROGRESS_DIR = path.join(TICKETS_DIR, 'in-progress');
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Проверяет условие (condition) тикета
|
|
39
|
+
*/
|
|
40
|
+
function checkCondition(condition) {
|
|
41
|
+
const { type, value } = condition;
|
|
42
|
+
|
|
43
|
+
switch (type) {
|
|
44
|
+
case 'file_exists':
|
|
45
|
+
const filePath = path.join(WORKFLOW_DIR, value);
|
|
46
|
+
return fs.existsSync(filePath);
|
|
47
|
+
|
|
48
|
+
case 'file_not_exists':
|
|
49
|
+
const filePath2 = path.join(WORKFLOW_DIR, value);
|
|
50
|
+
return !fs.existsSync(filePath2);
|
|
51
|
+
|
|
52
|
+
case 'tasks_completed':
|
|
53
|
+
// Проверяет, что указанные задачи выполнены (находятся в done/)
|
|
54
|
+
if (Array.isArray(value)) {
|
|
55
|
+
return value.every(taskId => {
|
|
56
|
+
const donePath = path.join(DONE_DIR, `${taskId}.md`);
|
|
57
|
+
return fs.existsSync(donePath);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
|
|
62
|
+
case 'date_after':
|
|
63
|
+
return new Date() > new Date(value);
|
|
64
|
+
|
|
65
|
+
case 'date_before':
|
|
66
|
+
return new Date() < new Date(value);
|
|
67
|
+
|
|
68
|
+
case 'manual_approval':
|
|
69
|
+
// Для ручного подтверждения всегда возвращаем false
|
|
70
|
+
// Требуется явное одобрение
|
|
71
|
+
return false;
|
|
72
|
+
|
|
73
|
+
default:
|
|
74
|
+
// Неизвестный тип условия - считаем выполненным
|
|
75
|
+
console.error(`[WARN] Unknown condition type: ${type}`);
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Проверяет зависимости тикета
|
|
82
|
+
*/
|
|
83
|
+
function checkDependencies(dependencies) {
|
|
84
|
+
if (!dependencies || dependencies.length === 0) {
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return dependencies.every(depId => {
|
|
89
|
+
const donePath = path.join(DONE_DIR, `${depId}.md`);
|
|
90
|
+
return fs.existsSync(donePath);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Считывает все тикеты из директории ready/
|
|
96
|
+
*/
|
|
97
|
+
function readReadyTickets() {
|
|
98
|
+
if (!fs.existsSync(READY_DIR)) {
|
|
99
|
+
return [];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const files = fs.readdirSync(READY_DIR)
|
|
103
|
+
.filter(f => f.endsWith('.md') && f !== '.gitkeep.md');
|
|
104
|
+
|
|
105
|
+
const tickets = [];
|
|
106
|
+
|
|
107
|
+
for (const file of files) {
|
|
108
|
+
const filePath = path.join(READY_DIR, file);
|
|
109
|
+
try {
|
|
110
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
111
|
+
const { frontmatter } = parseFrontmatter(content);
|
|
112
|
+
|
|
113
|
+
tickets.push({
|
|
114
|
+
id: frontmatter.id || file.replace('.md', ''),
|
|
115
|
+
frontmatter,
|
|
116
|
+
filePath
|
|
117
|
+
});
|
|
118
|
+
} catch (e) {
|
|
119
|
+
console.error(`[WARN] Failed to read ticket ${file}: ${e.message}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return tickets;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Считывает все тикеты из директории review/
|
|
128
|
+
*/
|
|
129
|
+
function readReviewTickets() {
|
|
130
|
+
if (!fs.existsSync(path.join(TICKETS_DIR, 'review'))) {
|
|
131
|
+
return [];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const files = fs.readdirSync(path.join(TICKETS_DIR, 'review'))
|
|
135
|
+
.filter(f => f.endsWith('.md') && f !== '.gitkeep.md');
|
|
136
|
+
|
|
137
|
+
const tickets = [];
|
|
138
|
+
|
|
139
|
+
for (const file of files) {
|
|
140
|
+
const filePath = path.join(TICKETS_DIR, 'review', file);
|
|
141
|
+
try {
|
|
142
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
143
|
+
const { frontmatter } = parseFrontmatter(content);
|
|
144
|
+
|
|
145
|
+
tickets.push({
|
|
146
|
+
id: frontmatter.id || file.replace('.md', ''),
|
|
147
|
+
frontmatter,
|
|
148
|
+
filePath
|
|
149
|
+
});
|
|
150
|
+
} catch (e) {
|
|
151
|
+
console.error(`[WARN] Failed to read ticket ${file}: ${e.message}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return tickets;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Проверяет, заполнен ли раздел результатов (Summary) в тикете
|
|
160
|
+
*/
|
|
161
|
+
function hasFilledResult(body) {
|
|
162
|
+
const resultSectionRegex = /^##\s*(Результат выполнения|Result)\s*$/m;
|
|
163
|
+
const sectionStart = body.search(resultSectionRegex);
|
|
164
|
+
|
|
165
|
+
if (sectionStart === -1) {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const nextSectionRegex = /^##\s+/gm;
|
|
170
|
+
nextSectionRegex.lastIndex = sectionStart + 1;
|
|
171
|
+
const nextSectionMatch = nextSectionRegex.exec(body);
|
|
172
|
+
const sectionEnd = nextSectionMatch ? nextSectionMatch.index : body.length;
|
|
173
|
+
|
|
174
|
+
const sectionContent = body.substring(sectionStart, sectionEnd);
|
|
175
|
+
|
|
176
|
+
const summaryRegex = /^###\s*(Summary|Что сделано)\s*$/m;
|
|
177
|
+
const summaryStart = sectionContent.search(summaryRegex);
|
|
178
|
+
|
|
179
|
+
if (summaryStart === -1) {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const nextSubsectionRegex = /^###\s+/gm;
|
|
184
|
+
nextSubsectionRegex.lastIndex = summaryStart + 1;
|
|
185
|
+
const nextSubsectionMatch = nextSubsectionRegex.exec(sectionContent);
|
|
186
|
+
const summaryEnd = nextSubsectionMatch ? nextSubsectionMatch.index : sectionContent.length;
|
|
187
|
+
|
|
188
|
+
const summaryContent = sectionContent.substring(summaryStart, summaryEnd);
|
|
189
|
+
const withoutComments = summaryContent.replace(/<!--[\s\S]*?-->/g, '').trim();
|
|
190
|
+
|
|
191
|
+
return withoutComments.length > 0;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Находит завершённые тикеты в in-progress/ (с заполненным Summary)
|
|
196
|
+
* Возвращает массив id тикетов
|
|
197
|
+
*/
|
|
198
|
+
function findCompletedInProgress() {
|
|
199
|
+
if (!fs.existsSync(IN_PROGRESS_DIR)) {
|
|
200
|
+
return [];
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const files = fs.readdirSync(IN_PROGRESS_DIR)
|
|
204
|
+
.filter(f => f.endsWith('.md') && f !== '.gitkeep.md');
|
|
205
|
+
|
|
206
|
+
const completed = [];
|
|
207
|
+
|
|
208
|
+
for (const file of files) {
|
|
209
|
+
const filePath = path.join(IN_PROGRESS_DIR, file);
|
|
210
|
+
try {
|
|
211
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
212
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
213
|
+
|
|
214
|
+
if (!hasFilledResult(body)) {
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
completed.push({
|
|
219
|
+
id: frontmatter.id || file.replace('.md', ''),
|
|
220
|
+
frontmatter,
|
|
221
|
+
filePath
|
|
222
|
+
});
|
|
223
|
+
} catch (e) {
|
|
224
|
+
console.error(`[WARN] Failed to read in-progress ticket ${file}: ${e.message}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return completed;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Выбирает следующий тикет для выполнения
|
|
233
|
+
*/
|
|
234
|
+
function pickNextTicket() {
|
|
235
|
+
const tickets = readReadyTickets();
|
|
236
|
+
|
|
237
|
+
if (tickets.length === 0) {
|
|
238
|
+
// Если ready/ пуст, проверяем review/ — нужно завершить ревью
|
|
239
|
+
let reviewTickets = readReviewTickets();
|
|
240
|
+
|
|
241
|
+
if (reviewTickets.length === 0) {
|
|
242
|
+
// Нет тикетов ни в ready/, ни в review/ — проверяем in-progress/
|
|
243
|
+
// на завершённые тикеты (с заполненным Summary)
|
|
244
|
+
const completedInProgress = findCompletedInProgress();
|
|
245
|
+
if (completedInProgress.length > 0) {
|
|
246
|
+
const first = completedInProgress[0];
|
|
247
|
+
console.log(`[INFO] Found completed ticket in in-progress/: ${first.id}`);
|
|
248
|
+
return {
|
|
249
|
+
status: 'completed_in_progress',
|
|
250
|
+
ticket_id: first.id
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (reviewTickets.length > 0) {
|
|
256
|
+
return {
|
|
257
|
+
status: 'in_review',
|
|
258
|
+
ticket_id: reviewTickets[0].id,
|
|
259
|
+
priority: reviewTickets[0].frontmatter.priority,
|
|
260
|
+
title: reviewTickets[0].frontmatter.title,
|
|
261
|
+
type: reviewTickets[0].frontmatter.type
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
return { status: 'empty', reason: 'No tickets in ready/' };
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Фильтрация по условиям и зависимостям
|
|
268
|
+
const eligibleTickets = tickets.filter(ticket => {
|
|
269
|
+
const { frontmatter } = ticket;
|
|
270
|
+
|
|
271
|
+
// Проверка условий
|
|
272
|
+
const conditions = frontmatter.conditions || [];
|
|
273
|
+
const conditionsMet = conditions.every(checkCondition);
|
|
274
|
+
if (!conditionsMet) {
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Проверка зависимостей
|
|
279
|
+
const dependencies = frontmatter.dependencies || [];
|
|
280
|
+
const depsMet = checkDependencies(dependencies);
|
|
281
|
+
if (!depsMet) {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return true;
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
if (eligibleTickets.length === 0) {
|
|
289
|
+
return {
|
|
290
|
+
status: 'empty',
|
|
291
|
+
reason: 'No eligible tickets (conditions/dependencies not met)'
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Сортировка по приоритету (меньше = важнее), затем по created_at
|
|
296
|
+
eligibleTickets.sort((a, b) => {
|
|
297
|
+
const priorityA = a.frontmatter.priority || 999;
|
|
298
|
+
const priorityB = b.frontmatter.priority || 999;
|
|
299
|
+
|
|
300
|
+
if (priorityA !== priorityB) {
|
|
301
|
+
return priorityA - priorityB;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// При равном приоритете - по дате создания (старые первые)
|
|
305
|
+
const dateA = new Date(a.frontmatter.created_at || '9999-12-31');
|
|
306
|
+
const dateB = new Date(b.frontmatter.created_at || '9999-12-31');
|
|
307
|
+
return dateA - dateB;
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
const selected = eligibleTickets[0];
|
|
311
|
+
|
|
312
|
+
return {
|
|
313
|
+
status: 'found',
|
|
314
|
+
ticket_id: selected.id,
|
|
315
|
+
priority: selected.frontmatter.priority,
|
|
316
|
+
title: selected.frontmatter.title,
|
|
317
|
+
type: selected.frontmatter.type
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Main entry point
|
|
322
|
+
async function main() {
|
|
323
|
+
console.log(`[INFO] Scanning ready/ directory: ${READY_DIR}`);
|
|
324
|
+
|
|
325
|
+
const result = pickNextTicket();
|
|
326
|
+
|
|
327
|
+
if (result.status === 'found') {
|
|
328
|
+
console.log(`[INFO] Selected ticket: ${result.ticket_id} (${result.title})`);
|
|
329
|
+
console.log(`[INFO] Priority: ${result.priority}, Type: ${result.type}`);
|
|
330
|
+
} else {
|
|
331
|
+
console.log(`[INFO] ${result.reason}`);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
printResult(result);
|
|
335
|
+
|
|
336
|
+
if (result.status === 'empty') {
|
|
337
|
+
process.exit(0);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
main().catch(e => {
|
|
342
|
+
console.error(`[ERROR] ${e.message}`);
|
|
343
|
+
printResult({ status: 'error', error: e.message });
|
|
344
|
+
process.exit(1);
|
|
345
|
+
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: analyze-report
|
|
3
|
+
description: Проанализировать отчёт о выполненных задачах и выработать рекомендации. Используй после завершения итерации работ для принятия решений о дальнейших действиях. Читает отчёты из .workflow/reports/ и формирует выводы.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Анализ отчёта
|
|
7
|
+
|
|
8
|
+
Этот skill анализирует отчёты о выполненных задачах и помогает принять решения о дальнейших действиях.
|
|
9
|
+
|
|
10
|
+
## Когда использовать
|
|
11
|
+
|
|
12
|
+
- Завершилась итерация работ
|
|
13
|
+
- Накопились выполненные задачи
|
|
14
|
+
- Нужно принять решение о следующих шагах
|
|
15
|
+
- Требуется ретроспектива
|
|
16
|
+
|
|
17
|
+
## Шаги выполнения
|
|
18
|
+
|
|
19
|
+
### 1. Прочитать отчёт
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
Путь: .workflow/reports/REPORT-{NNN}.md
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Извлечь:
|
|
26
|
+
- Период отчёта
|
|
27
|
+
- Выполненные задачи
|
|
28
|
+
- Проблемы
|
|
29
|
+
- Статистику
|
|
30
|
+
|
|
31
|
+
### 2. Проанализировать достижения
|
|
32
|
+
|
|
33
|
+
Для каждой выполненной задачи:
|
|
34
|
+
- Что было сделано?
|
|
35
|
+
- Соответствует ли результат ожиданиям?
|
|
36
|
+
- Были ли отклонения от плана?
|
|
37
|
+
|
|
38
|
+
### 3. Проанализировать проблемы
|
|
39
|
+
|
|
40
|
+
Для каждой проблемы:
|
|
41
|
+
- В чём причина?
|
|
42
|
+
- Как была решена (или не решена)?
|
|
43
|
+
- Можно ли предотвратить в будущем?
|
|
44
|
+
|
|
45
|
+
### 4. Оценить прогресс по плану
|
|
46
|
+
|
|
47
|
+
Если есть связанный план:
|
|
48
|
+
- Какой процент выполнен?
|
|
49
|
+
- Соблюдаются ли сроки?
|
|
50
|
+
- Нужна ли корректировка плана?
|
|
51
|
+
|
|
52
|
+
### 5. Сформулировать выводы
|
|
53
|
+
|
|
54
|
+
**Положительные моменты:**
|
|
55
|
+
- Что работает хорошо
|
|
56
|
+
- Какие практики стоит продолжать
|
|
57
|
+
|
|
58
|
+
**Области для улучшения:**
|
|
59
|
+
- Что можно улучшить
|
|
60
|
+
- Какие процессы неэффективны
|
|
61
|
+
|
|
62
|
+
**Рекомендации:**
|
|
63
|
+
- Конкретные действия
|
|
64
|
+
- Приоритеты
|
|
65
|
+
|
|
66
|
+
### 6. Принять решение
|
|
67
|
+
|
|
68
|
+
Возможные решения:
|
|
69
|
+
- **Продолжить** — следующие задачи по плану
|
|
70
|
+
- **Скорректировать** — изменить приоритеты или подход
|
|
71
|
+
- **Новый план** — текущий план исчерпан или нерелевантен
|
|
72
|
+
- **Эскалировать** — требуется вмешательство человека
|
|
73
|
+
|
|
74
|
+
### 7. Вывести структурированный результат
|
|
75
|
+
|
|
76
|
+
После анализа **обязательно** выведи блок результата.
|
|
77
|
+
|
|
78
|
+
Если план выполнен полностью:
|
|
79
|
+
```
|
|
80
|
+
---RESULT---
|
|
81
|
+
status: completed
|
|
82
|
+
---RESULT---
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Если есть пробелы/недочёты:
|
|
86
|
+
```
|
|
87
|
+
---RESULT---
|
|
88
|
+
status: has_gaps
|
|
89
|
+
gaps: Краткое описание что не сделано или не соответствует плану
|
|
90
|
+
---RESULT---
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Пример использования
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
Проанализируй отчёт .workflow/reports/REPORT-001.md и дай рекомендации.
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Результат
|
|
100
|
+
|
|
101
|
+
Структурированный анализ с:
|
|
102
|
+
- Оценкой прогресса
|
|
103
|
+
- Выявленными проблемами
|
|
104
|
+
- Рекомендациями
|
|
105
|
+
- Решением о дальнейших действиях
|
|
106
|
+
|
|
107
|
+
## Связанные skills
|
|
108
|
+
|
|
109
|
+
- `create-plan` — создание нового плана на основе анализа
|
|
110
|
+
- `create-report` — создание отчёта для анализа
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: check-conditions
|
|
3
|
+
description: Проверить готовность задач к выполнению. Проверяет условия тикетов в backlog и выводит список готовых к перемещению в ready. НЕ перемещает файлы — перемещение выполняется отдельным stage.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Проверка условий готовности задач
|
|
7
|
+
|
|
8
|
+
Этот skill проверяет условия выполнения для задач в backlog и **выводит список готовых**.
|
|
9
|
+
|
|
10
|
+
**Важно:** Этот skill НЕ перемещает тикеты. Перемещение backlog → ready выполняется отдельным stage пайплайна (скриптом).
|
|
11
|
+
|
|
12
|
+
## Когда использовать
|
|
13
|
+
|
|
14
|
+
- Перед выбором следующей задачи
|
|
15
|
+
- После завершения задачи (для разблокировки зависимых)
|
|
16
|
+
- Периодически для актуализации доски
|
|
17
|
+
|
|
18
|
+
## Шаги выполнения
|
|
19
|
+
|
|
20
|
+
### 1. Получить список задач
|
|
21
|
+
|
|
22
|
+
Прочитать все тикеты из `.workflow/tickets/backlog/` — **обязательно выполни реальное чтение директории**, не полагайся на память или предыдущий контекст.
|
|
23
|
+
|
|
24
|
+
### 2. Для каждой задачи проверить условия
|
|
25
|
+
|
|
26
|
+
Извлечь из frontmatter:
|
|
27
|
+
- `dependencies` — зависимости
|
|
28
|
+
- `conditions` — дополнительные условия
|
|
29
|
+
|
|
30
|
+
### 3. Проверить каждое условие
|
|
31
|
+
|
|
32
|
+
#### tasks_completed
|
|
33
|
+
|
|
34
|
+
```yaml
|
|
35
|
+
conditions:
|
|
36
|
+
- type: tasks_completed
|
|
37
|
+
value: ["IMPL-001", "IMPL-002"]
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Проверка:
|
|
41
|
+
- Найти тикет IMPL-001.md в `.workflow/tickets/done/`
|
|
42
|
+
- Если найден — условие выполнено
|
|
43
|
+
- Если не найден — условие НЕ выполнено
|
|
44
|
+
|
|
45
|
+
#### date_after
|
|
46
|
+
|
|
47
|
+
```yaml
|
|
48
|
+
conditions:
|
|
49
|
+
- type: date_after
|
|
50
|
+
value: "2024-01-15"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Проверка:
|
|
54
|
+
- Сравнить текущую дату с указанной
|
|
55
|
+
- Если текущая >= указанной — условие выполнено
|
|
56
|
+
|
|
57
|
+
#### file_exists
|
|
58
|
+
|
|
59
|
+
```yaml
|
|
60
|
+
conditions:
|
|
61
|
+
- type: file_exists
|
|
62
|
+
value: "src/config.py"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Проверка:
|
|
66
|
+
- Проверить существование файла
|
|
67
|
+
- Если существует — условие выполнено
|
|
68
|
+
|
|
69
|
+
#### manual_approval
|
|
70
|
+
|
|
71
|
+
```yaml
|
|
72
|
+
conditions:
|
|
73
|
+
- type: manual_approval
|
|
74
|
+
value: true
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Проверка:
|
|
78
|
+
- Требуется подтверждение человека
|
|
79
|
+
- Задача НЕ может быть автоматически перемещена в ready
|
|
80
|
+
|
|
81
|
+
### 4. Определить статус
|
|
82
|
+
|
|
83
|
+
| Все условия | Результат |
|
|
84
|
+
|-------------|-----------|
|
|
85
|
+
| Выполнены | Включить в `ready_tickets` |
|
|
86
|
+
| Частично | Оставить в `backlog/` |
|
|
87
|
+
| manual_approval | Оставить в `backlog/`, уведомить |
|
|
88
|
+
|
|
89
|
+
### 5. Сформировать отчёт
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
=== Проверка условий ===
|
|
93
|
+
|
|
94
|
+
Готовы к выполнению:
|
|
95
|
+
- IMPL-002 (все зависимости выполнены)
|
|
96
|
+
- DOCS-001 (дата наступила)
|
|
97
|
+
|
|
98
|
+
Ожидают:
|
|
99
|
+
- IMPL-003: ждёт IMPL-002
|
|
100
|
+
- REVIEW-001: требует manual_approval
|
|
101
|
+
|
|
102
|
+
Заблокированы:
|
|
103
|
+
- FIX-001: файл src/broken.py не существует
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 6. Вывести структурированный результат
|
|
107
|
+
|
|
108
|
+
**Важно:** НЕ перемещай тикеты. Только выведи список готовых.
|
|
109
|
+
|
|
110
|
+
Если есть готовые тикеты:
|
|
111
|
+
```
|
|
112
|
+
---RESULT---
|
|
113
|
+
status: has_ready
|
|
114
|
+
ready_tickets: IMPL-002, DOCS-001
|
|
115
|
+
---RESULT---
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Если готовых нет:
|
|
119
|
+
```
|
|
120
|
+
---RESULT---
|
|
121
|
+
status: default
|
|
122
|
+
ready_tickets:
|
|
123
|
+
---RESULT---
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Запрещено
|
|
127
|
+
|
|
128
|
+
- Перемещать файлы тикетов
|
|
129
|
+
- Редактировать frontmatter (status, updated_at)
|
|
130
|
+
- Вызывать move-ticket.js или другие скрипты перемещения
|
|
131
|
+
|
|
132
|
+
## Результат
|
|
133
|
+
|
|
134
|
+
- Отчёт о статусе всех задач в backlog
|
|
135
|
+
- Список готовых тикетов в поле `ready_tickets` блока RESULT
|
|
136
|
+
|
|
137
|
+
## Связанные skills
|
|
138
|
+
|
|
139
|
+
- `pick-next-task` — выбор задачи из ready
|
|
140
|
+
- `move-ticket` — перемещение тикетов (выполняется отдельным stage)
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: create-plan
|
|
3
|
+
description: Создать стратегический план для проекта. Используй когда нужно спланировать новую функциональность, проект или задачу. Создаёт файл плана в .workflow/plans/current/ по шаблону со SMART-целями, scope, рисками и критериями успеха.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Создание плана
|
|
7
|
+
|
|
8
|
+
Этот skill создаёт стратегический план для достижения цели.
|
|
9
|
+
|
|
10
|
+
## Когда использовать
|
|
11
|
+
|
|
12
|
+
- Начинается новый проект или крупная задача
|
|
13
|
+
- Нужно переосмыслить направление после отчёта
|
|
14
|
+
- Требуется структурированный план с декомпозицией
|
|
15
|
+
|
|
16
|
+
## Шаги выполнения
|
|
17
|
+
|
|
18
|
+
### 1. Изучить контекст
|
|
19
|
+
|
|
20
|
+
Если есть предыдущие отчёты в `.workflow/reports/`:
|
|
21
|
+
- Прочитай последний отчёт
|
|
22
|
+
- Выдели ключевые проблемы и достижения
|
|
23
|
+
- Учти рекомендации
|
|
24
|
+
|
|
25
|
+
Если есть активные планы в `.workflow/plans/current/`:
|
|
26
|
+
- Определи связь с новым планом
|
|
27
|
+
|
|
28
|
+
### 2. Сформулировать SMART-цель
|
|
29
|
+
|
|
30
|
+
Цель должна быть:
|
|
31
|
+
- **S**pecific — конкретной (что именно)
|
|
32
|
+
- **M**easurable — измеримой (как проверим)
|
|
33
|
+
- **A**chievable — достижимой (реалистичной)
|
|
34
|
+
- **R**elevant — релевантной (зачем нужна)
|
|
35
|
+
- **T**ime-bound — ограниченной во времени
|
|
36
|
+
|
|
37
|
+
### 3. Определить scope
|
|
38
|
+
|
|
39
|
+
**Включено:**
|
|
40
|
+
- Что будет сделано
|
|
41
|
+
- Какие результаты ожидаем
|
|
42
|
+
|
|
43
|
+
**Исключено:**
|
|
44
|
+
- Что НЕ будем делать
|
|
45
|
+
- Ограничения
|
|
46
|
+
|
|
47
|
+
### 4. Разбить на высокоуровневые задачи
|
|
48
|
+
|
|
49
|
+
Для каждой задачи укажи:
|
|
50
|
+
- Краткое название
|
|
51
|
+
- Приоритет (1-5, где 1 — критический)
|
|
52
|
+
- Зависимости от других задач
|
|
53
|
+
- Описание
|
|
54
|
+
|
|
55
|
+
Задачи должны быть декомпозируемыми. Порядок должен учитывать зависимости.
|
|
56
|
+
|
|
57
|
+
### 5. Определить риски
|
|
58
|
+
|
|
59
|
+
Для каждого риска:
|
|
60
|
+
- Описание
|
|
61
|
+
- Вероятность (высокая/средняя/низкая)
|
|
62
|
+
- Влияние (высокое/среднее/низкое)
|
|
63
|
+
- Митигация (как предотвратить/решить)
|
|
64
|
+
|
|
65
|
+
### 6. Записать критерии успеха
|
|
66
|
+
|
|
67
|
+
Как поймём что план выполнен:
|
|
68
|
+
- Конкретные результаты
|
|
69
|
+
- Метрики
|
|
70
|
+
- Проверяемые условия
|
|
71
|
+
|
|
72
|
+
### 7. Сохранить план
|
|
73
|
+
|
|
74
|
+
1. Прочитай шаблон: `.workflow/templates/plan-template.md`
|
|
75
|
+
2. Определи следующий ID: найди все файлы `PLAN-*.md` в `.workflow/plans/` (рекурсивно), возьми максимальный номер и прибавь 1. Если файлов нет — начни с `PLAN-001`.
|
|
76
|
+
3. Сохрани в: `.workflow/plans/current/PLAN-{NNN}.md`
|
|
77
|
+
|
|
78
|
+
## Пример использования
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
Создай план для системы авторизации.
|
|
82
|
+
Контекст: Python, FastAPI, PostgreSQL. Срок: 2 недели.
|
|
83
|
+
Требования: JWT токены, refresh токены, роли пользователей.
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Результат
|
|
87
|
+
|
|
88
|
+
Файл `.workflow/plans/current/PLAN-001.md` с:
|
|
89
|
+
- Целью и контекстом
|
|
90
|
+
- Scope (включено/исключено)
|
|
91
|
+
- Высокоуровневыми задачами
|
|
92
|
+
- Рисками и митигацией
|
|
93
|
+
- Критериями успеха
|
|
94
|
+
|
|
95
|
+
## Связанные skills
|
|
96
|
+
|
|
97
|
+
- `analyze-report` — анализ отчётов перед планированием
|
|
98
|
+
- `decompose-plan` — декомпозиция плана на тикеты
|