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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "workflow-ai",
3
- "version": "1.0.38",
3
+ "version": "1.0.40",
4
4
  "description": "AI Agent Workflow Coordinator — kanban-based pipeline for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
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
- return { closed: true, reason: 'All tickets done', total, done };
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
- return fs.existsSync(donePath);
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
- return fs.existsSync(donePath);
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
+ - **Пустой бэклог** — если файла нет, создай его с пустыми секциями
@@ -0,0 +1,10 @@
1
+ # Шаблон инициализации бэклога коуча
2
+ # Скопировать в .workflow/coach-backlog.yaml при первом запуске
3
+ version: 1
4
+ last_updated: ""
5
+
6
+ analyzed_tickets: []
7
+
8
+ applied_changes: []
9
+
10
+ audited_skills: []
@@ -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
- Ты получаешь промпт с секциями Context и Instructions.
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
  Задача из плана: «Провести конкурентный анализ и настроить трекинг конкурентов в аналитике»