workflow-ai 1.0.23 → 1.0.24
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
CHANGED
|
@@ -97,6 +97,12 @@ pipeline:
|
|
|
97
97
|
workdir: "."
|
|
98
98
|
description: "Скрипт для проверки условий тикетов в backlog/"
|
|
99
99
|
|
|
100
|
+
script-check-plan-decomposed:
|
|
101
|
+
command: "node"
|
|
102
|
+
args: [".workflow/src/scripts/check-plan-decomposed.js"]
|
|
103
|
+
workdir: "."
|
|
104
|
+
description: "Скрипт для проверки, декомпозирован ли текущий план"
|
|
105
|
+
|
|
100
106
|
script-move-to-review:
|
|
101
107
|
command: "node"
|
|
102
108
|
args: [".workflow/src/scripts/move-to-review.js"]
|
|
@@ -167,9 +173,40 @@ pipeline:
|
|
|
167
173
|
stage: move-to-review
|
|
168
174
|
params:
|
|
169
175
|
ticket_id: "$result.ticket_id"
|
|
170
|
-
empty: check-
|
|
176
|
+
empty: check-plan-decomposition
|
|
171
177
|
error: create-report
|
|
172
178
|
|
|
179
|
+
# -------------------------------------------------------------------------
|
|
180
|
+
# 0b. check-plan-decomposition
|
|
181
|
+
# Если нет тикетов — проверяет, есть ли недекомпозированный план.
|
|
182
|
+
# Если план не декомпозирован — декомпозирует перед check-conditions.
|
|
183
|
+
# -------------------------------------------------------------------------
|
|
184
|
+
check-plan-decomposition:
|
|
185
|
+
description: "Проверить, декомпозирован ли текущий план"
|
|
186
|
+
agent: script-check-plan-decomposed
|
|
187
|
+
goto:
|
|
188
|
+
needs_decomposition:
|
|
189
|
+
stage: decompose-plan
|
|
190
|
+
params:
|
|
191
|
+
plan_file: "$result.plan_file"
|
|
192
|
+
decomposed: check-conditions
|
|
193
|
+
no_plan: check-conditions
|
|
194
|
+
default: check-conditions
|
|
195
|
+
error: check-conditions
|
|
196
|
+
|
|
197
|
+
# -------------------------------------------------------------------------
|
|
198
|
+
# 0c. decompose-plan
|
|
199
|
+
# Декомпозирует план на тикеты в backlog/
|
|
200
|
+
# -------------------------------------------------------------------------
|
|
201
|
+
decompose-plan:
|
|
202
|
+
description: "Декомпозировать план на тикеты"
|
|
203
|
+
agent: claude-sonnet
|
|
204
|
+
fallback_agent: qwen-code
|
|
205
|
+
skill: decompose-plan
|
|
206
|
+
instructions: "Декомпозируй план .workflow/$context.plan_file на тикеты."
|
|
207
|
+
goto:
|
|
208
|
+
default: check-conditions
|
|
209
|
+
|
|
173
210
|
# -------------------------------------------------------------------------
|
|
174
211
|
# 1. check-conditions
|
|
175
212
|
# Проверяет условия тикетов в backlog/, выводит список готовых
|
package/package.json
CHANGED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* check-plan-decomposed.js — Проверяет, есть ли недекомпозированные планы.
|
|
5
|
+
*
|
|
6
|
+
* Логика:
|
|
7
|
+
* A) Если plan_id задан — проверяет только этот план
|
|
8
|
+
* B) Если plan_id НЕ задан — сканирует все планы в plans/current/
|
|
9
|
+
*
|
|
10
|
+
* Для каждого плана:
|
|
11
|
+
* 1. Если есть тикеты (backlog/, ready/, in-progress/, review/) с parent_plan == planId — decomposed
|
|
12
|
+
* 2. Иначе — needs_decomposition
|
|
13
|
+
*
|
|
14
|
+
* Результат:
|
|
15
|
+
* - needs_decomposition + plan_file — найден первый недекомпозированный план
|
|
16
|
+
* - decomposed — все планы декомпозированы
|
|
17
|
+
* - no_plan — нет планов в plans/current/
|
|
18
|
+
*
|
|
19
|
+
* Использование:
|
|
20
|
+
* node check-plan-decomposed.js "plan_id: PLAN-007"
|
|
21
|
+
* node check-plan-decomposed.js
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import fs from 'fs';
|
|
25
|
+
import path from 'path';
|
|
26
|
+
import { findProjectRoot } from '../lib/find-root.mjs';
|
|
27
|
+
import { parseFrontmatter, printResult, normalizePlanId, extractPlanId } from '../lib/utils.mjs';
|
|
28
|
+
|
|
29
|
+
const PROJECT_DIR = findProjectRoot();
|
|
30
|
+
const WORKFLOW_DIR = path.join(PROJECT_DIR, '.workflow');
|
|
31
|
+
const TICKETS_DIR = path.join(WORKFLOW_DIR, 'tickets');
|
|
32
|
+
const PLANS_DIR = path.join(WORKFLOW_DIR, 'plans', 'current');
|
|
33
|
+
|
|
34
|
+
const TICKET_DIRS = ['backlog', 'ready', 'in-progress', 'review'];
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Проверяет, есть ли тикеты, привязанные к данному плану
|
|
38
|
+
*/
|
|
39
|
+
function hasTicketsForPlan(planId) {
|
|
40
|
+
for (const dir of TICKET_DIRS) {
|
|
41
|
+
const dirPath = path.join(TICKETS_DIR, dir);
|
|
42
|
+
if (!fs.existsSync(dirPath)) continue;
|
|
43
|
+
|
|
44
|
+
const files = fs.readdirSync(dirPath)
|
|
45
|
+
.filter(f => f.endsWith('.md') && f !== '.gitkeep.md');
|
|
46
|
+
|
|
47
|
+
for (const file of files) {
|
|
48
|
+
try {
|
|
49
|
+
const content = fs.readFileSync(path.join(dirPath, file), 'utf8');
|
|
50
|
+
const { frontmatter } = parseFrontmatter(content);
|
|
51
|
+
if (normalizePlanId(frontmatter.parent_plan) === planId) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
} catch (e) {
|
|
55
|
+
console.error(`[WARN] Failed to read ${dir}/${file}: ${e.message}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Находит файл плана в plans/current/
|
|
64
|
+
*/
|
|
65
|
+
function findPlanFile(planId) {
|
|
66
|
+
if (!fs.existsSync(PLANS_DIR)) return null;
|
|
67
|
+
|
|
68
|
+
const expectedName = `${planId}.md`;
|
|
69
|
+
const filePath = path.join(PLANS_DIR, expectedName);
|
|
70
|
+
if (fs.existsSync(filePath)) {
|
|
71
|
+
return `plans/current/${expectedName}`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Поиск по всем файлам на случай другого именования
|
|
75
|
+
const files = fs.readdirSync(PLANS_DIR).filter(f => f.endsWith('.md'));
|
|
76
|
+
for (const file of files) {
|
|
77
|
+
if (normalizePlanId(file) === planId) {
|
|
78
|
+
return `plans/current/${file}`;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Возвращает все файлы планов из plans/current/
|
|
87
|
+
*/
|
|
88
|
+
function getAllPlanFiles() {
|
|
89
|
+
if (!fs.existsSync(PLANS_DIR)) return [];
|
|
90
|
+
|
|
91
|
+
return fs.readdirSync(PLANS_DIR)
|
|
92
|
+
.filter(f => f.endsWith('.md'))
|
|
93
|
+
.map(f => ({
|
|
94
|
+
planId: normalizePlanId(f),
|
|
95
|
+
planFile: `plans/current/${f}`
|
|
96
|
+
}))
|
|
97
|
+
.filter(p => p.planId !== null);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function main() {
|
|
101
|
+
const planId = extractPlanId();
|
|
102
|
+
|
|
103
|
+
if (planId) {
|
|
104
|
+
// Режим A: конкретный план
|
|
105
|
+
console.log(`[INFO] Checking decomposition for plan: ${planId}`);
|
|
106
|
+
|
|
107
|
+
const planFile = findPlanFile(planId);
|
|
108
|
+
if (!planFile) {
|
|
109
|
+
console.log(`[INFO] Plan ${planId} not found in plans/current/`);
|
|
110
|
+
printResult({ status: 'no_plan' });
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
console.log(`[INFO] Found plan file: ${planFile}`);
|
|
115
|
+
|
|
116
|
+
if (hasTicketsForPlan(planId)) {
|
|
117
|
+
console.log(`[INFO] Plan ${planId} already has tickets — decomposed`);
|
|
118
|
+
printResult({ status: 'decomposed' });
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
console.log(`[INFO] Plan ${planId} has no tickets — needs decomposition`);
|
|
123
|
+
printResult({ status: 'needs_decomposition', plan_file: planFile });
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Режим B: сканируем все планы в plans/current/
|
|
128
|
+
console.log('[INFO] No plan_id specified, scanning all plans in plans/current/');
|
|
129
|
+
|
|
130
|
+
const allPlans = getAllPlanFiles();
|
|
131
|
+
if (allPlans.length === 0) {
|
|
132
|
+
console.log('[INFO] No plans found in plans/current/');
|
|
133
|
+
printResult({ status: 'no_plan' });
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
console.log(`[INFO] Found ${allPlans.length} plan(s) in plans/current/`);
|
|
138
|
+
|
|
139
|
+
for (const { planId: pid, planFile } of allPlans) {
|
|
140
|
+
if (!hasTicketsForPlan(pid)) {
|
|
141
|
+
console.log(`[INFO] Plan ${pid} has no tickets — needs decomposition`);
|
|
142
|
+
printResult({ status: 'needs_decomposition', plan_file: planFile });
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
console.log(`[INFO] Plan ${pid} already decomposed`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
console.log('[INFO] All plans are decomposed');
|
|
149
|
+
printResult({ status: 'decomposed' });
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
main().catch(e => {
|
|
153
|
+
console.error(`[ERROR] ${e.message}`);
|
|
154
|
+
printResult({ status: 'error', error: e.message });
|
|
155
|
+
process.exit(1);
|
|
156
|
+
});
|