workflow-ai 1.0.40 → 1.0.42

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.
Files changed (43) hide show
  1. package/agent-templates/CLAUDE.md.tpl +10 -1
  2. package/agent-templates/QWEN.md.tpl +18 -1
  3. package/package.json +3 -1
  4. package/src/cli.mjs +79 -1
  5. package/src/global-dir.mjs +90 -0
  6. package/src/init.mjs +23 -36
  7. package/src/junction-manager.mjs +188 -0
  8. package/src/runner.mjs +26 -7
  9. package/src/scripts/archive-plan-tickets.js +0 -102
  10. package/src/scripts/check-anomalies.js +0 -161
  11. package/src/scripts/check-conditions.js +0 -254
  12. package/src/scripts/check-plan-decomposed.js +0 -179
  13. package/src/scripts/move-ticket.js +0 -228
  14. package/src/scripts/move-to-ready.js +0 -115
  15. package/src/scripts/move-to-review.js +0 -96
  16. package/src/scripts/pick-next-task.js +0 -723
  17. package/src/skills/analyze-report/SKILL.md +0 -110
  18. package/src/skills/check-relevance/SKILL.md +0 -236
  19. package/src/skills/coach/README.md +0 -100
  20. package/src/skills/coach/SKILL.md +0 -109
  21. package/src/skills/coach/algorithms/gap-analysis.md +0 -69
  22. package/src/skills/coach/algorithms/improvement-prioritization.md +0 -62
  23. package/src/skills/coach/algorithms/skill-scoring.md +0 -79
  24. package/src/skills/coach/knowledge/backlog-management.md +0 -71
  25. package/src/skills/coach/knowledge/common-antipatterns.md +0 -56
  26. package/src/skills/coach/knowledge/prompt-engineering.md +0 -86
  27. package/src/skills/coach/knowledge/skill-anatomy.md +0 -71
  28. package/src/skills/coach/templates/audit-report.md +0 -54
  29. package/src/skills/coach/templates/coach-backlog-init.yaml +0 -10
  30. package/src/skills/coach/templates/improvement-plan.md +0 -54
  31. package/src/skills/coach/templates/new-skill.md +0 -137
  32. package/src/skills/coach/workflows/analyze.md +0 -73
  33. package/src/skills/coach/workflows/audit.md +0 -68
  34. package/src/skills/coach/workflows/create.md +0 -66
  35. package/src/skills/coach/workflows/improve.md +0 -70
  36. package/src/skills/coach/workflows/research.md +0 -55
  37. package/src/skills/coach/workflows/review.md +0 -74
  38. package/src/skills/create-plan/SKILL.md +0 -98
  39. package/src/skills/create-report/SKILL.md +0 -156
  40. package/src/skills/decompose-gaps/SKILL.md +0 -161
  41. package/src/skills/decompose-plan/SKILL.md +0 -168
  42. package/src/skills/execute-task/SKILL.md +0 -161
  43. package/src/skills/review-result/SKILL.md +0 -285
@@ -1,228 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * move-ticket.js - Скрипт для перемещения тикетов между директориями канбан-доски
5
- *
6
- * Использование:
7
- * node move-ticket.js <ticket_id> <target>
8
- *
9
- * Пример:
10
- * node move-ticket.js IMPL-001 in-progress
11
- */
12
-
13
- import fs from 'fs';
14
- import path from 'path';
15
- import YAML from '../lib/js-yaml.mjs';
16
- import { findProjectRoot } from '../lib/find-root.mjs';
17
- import { parseFrontmatter, printResult, serializeFrontmatter } from '../lib/utils.mjs';
18
-
19
- // Корень проекта
20
- const PROJECT_DIR = findProjectRoot();
21
- // Базовая директория workflow
22
- const WORKFLOW_DIR = path.join(PROJECT_DIR, '.workflow');
23
- const TICKETS_DIR = path.join(WORKFLOW_DIR, 'tickets');
24
-
25
- // Доступные статусы
26
- const VALID_STATUSES = ['backlog', 'ready', 'in-progress', 'blocked', 'review', 'done', 'archive'];
27
-
28
- // Таблица допустимых переходов
29
- const VALID_TRANSITIONS = {
30
- 'backlog': ['ready', 'blocked', 'done'],
31
- 'ready': ['in-progress', 'review', 'backlog'],
32
- 'in-progress': ['done', 'blocked', 'review'],
33
- 'blocked': ['ready'],
34
- 'review': ['done', 'ready', 'in-progress', 'blocked'],
35
- 'done': ['ready', 'blocked', 'archive'],
36
- 'archive': ['backlog']
37
- };
38
-
39
- /**
40
- * Определяет текущий статус тикета по расположению файла
41
- */
42
- function getStatusFromPath(filePath) {
43
- const fileName = path.basename(filePath);
44
- for (const status of VALID_STATUSES) {
45
- const statusDir = path.join(TICKETS_DIR, status);
46
- const expectedPath = path.join(statusDir, fileName);
47
- if (filePath === expectedPath) {
48
- return status;
49
- }
50
- }
51
- return null;
52
- }
53
-
54
- /**
55
- * Проверяет допустимость перехода
56
- */
57
- function isValidTransition(from, to) {
58
- if (!VALID_STATUSES.includes(from)) {
59
- return { valid: false, error: `Неверный исходный статус: ${from}` };
60
- }
61
- if (!VALID_STATUSES.includes(to)) {
62
- return { valid: false, error: `Неверный целевой статус: ${to}. Доступные: ${VALID_STATUSES.join(', ')}` };
63
- }
64
-
65
- const allowedTransitions = VALID_TRANSITIONS[from] || [];
66
- if (!allowedTransitions.includes(to)) {
67
- return {
68
- valid: false,
69
- error: `Переход из ${from} в ${to} недопустим. Доступные переходы: ${allowedTransitions.join(', ') || 'нет'}`
70
- };
71
- }
72
-
73
- return { valid: true };
74
- }
75
-
76
- /**
77
- * Основная функция перемещения тикета
78
- */
79
- async function moveTicket(ticketId, target) {
80
- // Поиск файла тикета во всех директориях
81
- let sourceDir = null;
82
- let currentStatus = null;
83
-
84
- for (const status of VALID_STATUSES) {
85
- const statusDir = path.join(TICKETS_DIR, status);
86
- const ticketPath = path.join(statusDir, `${ticketId}.md`);
87
- if (fs.existsSync(ticketPath)) {
88
- sourceDir = statusDir;
89
- currentStatus = status;
90
- break;
91
- }
92
- }
93
-
94
- if (!sourceDir) {
95
- return {
96
- status: 'error',
97
- ticket_id: ticketId,
98
- error: `Тикет ${ticketId} не найден ни в одной из директорий`
99
- };
100
- }
101
-
102
- // Проверка допустимости перехода
103
- const transitionCheck = isValidTransition(currentStatus, target);
104
- if (!transitionCheck.valid) {
105
- return {
106
- status: 'error',
107
- ticket_id: ticketId,
108
- from: currentStatus,
109
- to: target,
110
- error: transitionCheck.error
111
- };
112
- }
113
-
114
- const sourcePath = path.join(sourceDir, `${ticketId}.md`);
115
- const targetDir = path.join(TICKETS_DIR, target);
116
- const targetPath = path.join(targetDir, `${ticketId}.md`);
117
-
118
- // Чтение файла тикета
119
- let content;
120
- try {
121
- content = fs.readFileSync(sourcePath, 'utf8');
122
- } catch (e) {
123
- return {
124
- status: 'error',
125
- ticket_id: ticketId,
126
- error: `Не удалось прочитать файл: ${e.message}`
127
- };
128
- }
129
-
130
- // Парсинг frontmatter
131
- let frontmatter, body;
132
- try {
133
- ({ frontmatter, body } = parseFrontmatter(content));
134
- } catch (e) {
135
- return {
136
- status: 'error',
137
- ticket_id: ticketId,
138
- error: e.message
139
- };
140
- }
141
-
142
- // Обновление frontmatter
143
- const now = new Date().toISOString();
144
- frontmatter.updated_at = now;
145
-
146
- // Если переход в done, добавляем completed_at
147
- if (target === 'done' && currentStatus !== 'done') {
148
- frontmatter.completed_at = now;
149
- }
150
-
151
- // Если переход из blocked, удаляем blocked_reason
152
- if (currentStatus === 'blocked' && frontmatter.blocked_reason) {
153
- delete frontmatter.blocked_reason;
154
- }
155
-
156
- // Сериализация нового контента
157
- const newContent = serializeFrontmatter(frontmatter) + body;
158
-
159
- // Создание целевой директории если не существует
160
- if (!fs.existsSync(targetDir)) {
161
- fs.mkdirSync(targetDir, { recursive: true });
162
- }
163
-
164
- // Перемещение файла
165
- try {
166
- fs.renameSync(sourcePath, targetPath);
167
- } catch (e) {
168
- return {
169
- status: 'error',
170
- ticket_id: ticketId,
171
- error: `Не удалось переместить файл: ${e.message}`
172
- };
173
- }
174
-
175
- // Запись обновлённого контента
176
- try {
177
- fs.writeFileSync(targetPath, newContent, 'utf8');
178
- } catch (e) {
179
- return {
180
- status: 'error',
181
- ticket_id: ticketId,
182
- error: `Не удалось записать файл: ${e.message}`
183
- };
184
- }
185
-
186
- return {
187
- status: 'moved',
188
- ticket_id: ticketId,
189
- from: currentStatus,
190
- to: target
191
- };
192
- }
193
-
194
- // Main entry point
195
- const rawArgs = process.argv.slice(2);
196
- let ticketId, target;
197
-
198
- if (rawArgs.length >= 2) {
199
- // Прямой вызов: node move-ticket.js IMPL-001 in-progress
200
- ticketId = rawArgs[0];
201
- target = rawArgs[1];
202
- } else if (rawArgs.length === 1) {
203
- // Вызов через pipeline runner: один аргумент — промпт с контекстом
204
- // Формат: "skill-name\n\nContext:\n ticket_id: X\n target: Y\n..."
205
- const prompt = rawArgs[0];
206
- const ticketMatch = prompt.match(/ticket_id:\s*(\S+)/);
207
- const targetMatch = prompt.match(/target:\s*(\S+)/);
208
- ticketId = ticketMatch?.[1];
209
- target = targetMatch?.[1];
210
- if (!ticketId || !target) {
211
- console.error('[ERROR] Cannot parse ticket_id or target from pipeline context');
212
- printResult({ status: 'error', error: 'Missing ticket_id or target in pipeline context' });
213
- process.exit(1);
214
- }
215
- } else {
216
- console.error('Usage: node move-ticket.js <ticket_id> <target>');
217
- console.error('Example: node move-ticket.js IMPL-001 in-progress');
218
- console.error('Available targets:', VALID_STATUSES.join(', '));
219
- printResult({ status: 'error', error: 'Missing arguments' });
220
- process.exit(1);
221
- }
222
-
223
- moveTicket(ticketId, target).then(result => {
224
- printResult(result);
225
- if (result.status === 'error') {
226
- process.exit(1);
227
- }
228
- });
@@ -1,115 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * move-to-ready.js — Перемещает тикеты из backlog/ в ready/
5
- *
6
- * Читает список ticket IDs из контекста (поле ready_tickets),
7
- * переданного pipeline runner'ом, и перемещает каждый тикет.
8
- *
9
- * Формат ready_tickets: "IMPL-002, DOCS-001" (через запятую)
10
- *
11
- * Выводит результат:
12
- * ---RESULT---
13
- * status: moved | default
14
- * moved: 2
15
- * ---RESULT---
16
- */
17
-
18
- import fs from 'fs';
19
- import path from 'path';
20
- import YAML from '../lib/js-yaml.mjs';
21
- import { findProjectRoot } from '../lib/find-root.mjs';
22
- import { parseFrontmatter, serializeFrontmatter } from '../lib/utils.mjs';
23
-
24
- // Корень проекта
25
- const PROJECT_DIR = findProjectRoot();
26
- const TICKETS_DIR = path.join(PROJECT_DIR, '.workflow', 'tickets');
27
- const BACKLOG_DIR = path.join(TICKETS_DIR, 'backlog');
28
- const READY_DIR = path.join(TICKETS_DIR, 'ready');
29
-
30
- /**
31
- * Парсит список ticket IDs из промпта (контекста pipeline runner)
32
- */
33
- function parseReadyTickets(prompt) {
34
- const match = prompt.match(/ready_tickets:\s*(.+)/);
35
- if (!match || !match[1].trim()) return [];
36
- return match[1].split(',').map(id => id.trim()).filter(Boolean);
37
- }
38
-
39
- /**
40
- * Перемещает один тикет из backlog/ в ready/
41
- */
42
- function moveToReady(ticketId) {
43
- const sourcePath = path.join(BACKLOG_DIR, `${ticketId}.md`);
44
- const targetPath = path.join(READY_DIR, `${ticketId}.md`);
45
-
46
- if (!fs.existsSync(sourcePath)) {
47
- console.error(`[WARN] ${ticketId}: not found in backlog/, skipping`);
48
- return false;
49
- }
50
-
51
- const content = fs.readFileSync(sourcePath, 'utf8');
52
- const { frontmatter, body } = parseFrontmatter(content);
53
-
54
- // Пропускаем тикеты, требующие ручного выполнения
55
- if (frontmatter.type === 'human') {
56
- console.log(`[INFO] ${ticketId}: type is 'human', skipping (requires manual execution)`);
57
- return false;
58
- }
59
-
60
- frontmatter.updated_at = new Date().toISOString();
61
-
62
- const newContent = serializeFrontmatter(frontmatter) + body;
63
-
64
- if (!fs.existsSync(READY_DIR)) {
65
- fs.mkdirSync(READY_DIR, { recursive: true });
66
- }
67
-
68
- fs.renameSync(sourcePath, targetPath);
69
- fs.writeFileSync(targetPath, newContent, 'utf8');
70
- return true;
71
- }
72
-
73
- function printResult(result) {
74
- console.log('---RESULT---');
75
- for (const [key, value] of Object.entries(result)) {
76
- console.log(`${key}: ${value}`);
77
- }
78
- console.log('---RESULT---');
79
- }
80
-
81
- async function main() {
82
- const rawArgs = process.argv.slice(2);
83
- const prompt = rawArgs[0] || '';
84
-
85
- const ticketIds = parseReadyTickets(prompt);
86
-
87
- if (ticketIds.length === 0) {
88
- console.log('[INFO] No tickets to move');
89
- printResult({ status: 'default', moved: 0 });
90
- return;
91
- }
92
-
93
- console.log(`[INFO] Moving ${ticketIds.length} ticket(s) to ready/`);
94
-
95
- let moved = 0;
96
- for (const id of ticketIds) {
97
- try {
98
- if (moveToReady(id)) {
99
- console.log(`[INFO] ${id}: backlog/ → ready/`);
100
- moved++;
101
- }
102
- } catch (e) {
103
- console.error(`[ERROR] ${id}: ${e.message}`);
104
- }
105
- }
106
-
107
- console.log(`[INFO] Moved: ${moved}/${ticketIds.length}`);
108
- printResult({ status: moved > 0 ? 'moved' : 'default', moved });
109
- }
110
-
111
- main().catch(e => {
112
- console.error(`[ERROR] ${e.message}`);
113
- printResult({ status: 'error', error: e.message });
114
- process.exit(1);
115
- });
@@ -1,96 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * move-to-review.js — Перемещает тикет из in-progress/ в review/
5
- *
6
- * Читает ticket_id из контекста pipeline runner'а.
7
- *
8
- * Выводит результат:
9
- * ---RESULT---
10
- * status: moved | error
11
- * ticket_id: IMPL-001
12
- * ---RESULT---
13
- */
14
-
15
- import fs from 'fs';
16
- import path from 'path';
17
- import { findProjectRoot } from '../lib/find-root.mjs';
18
- import { parseFrontmatter, serializeFrontmatter, printResult } from '../lib/utils.mjs';
19
-
20
- // Корень проекта
21
- const PROJECT_DIR = findProjectRoot();
22
- const TICKETS_DIR = path.join(PROJECT_DIR, '.workflow', 'tickets');
23
- const IN_PROGRESS_DIR = path.join(TICKETS_DIR, 'in-progress');
24
- const REVIEW_DIR = path.join(TICKETS_DIR, 'review');
25
-
26
- /**
27
- * Парсит ticket_id из промпта (контекста pipeline runner)
28
- */
29
- function parseTicketId(prompt) {
30
- const match = prompt.match(/ticket_id:\s*(\S+)/);
31
- return match ? match[1].trim() : null;
32
- }
33
-
34
- /**
35
- * Перемещает тикет из in-progress/ в review/
36
- */
37
- function moveToReview(ticketId) {
38
- const sourcePath = path.join(IN_PROGRESS_DIR, `${ticketId}.md`);
39
- const targetPath = path.join(REVIEW_DIR, `${ticketId}.md`);
40
-
41
- if (!fs.existsSync(sourcePath)) {
42
- // Ticket may have been moved directly to done/ by the agent — treat as already done
43
- const donePath = path.join(TICKETS_DIR, 'done', `${ticketId}.md`);
44
- if (fs.existsSync(donePath)) {
45
- return { status: 'skipped', ticket_id: ticketId, reason: `${ticketId} already in done/` };
46
- }
47
- return { status: 'error', ticket_id: ticketId, error: `${ticketId} not found in in-progress/` };
48
- }
49
-
50
- const content = fs.readFileSync(sourcePath, 'utf8');
51
- const { frontmatter, body } = parseFrontmatter(content);
52
-
53
- frontmatter.updated_at = new Date().toISOString();
54
-
55
- const newContent = serializeFrontmatter(frontmatter) + body;
56
-
57
- if (!fs.existsSync(REVIEW_DIR)) {
58
- fs.mkdirSync(REVIEW_DIR, { recursive: true });
59
- }
60
-
61
- fs.renameSync(sourcePath, targetPath);
62
- fs.writeFileSync(targetPath, newContent, 'utf8');
63
-
64
- return { status: 'moved', ticket_id: ticketId, from: 'in-progress', to: 'review' };
65
- }
66
-
67
- async function main() {
68
- const rawArgs = process.argv.slice(2);
69
- const prompt = rawArgs[0] || '';
70
-
71
- const ticketId = parseTicketId(prompt);
72
-
73
- if (!ticketId) {
74
- console.error('[ERROR] No ticket_id in context');
75
- printResult({ status: 'error', error: 'Missing ticket_id' });
76
- process.exit(1);
77
- }
78
-
79
- console.log(`[INFO] Moving ${ticketId}: in-progress/ → review/`);
80
- const result = moveToReview(ticketId);
81
- printResult(result);
82
-
83
- if (result.status === 'error') {
84
- process.exit(1);
85
- }
86
-
87
- if (result.status === 'skipped') {
88
- console.log(`[INFO] Skipped: ${result.reason}`);
89
- }
90
- }
91
-
92
- main().catch(e => {
93
- console.error(`[ERROR] ${e.message}`);
94
- printResult({ status: 'error', error: e.message });
95
- process.exit(1);
96
- });