@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.
Files changed (114) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/LICENSE +674 -0
  3. package/dist/config-manager.d.ts +24 -0
  4. package/dist/config-manager.js +68 -0
  5. package/dist/config-manager.js.map +1 -0
  6. package/dist/conversation-manager.d.ts +97 -0
  7. package/dist/conversation-manager.js +367 -0
  8. package/dist/conversation-manager.js.map +1 -0
  9. package/dist/database.d.ts +73 -0
  10. package/dist/database.js +500 -0
  11. package/dist/database.js.map +1 -0
  12. package/dist/file-detection-manager.d.ts +53 -0
  13. package/dist/file-detection-manager.js +221 -0
  14. package/dist/file-detection-manager.js.map +1 -0
  15. package/dist/git-manager.d.ts +14 -0
  16. package/dist/git-manager.js +59 -0
  17. package/dist/git-manager.js.map +1 -0
  18. package/dist/index.d.ts +19 -0
  19. package/dist/index.js +25 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/instruction-generator.d.ts +69 -0
  22. package/dist/instruction-generator.js +133 -0
  23. package/dist/instruction-generator.js.map +1 -0
  24. package/dist/interaction-logger.d.ts +37 -0
  25. package/dist/interaction-logger.js +87 -0
  26. package/dist/interaction-logger.js.map +1 -0
  27. package/dist/logger.d.ts +64 -0
  28. package/dist/logger.js +283 -0
  29. package/dist/logger.js.map +1 -0
  30. package/dist/path-validation-utils.d.ts +51 -0
  31. package/dist/path-validation-utils.js +202 -0
  32. package/dist/path-validation-utils.js.map +1 -0
  33. package/dist/plan-manager.d.ts +65 -0
  34. package/dist/plan-manager.js +256 -0
  35. package/dist/plan-manager.js.map +1 -0
  36. package/dist/project-docs-manager.d.ts +119 -0
  37. package/dist/project-docs-manager.js +357 -0
  38. package/dist/project-docs-manager.js.map +1 -0
  39. package/dist/state-machine-loader.d.ts +60 -0
  40. package/dist/state-machine-loader.js +235 -0
  41. package/dist/state-machine-loader.js.map +1 -0
  42. package/dist/state-machine-types.d.ts +58 -0
  43. package/dist/state-machine-types.js +7 -0
  44. package/dist/state-machine-types.js.map +1 -0
  45. package/dist/state-machine.d.ts +52 -0
  46. package/dist/state-machine.js +256 -0
  47. package/dist/state-machine.js.map +1 -0
  48. package/dist/system-prompt-generator.d.ts +17 -0
  49. package/dist/system-prompt-generator.js +113 -0
  50. package/dist/system-prompt-generator.js.map +1 -0
  51. package/dist/template-manager.d.ts +61 -0
  52. package/dist/template-manager.js +229 -0
  53. package/dist/template-manager.js.map +1 -0
  54. package/dist/transition-engine.d.ts +70 -0
  55. package/dist/transition-engine.js +240 -0
  56. package/dist/transition-engine.js.map +1 -0
  57. package/dist/types.d.ts +56 -0
  58. package/dist/types.js +5 -0
  59. package/dist/types.js.map +1 -0
  60. package/dist/workflow-manager.d.ts +89 -0
  61. package/dist/workflow-manager.js +466 -0
  62. package/dist/workflow-manager.js.map +1 -0
  63. package/package.json +27 -0
  64. package/src/config-manager.ts +96 -0
  65. package/src/conversation-manager.ts +492 -0
  66. package/src/database.ts +685 -0
  67. package/src/file-detection-manager.ts +302 -0
  68. package/src/git-manager.ts +64 -0
  69. package/src/index.ts +28 -0
  70. package/src/instruction-generator.ts +210 -0
  71. package/src/interaction-logger.ts +109 -0
  72. package/src/logger.ts +353 -0
  73. package/src/path-validation-utils.ts +261 -0
  74. package/src/plan-manager.ts +323 -0
  75. package/src/project-docs-manager.ts +522 -0
  76. package/src/state-machine-loader.ts +308 -0
  77. package/src/state-machine-types.ts +72 -0
  78. package/src/state-machine.ts +370 -0
  79. package/src/system-prompt-generator.ts +122 -0
  80. package/src/template-manager.ts +321 -0
  81. package/src/transition-engine.ts +386 -0
  82. package/src/types.ts +60 -0
  83. package/src/workflow-manager.ts +601 -0
  84. package/test/unit/conversation-manager.test.ts +179 -0
  85. package/test/unit/custom-workflow-loading.test.ts +174 -0
  86. package/test/unit/directory-linking-and-extensions.test.ts +338 -0
  87. package/test/unit/file-linking-integration.test.ts +256 -0
  88. package/test/unit/git-commit-integration.test.ts +91 -0
  89. package/test/unit/git-manager.test.ts +86 -0
  90. package/test/unit/install-workflow.test.ts +138 -0
  91. package/test/unit/instruction-generator.test.ts +247 -0
  92. package/test/unit/list-workflows-filtering.test.ts +68 -0
  93. package/test/unit/none-template-functionality.test.ts +224 -0
  94. package/test/unit/project-docs-manager.test.ts +337 -0
  95. package/test/unit/state-machine-loader.test.ts +234 -0
  96. package/test/unit/template-manager.test.ts +217 -0
  97. package/test/unit/validate-workflow-name.test.ts +150 -0
  98. package/test/unit/workflow-domain-filtering.test.ts +75 -0
  99. package/test/unit/workflow-enum-generation.test.ts +92 -0
  100. package/test/unit/workflow-manager-enhanced-path-resolution.test.ts +369 -0
  101. package/test/unit/workflow-manager-path-resolution.test.ts +150 -0
  102. package/test/unit/workflow-migration.test.ts +155 -0
  103. package/test/unit/workflow-override-by-name.test.ts +116 -0
  104. package/test/unit/workflow-prioritization.test.ts +38 -0
  105. package/test/unit/workflow-validation.test.ts +303 -0
  106. package/test/utils/e2e-test-setup.ts +453 -0
  107. package/test/utils/run-server-in-dir.sh +27 -0
  108. package/test/utils/temp-files.ts +308 -0
  109. package/test/utils/test-access.ts +79 -0
  110. package/test/utils/test-helpers.ts +286 -0
  111. package/test/utils/test-setup.ts +78 -0
  112. package/tsconfig.build.json +21 -0
  113. package/tsconfig.json +8 -0
  114. package/vitest.config.ts +18 -0
@@ -0,0 +1,302 @@
1
+ /**
2
+ * File Detection Manager
3
+ *
4
+ * Handles pattern-based file discovery and suggestions for existing documentation files.
5
+ * Supports auto-detection of common documentation patterns in projects.
6
+ */
7
+
8
+ import { readdir, access } from 'node:fs/promises';
9
+ import { join, basename } from 'node:path';
10
+ import { createLogger } from './logger.js';
11
+ import { PathValidationUtils } from './path-validation-utils.js';
12
+
13
+ const logger = createLogger('FileDetectionManager');
14
+
15
+ export interface DetectedFile {
16
+ path: string;
17
+ relativePath: string;
18
+ type: 'architecture' | 'requirements' | 'design';
19
+ confidence: 'high' | 'medium' | 'low';
20
+ }
21
+
22
+ export interface FileDetectionResult {
23
+ architecture: DetectedFile[];
24
+ requirements: DetectedFile[];
25
+ design: DetectedFile[];
26
+ }
27
+
28
+ export class FileDetectionManager {
29
+ private projectPath: string;
30
+
31
+ constructor(projectPath: string) {
32
+ this.projectPath = projectPath;
33
+ }
34
+
35
+ /**
36
+ * Detect existing documentation files in the project
37
+ */
38
+ async detectDocumentationFiles(): Promise<FileDetectionResult> {
39
+ logger.debug('Starting documentation file detection', {
40
+ projectPath: this.projectPath,
41
+ });
42
+
43
+ const searchLocations = this.getSearchLocations();
44
+ const patterns = PathValidationUtils.getCommonDocumentationPatterns();
45
+
46
+ const result: FileDetectionResult = {
47
+ architecture: [],
48
+ requirements: [],
49
+ design: [],
50
+ };
51
+
52
+ // Search in each location
53
+ for (const location of searchLocations) {
54
+ try {
55
+ await access(location);
56
+ const files = await this.scanLocation(location);
57
+
58
+ // Match files against patterns
59
+ for (const file of files) {
60
+ const matches = this.matchFileToPatterns(file, patterns);
61
+
62
+ for (const match of matches) {
63
+ result[match.type].push({
64
+ path: file.path,
65
+ relativePath: file.relativePath,
66
+ type: match.type,
67
+ confidence: match.confidence,
68
+ });
69
+ }
70
+ }
71
+ } catch (error) {
72
+ logger.debug('Search location not accessible', {
73
+ location,
74
+ error: error instanceof Error ? error.message : 'Unknown error',
75
+ });
76
+ }
77
+ }
78
+
79
+ // Sort by confidence and remove duplicates
80
+ result.architecture = this.sortAndDeduplicate(result.architecture);
81
+ result.requirements = this.sortAndDeduplicate(result.requirements);
82
+ result.design = this.sortAndDeduplicate(result.design);
83
+
84
+ logger.info('Documentation file detection completed', {
85
+ found: {
86
+ architecture: result.architecture.length,
87
+ requirements: result.requirements.length,
88
+ design: result.design.length,
89
+ },
90
+ });
91
+
92
+ return result;
93
+ }
94
+
95
+ /**
96
+ * Get search locations for documentation files
97
+ */
98
+ private getSearchLocations(): string[] {
99
+ return [
100
+ this.projectPath, // Project root
101
+ join(this.projectPath, 'docs'), // docs/ folder
102
+ join(this.projectPath, 'doc'), // doc/ folder
103
+ join(this.projectPath, '.vibe', 'docs'), // .vibe/docs/ folder
104
+ join(this.projectPath, 'documentation'), // documentation/ folder
105
+ ];
106
+ }
107
+
108
+ /**
109
+ * Scan a location for files
110
+ */
111
+ private async scanLocation(
112
+ location: string
113
+ ): Promise<Array<{ path: string; relativePath: string }>> {
114
+ try {
115
+ const entries = await readdir(location, { withFileTypes: true });
116
+ const files: Array<{ path: string; relativePath: string }> = [];
117
+
118
+ for (const entry of entries) {
119
+ if (entry.isFile()) {
120
+ const fullPath = join(location, entry.name);
121
+ const relativePath = fullPath.replace(this.projectPath + '/', '');
122
+
123
+ files.push({
124
+ path: fullPath,
125
+ relativePath,
126
+ });
127
+ }
128
+ }
129
+
130
+ return files;
131
+ } catch (error) {
132
+ logger.debug('Failed to scan location', {
133
+ location,
134
+ error: error instanceof Error ? error.message : 'Unknown error',
135
+ });
136
+ return [];
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Match a file against documentation patterns
142
+ */
143
+ private matchFileToPatterns(
144
+ file: { path: string; relativePath: string },
145
+ patterns: ReturnType<
146
+ typeof PathValidationUtils.getCommonDocumentationPatterns
147
+ >
148
+ ): Array<{
149
+ type: 'architecture' | 'requirements' | 'design';
150
+ confidence: 'high' | 'medium' | 'low';
151
+ }> {
152
+ const fileName = basename(file.path).toLowerCase();
153
+ const relativePath = file.relativePath.toLowerCase();
154
+ const matches: Array<{
155
+ type: 'architecture' | 'requirements' | 'design';
156
+ confidence: 'high' | 'medium' | 'low';
157
+ }> = [];
158
+
159
+ // Check architecture patterns
160
+ if (this.matchesPatterns(fileName, relativePath, patterns.architecture)) {
161
+ const confidence = this.getConfidence(fileName, 'architecture');
162
+ matches.push({ type: 'architecture', confidence });
163
+ }
164
+
165
+ // Check requirements patterns
166
+ if (this.matchesPatterns(fileName, relativePath, patterns.requirements)) {
167
+ const confidence = this.getConfidence(fileName, 'requirements');
168
+ matches.push({ type: 'requirements', confidence });
169
+ }
170
+
171
+ // Check design patterns
172
+ if (this.matchesPatterns(fileName, relativePath, patterns.design)) {
173
+ const confidence = this.getConfidence(fileName, 'design');
174
+ matches.push({ type: 'design', confidence });
175
+ }
176
+
177
+ return matches;
178
+ }
179
+
180
+ /**
181
+ * Check if file matches any of the patterns
182
+ */
183
+ private matchesPatterns(
184
+ fileName: string,
185
+ relativePath: string,
186
+ patterns: string[]
187
+ ): boolean {
188
+ return patterns.some(pattern => {
189
+ const normalizedPattern = pattern.toLowerCase();
190
+
191
+ // Exact filename match
192
+ if (fileName === normalizedPattern) {
193
+ return true;
194
+ }
195
+
196
+ // Relative path match
197
+ if (relativePath === normalizedPattern) {
198
+ return true;
199
+ }
200
+
201
+ // Pattern matching with wildcards
202
+ if (normalizedPattern.includes('*')) {
203
+ const regex = new RegExp(normalizedPattern.replace(/\*/g, '.*'));
204
+ return regex.test(fileName) || regex.test(relativePath);
205
+ }
206
+
207
+ return false;
208
+ });
209
+ }
210
+
211
+ /**
212
+ * Determine confidence level for a match
213
+ */
214
+ private getConfidence(
215
+ fileName: string,
216
+ type: string
217
+ ): 'high' | 'medium' | 'low' {
218
+ // High confidence for exact type matches
219
+ if (fileName.includes(type.toLowerCase())) {
220
+ return 'high';
221
+ }
222
+
223
+ // Medium confidence for README files (could contain any type)
224
+ if (fileName.includes('readme')) {
225
+ return 'medium';
226
+ }
227
+
228
+ // Low confidence for other matches
229
+ return 'low';
230
+ }
231
+
232
+ /**
233
+ * Sort by confidence and remove duplicates
234
+ */
235
+ private sortAndDeduplicate(files: DetectedFile[]): DetectedFile[] {
236
+ // Remove duplicates by path
237
+ const unique = files.filter(
238
+ (file, index, array) =>
239
+ array.findIndex(f => f.path === file.path) === index
240
+ );
241
+
242
+ // Sort by confidence (high first) and then by path length (shorter first)
243
+ return unique.sort((a, b) => {
244
+ const confidenceOrder = { high: 0, medium: 1, low: 2 };
245
+ const confidenceDiff =
246
+ confidenceOrder[a.confidence] - confidenceOrder[b.confidence];
247
+
248
+ if (confidenceDiff !== 0) {
249
+ return confidenceDiff;
250
+ }
251
+
252
+ return a.relativePath.length - b.relativePath.length;
253
+ });
254
+ }
255
+
256
+ /**
257
+ * Format file suggestions for LLM responses
258
+ */
259
+ formatSuggestions(detectionResult: FileDetectionResult): string {
260
+ const suggestions: string[] = [];
261
+
262
+ if (detectionResult.architecture.length > 0) {
263
+ suggestions.push(`**Architecture files found:**`);
264
+ for (const file of detectionResult.architecture.slice(0, 3)) {
265
+ suggestions.push(
266
+ ` - ${file.relativePath} (${file.confidence} confidence)`
267
+ );
268
+ }
269
+ }
270
+
271
+ if (detectionResult.requirements.length > 0) {
272
+ suggestions.push(`**Requirements files found:**`);
273
+ for (const file of detectionResult.requirements.slice(0, 3)) {
274
+ suggestions.push(
275
+ ` - ${file.relativePath} (${file.confidence} confidence)`
276
+ );
277
+ }
278
+ }
279
+
280
+ if (detectionResult.design.length > 0) {
281
+ suggestions.push(`**Design files found:**`);
282
+ for (const file of detectionResult.design.slice(0, 3)) {
283
+ suggestions.push(
284
+ ` - ${file.relativePath} (${file.confidence} confidence)`
285
+ );
286
+ }
287
+ }
288
+
289
+ if (suggestions.length === 0) {
290
+ return 'No existing documentation files detected.';
291
+ }
292
+
293
+ return [
294
+ 'Existing documentation files detected:',
295
+ '',
296
+ ...suggestions,
297
+ '',
298
+ 'You can use these files with `setup_project_docs` by providing the file paths instead of template names.',
299
+ 'Example: `setup_project_docs({ architecture: "README.md", requirements: "docs/requirements.md", design: "freestyle" })`',
300
+ ].join('\n');
301
+ }
302
+ }
@@ -0,0 +1,64 @@
1
+ import { execSync } from 'node:child_process';
2
+ import { existsSync } from 'node:fs';
3
+ import { createLogger } from './logger.js';
4
+
5
+ const logger = createLogger('GitManager');
6
+
7
+ export class GitManager {
8
+ /**
9
+ * Check if a directory is a git repository
10
+ */
11
+ static isGitRepository(projectPath: string): boolean {
12
+ return existsSync(`${projectPath}/.git`);
13
+ }
14
+
15
+ /**
16
+ * Get the current git branch for a project
17
+ */
18
+ static getCurrentBranch(projectPath: string): string {
19
+ try {
20
+ if (!this.isGitRepository(projectPath)) {
21
+ logger.debug('Not a git repository, using "default" as branch name', {
22
+ projectPath,
23
+ });
24
+ return 'default';
25
+ }
26
+
27
+ const branch = execSync('git rev-parse --abbrev-ref HEAD', {
28
+ cwd: projectPath,
29
+ encoding: 'utf-8',
30
+ stdio: ['ignore', 'pipe', 'ignore'],
31
+ }).trim();
32
+
33
+ logger.debug('Detected git branch', { projectPath, branch });
34
+ return branch;
35
+ } catch (_error) {
36
+ logger.debug('Failed to get git branch, using "default" as branch name', {
37
+ projectPath,
38
+ });
39
+ return 'default';
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Get the current HEAD commit hash (for tracking start of development)
45
+ */
46
+ static getCurrentCommitHash(projectPath: string): string | null {
47
+ try {
48
+ if (!this.isGitRepository(projectPath)) {
49
+ return null;
50
+ }
51
+
52
+ const hash = execSync('git rev-parse HEAD', {
53
+ cwd: projectPath,
54
+ encoding: 'utf-8',
55
+ stdio: ['ignore', 'pipe', 'ignore'],
56
+ }).trim();
57
+
58
+ return hash;
59
+ } catch (error) {
60
+ logger.debug('Failed to get current commit hash', { projectPath, error });
61
+ return null;
62
+ }
63
+ }
64
+ }
package/src/index.ts ADDED
@@ -0,0 +1,28 @@
1
+ // Core types and interfaces
2
+ export * from './types.js';
3
+ export * from './state-machine-types.js';
4
+
5
+ // State machine and workflow management
6
+ export * from './state-machine.js';
7
+ export * from './state-machine-loader.js';
8
+ export * from './workflow-manager.js';
9
+ export * from './transition-engine.js';
10
+
11
+ // Data management
12
+ export * from './database.js';
13
+ export * from './conversation-manager.js';
14
+
15
+ // Project and plan management
16
+ export * from './plan-manager.js';
17
+ export * from './template-manager.js';
18
+ export * from './project-docs-manager.js';
19
+ export * from './file-detection-manager.js';
20
+ export * from './config-manager.js';
21
+ export * from './git-manager.js';
22
+
23
+ // Utilities and generators
24
+ export * from './logger.js';
25
+ export * from './interaction-logger.js';
26
+ export * from './instruction-generator.js';
27
+ export * from './system-prompt-generator.js';
28
+ export * from './path-validation-utils.js';
@@ -0,0 +1,210 @@
1
+ /**
2
+ * Instruction Generator
3
+ *
4
+ * Creates phase-specific guidance for the LLM based on current conversation state.
5
+ * Customizes instructions based on project context and development phase.
6
+ * Supports custom state machine definitions for dynamic instruction generation.
7
+ * Handles variable substitution for project artifact references.
8
+ */
9
+
10
+ import type { ConversationContext } from './types.js';
11
+ import { PlanManager } from './plan-manager.js';
12
+ import { ProjectDocsManager } from './project-docs-manager.js';
13
+ import type { YamlStateMachine } from './state-machine-types.js';
14
+
15
+ export interface InstructionContext {
16
+ phase: string;
17
+ conversationContext: ConversationContext;
18
+ transitionReason: string;
19
+ isModeled: boolean;
20
+ planFileExists: boolean;
21
+ }
22
+
23
+ export interface GeneratedInstructions {
24
+ instructions: string;
25
+ planFileGuidance: string;
26
+ metadata: {
27
+ phase: string;
28
+ planFilePath: string;
29
+ transitionReason: string;
30
+ isModeled: boolean;
31
+ };
32
+ }
33
+
34
+ export class InstructionGenerator {
35
+ private planManager: PlanManager;
36
+ private projectDocsManager: ProjectDocsManager;
37
+ private stateMachine: YamlStateMachine | null = null;
38
+
39
+ constructor(planManager: PlanManager) {
40
+ this.planManager = planManager;
41
+ this.projectDocsManager = new ProjectDocsManager();
42
+ }
43
+
44
+ /**
45
+ * Set the state machine definition for dynamic instruction generation
46
+ */
47
+ setStateMachine(stateMachine: YamlStateMachine): void {
48
+ this.stateMachine = stateMachine;
49
+ }
50
+
51
+ /**
52
+ * Generate comprehensive instructions for the LLM
53
+ */
54
+ async generateInstructions(
55
+ baseInstructions: string,
56
+ context: InstructionContext
57
+ ): Promise<GeneratedInstructions> {
58
+ // Apply variable substitution to base instructions
59
+ const substitutedInstructions = this.applyVariableSubstitution(
60
+ baseInstructions,
61
+ context.conversationContext.projectPath
62
+ );
63
+
64
+ // Get plan file guidance
65
+ const planFileGuidance = this.planManager.generatePlanFileGuidance(
66
+ context.phase
67
+ );
68
+
69
+ // Enhance base instructions with context-specific guidance
70
+ const enhancedInstructions = await this.enhanceInstructions(
71
+ substitutedInstructions,
72
+ context,
73
+ planFileGuidance
74
+ );
75
+
76
+ return {
77
+ instructions: enhancedInstructions,
78
+ planFileGuidance,
79
+ metadata: {
80
+ phase: context.phase,
81
+ planFilePath: context.conversationContext.planFilePath,
82
+ transitionReason: context.transitionReason,
83
+ isModeled: context.isModeled,
84
+ },
85
+ };
86
+ }
87
+
88
+ /**
89
+ * Apply variable substitution to instructions
90
+ * Replaces project artifact variables with actual file paths
91
+ */
92
+ private applyVariableSubstitution(
93
+ instructions: string,
94
+ projectPath: string
95
+ ): string {
96
+ const substitutions =
97
+ this.projectDocsManager.getVariableSubstitutions(projectPath);
98
+
99
+ let result = instructions;
100
+ for (const [variable, value] of Object.entries(substitutions)) {
101
+ // Use global replace to handle multiple occurrences
102
+ result = result.replace(
103
+ new RegExp(this.escapeRegExp(variable), 'g'),
104
+ value
105
+ );
106
+ }
107
+
108
+ return result;
109
+ }
110
+
111
+ /**
112
+ * Escape special regex characters in variable names
113
+ */
114
+ private escapeRegExp(string: string): string {
115
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
116
+ }
117
+
118
+ /**
119
+ * Enhance base instructions with context-specific information
120
+ */
121
+ private async enhanceInstructions(
122
+ baseInstructions: string,
123
+ context: InstructionContext,
124
+ _planFileGuidance: string
125
+ ): Promise<string> {
126
+ const {
127
+ phase,
128
+ conversationContext,
129
+ transitionReason,
130
+ isModeled,
131
+ planFileExists,
132
+ } = context;
133
+
134
+ // Build plan-file-referential instructions
135
+ let enhanced = `Check your plan file at \`${conversationContext.planFilePath}\` and focus on the "${this.capitalizePhase(phase)}" section.
136
+
137
+ ${baseInstructions}
138
+
139
+ **Plan File Guidance:**
140
+ - Work on the tasks listed in the ${this.capitalizePhase(phase)} section
141
+ - Mark completed tasks with [x] as you finish them
142
+ - Add new tasks as they are identified during your work with the user
143
+ - Update the "Key Decisions" section with important choices made
144
+ - Add relevant notes to help maintain context`;
145
+
146
+ // Add project context
147
+ enhanced += `\n\n**Project Context:**
148
+ - Project: ${conversationContext.projectPath}
149
+ - Branch: ${conversationContext.gitBranch}
150
+ - Current Phase: ${phase}`;
151
+
152
+ // Add transition context if this is a modeled transition
153
+ if (isModeled && transitionReason) {
154
+ enhanced += `\n\n**Phase Context:**
155
+ - ${transitionReason}`;
156
+ }
157
+
158
+ // Add plan file creation note if needed
159
+ if (!planFileExists) {
160
+ enhanced +=
161
+ '\n\n**Note**: Plan file will be created when you first update it.';
162
+ }
163
+
164
+ return enhanced;
165
+ }
166
+
167
+ /**
168
+ * Get phase-specific contextual information based on state machine
169
+ */
170
+ private getPhaseSpecificContext(phase: string): string {
171
+ if (this.stateMachine) {
172
+ const phaseDefinition = this.stateMachine.states[phase];
173
+ if (phaseDefinition) {
174
+ return `**Context**: ${phaseDefinition.description}`;
175
+ }
176
+ }
177
+
178
+ throw new Error(
179
+ `State machine not set or unknown phase: ${phase}. This should not happen as state machine is always loaded.`
180
+ );
181
+ }
182
+
183
+ /**
184
+ * Get default phase context for standard phases
185
+ /**
186
+ * Get phase-specific reminders and best practices based on state machine
187
+ */
188
+ private getPhaseReminders(phase: string): string {
189
+ if (this.stateMachine) {
190
+ const phaseDefinition = this.stateMachine.states[phase];
191
+ if (phaseDefinition) {
192
+ return `**Remember**: \n- Focus on: ${phaseDefinition.description}\n- Update plan file with ${phase} progress\n- Mark completed tasks with [x]\n- Stay focused on current phase objectives`;
193
+ }
194
+ }
195
+
196
+ throw new Error(
197
+ `State machine not set or unknown phase: ${phase}. This should not happen as state machine is always loaded.`
198
+ );
199
+ }
200
+
201
+ /**
202
+ * Capitalize phase name for display
203
+ */
204
+ private capitalizePhase(phase: string): string {
205
+ return phase
206
+ .split('_')
207
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
208
+ .join(' ');
209
+ }
210
+ }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Interaction Logger module
3
+ *
4
+ * Handles logging of tool interactions to the database for auditing and debugging.
5
+ */
6
+
7
+ import { Database } from './database.js';
8
+ import { createLogger } from './logger.js';
9
+
10
+ import type { InteractionLog } from './types.js';
11
+
12
+ const logger = createLogger('InteractionLogger');
13
+
14
+ /**
15
+ * Handles logging of tool interactions to the database
16
+ */
17
+ export class InteractionLogger {
18
+ private database: Database;
19
+
20
+ /**
21
+ * Create a new InteractionLogger
22
+ *
23
+ * @param database - Database instance to use for logging
24
+ */
25
+ constructor(database: Database) {
26
+ this.database = database;
27
+ logger.debug('InteractionLogger initialized');
28
+ }
29
+
30
+ /**
31
+ * Log an interaction with a tool
32
+ *
33
+ * @param conversationId - ID of the conversation
34
+ * @param toolName - Name of the tool that was called
35
+ * @param inputParams - Input parameters to the tool (will be stringified)
36
+ * @param responseData - Response data from the tool (will be stringified)
37
+ * @param currentPhase - Current development phase
38
+ * @returns Promise that resolves when the log is saved
39
+ */
40
+ async logInteraction(
41
+ conversationId: string,
42
+ toolName: string,
43
+ inputParams: unknown,
44
+ responseData: unknown,
45
+ currentPhase: string
46
+ ): Promise<void> {
47
+ logger.debug('Logging interaction', {
48
+ conversationId,
49
+ toolName,
50
+ currentPhase,
51
+ });
52
+
53
+ try {
54
+ const timestamp = new Date().toISOString();
55
+
56
+ const log: InteractionLog = {
57
+ conversationId,
58
+ toolName,
59
+ inputParams: JSON.stringify(inputParams),
60
+ responseData: JSON.stringify(responseData),
61
+ currentPhase,
62
+ timestamp,
63
+ };
64
+
65
+ await this.database.logInteraction(log);
66
+
67
+ logger.info('Interaction logged successfully', {
68
+ conversationId,
69
+ toolName,
70
+ timestamp,
71
+ });
72
+ } catch (error) {
73
+ logger.error('Failed to log interaction', error as Error, {
74
+ conversationId,
75
+ toolName,
76
+ });
77
+ // Don't throw the error - logging should not break the main flow
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Get all interactions for a specific conversation
83
+ *
84
+ * @param conversationId - ID of the conversation to get logs for
85
+ * @returns Promise that resolves to an array of interaction logs
86
+ */
87
+ async getInteractionsByConversationId(
88
+ conversationId: string
89
+ ): Promise<InteractionLog[]> {
90
+ logger.debug('Getting interactions by conversation ID', { conversationId });
91
+
92
+ try {
93
+ const logs =
94
+ await this.database.getInteractionsByConversationId(conversationId);
95
+
96
+ logger.info('Retrieved interaction logs', {
97
+ conversationId,
98
+ count: logs.length,
99
+ });
100
+
101
+ return logs;
102
+ } catch (error) {
103
+ logger.error('Failed to get interaction logs', error as Error, {
104
+ conversationId,
105
+ });
106
+ throw error;
107
+ }
108
+ }
109
+ }