workflow-ai 1.0.11 → 1.0.13

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.
@@ -270,13 +270,13 @@ pipeline:
270
270
  fallback_agent: kilo-deepseek
271
271
  skill: execute-task
272
272
  counter: task_attempts
273
- agent_by_type:
274
- impl: claude-sonnet
275
- fix: claude-sonnet
276
- arch: claude-opus
277
- review: qwen-code
278
- docs: qwen-code
279
- plan: claude-opus
273
+ # agent_by_type:
274
+ # impl: claude-sonnet
275
+ # fix: claude-sonnet
276
+ # arch: claude-opus
277
+ # review: qwen-code
278
+ # docs: qwen-code
279
+ # plan: claude-opus
280
280
  agent_by_attempt:
281
281
  1: qwen-code
282
282
  2: claude-sonnet
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "workflow-ai",
3
- "version": "1.0.11",
3
+ "version": "1.0.13",
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
@@ -133,9 +133,18 @@ export function getLastReviewStatus(content) {
133
133
  });
134
134
 
135
135
  if (dataRows.length > 0) {
136
- const lastRow = dataRows[dataRows.length - 1];
137
- const cells = lastRow.split('|').map(c => c.trim()).filter(c => c);
138
- // Статус обычно во второй колонке (после даты), может содержать эмодзи (✅ passed / ❌ failed)
136
+ // Ищем строку с самой поздней датой (при равных — последняя по позиции)
137
+ let latestRow = dataRows[0];
138
+ let latestDate = '';
139
+ for (const row of dataRows) {
140
+ const cells = row.split('|').map(c => c.trim()).filter(c => c);
141
+ const dateStr = cells[0] || '';
142
+ if (dateStr >= latestDate) {
143
+ latestDate = dateStr;
144
+ latestRow = row;
145
+ }
146
+ }
147
+ const cells = latestRow.split('|').map(c => c.trim()).filter(c => c);
139
148
  const statusRaw = cells[1]?.toLowerCase() || '';
140
149
  if (statusRaw.includes('passed')) return 'passed';
141
150
  if (statusRaw.includes('failed')) return 'failed';
@@ -145,9 +154,17 @@ export function getLastReviewStatus(content) {
145
154
  // Пробуем распарсить текстовый формат (список)
146
155
  const listItems = reviewSection.split('\n').filter(line => line.trim().match(/^[-*]\s/));
147
156
  if (listItems.length > 0) {
148
- const lastItem = listItems[listItems.length - 1].trim();
149
- // Ищем статус в формате "- дата: passed/failed - комментарий"
150
- const statusMatch = lastItem.match(/:\s*(passed|failed)\b/i);
157
+ // Ищем элемент с самой поздней датой (при равных — последний по позиции)
158
+ let latestItem = listItems[0].trim();
159
+ let latestDate = '';
160
+ for (const item of listItems) {
161
+ const dateMatch = item.match(/(\d{4}-\d{2}-\d{2})/);
162
+ if (dateMatch && dateMatch[1] >= latestDate) {
163
+ latestDate = dateMatch[1];
164
+ latestItem = item.trim();
165
+ }
166
+ }
167
+ const statusMatch = latestItem.match(/:\s*(passed|failed)\b/i);
151
168
  if (statusMatch) return statusMatch[1].toLowerCase();
152
169
  }
153
170
 
@@ -186,6 +186,15 @@ function autoCorrectTickets() {
186
186
  }
187
187
  }
188
188
 
189
+ // Правила для backlog/ (защита от ошибочного перемещения завершённых тикетов)
190
+ processDirectory(BACKLOG_DIR, [
191
+ {
192
+ condition: (status) => status === 'passed',
193
+ toDir: DONE_DIR,
194
+ reason: 'review passed'
195
+ }
196
+ ]);
197
+
189
198
  // Правила для blocked/
190
199
  processDirectory(BLOCKED_DIR, [
191
200
  {