@auto-engineer/server-implementer 1.90.0 → 1.96.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.
package/ketchup-plan.md CHANGED
@@ -1,9 +1,14 @@
1
- # Ketchup Plan: Prevent implementer AI from redefining imported types
1
+ # Ketchup Plan: Fix implementer context/prompt gaps
2
2
 
3
3
  ## TODO
4
4
 
5
+ (none)
6
+
5
7
  ## DONE
6
8
 
9
+ - [x] Burst 9: Add discriminated union narrowing guidance to prompts (f43a84c9)
10
+ - [x] Burst 8: Load full shared directory into implementer context (e8f42abf)
11
+
7
12
  - [x] Burst 7: Add implementer prompt guardrails for phantom enums and hardcoded projections (14d8cbda)
8
13
  - [x] Burst 6: Fix `_state` reference in decide.ts.ejs instruction comment (dff8f329)
9
14
  - [x] Burst 5: Fix singleton projection instructions in projection.ts.ejs (3271cfe4)
package/package.json CHANGED
@@ -18,8 +18,8 @@
18
18
  "debug": "^4.3.4",
19
19
  "fast-glob": "^3.3.3",
20
20
  "vite": "^5.4.1",
21
- "@auto-engineer/model-factory": "1.90.0",
22
- "@auto-engineer/message-bus": "1.90.0"
21
+ "@auto-engineer/model-factory": "1.96.0",
22
+ "@auto-engineer/message-bus": "1.96.0"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@types/fs-extra": "^11.0.4",
@@ -29,9 +29,9 @@
29
29
  "glob": "^11.0.3",
30
30
  "tsx": "^4.20.3",
31
31
  "typescript": "^5.8.3",
32
- "@auto-engineer/cli": "1.90.0"
32
+ "@auto-engineer/cli": "1.96.0"
33
33
  },
34
- "version": "1.90.0",
34
+ "version": "1.96.0",
35
35
  "scripts": {
36
36
  "build": "tsc && tsx ../../scripts/fix-esm-imports.ts",
37
37
  "test": "vitest run --reporter=dot",
@@ -7,6 +7,7 @@ import fg from 'fast-glob';
7
7
  import { SYSTEM_PROMPT } from '../prompts/systemPrompt';
8
8
  import { buildShadowWarning } from '../utils/detectImportedTypeShadowing';
9
9
  import { extractCodeBlock } from '../utils/extractCodeBlock';
10
+ import { loadSharedContext } from '../utils/loadSharedContext';
10
11
  import { runTests } from './runTests';
11
12
 
12
13
  type TestAndTypecheckResult = {
@@ -108,6 +109,12 @@ async function loadContextFiles(sliceDir: string): Promise<Record<string, string
108
109
  const absPath = path.join(sliceDir, file);
109
110
  context[file] = await readFile(absPath, 'utf-8');
110
111
  }
112
+
113
+ const sharedContent = await loadSharedContext(sliceDir);
114
+ if (sharedContent) {
115
+ context['domain-shared-types.ts'] = sharedContent;
116
+ }
117
+
111
118
  return context;
112
119
  }
113
120
 
@@ -7,6 +7,7 @@ import { generateText } from 'ai';
7
7
  import createDebug from 'debug';
8
8
  import fg from 'fast-glob';
9
9
  import { buildShadowWarning } from '../utils/detectImportedTypeShadowing';
10
+ import { loadSharedContext } from '../utils/loadSharedContext';
10
11
 
11
12
  const debug = createDebug('auto:server-implementer:slice');
12
13
  const debugHandler = createDebug('auto:server-implementer:slice:handler');
@@ -109,10 +110,9 @@ async function loadContextFiles(sliceDir: string): Promise<Record<string, string
109
110
  context[file] = await readFile(absPath, 'utf-8');
110
111
  }
111
112
 
112
- const sharedTypesPath = path.resolve(sliceDir, '../../../shared/types.ts');
113
- if (existsSync(sharedTypesPath)) {
114
- const sharedTypesContent = await readFile(sharedTypesPath, 'utf-8');
115
- context['domain-shared-types.ts'] = sharedTypesContent;
113
+ const sharedContent = await loadSharedContext(sliceDir);
114
+ if (sharedContent) {
115
+ context['domain-shared-types.ts'] = sharedContent;
116
116
  }
117
117
 
118
118
  return context;
@@ -174,6 +174,7 @@ Key rules:
174
174
  - Keep implementations minimal and idiomatic.
175
175
  - 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'.
176
176
  - Derive all output values from inputs (event.data, command.data, state) or generate at runtime. Never reproduce literal values from test examples.
177
+ - When the State type is a discriminated union (e.g., type State = NotInitialized | Initialized), narrow the discriminant before accessing variant-specific properties. Example: if (_state.status === 'initialized') { ... _state.goalId ... }
177
178
 
178
179
  Avoid:
179
180
  - Adding new dependencies.
@@ -27,6 +27,7 @@ Key rules:
27
27
  - If a test exists, make it pass.
28
28
  - Keep implementations minimal and idiomatic.
29
29
  - Derive all output values from inputs (event.data, command.data, state) or generate at runtime. Never reproduce literal values from test examples.
30
+ - When the State type is a discriminated union (e.g., type State = NotInitialized | Initialized), narrow the discriminant before accessing variant-specific properties. Example: if (_state.status === 'initialized') { ... _state.goalId ... }
30
31
 
31
32
  Avoid:
32
33
  - Adding new dependencies.
@@ -0,0 +1,53 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
4
+ import { loadSharedContext } from './loadSharedContext';
5
+
6
+ describe('loadSharedContext', () => {
7
+ let tmpDir: string;
8
+ let sliceDir: string;
9
+ let sharedDir: string;
10
+
11
+ beforeEach(async () => {
12
+ tmpDir = await fs.mkdtemp(path.join(import.meta.dirname, '.tmp-'));
13
+ sharedDir = path.join(tmpDir, 'shared');
14
+ sliceDir = path.join(tmpDir, 'src', 'slices', 'some-slice');
15
+ await fs.mkdir(sliceDir, { recursive: true });
16
+ });
17
+
18
+ afterEach(async () => {
19
+ await fs.rm(tmpDir, { recursive: true, force: true });
20
+ });
21
+
22
+ it('returns undefined when shared directory does not exist', async () => {
23
+ expect(await loadSharedContext(sliceDir)).toBe(undefined);
24
+ });
25
+
26
+ it('returns undefined when shared directory has no .ts files', async () => {
27
+ await fs.mkdir(sharedDir, { recursive: true });
28
+ await fs.writeFile(path.join(sharedDir, 'readme.md'), '# Readme');
29
+
30
+ expect(await loadSharedContext(sliceDir)).toBe(undefined);
31
+ });
32
+
33
+ it('concatenates all .ts files from shared directory sorted by name', async () => {
34
+ await fs.mkdir(sharedDir, { recursive: true });
35
+ await fs.writeFile(path.join(sharedDir, 'types.ts'), 'export type Foo = string;');
36
+ await fs.writeFile(path.join(sharedDir, 'enums.ts'), 'export enum Bar { A = "a" }');
37
+
38
+ const result = await loadSharedContext(sliceDir);
39
+
40
+ expect(result).toBe(
41
+ '// File: shared/enums.ts\nexport enum Bar { A = "a" }\n\n// File: shared/types.ts\nexport type Foo = string;',
42
+ );
43
+ });
44
+
45
+ it('returns content for a single .ts file', async () => {
46
+ await fs.mkdir(sharedDir, { recursive: true });
47
+ await fs.writeFile(path.join(sharedDir, 'types.ts'), 'export type X = number;');
48
+
49
+ const result = await loadSharedContext(sliceDir);
50
+
51
+ expect(result).toBe('// File: shared/types.ts\nexport type X = number;');
52
+ });
53
+ });
@@ -0,0 +1,18 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { readFile } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import fg from 'fast-glob';
5
+
6
+ export async function loadSharedContext(sliceDir: string): Promise<string | undefined> {
7
+ const sharedDir = path.resolve(sliceDir, '../../../shared');
8
+ if (!existsSync(sharedDir)) return undefined;
9
+
10
+ const sharedFiles = await fg(['*.ts'], { cwd: sharedDir });
11
+ if (sharedFiles.length === 0) return undefined;
12
+
13
+ const contents: string[] = [];
14
+ for (const file of sharedFiles.sort()) {
15
+ contents.push(`// File: shared/${file}\n${await readFile(path.join(sharedDir, file), 'utf-8')}`);
16
+ }
17
+ return contents.join('\n\n');
18
+ }