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.
- package/configs/pipeline.yaml +6 -3
- package/package.json +3 -2
- package/src/runner.mjs +51 -11
- package/src/skills/execute-task/SKILL.md +6 -4
package/configs/pipeline.yaml
CHANGED
|
@@ -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:
|
|
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.
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|
968
|
-
? path.resolve(projectRoot, this.pipeline.execution.log_file)
|
|
969
|
-
: path.resolve(projectRoot, '.workflow/logs
|
|
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
|
-
|
|
1377
|
-
|
|
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
|
-
|
|
20
|
+
**ОБЯЗАТЕЛЬНО:** Используй ТОЛЬКО `ticket_id` из секции Context промпта. Запрещено выбирать другой тикет.
|
|
21
21
|
|
|
22
22
|
```
|
|
23
|
-
Путь: .workflow/tickets/
|
|
23
|
+
Путь: .workflow/tickets/in-progress/{TICKET-ID}.md
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
|
|
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
|
-
- Тикет остаётся
|
|
114
|
+
- Тикет остаётся на месте (перемещение выполняется отдельным stage)
|
|
113
115
|
|
|
114
116
|
## Связанные skills
|
|
115
117
|
|