sofia-cli 0.1.1 → 0.1.4

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 (136) hide show
  1. package/README.md +42 -20
  2. package/dist/infra/deploy.sh +193 -0
  3. package/dist/infra/gather-env.sh +211 -0
  4. package/dist/infra/infra/deploy.sh +193 -0
  5. package/dist/infra/infra/gather-env.sh +211 -0
  6. package/dist/infra/infra/main.bicep +90 -0
  7. package/dist/infra/infra/main.bicepparam +18 -0
  8. package/dist/infra/infra/resources.bicep +134 -0
  9. package/dist/infra/infra/teardown.sh +114 -0
  10. package/dist/infra/main.bicep +90 -0
  11. package/dist/infra/main.bicepparam +18 -0
  12. package/dist/infra/resources.bicep +134 -0
  13. package/dist/infra/teardown.sh +114 -0
  14. package/dist/src/cli/developCommand.js +0 -2
  15. package/dist/src/cli/index.js +8 -1
  16. package/dist/src/cli/workshopCommand.js +1 -1
  17. package/dist/src/develop/index.js +1 -1
  18. package/dist/src/develop/pocUtils.js +228 -0
  19. package/dist/src/develop/ralphLoop.js +3 -3
  20. package/dist/src/shared/data/cards.json +655 -670
  21. package/docs/architecture.md +2 -1
  22. package/package.json +5 -3
  23. package/src/cli/developCommand.ts +1 -3
  24. package/src/cli/index.ts +11 -1
  25. package/src/cli/workshopCommand.ts +21 -17
  26. package/src/develop/dynamicScaffolder.ts +36 -30
  27. package/src/develop/index.ts +13 -2
  28. package/src/develop/pocUtils.ts +296 -0
  29. package/src/develop/ralphLoop.ts +8 -28
  30. package/src/develop/templateRegistry.ts +19 -18
  31. package/src/shared/data/cards.json +655 -670
  32. package/tests/e2e/developE2e.spec.ts +3 -61
  33. package/tests/e2e/developFailureE2e.spec.ts +34 -38
  34. package/tests/integration/pocGithubMcp.spec.ts +29 -39
  35. package/tests/integration/pocLocalFallback.spec.ts +29 -39
  36. package/tests/integration/ralphLoopFlow.spec.ts +46 -66
  37. package/tests/integration/ralphLoopPartial.spec.ts +30 -37
  38. package/tests/unit/develop/githubMcpAdapter.spec.ts +0 -134
  39. package/tests/unit/develop/outputValidator.spec.ts +45 -21
  40. package/tests/unit/develop/ralphLoop.spec.ts +58 -94
  41. package/tsconfig.json +2 -1
  42. package/vitest.workspace.ts +5 -0
  43. package/dist/src/develop/pocScaffolder.js +0 -542
  44. package/dist/tests/e2e/developE2e.spec.js +0 -126
  45. package/dist/tests/e2e/developFailureE2e.spec.js +0 -247
  46. package/dist/tests/e2e/developPty.spec.js +0 -75
  47. package/dist/tests/e2e/discoveryWebSearchRelevance.spec.js +0 -84
  48. package/dist/tests/e2e/harness.spec.js +0 -83
  49. package/dist/tests/e2e/mcpLive.spec.js +0 -120
  50. package/dist/tests/e2e/newSession.e2e.spec.js +0 -177
  51. package/dist/tests/e2e/ralphLoopEnrichmentComparison.spec.js +0 -62
  52. package/dist/tests/e2e/workiqEnrichment.spec.js +0 -56
  53. package/dist/tests/e2e/zavaSimulation.spec.js +0 -452
  54. package/dist/tests/fixtures/test-fixture-project/src/add.js +0 -3
  55. package/dist/tests/fixtures/test-fixture-project/tests/failing.test.js +0 -6
  56. package/dist/tests/fixtures/test-fixture-project/tests/hanging.test.js +0 -8
  57. package/dist/tests/fixtures/test-fixture-project/tests/passing.test.js +0 -10
  58. package/dist/tests/fixtures/test-fixture-project/vitest.config.js +0 -6
  59. package/dist/tests/integration/autoStartConversation.spec.js +0 -138
  60. package/dist/tests/integration/defaultCommand.spec.js +0 -147
  61. package/dist/tests/integration/directCommandNonTty.spec.js +0 -224
  62. package/dist/tests/integration/directCommandTty.spec.js +0 -151
  63. package/dist/tests/integration/discoveryEnrichmentFlow.spec.js +0 -175
  64. package/dist/tests/integration/exportArtifacts.spec.js +0 -202
  65. package/dist/tests/integration/exportFallbackFlow.spec.js +0 -99
  66. package/dist/tests/integration/mcpDegradationFlow.spec.js +0 -190
  67. package/dist/tests/integration/mcpTransportFlow.spec.js +0 -139
  68. package/dist/tests/integration/newSessionFlow.spec.js +0 -343
  69. package/dist/tests/integration/pocGithubMcp.spec.js +0 -186
  70. package/dist/tests/integration/pocLocalFallback.spec.js +0 -171
  71. package/dist/tests/integration/pocScaffold.spec.js +0 -163
  72. package/dist/tests/integration/ralphLoopFlow.spec.js +0 -359
  73. package/dist/tests/integration/ralphLoopPartial.spec.js +0 -368
  74. package/dist/tests/integration/resumeAndBacktrack.spec.js +0 -247
  75. package/dist/tests/integration/spinnerLifecycle.spec.js +0 -220
  76. package/dist/tests/integration/summarizationFlow.spec.js +0 -115
  77. package/dist/tests/integration/testRunnerReal.spec.js +0 -52
  78. package/dist/tests/integration/webSearchAgent.spec.js +0 -128
  79. package/dist/tests/live/copilotSdkLive.spec.js +0 -107
  80. package/dist/tests/live/zavaFullWorkshop.spec.js +0 -392
  81. package/dist/tests/setup/loadEnv.js +0 -3
  82. package/dist/tests/unit/cli/developCommand.spec.js +0 -567
  83. package/dist/tests/unit/cli/directCommands.spec.js +0 -279
  84. package/dist/tests/unit/cli/envLoader.spec.js +0 -58
  85. package/dist/tests/unit/cli/ioContext.spec.js +0 -119
  86. package/dist/tests/unit/cli/preflight.spec.js +0 -108
  87. package/dist/tests/unit/cli/statusCommand.spec.js +0 -111
  88. package/dist/tests/unit/cli/workshopClientFallback.spec.js +0 -80
  89. package/dist/tests/unit/cli/workshopCommand.spec.js +0 -329
  90. package/dist/tests/unit/config/vitestEnvSetup.spec.js +0 -13
  91. package/dist/tests/unit/develop/checkpointState.spec.js +0 -315
  92. package/dist/tests/unit/develop/codeGenerator.spec.js +0 -355
  93. package/dist/tests/unit/develop/githubMcpAdapter.spec.js +0 -231
  94. package/dist/tests/unit/develop/mcpContextEnricher.spec.js +0 -433
  95. package/dist/tests/unit/develop/outputValidator.spec.js +0 -119
  96. package/dist/tests/unit/develop/pocScaffolder.spec.js +0 -353
  97. package/dist/tests/unit/develop/ralphLoop.spec.js +0 -1248
  98. package/dist/tests/unit/develop/templateRegistry.spec.js +0 -85
  99. package/dist/tests/unit/develop/testRunner.spec.js +0 -249
  100. package/dist/tests/unit/infraBicep.spec.js +0 -92
  101. package/dist/tests/unit/infraDeploy.spec.js +0 -82
  102. package/dist/tests/unit/infraTeardown.spec.js +0 -63
  103. package/dist/tests/unit/logging/logger.spec.js +0 -43
  104. package/dist/tests/unit/loop/conversationLoop.spec.js +0 -592
  105. package/dist/tests/unit/loop/phaseSummarizer.spec.js +0 -141
  106. package/dist/tests/unit/loop/streamingMarkdown.spec.js +0 -147
  107. package/dist/tests/unit/mcp/mcpManager.spec.js +0 -279
  108. package/dist/tests/unit/mcp/mcpTransport.spec.js +0 -529
  109. package/dist/tests/unit/mcp/retryPolicy.spec.js +0 -218
  110. package/dist/tests/unit/mcp/timeoutValidation.spec.js +0 -46
  111. package/dist/tests/unit/mcp/webSearch.spec.js +0 -567
  112. package/dist/tests/unit/phases/contextSummarizer.spec.js +0 -140
  113. package/dist/tests/unit/phases/discoveryEnricher.repeatCalls.spec.js +0 -93
  114. package/dist/tests/unit/phases/discoveryEnricher.spec.js +0 -411
  115. package/dist/tests/unit/phases/phaseExtractors.spec.js +0 -352
  116. package/dist/tests/unit/phases/phaseHandlers.spec.js +0 -425
  117. package/dist/tests/unit/prompts/promptLoader.spec.js +0 -118
  118. package/dist/tests/unit/schemas/pocSchemas.spec.js +0 -412
  119. package/dist/tests/unit/schemas/session.spec.js +0 -257
  120. package/dist/tests/unit/sessions/exportPaths.spec.js +0 -31
  121. package/dist/tests/unit/sessions/exportWriter.spec.js +0 -655
  122. package/dist/tests/unit/sessions/sessionManager.spec.js +0 -151
  123. package/dist/tests/unit/sessions/sessionStore.spec.js +0 -116
  124. package/dist/tests/unit/shared/activitySpinner.spec.js +0 -175
  125. package/dist/tests/unit/shared/cardsLoader.spec.js +0 -76
  126. package/dist/tests/unit/shared/copilotClient.spec.js +0 -155
  127. package/dist/tests/unit/shared/errorClassifier.spec.js +0 -131
  128. package/dist/tests/unit/shared/events.spec.js +0 -55
  129. package/dist/tests/unit/shared/markdownRenderer.spec.js +0 -35
  130. package/dist/tests/unit/shared/markdownRendererChunks.spec.js +0 -70
  131. package/dist/tests/unit/shared/tableRenderer.spec.js +0 -34
  132. package/dist/vitest.config.js +0 -14
  133. package/dist/vitest.live.config.js +0 -18
  134. package/src/develop/pocScaffolder.ts +0 -646
  135. package/tests/integration/pocScaffold.spec.ts +0 -220
  136. package/tests/unit/develop/pocScaffolder.spec.ts +0 -451
@@ -9,19 +9,18 @@
9
9
  * It validates the CLI plumbing, argument parsing, and file creation.
10
10
  */
11
11
  import { describe, it, expect, beforeEach, afterEach } from 'vitest';
12
- import { mkdtemp, rm, readFile, writeFile, mkdir } from 'node:fs/promises';
12
+ import { mkdtemp, rm, writeFile, mkdir } from 'node:fs/promises';
13
13
  import { join } from 'node:path';
14
14
  import { tmpdir } from 'node:os';
15
15
  import { createRequire } from 'node:module';
16
16
 
17
17
  import { buildCli } from '../../src/cli/index.js';
18
18
  import { validateSessionForDevelop } from '../../src/cli/developCommand.js';
19
- import { PocScaffolder } from '../../src/develop/pocScaffolder.js';
20
- import { validatePocOutput } from '../../src/develop/pocScaffolder.js';
21
19
  import type { WorkshopSession } from '../../src/shared/schemas/session.js';
22
20
 
23
21
  const require = createRequire(import.meta.url);
24
- const fixtureSession: WorkshopSession = require('../fixtures/completedSession.json') as WorkshopSession;
22
+ const fixtureSession: WorkshopSession =
23
+ require('../fixtures/completedSession.json') as WorkshopSession;
25
24
 
26
25
  describe('E2E: sofia dev command', () => {
27
26
  let workDir: string;
@@ -92,61 +91,4 @@ describe('E2E: sofia dev command', () => {
92
91
  expect(fixtureSession.plan!.milestones.length).toBeGreaterThan(0);
93
92
  });
94
93
  });
95
-
96
- describe('PocScaffolder with fixture session', () => {
97
- let outputDir: string;
98
-
99
- beforeEach(async () => {
100
- outputDir = await mkdtemp(join(tmpdir(), 'sofia-e2e-poc-'));
101
- });
102
-
103
- afterEach(async () => {
104
- await rm(outputDir, { recursive: true, force: true });
105
- });
106
-
107
- it('scaffolds valid PoC output from fixture session', async () => {
108
- const scaffolder = new PocScaffolder();
109
- const ctx = PocScaffolder.buildContext(fixtureSession, outputDir);
110
- await scaffolder.scaffold(ctx);
111
-
112
- const validation = await validatePocOutput(outputDir);
113
- expect(validation.valid).toBe(true);
114
- });
115
-
116
- it('generated package.json has correct project name from fixture', async () => {
117
- const scaffolder = new PocScaffolder();
118
- const ctx = PocScaffolder.buildContext(fixtureSession, outputDir);
119
- await scaffolder.scaffold(ctx);
120
-
121
- const pkgContent = await readFile(join(outputDir, 'package.json'), 'utf-8');
122
- const pkg = JSON.parse(pkgContent) as { name: string };
123
- expect(pkg.name).toBe('ai-powered-route-optimizer');
124
- });
125
-
126
- it('session JSON would be updated with poc state after loop', () => {
127
- // Verify the shape of poc state that RalphLoop would produce
128
- const expectedPocShape = {
129
- repoSource: 'local',
130
- iterations: expect.arrayContaining([
131
- expect.objectContaining({
132
- outcome: 'scaffold',
133
- }),
134
- ]),
135
- };
136
- // This test verifies the schema is correct
137
- const poc = {
138
- repoSource: 'local' as const,
139
- repoPath: outputDir,
140
- iterations: [
141
- {
142
- iteration: 1,
143
- startedAt: new Date().toISOString(),
144
- outcome: 'scaffold' as const,
145
- filesChanged: ['package.json', 'src/index.ts'],
146
- },
147
- ],
148
- };
149
- expect(poc).toMatchObject(expectedPocShape);
150
- });
151
- });
152
94
  });
@@ -12,7 +12,7 @@ import { tmpdir } from 'node:os';
12
12
  import { createRequire } from 'node:module';
13
13
 
14
14
  import { RalphLoop } from '../../src/develop/ralphLoop.js';
15
- import { PocScaffolder } from '../../src/develop/pocScaffolder.js';
15
+ import { generateDynamicScaffold } from '../../src/develop/dynamicScaffolder.js';
16
16
  import { TestRunner } from '../../src/develop/testRunner.js';
17
17
  import type { WorkshopSession } from '../../src/shared/schemas/session.js';
18
18
  import type { LoopIO } from '../../src/loop/conversationLoop.js';
@@ -40,8 +40,14 @@ vi.mock('node:child_process', async (importOriginal) => {
40
40
  };
41
41
  });
42
42
 
43
+ // Mock generateDynamicScaffold
44
+ vi.mock('../../src/develop/dynamicScaffolder.js', () => ({
45
+ generateDynamicScaffold: vi.fn(),
46
+ }));
47
+
43
48
  const require = createRequire(import.meta.url);
44
- const fixtureSession: WorkshopSession = require('../fixtures/completedSession.json') as WorkshopSession;
49
+ const fixtureSession: WorkshopSession =
50
+ require('../fixtures/completedSession.json') as WorkshopSession;
45
51
 
46
52
  describe('E2E: failure/recovery (T050)', () => {
47
53
  let tmpDir: string;
@@ -65,8 +71,12 @@ describe('E2E: failure/recovery (T050)', () => {
65
71
  return {
66
72
  writtenLines,
67
73
  activityLines,
68
- write: vi.fn((text: string) => { writtenLines.push(text); }),
69
- writeActivity: vi.fn((text: string) => { activityLines.push(text); }),
74
+ write: vi.fn((text: string) => {
75
+ writtenLines.push(text);
76
+ }),
77
+ writeActivity: vi.fn((text: string) => {
78
+ activityLines.push(text);
79
+ }),
70
80
  writeToolSummary: vi.fn(),
71
81
  readInput: vi.fn().mockResolvedValue(null),
72
82
  showDecisionGate: vi.fn(),
@@ -75,34 +85,26 @@ describe('E2E: failure/recovery (T050)', () => {
75
85
  };
76
86
  }
77
87
 
78
- function makeFakeScaffolder(outputDir: string): PocScaffolder {
79
- return {
80
- scaffold: vi.fn().mockImplementation(async () => {
81
- const { writeFile, mkdir } = await import('node:fs/promises');
82
- await mkdir(join(outputDir, 'src'), { recursive: true });
83
- await writeFile(join(outputDir, 'package.json'), JSON.stringify({
88
+ function setupDynamicScaffoldMock(outputDir: string): void {
89
+ vi.mocked(generateDynamicScaffold).mockImplementation(async () => {
90
+ const { writeFile, mkdir } = await import('node:fs/promises');
91
+ await mkdir(join(outputDir, 'src'), { recursive: true });
92
+ await writeFile(
93
+ join(outputDir, 'package.json'),
94
+ JSON.stringify({
84
95
  name: 'test-poc',
85
96
  scripts: { test: 'vitest run' },
86
97
  dependencies: {},
87
98
  devDependencies: {},
88
- }), 'utf-8');
89
- await writeFile(join(outputDir, 'src', 'index.ts'), 'export function main() {}', 'utf-8');
90
- return {
91
- createdFiles: ['package.json', 'src/index.ts'],
92
- skippedFiles: [],
93
- context: {
94
- projectName: 'test-poc',
95
- ideaTitle: 'Test',
96
- ideaDescription: 'Test',
97
- techStack: { language: 'TypeScript', runtime: 'Node.js 20', testRunner: 'npm test' },
98
- planSummary: 'Test',
99
- sessionId: fixtureSession.sessionId,
100
- outputDir,
101
- },
102
- };
103
- }),
104
- getTemplateFiles: () => [],
105
- } as unknown as PocScaffolder;
99
+ }),
100
+ 'utf-8',
101
+ );
102
+ await writeFile(join(outputDir, 'src', 'index.ts'), 'export function main() {}', 'utf-8');
103
+ return {
104
+ createdFiles: ['package.json', 'src/index.ts'],
105
+ techStack: { language: 'TypeScript', runtime: 'Node.js 20', testRunner: 'npm test' },
106
+ };
107
+ });
106
108
  }
107
109
 
108
110
  function makeAlwaysFailingClient(): CopilotClient {
@@ -134,7 +136,7 @@ describe('E2E: failure/recovery (T050)', () => {
134
136
 
135
137
  it('terminates with max-iterations when all tests keep failing', async () => {
136
138
  const io = makeIo();
137
- const scaffolder = makeFakeScaffolder(tmpDir);
139
+ setupDynamicScaffoldMock(tmpDir);
138
140
  const client = makeAlwaysFailingClient();
139
141
  const testRunner = makeAlwaysFailingTestRunner();
140
142
 
@@ -145,7 +147,6 @@ describe('E2E: failure/recovery (T050)', () => {
145
147
  outputDir: tmpDir,
146
148
  maxIterations: 2,
147
149
  testRunner,
148
- scaffolder,
149
150
  });
150
151
 
151
152
  const result = await ralph.run();
@@ -156,7 +157,7 @@ describe('E2E: failure/recovery (T050)', () => {
156
157
 
157
158
  it('verifies terminationReason=max-iterations in session state', async () => {
158
159
  const io = makeIo();
159
- const scaffolder = makeFakeScaffolder(tmpDir);
160
+ setupDynamicScaffoldMock(tmpDir);
160
161
  const client = makeAlwaysFailingClient();
161
162
  const testRunner = makeAlwaysFailingTestRunner();
162
163
 
@@ -167,7 +168,6 @@ describe('E2E: failure/recovery (T050)', () => {
167
168
  outputDir: tmpDir,
168
169
  maxIterations: 2,
169
170
  testRunner,
170
- scaffolder,
171
171
  });
172
172
 
173
173
  const result = await ralph.run();
@@ -177,7 +177,7 @@ describe('E2E: failure/recovery (T050)', () => {
177
177
 
178
178
  it('session has iteration history after failed loop', async () => {
179
179
  const io = makeIo();
180
- const scaffolder = makeFakeScaffolder(tmpDir);
180
+ setupDynamicScaffoldMock(tmpDir);
181
181
  const client = makeAlwaysFailingClient();
182
182
  const testRunner = makeAlwaysFailingTestRunner();
183
183
 
@@ -188,7 +188,6 @@ describe('E2E: failure/recovery (T050)', () => {
188
188
  outputDir: tmpDir,
189
189
  maxIterations: 2,
190
190
  testRunner,
191
- scaffolder,
192
191
  });
193
192
 
194
193
  const result = await ralph.run();
@@ -276,10 +275,7 @@ describe('E2E: failure/recovery (T050)', () => {
276
275
  });
277
276
 
278
277
  try {
279
- await developCommand(
280
- { session: fixtureSession.sessionId },
281
- { store, io: devIo, client },
282
- );
278
+ await developCommand({ session: fixtureSession.sessionId }, { store, io: devIo, client });
283
279
  } finally {
284
280
  RalphLoop.prototype.run = originalRun;
285
281
  }
@@ -13,7 +13,7 @@ import { createRequire } from 'node:module';
13
13
 
14
14
  import { RalphLoop } from '../../src/develop/ralphLoop.js';
15
15
  import { GitHubMcpAdapter } from '../../src/develop/githubMcpAdapter.js';
16
- import { PocScaffolder } from '../../src/develop/pocScaffolder.js';
16
+ import { generateDynamicScaffold } from '../../src/develop/dynamicScaffolder.js';
17
17
  import { TestRunner } from '../../src/develop/testRunner.js';
18
18
  import type { WorkshopSession } from '../../src/shared/schemas/session.js';
19
19
  import type { LoopIO } from '../../src/loop/conversationLoop.js';
@@ -42,6 +42,11 @@ vi.mock('node:child_process', async (importOriginal) => {
42
42
  };
43
43
  });
44
44
 
45
+ // Mock generateDynamicScaffold
46
+ vi.mock('../../src/develop/dynamicScaffolder.js', () => ({
47
+ generateDynamicScaffold: vi.fn(),
48
+ }));
49
+
45
50
  const require = createRequire(import.meta.url);
46
51
  const fixtureSession: WorkshopSession =
47
52
  require('../fixtures/completedSession.json') as WorkshopSession;
@@ -58,38 +63,26 @@ function makeIo(): LoopIO {
58
63
  };
59
64
  }
60
65
 
61
- function makeFakeScaffolder(outputDir: string): PocScaffolder {
62
- return {
63
- scaffold: vi.fn().mockImplementation(async () => {
64
- const { writeFile, mkdir } = await import('node:fs/promises');
65
- await mkdir(join(outputDir, 'src'), { recursive: true });
66
- await writeFile(
67
- join(outputDir, 'package.json'),
68
- JSON.stringify({
69
- name: 'test',
70
- scripts: { test: 'vitest run' },
71
- dependencies: {},
72
- devDependencies: {},
73
- }),
74
- 'utf-8',
75
- );
76
- await writeFile(join(outputDir, 'src', 'index.ts'), 'export function main() {}', 'utf-8');
77
- return {
78
- createdFiles: ['package.json', 'src/index.ts'],
79
- skippedFiles: [],
80
- context: {
81
- projectName: 'ai-powered-route-optimizer',
82
- ideaTitle: 'AI Route Optimizer',
83
- ideaDescription: 'Optimize routes',
84
- techStack: { language: 'TypeScript', runtime: 'Node.js 20', testRunner: 'npm test' },
85
- planSummary: 'Route optimization',
86
- sessionId: fixtureSession.sessionId,
87
- outputDir,
88
- },
89
- };
90
- }),
91
- getTemplateFiles: () => [],
92
- } as unknown as PocScaffolder;
66
+ function setupDynamicScaffoldMock(outputDir: string): void {
67
+ vi.mocked(generateDynamicScaffold).mockImplementation(async () => {
68
+ const { writeFile, mkdir } = await import('node:fs/promises');
69
+ await mkdir(join(outputDir, 'src'), { recursive: true });
70
+ await writeFile(
71
+ join(outputDir, 'package.json'),
72
+ JSON.stringify({
73
+ name: 'test',
74
+ scripts: { test: 'vitest run' },
75
+ dependencies: {},
76
+ devDependencies: {},
77
+ }),
78
+ 'utf-8',
79
+ );
80
+ await writeFile(join(outputDir, 'src', 'index.ts'), 'export function main() {}', 'utf-8');
81
+ return {
82
+ createdFiles: ['package.json', 'src/index.ts'],
83
+ techStack: { language: 'TypeScript', runtime: 'Node.js 20', testRunner: 'npm test' },
84
+ };
85
+ });
93
86
  }
94
87
 
95
88
  function makePassingClient(): CopilotClient {
@@ -137,7 +130,7 @@ describe.skip('RalphLoop — GitHub MCP flow (T034)', () => {
137
130
  const io = makeIo();
138
131
  const client = makePassingClient();
139
132
  const testRunner = makePassingTestRunner();
140
- const scaffolder = makeFakeScaffolder(tmpDir);
133
+ setupDynamicScaffoldMock(tmpDir);
141
134
 
142
135
  // Available GitHub MCP
143
136
  const availableMcpManager: McpManager = {
@@ -152,7 +145,6 @@ describe.skip('RalphLoop — GitHub MCP flow (T034)', () => {
152
145
  outputDir: tmpDir,
153
146
  maxIterations: 3,
154
147
  testRunner,
155
- scaffolder,
156
148
  });
157
149
 
158
150
  const result = await ralph.run();
@@ -164,7 +156,7 @@ describe.skip('RalphLoop — GitHub MCP flow (T034)', () => {
164
156
  const io = makeIo();
165
157
  const client = makePassingClient();
166
158
  const testRunner = makePassingTestRunner();
167
- const scaffolder = makeFakeScaffolder(tmpDir);
159
+ setupDynamicScaffoldMock(tmpDir);
168
160
 
169
161
  const availableMcpManager: McpManager = {
170
162
  isAvailable: (name: string) => name === 'github',
@@ -180,7 +172,6 @@ describe.skip('RalphLoop — GitHub MCP flow (T034)', () => {
180
172
  outputDir: tmpDir,
181
173
  maxIterations: 3,
182
174
  testRunner,
183
- scaffolder,
184
175
  });
185
176
 
186
177
  const result = await ralph.run();
@@ -196,7 +187,7 @@ describe.skip('RalphLoop — GitHub MCP flow (T034)', () => {
196
187
  const io = makeIo();
197
188
  const client = makePassingClient();
198
189
  const testRunner = makePassingTestRunner();
199
- const scaffolder = makeFakeScaffolder(tmpDir);
190
+ setupDynamicScaffoldMock(tmpDir);
200
191
 
201
192
  const availableMcpManager: McpManager = {
202
193
  isAvailable: (name: string) => name === 'github',
@@ -211,7 +202,6 @@ describe.skip('RalphLoop — GitHub MCP flow (T034)', () => {
211
202
  outputDir: tmpDir,
212
203
  maxIterations: 3,
213
204
  testRunner,
214
- scaffolder,
215
205
  });
216
206
 
217
207
  await ralph.run();
@@ -12,7 +12,7 @@ import { tmpdir } from 'node:os';
12
12
  import { createRequire } from 'node:module';
13
13
 
14
14
  import { RalphLoop } from '../../src/develop/ralphLoop.js';
15
- import { PocScaffolder } from '../../src/develop/pocScaffolder.js';
15
+ import { generateDynamicScaffold } from '../../src/develop/dynamicScaffolder.js';
16
16
  import { TestRunner } from '../../src/develop/testRunner.js';
17
17
  import type { WorkshopSession } from '../../src/shared/schemas/session.js';
18
18
  import type { LoopIO } from '../../src/loop/conversationLoop.js';
@@ -40,6 +40,11 @@ vi.mock('node:child_process', async (importOriginal) => {
40
40
  };
41
41
  });
42
42
 
43
+ // Mock generateDynamicScaffold
44
+ vi.mock('../../src/develop/dynamicScaffolder.js', () => ({
45
+ generateDynamicScaffold: vi.fn(),
46
+ }));
47
+
43
48
  const require = createRequire(import.meta.url);
44
49
  const fixtureSession: WorkshopSession =
45
50
  require('../fixtures/completedSession.json') as WorkshopSession;
@@ -60,38 +65,26 @@ function makeIo(): LoopIO & { activityMessages: string[] } {
60
65
  };
61
66
  }
62
67
 
63
- function makeFakeScaffolder(outputDir: string): PocScaffolder {
64
- return {
65
- scaffold: vi.fn().mockImplementation(async () => {
66
- const { writeFile, mkdir } = await import('node:fs/promises');
67
- await mkdir(join(outputDir, 'src'), { recursive: true });
68
- await writeFile(
69
- join(outputDir, 'package.json'),
70
- JSON.stringify({
71
- name: 'test',
72
- scripts: { test: 'vitest run' },
73
- dependencies: {},
74
- devDependencies: {},
75
- }),
76
- 'utf-8',
77
- );
78
- await writeFile(join(outputDir, 'src', 'index.ts'), 'export function main() {}', 'utf-8');
79
- return {
80
- createdFiles: ['package.json', 'src/index.ts'],
81
- skippedFiles: [],
82
- context: {
83
- projectName: 'test',
84
- ideaTitle: 'Test',
85
- ideaDescription: 'Test',
86
- techStack: { language: 'TypeScript', runtime: 'Node.js 20', testRunner: 'npm test' },
87
- planSummary: 'Test',
88
- sessionId: fixtureSession.sessionId,
89
- outputDir,
90
- },
91
- };
92
- }),
93
- getTemplateFiles: () => [],
94
- } as unknown as PocScaffolder;
68
+ function setupDynamicScaffoldMock(outputDir: string): void {
69
+ vi.mocked(generateDynamicScaffold).mockImplementation(async () => {
70
+ const { writeFile, mkdir } = await import('node:fs/promises');
71
+ await mkdir(join(outputDir, 'src'), { recursive: true });
72
+ await writeFile(
73
+ join(outputDir, 'package.json'),
74
+ JSON.stringify({
75
+ name: 'test',
76
+ scripts: { test: 'vitest run' },
77
+ dependencies: {},
78
+ devDependencies: {},
79
+ }),
80
+ 'utf-8',
81
+ );
82
+ await writeFile(join(outputDir, 'src', 'index.ts'), 'export function main() {}', 'utf-8');
83
+ return {
84
+ createdFiles: ['package.json', 'src/index.ts'],
85
+ techStack: { language: 'TypeScript', runtime: 'Node.js 20', testRunner: 'npm test' },
86
+ };
87
+ });
95
88
  }
96
89
 
97
90
  function makePassingClient(): CopilotClient {
@@ -137,7 +130,7 @@ describe('RalphLoop — local fallback (T033)', () => {
137
130
  const io = makeIo();
138
131
  const client = makePassingClient();
139
132
  const testRunner = makePassingTestRunner();
140
- const scaffolder = makeFakeScaffolder(tmpDir);
133
+ setupDynamicScaffoldMock(tmpDir);
141
134
 
142
135
  const ralph = new RalphLoop({
143
136
  client,
@@ -146,7 +139,6 @@ describe('RalphLoop — local fallback (T033)', () => {
146
139
  outputDir: tmpDir,
147
140
  maxIterations: 3,
148
141
  testRunner,
149
- scaffolder,
150
142
  });
151
143
 
152
144
  const result = await ralph.run();
@@ -160,7 +152,7 @@ describe('RalphLoop — local fallback (T033)', () => {
160
152
  const io = makeIo();
161
153
  const client = makePassingClient();
162
154
  const testRunner = makePassingTestRunner();
163
- const scaffolder = makeFakeScaffolder(tmpDir);
155
+ setupDynamicScaffoldMock(tmpDir);
164
156
 
165
157
  const ralph = new RalphLoop({
166
158
  client,
@@ -169,7 +161,6 @@ describe('RalphLoop — local fallback (T033)', () => {
169
161
  outputDir: tmpDir,
170
162
  maxIterations: 3,
171
163
  testRunner,
172
- scaffolder,
173
164
  });
174
165
 
175
166
  const result = await ralph.run();
@@ -181,7 +172,7 @@ describe('RalphLoop — local fallback (T033)', () => {
181
172
  const io = makeIo();
182
173
  const client = makePassingClient();
183
174
  const testRunner = makePassingTestRunner();
184
- const scaffolder = makeFakeScaffolder(tmpDir);
175
+ setupDynamicScaffoldMock(tmpDir);
185
176
 
186
177
  const ralph = new RalphLoop({
187
178
  client,
@@ -190,7 +181,6 @@ describe('RalphLoop — local fallback (T033)', () => {
190
181
  outputDir: tmpDir,
191
182
  maxIterations: 3,
192
183
  testRunner,
193
- scaffolder,
194
184
  });
195
185
 
196
186
  await ralph.run();