workflow-ai 1.0.21 → 1.0.23

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.
@@ -285,10 +285,10 @@ pipeline:
285
285
  # docs: qwen-code
286
286
  admin: kilo-minimax
287
287
  agent_by_attempt:
288
- 1: qwen-code
289
- 2: claude-sonnet
290
- 3: claude-opus
291
- 4: kilo-deepseek
288
+ 2: qwen-code
289
+ 3: claude-sonnet
290
+ 4: claude-opus
291
+ 5: kilo-deepseek
292
292
  goto:
293
293
  default:
294
294
  stage: move-to-review
@@ -482,6 +482,6 @@ pipeline:
482
482
  # ===========================================================================
483
483
  execution:
484
484
  max_steps: 1500
485
- delay_between_stages: 1
485
+ delay_between_stages: 5
486
486
  timeout_per_stage: 1800
487
487
  log_file: ".workflow/logs/pipeline.log"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "workflow-ai",
3
- "version": "1.0.21",
3
+ "version": "1.0.23",
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
@@ -117,10 +117,16 @@ export function getLastReviewStatus(content) {
117
117
  if (!content) return null;
118
118
 
119
119
  // Находим секцию "## Ревью" — захватываем всё до следующего заголовка ## или конца файла
120
- const reviewSectionMatch = content.match(/^##\s*Ревью\s*\n([\s\S]*)(?=\n^##\s|$)/m);
121
- if (!reviewSectionMatch) return null;
120
+ const headerIdx = content.search(/^##\s*Ревью\s*$/m);
121
+ if (headerIdx === -1) return null;
122
122
 
123
- const reviewSection = reviewSectionMatch[1].trim();
123
+ const bodyStart = content.indexOf('\n', headerIdx);
124
+ if (bodyStart === -1) return null;
125
+
126
+ const nextH2 = content.indexOf('\n## ', bodyStart);
127
+ const reviewSection = (nextH2 === -1
128
+ ? content.slice(bodyStart + 1)
129
+ : content.slice(bodyStart + 1, nextH2)).trim();
124
130
  if (!reviewSection) return null;
125
131
 
126
132
  // Пробуем распарсить табличный формат
package/src/runner.mjs CHANGED
@@ -725,28 +725,24 @@ class StageExecutor {
725
725
  }
726
726
 
727
727
  // Выбираем агента по приоритету:
728
- // 1. agent_by_attempt[counter] — ротация по попыткам
729
- // 2. agent_by_type[task_type] выбор по типу задачи
728
+ // 1. attempt=1 → agent_by_type[task_type] (первая попытка — по типу задачи)
729
+ // 2. attempt>1 → agent_by_attempt[counter] (повторные попытки ротация)
730
730
  // 3. stage.agent — явно указанный агент stage
731
731
  // 4. default_agent — глобальный дефолт
732
732
  let agentId = stage.agent || this.pipeline.default_agent;
733
+ const attempt = (stage.counter && this.counters[stage.counter]) || 0;
733
734
 
734
- // Приоритет 1: agent_by_type (выбор по типу задачи)
735
- // task_type берётся из контекста (возвращается из pick-next-task)
736
- if (stage.agent_by_type && this.context.task_type) {
735
+ if (attempt <= 1 && stage.agent_by_type && this.context.task_type) {
736
+ // Первая попытка: выбор по типу задачи
737
737
  const taskType = this.context.task_type;
738
738
  if (stage.agent_by_type[taskType]) {
739
- const typeBasedAgent = stage.agent_by_type[taskType];
739
+ agentId = stage.agent_by_type[taskType];
740
740
  if (this.logger) {
741
- this.logger.info(`Agent by type: task_type="${taskType}" → ${typeBasedAgent}`, stageId);
741
+ this.logger.info(`Agent by type: task_type="${taskType}" → ${agentId}`, stageId);
742
742
  }
743
- agentId = typeBasedAgent;
744
743
  }
745
- }
746
-
747
- // Приоритет 2: agent_by_attempt (ротация по попыткам) — перекрывает agent_by_type
748
- if (stage.agent_by_attempt && stage.counter) {
749
- const attempt = this.counters[stage.counter] || 0;
744
+ } else if (stage.agent_by_attempt && attempt > 1) {
745
+ // Повторные попытки: ротация по agent_by_attempt
750
746
  if (stage.agent_by_attempt[attempt]) {
751
747
  agentId = stage.agent_by_attempt[attempt];
752
748
  if (this.logger) {
@@ -17,6 +17,21 @@ description: Проверить актуальность тикета перед
17
17
 
18
18
  ## Шаги проверки
19
19
 
20
+ ### 0. Быстрый выход по существующему ревью
21
+
22
+ **До любых проверок** найди в тикете секцию `## Ревью`.
23
+
24
+ Если последняя запись имеет статус `⏭ skipped` — тикет уже был проверен и пропущен. **Повторно проверять НЕ НАДО**, новую запись skipped добавлять НЕ НАДО. Немедленно верни:
25
+
26
+ ```
27
+ ---RESULT---
28
+ status: irrelevant
29
+ reason: already_skipped
30
+ ---RESULT---
31
+ ```
32
+
33
+ Если последней записи нет или статус другой — переходи к шагу 1.
34
+
20
35
  ### 1. Прочитать тикет
21
36
 
22
37
  **ОБЯЗАТЕЛЬНО:** Используй `ticket_id` из секции Context промпта.
@@ -86,7 +101,7 @@ description: Проверить актуальность тикета перед
86
101
  Формат записи:
87
102
 
88
103
  ```
89
- | {текущая дата} | skipped | {причина irrelevant: plan_inactive / dod_completed / dependencies_inactive} |
104
+ | {текущая дата} | ⏭️ skipped | {причина irrelevant: plan_inactive / dod_completed / dependencies_inactive} |
90
105
  ```
91
106
 
92
107
  Пример:
@@ -96,7 +111,7 @@ description: Проверить актуальность тикета перед
96
111
 
97
112
  | Дата | Статус | Самари |
98
113
  |------|--------|--------|
99
- | 2026-03-10 | skipped | DoD уже выполнен, тикет неактуален |
114
+ | 2026-03-10 | ⏭️ skipped | DoD уже выполнен, тикет неактуален |
100
115
  ```
101
116
 
102
117
  Это единственная допустимая запись в файл — только добавление строки в таблицу ревью.
@@ -83,6 +83,8 @@ ID тикета передаётся в промпте как `ticket_id` в с
83
83
 
84
84
  ### 4. Сформировать вердикт
85
85
 
86
+ > **ВАЖНО:** Review-result НИКОГДА не пишет статус `skipped`. Допустимы только `passed` или `failed`. Статус `skipped` — прерогатива check-relevance.
87
+
86
88
  Возвращать структурированный результат строго в одном из двух форматов:
87
89
 
88
90
  > **КРИТИЧНО**: `status` принимает ТОЛЬКО два значения: `passed` или `failed`.