@codemcp/workflows-core 3.1.21 → 3.2.0

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 (80) hide show
  1. package/package.json +9 -5
  2. package/resources/templates/architecture/arc42/arc42-template-EN.md +1077 -0
  3. package/resources/templates/architecture/arc42/images/01_2_iso-25010-topics-EN.drawio-2023.png +0 -0
  4. package/resources/templates/architecture/arc42/images/01_2_iso-25010-topics-EN.drawio.png +0 -0
  5. package/resources/templates/architecture/arc42/images/05_building_blocks-EN.png +0 -0
  6. package/resources/templates/architecture/arc42/images/08-concepts-EN.drawio.png +0 -0
  7. package/resources/templates/architecture/arc42/images/arc42-logo.png +0 -0
  8. package/resources/templates/architecture/c4.md +224 -0
  9. package/resources/templates/architecture/freestyle.md +53 -0
  10. package/resources/templates/architecture/none.md +17 -0
  11. package/resources/templates/design/comprehensive.md +207 -0
  12. package/resources/templates/design/freestyle.md +37 -0
  13. package/resources/templates/design/none.md +17 -0
  14. package/resources/templates/requirements/ears.md +90 -0
  15. package/resources/templates/requirements/freestyle.md +42 -0
  16. package/resources/templates/requirements/none.md +17 -0
  17. package/resources/workflows/big-bang-conversion.yaml +539 -0
  18. package/resources/workflows/boundary-testing.yaml +334 -0
  19. package/resources/workflows/bugfix.yaml +185 -0
  20. package/resources/workflows/business-analysis.yaml +671 -0
  21. package/resources/workflows/c4-analysis.yaml +485 -0
  22. package/resources/workflows/epcc.yaml +161 -0
  23. package/resources/workflows/greenfield.yaml +189 -0
  24. package/resources/workflows/minor.yaml +127 -0
  25. package/resources/workflows/posts.yaml +207 -0
  26. package/resources/workflows/slides.yaml +256 -0
  27. package/resources/workflows/tdd.yaml +157 -0
  28. package/resources/workflows/waterfall.yaml +195 -0
  29. package/.turbo/turbo-build.log +0 -4
  30. package/src/config-manager.ts +0 -96
  31. package/src/conversation-manager.ts +0 -489
  32. package/src/database.ts +0 -427
  33. package/src/file-detection-manager.ts +0 -302
  34. package/src/git-manager.ts +0 -64
  35. package/src/index.ts +0 -28
  36. package/src/instruction-generator.ts +0 -210
  37. package/src/interaction-logger.ts +0 -109
  38. package/src/logger.ts +0 -353
  39. package/src/path-validation-utils.ts +0 -261
  40. package/src/plan-manager.ts +0 -323
  41. package/src/project-docs-manager.ts +0 -523
  42. package/src/state-machine-loader.ts +0 -365
  43. package/src/state-machine-types.ts +0 -72
  44. package/src/state-machine.ts +0 -370
  45. package/src/system-prompt-generator.ts +0 -122
  46. package/src/template-manager.ts +0 -328
  47. package/src/transition-engine.ts +0 -386
  48. package/src/types.ts +0 -60
  49. package/src/workflow-manager.ts +0 -606
  50. package/test/unit/conversation-manager.test.ts +0 -179
  51. package/test/unit/custom-workflow-loading.test.ts +0 -174
  52. package/test/unit/directory-linking-and-extensions.test.ts +0 -338
  53. package/test/unit/file-linking-integration.test.ts +0 -256
  54. package/test/unit/git-commit-integration.test.ts +0 -91
  55. package/test/unit/git-manager.test.ts +0 -86
  56. package/test/unit/install-workflow.test.ts +0 -138
  57. package/test/unit/instruction-generator.test.ts +0 -247
  58. package/test/unit/list-workflows-filtering.test.ts +0 -68
  59. package/test/unit/none-template-functionality.test.ts +0 -224
  60. package/test/unit/project-docs-manager.test.ts +0 -337
  61. package/test/unit/state-machine-loader.test.ts +0 -234
  62. package/test/unit/template-manager.test.ts +0 -217
  63. package/test/unit/validate-workflow-name.test.ts +0 -150
  64. package/test/unit/workflow-domain-filtering.test.ts +0 -75
  65. package/test/unit/workflow-enum-generation.test.ts +0 -92
  66. package/test/unit/workflow-manager-enhanced-path-resolution.test.ts +0 -369
  67. package/test/unit/workflow-manager-path-resolution.test.ts +0 -150
  68. package/test/unit/workflow-migration.test.ts +0 -155
  69. package/test/unit/workflow-override-by-name.test.ts +0 -116
  70. package/test/unit/workflow-prioritization.test.ts +0 -38
  71. package/test/unit/workflow-validation.test.ts +0 -303
  72. package/test/utils/e2e-test-setup.ts +0 -453
  73. package/test/utils/run-server-in-dir.sh +0 -27
  74. package/test/utils/temp-files.ts +0 -308
  75. package/test/utils/test-access.ts +0 -79
  76. package/test/utils/test-helpers.ts +0 -286
  77. package/test/utils/test-setup.ts +0 -78
  78. package/tsconfig.build.json +0 -21
  79. package/tsconfig.json +0 -8
  80. package/vitest.config.ts +0 -18
@@ -1,523 +0,0 @@
1
- /**
2
- * Project Docs Manager
3
- *
4
- * Manages project documentation artifacts (architecture.md, requirements.md, design.md)
5
- * separate from the workflow-specific plan files. Handles creation, validation, and
6
- * path resolution for project documents. Now supports both template creation and
7
- * file linking via symlinks.
8
- */
9
-
10
- import {
11
- writeFile,
12
- access,
13
- mkdir,
14
- unlink,
15
- symlink,
16
- lstat,
17
- stat,
18
- readdir,
19
- } from 'node:fs/promises';
20
- import { join, dirname, relative, extname, basename } from 'node:path';
21
- import { createLogger } from './logger.js';
22
- import { TemplateManager, TemplateOptions } from './template-manager.js';
23
-
24
- const logger = createLogger('ProjectDocsManager');
25
-
26
- export interface ProjectDocsInfo {
27
- architecture: { path: string; exists: boolean };
28
- requirements: { path: string; exists: boolean };
29
- design: { path: string; exists: boolean };
30
- }
31
-
32
- export interface CreateOrLinkResult {
33
- created: string[];
34
- linked: string[];
35
- skipped: string[];
36
- }
37
-
38
- export class ProjectDocsManager {
39
- public templateManager: TemplateManager; // Make public for access from other classes
40
-
41
- constructor() {
42
- this.templateManager = new TemplateManager();
43
- }
44
-
45
- /**
46
- * Get project docs directory path
47
- */
48
- getDocsPath(projectPath: string): string {
49
- return join(projectPath, '.vibe', 'docs');
50
- }
51
-
52
- /**
53
- * Determine the appropriate extension for a document based on source path
54
- */
55
- private async getDocumentExtension(sourcePath?: string): Promise<string> {
56
- if (!sourcePath) {
57
- return '.md'; // Default for templates
58
- }
59
-
60
- try {
61
- const stats = await stat(sourcePath);
62
-
63
- if (stats.isDirectory()) {
64
- return ''; // No extension for directories
65
- }
66
-
67
- // For files, preserve the original extension
68
- const ext = extname(sourcePath);
69
- return ext || '.md'; // Default to .md if no extension
70
- } catch (_error) {
71
- return '.md'; // Default if we can't stat the source
72
- }
73
- }
74
-
75
- /**
76
- * Get the target filename for a document type
77
- */
78
- private async getDocumentFilename(
79
- type: 'architecture' | 'requirements' | 'design',
80
- sourcePath?: string
81
- ): Promise<string> {
82
- const extension = await this.getDocumentExtension(sourcePath);
83
-
84
- // Always use the standardized document type name
85
- // This ensures consistent symlink names regardless of source path
86
- return extension === '' ? type : `${type}${extension}`;
87
- }
88
-
89
- /**
90
- * Get paths for all project documents
91
- */
92
- getDocumentPaths(projectPath: string): {
93
- architecture: string;
94
- requirements: string;
95
- design: string;
96
- } {
97
- const docsPath = this.getDocsPath(projectPath);
98
- return {
99
- architecture: join(docsPath, 'architecture.md'),
100
- requirements: join(docsPath, 'requirements.md'),
101
- design: join(docsPath, 'design.md'),
102
- };
103
- }
104
-
105
- /**
106
- * Get paths for all project documents with dynamic extensions based on source paths
107
- */
108
- async getDocumentPathsWithExtensions(
109
- projectPath: string,
110
- sourcePaths?: Partial<{
111
- architecture: string;
112
- requirements: string;
113
- design: string;
114
- }>
115
- ): Promise<{
116
- architecture: string;
117
- requirements: string;
118
- design: string;
119
- }> {
120
- const docsPath = this.getDocsPath(projectPath);
121
-
122
- const archFilename = await this.getDocumentFilename(
123
- 'architecture',
124
- sourcePaths?.architecture
125
- );
126
- const reqFilename = await this.getDocumentFilename(
127
- 'requirements',
128
- sourcePaths?.requirements
129
- );
130
- const designFilename = await this.getDocumentFilename(
131
- 'design',
132
- sourcePaths?.design
133
- );
134
-
135
- return {
136
- architecture: join(docsPath, archFilename),
137
- requirements: join(docsPath, reqFilename),
138
- design: join(docsPath, designFilename),
139
- };
140
- }
141
-
142
- /**
143
- * Check which project documents exist
144
- */
145
- async getProjectDocsInfo(projectPath: string): Promise<ProjectDocsInfo> {
146
- const paths = this.getDocumentPaths(projectPath);
147
-
148
- const checkExists = async (path: string): Promise<boolean> => {
149
- try {
150
- await access(path);
151
- return true;
152
- } catch (_error) {
153
- return false;
154
- }
155
- };
156
-
157
- // Check for documents with different extensions using simple logic
158
- const checkExistsWithExtensions = async (
159
- basePath: string,
160
- docType: string
161
- ): Promise<{ exists: boolean; actualPath: string }> => {
162
- // First check the default .md path
163
- if (await checkExists(basePath)) {
164
- return { exists: true, actualPath: basePath };
165
- }
166
-
167
- // Check for the document type with any extension or as directory
168
- const docsDir = dirname(basePath);
169
-
170
- try {
171
- const entries = await readdir(docsDir);
172
-
173
- // Look for entries that match the document type exactly or start with it
174
- for (const entry of entries) {
175
- const entryWithoutExt = entry.replace(/\.[^/.]+$/, '');
176
-
177
- if (entryWithoutExt === docType || entry === docType) {
178
- const entryPath = join(docsDir, entry);
179
- if (await checkExists(entryPath)) {
180
- return { exists: true, actualPath: entryPath };
181
- }
182
- }
183
- }
184
- } catch (_error) {
185
- // Directory might not exist yet
186
- }
187
-
188
- return { exists: false, actualPath: basePath };
189
- };
190
-
191
- const archResult = await checkExistsWithExtensions(
192
- paths.architecture,
193
- 'architecture'
194
- );
195
- const reqResult = await checkExistsWithExtensions(
196
- paths.requirements,
197
- 'requirements'
198
- );
199
- const designResult = await checkExistsWithExtensions(
200
- paths.design,
201
- 'design'
202
- );
203
-
204
- return {
205
- architecture: {
206
- path: archResult.actualPath,
207
- exists: archResult.exists,
208
- },
209
- requirements: {
210
- path: reqResult.actualPath,
211
- exists: reqResult.exists,
212
- },
213
- design: {
214
- path: designResult.actualPath,
215
- exists: designResult.exists,
216
- },
217
- };
218
- }
219
-
220
- /**
221
- * Create project documents using templates (legacy method for backward compatibility)
222
- */
223
- async createProjectDocs(
224
- projectPath: string,
225
- options?: TemplateOptions
226
- ): Promise<{ created: string[]; skipped: string[] }> {
227
- const result = await this.createOrLinkProjectDocs(projectPath, options, {});
228
- return {
229
- created: result.created,
230
- skipped: result.skipped,
231
- };
232
- }
233
-
234
- /**
235
- * Create or link project documents using templates and/or file paths
236
- */
237
- async createOrLinkProjectDocs(
238
- projectPath: string,
239
- templateOptions?: Partial<TemplateOptions>,
240
- filePaths?: Partial<{
241
- architecture: string;
242
- requirements: string;
243
- design: string;
244
- }>
245
- ): Promise<CreateOrLinkResult> {
246
- const defaults = await this.templateManager.getDefaults();
247
- const finalTemplateOptions = { ...defaults, ...templateOptions };
248
-
249
- const docsPath = this.getDocsPath(projectPath);
250
-
251
- // Use dynamic paths that consider source file extensions
252
- const paths = await this.getDocumentPathsWithExtensions(
253
- projectPath,
254
- filePaths
255
- );
256
-
257
- // Check existing documents using the old static paths for backward compatibility
258
-
259
- const info = await this.getProjectDocsInfo(projectPath);
260
-
261
- // Ensure docs directory exists
262
- await mkdir(docsPath, { recursive: true });
263
-
264
- const created: string[] = [];
265
- const linked: string[] = [];
266
- const skipped: string[] = [];
267
-
268
- // Handle architecture document
269
- if (!info.architecture.exists) {
270
- if (filePaths?.architecture) {
271
- await this.createSymlink(filePaths.architecture, paths.architecture);
272
- const filename = basename(paths.architecture);
273
- linked.push(filename);
274
- } else {
275
- await this.createDocument(
276
- 'architecture',
277
- finalTemplateOptions.architecture,
278
- paths.architecture,
279
- docsPath
280
- );
281
- const filename = basename(paths.architecture);
282
- created.push(filename);
283
- }
284
- } else {
285
- skipped.push('architecture.md');
286
- }
287
-
288
- // Handle requirements document
289
- if (!info.requirements.exists) {
290
- if (filePaths?.requirements) {
291
- await this.createSymlink(filePaths.requirements, paths.requirements);
292
- const filename = basename(paths.requirements);
293
- linked.push(filename);
294
- } else {
295
- await this.createDocument(
296
- 'requirements',
297
- finalTemplateOptions.requirements,
298
- paths.requirements,
299
- docsPath
300
- );
301
- const filename = basename(paths.requirements);
302
- created.push(filename);
303
- }
304
- } else {
305
- skipped.push('requirements.md');
306
- }
307
-
308
- // Handle design document
309
- if (!info.design.exists) {
310
- if (filePaths?.design) {
311
- await this.createSymlink(filePaths.design, paths.design);
312
- const filename = basename(paths.design);
313
- linked.push(filename);
314
- } else {
315
- await this.createDocument(
316
- 'design',
317
- finalTemplateOptions.design,
318
- paths.design,
319
- docsPath
320
- );
321
- const filename = basename(paths.design);
322
- created.push(filename);
323
- }
324
- } else {
325
- skipped.push('design.md');
326
- }
327
-
328
- logger.info('Project docs creation/linking completed', {
329
- created,
330
- linked,
331
- skipped,
332
- projectPath,
333
- templateOptions: finalTemplateOptions,
334
- filePaths,
335
- });
336
-
337
- return { created, linked, skipped };
338
- }
339
-
340
- /**
341
- * Create a symlink to an existing file
342
- */
343
- async createSymlink(sourcePath: string, targetPath: string): Promise<void> {
344
- try {
345
- // Remove existing file/symlink if it exists
346
- await this.removeExistingDocument(targetPath);
347
-
348
- // Create relative symlink for better portability
349
- const targetDir = dirname(targetPath);
350
- const relativePath = relative(targetDir, sourcePath);
351
-
352
- await symlink(relativePath, targetPath);
353
-
354
- logger.debug('Symlink created successfully', {
355
- sourcePath,
356
- targetPath,
357
- relativePath,
358
- });
359
- } catch (error) {
360
- logger.error('Failed to create symlink', error as Error, {
361
- sourcePath,
362
- targetPath,
363
- });
364
- throw new Error(
365
- `Failed to create symlink: ${error instanceof Error ? error.message : 'Unknown error'}`
366
- );
367
- }
368
- }
369
-
370
- /**
371
- * Remove existing document or symlink
372
- */
373
- private async removeExistingDocument(documentPath: string): Promise<void> {
374
- try {
375
- const stats = await lstat(documentPath);
376
- await unlink(documentPath);
377
-
378
- logger.debug('Existing document removed', {
379
- documentPath,
380
- wasSymlink: stats.isSymbolicLink(),
381
- });
382
- } catch (error) {
383
- // File doesn't exist, which is fine
384
- const nodeError = error as NodeJS.ErrnoException;
385
- if (
386
- error instanceof Error &&
387
- 'code' in nodeError &&
388
- nodeError.code !== 'ENOENT'
389
- ) {
390
- logger.debug('Failed to remove existing document', {
391
- documentPath,
392
- error: nodeError.message,
393
- });
394
- }
395
- }
396
- }
397
-
398
- /**
399
- * Create a single document from template
400
- */
401
- private async createDocument(
402
- type: 'architecture' | 'requirements' | 'design',
403
- template: string,
404
- documentPath: string,
405
- docsPath: string
406
- ): Promise<void> {
407
- try {
408
- const templateResult = await this.templateManager.loadTemplate(
409
- type,
410
- template
411
- );
412
-
413
- // Write the main document
414
- await writeFile(documentPath, templateResult.content, 'utf-8');
415
-
416
- // Write additional files (like images for arc42)
417
- if (templateResult.additionalFiles) {
418
- for (const file of templateResult.additionalFiles) {
419
- const filePath = join(docsPath, file.relativePath);
420
- const fileDir = dirname(filePath);
421
-
422
- // Ensure directory exists
423
- await mkdir(fileDir, { recursive: true });
424
-
425
- // Write file
426
- await writeFile(filePath, file.content.toString());
427
- }
428
- }
429
-
430
- logger.debug(`Created ${type} document`, { documentPath, template });
431
- } catch (error) {
432
- logger.error(`Failed to create ${type} document`, error as Error, {
433
- documentPath,
434
- template,
435
- });
436
- throw error;
437
- }
438
- }
439
-
440
- /**
441
- * Get variable substitutions for workflow instructions
442
- */
443
- getVariableSubstitutions(projectPath: string): Record<string, string> {
444
- const paths = this.getDocumentPaths(projectPath);
445
-
446
- return {
447
- $ARCHITECTURE_DOC: paths.architecture,
448
- $REQUIREMENTS_DOC: paths.requirements,
449
- $DESIGN_DOC: paths.design,
450
- };
451
- }
452
-
453
- /**
454
- * Get variable substitutions for workflow instructions with dynamic paths
455
- */
456
- async getVariableSubstitutionsWithExtensions(
457
- projectPath: string,
458
- sourcePaths?: Partial<{
459
- architecture: string;
460
- requirements: string;
461
- design: string;
462
- }>
463
- ): Promise<Record<string, string>> {
464
- const paths = await this.getDocumentPathsWithExtensions(
465
- projectPath,
466
- sourcePaths
467
- );
468
-
469
- return {
470
- $ARCHITECTURE_DOC: paths.architecture,
471
- $REQUIREMENTS_DOC: paths.requirements,
472
- $DESIGN_DOC: paths.design,
473
- };
474
- }
475
-
476
- /**
477
- * Read a project document - returns the path for LLM to read as needed
478
- */
479
- async readDocument(
480
- projectPath: string,
481
- type: 'architecture' | 'requirements' | 'design'
482
- ): Promise<string> {
483
- // Use the dynamic path detection to get the actual document path
484
- const docsInfo = await this.getProjectDocsInfo(projectPath);
485
- const documentPath = docsInfo[type].path;
486
-
487
- if (!docsInfo[type].exists) {
488
- throw new Error(`${type} document not found: ${documentPath}`);
489
- }
490
-
491
- // Return the pure path for the LLM to read as needed
492
- // This is more efficient for large documents and gives LLM full control
493
- return documentPath;
494
- }
495
-
496
- /**
497
- * Check if all project documents exist
498
- */
499
- async allDocumentsExist(projectPath: string): Promise<boolean> {
500
- const info = await this.getProjectDocsInfo(projectPath);
501
- return (
502
- info.architecture.exists && info.requirements.exists && info.design.exists
503
- );
504
- }
505
-
506
- /**
507
- * Check if a document is a symlink
508
- */
509
- async isSymlink(
510
- projectPath: string,
511
- type: 'architecture' | 'requirements' | 'design'
512
- ): Promise<boolean> {
513
- const paths = this.getDocumentPaths(projectPath);
514
- const documentPath = paths[type];
515
-
516
- try {
517
- const stats = await lstat(documentPath);
518
- return stats.isSymbolicLink();
519
- } catch (_error) {
520
- return false;
521
- }
522
- }
523
- }