workflow-ai 1.0.4 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "workflow-ai",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
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
@@ -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
- const source = this.args.plan ? 'CLI' : 'auto-detected';
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.warn('No plan_id set and no active plan found in .workflow/plans/current/', 'PipelineRunner');
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');
@@ -102,43 +102,13 @@ function readTickets(dir) {
102
102
  return tickets;
103
103
  }
104
104
 
105
- /**
106
- * Нормализует входное значение в формат PLAN-NNN.
107
- * Принимает: "PLAN-007", "7", "007", "plan-7", "plans/PLAN-007.md", "/abs/path/PLAN-007.md"
108
- */
109
- function normalizePlanId(raw) {
110
- if (!raw) return null;
111
-
112
- // Извлекаем имя файла если передан путь
113
- const basename = path.basename(raw, '.md');
114
-
115
- // Уже в формате PLAN-NNN (регистронезависимо)
116
- const full = basename.match(/^plan-(\d+)$/i);
117
- if (full) return `PLAN-${String(parseInt(full[1], 10)).padStart(3, '0')}`;
118
-
119
- // Просто цифра или число: "7", "007"
120
- const num = raw.trim().match(/^(\d+)$/);
121
- if (num) return `PLAN-${String(parseInt(num[1], 10)).padStart(3, '0')}`;
122
-
123
- return null;
124
- }
125
-
126
- /**
127
- * Извлекает plan_id из аргументов командной строки (контекст пайплайна)
128
- */
129
- function extractPlanId() {
130
- const prompt = process.argv.slice(2)[0] || '';
131
- const match = prompt.match(/plan_id:\s*(\S+)/i);
132
- return match ? normalizePlanId(match[1]) : null;
133
- }
134
-
135
105
  /**
136
106
  * Проверяет все тикеты в backlog/ и возвращает список готовых
137
107
  */
138
108
  function checkBacklog(planId) {
139
109
  const allTickets = readTickets(BACKLOG_DIR);
140
110
  const tickets = planId
141
- ? allTickets.filter(t => t.frontmatter.parent_plan === planId)
111
+ ? allTickets.filter(t => normalizePlanId(t.frontmatter.parent_plan) === planId)
142
112
  : allTickets;
143
113
 
144
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();
@@ -229,17 +229,22 @@ function findCompletedInProgress() {
229
229
  /**
230
230
  * Выбирает следующий тикет для выполнения
231
231
  */
232
- function pickNextTicket() {
233
- const tickets = readReadyTickets();
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);
234
239
 
235
240
  if (tickets.length === 0) {
236
241
  // Если ready/ пуст, проверяем review/ — нужно завершить ревью
237
- let reviewTickets = readReviewTickets();
242
+ let reviewTickets = filterByPlan(readReviewTickets(), planId);
238
243
 
239
244
  if (reviewTickets.length === 0) {
240
245
  // Нет тикетов ни в ready/, ни в review/ — проверяем in-progress/
241
246
  // на завершённые тикеты (с заполненным Summary)
242
- const completedInProgress = findCompletedInProgress();
247
+ const completedInProgress = filterByPlan(findCompletedInProgress(), planId);
243
248
  if (completedInProgress.length > 0) {
244
249
  const first = completedInProgress[0];
245
250
  console.log(`[INFO] Found completed ticket in in-progress/: ${first.id}`);
@@ -318,9 +323,15 @@ function pickNextTicket() {
318
323
 
319
324
  // Main entry point
320
325
  async function main() {
326
+ const planId = extractPlanId();
327
+
328
+ if (planId) {
329
+ console.log(`[INFO] Filtering by plan_id: ${planId}`);
330
+ }
331
+
321
332
  console.log(`[INFO] Scanning ready/ directory: ${READY_DIR}`);
322
333
 
323
- const result = pickNextTicket();
334
+ const result = pickNextTicket(planId);
324
335
 
325
336
  if (result.status === 'found') {
326
337
  console.log(`[INFO] Selected ticket: ${result.ticket_id} (${result.title})`);