workflow-ai 1.0.60 → 1.0.62

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/src/init.mjs CHANGED
@@ -1,437 +1,437 @@
1
- import { existsSync, mkdirSync, copyFileSync, readFileSync, writeFileSync, appendFileSync, symlinkSync, statSync, readdirSync, unlinkSync } from 'node:fs';
2
- import { join, resolve, dirname, basename } from 'node:path';
3
- import { execSync } from 'node:child_process';
4
- import { fileURLToPath } from 'node:url';
5
- import { getGlobalDir, ensureGlobalDir } from './global-dir.mjs';
6
- import { createSkillJunctions, createScriptJunction, createConfigJunction } from './junction-manager.mjs';
7
-
8
- /**
9
- * Возвращает абсолютный путь к корню npm-пакета через import.meta.url.
10
- *
11
- * @returns {string} Абсолютный путь к корню пакета
12
- */
13
- function getPackageRoot() {
14
- const __filename = fileURLToPath(import.meta.url);
15
- const __dirname = dirname(__filename);
16
- // result/src → result
17
- return resolve(__dirname, '../');
18
- }
19
-
20
- /**
21
- * Создаёт директорию если она не существует.
22
- *
23
- * @param {string} dirPath - Путь к директории
24
- */
25
- function ensureDir(dirPath) {
26
- if (!existsSync(dirPath)) {
27
- mkdirSync(dirPath, { recursive: true });
28
- }
29
- }
30
-
31
- /**
32
- * Копирует файл из источника в назначение.
33
- *
34
- * @param {string} src - Исходный путь
35
- * @param {string} dest - Путь назначения
36
- */
37
- function copyFile(src, dest) {
38
- const destDir = dirname(dest);
39
- ensureDir(destDir);
40
- copyFileSync(src, dest);
41
- }
42
-
43
- /**
44
- * Рекурсивно копирует директорию.
45
- *
46
- * @param {string} srcDir - Исходная директория
47
- * @param {string} destDir - Директория назначения
48
- */
49
- function copyDirRecursive(srcDir, destDir) {
50
- ensureDir(destDir);
51
-
52
- const entries = [];
53
- try {
54
- const dirEntries = readdirSync(srcDir, { withFileTypes: true });
55
- for (const entry of dirEntries) {
56
- entries.push(entry);
57
- }
58
- } catch (e) {
59
- // Directory doesn't exist, skip
60
- return;
61
- }
62
-
63
- for (const entry of entries) {
64
- const srcPath = join(srcDir, entry.name);
65
- const destPath = join(destDir, entry.name);
66
-
67
- if (entry.isDirectory()) {
68
- copyDirRecursive(srcPath, destPath);
69
- } else {
70
- copyFile(srcPath, destPath);
71
- }
72
- }
73
- }
74
-
75
- /**
76
- * Генерирует таблицу skills из директории .workflow/src/skills/.
77
- *
78
- * @param {string} workflowRoot - Путь к корню .workflow
79
- * @returns {string} Markdown-таблица с навыками
80
- */
81
- function generateSkillsTable(workflowRoot) {
82
- const skillsDir = join(workflowRoot, 'src', 'skills');
83
-
84
- if (!existsSync(skillsDir)) {
85
- return '| Задача | Инструкция |\n|--------|------------|\n';
86
- }
87
-
88
- const skillsMap = {
89
- 'create-plan': 'Создание плана',
90
- 'analyze-report': 'Анализ отчёта',
91
- 'decompose-plan': 'Декомпозиция плана',
92
- 'check-conditions': 'Проверка готовности',
93
- 'create-report': 'Создание отчёта',
94
- 'execute-task': 'Выполнение задачи',
95
- 'move-ticket': 'Перемещение тикета',
96
- 'pick-next-task': 'Выбор следующей задачи',
97
- 'decompose-gaps': 'Декомпозиция пробелов',
98
- 'review-result': 'Ревью результата',
99
- 'coach': 'Коуч скилов',
100
- 'deep-research': 'Глубокий ресерч'
101
- };
102
-
103
- let table = '| Задача | Инструкция |\n|--------|------------|\n';
104
-
105
- const skillDirs = readdirSync(skillsDir, { withFileTypes: true })
106
- .filter(entry => entry.isDirectory() || entry.isSymbolicLink())
107
- .map(entry => entry.name);
108
-
109
- for (const skillDir of skillDirs) {
110
- const description = skillsMap[skillDir] || skillDir;
111
- const instruction = `.workflow/src/skills/${skillDir}/SKILL.md`;
112
- table += `| ${description} | \`${instruction}\` |\n`;
113
- }
114
-
115
- return table;
116
- }
117
-
118
- /**
119
- * Генерирует CLAUDE.md из шаблона.
120
- *
121
- * @param {string} workflowRoot - Путь к корню .workflow
122
- * @param {string} projectRoot - Путь к корню проекта
123
- * @param {string} packageRoot - Путь к корню пакета
124
- */
125
- function generateClaudeMd(workflowRoot, projectRoot, packageRoot) {
126
- const templatePath = join(packageRoot, 'agent-templates', 'CLAUDE.md.tpl');
127
- const destPath = join(projectRoot, 'CLAUDE.md');
128
-
129
- let content;
130
- if (existsSync(templatePath)) {
131
- content = readFileSync(templatePath, 'utf-8');
132
- } else {
133
- // Default template
134
- content = `# Инструкции для Claude Code
135
-
136
- Этот проект использует систему координации AI-агентов через файловую канбан-доску.
137
-
138
- ## Структура проекта
139
-
140
- - \`.workflow/\` — канбан-доска с тикетами
141
- - \`.workflow/src/skills/\` — инструкции для выполнения задач
142
-
143
- ## Доступные Skills
144
-
145
- {{SKILLS_TABLE}}
146
-
147
- ## Workflow
148
-
149
- 1. **Планирование**: Создай план в \`.workflow/plans/current/\`
150
- 2. **Декомпозиция**: Разбей план на тикеты в \`.workflow/tickets/backlog/\`
151
- 3. **Выполнение**: Бери задачи из \`ready/\`, выполняй, перемещай в \`done/\`
152
- 4. **Отчётность**: Создавай отчёты в \`.workflow/reports/\`
153
-
154
- ## Шаблоны
155
-
156
- - \`.workflow/templates/ticket-template.md\` — шаблон тикета
157
- - \`.workflow/templates/plan-template.md\` — шаблон плана
158
- - \`.workflow/templates/report-template.md\` — шаблон отчёта
159
-
160
- ## Конфигурация
161
-
162
- Настройки в \`.workflow/config/config.yaml\`
163
-
164
- ## Правила написания кода
165
- При написании кода использовать методологии TDD, SOLID, DRY
166
- `;
167
- }
168
-
169
- const skillsTable = generateSkillsTable(workflowRoot);
170
- content = content.replace('{{SKILLS_TABLE}}', skillsTable);
171
-
172
- writeFileSync(destPath, content, 'utf-8');
173
- }
174
-
175
- /**
176
- * Генерирует QWEN.md из шаблона.
177
- *
178
- * @param {string} workflowRoot - Путь к корню .workflow
179
- * @param {string} projectRoot - Путь к корню проекта
180
- * @param {string} packageRoot - Путь к корню пакета
181
- */
182
- function generateQwenMd(workflowRoot, projectRoot, packageRoot) {
183
- const templatePath = join(packageRoot, 'agent-templates', 'QWEN.md.tpl');
184
- const destPath = join(projectRoot, 'QWEN.md');
185
-
186
- let content;
187
- if (existsSync(templatePath)) {
188
- content = readFileSync(templatePath, 'utf-8');
189
- } else {
190
- // Default template
191
- content = `# Инструкции для qwen Code
192
-
193
- Этот проект использует систему координации AI-агентов через файловую канбан-доску.
194
-
195
- ## Структура проекта
196
-
197
- - \`.workflow/\` — канбан-доска с тикетами
198
- - \`.workflow/src/skills/\` — инструкции для выполнения задач
199
-
200
- ## Доступные Skills
201
-
202
- {{SKILLS_TABLE}}
203
-
204
- ## Workflow
205
-
206
- 1. **Планирование**: Создай план в \`.workflow/plans/current/\`
207
- 2. **Декомпозиция**: Разбей план на тикеты в \`.workflow/tickets/backlog/\`
208
- 3. **Выполнение**: Бери задачи из \`ready/\`, выполняй, перемещай в \`done/\`
209
- 4. **Отчётность**: Создавай отчёты в \`.workflow/reports/\`
210
-
211
- ## Шаблоны
212
-
213
- - \`.workflow/templates/ticket-template.md\` — шаблон тикета
214
- - \`.workflow/templates/plan-template.md\` — шаблон плана
215
- - \`.workflow/templates/report-template.md\` — шаблон отчёта
216
-
217
- ## Конфигурация
218
-
219
- Настройки в \`.workflow/config/config.yaml\`
220
-
221
- ## Правила написания кода
222
- При написании кода использовать методологии TDD, SOLID, DRY
223
- `;
224
- }
225
-
226
- const skillsTable = generateSkillsTable(workflowRoot);
227
- content = content.replace('{{SKILLS_TABLE}}', skillsTable);
228
-
229
- writeFileSync(destPath, content, 'utf-8');
230
- }
231
-
232
- /**
233
- * Генерирует .kilocodemodes из шаблона agent-templates/kilocodemodes.tpl.
234
- *
235
- * @param {string} projectRoot - Путь к корню проекта
236
- * @param {string} packageRoot - Путь к корню пакета
237
- */
238
- function generateKilocodemodes(projectRoot, packageRoot) {
239
- const templatePath = join(packageRoot, 'agent-templates', 'kilocodemodes.tpl');
240
- const destPath = join(projectRoot, '.kilocodemodes');
241
-
242
- if (existsSync(templatePath)) {
243
- copyFileSync(templatePath, destPath);
244
- }
245
- }
246
-
247
- /**
248
- * Обновляет .gitignore, добавляя указанные строки.
249
- *
250
- * @param {string} projectRoot - Путь к корню проекта
251
- */
252
- function updateGitignore(projectRoot) {
253
- const gitignorePath = join(projectRoot, '.gitignore');
254
- const linesToAdd = [
255
- '',
256
- '# Workflow AI specific',
257
- '.workflow-state/',
258
- '.cache/',
259
- '.workflow/',
260
- '',
261
- '# AI',
262
- 'QWEN.md',
263
- 'CLAUDE.md',
264
- '.kilocode/',
265
- '.kilocodemodes',
266
- ];
267
-
268
- let currentContent = '';
269
- if (existsSync(gitignorePath)) {
270
- currentContent = readFileSync(gitignorePath, 'utf-8');
271
- }
272
-
273
- const existingLines = currentContent.split('\n').map(line => line.trim());
274
-
275
- const newLines = linesToAdd.filter(line => line === '' || !existingLines.includes(line));
276
- if (newLines.some(line => line !== '')) {
277
- appendFileSync(gitignorePath, newLines.join('\n') + '\n');
278
- }
279
- }
280
-
281
- /**
282
- * Создаёт симлинки .kilocode.
283
- *
284
- * @param {string} projectRoot - Путь к корню проекта
285
- * @param {boolean} force - Принудительное создание
286
- * @returns {{ success: boolean, warning?: string }} Результат операции
287
- */
288
- function createKilocodeSymlinks(projectRoot, force = false) {
289
- const kilocodeDir = join(projectRoot, '.kilocode');
290
- const skillsTarget = join(projectRoot, '.workflow', 'src', 'skills');
291
- const skillsLink = join(kilocodeDir, 'skills');
292
-
293
- ensureDir(kilocodeDir);
294
-
295
- const isWindows = process.platform === 'win32';
296
-
297
- try {
298
- // Remove existing link if exists
299
- if (existsSync(skillsLink)) {
300
- const stats = statSync(skillsLink);
301
- if (stats.isSymbolicLink() || stats.isDirectory()) {
302
- try {
303
- if (isWindows) {
304
- execSync(`rmdir "${skillsLink}"`);
305
- } else {
306
- unlinkSync(skillsLink);
307
- }
308
- } catch (e) {
309
- // Ignore errors
310
- }
311
- }
312
- }
313
-
314
- if (isWindows) {
315
- // Windows: use Junction Point
316
- try {
317
- execSync(`mklink /J "${skillsLink}" "${skillsTarget}"`);
318
- return { success: true };
319
- } catch (e) {
320
- // Fallback: copy directory
321
- copyDirRecursive(skillsTarget, skillsLink);
322
- return {
323
- success: true,
324
- warning: 'Junction Point creation failed, copied files instead'
325
- };
326
- }
327
- } else {
328
- // Linux/macOS: use symlink
329
- symlinkSync(skillsTarget, skillsLink);
330
- return { success: true };
331
- }
332
- } catch (e) {
333
- return {
334
- success: false,
335
- warning: `Failed to create symlink: ${e.message}`
336
- };
337
- }
338
- }
339
-
340
- /**
341
- * Инициализирует проект, создавая структуру .workflow/ и копируя файлы.
342
- *
343
- * @param {string} targetPath - Путь к целевому проекту (по умолчанию process.cwd())
344
- * @param {object} options - Опции инициализации
345
- * @param {boolean} options.force - Принудительная перезапись файлов
346
- * @returns {object} Результат инициализации
347
- */
348
- export function initProject(targetPath = process.cwd(), options = {}) {
349
- const { force = false } = options;
350
- const projectRoot = resolve(targetPath);
351
- const workflowRoot = join(projectRoot, '.workflow');
352
- const packageRoot = getPackageRoot();
353
-
354
- const result = {
355
- steps: [],
356
- warnings: [],
357
- errors: []
358
- };
359
-
360
- // Step 1: Create .workflow/ structure (15 directories)
361
- const directories = [
362
- 'tickets/backlog',
363
- 'tickets/ready',
364
- 'tickets/in-progress',
365
- 'tickets/blocked',
366
- 'tickets/review',
367
- 'tickets/done',
368
- 'plans/current',
369
- 'plans/archive',
370
- 'reports',
371
- 'logs',
372
- 'templates',
373
- 'src/skills'
374
- ];
375
-
376
- for (const dir of directories) {
377
- ensureDir(join(workflowRoot, dir));
378
- }
379
- result.steps.push('Created .workflow/ directory structure (15 directories)');
380
-
381
- // Step 2: Ensure global dir and create skill junctions
382
- const globalDir = getGlobalDir();
383
- ensureGlobalDir(packageRoot);
384
- const srcSkillsDest = join(workflowRoot, 'src', 'skills');
385
- createSkillJunctions(globalDir, srcSkillsDest);
386
- result.steps.push('Created skill junctions from global dir → .workflow/src/skills/');
387
-
388
- // Step 3: Create script junction
389
- const srcScriptsDest = join(workflowRoot, 'src', 'scripts');
390
- createScriptJunction(globalDir, srcScriptsDest);
391
- result.steps.push('Created script junction from global dir → .workflow/src/scripts/');
392
-
393
- // Step 5: Copy templates (3 templates)
394
- const templatesSrc = join(packageRoot, 'templates');
395
- const templatesDest = join(workflowRoot, 'templates');
396
- ensureDir(templatesDest);
397
-
398
- const templateFiles = ['ticket-template.md', 'plan-template.md', 'report-template.md'];
399
- for (const template of templateFiles) {
400
- const srcPath = join(templatesSrc, template);
401
- const destPath = join(templatesDest, template);
402
- if (existsSync(srcPath)) {
403
- copyFile(srcPath, destPath);
404
- }
405
- }
406
- result.steps.push('Copied 3 templates → .workflow/templates/');
407
-
408
- // Step 6: Create config junction
409
- const configDest = join(workflowRoot, 'config');
410
- createConfigJunction(globalDir, configDest);
411
- result.steps.push('Created config junction from global dir → .workflow/config/');
412
-
413
- // Step 7: Create .kilocode symlinks
414
- const symlinkResult = createKilocodeSymlinks(projectRoot, force);
415
- if (symlinkResult.success) {
416
- result.steps.push('Created .kilocode symlinks (Junction Point on Windows)');
417
- if (symlinkResult.warning) {
418
- result.warnings.push(symlinkResult.warning);
419
- }
420
- } else {
421
- result.errors.push(symlinkResult.warning || 'Failed to create .kilocode symlinks');
422
- }
423
-
424
- // Step 8: Generate CLAUDE.md, QWEN.md and .kilocodemodes
425
- generateClaudeMd(workflowRoot, projectRoot, packageRoot);
426
- generateQwenMd(workflowRoot, projectRoot, packageRoot);
427
- generateKilocodemodes(projectRoot, packageRoot);
428
- result.steps.push('Generated CLAUDE.md, QWEN.md and .kilocodemodes from agent-templates');
429
-
430
- // Step 9: Update .gitignore
431
- updateGitignore(projectRoot);
432
- result.steps.push('Updated .gitignore with .workflow/logs/');
433
-
434
- return result;
435
- }
436
-
437
- export default initProject;
1
+ import { existsSync, mkdirSync, copyFileSync, readFileSync, writeFileSync, appendFileSync, symlinkSync, statSync, readdirSync, unlinkSync } from 'node:fs';
2
+ import { join, resolve, dirname, basename } from 'node:path';
3
+ import { execSync } from 'node:child_process';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { getGlobalDir, ensureGlobalDir } from './global-dir.mjs';
6
+ import { createSkillJunctions, createScriptJunction, createConfigJunction } from './junction-manager.mjs';
7
+
8
+ /**
9
+ * Возвращает абсолютный путь к корню npm-пакета через import.meta.url.
10
+ *
11
+ * @returns {string} Абсолютный путь к корню пакета
12
+ */
13
+ function getPackageRoot() {
14
+ const __filename = fileURLToPath(import.meta.url);
15
+ const __dirname = dirname(__filename);
16
+ // result/src → result
17
+ return resolve(__dirname, '../');
18
+ }
19
+
20
+ /**
21
+ * Создаёт директорию если она не существует.
22
+ *
23
+ * @param {string} dirPath - Путь к директории
24
+ */
25
+ function ensureDir(dirPath) {
26
+ if (!existsSync(dirPath)) {
27
+ mkdirSync(dirPath, { recursive: true });
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Копирует файл из источника в назначение.
33
+ *
34
+ * @param {string} src - Исходный путь
35
+ * @param {string} dest - Путь назначения
36
+ */
37
+ function copyFile(src, dest) {
38
+ const destDir = dirname(dest);
39
+ ensureDir(destDir);
40
+ copyFileSync(src, dest);
41
+ }
42
+
43
+ /**
44
+ * Рекурсивно копирует директорию.
45
+ *
46
+ * @param {string} srcDir - Исходная директория
47
+ * @param {string} destDir - Директория назначения
48
+ */
49
+ function copyDirRecursive(srcDir, destDir) {
50
+ ensureDir(destDir);
51
+
52
+ const entries = [];
53
+ try {
54
+ const dirEntries = readdirSync(srcDir, { withFileTypes: true });
55
+ for (const entry of dirEntries) {
56
+ entries.push(entry);
57
+ }
58
+ } catch (e) {
59
+ // Directory doesn't exist, skip
60
+ return;
61
+ }
62
+
63
+ for (const entry of entries) {
64
+ const srcPath = join(srcDir, entry.name);
65
+ const destPath = join(destDir, entry.name);
66
+
67
+ if (entry.isDirectory()) {
68
+ copyDirRecursive(srcPath, destPath);
69
+ } else {
70
+ copyFile(srcPath, destPath);
71
+ }
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Генерирует таблицу skills из директории .workflow/src/skills/.
77
+ *
78
+ * @param {string} workflowRoot - Путь к корню .workflow
79
+ * @returns {string} Markdown-таблица с навыками
80
+ */
81
+ function generateSkillsTable(workflowRoot) {
82
+ const skillsDir = join(workflowRoot, 'src', 'skills');
83
+
84
+ if (!existsSync(skillsDir)) {
85
+ return '| Задача | Инструкция |\n|--------|------------|\n';
86
+ }
87
+
88
+ const skillsMap = {
89
+ 'create-plan': 'Создание плана',
90
+ 'analyze-report': 'Анализ отчёта',
91
+ 'decompose-plan': 'Декомпозиция плана',
92
+ 'check-conditions': 'Проверка готовности',
93
+ 'create-report': 'Создание отчёта',
94
+ 'execute-task': 'Выполнение задачи',
95
+ 'move-ticket': 'Перемещение тикета',
96
+ 'pick-next-task': 'Выбор следующей задачи',
97
+ 'decompose-gaps': 'Декомпозиция пробелов',
98
+ 'review-result': 'Ревью результата',
99
+ 'coach': 'Коуч скилов',
100
+ 'deep-research': 'Глубокий ресерч'
101
+ };
102
+
103
+ let table = '| Задача | Инструкция |\n|--------|------------|\n';
104
+
105
+ const skillDirs = readdirSync(skillsDir, { withFileTypes: true })
106
+ .filter(entry => entry.isDirectory() || entry.isSymbolicLink())
107
+ .map(entry => entry.name);
108
+
109
+ for (const skillDir of skillDirs) {
110
+ const description = skillsMap[skillDir] || skillDir;
111
+ const instruction = `.workflow/src/skills/${skillDir}/SKILL.md`;
112
+ table += `| ${description} | \`${instruction}\` |\n`;
113
+ }
114
+
115
+ return table;
116
+ }
117
+
118
+ /**
119
+ * Генерирует CLAUDE.md из шаблона.
120
+ *
121
+ * @param {string} workflowRoot - Путь к корню .workflow
122
+ * @param {string} projectRoot - Путь к корню проекта
123
+ * @param {string} packageRoot - Путь к корню пакета
124
+ */
125
+ function generateClaudeMd(workflowRoot, projectRoot, packageRoot) {
126
+ const templatePath = join(packageRoot, 'agent-templates', 'CLAUDE.md.tpl');
127
+ const destPath = join(projectRoot, 'CLAUDE.md');
128
+
129
+ let content;
130
+ if (existsSync(templatePath)) {
131
+ content = readFileSync(templatePath, 'utf-8');
132
+ } else {
133
+ // Default template
134
+ content = `# Инструкции для Claude Code
135
+
136
+ Этот проект использует систему координации AI-агентов через файловую канбан-доску.
137
+
138
+ ## Структура проекта
139
+
140
+ - \`.workflow/\` — канбан-доска с тикетами
141
+ - \`.workflow/src/skills/\` — инструкции для выполнения задач
142
+
143
+ ## Доступные Skills
144
+
145
+ {{SKILLS_TABLE}}
146
+
147
+ ## Workflow
148
+
149
+ 1. **Планирование**: Создай план в \`.workflow/plans/current/\`
150
+ 2. **Декомпозиция**: Разбей план на тикеты в \`.workflow/tickets/backlog/\`
151
+ 3. **Выполнение**: Бери задачи из \`ready/\`, выполняй, перемещай в \`done/\`
152
+ 4. **Отчётность**: Создавай отчёты в \`.workflow/reports/\`
153
+
154
+ ## Шаблоны
155
+
156
+ - \`.workflow/templates/ticket-template.md\` — шаблон тикета
157
+ - \`.workflow/templates/plan-template.md\` — шаблон плана
158
+ - \`.workflow/templates/report-template.md\` — шаблон отчёта
159
+
160
+ ## Конфигурация
161
+
162
+ Настройки в \`.workflow/config/config.yaml\`
163
+
164
+ ## Правила написания кода
165
+ При написании кода использовать методологии TDD, SOLID, DRY
166
+ `;
167
+ }
168
+
169
+ const skillsTable = generateSkillsTable(workflowRoot);
170
+ content = content.replace('{{SKILLS_TABLE}}', skillsTable);
171
+
172
+ writeFileSync(destPath, content, 'utf-8');
173
+ }
174
+
175
+ /**
176
+ * Генерирует QWEN.md из шаблона.
177
+ *
178
+ * @param {string} workflowRoot - Путь к корню .workflow
179
+ * @param {string} projectRoot - Путь к корню проекта
180
+ * @param {string} packageRoot - Путь к корню пакета
181
+ */
182
+ function generateQwenMd(workflowRoot, projectRoot, packageRoot) {
183
+ const templatePath = join(packageRoot, 'agent-templates', 'QWEN.md.tpl');
184
+ const destPath = join(projectRoot, 'QWEN.md');
185
+
186
+ let content;
187
+ if (existsSync(templatePath)) {
188
+ content = readFileSync(templatePath, 'utf-8');
189
+ } else {
190
+ // Default template
191
+ content = `# Инструкции для qwen Code
192
+
193
+ Этот проект использует систему координации AI-агентов через файловую канбан-доску.
194
+
195
+ ## Структура проекта
196
+
197
+ - \`.workflow/\` — канбан-доска с тикетами
198
+ - \`.workflow/src/skills/\` — инструкции для выполнения задач
199
+
200
+ ## Доступные Skills
201
+
202
+ {{SKILLS_TABLE}}
203
+
204
+ ## Workflow
205
+
206
+ 1. **Планирование**: Создай план в \`.workflow/plans/current/\`
207
+ 2. **Декомпозиция**: Разбей план на тикеты в \`.workflow/tickets/backlog/\`
208
+ 3. **Выполнение**: Бери задачи из \`ready/\`, выполняй, перемещай в \`done/\`
209
+ 4. **Отчётность**: Создавай отчёты в \`.workflow/reports/\`
210
+
211
+ ## Шаблоны
212
+
213
+ - \`.workflow/templates/ticket-template.md\` — шаблон тикета
214
+ - \`.workflow/templates/plan-template.md\` — шаблон плана
215
+ - \`.workflow/templates/report-template.md\` — шаблон отчёта
216
+
217
+ ## Конфигурация
218
+
219
+ Настройки в \`.workflow/config/config.yaml\`
220
+
221
+ ## Правила написания кода
222
+ При написании кода использовать методологии TDD, SOLID, DRY
223
+ `;
224
+ }
225
+
226
+ const skillsTable = generateSkillsTable(workflowRoot);
227
+ content = content.replace('{{SKILLS_TABLE}}', skillsTable);
228
+
229
+ writeFileSync(destPath, content, 'utf-8');
230
+ }
231
+
232
+ /**
233
+ * Генерирует .kilocodemodes из шаблона agent-templates/kilocodemodes.tpl.
234
+ *
235
+ * @param {string} projectRoot - Путь к корню проекта
236
+ * @param {string} packageRoot - Путь к корню пакета
237
+ */
238
+ function generateKilocodemodes(projectRoot, packageRoot) {
239
+ const templatePath = join(packageRoot, 'agent-templates', 'kilocodemodes.tpl');
240
+ const destPath = join(projectRoot, '.kilocodemodes');
241
+
242
+ if (existsSync(templatePath)) {
243
+ copyFileSync(templatePath, destPath);
244
+ }
245
+ }
246
+
247
+ /**
248
+ * Обновляет .gitignore, добавляя указанные строки.
249
+ *
250
+ * @param {string} projectRoot - Путь к корню проекта
251
+ */
252
+ function updateGitignore(projectRoot) {
253
+ const gitignorePath = join(projectRoot, '.gitignore');
254
+ const linesToAdd = [
255
+ '',
256
+ '# Workflow AI specific',
257
+ '.workflow-state/',
258
+ '.cache/',
259
+ '.workflow/',
260
+ '',
261
+ '# AI',
262
+ 'QWEN.md',
263
+ 'CLAUDE.md',
264
+ '.kilocode/',
265
+ '.kilocodemodes',
266
+ ];
267
+
268
+ let currentContent = '';
269
+ if (existsSync(gitignorePath)) {
270
+ currentContent = readFileSync(gitignorePath, 'utf-8');
271
+ }
272
+
273
+ const existingLines = currentContent.split('\n').map(line => line.trim());
274
+
275
+ const newLines = linesToAdd.filter(line => line === '' || !existingLines.includes(line));
276
+ if (newLines.some(line => line !== '')) {
277
+ appendFileSync(gitignorePath, newLines.join('\n') + '\n');
278
+ }
279
+ }
280
+
281
+ /**
282
+ * Создаёт симлинки .kilocode.
283
+ *
284
+ * @param {string} projectRoot - Путь к корню проекта
285
+ * @param {boolean} force - Принудительное создание
286
+ * @returns {{ success: boolean, warning?: string }} Результат операции
287
+ */
288
+ function createKilocodeSymlinks(projectRoot, force = false) {
289
+ const kilocodeDir = join(projectRoot, '.kilocode');
290
+ const skillsTarget = join(projectRoot, '.workflow', 'src', 'skills');
291
+ const skillsLink = join(kilocodeDir, 'skills');
292
+
293
+ ensureDir(kilocodeDir);
294
+
295
+ const isWindows = process.platform === 'win32';
296
+
297
+ try {
298
+ // Remove existing link if exists
299
+ if (existsSync(skillsLink)) {
300
+ const stats = statSync(skillsLink);
301
+ if (stats.isSymbolicLink() || stats.isDirectory()) {
302
+ try {
303
+ if (isWindows) {
304
+ execSync(`rmdir "${skillsLink}"`);
305
+ } else {
306
+ unlinkSync(skillsLink);
307
+ }
308
+ } catch (e) {
309
+ // Ignore errors
310
+ }
311
+ }
312
+ }
313
+
314
+ if (isWindows) {
315
+ // Windows: use Junction Point
316
+ try {
317
+ execSync(`mklink /J "${skillsLink}" "${skillsTarget}"`);
318
+ return { success: true };
319
+ } catch (e) {
320
+ // Fallback: copy directory
321
+ copyDirRecursive(skillsTarget, skillsLink);
322
+ return {
323
+ success: true,
324
+ warning: 'Junction Point creation failed, copied files instead'
325
+ };
326
+ }
327
+ } else {
328
+ // Linux/macOS: use symlink
329
+ symlinkSync(skillsTarget, skillsLink);
330
+ return { success: true };
331
+ }
332
+ } catch (e) {
333
+ return {
334
+ success: false,
335
+ warning: `Failed to create symlink: ${e.message}`
336
+ };
337
+ }
338
+ }
339
+
340
+ /**
341
+ * Инициализирует проект, создавая структуру .workflow/ и копируя файлы.
342
+ *
343
+ * @param {string} targetPath - Путь к целевому проекту (по умолчанию process.cwd())
344
+ * @param {object} options - Опции инициализации
345
+ * @param {boolean} options.force - Принудительная перезапись файлов
346
+ * @returns {object} Результат инициализации
347
+ */
348
+ export function initProject(targetPath = process.cwd(), options = {}) {
349
+ const { force = false } = options;
350
+ const projectRoot = resolve(targetPath);
351
+ const workflowRoot = join(projectRoot, '.workflow');
352
+ const packageRoot = getPackageRoot();
353
+
354
+ const result = {
355
+ steps: [],
356
+ warnings: [],
357
+ errors: []
358
+ };
359
+
360
+ // Step 1: Create .workflow/ structure (15 directories)
361
+ const directories = [
362
+ 'tickets/backlog',
363
+ 'tickets/ready',
364
+ 'tickets/in-progress',
365
+ 'tickets/blocked',
366
+ 'tickets/review',
367
+ 'tickets/done',
368
+ 'plans/current',
369
+ 'plans/archive',
370
+ 'reports',
371
+ 'logs',
372
+ 'templates',
373
+ 'src/skills'
374
+ ];
375
+
376
+ for (const dir of directories) {
377
+ ensureDir(join(workflowRoot, dir));
378
+ }
379
+ result.steps.push('Created .workflow/ directory structure (15 directories)');
380
+
381
+ // Step 2: Ensure global dir and create skill junctions
382
+ const globalDir = getGlobalDir();
383
+ ensureGlobalDir(packageRoot);
384
+ const srcSkillsDest = join(workflowRoot, 'src', 'skills');
385
+ createSkillJunctions(globalDir, srcSkillsDest);
386
+ result.steps.push('Created skill junctions from global dir → .workflow/src/skills/');
387
+
388
+ // Step 3: Create script junction
389
+ const srcScriptsDest = join(workflowRoot, 'src', 'scripts');
390
+ createScriptJunction(globalDir, srcScriptsDest);
391
+ result.steps.push('Created script junction from global dir → .workflow/src/scripts/');
392
+
393
+ // Step 5: Copy templates (3 templates)
394
+ const templatesSrc = join(packageRoot, 'templates');
395
+ const templatesDest = join(workflowRoot, 'templates');
396
+ ensureDir(templatesDest);
397
+
398
+ const templateFiles = ['ticket-template.md', 'plan-template.md', 'report-template.md'];
399
+ for (const template of templateFiles) {
400
+ const srcPath = join(templatesSrc, template);
401
+ const destPath = join(templatesDest, template);
402
+ if (existsSync(srcPath)) {
403
+ copyFile(srcPath, destPath);
404
+ }
405
+ }
406
+ result.steps.push('Copied 3 templates → .workflow/templates/');
407
+
408
+ // Step 6: Create config junction
409
+ const configDest = join(workflowRoot, 'config');
410
+ createConfigJunction(globalDir, configDest);
411
+ result.steps.push('Created config junction from global dir → .workflow/config/');
412
+
413
+ // Step 7: Create .kilocode symlinks
414
+ const symlinkResult = createKilocodeSymlinks(projectRoot, force);
415
+ if (symlinkResult.success) {
416
+ result.steps.push('Created .kilocode symlinks (Junction Point on Windows)');
417
+ if (symlinkResult.warning) {
418
+ result.warnings.push(symlinkResult.warning);
419
+ }
420
+ } else {
421
+ result.errors.push(symlinkResult.warning || 'Failed to create .kilocode symlinks');
422
+ }
423
+
424
+ // Step 8: Generate CLAUDE.md, QWEN.md and .kilocodemodes
425
+ generateClaudeMd(workflowRoot, projectRoot, packageRoot);
426
+ generateQwenMd(workflowRoot, projectRoot, packageRoot);
427
+ generateKilocodemodes(projectRoot, packageRoot);
428
+ result.steps.push('Generated CLAUDE.md, QWEN.md and .kilocodemodes from agent-templates');
429
+
430
+ // Step 9: Update .gitignore
431
+ updateGitignore(projectRoot);
432
+ result.steps.push('Updated .gitignore with .workflow/logs/');
433
+
434
+ return result;
435
+ }
436
+
437
+ export default initProject;