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.
- package/README.md +42 -20
- package/dist/infra/deploy.sh +193 -0
- package/dist/infra/gather-env.sh +211 -0
- package/dist/infra/infra/deploy.sh +193 -0
- package/dist/infra/infra/gather-env.sh +211 -0
- package/dist/infra/infra/main.bicep +90 -0
- package/dist/infra/infra/main.bicepparam +18 -0
- package/dist/infra/infra/resources.bicep +134 -0
- package/dist/infra/infra/teardown.sh +114 -0
- package/dist/infra/main.bicep +90 -0
- package/dist/infra/main.bicepparam +18 -0
- package/dist/infra/resources.bicep +134 -0
- package/dist/infra/teardown.sh +114 -0
- package/dist/src/cli/developCommand.js +0 -2
- package/dist/src/cli/index.js +8 -1
- package/dist/src/cli/workshopCommand.js +1 -1
- package/dist/src/develop/index.js +1 -1
- package/dist/src/develop/pocUtils.js +228 -0
- package/dist/src/develop/ralphLoop.js +8 -27
- package/dist/src/shared/data/cards.json +655 -670
- package/docs/architecture.md +2 -1
- package/package.json +5 -3
- package/src/cli/developCommand.ts +1 -3
- package/src/cli/index.ts +11 -1
- package/src/cli/workshopCommand.ts +21 -17
- package/src/develop/dynamicScaffolder.ts +36 -30
- package/src/develop/index.ts +13 -2
- package/src/develop/pocUtils.ts +296 -0
- package/src/develop/ralphLoop.ts +8 -28
- package/src/develop/templateRegistry.ts +19 -18
- package/src/shared/data/cards.json +655 -670
- package/tests/e2e/developE2e.spec.ts +3 -61
- package/tests/e2e/developFailureE2e.spec.ts +34 -38
- package/tests/integration/pocGithubMcp.spec.ts +29 -39
- package/tests/integration/pocLocalFallback.spec.ts +29 -39
- package/tests/integration/ralphLoopFlow.spec.ts +46 -66
- package/tests/integration/ralphLoopPartial.spec.ts +30 -37
- package/tests/unit/develop/githubMcpAdapter.spec.ts +0 -134
- package/tests/unit/develop/outputValidator.spec.ts +45 -21
- package/tests/unit/develop/ralphLoop.spec.ts +58 -94
- package/tsconfig.json +2 -1
- package/vitest.workspace.ts +5 -0
- package/dist/src/develop/pocScaffolder.js +0 -542
- package/dist/tests/e2e/developE2e.spec.js +0 -126
- package/dist/tests/e2e/developFailureE2e.spec.js +0 -247
- package/dist/tests/e2e/developPty.spec.js +0 -75
- package/dist/tests/e2e/discoveryWebSearchRelevance.spec.js +0 -84
- package/dist/tests/e2e/harness.spec.js +0 -83
- package/dist/tests/e2e/mcpLive.spec.js +0 -120
- package/dist/tests/e2e/newSession.e2e.spec.js +0 -177
- package/dist/tests/e2e/ralphLoopEnrichmentComparison.spec.js +0 -62
- package/dist/tests/e2e/workiqEnrichment.spec.js +0 -56
- package/dist/tests/e2e/zavaSimulation.spec.js +0 -452
- package/dist/tests/fixtures/test-fixture-project/src/add.js +0 -3
- package/dist/tests/fixtures/test-fixture-project/tests/failing.test.js +0 -6
- package/dist/tests/fixtures/test-fixture-project/tests/hanging.test.js +0 -8
- package/dist/tests/fixtures/test-fixture-project/tests/passing.test.js +0 -10
- package/dist/tests/fixtures/test-fixture-project/vitest.config.js +0 -6
- package/dist/tests/integration/autoStartConversation.spec.js +0 -138
- package/dist/tests/integration/defaultCommand.spec.js +0 -147
- package/dist/tests/integration/directCommandNonTty.spec.js +0 -224
- package/dist/tests/integration/directCommandTty.spec.js +0 -151
- package/dist/tests/integration/discoveryEnrichmentFlow.spec.js +0 -175
- package/dist/tests/integration/exportArtifacts.spec.js +0 -202
- package/dist/tests/integration/exportFallbackFlow.spec.js +0 -99
- package/dist/tests/integration/mcpDegradationFlow.spec.js +0 -190
- package/dist/tests/integration/mcpTransportFlow.spec.js +0 -139
- package/dist/tests/integration/newSessionFlow.spec.js +0 -343
- package/dist/tests/integration/pocGithubMcp.spec.js +0 -186
- package/dist/tests/integration/pocLocalFallback.spec.js +0 -171
- package/dist/tests/integration/pocScaffold.spec.js +0 -163
- package/dist/tests/integration/ralphLoopFlow.spec.js +0 -359
- package/dist/tests/integration/ralphLoopPartial.spec.js +0 -368
- package/dist/tests/integration/resumeAndBacktrack.spec.js +0 -247
- package/dist/tests/integration/spinnerLifecycle.spec.js +0 -220
- package/dist/tests/integration/summarizationFlow.spec.js +0 -115
- package/dist/tests/integration/testRunnerReal.spec.js +0 -52
- package/dist/tests/integration/webSearchAgent.spec.js +0 -128
- package/dist/tests/live/copilotSdkLive.spec.js +0 -107
- package/dist/tests/live/zavaFullWorkshop.spec.js +0 -392
- package/dist/tests/setup/loadEnv.js +0 -3
- package/dist/tests/unit/cli/developCommand.spec.js +0 -567
- package/dist/tests/unit/cli/directCommands.spec.js +0 -279
- package/dist/tests/unit/cli/envLoader.spec.js +0 -58
- package/dist/tests/unit/cli/ioContext.spec.js +0 -119
- package/dist/tests/unit/cli/preflight.spec.js +0 -108
- package/dist/tests/unit/cli/statusCommand.spec.js +0 -111
- package/dist/tests/unit/cli/workshopClientFallback.spec.js +0 -80
- package/dist/tests/unit/cli/workshopCommand.spec.js +0 -328
- package/dist/tests/unit/config/vitestEnvSetup.spec.js +0 -13
- package/dist/tests/unit/develop/checkpointState.spec.js +0 -315
- package/dist/tests/unit/develop/codeGenerator.spec.js +0 -355
- package/dist/tests/unit/develop/githubMcpAdapter.spec.js +0 -231
- package/dist/tests/unit/develop/mcpContextEnricher.spec.js +0 -433
- package/dist/tests/unit/develop/outputValidator.spec.js +0 -119
- package/dist/tests/unit/develop/pocScaffolder.spec.js +0 -353
- package/dist/tests/unit/develop/ralphLoop.spec.js +0 -1248
- package/dist/tests/unit/develop/templateRegistry.spec.js +0 -85
- package/dist/tests/unit/develop/testRunner.spec.js +0 -249
- package/dist/tests/unit/infraBicep.spec.js +0 -92
- package/dist/tests/unit/infraDeploy.spec.js +0 -82
- package/dist/tests/unit/infraTeardown.spec.js +0 -63
- package/dist/tests/unit/logging/logger.spec.js +0 -43
- package/dist/tests/unit/loop/conversationLoop.spec.js +0 -592
- package/dist/tests/unit/loop/phaseSummarizer.spec.js +0 -141
- package/dist/tests/unit/loop/streamingMarkdown.spec.js +0 -147
- package/dist/tests/unit/mcp/mcpManager.spec.js +0 -279
- package/dist/tests/unit/mcp/mcpTransport.spec.js +0 -529
- package/dist/tests/unit/mcp/retryPolicy.spec.js +0 -218
- package/dist/tests/unit/mcp/timeoutValidation.spec.js +0 -46
- package/dist/tests/unit/mcp/webSearch.spec.js +0 -567
- package/dist/tests/unit/phases/contextSummarizer.spec.js +0 -140
- package/dist/tests/unit/phases/discoveryEnricher.repeatCalls.spec.js +0 -93
- package/dist/tests/unit/phases/discoveryEnricher.spec.js +0 -411
- package/dist/tests/unit/phases/phaseExtractors.spec.js +0 -352
- package/dist/tests/unit/phases/phaseHandlers.spec.js +0 -425
- package/dist/tests/unit/prompts/promptLoader.spec.js +0 -118
- package/dist/tests/unit/schemas/pocSchemas.spec.js +0 -412
- package/dist/tests/unit/schemas/session.spec.js +0 -257
- package/dist/tests/unit/sessions/exportPaths.spec.js +0 -31
- package/dist/tests/unit/sessions/exportWriter.spec.js +0 -655
- package/dist/tests/unit/sessions/sessionManager.spec.js +0 -151
- package/dist/tests/unit/sessions/sessionStore.spec.js +0 -116
- package/dist/tests/unit/shared/activitySpinner.spec.js +0 -175
- package/dist/tests/unit/shared/cardsLoader.spec.js +0 -76
- package/dist/tests/unit/shared/copilotClient.spec.js +0 -155
- package/dist/tests/unit/shared/errorClassifier.spec.js +0 -131
- package/dist/tests/unit/shared/events.spec.js +0 -55
- package/dist/tests/unit/shared/markdownRenderer.spec.js +0 -35
- package/dist/tests/unit/shared/markdownRendererChunks.spec.js +0 -70
- package/dist/tests/unit/shared/tableRenderer.spec.js +0 -34
- package/dist/vitest.config.js +0 -14
- package/dist/vitest.live.config.js +0 -18
- package/src/develop/pocScaffolder.ts +0 -646
- package/tests/integration/pocScaffold.spec.ts +0 -220
- package/tests/unit/develop/pocScaffolder.spec.ts +0 -451
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PoC Utilities.
|
|
3
|
+
*
|
|
4
|
+
* Standalone helper functions and types extracted from the former
|
|
5
|
+
* PocScaffolder class: git init, TODO scanning, output validation,
|
|
6
|
+
* and shared type definitions used by the scaffold / Ralph loop pipeline.
|
|
7
|
+
*/
|
|
8
|
+
import { writeFile, access, readFile, readdir, stat } from 'node:fs/promises';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { execSync } from 'node:child_process';
|
|
11
|
+
|
|
12
|
+
import type { TechStack, WorkshopSession } from '../shared/schemas/session.js';
|
|
13
|
+
import type { TemplateEntry } from './templateRegistry.js';
|
|
14
|
+
|
|
15
|
+
// ── Types ────────────────────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
export interface ScaffoldContext {
|
|
18
|
+
/** Kebab-case project name derived from idea title */
|
|
19
|
+
projectName: string;
|
|
20
|
+
/** Original idea title */
|
|
21
|
+
ideaTitle: string;
|
|
22
|
+
/** Idea description from workshop */
|
|
23
|
+
ideaDescription: string;
|
|
24
|
+
/** Tech stack to use */
|
|
25
|
+
techStack: TechStack;
|
|
26
|
+
/** Summary of the implementation plan */
|
|
27
|
+
planSummary: string;
|
|
28
|
+
/** Workshop session ID */
|
|
29
|
+
sessionId: string;
|
|
30
|
+
/** Output directory (absolute path) */
|
|
31
|
+
outputDir: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface TemplateFile {
|
|
35
|
+
/** Path relative to outputDir */
|
|
36
|
+
path: string;
|
|
37
|
+
/** File content string or generator function */
|
|
38
|
+
content: string | ((ctx: ScaffoldContext) => string);
|
|
39
|
+
/** Skip writing if file already exists (default: true) */
|
|
40
|
+
skipIfExists?: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface ScaffoldResult {
|
|
44
|
+
/** Files that were created */
|
|
45
|
+
createdFiles: string[];
|
|
46
|
+
/** Files that were skipped because they already existed */
|
|
47
|
+
skippedFiles: string[];
|
|
48
|
+
/** The scaffold context used */
|
|
49
|
+
context: ScaffoldContext;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface ValidationResult {
|
|
53
|
+
valid: boolean;
|
|
54
|
+
missingFiles: string[];
|
|
55
|
+
errors: string[];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Convert an idea title to a kebab-case project name.
|
|
62
|
+
*/
|
|
63
|
+
export function toKebabCase(title: string): string {
|
|
64
|
+
return title
|
|
65
|
+
.toLowerCase()
|
|
66
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
67
|
+
.replace(/^-+|-+$/g, '')
|
|
68
|
+
.substring(0, 64);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function fileExists(filePath: string): Promise<boolean> {
|
|
72
|
+
try {
|
|
73
|
+
await access(filePath);
|
|
74
|
+
return true;
|
|
75
|
+
} catch {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ── Build Context ────────────────────────────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Build a ScaffoldContext from a workshop session.
|
|
84
|
+
*/
|
|
85
|
+
export function buildScaffoldContext(
|
|
86
|
+
session: WorkshopSession,
|
|
87
|
+
outputDir: string,
|
|
88
|
+
templateEntry?: TemplateEntry,
|
|
89
|
+
): ScaffoldContext {
|
|
90
|
+
const idea = session.ideas?.find((i) => i.id === session.selection?.ideaId);
|
|
91
|
+
const ideaTitle = idea?.title ?? 'AI PoC';
|
|
92
|
+
const ideaDescription = idea?.description ?? 'A proof-of-concept AI application.';
|
|
93
|
+
|
|
94
|
+
const planSummary = session.plan?.architectureNotes
|
|
95
|
+
? session.plan.architectureNotes
|
|
96
|
+
: (session.plan?.milestones?.map((m) => m.title).join(', ') ?? 'See plan for details');
|
|
97
|
+
|
|
98
|
+
const techStack: TechStack = templateEntry?.techStack
|
|
99
|
+
? { ...templateEntry.techStack }
|
|
100
|
+
: {
|
|
101
|
+
language: 'TypeScript',
|
|
102
|
+
runtime: 'Node.js 20',
|
|
103
|
+
testRunner: 'npm test',
|
|
104
|
+
buildCommand: 'npm run build',
|
|
105
|
+
framework: undefined,
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// Infer framework from plan if present
|
|
109
|
+
if (session.plan?.architectureNotes) {
|
|
110
|
+
const notes = session.plan.architectureNotes.toLowerCase();
|
|
111
|
+
if (notes.includes('express')) techStack.framework = 'Express';
|
|
112
|
+
else if (notes.includes('fastapi')) techStack.framework = 'FastAPI';
|
|
113
|
+
else if (notes.includes('next')) techStack.framework = 'Next.js';
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
projectName: toKebabCase(ideaTitle),
|
|
118
|
+
ideaTitle,
|
|
119
|
+
ideaDescription,
|
|
120
|
+
techStack,
|
|
121
|
+
planSummary,
|
|
122
|
+
sessionId: session.sessionId,
|
|
123
|
+
outputDir,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ── Git Initialization ───────────────────────────────────────────────────────
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Initialize a local git repository in the output directory.
|
|
131
|
+
* Creates an initial commit with all scaffold files.
|
|
132
|
+
*
|
|
133
|
+
* @param outputDir The directory to initialize git in
|
|
134
|
+
* @returns true if successful, false otherwise
|
|
135
|
+
*/
|
|
136
|
+
export async function initializeGitRepo(outputDir: string): Promise<boolean> {
|
|
137
|
+
try {
|
|
138
|
+
const gitDir = join(outputDir, '.git');
|
|
139
|
+
const exists = await fileExists(gitDir);
|
|
140
|
+
if (exists) {
|
|
141
|
+
return true; // Already initialized
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
execSync('git init', { cwd: outputDir, stdio: 'ignore' });
|
|
145
|
+
execSync('git add .', { cwd: outputDir, stdio: 'ignore' });
|
|
146
|
+
execSync('git commit -m "chore: initial scaffold from sofIA"', {
|
|
147
|
+
cwd: outputDir,
|
|
148
|
+
stdio: 'ignore',
|
|
149
|
+
env: {
|
|
150
|
+
...process.env,
|
|
151
|
+
GIT_AUTHOR_NAME: 'sofIA',
|
|
152
|
+
GIT_AUTHOR_EMAIL: 'sofia@workshop.local',
|
|
153
|
+
GIT_COMMITTER_NAME: 'sofIA',
|
|
154
|
+
GIT_COMMITTER_EMAIL: 'sofia@workshop.local',
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
return true;
|
|
159
|
+
} catch (_err) {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ── TODO Scanning ────────────────────────────────────────────────────────────
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Scan scaffold files for TODO markers and update .sofia-metadata.json.
|
|
168
|
+
*/
|
|
169
|
+
export async function scanAndRecordTodos(outputDir: string): Promise<{
|
|
170
|
+
totalInitial: number;
|
|
171
|
+
remaining: number;
|
|
172
|
+
markers: string[];
|
|
173
|
+
}> {
|
|
174
|
+
const markers: string[] = [];
|
|
175
|
+
|
|
176
|
+
async function scanDir(dir: string, base: string): Promise<void> {
|
|
177
|
+
let entries;
|
|
178
|
+
try {
|
|
179
|
+
entries = await readdir(dir);
|
|
180
|
+
} catch {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
for (const entry of entries) {
|
|
184
|
+
if (entry === 'node_modules' || entry === '.git' || entry === 'dist') continue;
|
|
185
|
+
const full = join(dir, entry);
|
|
186
|
+
const rel = base ? `${base}/${entry}` : entry;
|
|
187
|
+
let s;
|
|
188
|
+
try {
|
|
189
|
+
s = await stat(full);
|
|
190
|
+
} catch {
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (s.isDirectory()) {
|
|
194
|
+
await scanDir(full, rel);
|
|
195
|
+
} else if (s.isFile()) {
|
|
196
|
+
try {
|
|
197
|
+
const content = await readFile(full, 'utf-8');
|
|
198
|
+
const lines = content.split('\n');
|
|
199
|
+
for (let i = 0; i < lines.length; i++) {
|
|
200
|
+
if (lines[i].includes('TODO:')) {
|
|
201
|
+
markers.push(`${rel}:${i + 1}: ${lines[i].trim()}`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
} catch {
|
|
205
|
+
// skip binary or unreadable files
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
await scanDir(outputDir, '');
|
|
212
|
+
|
|
213
|
+
const todos = {
|
|
214
|
+
totalInitial: markers.length,
|
|
215
|
+
remaining: markers.length,
|
|
216
|
+
markers,
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
// Update .sofia-metadata.json with TODO info
|
|
220
|
+
const metadataPath = join(outputDir, '.sofia-metadata.json');
|
|
221
|
+
try {
|
|
222
|
+
const raw = await readFile(metadataPath, 'utf-8');
|
|
223
|
+
const metadata = JSON.parse(raw);
|
|
224
|
+
metadata.todos = todos;
|
|
225
|
+
await writeFile(metadataPath, JSON.stringify(metadata, null, 2) + '\n', 'utf-8');
|
|
226
|
+
} catch {
|
|
227
|
+
// Metadata file may not exist yet
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return todos;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// ── Output Validator ─────────────────────────────────────────────────────────
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Validate that a scaffold directory meets the poc-output contract requirements.
|
|
237
|
+
*/
|
|
238
|
+
export async function validatePocOutput(outputDir: string): Promise<ValidationResult> {
|
|
239
|
+
const requiredFiles = [
|
|
240
|
+
'package.json',
|
|
241
|
+
'README.md',
|
|
242
|
+
'tsconfig.json',
|
|
243
|
+
'.gitignore',
|
|
244
|
+
'.sofia-metadata.json',
|
|
245
|
+
];
|
|
246
|
+
|
|
247
|
+
const missingFiles: string[] = [];
|
|
248
|
+
const errors: string[] = [];
|
|
249
|
+
|
|
250
|
+
for (const file of requiredFiles) {
|
|
251
|
+
const exists = await fileExists(join(outputDir, file));
|
|
252
|
+
if (!exists) {
|
|
253
|
+
missingFiles.push(file);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (!missingFiles.includes('package.json')) {
|
|
258
|
+
try {
|
|
259
|
+
const pkgContent = await readFile(join(outputDir, 'package.json'), 'utf-8');
|
|
260
|
+
const pkg = JSON.parse(pkgContent) as { scripts?: Record<string, string> };
|
|
261
|
+
if (!pkg.scripts?.test) {
|
|
262
|
+
errors.push('package.json is missing "test" script');
|
|
263
|
+
}
|
|
264
|
+
} catch {
|
|
265
|
+
errors.push('package.json is not valid JSON');
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
let hasSrcTs = false;
|
|
270
|
+
try {
|
|
271
|
+
const srcFiles = await readdir(join(outputDir, 'src'));
|
|
272
|
+
hasSrcTs = srcFiles.some((f) => f.endsWith('.ts'));
|
|
273
|
+
} catch {
|
|
274
|
+
// src/ doesn't exist
|
|
275
|
+
}
|
|
276
|
+
if (!hasSrcTs) {
|
|
277
|
+
errors.push('No TypeScript files found in src/');
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
let hasTestFile = false;
|
|
281
|
+
try {
|
|
282
|
+
const testFiles = await readdir(join(outputDir, 'tests'));
|
|
283
|
+
hasTestFile = testFiles.some((f) => f.endsWith('.test.ts'));
|
|
284
|
+
} catch {
|
|
285
|
+
// tests/ doesn't exist
|
|
286
|
+
}
|
|
287
|
+
if (!hasTestFile) {
|
|
288
|
+
errors.push('No test files found in tests/');
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
valid: missingFiles.length === 0 && errors.length === 0,
|
|
293
|
+
missingFiles,
|
|
294
|
+
errors,
|
|
295
|
+
};
|
|
296
|
+
}
|
package/src/develop/ralphLoop.ts
CHANGED
|
@@ -23,7 +23,7 @@ import type {
|
|
|
23
23
|
} from '../shared/schemas/session.js';
|
|
24
24
|
// McpManager import removed - accessed via McpContextEnricher.mcpManager public property
|
|
25
25
|
import { exportWorkshopDocs } from '../sessions/exportWriter.js';
|
|
26
|
-
import {
|
|
26
|
+
import { initializeGitRepo, scanAndRecordTodos, validatePocOutput } from './pocUtils.js';
|
|
27
27
|
import { generateDynamicScaffold } from './dynamicScaffolder.js';
|
|
28
28
|
import type { DynamicScaffoldResult } from './dynamicScaffolder.js';
|
|
29
29
|
import { TestRunner } from './testRunner.js';
|
|
@@ -55,8 +55,6 @@ export interface RalphLoopOptions {
|
|
|
55
55
|
enricher?: McpContextEnricher;
|
|
56
56
|
/** Override TestRunner for testing */
|
|
57
57
|
testRunner?: TestRunner;
|
|
58
|
-
/** Override PocScaffolder for testing */
|
|
59
|
-
scaffolder?: PocScaffolder;
|
|
60
58
|
/** Checkpoint state for resume behavior */
|
|
61
59
|
checkpoint?: CheckpointState;
|
|
62
60
|
/** Template entry for install/test commands */
|
|
@@ -249,29 +247,11 @@ export class RalphLoop {
|
|
|
249
247
|
const scaffoldStart = Date.now();
|
|
250
248
|
|
|
251
249
|
try {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
createdFiles: legacyResult.createdFiles,
|
|
258
|
-
techStack: scaffoldCtx.techStack
|
|
259
|
-
? {
|
|
260
|
-
language: scaffoldCtx.techStack.language ?? 'unknown',
|
|
261
|
-
runtime: scaffoldCtx.techStack.runtime ?? 'unknown',
|
|
262
|
-
testRunner: scaffoldCtx.techStack.testRunner ?? 'unknown',
|
|
263
|
-
framework: scaffoldCtx.techStack.framework,
|
|
264
|
-
buildCommand: scaffoldCtx.techStack.buildCommand,
|
|
265
|
-
}
|
|
266
|
-
: { language: 'unknown', runtime: 'unknown', testRunner: 'unknown' },
|
|
267
|
-
};
|
|
268
|
-
} else {
|
|
269
|
-
scaffoldResult = await generateDynamicScaffold({
|
|
270
|
-
client,
|
|
271
|
-
session,
|
|
272
|
-
outputDir,
|
|
273
|
-
});
|
|
274
|
-
}
|
|
250
|
+
scaffoldResult = await generateDynamicScaffold({
|
|
251
|
+
client,
|
|
252
|
+
session,
|
|
253
|
+
outputDir,
|
|
254
|
+
});
|
|
275
255
|
techStack = scaffoldResult.techStack;
|
|
276
256
|
} catch (err: unknown) {
|
|
277
257
|
spinner?.stop();
|
|
@@ -315,7 +295,7 @@ export class RalphLoop {
|
|
|
315
295
|
}
|
|
316
296
|
|
|
317
297
|
// Initialize local git repository
|
|
318
|
-
const gitInitialized = await
|
|
298
|
+
const gitInitialized = await initializeGitRepo(outputDir);
|
|
319
299
|
if (gitInitialized) {
|
|
320
300
|
io.writeActivity('✓ Initialized git repository with initial commit');
|
|
321
301
|
io.writeActivity('');
|
|
@@ -629,7 +609,7 @@ export class RalphLoop {
|
|
|
629
609
|
|
|
630
610
|
// FR-022: Rescan TODO markers after applying changes
|
|
631
611
|
try {
|
|
632
|
-
await
|
|
612
|
+
await scanAndRecordTodos(outputDir);
|
|
633
613
|
} catch {
|
|
634
614
|
// Non-critical — ignore scanning errors
|
|
635
615
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Contract: specs/004-dev-resume-hardening/contracts/cli.md
|
|
8
8
|
*/
|
|
9
|
-
import type { TemplateFile, ScaffoldContext } from './
|
|
9
|
+
import type { TemplateFile, ScaffoldContext } from './pocUtils.js';
|
|
10
10
|
import type { TechStack } from '../shared/schemas/session.js';
|
|
11
11
|
|
|
12
12
|
// ── Types ────────────────────────────────────────────────────────────────────
|
|
@@ -84,24 +84,25 @@ coverage/
|
|
|
84
84
|
{
|
|
85
85
|
path: 'tsconfig.json',
|
|
86
86
|
skipIfExists: true,
|
|
87
|
-
content:
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
87
|
+
content:
|
|
88
|
+
JSON.stringify(
|
|
89
|
+
{
|
|
90
|
+
compilerOptions: {
|
|
91
|
+
target: 'ES2022',
|
|
92
|
+
module: 'Node16',
|
|
93
|
+
moduleResolution: 'Node16',
|
|
94
|
+
strict: true,
|
|
95
|
+
outDir: 'dist',
|
|
96
|
+
rootDir: 'src',
|
|
97
|
+
declaration: true,
|
|
98
|
+
esModuleInterop: true,
|
|
99
|
+
skipLibCheck: true,
|
|
100
|
+
},
|
|
101
|
+
include: ['src'],
|
|
99
102
|
},
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
2,
|
|
104
|
-
) + '\n',
|
|
103
|
+
null,
|
|
104
|
+
2,
|
|
105
|
+
) + '\n',
|
|
105
106
|
},
|
|
106
107
|
{
|
|
107
108
|
path: 'README.md',
|