workflow-ai 1.0.3 → 1.0.5
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 +1 -1
- package/package.json +1 -1
- package/src/lib/utils.mjs +32 -0
- package/src/runner.mjs +2 -31
- package/src/scripts/check-conditions.js +3 -32
- package/src/scripts/pick-next-task.js +18 -6
- package/templates/plan-template.md +1 -1
- package/templates/report-template.md +2 -1
package/configs/pipeline.yaml
CHANGED
|
@@ -53,7 +53,7 @@ pipeline:
|
|
|
53
53
|
command: "kilo"
|
|
54
54
|
args: ["-m", "kilo/minimax/minimax-m2.5:free", "--agent", "orchestrator", "run"]
|
|
55
55
|
workdir: "."
|
|
56
|
-
description: "
|
|
56
|
+
description: "Агент с мульти-режимами (architect, code, debug)"
|
|
57
57
|
|
|
58
58
|
kilo-glm:
|
|
59
59
|
command: "kilo"
|
package/package.json
CHANGED
package/src/lib/utils.mjs
CHANGED
|
@@ -53,6 +53,38 @@ export function printResult(result) {
|
|
|
53
53
|
console.log('---RESULT---');
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Нормализует входное значение в формат PLAN-NNN.
|
|
58
|
+
* Принимает: "PLAN-007", "7", "007", "plan-7", "plans/PLAN-007.md", "/abs/path/PLAN-007.md"
|
|
59
|
+
*
|
|
60
|
+
* @param {string} raw - Входное значение
|
|
61
|
+
* @returns {string|null} Нормализованный ID плана или null
|
|
62
|
+
*/
|
|
63
|
+
export function normalizePlanId(raw) {
|
|
64
|
+
if (!raw) return null;
|
|
65
|
+
|
|
66
|
+
const basename = path.basename(raw, '.md');
|
|
67
|
+
|
|
68
|
+
const full = basename.match(/^plan-(\d+)$/i);
|
|
69
|
+
if (full) return `PLAN-${String(parseInt(full[1], 10)).padStart(3, '0')}`;
|
|
70
|
+
|
|
71
|
+
const num = raw.trim().match(/^(\d+)$/);
|
|
72
|
+
if (num) return `PLAN-${String(parseInt(num[1], 10)).padStart(3, '0')}`;
|
|
73
|
+
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Извлекает plan_id из аргументов командной строки (контекст пайплайна).
|
|
79
|
+
*
|
|
80
|
+
* @returns {string|null} Нормализованный plan_id или null
|
|
81
|
+
*/
|
|
82
|
+
export function extractPlanId() {
|
|
83
|
+
const prompt = process.argv.slice(2)[0] || '';
|
|
84
|
+
const match = prompt.match(/plan_id:\s*(\S+)/i);
|
|
85
|
+
return match ? normalizePlanId(match[1]) : null;
|
|
86
|
+
}
|
|
87
|
+
|
|
56
88
|
/**
|
|
57
89
|
* Возвращает абсолютный путь к корню npm-пакета через import.meta.url.
|
|
58
90
|
*
|
package/src/runner.mjs
CHANGED
|
@@ -924,8 +924,6 @@ class PipelineRunner {
|
|
|
924
924
|
// Инициализация контекста из CLI аргументов
|
|
925
925
|
if (args.plan) {
|
|
926
926
|
this.context.plan_id = args.plan;
|
|
927
|
-
} else if (!this.context.plan_id) {
|
|
928
|
-
this._detectCurrentPlanId(projectRoot);
|
|
929
927
|
}
|
|
930
928
|
|
|
931
929
|
// Инициализация FileGuard для защиты файлов от изменений агентами
|
|
@@ -937,32 +935,6 @@ class PipelineRunner {
|
|
|
937
935
|
this.setupGracefulShutdown();
|
|
938
936
|
}
|
|
939
937
|
|
|
940
|
-
/**
|
|
941
|
-
* Автоматически определяет plan_id из .workflow/plans/current/
|
|
942
|
-
* Ищет первый .md файл (не .gitkeep) и читает поле id: из frontmatter
|
|
943
|
-
* @param {string} projectRoot - корневая директория проекта
|
|
944
|
-
* @returns {string} plan_id или пустая строка если не найден
|
|
945
|
-
*/
|
|
946
|
-
_detectCurrentPlanId(projectRoot) {
|
|
947
|
-
const plansDir = path.resolve(projectRoot, '.workflow/plans/current');
|
|
948
|
-
if (!fs.existsSync(plansDir)) return '';
|
|
949
|
-
|
|
950
|
-
const files = fs.readdirSync(plansDir)
|
|
951
|
-
.filter(f => f.endsWith('.md') && f !== '.gitkeep.md')
|
|
952
|
-
.sort();
|
|
953
|
-
|
|
954
|
-
for (const file of files) {
|
|
955
|
-
const content = fs.readFileSync(path.join(plansDir, file), 'utf8');
|
|
956
|
-
const match = content.match(/^id:\s*["']?([^"'\n]+)["']?/m);
|
|
957
|
-
if (match) {
|
|
958
|
-
this.context.plan_id = match[1].trim();
|
|
959
|
-
return this.context.plan_id;
|
|
960
|
-
}
|
|
961
|
-
}
|
|
962
|
-
|
|
963
|
-
return '';
|
|
964
|
-
}
|
|
965
|
-
|
|
966
938
|
/**
|
|
967
939
|
* Асинхронно инициализирует runner (logger)
|
|
968
940
|
*/
|
|
@@ -977,10 +949,9 @@ class PipelineRunner {
|
|
|
977
949
|
}
|
|
978
950
|
|
|
979
951
|
if (this.context.plan_id) {
|
|
980
|
-
|
|
981
|
-
this.logger.info(`Plan ID: ${this.context.plan_id} (${source})`, 'PipelineRunner');
|
|
952
|
+
this.logger.info(`Plan ID: ${this.context.plan_id}`, 'PipelineRunner');
|
|
982
953
|
} else {
|
|
983
|
-
this.logger.
|
|
954
|
+
this.logger.info('No plan_id set — processing all tickets', 'PipelineRunner');
|
|
984
955
|
}
|
|
985
956
|
}
|
|
986
957
|
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
import fs from 'fs';
|
|
29
29
|
import path from 'path';
|
|
30
30
|
import { findProjectRoot } from '../lib/find-root.mjs';
|
|
31
|
-
import { parseFrontmatter, printResult } from '../lib/utils.mjs';
|
|
31
|
+
import { parseFrontmatter, printResult, normalizePlanId, extractPlanId } from '../lib/utils.mjs';
|
|
32
32
|
|
|
33
33
|
const PROJECT_DIR = findProjectRoot();
|
|
34
34
|
const WORKFLOW_DIR = path.join(PROJECT_DIR, '.workflow');
|
|
@@ -51,6 +51,7 @@ function checkCondition(condition) {
|
|
|
51
51
|
return !fs.existsSync(path.join(PROJECT_DIR, value));
|
|
52
52
|
|
|
53
53
|
case 'tasks_completed': {
|
|
54
|
+
if (!value || (Array.isArray(value) && value.length === 0)) return true;
|
|
54
55
|
const ids = Array.isArray(value) ? value : [value];
|
|
55
56
|
return ids.every(taskId => fs.existsSync(path.join(DONE_DIR, `${taskId}.md`)));
|
|
56
57
|
}
|
|
@@ -101,43 +102,13 @@ function readTickets(dir) {
|
|
|
101
102
|
return tickets;
|
|
102
103
|
}
|
|
103
104
|
|
|
104
|
-
/**
|
|
105
|
-
* Нормализует входное значение в формат PLAN-NNN.
|
|
106
|
-
* Принимает: "PLAN-007", "7", "007", "plan-7", "plans/PLAN-007.md", "/abs/path/PLAN-007.md"
|
|
107
|
-
*/
|
|
108
|
-
function normalizePlanId(raw) {
|
|
109
|
-
if (!raw) return null;
|
|
110
|
-
|
|
111
|
-
// Извлекаем имя файла если передан путь
|
|
112
|
-
const basename = path.basename(raw, '.md');
|
|
113
|
-
|
|
114
|
-
// Уже в формате PLAN-NNN (регистронезависимо)
|
|
115
|
-
const full = basename.match(/^plan-(\d+)$/i);
|
|
116
|
-
if (full) return `PLAN-${String(parseInt(full[1], 10)).padStart(3, '0')}`;
|
|
117
|
-
|
|
118
|
-
// Просто цифра или число: "7", "007"
|
|
119
|
-
const num = raw.trim().match(/^(\d+)$/);
|
|
120
|
-
if (num) return `PLAN-${String(parseInt(num[1], 10)).padStart(3, '0')}`;
|
|
121
|
-
|
|
122
|
-
return null;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Извлекает plan_id из аргументов командной строки (контекст пайплайна)
|
|
127
|
-
*/
|
|
128
|
-
function extractPlanId() {
|
|
129
|
-
const prompt = process.argv.slice(2)[0] || '';
|
|
130
|
-
const match = prompt.match(/plan_id:\s*(\S+)/i);
|
|
131
|
-
return match ? normalizePlanId(match[1]) : null;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
105
|
/**
|
|
135
106
|
* Проверяет все тикеты в backlog/ и возвращает список готовых
|
|
136
107
|
*/
|
|
137
108
|
function checkBacklog(planId) {
|
|
138
109
|
const allTickets = readTickets(BACKLOG_DIR);
|
|
139
110
|
const tickets = planId
|
|
140
|
-
? allTickets.filter(t => t.frontmatter.parent_plan === planId)
|
|
111
|
+
? allTickets.filter(t => normalizePlanId(t.frontmatter.parent_plan) === planId)
|
|
141
112
|
: allTickets;
|
|
142
113
|
|
|
143
114
|
const ready = [];
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
import fs from 'fs';
|
|
22
22
|
import path from 'path';
|
|
23
23
|
import { findProjectRoot } from '../lib/find-root.mjs';
|
|
24
|
-
import { parseFrontmatter, printResult } from '../lib/utils.mjs';
|
|
24
|
+
import { parseFrontmatter, printResult, normalizePlanId, extractPlanId } from '../lib/utils.mjs';
|
|
25
25
|
|
|
26
26
|
// Корень проекта
|
|
27
27
|
const PROJECT_DIR = findProjectRoot();
|
|
@@ -50,6 +50,7 @@ function checkCondition(condition) {
|
|
|
50
50
|
|
|
51
51
|
case 'tasks_completed':
|
|
52
52
|
// Проверяет, что указанные задачи выполнены (находятся в done/)
|
|
53
|
+
if (!value || (Array.isArray(value) && value.length === 0)) return true;
|
|
53
54
|
const ids = Array.isArray(value) ? value : [value];
|
|
54
55
|
return ids.every(taskId => {
|
|
55
56
|
const donePath = path.join(DONE_DIR, `${taskId}.md`);
|
|
@@ -228,17 +229,22 @@ function findCompletedInProgress() {
|
|
|
228
229
|
/**
|
|
229
230
|
* Выбирает следующий тикет для выполнения
|
|
230
231
|
*/
|
|
231
|
-
function
|
|
232
|
-
|
|
232
|
+
function filterByPlan(tickets, planId) {
|
|
233
|
+
if (!planId) return tickets;
|
|
234
|
+
return tickets.filter(t => normalizePlanId(t.frontmatter.parent_plan) === planId);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function pickNextTicket(planId) {
|
|
238
|
+
const tickets = filterByPlan(readReadyTickets(), planId);
|
|
233
239
|
|
|
234
240
|
if (tickets.length === 0) {
|
|
235
241
|
// Если ready/ пуст, проверяем review/ — нужно завершить ревью
|
|
236
|
-
let reviewTickets = readReviewTickets();
|
|
242
|
+
let reviewTickets = filterByPlan(readReviewTickets(), planId);
|
|
237
243
|
|
|
238
244
|
if (reviewTickets.length === 0) {
|
|
239
245
|
// Нет тикетов ни в ready/, ни в review/ — проверяем in-progress/
|
|
240
246
|
// на завершённые тикеты (с заполненным Summary)
|
|
241
|
-
const completedInProgress = findCompletedInProgress();
|
|
247
|
+
const completedInProgress = filterByPlan(findCompletedInProgress(), planId);
|
|
242
248
|
if (completedInProgress.length > 0) {
|
|
243
249
|
const first = completedInProgress[0];
|
|
244
250
|
console.log(`[INFO] Found completed ticket in in-progress/: ${first.id}`);
|
|
@@ -317,9 +323,15 @@ function pickNextTicket() {
|
|
|
317
323
|
|
|
318
324
|
// Main entry point
|
|
319
325
|
async function main() {
|
|
326
|
+
const planId = extractPlanId();
|
|
327
|
+
|
|
328
|
+
if (planId) {
|
|
329
|
+
console.log(`[INFO] Filtering by plan_id: ${planId}`);
|
|
330
|
+
}
|
|
331
|
+
|
|
320
332
|
console.log(`[INFO] Scanning ready/ directory: ${READY_DIR}`);
|
|
321
333
|
|
|
322
|
-
const result = pickNextTicket();
|
|
334
|
+
const result = pickNextTicket(planId);
|
|
323
335
|
|
|
324
336
|
if (result.status === 'found') {
|
|
325
337
|
console.log(`[INFO] Selected ticket: ${result.ticket_id} (${result.title})`);
|