workflow-ai 1.0.10 → 1.0.12

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.10",
3
+ "version": "1.0.12",
4
4
  "description": "AI Agent Workflow Coordinator — kanban-based pipeline for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.mjs CHANGED
@@ -1,131 +1,131 @@
1
- #!/usr/bin/env node
2
-
3
- import { initProject } from './init.mjs';
4
- import { runPipeline } from './runner.mjs';
5
- import { readFileSync } from 'node:fs';
6
- import { join, dirname } from 'node:path';
7
- import { fileURLToPath } from 'node:url';
8
-
9
- const __dirname = dirname(fileURLToPath(import.meta.url));
10
- const pkgPath = join(__dirname, '..', 'package.json');
11
-
12
- const HELP_TEXT = `workflow-ai v1.0.0
13
-
14
- Usage:
15
- workflow init [path] [--force] Initialize .workflow/ in target directory
16
- workflow run [options] Run the AI pipeline
17
- workflow help Show this help
18
- workflow version Show version
19
-
20
- Run options:
21
- --plan <plan> Plan ID to execute
22
- --config <path> Config file path
23
- --project <path> Project root (default: auto-detect)
24
- `;
25
-
26
- function showHelp() {
27
- console.log(HELP_TEXT);
28
- }
29
-
30
- function showVersion() {
31
- try {
32
- const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
33
- console.log(`workflow-ai v${pkg.version}`);
34
- } catch (err) {
35
- console.error('Error reading package.json:', err.message);
36
- process.exit(1);
37
- }
38
- }
39
-
40
- function parseArgs(argv) {
41
- const args = { _: [] };
42
- let i = 0;
43
- while (i < argv.length) {
44
- const arg = argv[i];
45
- if (arg.startsWith('--')) {
46
- const key = arg.slice(2);
47
- if (i + 1 < argv.length && !argv[i + 1].startsWith('--')) {
48
- args[key] = argv[i + 1];
49
- i += 2;
50
- } else {
51
- args[key] = true;
52
- i += 1;
53
- }
54
- } else {
55
- args._.push(arg);
56
- i += 1;
57
- }
58
- }
59
- return args;
60
- }
61
-
62
- async function runInit(args) {
63
- const targetPath = args._[0] || process.cwd();
64
- const force = args.force === true;
65
- const result = initProject(targetPath, { force });
66
-
67
- if (result.errors && result.errors.length > 0) {
68
- console.error('Errors:', result.errors.join(', '));
69
- process.exit(1);
70
- }
71
-
72
- console.log('✅ Initialization completed:');
73
- result.steps.forEach(step => console.log(` • ${step}`));
74
- if (result.warnings && result.warnings.length > 0) {
75
- console.warn('Warnings:', result.warnings.join(', '));
76
- }
77
- }
78
-
79
- async function runRun(args) {
80
- // Expose wf's node_modules to child ESM scripts via a custom loader
81
- const loaderPath = join(__dirname, 'wf-loader.mjs');
82
- const loaderUrl = `file:///${loaderPath.replace(/\\/g, '/')}`;
83
- process.env.NODE_OPTIONS = process.env.NODE_OPTIONS
84
- ? `${process.env.NODE_OPTIONS} --import ${loaderUrl}`
85
- : `--import ${loaderUrl}`;
86
-
87
- const argv = [];
88
- if (args.plan) {
89
- argv.push('--plan', args.plan);
90
- }
91
- if (args.config) {
92
- argv.push('--config', args.config);
93
- }
94
- if (args.project) {
95
- argv.push('--project', args.project);
96
- }
97
-
98
- await runPipeline(argv);
99
- }
100
-
101
- export function run(argv) {
102
- const args = parseArgs(argv);
103
- const command = args._.shift();
104
-
105
- switch (command) {
106
- case 'init':
107
- runInit(args);
108
- break;
109
- case 'run':
110
- runRun(args);
111
- break;
112
- case 'help':
113
- showHelp();
114
- break;
115
- case 'version':
116
- showVersion();
117
- break;
118
- default:
119
- if (!command) {
120
- showHelp();
121
- } else {
122
- console.error(`Unknown command: ${command}`);
123
- console.error('Run "workflow help" for usage.');
124
- process.exit(1);
125
- }
126
- }
127
- }
128
-
129
- if (import.meta.url === `file://${process.argv[1]}`) {
130
- run(process.argv.slice(2));
1
+ #!/usr/bin/env node
2
+
3
+ import { initProject } from './init.mjs';
4
+ import { runPipeline } from './runner.mjs';
5
+ import { readFileSync } from 'node:fs';
6
+ import { join, dirname } from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+
9
+ const __dirname = dirname(fileURLToPath(import.meta.url));
10
+ const pkgPath = join(__dirname, '..', 'package.json');
11
+
12
+ const HELP_TEXT = `workflow-ai v1.0.0
13
+
14
+ Usage:
15
+ workflow init [path] [--force] Initialize .workflow/ in target directory
16
+ workflow run [options] Run the AI pipeline
17
+ workflow help Show this help
18
+ workflow version Show version
19
+
20
+ Run options:
21
+ --plan <plan> Plan ID to execute
22
+ --config <path> Config file path
23
+ --project <path> Project root (default: auto-detect)
24
+ `;
25
+
26
+ function showHelp() {
27
+ console.log(HELP_TEXT);
28
+ }
29
+
30
+ function showVersion() {
31
+ try {
32
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
33
+ console.log(`workflow-ai v${pkg.version}`);
34
+ } catch (err) {
35
+ console.error('Error reading package.json:', err.message);
36
+ process.exit(1);
37
+ }
38
+ }
39
+
40
+ function parseArgs(argv) {
41
+ const args = { _: [] };
42
+ let i = 0;
43
+ while (i < argv.length) {
44
+ const arg = argv[i];
45
+ if (arg.startsWith('--')) {
46
+ const key = arg.slice(2);
47
+ if (i + 1 < argv.length && !argv[i + 1].startsWith('--')) {
48
+ args[key] = argv[i + 1];
49
+ i += 2;
50
+ } else {
51
+ args[key] = true;
52
+ i += 1;
53
+ }
54
+ } else {
55
+ args._.push(arg);
56
+ i += 1;
57
+ }
58
+ }
59
+ return args;
60
+ }
61
+
62
+ async function runInit(args) {
63
+ const targetPath = args._[0] || process.cwd();
64
+ const force = args.force === true;
65
+ const result = initProject(targetPath, { force });
66
+
67
+ if (result.errors && result.errors.length > 0) {
68
+ console.error('Errors:', result.errors.join(', '));
69
+ process.exit(1);
70
+ }
71
+
72
+ console.log('✅ Initialization completed:');
73
+ result.steps.forEach(step => console.log(` • ${step}`));
74
+ if (result.warnings && result.warnings.length > 0) {
75
+ console.warn('Warnings:', result.warnings.join(', '));
76
+ }
77
+ }
78
+
79
+ async function runRun(args) {
80
+ // Expose wf's node_modules to child ESM scripts via a custom loader
81
+ const loaderPath = join(__dirname, 'wf-loader.mjs');
82
+ const loaderUrl = `file:///${loaderPath.replace(/\\/g, '/')}`;
83
+ process.env.NODE_OPTIONS = process.env.NODE_OPTIONS
84
+ ? `${process.env.NODE_OPTIONS} --import ${loaderUrl}`
85
+ : `--import ${loaderUrl}`;
86
+
87
+ const argv = [];
88
+ if (args.plan) {
89
+ argv.push('--plan', args.plan);
90
+ }
91
+ if (args.config) {
92
+ argv.push('--config', args.config);
93
+ }
94
+ if (args.project) {
95
+ argv.push('--project', args.project);
96
+ }
97
+
98
+ await runPipeline(argv);
99
+ }
100
+
101
+ export function run(argv) {
102
+ const args = parseArgs(argv);
103
+ const command = args._.shift();
104
+
105
+ switch (command) {
106
+ case 'init':
107
+ runInit(args);
108
+ break;
109
+ case 'run':
110
+ runRun(args);
111
+ break;
112
+ case 'help':
113
+ showHelp();
114
+ break;
115
+ case 'version':
116
+ showVersion();
117
+ break;
118
+ default:
119
+ if (!command) {
120
+ showHelp();
121
+ } else {
122
+ console.error(`Unknown command: ${command}`);
123
+ console.error('Run "workflow help" for usage.');
124
+ process.exit(1);
125
+ }
126
+ }
127
+ }
128
+
129
+ if (import.meta.url === `file://${process.argv[1]}`) {
130
+ run(process.argv.slice(2));
131
131
  }
package/src/lib/utils.mjs CHANGED
@@ -96,3 +96,77 @@ export function getPackageRoot() {
96
96
  // result/src/lib → result
97
97
  return path.resolve(__dirname, '../../');
98
98
  }
99
+
100
+ /**
101
+ * Парсит секцию "## Ревью" тикета и возвращает статус последней записи.
102
+ * Поддерживает табличный и текстовый форматы.
103
+ *
104
+ * Табличный формат:
105
+ * | Дата | Статус | Комментарий |
106
+ * |------|--------|-------------|
107
+ * | 2026-03-08 | passed | Всё ок |
108
+ *
109
+ * Текстовый формат:
110
+ * - 2026-03-08: passed - Всё ок
111
+ * - 2026-03-08: failed - Есть замечания
112
+ *
113
+ * @param {string} content - Содержимое тикета (markdown)
114
+ * @returns {string|null} "passed", "failed" или null (если нет ревью)
115
+ */
116
+ export function getLastReviewStatus(content) {
117
+ if (!content) return null;
118
+
119
+ // Находим секцию "## Ревью" — захватываем всё до следующего заголовка ## или конца файла
120
+ const reviewSectionMatch = content.match(/^##\s*Ревью\s*\n([\s\S]*)(?=\n^##\s|$)/m);
121
+ if (!reviewSectionMatch) return null;
122
+
123
+ const reviewSection = reviewSectionMatch[1].trim();
124
+ if (!reviewSection) return null;
125
+
126
+ // Пробуем распарсить табличный формат
127
+ const tableRows = reviewSection.split('\n').filter(line => line.trim().startsWith('|'));
128
+ if (tableRows.length >= 2) {
129
+ // Есть заголовок и разделитель, ищем строки с данными
130
+ const dataRows = tableRows.slice(2).filter(row => {
131
+ const cells = row.split('|').map(c => c.trim()).filter(c => c);
132
+ return cells.length >= 2;
133
+ });
134
+
135
+ if (dataRows.length > 0) {
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);
148
+ const statusRaw = cells[1]?.toLowerCase() || '';
149
+ if (statusRaw.includes('passed')) return 'passed';
150
+ if (statusRaw.includes('failed')) return 'failed';
151
+ }
152
+ }
153
+
154
+ // Пробуем распарсить текстовый формат (список)
155
+ const listItems = reviewSection.split('\n').filter(line => line.trim().match(/^[-*]\s/));
156
+ if (listItems.length > 0) {
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);
168
+ if (statusMatch) return statusMatch[1].toLowerCase();
169
+ }
170
+
171
+ return null;
172
+ }