sofia-cli 0.1.2 → 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 +8 -27
  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 -328
  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
@@ -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';
@@ -42,14 +42,19 @@ vi.mock('node:child_process', async (importOriginal) => {
42
42
  });
43
43
 
44
44
  // Mock validatePocOutput to always pass in integration tests
45
- vi.mock('../../src/develop/pocScaffolder.js', async (importOriginal) => {
46
- const actual = await importOriginal<typeof import('../../src/develop/pocScaffolder.js')>();
45
+ vi.mock('../../src/develop/pocUtils.js', async (importOriginal) => {
46
+ const actual = await importOriginal<typeof import('../../src/develop/pocUtils.js')>();
47
47
  return {
48
48
  ...actual,
49
49
  validatePocOutput: vi.fn().mockResolvedValue({ valid: true, missingFiles: [], errors: [] }),
50
50
  };
51
51
  });
52
52
 
53
+ // Mock generateDynamicScaffold to create minimal files for tests
54
+ vi.mock('../../src/develop/dynamicScaffolder.js', () => ({
55
+ generateDynamicScaffold: vi.fn(),
56
+ }));
57
+
53
58
  const require = createRequire(import.meta.url);
54
59
  const fixtureSession: WorkshopSession =
55
60
  require('../fixtures/completedSession.json') as WorkshopSession;
@@ -68,48 +73,36 @@ function makeIo(): LoopIO {
68
73
  };
69
74
  }
70
75
 
71
- function makeFakeScaffolder(outputDir: string): PocScaffolder {
72
- return {
73
- scaffold: vi.fn().mockImplementation(async () => {
74
- const { writeFile, mkdir } = await import('node:fs/promises');
75
- await mkdir(join(outputDir, 'src'), { recursive: true });
76
- await mkdir(join(outputDir, 'tests'), { recursive: true });
77
- await writeFile(
78
- join(outputDir, 'package.json'),
79
- JSON.stringify({
80
- name: 'route-optimizer-poc',
81
- scripts: { test: 'vitest run' },
82
- dependencies: {},
83
- devDependencies: { vitest: '^3.0.0' },
84
- }),
85
- 'utf-8',
86
- );
87
- await writeFile(
88
- join(outputDir, 'src', 'index.ts'),
89
- '// TODO: implement\nexport function optimize() { return []; }',
90
- 'utf-8',
91
- );
92
- await writeFile(
93
- join(outputDir, 'tests', 'index.test.ts'),
94
- 'import { describe, it, expect } from "vitest";\nimport { optimize } from "../src/index.js";\ndescribe("optimizer", () => { it("should return stops", () => { expect(optimize().length).toBeGreaterThan(0); }); });',
95
- 'utf-8',
96
- );
97
- return {
98
- createdFiles: ['package.json', 'src/index.ts', 'tests/index.test.ts'],
99
- skippedFiles: [],
100
- context: {
101
- projectName: 'route-optimizer-poc',
102
- ideaTitle: 'AI-Powered Route Optimizer',
103
- ideaDescription: 'Optimize routes',
104
- techStack: { language: 'TypeScript', runtime: 'Node.js 20', testRunner: 'npm test' },
105
- planSummary: 'Route optimization',
106
- sessionId: fixtureSession.sessionId,
107
- outputDir,
108
- },
109
- };
110
- }),
111
- getTemplateFiles: () => ['package.json', 'src/index.ts', 'tests/index.test.ts'],
112
- } as unknown as PocScaffolder;
76
+ function setupDynamicScaffoldMock(outputDir: string): void {
77
+ vi.mocked(generateDynamicScaffold).mockImplementation(async () => {
78
+ const { writeFile, mkdir } = await import('node:fs/promises');
79
+ await mkdir(join(outputDir, 'src'), { recursive: true });
80
+ await mkdir(join(outputDir, 'tests'), { recursive: true });
81
+ await writeFile(
82
+ join(outputDir, 'package.json'),
83
+ JSON.stringify({
84
+ name: 'route-optimizer-poc',
85
+ scripts: { test: 'vitest run' },
86
+ dependencies: {},
87
+ devDependencies: { vitest: '^3.0.0' },
88
+ }),
89
+ 'utf-8',
90
+ );
91
+ await writeFile(
92
+ join(outputDir, 'src', 'index.ts'),
93
+ '// TODO: implement\nexport function optimize() { return []; }',
94
+ 'utf-8',
95
+ );
96
+ await writeFile(
97
+ join(outputDir, 'tests', 'index.test.ts'),
98
+ 'import { describe, it, expect } from "vitest";\nimport { optimize } from "../src/index.js";\ndescribe("optimizer", () => { it("should return stops", () => { expect(optimize().length).toBeGreaterThan(0); }); });',
99
+ 'utf-8',
100
+ );
101
+ return {
102
+ createdFiles: ['package.json', 'src/index.ts', 'tests/index.test.ts'],
103
+ techStack: { language: 'TypeScript', runtime: 'Node.js 20', testRunner: 'npm test' },
104
+ };
105
+ });
113
106
  }
114
107
 
115
108
  // ── SC-002-003: Iterative refinement test ────────────────────────────────────
@@ -128,7 +121,7 @@ describe('RalphLoop integration — iterative refinement (SC-002-003)', () => {
128
121
 
129
122
  it('scaffold → fail tests → LLM fix → tests pass → success', async () => {
130
123
  const io = makeIo();
131
- const scaffolder = makeFakeScaffolder(tmpDir);
124
+ setupDynamicScaffoldMock(tmpDir);
132
125
 
133
126
  // Test runner: fails first, passes second
134
127
  let testCallCount = 0;
@@ -200,7 +193,6 @@ describe('RalphLoop integration — iterative refinement (SC-002-003)', () => {
200
193
  outputDir: tmpDir,
201
194
  maxIterations: 5,
202
195
  testRunner,
203
- scaffolder,
204
196
  onSessionUpdate: async (session) => {
205
197
  sessionUpdates.push({ ...session });
206
198
  },
@@ -231,7 +223,7 @@ describe('RalphLoop integration — iterative refinement (SC-002-003)', () => {
231
223
 
232
224
  it('verifies failing tests are passed to LLM in iteration prompt (SC-002-003)', async () => {
233
225
  const io = makeIo();
234
- const scaffolder = makeFakeScaffolder(tmpDir);
226
+ setupDynamicScaffoldMock(tmpDir);
235
227
 
236
228
  let testCallCount = 0;
237
229
  const testRunner: TestRunner = {
@@ -287,7 +279,6 @@ describe('RalphLoop integration — iterative refinement (SC-002-003)', () => {
287
279
  outputDir: tmpDir,
288
280
  maxIterations: 3,
289
281
  testRunner,
290
- scaffolder,
291
282
  });
292
283
 
293
284
  await ralph.run();
@@ -314,9 +305,8 @@ describe('TODO tracking integration (T074)', () => {
314
305
  it('writes TODO counts to .sofia-metadata.json during scaffold and updates after iteration', async () => {
315
306
  const { writeFile, mkdir } = await import('node:fs/promises');
316
307
 
317
- // Create a scaffolder that writes files with TODO markers
318
- const todoScaffolder: PocScaffolder = {
319
- scaffold: vi.fn().mockImplementation(async () => {
308
+ function setupTodoScaffoldMock(): void {
309
+ vi.mocked(generateDynamicScaffold).mockImplementation(async () => {
320
310
  await mkdir(join(tmpDir, 'src'), { recursive: true });
321
311
  await mkdir(join(tmpDir, 'tests'), { recursive: true });
322
312
  await writeFile(
@@ -344,20 +334,11 @@ describe('TODO tracking integration (T074)', () => {
344
334
  );
345
335
  return {
346
336
  createdFiles: ['package.json', 'src/index.ts', '.sofia-metadata.json'],
347
- skippedFiles: [],
348
- context: {
349
- projectName: 'todo-test-poc',
350
- ideaTitle: 'Test',
351
- ideaDescription: 'Test',
352
- techStack: { language: 'TypeScript', runtime: 'Node.js 20', testRunner: 'npm test' },
353
- planSummary: 'Test',
354
- sessionId: fixtureSession.sessionId,
355
- outputDir: tmpDir,
356
- },
337
+ techStack: { language: 'TypeScript', runtime: 'Node.js 20', testRunner: 'npm test' },
357
338
  };
358
- }),
359
- getTemplateFiles: () => ['package.json', 'src/index.ts'],
360
- } as unknown as PocScaffolder;
339
+ });
340
+ }
341
+ setupTodoScaffoldMock();
361
342
 
362
343
  // Test runner that fails on first call (triggering TODO rescan), then passes
363
344
  let runCount = 0;
@@ -412,7 +393,6 @@ describe('TODO tracking integration (T074)', () => {
412
393
  outputDir: tmpDir,
413
394
  maxIterations: 3,
414
395
  testRunner: failThenPassRunner,
415
- scaffolder: todoScaffolder,
416
396
  });
417
397
 
418
398
  await ralph.run();
@@ -13,7 +13,7 @@ import { tmpdir } from 'node:os';
13
13
  import { createRequire } from 'node:module';
14
14
 
15
15
  import { RalphLoop } from '../../src/develop/ralphLoop.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,8 +42,14 @@ 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
- const fixtureSession: WorkshopSession = require('../fixtures/completedSession.json') as WorkshopSession;
51
+ const fixtureSession: WorkshopSession =
52
+ require('../fixtures/completedSession.json') as WorkshopSession;
47
53
 
48
54
  function makeIo(): LoopIO {
49
55
  return {
@@ -57,34 +63,26 @@ function makeIo(): LoopIO {
57
63
  };
58
64
  }
59
65
 
60
- function makeFakeScaffolder(outputDir: string): PocScaffolder {
61
- return {
62
- scaffold: vi.fn().mockImplementation(async () => {
63
- const { writeFile, mkdir } = await import('node:fs/promises');
64
- await mkdir(join(outputDir, 'src'), { recursive: true });
65
- await writeFile(join(outputDir, 'package.json'), JSON.stringify({
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({
66
73
  name: 'test-poc',
67
74
  scripts: { test: 'vitest run' },
68
75
  dependencies: {},
69
76
  devDependencies: {},
70
- }), 'utf-8');
71
- await writeFile(join(outputDir, 'src', 'index.ts'), 'export function main() {}', 'utf-8');
72
- return {
73
- createdFiles: ['package.json', 'src/index.ts'],
74
- skippedFiles: [],
75
- context: {
76
- projectName: 'test-poc',
77
- ideaTitle: 'Test',
78
- ideaDescription: 'Test',
79
- techStack: { language: 'TypeScript', runtime: 'Node.js 20', testRunner: 'npm test' },
80
- planSummary: 'Test',
81
- sessionId: fixtureSession.sessionId,
82
- outputDir,
83
- },
84
- };
85
- }),
86
- getTemplateFiles: () => [],
87
- } as unknown as PocScaffolder;
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
+ });
88
86
  }
89
87
 
90
88
  function makeClient(): CopilotClient {
@@ -119,7 +117,7 @@ describe('RalphLoop integration — partial/failed outcomes', () => {
119
117
  it('sets finalStatus=partial when some tests pass at max-iterations', async () => {
120
118
  const io = makeIo();
121
119
  const client = makeClient();
122
- const scaffolder = makeFakeScaffolder(tmpDir);
120
+ setupDynamicScaffoldMock(tmpDir);
123
121
 
124
122
  const testRunner: TestRunner = {
125
123
  run: vi.fn().mockResolvedValue({
@@ -140,7 +138,6 @@ describe('RalphLoop integration — partial/failed outcomes', () => {
140
138
  outputDir: tmpDir,
141
139
  maxIterations: 2,
142
140
  testRunner,
143
- scaffolder,
144
141
  });
145
142
 
146
143
  const result = await ralph.run();
@@ -154,7 +151,7 @@ describe('RalphLoop integration — partial/failed outcomes', () => {
154
151
  it('sets finalStatus=failed when no tests pass at max-iterations', async () => {
155
152
  const io = makeIo();
156
153
  const client = makeClient();
157
- const scaffolder = makeFakeScaffolder(tmpDir);
154
+ setupDynamicScaffoldMock(tmpDir);
158
155
 
159
156
  const testRunner: TestRunner = {
160
157
  run: vi.fn().mockResolvedValue({
@@ -178,7 +175,6 @@ describe('RalphLoop integration — partial/failed outcomes', () => {
178
175
  outputDir: tmpDir,
179
176
  maxIterations: 2,
180
177
  testRunner,
181
- scaffolder,
182
178
  });
183
179
 
184
180
  const result = await ralph.run();
@@ -189,7 +185,7 @@ describe('RalphLoop integration — partial/failed outcomes', () => {
189
185
 
190
186
  it('records error iteration when LLM returns empty response, continues loop', async () => {
191
187
  const io = makeIo();
192
- const scaffolder = makeFakeScaffolder(tmpDir);
188
+ setupDynamicScaffoldMock(tmpDir);
193
189
 
194
190
  let testCallCount = 0;
195
191
  const testRunner: TestRunner = {
@@ -254,7 +250,6 @@ describe('RalphLoop integration — partial/failed outcomes', () => {
254
250
  outputDir: tmpDir,
255
251
  maxIterations: 5,
256
252
  testRunner,
257
- scaffolder,
258
253
  });
259
254
 
260
255
  const result = await ralph.run();
@@ -272,7 +267,7 @@ describe('RalphLoop integration — partial/failed outcomes', () => {
272
267
  it('records terminationReason in session poc state', async () => {
273
268
  const io = makeIo();
274
269
  const client = makeClient();
275
- const scaffolder = makeFakeScaffolder(tmpDir);
270
+ setupDynamicScaffoldMock(tmpDir);
276
271
 
277
272
  const testRunner: TestRunner = {
278
273
  run: vi.fn().mockResolvedValue({
@@ -293,7 +288,6 @@ describe('RalphLoop integration — partial/failed outcomes', () => {
293
288
  outputDir: tmpDir,
294
289
  maxIterations: 2,
295
290
  testRunner,
296
- scaffolder,
297
291
  });
298
292
 
299
293
  const result = await ralph.run();
@@ -381,7 +375,7 @@ describe('resume from interrupted session', () => {
381
375
  }),
382
376
  } as unknown as TestRunner;
383
377
 
384
- const scaffolder = makeFakeScaffolder(tmpDir);
378
+ setupDynamicScaffoldMock(tmpDir);
385
379
 
386
380
  const ralph = new RalphLoop({
387
381
  client: passingClient,
@@ -390,7 +384,6 @@ describe('resume from interrupted session', () => {
390
384
  outputDir: tmpDir,
391
385
  maxIterations: 10,
392
386
  testRunner,
393
- scaffolder,
394
387
  checkpoint: {
395
388
  hasPriorRun: true,
396
389
  completedIterations: 2,
@@ -405,7 +398,7 @@ describe('resume from interrupted session', () => {
405
398
  const result = await ralph.run();
406
399
 
407
400
  // Should have seeded from prior iterations and continued
408
- // Note: finalStatus may be 'partial' since makeFakeScaffolder doesn't create all
401
+ // Note: finalStatus may be 'partial' since setupDynamicScaffoldMock doesn't create all
409
402
  // files required by validatePocOutput (README.md, tsconfig.json, etc.)
410
403
  expect(['success', 'partial']).toContain(result.finalStatus);
411
404
  // Total iterations should include the 2 prior + scaffold + tests-passing
@@ -3,7 +3,6 @@
3
3
  *
4
4
  * Verifies:
5
5
  * - isAvailable() checks McpManager.isAvailable('github')
6
- * - createRepository() calls MCP tool
7
6
  * - pushFiles() commits and pushes
8
7
  * - Graceful fallback returns { available: false } when MCP unavailable
9
8
  */
@@ -48,82 +47,6 @@ describe('GitHubMcpAdapter', () => {
48
47
  });
49
48
  });
50
49
 
51
- describe('createRepository()', () => {
52
- it('calls mcpManager.callTool with create_repository and returns parsed result', async () => {
53
- const callTool = vi.fn().mockResolvedValue({
54
- html_url: 'https://github.com/acme/poc-route-optimizer',
55
- name: 'poc-route-optimizer',
56
- });
57
- const mgr = makeMcpManager(true, callTool);
58
- const adapter = new GitHubMcpAdapter(mgr);
59
- const result = await adapter.createRepository({
60
- name: 'poc-route-optimizer',
61
- description: 'AI route optimization PoC',
62
- });
63
-
64
- expect(result.available).toBe(true);
65
- if (result.available) {
66
- expect(result.repoUrl).toBe('https://github.com/acme/poc-route-optimizer');
67
- expect(result.repoName).toBe('poc-route-optimizer');
68
- }
69
- expect(callTool).toHaveBeenCalledWith(
70
- 'github',
71
- 'create_repository',
72
- {
73
- name: 'poc-route-optimizer',
74
- description: 'AI route optimization PoC',
75
- private: true,
76
- },
77
- { timeoutMs: 60_000 },
78
- );
79
- });
80
-
81
- it('returns { available: false } when GitHub MCP is unavailable', async () => {
82
- const adapter = new GitHubMcpAdapter(makeMcpManager(false));
83
- const result = await adapter.createRepository({
84
- name: 'poc-route-optimizer',
85
- });
86
-
87
- expect(result.available).toBe(false);
88
- if (!result.available) {
89
- expect(result.reason).toBeDefined();
90
- }
91
- });
92
-
93
- it('returns { available: false } when callTool throws', async () => {
94
- const callTool = vi.fn().mockRejectedValue(new Error('MCP callTool not yet wired'));
95
- const adapter = new GitHubMcpAdapter(makeMcpManager(true, callTool));
96
- const result = await adapter.createRepository({ name: 'poc-fail' });
97
-
98
- expect(result.available).toBe(false);
99
- if (!result.available) {
100
- expect(result.reason).toContain('not yet wired');
101
- }
102
- });
103
-
104
- it('stores repo URL after successful creation', async () => {
105
- const callTool = vi.fn().mockResolvedValue({
106
- html_url: 'https://github.com/acme/my-poc',
107
- name: 'my-poc',
108
- });
109
- const adapter = new GitHubMcpAdapter(makeMcpManager(true, callTool));
110
- await adapter.createRepository({ name: 'my-poc' });
111
-
112
- expect(adapter.getRepoUrl()).toBe('https://github.com/acme/my-poc');
113
- });
114
-
115
- it('returns { available: false } when response has no URL', async () => {
116
- const callTool = vi.fn().mockResolvedValue({ id: 123 });
117
- const adapter = new GitHubMcpAdapter(makeMcpManager(true, callTool));
118
- const result = await adapter.createRepository({ name: 'no-url' });
119
-
120
- expect(result.available).toBe(false);
121
- if (!result.available) {
122
- expect(result.reason).toContain('missing repository URL');
123
- }
124
- });
125
- });
126
-
127
50
  describe('pushFiles()', () => {
128
51
  it('calls mcpManager.callTool with push_files and returns commitSha', async () => {
129
52
  const callTool = vi.fn().mockResolvedValue({ sha: 'abc123ff' });
@@ -203,63 +126,6 @@ describe('GitHubMcpAdapter', () => {
203
126
  const adapter = new GitHubMcpAdapter(makeMcpManager(true));
204
127
  expect(adapter.getRepoUrl()).toBeUndefined();
205
128
  });
206
-
207
- it('returns URL after successful createRepository', async () => {
208
- const callTool = vi.fn().mockResolvedValue({
209
- html_url: 'https://github.com/acme/test-poc',
210
- name: 'test-poc',
211
- });
212
- const adapter = new GitHubMcpAdapter(makeMcpManager(true, callTool));
213
- await adapter.createRepository({ name: 'test-poc' });
214
- expect(adapter.getRepoUrl()).toBe('https://github.com/acme/test-poc');
215
- });
216
- });
217
-
218
- // ── T008: Contract tests per contracts/github-adapter.md ────────────────
219
-
220
- describe('createRepository() — contract: URL fallback chain', () => {
221
- it('falls back to response.url when html_url is missing', async () => {
222
- const callTool = vi.fn().mockResolvedValue({
223
- url: 'https://api.github.com/repos/acme/poc',
224
- name: 'poc',
225
- });
226
- const adapter = new GitHubMcpAdapter(makeMcpManager(true, callTool));
227
- const result = await adapter.createRepository({ name: 'poc' });
228
-
229
- expect(result.available).toBe(true);
230
- if (result.available) {
231
- expect(result.repoUrl).toBe('https://api.github.com/repos/acme/poc');
232
- }
233
- });
234
-
235
- it('falls back to response.clone_url when html_url and url are missing', async () => {
236
- const callTool = vi.fn().mockResolvedValue({
237
- clone_url: 'https://github.com/acme/poc.git',
238
- name: 'poc',
239
- });
240
- const adapter = new GitHubMcpAdapter(makeMcpManager(true, callTool));
241
- const result = await adapter.createRepository({ name: 'poc' });
242
-
243
- expect(result.available).toBe(true);
244
- if (result.available) {
245
- expect(result.repoUrl).toBe('https://github.com/acme/poc.git');
246
- }
247
- });
248
-
249
- it('extracts repoName from response.full_name as fallback', async () => {
250
- const callTool = vi.fn().mockResolvedValue({
251
- html_url: 'https://github.com/acme/poc',
252
- full_name: 'acme/poc',
253
- });
254
- const adapter = new GitHubMcpAdapter(makeMcpManager(true, callTool));
255
- const result = await adapter.createRepository({ name: 'poc' });
256
-
257
- expect(result.available).toBe(true);
258
- if (result.available) {
259
- // name not in response → full_name fallback returns 'acme/poc'
260
- expect(result.repoName).toBe('acme/poc');
261
- }
262
- });
263
129
  });
264
130
 
265
131
  describe('pushFiles() — contract: commitSha extraction', () => {
@@ -8,7 +8,7 @@ import { mkdtemp, rm, writeFile, mkdir } from 'node:fs/promises';
8
8
  import { join } from 'node:path';
9
9
  import { tmpdir } from 'node:os';
10
10
 
11
- import { validatePocOutput } from '../../../src/develop/pocScaffolder.js';
11
+ import { validatePocOutput } from '../../../src/develop/pocUtils.js';
12
12
 
13
13
  describe('validatePocOutput', () => {
14
14
  let tmpDir: string;
@@ -24,24 +24,44 @@ describe('validatePocOutput', () => {
24
24
  async function createCompleteScaffold(): Promise<void> {
25
25
  await mkdir(join(tmpDir, 'src'), { recursive: true });
26
26
  await mkdir(join(tmpDir, 'tests'), { recursive: true });
27
- await writeFile(join(tmpDir, 'package.json'), JSON.stringify({
28
- name: 'test-poc',
29
- version: '0.1.0',
30
- scripts: { test: 'vitest run', build: 'tsc', start: 'node dist/index.js' },
31
- dependencies: {},
32
- devDependencies: { vitest: '^3.0.0', typescript: '^5.0.0' },
33
- }), 'utf-8');
27
+ await writeFile(
28
+ join(tmpDir, 'package.json'),
29
+ JSON.stringify({
30
+ name: 'test-poc',
31
+ version: '0.1.0',
32
+ scripts: { test: 'vitest run', build: 'tsc', start: 'node dist/index.js' },
33
+ dependencies: {},
34
+ devDependencies: { vitest: '^3.0.0', typescript: '^5.0.0' },
35
+ }),
36
+ 'utf-8',
37
+ );
34
38
  await writeFile(join(tmpDir, 'README.md'), '# Test PoC\n\nA test proof of concept.', 'utf-8');
35
- await writeFile(join(tmpDir, 'tsconfig.json'), JSON.stringify({
36
- compilerOptions: { target: 'ES2022', module: 'Node16', strict: true },
37
- }), 'utf-8');
39
+ await writeFile(
40
+ join(tmpDir, 'tsconfig.json'),
41
+ JSON.stringify({
42
+ compilerOptions: { target: 'ES2022', module: 'Node16', strict: true },
43
+ }),
44
+ 'utf-8',
45
+ );
38
46
  await writeFile(join(tmpDir, '.gitignore'), 'node_modules/\ndist/\ncoverage/\n', 'utf-8');
39
- await writeFile(join(tmpDir, '.sofia-metadata.json'), JSON.stringify({
40
- sessionId: 'test-001',
41
- featureSpec: '002-poc-generation',
42
- }), 'utf-8');
43
- await writeFile(join(tmpDir, 'src', 'index.ts'), 'export function main() { return "ok"; }', 'utf-8');
44
- await writeFile(join(tmpDir, 'tests', 'index.test.ts'), 'import { test, expect } from "vitest"; test("works", () => { expect(true).toBe(true); });', 'utf-8');
47
+ await writeFile(
48
+ join(tmpDir, '.sofia-metadata.json'),
49
+ JSON.stringify({
50
+ sessionId: 'test-001',
51
+ featureSpec: '002-poc-generation',
52
+ }),
53
+ 'utf-8',
54
+ );
55
+ await writeFile(
56
+ join(tmpDir, 'src', 'index.ts'),
57
+ 'export function main() { return "ok"; }',
58
+ 'utf-8',
59
+ );
60
+ await writeFile(
61
+ join(tmpDir, 'tests', 'index.test.ts'),
62
+ 'import { test, expect } from "vitest"; test("works", () => { expect(true).toBe(true); });',
63
+ 'utf-8',
64
+ );
45
65
  }
46
66
 
47
67
  it('1. validates package.json exists and has test script', async () => {
@@ -115,10 +135,14 @@ describe('validatePocOutput', () => {
115
135
 
116
136
  it('reports error when package.json has no test script', async () => {
117
137
  await createCompleteScaffold();
118
- await writeFile(join(tmpDir, 'package.json'), JSON.stringify({
119
- name: 'test-poc',
120
- scripts: { build: 'tsc' }, // no test script
121
- }), 'utf-8');
138
+ await writeFile(
139
+ join(tmpDir, 'package.json'),
140
+ JSON.stringify({
141
+ name: 'test-poc',
142
+ scripts: { build: 'tsc' }, // no test script
143
+ }),
144
+ 'utf-8',
145
+ );
122
146
  const result = await validatePocOutput(tmpDir);
123
147
  expect(result.valid).toBe(false);
124
148
  expect(result.errors).toContain('package.json is missing "test" script');