workflow-ai 1.0.56 → 1.0.58
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/README.md +20 -9
- package/agent-templates/CLAUDE.md.tpl +8 -0
- package/agent-templates/QWEN.md.tpl +8 -0
- package/agent-templates/kilocodemodes.tpl +13 -0
- package/package.json +1 -1
- package/src/cli.mjs +42 -11
- package/src/global-dir.mjs +9 -4
- package/src/init.mjs +26 -35
- package/src/junction-manager.mjs +46 -11
- package/src/runner.mjs +9 -0
- package/src/scripts/archive-plan-tickets.js +102 -0
- package/src/scripts/check-anomalies.js +161 -0
- package/src/scripts/check-conditions.js +258 -0
- package/src/scripts/check-plan-decomposed.js +179 -0
- package/src/scripts/check-plan-templates.js +295 -0
- package/src/scripts/check-relevance.js +308 -0
- package/src/scripts/get-next-id.js +110 -0
- package/src/scripts/move-ticket.js +260 -0
- package/src/scripts/move-to-ready.js +115 -0
- package/src/scripts/move-to-review.js +151 -0
- package/src/scripts/pick-next-task.js +723 -0
- package/templates/ticket-template.md +3 -0
package/README.md
CHANGED
|
@@ -26,8 +26,10 @@ workflow run
|
|
|
26
26
|
|---------|-------------|
|
|
27
27
|
| `workflow init [path] [--force]` | Initialize `.workflow/` directory with kanban board structure |
|
|
28
28
|
| `workflow run [options]` | Execute the AI pipeline |
|
|
29
|
-
| `workflow update [path]` | Update global dir and recreate junctions
|
|
29
|
+
| `workflow update [path]` | Update global dir and recreate junctions |
|
|
30
30
|
| `workflow eject <skill> [path]` | Eject a skill (copy from global to project) |
|
|
31
|
+
| `workflow eject-scripts [path]` | Eject scripts (copy from global to project) |
|
|
32
|
+
| `workflow eject-configs [path]` | Eject configs (copy from global to project) |
|
|
31
33
|
| `workflow list [path]` | List skills with status (shared/ejected/project-only) |
|
|
32
34
|
| `workflow help` | Show help |
|
|
33
35
|
| `workflow version` | Show version |
|
|
@@ -46,10 +48,7 @@ The `workflow init` command creates the `.workflow/` directory structure:
|
|
|
46
48
|
|
|
47
49
|
```
|
|
48
50
|
.workflow/
|
|
49
|
-
├── config/
|
|
50
|
-
│ ├── config.yaml # Workflow configuration
|
|
51
|
-
│ ├── pipeline.yaml # Pipeline stages and agents
|
|
52
|
-
│ └── ticket-movement-rules.yaml
|
|
51
|
+
├── config/ # → junction to ~/.workflow/configs/ (eject to customize)
|
|
53
52
|
├── plans/
|
|
54
53
|
│ ├── current/ # Current development plans
|
|
55
54
|
│ ├── templates/ # Plan templates with triggers (recurring plans)
|
|
@@ -66,8 +65,8 @@ The `workflow init` command creates the `.workflow/` directory structure:
|
|
|
66
65
|
├── metrics/ # Performance metrics
|
|
67
66
|
├── templates/ # Ticket/plan/report templates
|
|
68
67
|
└── src/
|
|
69
|
-
├── skills/ # Skill instructions (
|
|
70
|
-
└── scripts/ # Automation scripts (
|
|
68
|
+
├── skills/ # Skill instructions (junctions to global, per-skill)
|
|
69
|
+
└── scripts/ # Automation scripts (junction to global)
|
|
71
70
|
```
|
|
72
71
|
|
|
73
72
|
## Pipeline
|
|
@@ -119,10 +118,22 @@ Built-in skills for different task types:
|
|
|
119
118
|
| `execute-task` | Task execution |
|
|
120
119
|
| `review-result` | Result review against DoD |
|
|
121
120
|
|
|
122
|
-
Skills are stored globally in `~/.workflow/skills/` and
|
|
121
|
+
Skills are stored globally in `~/.workflow/skills/` and linked into projects via junctions.
|
|
123
122
|
|
|
124
123
|
Use `workflow eject <skill>` to copy a skill into the project for customization.
|
|
125
124
|
|
|
125
|
+
## Scripts
|
|
126
|
+
|
|
127
|
+
Scripts are stored globally in `~/.workflow/scripts/` and linked as a single junction into `.workflow/src/scripts/`.
|
|
128
|
+
|
|
129
|
+
Use `workflow eject-scripts` to copy scripts into the project for customization.
|
|
130
|
+
|
|
131
|
+
## Configs
|
|
132
|
+
|
|
133
|
+
Configs are stored globally in `~/.workflow/configs/` and linked as a single junction into `.workflow/config/`.
|
|
134
|
+
|
|
135
|
+
Use `workflow eject-configs` to copy configs into the project for customization.
|
|
136
|
+
|
|
126
137
|
## Plan Templates
|
|
127
138
|
|
|
128
139
|
Plan templates allow recurring plans to be created automatically. Templates live in `.workflow/plans/templates/` and contain trigger conditions in their frontmatter.
|
|
@@ -186,7 +197,7 @@ workflow-ai/
|
|
|
186
197
|
│ ├── runner.mjs # Core pipeline orchestrator
|
|
187
198
|
│ ├── init.mjs # Project initialization
|
|
188
199
|
│ ├── global-dir.mjs # Global ~/.workflow/ management
|
|
189
|
-
│ ├── junction-manager.mjs #
|
|
200
|
+
│ ├── junction-manager.mjs # Junction/symlink management
|
|
190
201
|
│ ├── wf-loader.mjs # Config loader
|
|
191
202
|
│ ├── lib/ # Utility libraries
|
|
192
203
|
│ └── tests/ # Test suite
|
|
@@ -21,6 +21,14 @@
|
|
|
21
21
|
| Выбор следующей задачи | `node .workflow/src/scripts/pick-next-task.js` |
|
|
22
22
|
| Перемещение готовых в ready | `node .workflow/src/scripts/move-to-ready.js` |
|
|
23
23
|
|
|
24
|
+
### Кастомизация (eject)
|
|
25
|
+
|
|
26
|
+
| Действие | Команда |
|
|
27
|
+
|----------|---------|
|
|
28
|
+
| Eject скила | `workflow eject <skill-name>` |
|
|
29
|
+
| Eject скриптов | `workflow eject-scripts` |
|
|
30
|
+
| Eject конфигов | `workflow eject-configs` |
|
|
31
|
+
|
|
24
32
|
## Workflow
|
|
25
33
|
|
|
26
34
|
1. **Планирование**: Создай план в `.workflow/plans/current/`
|
|
@@ -21,6 +21,14 @@
|
|
|
21
21
|
| Выбор следующей задачи | `node .workflow/src/scripts/pick-next-task.js` |
|
|
22
22
|
| Перемещение готовых в ready | `node .workflow/src/scripts/move-to-ready.js` |
|
|
23
23
|
|
|
24
|
+
### Кастомизация (eject)
|
|
25
|
+
|
|
26
|
+
| Действие | Команда |
|
|
27
|
+
|----------|---------|
|
|
28
|
+
| Eject скила | `workflow eject <skill-name>` |
|
|
29
|
+
| Eject скриптов | `workflow eject-scripts` |
|
|
30
|
+
| Eject конфигов | `workflow eject-configs` |
|
|
31
|
+
|
|
24
32
|
## Workflow
|
|
25
33
|
|
|
26
34
|
1. **Планирование**: Создай план в `.workflow/plans/current/`
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
customModes:
|
|
2
|
+
- slug: manual-testing
|
|
3
|
+
name: manual-testing
|
|
4
|
+
roleDefinition: QA-инженер (тестировщик). Находит дефекты, проверяет качество реализации через браузер и desktop-инструменты. Составляет тест-планы и тест-кейсы, выполняет smoke/regression/exploratory/acceptance тестирование, фиксирует баги с evidence. НЕ пишет автотесты в коде, НЕ исправляет баги, НЕ нагружает систему.
|
|
5
|
+
whenToUse: Тикеты QA-* или с тегом тестирования. Запросы на smoke/regression/exploratory/acceptance тестирование. Необходимость проверки через реальный UI (браузер/desktop). Составление тест-планов и тест-кейсов. Баг-репорты.
|
|
6
|
+
groups:
|
|
7
|
+
- read
|
|
8
|
+
- edit
|
|
9
|
+
- browser
|
|
10
|
+
- command
|
|
11
|
+
- mcp
|
|
12
|
+
source: project
|
|
13
|
+
customInstructions: Используй скил manual-testing.
|
package/package.json
CHANGED
package/src/cli.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import { readFileSync } from 'node:fs';
|
|
|
6
6
|
import { join, dirname, resolve } from 'node:path';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
8
|
import { getGlobalDir, refreshGlobalDir, ensureGlobalDir } from './global-dir.mjs';
|
|
9
|
-
import { createSkillJunctions,
|
|
9
|
+
import { createSkillJunctions, createScriptJunction, createConfigJunction, ejectSkill, ejectScripts, ejectConfigs, listSkillsWithStatus } from './junction-manager.mjs';
|
|
10
10
|
|
|
11
11
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
12
|
const pkgPath = join(__dirname, '..', 'package.json');
|
|
@@ -14,13 +14,15 @@ const pkgPath = join(__dirname, '..', 'package.json');
|
|
|
14
14
|
const HELP_TEXT = `workflow-ai v1.0.0
|
|
15
15
|
|
|
16
16
|
Usage:
|
|
17
|
-
workflow init [path] [--force]
|
|
18
|
-
workflow run [options]
|
|
19
|
-
workflow update [path]
|
|
20
|
-
workflow eject <skill> [path]
|
|
21
|
-
workflow
|
|
22
|
-
workflow
|
|
23
|
-
workflow
|
|
17
|
+
workflow init [path] [--force] Initialize .workflow/ in target directory
|
|
18
|
+
workflow run [options] Run the AI pipeline
|
|
19
|
+
workflow update [path] Update global dir and recreate junctions
|
|
20
|
+
workflow eject <skill> [path] Eject a skill (copy from global to project)
|
|
21
|
+
workflow eject-scripts [path] Eject scripts (copy from global to project)
|
|
22
|
+
workflow eject-configs [path] Eject configs (copy from global to project)
|
|
23
|
+
workflow list [path] List skills with status (shared/ejected/project-only)
|
|
24
|
+
workflow help Show this help
|
|
25
|
+
workflow version Show version
|
|
24
26
|
|
|
25
27
|
Run options:
|
|
26
28
|
--plan <plan> Plan ID to execute
|
|
@@ -94,7 +96,6 @@ function runUpdate(args) {
|
|
|
94
96
|
const workflowRoot = getWorkflowRoot(projectRoot);
|
|
95
97
|
const packageRoot = getPackageRoot();
|
|
96
98
|
const globalDir = getGlobalDir();
|
|
97
|
-
|
|
98
99
|
refreshGlobalDir(packageRoot);
|
|
99
100
|
console.log('✅ Global dir updated (~/.workflow/)');
|
|
100
101
|
|
|
@@ -103,8 +104,12 @@ function runUpdate(args) {
|
|
|
103
104
|
console.log('✅ Skill junctions recreated');
|
|
104
105
|
|
|
105
106
|
const scriptsDir = join(workflowRoot, 'src', 'scripts');
|
|
106
|
-
|
|
107
|
-
console.log('✅ Script
|
|
107
|
+
createScriptJunction(globalDir, scriptsDir);
|
|
108
|
+
console.log('✅ Script junction recreated');
|
|
109
|
+
|
|
110
|
+
const configDir = join(workflowRoot, 'config');
|
|
111
|
+
createConfigJunction(globalDir, configDir);
|
|
112
|
+
console.log('✅ Config junction recreated');
|
|
108
113
|
}
|
|
109
114
|
|
|
110
115
|
function runEject(args) {
|
|
@@ -122,6 +127,26 @@ function runEject(args) {
|
|
|
122
127
|
console.log(`✅ Skill "${skillName}" ejected (copied to project)`);
|
|
123
128
|
}
|
|
124
129
|
|
|
130
|
+
function runEjectScripts(args) {
|
|
131
|
+
const projectRoot = resolve(args._[0] || process.cwd());
|
|
132
|
+
const workflowRoot = getWorkflowRoot(projectRoot);
|
|
133
|
+
const globalDir = getGlobalDir();
|
|
134
|
+
const scriptsDir = join(workflowRoot, 'src', 'scripts');
|
|
135
|
+
|
|
136
|
+
ejectScripts(globalDir, scriptsDir);
|
|
137
|
+
console.log('✅ Scripts ejected (copied to project)');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function runEjectConfigs(args) {
|
|
141
|
+
const projectRoot = resolve(args._[0] || process.cwd());
|
|
142
|
+
const workflowRoot = getWorkflowRoot(projectRoot);
|
|
143
|
+
const globalDir = getGlobalDir();
|
|
144
|
+
const configDir = join(workflowRoot, 'config');
|
|
145
|
+
|
|
146
|
+
ejectConfigs(globalDir, configDir);
|
|
147
|
+
console.log('✅ Configs ejected (copied to project)');
|
|
148
|
+
}
|
|
149
|
+
|
|
125
150
|
function runList(args) {
|
|
126
151
|
const projectRoot = resolve(args._[0] || process.cwd());
|
|
127
152
|
const workflowRoot = getWorkflowRoot(projectRoot);
|
|
@@ -184,6 +209,12 @@ export function run(argv) {
|
|
|
184
209
|
case 'eject':
|
|
185
210
|
runEject(args);
|
|
186
211
|
break;
|
|
212
|
+
case 'eject-scripts':
|
|
213
|
+
runEjectScripts(args);
|
|
214
|
+
break;
|
|
215
|
+
case 'eject-configs':
|
|
216
|
+
runEjectConfigs(args);
|
|
217
|
+
break;
|
|
187
218
|
case 'list':
|
|
188
219
|
runList(args);
|
|
189
220
|
break;
|
package/src/global-dir.mjs
CHANGED
|
@@ -35,12 +35,14 @@ function copyDirectory(src, dest) {
|
|
|
35
35
|
cpSync(src, dest, { recursive: true });
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
function
|
|
38
|
+
function copySkillsScriptsAndConfigs(packageRoot) {
|
|
39
39
|
const globalDir = getGlobalDir();
|
|
40
40
|
const srcSkills = join(packageRoot, 'src', 'skills');
|
|
41
41
|
const srcScripts = join(packageRoot, 'src', 'scripts');
|
|
42
|
+
const srcConfigs = join(packageRoot, 'configs');
|
|
42
43
|
const destSkills = join(globalDir, 'skills');
|
|
43
44
|
const destScripts = join(globalDir, 'scripts');
|
|
45
|
+
const destConfigs = join(globalDir, 'configs');
|
|
44
46
|
|
|
45
47
|
if (existsSync(srcSkills)) {
|
|
46
48
|
copyDirectory(srcSkills, destSkills);
|
|
@@ -48,6 +50,9 @@ function copySkillsAndScripts(packageRoot) {
|
|
|
48
50
|
if (existsSync(srcScripts)) {
|
|
49
51
|
copyDirectory(srcScripts, destScripts);
|
|
50
52
|
}
|
|
53
|
+
if (existsSync(srcConfigs)) {
|
|
54
|
+
copyDirectory(srcConfigs, destConfigs);
|
|
55
|
+
}
|
|
51
56
|
}
|
|
52
57
|
|
|
53
58
|
export function isGlobalDirStale(packageRoot) {
|
|
@@ -67,7 +72,7 @@ export function ensureGlobalDir(packageRoot) {
|
|
|
67
72
|
const globalDir = getGlobalDir();
|
|
68
73
|
if (!existsSync(globalDir)) {
|
|
69
74
|
mkdirSync(globalDir, { recursive: true });
|
|
70
|
-
|
|
75
|
+
copySkillsScriptsAndConfigs(packageRoot);
|
|
71
76
|
const version = getPackageVersion(packageRoot);
|
|
72
77
|
writeFileSync(join(globalDir, '.version'), version);
|
|
73
78
|
return;
|
|
@@ -75,7 +80,7 @@ export function ensureGlobalDir(packageRoot) {
|
|
|
75
80
|
if (isGlobalDirStale(packageRoot)) {
|
|
76
81
|
const version = getPackageVersion(packageRoot);
|
|
77
82
|
writeFileSync(join(globalDir, '.version'), version);
|
|
78
|
-
|
|
83
|
+
copySkillsScriptsAndConfigs(packageRoot);
|
|
79
84
|
}
|
|
80
85
|
}
|
|
81
86
|
|
|
@@ -86,5 +91,5 @@ export function refreshGlobalDir(packageRoot) {
|
|
|
86
91
|
}
|
|
87
92
|
const version = getPackageVersion(packageRoot);
|
|
88
93
|
writeFileSync(join(globalDir, '.version'), version);
|
|
89
|
-
|
|
94
|
+
copySkillsScriptsAndConfigs(packageRoot);
|
|
90
95
|
}
|
package/src/init.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { join, resolve, dirname, basename } from 'node:path';
|
|
|
3
3
|
import { execSync } from 'node:child_process';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { getGlobalDir, ensureGlobalDir } from './global-dir.mjs';
|
|
6
|
-
import { createSkillJunctions,
|
|
6
|
+
import { createSkillJunctions, createScriptJunction, createConfigJunction } from './junction-manager.mjs';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Возвращает абсолютный путь к корню npm-пакета через import.meta.url.
|
|
@@ -230,6 +230,21 @@ function generateQwenMd(workflowRoot, projectRoot, packageRoot) {
|
|
|
230
230
|
writeFileSync(destPath, content, 'utf-8');
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
+
/**
|
|
234
|
+
* Генерирует .kilocodemodes из шаблона agent-templates/kilocodemodes.tpl.
|
|
235
|
+
*
|
|
236
|
+
* @param {string} projectRoot - Путь к корню проекта
|
|
237
|
+
* @param {string} packageRoot - Путь к корню пакета
|
|
238
|
+
*/
|
|
239
|
+
function generateKilocodemodes(projectRoot, packageRoot) {
|
|
240
|
+
const templatePath = join(packageRoot, 'agent-templates', 'kilocodemodes.tpl');
|
|
241
|
+
const destPath = join(projectRoot, '.kilocodemodes');
|
|
242
|
+
|
|
243
|
+
if (existsSync(templatePath)) {
|
|
244
|
+
copyFileSync(templatePath, destPath);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
233
248
|
/**
|
|
234
249
|
* Обновляет .gitignore, добавляя указанные строки.
|
|
235
250
|
*
|
|
@@ -248,6 +263,7 @@ function updateGitignore(projectRoot) {
|
|
|
248
263
|
'QWEN.md',
|
|
249
264
|
'CLAUDE.md',
|
|
250
265
|
'.kilocode/',
|
|
266
|
+
'.kilocodemodes',
|
|
251
267
|
];
|
|
252
268
|
|
|
253
269
|
let currentContent = '';
|
|
@@ -355,7 +371,6 @@ export function initProject(targetPath = process.cwd(), options = {}) {
|
|
|
355
371
|
'reports',
|
|
356
372
|
'logs',
|
|
357
373
|
'templates',
|
|
358
|
-
'config',
|
|
359
374
|
'src/skills'
|
|
360
375
|
];
|
|
361
376
|
|
|
@@ -371,10 +386,10 @@ export function initProject(targetPath = process.cwd(), options = {}) {
|
|
|
371
386
|
createSkillJunctions(globalDir, srcSkillsDest);
|
|
372
387
|
result.steps.push('Created skill junctions from global dir → .workflow/src/skills/');
|
|
373
388
|
|
|
374
|
-
// Step 3: Create script
|
|
389
|
+
// Step 3: Create script junction
|
|
375
390
|
const srcScriptsDest = join(workflowRoot, 'src', 'scripts');
|
|
376
|
-
|
|
377
|
-
result.steps.push('Created script
|
|
391
|
+
createScriptJunction(globalDir, srcScriptsDest);
|
|
392
|
+
result.steps.push('Created script junction from global dir → .workflow/src/scripts/');
|
|
378
393
|
|
|
379
394
|
// Step 5: Copy templates (3 templates)
|
|
380
395
|
const templatesSrc = join(packageRoot, 'templates');
|
|
@@ -391,35 +406,10 @@ export function initProject(targetPath = process.cwd(), options = {}) {
|
|
|
391
406
|
}
|
|
392
407
|
result.steps.push('Copied 3 templates → .workflow/templates/');
|
|
393
408
|
|
|
394
|
-
// Step 6:
|
|
409
|
+
// Step 6: Create config junction
|
|
395
410
|
const configDest = join(workflowRoot, 'config');
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
// config.yaml — only if not exists (fresh only)
|
|
399
|
-
const configYamlDest = join(configDest, 'config.yaml');
|
|
400
|
-
if (!existsSync(configYamlDest) || force) {
|
|
401
|
-
const configYamlSrc = join(packageRoot, 'configs', 'config.yaml');
|
|
402
|
-
if (existsSync(configYamlSrc)) {
|
|
403
|
-
copyFile(configYamlSrc, configYamlDest);
|
|
404
|
-
result.steps.push('Generated config.yaml (fresh)');
|
|
405
|
-
}
|
|
406
|
-
} else {
|
|
407
|
-
result.warnings.push('config.yaml already exists, skipped (use --force to overwrite)');
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
// pipeline.yaml — always
|
|
411
|
-
const pipelineSrc = join(packageRoot, 'configs', 'pipeline.yaml');
|
|
412
|
-
if (existsSync(pipelineSrc)) {
|
|
413
|
-
copyFile(pipelineSrc, join(configDest, 'pipeline.yaml'));
|
|
414
|
-
result.steps.push('Generated pipeline.yaml (overwritten)');
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
// ticket-movement-rules.yaml — always
|
|
418
|
-
const movementRulesSrc = join(packageRoot, 'configs', 'ticket-movement-rules.yaml');
|
|
419
|
-
if (existsSync(movementRulesSrc)) {
|
|
420
|
-
copyFile(movementRulesSrc, join(configDest, 'ticket-movement-rules.yaml'));
|
|
421
|
-
result.steps.push('Generated ticket-movement-rules.yaml (overwritten)');
|
|
422
|
-
}
|
|
411
|
+
createConfigJunction(globalDir, configDest);
|
|
412
|
+
result.steps.push('Created config junction from global dir → .workflow/config/');
|
|
423
413
|
|
|
424
414
|
// Step 7: Create .kilocode symlinks
|
|
425
415
|
const symlinkResult = createKilocodeSymlinks(projectRoot, force);
|
|
@@ -432,10 +422,11 @@ export function initProject(targetPath = process.cwd(), options = {}) {
|
|
|
432
422
|
result.errors.push(symlinkResult.warning || 'Failed to create .kilocode symlinks');
|
|
433
423
|
}
|
|
434
424
|
|
|
435
|
-
// Step 8: Generate CLAUDE.md and
|
|
425
|
+
// Step 8: Generate CLAUDE.md, QWEN.md and .kilocodemodes
|
|
436
426
|
generateClaudeMd(workflowRoot, projectRoot, packageRoot);
|
|
437
427
|
generateQwenMd(workflowRoot, projectRoot, packageRoot);
|
|
438
|
-
|
|
428
|
+
generateKilocodemodes(projectRoot, packageRoot);
|
|
429
|
+
result.steps.push('Generated CLAUDE.md, QWEN.md and .kilocodemodes from agent-templates');
|
|
439
430
|
|
|
440
431
|
// Step 9: Update .gitignore
|
|
441
432
|
updateGitignore(projectRoot);
|
package/src/junction-manager.mjs
CHANGED
|
@@ -109,24 +109,59 @@ export function createSkillJunctions(globalDir, projectSkillsDir) {
|
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
export function
|
|
112
|
+
export function createScriptJunction(globalDir, projectScriptsDir) {
|
|
113
113
|
const globalScriptsDir = join(globalDir, 'scripts');
|
|
114
114
|
if (!existsSync(globalScriptsDir)) {
|
|
115
115
|
return;
|
|
116
116
|
}
|
|
117
|
-
|
|
118
|
-
|
|
117
|
+
|
|
118
|
+
// If local (ejected) scripts dir exists, don't overwrite
|
|
119
|
+
if (existsSync(projectScriptsDir) && !isJunction(projectScriptsDir)) {
|
|
120
|
+
return;
|
|
119
121
|
}
|
|
120
122
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
123
|
+
createJunction(globalScriptsDir, projectScriptsDir);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function createConfigJunction(globalDir, projectConfigDir) {
|
|
127
|
+
const globalConfigDir = join(globalDir, 'configs');
|
|
128
|
+
if (!existsSync(globalConfigDir)) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// If local (ejected) config dir exists, don't overwrite
|
|
133
|
+
if (existsSync(projectConfigDir) && !isJunction(projectConfigDir)) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
createJunction(globalConfigDir, projectConfigDir);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function ejectConfigs(globalDir, projectConfigDir) {
|
|
141
|
+
const globalConfigDir = join(globalDir, 'configs');
|
|
142
|
+
|
|
143
|
+
if (!existsSync(globalConfigDir)) {
|
|
144
|
+
throw new Error('Configs do not exist in global dir');
|
|
129
145
|
}
|
|
146
|
+
|
|
147
|
+
removeJunction(projectConfigDir);
|
|
148
|
+
cpSync(globalConfigDir, projectConfigDir, { recursive: true });
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function ejectScripts(globalDir, projectScriptsDir) {
|
|
152
|
+
const globalScriptsDir = join(globalDir, 'scripts');
|
|
153
|
+
|
|
154
|
+
if (!existsSync(globalScriptsDir)) {
|
|
155
|
+
throw new Error('Scripts do not exist in global dir');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
removeJunction(projectScriptsDir);
|
|
159
|
+
cpSync(globalScriptsDir, projectScriptsDir, { recursive: true });
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** @deprecated Use createScriptJunction instead */
|
|
163
|
+
export function createScriptHardlinks(globalDir, projectScriptsDir) {
|
|
164
|
+
createScriptJunction(globalDir, projectScriptsDir);
|
|
130
165
|
}
|
|
131
166
|
|
|
132
167
|
export function ejectSkill(skillName, globalDir, projectSkillsDir) {
|
package/src/runner.mjs
CHANGED
|
@@ -1030,6 +1030,15 @@ class StageExecutor {
|
|
|
1030
1030
|
}
|
|
1031
1031
|
this.logger.info(`OUTPUT ↑`, stageId);
|
|
1032
1032
|
}
|
|
1033
|
+
|
|
1034
|
+
// Логгируем stderr независимо от exit code
|
|
1035
|
+
if (stderr.trim()) {
|
|
1036
|
+
this.logger.warn(`STDERR ↓`, stageId);
|
|
1037
|
+
for (const line of stderr.trim().split('\n')) {
|
|
1038
|
+
this.logger.warn(` ${line}`, stageId);
|
|
1039
|
+
}
|
|
1040
|
+
this.logger.warn(`STDERR ↑`, stageId);
|
|
1041
|
+
}
|
|
1033
1042
|
}
|
|
1034
1043
|
|
|
1035
1044
|
// Парсим результат из вывода агента через ResultParser
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* archive-plan-tickets.js - Архивирует все done-тикеты указанного плана
|
|
5
|
+
*
|
|
6
|
+
* Использование:
|
|
7
|
+
* node archive-plan-tickets.js <plan_id>
|
|
8
|
+
*
|
|
9
|
+
* Пример:
|
|
10
|
+
* node archive-plan-tickets.js PLAN-002
|
|
11
|
+
* node archive-plan-tickets.js 2
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import fs from 'fs';
|
|
15
|
+
import path from 'path';
|
|
16
|
+
import { findProjectRoot } from 'workflow-ai/lib/find-root.mjs';
|
|
17
|
+
import { parseFrontmatter, serializeFrontmatter, normalizePlanId, extractPlanId, printResult } from 'workflow-ai/lib/utils.mjs';
|
|
18
|
+
|
|
19
|
+
const PROJECT_DIR = findProjectRoot();
|
|
20
|
+
const WORKFLOW_DIR = path.join(PROJECT_DIR, '.workflow');
|
|
21
|
+
const TICKETS_DIR = path.join(WORKFLOW_DIR, 'tickets');
|
|
22
|
+
const DONE_DIR = path.join(TICKETS_DIR, 'done');
|
|
23
|
+
const ARCHIVE_DIR = path.join(TICKETS_DIR, 'archive');
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Архивирует все done-тикеты указанного плана
|
|
27
|
+
*/
|
|
28
|
+
function archivePlanTickets(planId) {
|
|
29
|
+
if (!planId) {
|
|
30
|
+
return { status: 'error', error: 'Missing plan_id' };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!fs.existsSync(DONE_DIR)) {
|
|
34
|
+
return { status: 'ok', plan_id: planId, archived: 0, ticket_ids: '' };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!fs.existsSync(ARCHIVE_DIR)) {
|
|
38
|
+
fs.mkdirSync(ARCHIVE_DIR, { recursive: true });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const files = fs.readdirSync(DONE_DIR).filter(f => f.endsWith('.md') && f !== '.gitkeep.md');
|
|
42
|
+
const archived = [];
|
|
43
|
+
|
|
44
|
+
for (const file of files) {
|
|
45
|
+
const filePath = path.join(DONE_DIR, file);
|
|
46
|
+
try {
|
|
47
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
48
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
49
|
+
|
|
50
|
+
const ticketPlanId = normalizePlanId(frontmatter.parent_plan);
|
|
51
|
+
if (ticketPlanId !== planId) continue;
|
|
52
|
+
|
|
53
|
+
const ticketId = frontmatter.id || file.replace('.md', '');
|
|
54
|
+
|
|
55
|
+
frontmatter.updated_at = new Date().toISOString();
|
|
56
|
+
frontmatter.archived_at = new Date().toISOString();
|
|
57
|
+
|
|
58
|
+
const destPath = path.join(ARCHIVE_DIR, file);
|
|
59
|
+
fs.writeFileSync(destPath, serializeFrontmatter(frontmatter) + body, 'utf8');
|
|
60
|
+
fs.unlinkSync(filePath);
|
|
61
|
+
|
|
62
|
+
archived.push(ticketId);
|
|
63
|
+
console.log(`[ARCHIVE] ${ticketId}: done → archive`);
|
|
64
|
+
} catch (e) {
|
|
65
|
+
console.error(`[ERROR] Failed to archive ${file}: ${e.message}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
status: 'ok',
|
|
71
|
+
plan_id: planId,
|
|
72
|
+
archived: archived.length,
|
|
73
|
+
ticket_ids: archived.join(',')
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Main entry point
|
|
78
|
+
const rawArgs = process.argv.slice(2);
|
|
79
|
+
let planId;
|
|
80
|
+
|
|
81
|
+
if (rawArgs.length >= 1) {
|
|
82
|
+
// Прямой вызов или pipeline context
|
|
83
|
+
const arg = rawArgs[0];
|
|
84
|
+
const planMatch = arg.match(/plan_id:\s*(\S+)/i);
|
|
85
|
+
planId = planMatch ? normalizePlanId(planMatch[1]) : normalizePlanId(arg);
|
|
86
|
+
} else {
|
|
87
|
+
planId = extractPlanId();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (!planId) {
|
|
91
|
+
console.error('Usage: node archive-plan-tickets.js <plan_id>');
|
|
92
|
+
console.error('Example: node archive-plan-tickets.js PLAN-002');
|
|
93
|
+
printResult({ status: 'error', error: 'Missing plan_id argument' });
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const result = archivePlanTickets(planId);
|
|
98
|
+
printResult(result);
|
|
99
|
+
|
|
100
|
+
if (result.status === 'error') {
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|