workflow-ai 1.0.8 → 1.0.9

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.
@@ -97,6 +97,9 @@ pipeline:
97
97
  workdir: "."
98
98
  description: "Скрипт для перемещения завершённых тикетов из in-progress/ в review/"
99
99
 
100
+
101
+ default_agent: qwen-code
102
+
100
103
  # ===========================================================================
101
104
  # Этапы (stages)
102
105
  # ===========================================================================
@@ -301,8 +304,7 @@ pipeline:
301
304
  # -------------------------------------------------------------------------
302
305
  # 4b. increment-task-attempts
303
306
  # Отдельный стейдж обновления счётчика попыток выполнения тикета.
304
- # При достижении max — блокирует тикет. Иначе — отправляет на доработку.
305
- # Проверка лимита происходит ДО повторного execute-task.
307
+ # При достижении max — блокирует тикет. Иначе — возвращает в очередь.
306
308
  # -------------------------------------------------------------------------
307
309
  increment-task-attempts:
308
310
  description: "Обновить счётчик попыток выполнения тикета"
@@ -311,9 +313,10 @@ pipeline:
311
313
  max: 6
312
314
  goto:
313
315
  default:
314
- stage: execute-task
316
+ stage: move-ticket
315
317
  params:
316
318
  ticket_id: "$context.ticket_id"
319
+ target: ready
317
320
  max_reached:
318
321
  stage: move-ticket
319
322
  params:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "workflow-ai",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "description": "AI Agent Workflow Coordinator — kanban-based pipeline for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -23,7 +23,8 @@
23
23
  "node": ">=18.0.0"
24
24
  },
25
25
  "scripts": {
26
- "test": "node --test src/tests/*.test.mjs"
26
+ "test": "node --test src/tests/*.test.mjs",
27
+ "release": "npm version patch && npm publish"
27
28
  },
28
29
  "dependencies": {
29
30
  "js-yaml": "^4.1.0"
package/src/runner.mjs CHANGED
@@ -378,6 +378,41 @@ class PromptBuilder {
378
378
  // ResultParser — парсит вывод агентов и извлекает структурированные данные
379
379
  // ============================================================================
380
380
  class ResultParser {
381
+ // Карта нормализации статусов: синонимы → каноническое значение
382
+ static STATUS_ALIASES = {
383
+ pass: 'passed',
384
+ approved: 'passed',
385
+ success: 'passed',
386
+ succeeded: 'passed',
387
+ ok: 'passed',
388
+ accepted: 'passed',
389
+ lgtm: 'passed',
390
+ fixed: 'passed',
391
+ resolved: 'passed',
392
+ fail: 'failed',
393
+ rejected: 'failed',
394
+ denied: 'failed',
395
+ not_passed: 'failed',
396
+ err: 'error',
397
+ crash: 'error',
398
+ timeout: 'error',
399
+ };
400
+
401
+ /**
402
+ * Нормализует статус: приводит синонимы к каноническому значению
403
+ * @param {string} status
404
+ * @returns {string}
405
+ */
406
+ normalizeStatus(status) {
407
+ const lower = status.toLowerCase();
408
+ const canonical = ResultParser.STATUS_ALIASES[lower];
409
+ if (canonical) {
410
+ console.log(`[ResultParser] Normalized status: "${status}" → "${canonical}"`);
411
+ return canonical;
412
+ }
413
+ return status;
414
+ }
415
+
381
416
  /**
382
417
  * Парсит вывод агента и извлекает результат между маркерами
383
418
  * @param {string} output - stdout агента
@@ -396,10 +431,11 @@ class ResultParser {
396
431
  const resultBlock = output.substring(startIdx + marker.length, endIdx).trim();
397
432
  const data = this.parseResultBlock(resultBlock);
398
433
 
399
- console.log(`[ResultParser] Parsed structured result for ${stageId}: status=${data.status}`);
434
+ const normalizedStatus = this.normalizeStatus(data.status || 'default');
435
+ console.log(`[ResultParser] Parsed structured result for ${stageId}: status=${normalizedStatus}`);
400
436
 
401
437
  return {
402
- status: data.status || 'default',
438
+ status: normalizedStatus,
403
439
  data: data.data || {},
404
440
  raw: output,
405
441
  parsed: true
@@ -484,10 +520,11 @@ class ResultParser {
484
520
  }
485
521
  }
486
522
 
487
- console.log(`[ResultParser] Fallback parsing for ${stageId}: status=${status}`);
523
+ const normalizedStatus = this.normalizeStatus(status);
524
+ console.log(`[ResultParser] Fallback parsing for ${stageId}: status=${normalizedStatus}`);
488
525
 
489
526
  return {
490
- status,
527
+ status: normalizedStatus,
491
528
  data: extractedData,
492
529
  raw: output,
493
530
  parsed: false
@@ -682,7 +719,7 @@ class StageExecutor {
682
719
  }
683
720
 
684
721
  // Выбираем агента: если есть agent_by_attempt и счётчик — ротация по попыткам
685
- let agentId = stage.agent;
722
+ let agentId = stage.agent || this.pipeline.default_agent;
686
723
  if (stage.agent_by_attempt && stage.counter) {
687
724
  const attempt = this.counters[stage.counter] || 0;
688
725
  if (stage.agent_by_attempt[attempt]) {
@@ -963,10 +1000,12 @@ class PipelineRunner {
963
1000
  // Базовая директория проекта вычисляется динамически
964
1001
  const projectRoot = args.project ? path.resolve(args.project) : findProjectRoot();
965
1002
 
966
- // Инициализация Logger
967
- const logFilePath = this.pipeline.execution?.log_file
968
- ? path.resolve(projectRoot, this.pipeline.execution.log_file)
969
- : path.resolve(projectRoot, '.workflow/logs/pipeline.log');
1003
+ // Инициализация Logger — каждый запуск пишется в отдельный файл
1004
+ const logDir = this.pipeline.execution?.log_file
1005
+ ? path.dirname(path.resolve(projectRoot, this.pipeline.execution.log_file))
1006
+ : path.resolve(projectRoot, '.workflow/logs');
1007
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-').replace('T', '_').substring(0, 19);
1008
+ const logFilePath = path.resolve(logDir, `pipeline_${timestamp}.log`);
970
1009
  this.logger = new Logger(logFilePath);
971
1010
  this.loggerInitialized = false;
972
1011
 
@@ -1373,8 +1412,9 @@ function validateConfig(config) {
1373
1412
  const stageIds = Object.keys(pipeline.stages);
1374
1413
 
1375
1414
  for (const [stageId, stage] of Object.entries(pipeline.stages)) {
1376
- if (stage.agent && !agentIds.includes(stage.agent)) {
1377
- errors.push(`Stage "${stageId}" references non-existent agent: ${stage.agent}`);
1415
+ const resolvedAgent = stage.agent || pipeline.default_agent;
1416
+ if (resolvedAgent && !agentIds.includes(resolvedAgent)) {
1417
+ errors.push(`Stage "${stageId}" references non-existent agent: ${resolvedAgent}`);
1378
1418
  }
1379
1419
 
1380
1420
  if (stage.goto) {
@@ -17,13 +17,15 @@ description: Выполнить задачу из тикета. Использу
17
17
 
18
18
  ### 1. Прочитать тикет
19
19
 
20
- ID тикета передаётся в промпте как `ticket_id` в секции Context.
20
+ **ОБЯЗАТЕЛЬНО:** Используй ТОЛЬКО `ticket_id` из секции Context промпта. Запрещено выбирать другой тикет.
21
21
 
22
22
  ```
23
- Путь: .workflow/tickets/ready/{TICKET-ID}.md
23
+ Путь: .workflow/tickets/in-progress/{TICKET-ID}.md
24
24
  ```
25
25
 
26
- **Важно:** НЕ перемещай тикет он остаётся в `ready/` на время выполнения. Перемещение выполняется отдельным stage пайплайна.
26
+ Если тикет не найден в `in-progress/`, проверь `review/` (при повторном выполнении тикет может быть там).
27
+
28
+ **Важно:** НЕ перемещай тикет. Перемещение выполняется отдельным stage пайплайна.
27
29
 
28
30
  Извлечь:
29
31
  - Описание задачи
@@ -109,7 +111,7 @@ status: default
109
111
 
110
112
  - Выполненная работа (код, документация и т.д.)
111
113
  - Обновлённый тикет с секцией Result
112
- - Тикет остаётся в `ready/` (перемещение выполняется отдельным stage)
114
+ - Тикет остаётся на месте (перемещение выполняется отдельным stage)
113
115
 
114
116
  ## Связанные skills
115
117