@codemcp/workflows-core 3.1.16
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/.turbo/turbo-build.log +4 -0
- package/LICENSE +674 -0
- package/dist/config-manager.d.ts +24 -0
- package/dist/config-manager.js +68 -0
- package/dist/config-manager.js.map +1 -0
- package/dist/conversation-manager.d.ts +97 -0
- package/dist/conversation-manager.js +367 -0
- package/dist/conversation-manager.js.map +1 -0
- package/dist/database.d.ts +73 -0
- package/dist/database.js +500 -0
- package/dist/database.js.map +1 -0
- package/dist/file-detection-manager.d.ts +53 -0
- package/dist/file-detection-manager.js +221 -0
- package/dist/file-detection-manager.js.map +1 -0
- package/dist/git-manager.d.ts +14 -0
- package/dist/git-manager.js +59 -0
- package/dist/git-manager.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/instruction-generator.d.ts +69 -0
- package/dist/instruction-generator.js +133 -0
- package/dist/instruction-generator.js.map +1 -0
- package/dist/interaction-logger.d.ts +37 -0
- package/dist/interaction-logger.js +87 -0
- package/dist/interaction-logger.js.map +1 -0
- package/dist/logger.d.ts +64 -0
- package/dist/logger.js +283 -0
- package/dist/logger.js.map +1 -0
- package/dist/path-validation-utils.d.ts +51 -0
- package/dist/path-validation-utils.js +202 -0
- package/dist/path-validation-utils.js.map +1 -0
- package/dist/plan-manager.d.ts +65 -0
- package/dist/plan-manager.js +256 -0
- package/dist/plan-manager.js.map +1 -0
- package/dist/project-docs-manager.d.ts +119 -0
- package/dist/project-docs-manager.js +357 -0
- package/dist/project-docs-manager.js.map +1 -0
- package/dist/state-machine-loader.d.ts +60 -0
- package/dist/state-machine-loader.js +235 -0
- package/dist/state-machine-loader.js.map +1 -0
- package/dist/state-machine-types.d.ts +58 -0
- package/dist/state-machine-types.js +7 -0
- package/dist/state-machine-types.js.map +1 -0
- package/dist/state-machine.d.ts +52 -0
- package/dist/state-machine.js +256 -0
- package/dist/state-machine.js.map +1 -0
- package/dist/system-prompt-generator.d.ts +17 -0
- package/dist/system-prompt-generator.js +113 -0
- package/dist/system-prompt-generator.js.map +1 -0
- package/dist/template-manager.d.ts +61 -0
- package/dist/template-manager.js +229 -0
- package/dist/template-manager.js.map +1 -0
- package/dist/transition-engine.d.ts +70 -0
- package/dist/transition-engine.js +240 -0
- package/dist/transition-engine.js.map +1 -0
- package/dist/types.d.ts +56 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/workflow-manager.d.ts +89 -0
- package/dist/workflow-manager.js +466 -0
- package/dist/workflow-manager.js.map +1 -0
- package/package.json +27 -0
- package/src/config-manager.ts +96 -0
- package/src/conversation-manager.ts +492 -0
- package/src/database.ts +685 -0
- package/src/file-detection-manager.ts +302 -0
- package/src/git-manager.ts +64 -0
- package/src/index.ts +28 -0
- package/src/instruction-generator.ts +210 -0
- package/src/interaction-logger.ts +109 -0
- package/src/logger.ts +353 -0
- package/src/path-validation-utils.ts +261 -0
- package/src/plan-manager.ts +323 -0
- package/src/project-docs-manager.ts +522 -0
- package/src/state-machine-loader.ts +308 -0
- package/src/state-machine-types.ts +72 -0
- package/src/state-machine.ts +370 -0
- package/src/system-prompt-generator.ts +122 -0
- package/src/template-manager.ts +321 -0
- package/src/transition-engine.ts +386 -0
- package/src/types.ts +60 -0
- package/src/workflow-manager.ts +601 -0
- package/test/unit/conversation-manager.test.ts +179 -0
- package/test/unit/custom-workflow-loading.test.ts +174 -0
- package/test/unit/directory-linking-and-extensions.test.ts +338 -0
- package/test/unit/file-linking-integration.test.ts +256 -0
- package/test/unit/git-commit-integration.test.ts +91 -0
- package/test/unit/git-manager.test.ts +86 -0
- package/test/unit/install-workflow.test.ts +138 -0
- package/test/unit/instruction-generator.test.ts +247 -0
- package/test/unit/list-workflows-filtering.test.ts +68 -0
- package/test/unit/none-template-functionality.test.ts +224 -0
- package/test/unit/project-docs-manager.test.ts +337 -0
- package/test/unit/state-machine-loader.test.ts +234 -0
- package/test/unit/template-manager.test.ts +217 -0
- package/test/unit/validate-workflow-name.test.ts +150 -0
- package/test/unit/workflow-domain-filtering.test.ts +75 -0
- package/test/unit/workflow-enum-generation.test.ts +92 -0
- package/test/unit/workflow-manager-enhanced-path-resolution.test.ts +369 -0
- package/test/unit/workflow-manager-path-resolution.test.ts +150 -0
- package/test/unit/workflow-migration.test.ts +155 -0
- package/test/unit/workflow-override-by-name.test.ts +116 -0
- package/test/unit/workflow-prioritization.test.ts +38 -0
- package/test/unit/workflow-validation.test.ts +303 -0
- package/test/utils/e2e-test-setup.ts +453 -0
- package/test/utils/run-server-in-dir.sh +27 -0
- package/test/utils/temp-files.ts +308 -0
- package/test/utils/test-access.ts +79 -0
- package/test/utils/test-helpers.ts +286 -0
- package/test/utils/test-setup.ts +78 -0
- package/tsconfig.build.json +21 -0
- package/tsconfig.json +8 -0
- package/vitest.config.ts +18 -0
@@ -0,0 +1,522 @@
|
|
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
|
+
if (
|
385
|
+
error instanceof Error &&
|
386
|
+
'code' in error &&
|
387
|
+
error.code !== 'ENOENT'
|
388
|
+
) {
|
389
|
+
logger.debug('Failed to remove existing document', {
|
390
|
+
documentPath,
|
391
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
392
|
+
});
|
393
|
+
}
|
394
|
+
}
|
395
|
+
}
|
396
|
+
|
397
|
+
/**
|
398
|
+
* Create a single document from template
|
399
|
+
*/
|
400
|
+
private async createDocument(
|
401
|
+
type: 'architecture' | 'requirements' | 'design',
|
402
|
+
template: string,
|
403
|
+
documentPath: string,
|
404
|
+
docsPath: string
|
405
|
+
): Promise<void> {
|
406
|
+
try {
|
407
|
+
const templateResult = await this.templateManager.loadTemplate(
|
408
|
+
type,
|
409
|
+
template
|
410
|
+
);
|
411
|
+
|
412
|
+
// Write the main document
|
413
|
+
await writeFile(documentPath, templateResult.content, 'utf-8');
|
414
|
+
|
415
|
+
// Write additional files (like images for arc42)
|
416
|
+
if (templateResult.additionalFiles) {
|
417
|
+
for (const file of templateResult.additionalFiles) {
|
418
|
+
const filePath = join(docsPath, file.relativePath);
|
419
|
+
const fileDir = dirname(filePath);
|
420
|
+
|
421
|
+
// Ensure directory exists
|
422
|
+
await mkdir(fileDir, { recursive: true });
|
423
|
+
|
424
|
+
// Write file
|
425
|
+
await writeFile(filePath, file.content);
|
426
|
+
}
|
427
|
+
}
|
428
|
+
|
429
|
+
logger.debug(`Created ${type} document`, { documentPath, template });
|
430
|
+
} catch (error) {
|
431
|
+
logger.error(`Failed to create ${type} document`, error as Error, {
|
432
|
+
documentPath,
|
433
|
+
template,
|
434
|
+
});
|
435
|
+
throw error;
|
436
|
+
}
|
437
|
+
}
|
438
|
+
|
439
|
+
/**
|
440
|
+
* Get variable substitutions for workflow instructions
|
441
|
+
*/
|
442
|
+
getVariableSubstitutions(projectPath: string): Record<string, string> {
|
443
|
+
const paths = this.getDocumentPaths(projectPath);
|
444
|
+
|
445
|
+
return {
|
446
|
+
$ARCHITECTURE_DOC: paths.architecture,
|
447
|
+
$REQUIREMENTS_DOC: paths.requirements,
|
448
|
+
$DESIGN_DOC: paths.design,
|
449
|
+
};
|
450
|
+
}
|
451
|
+
|
452
|
+
/**
|
453
|
+
* Get variable substitutions for workflow instructions with dynamic paths
|
454
|
+
*/
|
455
|
+
async getVariableSubstitutionsWithExtensions(
|
456
|
+
projectPath: string,
|
457
|
+
sourcePaths?: Partial<{
|
458
|
+
architecture: string;
|
459
|
+
requirements: string;
|
460
|
+
design: string;
|
461
|
+
}>
|
462
|
+
): Promise<Record<string, string>> {
|
463
|
+
const paths = await this.getDocumentPathsWithExtensions(
|
464
|
+
projectPath,
|
465
|
+
sourcePaths
|
466
|
+
);
|
467
|
+
|
468
|
+
return {
|
469
|
+
$ARCHITECTURE_DOC: paths.architecture,
|
470
|
+
$REQUIREMENTS_DOC: paths.requirements,
|
471
|
+
$DESIGN_DOC: paths.design,
|
472
|
+
};
|
473
|
+
}
|
474
|
+
|
475
|
+
/**
|
476
|
+
* Read a project document - returns the path for LLM to read as needed
|
477
|
+
*/
|
478
|
+
async readDocument(
|
479
|
+
projectPath: string,
|
480
|
+
type: 'architecture' | 'requirements' | 'design'
|
481
|
+
): Promise<string> {
|
482
|
+
// Use the dynamic path detection to get the actual document path
|
483
|
+
const docsInfo = await this.getProjectDocsInfo(projectPath);
|
484
|
+
const documentPath = docsInfo[type].path;
|
485
|
+
|
486
|
+
if (!docsInfo[type].exists) {
|
487
|
+
throw new Error(`${type} document not found: ${documentPath}`);
|
488
|
+
}
|
489
|
+
|
490
|
+
// Return the pure path for the LLM to read as needed
|
491
|
+
// This is more efficient for large documents and gives LLM full control
|
492
|
+
return documentPath;
|
493
|
+
}
|
494
|
+
|
495
|
+
/**
|
496
|
+
* Check if all project documents exist
|
497
|
+
*/
|
498
|
+
async allDocumentsExist(projectPath: string): Promise<boolean> {
|
499
|
+
const info = await this.getProjectDocsInfo(projectPath);
|
500
|
+
return (
|
501
|
+
info.architecture.exists && info.requirements.exists && info.design.exists
|
502
|
+
);
|
503
|
+
}
|
504
|
+
|
505
|
+
/**
|
506
|
+
* Check if a document is a symlink
|
507
|
+
*/
|
508
|
+
async isSymlink(
|
509
|
+
projectPath: string,
|
510
|
+
type: 'architecture' | 'requirements' | 'design'
|
511
|
+
): Promise<boolean> {
|
512
|
+
const paths = this.getDocumentPaths(projectPath);
|
513
|
+
const documentPath = paths[type];
|
514
|
+
|
515
|
+
try {
|
516
|
+
const stats = await lstat(documentPath);
|
517
|
+
return stats.isSymbolicLink();
|
518
|
+
} catch (_error) {
|
519
|
+
return false;
|
520
|
+
}
|
521
|
+
}
|
522
|
+
}
|