@auto-engineer/server-implementer 0.11.8 → 0.11.10
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/CHANGELOG.md +18 -0
- package/dist/src/commands/implement-slice.d.ts.map +1 -1
- package/dist/src/commands/implement-slice.js +115 -51
- package/dist/src/commands/implement-slice.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/src/commands/implement-slice.ts +152 -56
- package/.turbo/turbo-build.log +0 -5
- package/.turbo/turbo-format.log +0 -4
- package/.turbo/turbo-lint.log +0 -4
- package/.turbo/turbo-test.log +0 -13
- package/.turbo/turbo-type-check.log +0 -4
package/package.json
CHANGED
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
"debug": "^4.3.4",
|
|
18
18
|
"fast-glob": "^3.3.3",
|
|
19
19
|
"vite": "^5.4.1",
|
|
20
|
-
"@auto-engineer/ai-gateway": "0.11.
|
|
21
|
-
"@auto-engineer/message-bus": "0.11.
|
|
20
|
+
"@auto-engineer/ai-gateway": "0.11.10",
|
|
21
|
+
"@auto-engineer/message-bus": "0.11.10"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@types/fs-extra": "^11.0.4",
|
|
@@ -28,9 +28,9 @@
|
|
|
28
28
|
"glob": "^11.0.3",
|
|
29
29
|
"tsx": "^4.20.3",
|
|
30
30
|
"typescript": "^5.8.3",
|
|
31
|
-
"@auto-engineer/cli": "0.11.
|
|
31
|
+
"@auto-engineer/cli": "0.11.10"
|
|
32
32
|
},
|
|
33
|
-
"version": "0.11.
|
|
33
|
+
"version": "0.11.10",
|
|
34
34
|
"scripts": {
|
|
35
35
|
"build": "tsc && tsx ../../scripts/fix-esm-imports.ts",
|
|
36
36
|
"test": "vitest run --reporter=dot",
|
|
@@ -88,7 +88,6 @@ function extractCodeBlock(text: string): string {
|
|
|
88
88
|
.trim();
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
// Load all TypeScript files from the slice directory
|
|
92
91
|
async function loadContextFiles(sliceDir: string): Promise<Record<string, string>> {
|
|
93
92
|
const files = await fg(['*.ts'], { cwd: sliceDir });
|
|
94
93
|
const context: Record<string, string> = {};
|
|
@@ -96,36 +95,58 @@ async function loadContextFiles(sliceDir: string): Promise<Record<string, string
|
|
|
96
95
|
const absPath = path.join(sliceDir, file);
|
|
97
96
|
context[file] = await readFile(absPath, 'utf-8');
|
|
98
97
|
}
|
|
98
|
+
|
|
99
|
+
const sharedTypesPath = path.resolve(sliceDir, '../../../shared/types.ts');
|
|
100
|
+
if (existsSync(sharedTypesPath)) {
|
|
101
|
+
const sharedTypesContent = await readFile(sharedTypesPath, 'utf-8');
|
|
102
|
+
context['domain-shared-types.ts'] = sharedTypesContent;
|
|
103
|
+
}
|
|
104
|
+
|
|
99
105
|
return context;
|
|
100
106
|
}
|
|
101
107
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
108
|
+
const IMPLEMENTATION_MARKER = '// @auto-implement';
|
|
109
|
+
|
|
110
|
+
function hasImplementationMarker(content: string): boolean {
|
|
111
|
+
return content.includes(IMPLEMENTATION_MARKER);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function addImplementationMarker(content: string): string {
|
|
115
|
+
if (hasImplementationMarker(content)) {
|
|
116
|
+
return content;
|
|
106
117
|
}
|
|
107
|
-
return
|
|
108
|
-
|
|
118
|
+
return `${IMPLEMENTATION_MARKER}\n${content}`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function needsImplementation(content: string): boolean {
|
|
122
|
+
return (
|
|
123
|
+
hasImplementationMarker(content) || content.includes('TODO:') || content.includes('IMPLEMENTATION INSTRUCTIONS')
|
|
109
124
|
);
|
|
110
125
|
}
|
|
111
126
|
|
|
112
|
-
|
|
127
|
+
function findFilesToImplement(contextFiles: Record<string, string>): Array<[string, string]> {
|
|
128
|
+
return Object.entries(contextFiles).filter(([, content]) => hasImplementationMarker(content));
|
|
129
|
+
}
|
|
130
|
+
|
|
113
131
|
const SYSTEM_PROMPT = `
|
|
114
132
|
You are a software engineer implementing missing logic in a sliced event-driven TypeScript server. Each slice contains partially scaffolded code, and your task is to complete the logic following implementation instructions embedded in each file.
|
|
115
133
|
|
|
116
134
|
Project Characteristics:
|
|
117
135
|
- Architecture: sliced event-sourced CQRS (Command, Query, Reaction slices)
|
|
118
|
-
- Language: TypeScript with type-graphql and
|
|
136
|
+
- Language: TypeScript with type-graphql and @event-driven-io/emmett
|
|
119
137
|
- Each slice has scaffolded files with implementation instructions clearly marked with comments (e.g., '## IMPLEMENTATION INSTRUCTIONS ##') or TODOs.
|
|
120
138
|
- Tests (e.g., *.specs.ts) must pass.
|
|
121
139
|
- Type errors are not allowed.
|
|
140
|
+
- The domain uses shared enums defined in domain/shared/types.ts for type-safe values. When a field type is an enum (e.g., Status), you MUST use enum constants (e.g., Status.IN_PROGRESS) instead of string literals (e.g., 'in_progress').
|
|
122
141
|
|
|
123
142
|
Your Goal:
|
|
124
143
|
- Read the implementation instructions from the provided file.
|
|
125
144
|
- Generate only the code needed to fulfill the instructions, nothing extra and provide back the whole file without the instructions.
|
|
126
145
|
- Maintain immutability and adhere to functional best practices.
|
|
127
146
|
- Use only the types and domain constructs already present in the slice.
|
|
147
|
+
- CRITICAL: When a field has an enum type (e.g., status: Status), you MUST use the enum constant (e.g., Status.IN_PROGRESS) NOT a string literal (e.g., 'in_progress'). Check domain-shared-types.ts for the exact enum constant names.
|
|
128
148
|
- Do not remove existing imports or types that are still referenced or required in the file.
|
|
149
|
+
- Preserve index signatures like [key: string]: unknown as they are required for TypeScript compatibility.
|
|
129
150
|
- Return the entire updated file, not just the modified parts and remove any TODO comments or instructions after implementing the logic
|
|
130
151
|
|
|
131
152
|
Key rules:
|
|
@@ -138,24 +159,61 @@ Key rules:
|
|
|
138
159
|
- All code must be TypeScript compliant and follow functional patterns.
|
|
139
160
|
- If a test exists, make it pass.
|
|
140
161
|
- Keep implementations minimal and idiomatic.
|
|
162
|
+
- CRITICAL: When assigning values to enum-typed fields, use the enum constant name from domain-shared-types.ts. For example, if Status enum has IN_PROGRESS = 'in_progress', use Status.IN_PROGRESS not 'in_progress'.
|
|
141
163
|
|
|
142
164
|
Avoid:
|
|
143
165
|
- Adding new dependencies.
|
|
144
166
|
- Refactoring unrelated code.
|
|
145
167
|
- Changing the structure of already scaffolded files unless instructed.
|
|
168
|
+
- Using string literals for enum-typed fields. ALWAYS use the enum constant from domain-shared-types.ts (e.g., if status field type is Status and Status enum defines IN_PROGRESS = 'in_progress', use Status.IN_PROGRESS not the string 'in_progress').
|
|
146
169
|
|
|
147
170
|
You will receive:
|
|
148
171
|
- The path of the file to implement.
|
|
149
172
|
- The current contents of the file, with instruction comments.
|
|
150
173
|
- Other relevant files from the same slice (e.g., types, test, state, etc.).
|
|
174
|
+
- Shared domain types including enum definitions (domain-shared-types.ts).
|
|
151
175
|
|
|
152
176
|
You must:
|
|
153
177
|
- Return the entire updated file (no commentary and remove all implementation instructions).
|
|
154
178
|
- Ensure the output is valid TypeScript.
|
|
179
|
+
- Use enum constants from domain-shared-types.ts when appropriate.
|
|
180
|
+
`;
|
|
181
|
+
|
|
182
|
+
function extractEnumExamples(sharedTypesContent: string): string {
|
|
183
|
+
const enumMatches = sharedTypesContent.matchAll(/export enum (\w+) \{([^}]+)\}/g);
|
|
184
|
+
const examples: string[] = [];
|
|
185
|
+
|
|
186
|
+
for (const match of enumMatches) {
|
|
187
|
+
const enumName = match[1];
|
|
188
|
+
const enumBody = match[2];
|
|
189
|
+
const firstConstant = enumBody.match(/\s*(\w+)\s*=\s*['"]([^'"]+)['"]/);
|
|
190
|
+
|
|
191
|
+
if (firstConstant !== null) {
|
|
192
|
+
const constantName = firstConstant[1];
|
|
193
|
+
const constantValue = firstConstant[2];
|
|
194
|
+
examples.push(` - ${enumName}.${constantName} (NOT '${constantValue}')`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (examples.length === 0) {
|
|
199
|
+
return '';
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return `
|
|
203
|
+
📌 CRITICAL: Enum Usage Examples from Your Context:
|
|
204
|
+
${examples.join('\n')}
|
|
205
|
+
|
|
206
|
+
Pattern: Always use EnumName.CONSTANT_NAME for enum-typed fields.
|
|
207
|
+
Never use string literals like 'pending' or 'Pending' when an enum constant exists.
|
|
155
208
|
`;
|
|
209
|
+
}
|
|
156
210
|
|
|
157
|
-
// Build prompt for initial implementation
|
|
158
211
|
function buildInitialPrompt(targetFile: string, context: Record<string, string>): string {
|
|
212
|
+
const sharedTypes = context['domain-shared-types.ts'];
|
|
213
|
+
const sliceFiles = Object.entries(context).filter(
|
|
214
|
+
([name]) => name !== targetFile && name !== 'domain-shared-types.ts',
|
|
215
|
+
);
|
|
216
|
+
|
|
159
217
|
return `
|
|
160
218
|
${SYSTEM_PROMPT}
|
|
161
219
|
|
|
@@ -164,20 +222,32 @@ ${SYSTEM_PROMPT}
|
|
|
164
222
|
|
|
165
223
|
${context[targetFile]}
|
|
166
224
|
|
|
167
|
-
|
|
225
|
+
${
|
|
226
|
+
sharedTypes !== undefined
|
|
227
|
+
? `---
|
|
228
|
+
📦 Shared domain types (available via import from '../../../shared'):
|
|
229
|
+
${sharedTypes}
|
|
230
|
+
|
|
231
|
+
${extractEnumExamples(sharedTypes)}
|
|
232
|
+
IMPORTANT: Use enum constants (e.g., Status.PENDING) instead of string literals (e.g., 'pending') when working with enum types.
|
|
233
|
+
|
|
234
|
+
`
|
|
235
|
+
: ''
|
|
236
|
+
}---
|
|
168
237
|
🧠 Other files in the same slice:
|
|
169
|
-
${
|
|
170
|
-
.filter(([name]) => name !== targetFile)
|
|
171
|
-
.map(([name, content]) => `// File: ${name}\n${content}`)
|
|
172
|
-
.join('\n\n')}
|
|
238
|
+
${sliceFiles.map(([name, content]) => `// File: ${name}\n${content}`).join('\n\n')}
|
|
173
239
|
|
|
174
240
|
---
|
|
175
|
-
Return only the whole updated file of ${targetFile}. Do not remove existing imports or types that are still referenced or required in the file. The file returned has to be production ready.
|
|
241
|
+
Return only the whole updated file of ${targetFile}. Do not remove existing imports or types that are still referenced or required in the file. The file returned has to be production ready. Remember to use enum constants from domain-shared-types.ts instead of string literals.
|
|
176
242
|
`.trim();
|
|
177
243
|
}
|
|
178
244
|
|
|
179
|
-
// Build prompt for retry with context
|
|
180
245
|
function buildRetryPrompt(targetFile: string, context: Record<string, string>, previousOutputs: string): string {
|
|
246
|
+
const sharedTypes = context['domain-shared-types.ts'];
|
|
247
|
+
const sliceFiles = Object.entries(context).filter(
|
|
248
|
+
([name]) => name !== targetFile && name !== 'domain-shared-types.ts',
|
|
249
|
+
);
|
|
250
|
+
|
|
181
251
|
return `
|
|
182
252
|
${SYSTEM_PROMPT}
|
|
183
253
|
|
|
@@ -190,18 +260,69 @@ ${previousOutputs}
|
|
|
190
260
|
|
|
191
261
|
${context[targetFile]}
|
|
192
262
|
|
|
263
|
+
${
|
|
264
|
+
sharedTypes !== undefined
|
|
265
|
+
? `---
|
|
266
|
+
📦 Shared domain types (available via import from '../../../shared'):
|
|
267
|
+
${sharedTypes}
|
|
268
|
+
|
|
269
|
+
${extractEnumExamples(sharedTypes)}
|
|
270
|
+
IMPORTANT: Use enum constants (e.g., Status.PENDING) instead of string literals (e.g., 'pending') when working with enum types.
|
|
271
|
+
|
|
272
|
+
`
|
|
273
|
+
: ''
|
|
274
|
+
}---
|
|
193
275
|
🧠 Other files in the same slice:
|
|
194
|
-
${
|
|
195
|
-
.filter(([name]) => name !== targetFile)
|
|
196
|
-
.map(([name, content]) => `// File: ${name}\n${content}`)
|
|
197
|
-
.join('\n\n')}
|
|
276
|
+
${sliceFiles.map(([name, content]) => `// File: ${name}\n${content}`).join('\n\n')}
|
|
198
277
|
|
|
199
278
|
---
|
|
200
|
-
Return only the corrected full contents of ${targetFile}, no commentary, no markdown.
|
|
279
|
+
Return only the corrected full contents of ${targetFile}, no commentary, no markdown. Remember to use enum constants from domain-shared-types.ts instead of string literals.
|
|
201
280
|
`.trim();
|
|
202
281
|
}
|
|
203
282
|
|
|
204
|
-
|
|
283
|
+
async function addMarkersToFiles(slicePath: string, contextFiles: Record<string, string>): Promise<void> {
|
|
284
|
+
const filesToMark = Object.entries(contextFiles).filter(([, content]) => needsImplementation(content));
|
|
285
|
+
debugProcess(`Found ${filesToMark.length} files needing implementation markers`);
|
|
286
|
+
|
|
287
|
+
for (const [filename, content] of filesToMark) {
|
|
288
|
+
if (!hasImplementationMarker(content)) {
|
|
289
|
+
const markedContent = addImplementationMarker(content);
|
|
290
|
+
await writeFile(path.join(slicePath, filename), markedContent, 'utf-8');
|
|
291
|
+
contextFiles[filename] = markedContent;
|
|
292
|
+
debugProcess(`Added implementation marker to ${filename}`);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
async function implementFile(
|
|
298
|
+
slicePath: string,
|
|
299
|
+
targetFile: string,
|
|
300
|
+
contextFiles: Record<string, string>,
|
|
301
|
+
retryContext?: { previousOutputs?: string; attemptNumber?: number },
|
|
302
|
+
): Promise<void> {
|
|
303
|
+
debugProcess(`Implementing ${targetFile}`);
|
|
304
|
+
|
|
305
|
+
const previousOutputs = retryContext?.previousOutputs;
|
|
306
|
+
const isRetry = previousOutputs !== undefined && previousOutputs.length > 0;
|
|
307
|
+
const prompt = isRetry
|
|
308
|
+
? buildRetryPrompt(targetFile, contextFiles, previousOutputs)
|
|
309
|
+
: buildInitialPrompt(targetFile, contextFiles);
|
|
310
|
+
|
|
311
|
+
if (isRetry) {
|
|
312
|
+
debugProcess(`Using retry prompt for attempt #${retryContext?.attemptNumber ?? 2}`);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const aiOutput = await generateTextWithAI(prompt, { maxTokens: 8000 });
|
|
316
|
+
let cleanedCode = extractCodeBlock(aiOutput);
|
|
317
|
+
cleanedCode = addImplementationMarker(cleanedCode);
|
|
318
|
+
|
|
319
|
+
const filePath = path.join(slicePath, targetFile);
|
|
320
|
+
await writeFile(filePath, cleanedCode, 'utf-8');
|
|
321
|
+
debugProcess(`Successfully implemented ${targetFile}`);
|
|
322
|
+
|
|
323
|
+
contextFiles[targetFile] = cleanedCode;
|
|
324
|
+
}
|
|
325
|
+
|
|
205
326
|
async function implementSlice(
|
|
206
327
|
slicePath: string,
|
|
207
328
|
context?: { previousOutputs?: string; attemptNumber?: number },
|
|
@@ -211,46 +332,23 @@ async function implementSlice(
|
|
|
211
332
|
debugProcess(`Implementing slice: ${sliceName}`);
|
|
212
333
|
|
|
213
334
|
try {
|
|
214
|
-
// Load all context files
|
|
215
335
|
const contextFiles = await loadContextFiles(slicePath);
|
|
216
336
|
debugProcess(`Loaded ${Object.keys(contextFiles).join(', ')} files from slice`);
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
const
|
|
337
|
+
|
|
338
|
+
await addMarkersToFiles(slicePath, contextFiles);
|
|
339
|
+
|
|
340
|
+
const filesToImplement = findFilesToImplement(contextFiles);
|
|
341
|
+
debugProcess(`Found ${filesToImplement.length} files with markers to implement`);
|
|
221
342
|
|
|
222
343
|
if (filesToImplement.length === 0) {
|
|
223
|
-
debugProcess('No files with
|
|
344
|
+
debugProcess('No files with markers found');
|
|
224
345
|
return { success: true, filesImplemented: [] };
|
|
225
346
|
}
|
|
226
347
|
|
|
227
|
-
|
|
348
|
+
const implementedFiles: string[] = [];
|
|
228
349
|
for (const [targetFile] of filesToImplement) {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
let prompt: string;
|
|
232
|
-
if (context !== undefined && context.previousOutputs !== undefined && context.previousOutputs.length > 0) {
|
|
233
|
-
// Use retry prompt if we have previous context
|
|
234
|
-
prompt = buildRetryPrompt(targetFile, contextFiles, context.previousOutputs);
|
|
235
|
-
debugProcess(`Using retry prompt for attempt #${context.attemptNumber ?? 2}`);
|
|
236
|
-
} else {
|
|
237
|
-
// Use initial prompt for first attempt
|
|
238
|
-
prompt = buildInitialPrompt(targetFile, contextFiles);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// Generate implementation with AI
|
|
242
|
-
const aiOutput = await generateTextWithAI(prompt);
|
|
243
|
-
const cleanedCode = extractCodeBlock(aiOutput);
|
|
244
|
-
|
|
245
|
-
// Write the implemented file
|
|
246
|
-
const filePath = path.join(slicePath, targetFile);
|
|
247
|
-
await writeFile(filePath, cleanedCode, 'utf-8');
|
|
248
|
-
|
|
249
|
-
debugProcess(`Successfully implemented ${targetFile}`);
|
|
350
|
+
await implementFile(slicePath, targetFile, contextFiles, context);
|
|
250
351
|
implementedFiles.push(targetFile);
|
|
251
|
-
|
|
252
|
-
// Update context for next file
|
|
253
|
-
contextFiles[targetFile] = cleanedCode;
|
|
254
352
|
}
|
|
255
353
|
|
|
256
354
|
return { success: true, filesImplemented: implementedFiles };
|
|
@@ -323,7 +421,6 @@ function logRetryContext(context: { previousOutputs?: string; attemptNumber?: nu
|
|
|
323
421
|
export async function handleImplementSliceCommand(
|
|
324
422
|
command: ImplementSliceCommand,
|
|
325
423
|
): Promise<SliceImplementedEvent | SliceImplementationFailedEvent> {
|
|
326
|
-
// Handle both 'slicePath' and 'path' parameters for backward compatibility
|
|
327
424
|
const rawData = command.data as ImplementSliceCommand['data'] & { path?: string };
|
|
328
425
|
const slicePath = rawData.slicePath ?? rawData.path;
|
|
329
426
|
const { context } = command.data;
|
|
@@ -367,5 +464,4 @@ export async function handleImplementSliceCommand(
|
|
|
367
464
|
}
|
|
368
465
|
}
|
|
369
466
|
|
|
370
|
-
// Default export is the command handler
|
|
371
467
|
export default commandHandler;
|
package/.turbo/turbo-build.log
DELETED
package/.turbo/turbo-format.log
DELETED
package/.turbo/turbo-lint.log
DELETED
package/.turbo/turbo-test.log
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
> @auto-engineer/server-implementer@0.11.7 test /Users/sam/WebstormProjects/top/auto-engineer-3/packages/server-implementer
|
|
3
|
-
> vitest run --reporter=dot
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
RUN v3.2.4 /Users/sam/WebstormProjects/top/auto-engineer-3/packages/server-implementer
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
No test files found, exiting with code 0
|
|
10
|
-
|
|
11
|
-
include: **/*.specs.{js,ts}
|
|
12
|
-
exclude: **/.tmp/**, **/node_modules/**, **/dist/**, **/examples/**, **/create-auto-app/templates/**, **/*-starter/**, **/vite.config.ts
|
|
13
|
-
|