workflow-ai 1.0.38 → 1.0.40
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/package.json +1 -1
- package/src/lib/utils.mjs +30 -3
- package/src/runner.mjs +0 -2
- package/src/scripts/archive-plan-tickets.js +102 -0
- package/src/scripts/check-conditions.js +7 -0
- package/src/scripts/move-ticket.js +3 -2
- package/src/scripts/move-to-ready.js +6 -0
- package/src/scripts/pick-next-task.js +75 -4
- package/src/skills/coach/SKILL.md +12 -0
- package/src/skills/coach/knowledge/backlog-management.md +71 -0
- package/src/skills/coach/templates/coach-backlog-init.yaml +10 -0
- package/src/skills/coach/workflows/analyze.md +17 -1
- package/src/skills/coach/workflows/audit.md +14 -0
- package/src/skills/coach/workflows/improve.md +14 -0
- package/src/skills/coach/workflows/review.md +6 -0
- package/src/skills/decompose-plan/SKILL.md +5 -11
package/package.json
CHANGED
package/src/lib/utils.mjs
CHANGED
|
@@ -198,7 +198,7 @@ export function checkAndClosePlan(workflowDir, planId) {
|
|
|
198
198
|
}
|
|
199
199
|
|
|
200
200
|
const ticketsDir = path.join(workflowDir, 'tickets');
|
|
201
|
-
const allDirNames = ['backlog', 'ready', 'in-progress', 'blocked', 'review', 'done'];
|
|
201
|
+
const allDirNames = ['backlog', 'ready', 'in-progress', 'blocked', 'review', 'done', 'archive'];
|
|
202
202
|
const allTickets = [];
|
|
203
203
|
|
|
204
204
|
for (const dirName of allDirNames) {
|
|
@@ -218,7 +218,7 @@ export function checkAndClosePlan(workflowDir, planId) {
|
|
|
218
218
|
}
|
|
219
219
|
|
|
220
220
|
const total = allTickets.length;
|
|
221
|
-
const done = allTickets.filter(t => t.dir === 'done').length;
|
|
221
|
+
const done = allTickets.filter(t => t.dir === 'done' || t.dir === 'archive').length;
|
|
222
222
|
|
|
223
223
|
if (total === 0) {
|
|
224
224
|
return { closed: false, reason: 'No tickets found for plan', total, done };
|
|
@@ -255,5 +255,32 @@ export function checkAndClosePlan(workflowDir, planId) {
|
|
|
255
255
|
|
|
256
256
|
fs.writeFileSync(planPath, serializeFrontmatter(frontmatter) + body, 'utf8');
|
|
257
257
|
|
|
258
|
-
|
|
258
|
+
// Архивируем все done-тикеты этого плана
|
|
259
|
+
const archiveDir = path.join(ticketsDir, 'archive');
|
|
260
|
+
if (!fs.existsSync(archiveDir)) {
|
|
261
|
+
fs.mkdirSync(archiveDir, { recursive: true });
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const archived = [];
|
|
265
|
+
const doneDir = path.join(ticketsDir, 'done');
|
|
266
|
+
if (fs.existsSync(doneDir)) {
|
|
267
|
+
const doneTickets = allTickets.filter(t => t.dir === 'done');
|
|
268
|
+
for (const ticket of doneTickets) {
|
|
269
|
+
const srcPath = path.join(doneDir, `${ticket.id}.md`);
|
|
270
|
+
const destPath = path.join(archiveDir, `${ticket.id}.md`);
|
|
271
|
+
try {
|
|
272
|
+
if (fs.existsSync(srcPath)) {
|
|
273
|
+
const content = fs.readFileSync(srcPath, 'utf8');
|
|
274
|
+
const { frontmatter: fm, body: bd } = parseFrontmatter(content);
|
|
275
|
+
fm.updated_at = new Date().toISOString();
|
|
276
|
+
fm.archived_at = new Date().toISOString();
|
|
277
|
+
fs.writeFileSync(destPath, serializeFrontmatter(fm) + bd, 'utf8');
|
|
278
|
+
fs.unlinkSync(srcPath);
|
|
279
|
+
archived.push(ticket.id);
|
|
280
|
+
}
|
|
281
|
+
} catch (_) { /* skip errors */ }
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return { closed: true, reason: 'All tickets done', total, done, archived };
|
|
259
286
|
}
|
package/src/runner.mjs
CHANGED
|
@@ -862,8 +862,6 @@ class StageExecutor {
|
|
|
862
862
|
const child = spawn(agent.command, args, {
|
|
863
863
|
cwd: path.resolve(this.projectRoot, agent.workdir || '.'),
|
|
864
864
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
865
|
-
// npm-бинари (.cmd) на Windows требуют shell: true,
|
|
866
|
-
// но нативные executables (node) — нет: shell: true обрезает многострочные аргументы
|
|
867
865
|
shell: process.platform === 'win32' && agent.command !== 'node'
|
|
868
866
|
});
|
|
869
867
|
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* archive-plan-tickets.js - Архивирует все done-тикеты указанного плана
|
|
5
|
+
*
|
|
6
|
+
* Использование:
|
|
7
|
+
* node archive-plan-tickets.js <plan_id>
|
|
8
|
+
*
|
|
9
|
+
* Пример:
|
|
10
|
+
* node archive-plan-tickets.js PLAN-002
|
|
11
|
+
* node archive-plan-tickets.js 2
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import fs from 'fs';
|
|
15
|
+
import path from 'path';
|
|
16
|
+
import { findProjectRoot } from '../lib/find-root.mjs';
|
|
17
|
+
import { parseFrontmatter, serializeFrontmatter, normalizePlanId, extractPlanId, printResult } from '../lib/utils.mjs';
|
|
18
|
+
|
|
19
|
+
const PROJECT_DIR = findProjectRoot();
|
|
20
|
+
const WORKFLOW_DIR = path.join(PROJECT_DIR, '.workflow');
|
|
21
|
+
const TICKETS_DIR = path.join(WORKFLOW_DIR, 'tickets');
|
|
22
|
+
const DONE_DIR = path.join(TICKETS_DIR, 'done');
|
|
23
|
+
const ARCHIVE_DIR = path.join(TICKETS_DIR, 'archive');
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Архивирует все done-тикеты указанного плана
|
|
27
|
+
*/
|
|
28
|
+
function archivePlanTickets(planId) {
|
|
29
|
+
if (!planId) {
|
|
30
|
+
return { status: 'error', error: 'Missing plan_id' };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!fs.existsSync(DONE_DIR)) {
|
|
34
|
+
return { status: 'ok', plan_id: planId, archived: 0, ticket_ids: '' };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!fs.existsSync(ARCHIVE_DIR)) {
|
|
38
|
+
fs.mkdirSync(ARCHIVE_DIR, { recursive: true });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const files = fs.readdirSync(DONE_DIR).filter(f => f.endsWith('.md') && f !== '.gitkeep.md');
|
|
42
|
+
const archived = [];
|
|
43
|
+
|
|
44
|
+
for (const file of files) {
|
|
45
|
+
const filePath = path.join(DONE_DIR, file);
|
|
46
|
+
try {
|
|
47
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
48
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
49
|
+
|
|
50
|
+
const ticketPlanId = normalizePlanId(frontmatter.parent_plan);
|
|
51
|
+
if (ticketPlanId !== planId) continue;
|
|
52
|
+
|
|
53
|
+
const ticketId = frontmatter.id || file.replace('.md', '');
|
|
54
|
+
|
|
55
|
+
frontmatter.updated_at = new Date().toISOString();
|
|
56
|
+
frontmatter.archived_at = new Date().toISOString();
|
|
57
|
+
|
|
58
|
+
const destPath = path.join(ARCHIVE_DIR, file);
|
|
59
|
+
fs.writeFileSync(destPath, serializeFrontmatter(frontmatter) + body, 'utf8');
|
|
60
|
+
fs.unlinkSync(filePath);
|
|
61
|
+
|
|
62
|
+
archived.push(ticketId);
|
|
63
|
+
console.log(`[ARCHIVE] ${ticketId}: done → archive`);
|
|
64
|
+
} catch (e) {
|
|
65
|
+
console.error(`[ERROR] Failed to archive ${file}: ${e.message}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
status: 'ok',
|
|
71
|
+
plan_id: planId,
|
|
72
|
+
archived: archived.length,
|
|
73
|
+
ticket_ids: archived.join(',')
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Main entry point
|
|
78
|
+
const rawArgs = process.argv.slice(2);
|
|
79
|
+
let planId;
|
|
80
|
+
|
|
81
|
+
if (rawArgs.length >= 1) {
|
|
82
|
+
// Прямой вызов или pipeline context
|
|
83
|
+
const arg = rawArgs[0];
|
|
84
|
+
const planMatch = arg.match(/plan_id:\s*(\S+)/i);
|
|
85
|
+
planId = planMatch ? normalizePlanId(planMatch[1]) : normalizePlanId(arg);
|
|
86
|
+
} else {
|
|
87
|
+
planId = extractPlanId();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (!planId) {
|
|
91
|
+
console.error('Usage: node archive-plan-tickets.js <plan_id>');
|
|
92
|
+
console.error('Example: node archive-plan-tickets.js PLAN-002');
|
|
93
|
+
printResult({ status: 'error', error: 'Missing plan_id argument' });
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const result = archivePlanTickets(planId);
|
|
98
|
+
printResult(result);
|
|
99
|
+
|
|
100
|
+
if (result.status === 'error') {
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
@@ -144,6 +144,13 @@ function checkBacklog(planId) {
|
|
|
144
144
|
|
|
145
145
|
for (const ticket of tickets) {
|
|
146
146
|
const { frontmatter, id } = ticket;
|
|
147
|
+
|
|
148
|
+
// Пропускаем тикеты, требующие ручного выполнения
|
|
149
|
+
if (frontmatter.type === 'human') {
|
|
150
|
+
console.log(`[INFO] ${id}: type is 'human', skipping (requires manual execution)`);
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
|
|
147
154
|
const conditions = frontmatter.conditions || [];
|
|
148
155
|
const dependencies = frontmatter.dependencies || [];
|
|
149
156
|
|
|
@@ -23,7 +23,7 @@ const WORKFLOW_DIR = path.join(PROJECT_DIR, '.workflow');
|
|
|
23
23
|
const TICKETS_DIR = path.join(WORKFLOW_DIR, 'tickets');
|
|
24
24
|
|
|
25
25
|
// Доступные статусы
|
|
26
|
-
const VALID_STATUSES = ['backlog', 'ready', 'in-progress', 'blocked', 'review', 'done'];
|
|
26
|
+
const VALID_STATUSES = ['backlog', 'ready', 'in-progress', 'blocked', 'review', 'done', 'archive'];
|
|
27
27
|
|
|
28
28
|
// Таблица допустимых переходов
|
|
29
29
|
const VALID_TRANSITIONS = {
|
|
@@ -32,7 +32,8 @@ const VALID_TRANSITIONS = {
|
|
|
32
32
|
'in-progress': ['done', 'blocked', 'review'],
|
|
33
33
|
'blocked': ['ready'],
|
|
34
34
|
'review': ['done', 'ready', 'in-progress', 'blocked'],
|
|
35
|
-
'done': ['ready', 'blocked']
|
|
35
|
+
'done': ['ready', 'blocked', 'archive'],
|
|
36
|
+
'archive': ['backlog']
|
|
36
37
|
};
|
|
37
38
|
|
|
38
39
|
/**
|
|
@@ -51,6 +51,12 @@ function moveToReady(ticketId) {
|
|
|
51
51
|
const content = fs.readFileSync(sourcePath, 'utf8');
|
|
52
52
|
const { frontmatter, body } = parseFrontmatter(content);
|
|
53
53
|
|
|
54
|
+
// Пропускаем тикеты, требующие ручного выполнения
|
|
55
|
+
if (frontmatter.type === 'human') {
|
|
56
|
+
console.log(`[INFO] ${ticketId}: type is 'human', skipping (requires manual execution)`);
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
54
60
|
frontmatter.updated_at = new Date().toISOString();
|
|
55
61
|
|
|
56
62
|
const newContent = serializeFrontmatter(frontmatter) + body;
|
|
@@ -36,6 +36,7 @@ const DONE_DIR = path.join(TICKETS_DIR, 'done');
|
|
|
36
36
|
const IN_PROGRESS_DIR = path.join(TICKETS_DIR, 'in-progress');
|
|
37
37
|
const BLOCKED_DIR = path.join(TICKETS_DIR, 'blocked');
|
|
38
38
|
const REVIEW_DIR = path.join(TICKETS_DIR, 'review');
|
|
39
|
+
const ARCHIVE_DIR = path.join(TICKETS_DIR, 'archive');
|
|
39
40
|
const BACKLOG_DIR = path.join(TICKETS_DIR, 'backlog');
|
|
40
41
|
|
|
41
42
|
|
|
@@ -60,7 +61,8 @@ function checkCondition(condition) {
|
|
|
60
61
|
const ids = Array.isArray(value) ? value : [value];
|
|
61
62
|
return ids.every(taskId => {
|
|
62
63
|
const donePath = path.join(DONE_DIR, `${taskId}.md`);
|
|
63
|
-
|
|
64
|
+
const archivePath = path.join(ARCHIVE_DIR, `${taskId}.md`);
|
|
65
|
+
return fs.existsSync(donePath) || fs.existsSync(archivePath);
|
|
64
66
|
});
|
|
65
67
|
|
|
66
68
|
case 'date_after':
|
|
@@ -146,7 +148,7 @@ function parseReviewSection(content) {
|
|
|
146
148
|
* @returns {object} Метрики: iterations, avgTimeToFirstPassed, failedVsPassed
|
|
147
149
|
*/
|
|
148
150
|
function calculateReviewMetrics() {
|
|
149
|
-
const allDirs = [BACKLOG_DIR, READY_DIR, IN_PROGRESS_DIR, BLOCKED_DIR, REVIEW_DIR, DONE_DIR];
|
|
151
|
+
const allDirs = [BACKLOG_DIR, READY_DIR, IN_PROGRESS_DIR, BLOCKED_DIR, REVIEW_DIR, DONE_DIR, ARCHIVE_DIR];
|
|
150
152
|
const ticketMetrics = {};
|
|
151
153
|
let totalFailed = 0;
|
|
152
154
|
let totalPassed = 0;
|
|
@@ -212,7 +214,8 @@ function checkDependencies(dependencies) {
|
|
|
212
214
|
|
|
213
215
|
return dependencies.every(depId => {
|
|
214
216
|
const donePath = path.join(DONE_DIR, `${depId}.md`);
|
|
215
|
-
|
|
217
|
+
const archivePath = path.join(ARCHIVE_DIR, `${depId}.md`);
|
|
218
|
+
return fs.existsSync(donePath) || fs.existsSync(archivePath);
|
|
216
219
|
});
|
|
217
220
|
}
|
|
218
221
|
|
|
@@ -232,7 +235,8 @@ function autoCorrectTickets(config) {
|
|
|
232
235
|
in_progress: IN_PROGRESS_DIR,
|
|
233
236
|
blocked: BLOCKED_DIR,
|
|
234
237
|
review: REVIEW_DIR,
|
|
235
|
-
done: DONE_DIR
|
|
238
|
+
done: DONE_DIR,
|
|
239
|
+
archive: ARCHIVE_DIR
|
|
236
240
|
};
|
|
237
241
|
|
|
238
242
|
/**
|
|
@@ -519,6 +523,12 @@ function pickNextTicket(planId) {
|
|
|
519
523
|
const eligibleTickets = tickets.filter(ticket => {
|
|
520
524
|
const { frontmatter } = ticket;
|
|
521
525
|
|
|
526
|
+
// Пропускаем тикеты, требующие ручного выполнения
|
|
527
|
+
if (frontmatter.type === 'human') {
|
|
528
|
+
logger.info(`Skipping ticket ${ticket.id}: type is 'human' (requires manual execution)`);
|
|
529
|
+
return false;
|
|
530
|
+
}
|
|
531
|
+
|
|
522
532
|
// Проверка условий
|
|
523
533
|
const conditions = frontmatter.conditions || [];
|
|
524
534
|
const conditionsMet = conditions.every(checkCondition);
|
|
@@ -569,6 +579,61 @@ function pickNextTicket(planId) {
|
|
|
569
579
|
};
|
|
570
580
|
}
|
|
571
581
|
|
|
582
|
+
/**
|
|
583
|
+
* Архивирует все done-тикеты, принадлежащие архивным планам (plans/archive/).
|
|
584
|
+
* Сканирует все планы в plans/archive/, находит их тикеты в done/ и перемещает в archive/.
|
|
585
|
+
*/
|
|
586
|
+
function archiveTicketsOfArchivedPlans() {
|
|
587
|
+
const archivedPlansDir = path.join(WORKFLOW_DIR, 'plans', 'archive');
|
|
588
|
+
if (!fs.existsSync(archivedPlansDir)) return { archived: [] };
|
|
589
|
+
|
|
590
|
+
// Собираем ID всех архивных планов
|
|
591
|
+
const archivedPlanIds = new Set();
|
|
592
|
+
const planFiles = fs.readdirSync(archivedPlansDir).filter(f => f.endsWith('.md'));
|
|
593
|
+
for (const file of planFiles) {
|
|
594
|
+
const id = normalizePlanId(file);
|
|
595
|
+
if (id) archivedPlanIds.add(id);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
if (archivedPlanIds.size === 0) return { archived: [] };
|
|
599
|
+
|
|
600
|
+
if (!fs.existsSync(DONE_DIR)) return { archived: [] };
|
|
601
|
+
|
|
602
|
+
if (!fs.existsSync(ARCHIVE_DIR)) {
|
|
603
|
+
fs.mkdirSync(ARCHIVE_DIR, { recursive: true });
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
const archived = [];
|
|
607
|
+
const files = fs.readdirSync(DONE_DIR).filter(f => f.endsWith('.md') && f !== '.gitkeep.md');
|
|
608
|
+
|
|
609
|
+
for (const file of files) {
|
|
610
|
+
const filePath = path.join(DONE_DIR, file);
|
|
611
|
+
try {
|
|
612
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
613
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
614
|
+
const ticketPlanId = normalizePlanId(frontmatter.parent_plan);
|
|
615
|
+
|
|
616
|
+
if (!ticketPlanId || !archivedPlanIds.has(ticketPlanId)) continue;
|
|
617
|
+
|
|
618
|
+
const ticketId = frontmatter.id || file.replace('.md', '');
|
|
619
|
+
|
|
620
|
+
frontmatter.updated_at = new Date().toISOString();
|
|
621
|
+
frontmatter.archived_at = new Date().toISOString();
|
|
622
|
+
|
|
623
|
+
const destPath = path.join(ARCHIVE_DIR, file);
|
|
624
|
+
fs.writeFileSync(destPath, serializeFrontmatter(frontmatter) + body, 'utf8');
|
|
625
|
+
fs.unlinkSync(filePath);
|
|
626
|
+
|
|
627
|
+
archived.push(ticketId);
|
|
628
|
+
logger.info(`[ARCHIVE] ${ticketId}: done → archive (plan ${ticketPlanId} is archived)`);
|
|
629
|
+
} catch (e) {
|
|
630
|
+
logger.warn(`Failed to archive ticket ${file}: ${e.message}`);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
return { archived };
|
|
635
|
+
}
|
|
636
|
+
|
|
572
637
|
// Main entry point
|
|
573
638
|
async function main() {
|
|
574
639
|
const planId = extractPlanId();
|
|
@@ -592,6 +657,12 @@ async function main() {
|
|
|
592
657
|
logger.info(`Auto-corrected ${correctionResult.moved.length} ticket(s)`);
|
|
593
658
|
}
|
|
594
659
|
|
|
660
|
+
// Архивируем done-тикеты архивных планов
|
|
661
|
+
const archiveResult = archiveTicketsOfArchivedPlans();
|
|
662
|
+
if (archiveResult.archived.length > 0) {
|
|
663
|
+
logger.info(`Archived ${archiveResult.archived.length} ticket(s) from archived plans: ${archiveResult.archived.join(', ')}`);
|
|
664
|
+
}
|
|
665
|
+
|
|
595
666
|
if (planId) {
|
|
596
667
|
const closeResult = checkAndClosePlan(WORKFLOW_DIR, planId);
|
|
597
668
|
if (closeResult.closed) {
|
|
@@ -27,6 +27,17 @@ ticket_prefix: COACH
|
|
|
27
27
|
| **План** | Файл в `.workflow/plans/` — источник контекста для анализа |
|
|
28
28
|
| **Тикет** | Файл в `.workflow/tickets/` — единица работы для анализа результатов |
|
|
29
29
|
| **Отчёт** | Файл в `.workflow/reports/` — источник метрик и выводов |
|
|
30
|
+
| **Бэклог коуча** | Файл `.workflow/coach-backlog.yaml` — реестр проанализированных тикетов и внесённых правок |
|
|
31
|
+
|
|
32
|
+
## Обязательный шаг: Бэклог коуча
|
|
33
|
+
|
|
34
|
+
**ПЕРЕД ЛЮБОЙ работой** выполни:
|
|
35
|
+
|
|
36
|
+
1. Прочитай `.workflow/coach-backlog.yaml` (если не существует — создай с пустыми секциями)
|
|
37
|
+
2. Загрузи `knowledge/backlog-management.md` — правила ведения бэклога
|
|
38
|
+
3. При анализе тикетов — **пропускай** те, что уже есть в `analyzed_tickets`
|
|
39
|
+
4. При внесении правок — **проверяй** `applied_changes`, не предлагай уже сделанное
|
|
40
|
+
5. **После завершения** — обнови бэклог: добавь проанализированные тикеты и внесённые правки
|
|
30
41
|
|
|
31
42
|
## Маршрутизация тикетов COACH-*
|
|
32
43
|
|
|
@@ -52,6 +63,7 @@ ticket_prefix: COACH
|
|
|
52
63
|
| `knowledge/skill-anatomy.md` | При создании или аудите скила — эталонная структура |
|
|
53
64
|
| `knowledge/common-antipatterns.md` | При аудите и ревью — типичные ошибки в скилах |
|
|
54
65
|
| `knowledge/prompt-engineering.md` | При улучшении формулировок в SKILL.md и воркфлоу |
|
|
66
|
+
| `knowledge/backlog-management.md` | **ВСЕГДА** — правила ведения бэклога проанализированных тикетов и правок |
|
|
55
67
|
|
|
56
68
|
## Загрузка алгоритмов
|
|
57
69
|
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Управление бэклогом коуча
|
|
2
|
+
|
|
3
|
+
Коуч ведёт бэклог своей работы в файле `.workflow/coach-backlog.yaml`. Этот файл — единственный источник правды о том, что уже было проанализировано и какие правки внесены.
|
|
4
|
+
|
|
5
|
+
## Структура бэклога
|
|
6
|
+
|
|
7
|
+
```yaml
|
|
8
|
+
# .workflow/coach-backlog.yaml
|
|
9
|
+
version: 1
|
|
10
|
+
last_updated: "2026-03-20"
|
|
11
|
+
|
|
12
|
+
# Проанализированные тикеты
|
|
13
|
+
analyzed_tickets:
|
|
14
|
+
- ticket_id: "GML-001"
|
|
15
|
+
ticket_file: "tickets/done/GML-001.md"
|
|
16
|
+
analyzed_date: "2026-03-20"
|
|
17
|
+
coach_ticket: "COACH-005" # тикет коуча, в рамках которого проведён анализ
|
|
18
|
+
findings_count: 3
|
|
19
|
+
summary: "Недостаток knowledge по TAM/SAM/SOM, нечёткий воркфлоу"
|
|
20
|
+
|
|
21
|
+
# Внесённые правки в скилы
|
|
22
|
+
applied_changes:
|
|
23
|
+
- change_id: "CHG-001"
|
|
24
|
+
date: "2026-03-20"
|
|
25
|
+
coach_ticket: "COACH-005"
|
|
26
|
+
target_skill: "growth-marketing-lead"
|
|
27
|
+
changed_files:
|
|
28
|
+
- "src/skills/growth-marketing-lead/knowledge/market-sizing.md"
|
|
29
|
+
- "src/skills/growth-marketing-lead/workflows/analyze.md"
|
|
30
|
+
change_type: "improve" # improve | fix | add | refactor
|
|
31
|
+
description: "Добавлен knowledge-модуль market-sizing, уточнён воркфлоу анализа"
|
|
32
|
+
based_on_tickets:
|
|
33
|
+
- "GML-001"
|
|
34
|
+
- "GML-003"
|
|
35
|
+
|
|
36
|
+
# Скилы, прошедшие аудит
|
|
37
|
+
audited_skills:
|
|
38
|
+
- skill_name: "growth-marketing-lead"
|
|
39
|
+
last_audit_date: "2026-03-20"
|
|
40
|
+
coach_ticket: "COACH-004"
|
|
41
|
+
score: 72
|
|
42
|
+
status: "NEEDS_WORK"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Правила работы с бэклогом
|
|
46
|
+
|
|
47
|
+
### Перед началом анализа (ОБЯЗАТЕЛЬНО)
|
|
48
|
+
|
|
49
|
+
1. Прочитай `.workflow/coach-backlog.yaml`
|
|
50
|
+
2. Проверь, какие тикеты уже проанализированы (`analyzed_tickets`)
|
|
51
|
+
3. **Пропусти** тикеты, которые уже есть в бэклоге
|
|
52
|
+
4. Проверь, какие правки уже внесены (`applied_changes`) в целевой скил
|
|
53
|
+
5. **Учитывай** уже внесённые правки — не предлагай то, что уже исправлено
|
|
54
|
+
|
|
55
|
+
### После завершения анализа (ОБЯЗАТЕЛЬНО)
|
|
56
|
+
|
|
57
|
+
1. Добавь каждый проанализированный тикет в `analyzed_tickets`
|
|
58
|
+
2. Добавь каждую внесённую правку в `applied_changes`
|
|
59
|
+
3. Если был аудит — обнови `audited_skills`
|
|
60
|
+
4. Обнови `last_updated`
|
|
61
|
+
|
|
62
|
+
### Генерация ID
|
|
63
|
+
|
|
64
|
+
- `change_id`: `CHG-{порядковый номер, 3 цифры}` — следующий после максимального в бэклоге
|
|
65
|
+
|
|
66
|
+
## Антипаттерны
|
|
67
|
+
|
|
68
|
+
- **Повторный анализ** — анализировать тикет, который уже в бэклоге
|
|
69
|
+
- **Дублирование правок** — предлагать изменение, которое уже в `applied_changes`
|
|
70
|
+
- **Забыть записать** — внести правку, но не добавить в бэклог
|
|
71
|
+
- **Пустой бэклог** — если файла нет, создай его с пустыми секциями
|
|
@@ -4,9 +4,18 @@
|
|
|
4
4
|
|
|
5
5
|
## Алгоритм выполнения
|
|
6
6
|
|
|
7
|
+
### 0. Загрузка бэклога (ОБЯЗАТЕЛЬНО)
|
|
8
|
+
|
|
9
|
+
1. Прочитай `.workflow/coach-backlog.yaml`
|
|
10
|
+
2. Выпиши список уже проанализированных `ticket_id` из `analyzed_tickets`
|
|
11
|
+
3. Выпиши список уже внесённых правок из `applied_changes` для целевого скила
|
|
12
|
+
4. **В шаге 1 пропусти тикеты, которые уже в бэклоге**
|
|
13
|
+
5. **В шаге 5 не предлагай правки, которые уже в `applied_changes`**
|
|
14
|
+
|
|
7
15
|
### 1. Сбор данных
|
|
8
16
|
|
|
9
|
-
Собери все завершённые артефакты, связанные со
|
|
17
|
+
Собери все завершённые артефакты, связанные со скилом.
|
|
18
|
+
**Исключи тикеты, уже присутствующие в бэклоге (`analyzed_tickets`).**
|
|
10
19
|
|
|
11
20
|
| Источник | Где искать | Что извлечь |
|
|
12
21
|
|----------|-----------|-------------|
|
|
@@ -55,3 +64,10 @@
|
|
|
55
64
|
- Конкретные рекомендации с указанием файлов для изменения
|
|
56
65
|
|
|
57
66
|
Результат оформи как часть отчёта → `templates/audit-report.md`
|
|
67
|
+
|
|
68
|
+
### 6. Обновление бэклога (ОБЯЗАТЕЛЬНО)
|
|
69
|
+
|
|
70
|
+
1. Добавь каждый проанализированный тикет в `analyzed_tickets` бэклога
|
|
71
|
+
2. Если внесены правки — добавь в `applied_changes`
|
|
72
|
+
3. Обнови `last_updated`
|
|
73
|
+
4. Правила формата → `knowledge/backlog-management.md`
|
|
@@ -4,6 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
## Алгоритм выполнения
|
|
6
6
|
|
|
7
|
+
### 0. Загрузка бэклога (ОБЯЗАТЕЛЬНО)
|
|
8
|
+
|
|
9
|
+
1. Прочитай `.workflow/coach-backlog.yaml`
|
|
10
|
+
2. Проверь `audited_skills` — когда последний раз проводился аудит этого скила
|
|
11
|
+
3. Проверь `applied_changes` — какие правки уже внесены после предыдущего аудита
|
|
12
|
+
4. **Учитывай уже внесённые правки** — не отмечай как проблему то, что уже исправлено
|
|
13
|
+
|
|
7
14
|
### 1. Инвентаризация
|
|
8
15
|
|
|
9
16
|
Прочитай все файлы скила и составь карту:
|
|
@@ -52,3 +59,10 @@
|
|
|
52
59
|
- Список найденных проблем с приоритетами
|
|
53
60
|
- Конкретные рекомендации по каждой проблеме
|
|
54
61
|
- План улучшений с приоритизацией
|
|
62
|
+
|
|
63
|
+
### 7. Обновление бэклога (ОБЯЗАТЕЛЬНО)
|
|
64
|
+
|
|
65
|
+
1. Обнови/добавь запись в `audited_skills` бэклога
|
|
66
|
+
2. Если анализировались тикеты — добавь в `analyzed_tickets`
|
|
67
|
+
3. Обнови `last_updated`
|
|
68
|
+
4. Правила формата → `knowledge/backlog-management.md`
|
|
@@ -4,6 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
## Алгоритм выполнения
|
|
6
6
|
|
|
7
|
+
### 0. Загрузка бэклога (ОБЯЗАТЕЛЬНО)
|
|
8
|
+
|
|
9
|
+
1. Прочитай `.workflow/coach-backlog.yaml`
|
|
10
|
+
2. Проверь `applied_changes` для целевого скила — какие правки уже внесены
|
|
11
|
+
3. **Не вноси повторно правки, которые уже есть в бэклоге**
|
|
12
|
+
4. Учитывай контекст предыдущих улучшений при планировании новых
|
|
13
|
+
|
|
7
14
|
### 1. Определи scope улучшения
|
|
8
15
|
|
|
9
16
|
Из тикета извлеки:
|
|
@@ -54,3 +61,10 @@
|
|
|
54
61
|
- Какие файлы затронуты
|
|
55
62
|
- Ожидаемый эффект
|
|
56
63
|
- Как проверить результат
|
|
64
|
+
|
|
65
|
+
### 8. Обновление бэклога (ОБЯЗАТЕЛЬНО)
|
|
66
|
+
|
|
67
|
+
1. Добавь каждую внесённую правку в `applied_changes` бэклога
|
|
68
|
+
2. Если анализировались тикеты — добавь в `analyzed_tickets`
|
|
69
|
+
3. Обнови `last_updated`
|
|
70
|
+
4. Правила формата → `knowledge/backlog-management.md`
|
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
## Алгоритм выполнения
|
|
6
6
|
|
|
7
|
+
### 0. Загрузка бэклога (ОБЯЗАТЕЛЬНО)
|
|
8
|
+
|
|
9
|
+
1. Прочитай `.workflow/coach-backlog.yaml`
|
|
10
|
+
2. Проверь `applied_changes` — какие правки уже внесены в целевой скил
|
|
11
|
+
3. **Учитывай контекст предыдущих правок** при формировании замечаний
|
|
12
|
+
|
|
7
13
|
### 1. Определи фокус ревью
|
|
8
14
|
|
|
9
15
|
Из тикета извлеки: какой аспект проверить?
|
|
@@ -13,18 +13,9 @@ description: Декомпозировать план на исполняемые
|
|
|
13
13
|
- Нужно разбить крупную задачу на подзадачи
|
|
14
14
|
- Требуется детальная декомпозиция
|
|
15
15
|
|
|
16
|
-
## ⛔
|
|
16
|
+
## ⛔ Какой план декомпозировать
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
**Если в Context или Instructions есть `plan_id` или `plan_file` или путь к конкретному плану (например `PLAN-005.md`):**
|
|
21
|
-
- Декомпозируй ТОЛЬКО этот план
|
|
22
|
-
- НЕ сканируй `plans/current/` — это ЗАПРЕЩЕНО
|
|
23
|
-
- НЕ проверяй статус плана (`draft`, `active` — неважно) — это ЗАПРЕЩЕНО
|
|
24
|
-
- НЕ читай и не упоминай другие планы — это ЗАПРЕЩЕНО
|
|
25
|
-
- Открой файл плана и сразу переходи к шагу 1
|
|
26
|
-
|
|
27
|
-
**Только если нигде в промпте нет указания на конкретный план** — тогда сканируй `plans/current/` и декомпозируй только планы со статусом `approved`.
|
|
18
|
+
Декомпозируй план, указанный в секции Instructions твоего промпта. Открой этот файл и сразу переходи к шагу 1. Не сканируй папку plans/current/. Не проверяй статус плана. Не читай другие планы.
|
|
28
19
|
|
|
29
20
|
## Шаги выполнения
|
|
30
21
|
|
|
@@ -65,6 +56,9 @@ description: Декомпозировать план на исполняемые
|
|
|
65
56
|
- DoD-критерий: «Результаты внесены непосредственно в файл тикета»
|
|
66
57
|
- Указание в каком формате ожидается результат
|
|
67
58
|
|
|
59
|
+
**⛔ НЕ создавать conditional/fallback HUMAN-тикеты заранее.**
|
|
60
|
+
Если HUMAN-тикет нужен только при провале агентского тикета (например, «выполнить вручную, если агент не смог пройти auth») — **не создавай его на этапе декомпозиции**. Такие тикеты создаёт `decompose-gaps` когда проблема реально возникла. Иначе они висят в backlog вечно и засоряют доску.
|
|
61
|
+
|
|
68
62
|
**Пример разбиения гибридной задачи:**
|
|
69
63
|
|
|
70
64
|
Задача из плана: «Провести конкурентный анализ и настроить трекинг конкурентов в аналитике»
|