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
@@ -16,7 +16,8 @@ src/
16
16
  ├── develop/ # PoC generation & Ralph loop
17
17
  │ ├── index.ts # Barrel export
18
18
  │ ├── ralphLoop.ts # Autonomous iteration orchestrator
19
- │ ├── pocScaffolder.ts # PoC project scaffolding + output validation
19
+ │ ├── pocUtils.ts # PoC output validation & scaffold utilities
20
+ │ ├── dynamicScaffolder.ts # LLM-driven dynamic PoC scaffolding
20
21
  │ ├── codeGenerator.ts # LLM-driven code generation & prompt building
21
22
  │ ├── testRunner.ts # Test execution and result parsing
22
23
  │ ├── mcpContextEnricher.ts# MCP-driven context enrichment (Context7, Azure, web search)
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "sofia-cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.4",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "dist/src/cli/index.js",
7
+ "license": "MIT",
7
8
  "bin": {
8
9
  "sofia": "dist/src/cli/index.js"
9
10
  },
@@ -12,7 +13,7 @@
12
13
  },
13
14
  "scripts": {
14
15
  "build": "tsc -p tsconfig.json && npm run build:assets",
15
- "build:assets": "cp -r src/prompts/ dist/src/ && cp src/shared/data/cards.json dist/src/shared/data/",
16
+ "build:assets": "cp -r src/prompts/ dist/src/ && cp src/shared/data/cards.json dist/src/shared/data/ && cp -r infra/ dist/infra/ ",
16
17
  "dev": "tsx watch src/cli/index.ts",
17
18
  "start": "tsx src/cli/index.ts",
18
19
  "clean": "rimraf dist coverage .nyc_output",
@@ -27,7 +28,8 @@
27
28
  "test:e2e": "vitest run tests/e2e",
28
29
  "test:live": "vitest run --config vitest.live.config.ts",
29
30
  "sofiacli": "node --enable-source-maps dist/cli/index.js",
30
- "sofia": "npm run sofiacli"
31
+ "sofia": "npm run sofiacli",
32
+ "prepublishOnly": "npm run build && npm link"
31
33
  },
32
34
  "dependencies": {
33
35
  "@azure/ai-projects": "^2.0.0-beta.5",
@@ -20,7 +20,6 @@ import { RalphLoop } from '../develop/ralphLoop.js';
20
20
  import { McpContextEnricher } from '../develop/mcpContextEnricher.js';
21
21
  import { deriveCheckpointState } from '../develop/checkpointState.js';
22
22
  import { createDefaultRegistry, selectTemplate } from '../develop/templateRegistry.js';
23
- import { PocScaffolder } from '../develop/pocScaffolder.js';
24
23
 
25
24
  // ── Types ────────────────────────────────────────────────────────────────────
26
25
 
@@ -221,7 +220,6 @@ export async function developCommand(
221
220
  outputDir,
222
221
  enricher,
223
222
  checkpoint,
224
- scaffolder: new PocScaffolder(template),
225
223
  templateEntry: template,
226
224
  onSessionUpdate: async (updated) => {
227
225
  await store.save(updated);
@@ -293,7 +291,7 @@ export async function developCommand(
293
291
  'Recovery options:',
294
292
  ` • Resume: sofia dev --session ${sessionId}`,
295
293
  ` • More iterations: sofia dev --session ${sessionId} --max-iterations 20`,
296
- ` • More iterations: sofia dev --session ${sessionId} --max-iterations 30`,
294
+ ` • More iterations: sofia dev --session ${sessionId} --max-iterations 30`,
297
295
  ` • Start fresh: sofia dev --session ${sessionId} --force`,
298
296
  '',
299
297
  ].join('\n'),
package/src/cli/index.ts CHANGED
@@ -8,6 +8,7 @@
8
8
  * - status: display session status
9
9
  * - export: export session artifacts
10
10
  */
11
+ import { existsSync, readFileSync } from 'node:fs';
11
12
  import path from 'node:path';
12
13
  import { fileURLToPath } from 'node:url';
13
14
  import { Command } from 'commander';
@@ -19,6 +20,15 @@ import type { PhaseValue } from '../shared/schemas/session.js';
19
20
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
20
21
  loadEnvFile(path.resolve(__dirname, '../../.env'));
21
22
 
23
+ // ── Read version from package.json (works from both src/ and dist/) ────────
24
+ let _pkgRoot = __dirname;
25
+ while (_pkgRoot !== path.dirname(_pkgRoot) && !existsSync(path.join(_pkgRoot, 'package.json'))) {
26
+ _pkgRoot = path.dirname(_pkgRoot);
27
+ }
28
+ const { version } = JSON.parse(readFileSync(path.join(_pkgRoot, 'package.json'), 'utf-8')) as {
29
+ version: string;
30
+ };
31
+
22
32
  // ── Handler types ──────────────────────────────────────────────────────────
23
33
 
24
34
  export interface CliHandlers {
@@ -36,7 +46,7 @@ export interface CliHandlers {
36
46
  export function buildCli(handlers?: Partial<CliHandlers>): Command {
37
47
  const program = new Command();
38
48
 
39
- program.name('sofia').description('sofIA — AI Discovery Workshop CLI').version('0.1.0');
49
+ program.name('sofia').description('sofIA — AI Discovery Workshop CLI').version(version);
40
50
 
41
51
  // ── Global options ──────────────────────────────────────────────────────
42
52
 
@@ -19,7 +19,11 @@ import { createPhaseHandler, getPhaseOrder, getNextPhase } from '../phases/phase
19
19
  import type { PhaseHandlerConfig } from '../phases/phaseHandlers.js';
20
20
  import type { SofiaEvent } from '../shared/events.js';
21
21
  import { renderMarkdown } from '../shared/markdownRenderer.js';
22
- import { destroyWebSearchSession, isWebSearchConfigured, createWebSearchTool } from '../mcp/webSearch.js';
22
+ import {
23
+ destroyWebSearchSession,
24
+ isWebSearchConfigured,
25
+ createWebSearchTool,
26
+ } from '../mcp/webSearch.js';
23
27
  import type { WebSearchConfig } from '../mcp/webSearch.js';
24
28
  import { loadEnvFile } from './envLoader.js';
25
29
  import { loadMcpConfig, McpManager } from '../mcp/mcpManager.js';
@@ -211,7 +215,7 @@ async function runWorkshop(
211
215
  session.plan?.architectureNotes,
212
216
  session.plan?.dependencies,
213
217
  );
214
-
218
+
215
219
  const ralphLoop = new RalphLoop({
216
220
  client,
217
221
  io,
@@ -232,18 +236,18 @@ async function runWorkshop(
232
236
  checkpoint,
233
237
  templateEntry,
234
238
  });
235
-
239
+
236
240
  const result = await ralphLoop.run();
237
241
  session = result.session;
238
-
242
+
239
243
  if (result.finalStatus === 'success') {
240
244
  io.write(
241
245
  renderMarkdown(
242
246
  `\n✅ PoC generated successfully in ${result.outputDir}\n\n` +
243
- `Next steps:\n` +
244
- `- Review the generated code\n` +
245
- `- Run tests: \`cd ${result.outputDir} && npm test\`\n` +
246
- `- Continue developing: \`sofia dev --session ${session.sessionId}\`\n`,
247
+ `Next steps:\n` +
248
+ `- Review the generated code\n` +
249
+ `- Run tests: \`cd ${result.outputDir} && npm test\`\n` +
250
+ `- Continue developing: \`sofia dev --session ${session.sessionId}\`\n`,
247
251
  { isTTY: io.isTTY },
248
252
  ),
249
253
  );
@@ -251,10 +255,10 @@ async function runWorkshop(
251
255
  io.write(
252
256
  renderMarkdown(
253
257
  `\n⚠️ PoC generation incomplete (${result.terminationReason})\n\n` +
254
- `The workshop can continue. To retry or resume PoC development:\n` +
255
- `\`\`\`\n` +
256
- `sofia dev --session ${session.sessionId}\n` +
257
- `\`\`\`\n`,
258
+ `The workshop can continue. To retry or resume PoC development:\n` +
259
+ `\`\`\`\n` +
260
+ `sofia dev --session ${session.sessionId}\n` +
261
+ `\`\`\`\n`,
258
262
  { isTTY: io.isTTY },
259
263
  ),
260
264
  );
@@ -264,15 +268,15 @@ async function runWorkshop(
264
268
  io.write(
265
269
  renderMarkdown(
266
270
  `\n⚠️ PoC generation failed: ${msg}\n\n` +
267
- `The workshop can continue. To retry:\n` +
268
- `\`\`\`\n` +
269
- `sofia dev --session ${session.sessionId}\n` +
270
- `\`\`\`\n`,
271
+ `The workshop can continue. To retry:\n` +
272
+ `\`\`\`\n` +
273
+ `sofia dev --session ${session.sessionId}\n` +
274
+ `\`\`\`\n`,
271
275
  { isTTY: io.isTTY },
272
276
  ),
273
277
  );
274
278
  }
275
-
279
+
276
280
  // Continue to next phase regardless of PoC outcome
277
281
  }
278
282
  currentPhaseIdx = phaseOrder.indexOf(next);
@@ -47,7 +47,7 @@ function buildScaffoldPrompt(session: WorkshopSession): string {
47
47
  const idea = session.ideas?.find((i) => i.id === session.selection?.ideaId);
48
48
  const ideaTitle = idea?.title ?? session.selection?.ideaId ?? 'AI Solution';
49
49
  const ideaDescription = idea?.description ?? 'AI-powered solution from workshop';
50
-
50
+
51
51
  const businessContext = session.businessContext
52
52
  ? `## Business Context\n\n` +
53
53
  `Company: ${session.businessContext.businessDescription}\n\n` +
@@ -124,40 +124,44 @@ Focus on **quality over quantity** - generate files that matter for demonstratin
124
124
  */
125
125
  function parseCodeBlocks(response: string): Array<{ file: string; content: string }> {
126
126
  const blocks: Array<{ file: string; content: string }> = [];
127
-
127
+
128
128
  // Match fenced code blocks with file= attribute
129
129
  const regex = /```(?:\w+)?\s+file=([^\s]+)\n([\s\S]*?)```/g;
130
130
  let match;
131
-
131
+
132
132
  while ((match = regex.exec(response)) !== null) {
133
133
  const file = match[1].trim();
134
134
  const content = match[2];
135
135
  blocks.push({ file, content });
136
136
  }
137
-
137
+
138
138
  return blocks;
139
139
  }
140
140
 
141
141
  /**
142
142
  * Infer tech stack from generated files.
143
143
  */
144
- function inferTechStack(files: Array<{ file: string; content: string }>): DynamicScaffoldResult['techStack'] {
144
+ function inferTechStack(
145
+ files: Array<{ file: string; content: string }>,
146
+ ): DynamicScaffoldResult['techStack'] {
145
147
  const hasPackageJson = files.some((f) => f.file === 'package.json');
146
148
  const hasTypeScript = files.some((f) => f.file.endsWith('.ts') || f.file === 'tsconfig.json');
147
- const hasPython = files.some((f) => f.file.endsWith('.py') || f.file === 'requirements.txt' || f.file === 'pyproject.toml');
148
-
149
+ const hasPython = files.some(
150
+ (f) => f.file.endsWith('.py') || f.file === 'requirements.txt' || f.file === 'pyproject.toml',
151
+ );
152
+
149
153
  // Try to parse package.json for more details
150
154
  let testRunner = 'npm test';
151
155
  let framework: string | undefined;
152
156
  let buildCommand: string | undefined;
153
-
157
+
154
158
  const packageJson = files.find((f) => f.file === 'package.json');
155
159
  if (packageJson) {
156
160
  try {
157
161
  const pkg = JSON.parse(packageJson.content);
158
162
  testRunner = pkg.scripts?.test ?? 'npm test';
159
163
  buildCommand = pkg.scripts?.build;
160
-
164
+
161
165
  // Detect framework from dependencies
162
166
  if (pkg.dependencies?.['express'] || pkg.devDependencies?.['express']) framework = 'Express';
163
167
  if (pkg.dependencies?.['react'] || pkg.devDependencies?.['react']) framework = 'React';
@@ -166,7 +170,7 @@ function inferTechStack(files: Array<{ file: string; content: string }>): Dynami
166
170
  // Ignore parse errors
167
171
  }
168
172
  }
169
-
173
+
170
174
  if (hasTypeScript) {
171
175
  return {
172
176
  language: 'TypeScript',
@@ -176,7 +180,7 @@ function inferTechStack(files: Array<{ file: string; content: string }>): Dynami
176
180
  buildCommand,
177
181
  };
178
182
  }
179
-
183
+
180
184
  if (hasPython) {
181
185
  return {
182
186
  language: 'Python',
@@ -185,7 +189,7 @@ function inferTechStack(files: Array<{ file: string; content: string }>): Dynami
185
189
  buildCommand: undefined,
186
190
  };
187
191
  }
188
-
192
+
189
193
  // Default to Node.js
190
194
  return {
191
195
  language: hasPackageJson ? 'JavaScript' : 'TypeScript',
@@ -199,7 +203,7 @@ function inferTechStack(files: Array<{ file: string; content: string }>): Dynami
199
203
 
200
204
  /**
201
205
  * Generate initial PoC scaffold using LLM.
202
- *
206
+ *
203
207
  * Replaces the template-based PocScaffolder with dynamic generation
204
208
  * that's grounded in the workshop session context.
205
209
  */
@@ -207,49 +211,50 @@ export async function generateDynamicScaffold(
207
211
  context: DynamicScaffoldContext,
208
212
  ): Promise<DynamicScaffoldResult> {
209
213
  const { session, outputDir, client } = context;
210
-
214
+
211
215
  // Build comprehensive prompt from session
212
216
  const prompt = buildScaffoldPrompt(session);
213
-
217
+
214
218
  // Create conversation session with system prompt
215
219
  const conversationSession = await client.createSession({
216
- systemPrompt: 'You are a senior software architect generating proof-of-concept project structures for AI solutions.',
220
+ systemPrompt:
221
+ 'You are a senior software architect generating proof-of-concept project structures for AI solutions.',
217
222
  });
218
-
223
+
219
224
  // Send user prompt and collect response
220
225
  let fullResponse = '';
221
226
  const stream = conversationSession.send({
222
227
  role: 'user',
223
228
  content: prompt,
224
229
  });
225
-
230
+
226
231
  for await (const event of stream) {
227
232
  if (event.type === 'TextDelta') {
228
233
  fullResponse += event.text;
229
234
  }
230
235
  }
231
-
236
+
232
237
  // Parse file blocks from response
233
238
  const files = parseCodeBlocks(fullResponse);
234
-
239
+
235
240
  if (files.length === 0) {
236
241
  throw new Error('LLM did not generate any files. Response: ' + fullResponse.substring(0, 500));
237
242
  }
238
-
243
+
239
244
  // Write files to disk
240
245
  const createdFiles: string[] = [];
241
-
246
+
242
247
  for (const { file, content } of files) {
243
248
  const fullPath = join(outputDir, file);
244
-
249
+
245
250
  // Ensure directory exists
246
251
  await mkdir(dirname(fullPath), { recursive: true });
247
-
252
+
248
253
  // Write file
249
254
  await writeFile(fullPath, content, 'utf-8');
250
255
  createdFiles.push(file);
251
256
  }
252
-
257
+
253
258
  // Export workshop documentation
254
259
  try {
255
260
  const workshopResult = await exportWorkshopDocs(session, outputDir);
@@ -257,26 +262,27 @@ export async function generateDynamicScaffold(
257
262
  } catch {
258
263
  // Non-fatal - PoC is still usable without workshop docs
259
264
  }
260
-
265
+
261
266
  // Create .sofia-metadata.json
262
267
  const metadata = {
263
268
  sessionId: session.sessionId,
264
269
  featureSpec: '002-poc-generation',
265
270
  generatedAt: new Date().toISOString(),
266
- ideaTitle: session.ideas?.find((i) => i.id === session.selection?.ideaId)?.title ?? 'AI Solution',
271
+ ideaTitle:
272
+ session.ideas?.find((i) => i.id === session.selection?.ideaId)?.title ?? 'AI Solution',
267
273
  totalIterations: 0,
268
274
  finalStatus: null,
269
275
  terminationReason: null,
270
276
  generatedBy: 'dynamic-scaffold',
271
277
  };
272
-
278
+
273
279
  const metadataPath = join(outputDir, '.sofia-metadata.json');
274
280
  await writeFile(metadataPath, JSON.stringify(metadata, null, 2) + '\n', 'utf-8');
275
281
  createdFiles.push('.sofia-metadata.json');
276
-
282
+
277
283
  // Infer tech stack from generated files
278
284
  const techStack = inferTechStack(files);
279
-
285
+
280
286
  return {
281
287
  createdFiles,
282
288
  techStack,
@@ -5,8 +5,19 @@
5
5
  * Orchestrates: scaffold → install → iterate (test → fix → repeat) → complete.
6
6
  */
7
7
 
8
- export { PocScaffolder } from './pocScaffolder.js';
9
- export type { ScaffoldContext, ScaffoldResult } from './pocScaffolder.js';
8
+ export {
9
+ validatePocOutput,
10
+ initializeGitRepo,
11
+ scanAndRecordTodos,
12
+ toKebabCase,
13
+ buildScaffoldContext,
14
+ } from './pocUtils.js';
15
+ export type {
16
+ ScaffoldContext,
17
+ ScaffoldResult,
18
+ TemplateFile,
19
+ ValidationResult,
20
+ } from './pocUtils.js';
10
21
 
11
22
  export { generateDynamicScaffold } from './dynamicScaffolder.js';
12
23
  export type { DynamicScaffoldContext, DynamicScaffoldResult } from './dynamicScaffolder.js';