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
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* T015: Integration test for scaffold-only flow.
|
|
3
|
-
*
|
|
4
|
-
* Runs scaffolder with fixture session → verify output directory structure
|
|
5
|
-
* matches poc-output contract → verify package.json has test script →
|
|
6
|
-
* verify .sofia-metadata.json links to session.
|
|
7
|
-
*/
|
|
8
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
9
|
-
import { mkdtemp, rm, readFile } from 'node:fs/promises';
|
|
10
|
-
import { join } from 'node:path';
|
|
11
|
-
import { tmpdir } from 'node:os';
|
|
12
|
-
import { existsSync } from 'node:fs';
|
|
13
|
-
import { createRequire } from 'node:module';
|
|
14
|
-
|
|
15
|
-
import { PocScaffolder, validatePocOutput } from '../../src/develop/pocScaffolder.js';
|
|
16
|
-
import type { WorkshopSession } from '../../src/shared/schemas/session.js';
|
|
17
|
-
|
|
18
|
-
// ── Load fixture session ──────────────────────────────────────────────────────
|
|
19
|
-
|
|
20
|
-
const require = createRequire(import.meta.url);
|
|
21
|
-
const fixtureSession: WorkshopSession = require('../fixtures/completedSession.json') as WorkshopSession;
|
|
22
|
-
|
|
23
|
-
describe('PoC Scaffold Integration', () => {
|
|
24
|
-
let tmpDir: string;
|
|
25
|
-
|
|
26
|
-
beforeEach(async () => {
|
|
27
|
-
tmpDir = await mkdtemp(join(tmpdir(), 'sofia-scaffold-integration-'));
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
afterEach(async () => {
|
|
31
|
-
await rm(tmpDir, { recursive: true, force: true });
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it('creates valid directory structure from fixture session', async () => {
|
|
35
|
-
const scaffolder = new PocScaffolder();
|
|
36
|
-
const ctx = PocScaffolder.buildContext(fixtureSession, tmpDir);
|
|
37
|
-
const result = await scaffolder.scaffold(ctx);
|
|
38
|
-
|
|
39
|
-
// Verify files were created
|
|
40
|
-
expect(result.createdFiles.length).toBeGreaterThan(0);
|
|
41
|
-
|
|
42
|
-
// Verify all required files per poc-output contract
|
|
43
|
-
const requiredFiles = [
|
|
44
|
-
'.gitignore',
|
|
45
|
-
'README.md',
|
|
46
|
-
'package.json',
|
|
47
|
-
'tsconfig.json',
|
|
48
|
-
'.sofia-metadata.json',
|
|
49
|
-
];
|
|
50
|
-
|
|
51
|
-
for (const file of requiredFiles) {
|
|
52
|
-
expect(existsSync(join(tmpDir, file)), `Expected ${file} to exist`).toBe(true);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Verify src/ and tests/ directories
|
|
56
|
-
expect(existsSync(join(tmpDir, 'src', 'index.ts'))).toBe(true);
|
|
57
|
-
expect(existsSync(join(tmpDir, 'tests', 'index.test.ts'))).toBe(true);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('package.json has required test script', async () => {
|
|
61
|
-
const scaffolder = new PocScaffolder();
|
|
62
|
-
const ctx = PocScaffolder.buildContext(fixtureSession, tmpDir);
|
|
63
|
-
await scaffolder.scaffold(ctx);
|
|
64
|
-
|
|
65
|
-
const pkgContent = await readFile(join(tmpDir, 'package.json'), 'utf-8');
|
|
66
|
-
const pkg = JSON.parse(pkgContent) as {
|
|
67
|
-
name: string;
|
|
68
|
-
scripts: Record<string, string>;
|
|
69
|
-
type: string;
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
expect(pkg.scripts.test).toBeDefined();
|
|
73
|
-
expect(pkg.scripts.test).toBe('vitest run');
|
|
74
|
-
expect(pkg.type).toBe('module');
|
|
75
|
-
expect(pkg.name).toBe('ai-powered-route-optimizer'); // from fixture
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('.sofia-metadata.json links to session', async () => {
|
|
79
|
-
const scaffolder = new PocScaffolder();
|
|
80
|
-
const ctx = PocScaffolder.buildContext(fixtureSession, tmpDir);
|
|
81
|
-
await scaffolder.scaffold(ctx);
|
|
82
|
-
|
|
83
|
-
const metaContent = await readFile(join(tmpDir, '.sofia-metadata.json'), 'utf-8');
|
|
84
|
-
const meta = JSON.parse(metaContent) as {
|
|
85
|
-
sessionId: string;
|
|
86
|
-
featureSpec: string;
|
|
87
|
-
ideaTitle: string;
|
|
88
|
-
generatedAt: string;
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
expect(meta.sessionId).toBe(fixtureSession.sessionId);
|
|
92
|
-
expect(meta.featureSpec).toBe('002-poc-generation');
|
|
93
|
-
expect(meta.ideaTitle).toBe('AI-Powered Route Optimizer');
|
|
94
|
-
expect(meta.generatedAt).toBeDefined();
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it('tsconfig.json is valid JSON with strict mode', async () => {
|
|
98
|
-
const scaffolder = new PocScaffolder();
|
|
99
|
-
const ctx = PocScaffolder.buildContext(fixtureSession, tmpDir);
|
|
100
|
-
await scaffolder.scaffold(ctx);
|
|
101
|
-
|
|
102
|
-
const tsconfigContent = await readFile(join(tmpDir, 'tsconfig.json'), 'utf-8');
|
|
103
|
-
const tsconfig = JSON.parse(tsconfigContent) as {
|
|
104
|
-
compilerOptions: { strict: boolean; module: string };
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
expect(tsconfig.compilerOptions.strict).toBe(true);
|
|
108
|
-
expect(tsconfig.compilerOptions.module).toBe('Node16');
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it('.gitignore contains required patterns', async () => {
|
|
112
|
-
const scaffolder = new PocScaffolder();
|
|
113
|
-
const ctx = PocScaffolder.buildContext(fixtureSession, tmpDir);
|
|
114
|
-
await scaffolder.scaffold(ctx);
|
|
115
|
-
|
|
116
|
-
const gitignoreContent = await readFile(join(tmpDir, '.gitignore'), 'utf-8');
|
|
117
|
-
expect(gitignoreContent).toContain('node_modules/');
|
|
118
|
-
expect(gitignoreContent).toContain('dist/');
|
|
119
|
-
expect(gitignoreContent).toContain('coverage/');
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it('README.md contains idea title and generated-by attribution', async () => {
|
|
123
|
-
const scaffolder = new PocScaffolder();
|
|
124
|
-
const ctx = PocScaffolder.buildContext(fixtureSession, tmpDir);
|
|
125
|
-
await scaffolder.scaffold(ctx);
|
|
126
|
-
|
|
127
|
-
const readmeContent = await readFile(join(tmpDir, 'README.md'), 'utf-8');
|
|
128
|
-
expect(readmeContent).toContain('AI-Powered Route Optimizer');
|
|
129
|
-
expect(readmeContent).toContain(fixtureSession.sessionId);
|
|
130
|
-
expect(readmeContent).toContain('sofIA');
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it('validatePocOutput returns valid=true for complete scaffold', async () => {
|
|
134
|
-
const scaffolder = new PocScaffolder();
|
|
135
|
-
const ctx = PocScaffolder.buildContext(fixtureSession, tmpDir);
|
|
136
|
-
await scaffolder.scaffold(ctx);
|
|
137
|
-
|
|
138
|
-
const validation = await validatePocOutput(tmpDir);
|
|
139
|
-
|
|
140
|
-
expect(validation.valid).toBe(true);
|
|
141
|
-
expect(validation.missingFiles).toHaveLength(0);
|
|
142
|
-
expect(validation.errors).toHaveLength(0);
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
it('infers tech stack from plan architecture notes', async () => {
|
|
146
|
-
const _scaffolder = new PocScaffolder();
|
|
147
|
-
const ctx = PocScaffolder.buildContext(fixtureSession, tmpDir);
|
|
148
|
-
|
|
149
|
-
// Fixture session has 'express' in architectureNotes
|
|
150
|
-
expect(ctx.techStack.language).toBe('TypeScript');
|
|
151
|
-
expect(ctx.techStack.runtime).toBe('Node.js 20');
|
|
152
|
-
expect(ctx.techStack.framework).toBe('Express');
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
it('src/index.ts exports a main function', async () => {
|
|
156
|
-
const scaffolder = new PocScaffolder();
|
|
157
|
-
const ctx = PocScaffolder.buildContext(fixtureSession, tmpDir);
|
|
158
|
-
await scaffolder.scaffold(ctx);
|
|
159
|
-
|
|
160
|
-
const indexContent = await readFile(join(tmpDir, 'src', 'index.ts'), 'utf-8');
|
|
161
|
-
expect(indexContent).toContain('export async function main');
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
it('tests/index.test.ts contains vitest imports', async () => {
|
|
165
|
-
const scaffolder = new PocScaffolder();
|
|
166
|
-
const ctx = PocScaffolder.buildContext(fixtureSession, tmpDir);
|
|
167
|
-
await scaffolder.scaffold(ctx);
|
|
168
|
-
|
|
169
|
-
const testContent = await readFile(join(tmpDir, 'tests', 'index.test.ts'), 'utf-8');
|
|
170
|
-
expect(testContent).toContain('vitest');
|
|
171
|
-
expect(testContent).toContain('describe');
|
|
172
|
-
expect(testContent).toContain('expect');
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
// ── python-pytest template integration (T037) ────────────────────────────
|
|
177
|
-
|
|
178
|
-
describe('python-pytest template', () => {
|
|
179
|
-
let tmpDir: string;
|
|
180
|
-
|
|
181
|
-
beforeEach(async () => {
|
|
182
|
-
tmpDir = await mkdtemp(join(tmpdir(), 'sofia-python-scaffold-'));
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
afterEach(async () => {
|
|
186
|
-
await rm(tmpDir, { recursive: true, force: true });
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
it('scaffolds expected Python project structure (T037)', async () => {
|
|
190
|
-
const { PYTHON_PYTEST_TEMPLATE } = await import('../../src/develop/templateRegistry.js');
|
|
191
|
-
|
|
192
|
-
const scaffolder = new PocScaffolder(PYTHON_PYTEST_TEMPLATE);
|
|
193
|
-
const ctx = PocScaffolder.buildContext(fixtureSession, tmpDir, PYTHON_PYTEST_TEMPLATE);
|
|
194
|
-
|
|
195
|
-
const result = await scaffolder.scaffold(ctx);
|
|
196
|
-
|
|
197
|
-
expect(result.createdFiles).toContain('requirements.txt');
|
|
198
|
-
expect(result.createdFiles).toContain('pytest.ini');
|
|
199
|
-
expect(result.createdFiles).toContain('src/__init__.py');
|
|
200
|
-
expect(result.createdFiles).toContain('src/main.py');
|
|
201
|
-
expect(result.createdFiles).toContain('tests/test_main.py');
|
|
202
|
-
expect(result.createdFiles).toContain('.sofia-metadata.json');
|
|
203
|
-
expect(result.createdFiles).toContain('README.md');
|
|
204
|
-
|
|
205
|
-
// Verify files exist on disk
|
|
206
|
-
expect(existsSync(join(tmpDir, 'requirements.txt'))).toBe(true);
|
|
207
|
-
expect(existsSync(join(tmpDir, 'src', 'main.py'))).toBe(true);
|
|
208
|
-
expect(existsSync(join(tmpDir, 'tests', 'test_main.py'))).toBe(true);
|
|
209
|
-
|
|
210
|
-
// Verify content
|
|
211
|
-
const reqContent = await readFile(join(tmpDir, 'requirements.txt'), 'utf-8');
|
|
212
|
-
expect(reqContent).toContain('pytest');
|
|
213
|
-
|
|
214
|
-
const mainContent = await readFile(join(tmpDir, 'src', 'main.py'), 'utf-8');
|
|
215
|
-
expect(mainContent).toContain('def main');
|
|
216
|
-
|
|
217
|
-
// Verify techStack uses Python
|
|
218
|
-
expect(ctx.techStack.language).toBe('Python');
|
|
219
|
-
});
|
|
220
|
-
});
|
|
@@ -1,451 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* T011: Unit tests for PocScaffolder.
|
|
3
|
-
*
|
|
4
|
-
* Verifies scaffold creates all required files, skip-if-exists behavior,
|
|
5
|
-
* and ScaffoldContext population from session.
|
|
6
|
-
*/
|
|
7
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
8
|
-
import { mkdtemp, rm, readFile, readdir, stat } from 'node:fs/promises';
|
|
9
|
-
import { join } from 'node:path';
|
|
10
|
-
import { tmpdir } from 'node:os';
|
|
11
|
-
|
|
12
|
-
import { PocScaffolder, toKebabCase, validatePocOutput } from '../../../src/develop/pocScaffolder.js';
|
|
13
|
-
import type { WorkshopSession } from '../../../src/shared/schemas/session.js';
|
|
14
|
-
|
|
15
|
-
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
16
|
-
|
|
17
|
-
function makeSession(overrides?: Partial<WorkshopSession>): WorkshopSession {
|
|
18
|
-
const now = new Date().toISOString();
|
|
19
|
-
return {
|
|
20
|
-
sessionId: 'scaffold-test-001',
|
|
21
|
-
schemaVersion: '1.0.0',
|
|
22
|
-
createdAt: now,
|
|
23
|
-
updatedAt: now,
|
|
24
|
-
phase: 'Develop',
|
|
25
|
-
status: 'Active',
|
|
26
|
-
participants: [],
|
|
27
|
-
artifacts: { generatedFiles: [] },
|
|
28
|
-
ideas: [
|
|
29
|
-
{
|
|
30
|
-
id: 'idea-1',
|
|
31
|
-
title: 'AI Route Optimizer',
|
|
32
|
-
description: 'Optimize delivery routes using AI.',
|
|
33
|
-
workflowStepIds: ['a1'],
|
|
34
|
-
},
|
|
35
|
-
],
|
|
36
|
-
selection: {
|
|
37
|
-
ideaId: 'idea-1',
|
|
38
|
-
selectionRationale: 'Best idea.',
|
|
39
|
-
confirmedByUser: true,
|
|
40
|
-
},
|
|
41
|
-
plan: {
|
|
42
|
-
milestones: [{ id: 'm1', title: 'Setup', items: ['Init project'] }],
|
|
43
|
-
architectureNotes: 'Node.js 20 + TypeScript + Express. Use vitest for tests.',
|
|
44
|
-
dependencies: ['express', 'typescript', 'vitest'],
|
|
45
|
-
},
|
|
46
|
-
...overrides,
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
describe('toKebabCase', () => {
|
|
51
|
-
it('converts a title to kebab case', () => {
|
|
52
|
-
expect(toKebabCase('AI Route Optimizer')).toBe('ai-route-optimizer');
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('removes special characters', () => {
|
|
56
|
-
expect(toKebabCase('AI-Powered Delivery (Beta)')).toBe('ai-powered-delivery-beta');
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('handles leading/trailing spaces', () => {
|
|
60
|
-
expect(toKebabCase(' My Project ')).toBe('my-project');
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it('truncates to 64 characters', () => {
|
|
64
|
-
const long = 'a'.repeat(100);
|
|
65
|
-
expect(toKebabCase(long)).toHaveLength(64);
|
|
66
|
-
});
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
describe('PocScaffolder', () => {
|
|
70
|
-
let tmpDir: string;
|
|
71
|
-
|
|
72
|
-
beforeEach(async () => {
|
|
73
|
-
tmpDir = await mkdtemp(join(tmpdir(), 'sofia-scaffold-test-'));
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
afterEach(async () => {
|
|
77
|
-
await rm(tmpDir, { recursive: true, force: true });
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
describe('buildContext', () => {
|
|
81
|
-
it('builds a context from a session', () => {
|
|
82
|
-
const session = makeSession();
|
|
83
|
-
const ctx = PocScaffolder.buildContext(session, tmpDir);
|
|
84
|
-
|
|
85
|
-
expect(ctx.sessionId).toBe('scaffold-test-001');
|
|
86
|
-
expect(ctx.projectName).toBe('ai-route-optimizer');
|
|
87
|
-
expect(ctx.ideaTitle).toBe('AI Route Optimizer');
|
|
88
|
-
expect(ctx.ideaDescription).toBe('Optimize delivery routes using AI.');
|
|
89
|
-
expect(ctx.outputDir).toBe(tmpDir);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it('infers Express framework from architecture notes', () => {
|
|
93
|
-
const session = makeSession();
|
|
94
|
-
const ctx = PocScaffolder.buildContext(session, tmpDir);
|
|
95
|
-
expect(ctx.techStack.framework).toBe('Express');
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('uses idea title as project name', () => {
|
|
99
|
-
const session = makeSession({
|
|
100
|
-
ideas: [{ id: 'idea-1', title: 'My Cool AI App', description: 'desc', workflowStepIds: [] }],
|
|
101
|
-
});
|
|
102
|
-
const ctx = PocScaffolder.buildContext(session, tmpDir);
|
|
103
|
-
expect(ctx.projectName).toBe('my-cool-ai-app');
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it('falls back gracefully when no ideas or selection', () => {
|
|
107
|
-
const session = makeSession({ ideas: [], selection: undefined });
|
|
108
|
-
const ctx = PocScaffolder.buildContext(session, tmpDir);
|
|
109
|
-
expect(ctx.ideaTitle).toBe('AI PoC');
|
|
110
|
-
expect(ctx.projectName).toBe('ai-poc');
|
|
111
|
-
});
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
describe('scaffold', () => {
|
|
115
|
-
it('creates all required files', async () => {
|
|
116
|
-
const session = makeSession();
|
|
117
|
-
const scaffolder = new PocScaffolder();
|
|
118
|
-
const ctx = PocScaffolder.buildContext(session, tmpDir);
|
|
119
|
-
|
|
120
|
-
const result = await scaffolder.scaffold(ctx);
|
|
121
|
-
|
|
122
|
-
// Check all required files were created
|
|
123
|
-
const requiredFiles = [
|
|
124
|
-
'.gitignore',
|
|
125
|
-
'package.json',
|
|
126
|
-
'tsconfig.json',
|
|
127
|
-
'README.md',
|
|
128
|
-
'src/index.ts',
|
|
129
|
-
'tests/index.test.ts',
|
|
130
|
-
'.sofia-metadata.json',
|
|
131
|
-
];
|
|
132
|
-
|
|
133
|
-
for (const file of requiredFiles) {
|
|
134
|
-
const fullPath = join(tmpDir, file);
|
|
135
|
-
const exists = await stat(fullPath).then(() => true).catch(() => false);
|
|
136
|
-
expect(exists, `Expected ${file} to exist`).toBe(true);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
expect(result.createdFiles).toContain('package.json');
|
|
140
|
-
expect(result.createdFiles).toContain('README.md');
|
|
141
|
-
expect(result.createdFiles).toContain('.gitignore');
|
|
142
|
-
expect(result.createdFiles).toContain('.sofia-metadata.json');
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
it('package.json has required structure', async () => {
|
|
146
|
-
const session = makeSession();
|
|
147
|
-
const scaffolder = new PocScaffolder();
|
|
148
|
-
const ctx = PocScaffolder.buildContext(session, tmpDir);
|
|
149
|
-
await scaffolder.scaffold(ctx);
|
|
150
|
-
|
|
151
|
-
const content = await readFile(join(tmpDir, 'package.json'), 'utf-8');
|
|
152
|
-
const pkg = JSON.parse(content) as Record<string, unknown>;
|
|
153
|
-
|
|
154
|
-
expect(pkg.type).toBe('module');
|
|
155
|
-
expect((pkg.scripts as Record<string, string>).test).toBe('vitest run');
|
|
156
|
-
expect((pkg.scripts as Record<string, string>).build).toBeDefined();
|
|
157
|
-
expect(pkg.version).toBe('0.1.0');
|
|
158
|
-
expect(pkg.name).toBe('ai-route-optimizer');
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
it('tsconfig.json has strict TypeScript config', async () => {
|
|
162
|
-
const session = makeSession();
|
|
163
|
-
const scaffolder = new PocScaffolder();
|
|
164
|
-
const ctx = PocScaffolder.buildContext(session, tmpDir);
|
|
165
|
-
await scaffolder.scaffold(ctx);
|
|
166
|
-
|
|
167
|
-
const content = await readFile(join(tmpDir, 'tsconfig.json'), 'utf-8');
|
|
168
|
-
const tsconfig = JSON.parse(content) as {
|
|
169
|
-
compilerOptions: { strict: boolean; target: string };
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
expect(tsconfig.compilerOptions.strict).toBe(true);
|
|
173
|
-
expect(tsconfig.compilerOptions.target).toBe('ES2022');
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
it('.gitignore excludes node_modules, dist, coverage', async () => {
|
|
177
|
-
const session = makeSession();
|
|
178
|
-
const scaffolder = new PocScaffolder();
|
|
179
|
-
const ctx = PocScaffolder.buildContext(session, tmpDir);
|
|
180
|
-
await scaffolder.scaffold(ctx);
|
|
181
|
-
|
|
182
|
-
const content = await readFile(join(tmpDir, '.gitignore'), 'utf-8');
|
|
183
|
-
expect(content).toContain('node_modules/');
|
|
184
|
-
expect(content).toContain('dist/');
|
|
185
|
-
expect(content).toContain('coverage/');
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
it('.sofia-metadata.json links to session', async () => {
|
|
189
|
-
const session = makeSession();
|
|
190
|
-
const scaffolder = new PocScaffolder();
|
|
191
|
-
const ctx = PocScaffolder.buildContext(session, tmpDir);
|
|
192
|
-
await scaffolder.scaffold(ctx);
|
|
193
|
-
|
|
194
|
-
const content = await readFile(join(tmpDir, '.sofia-metadata.json'), 'utf-8');
|
|
195
|
-
const meta = JSON.parse(content) as Record<string, unknown>;
|
|
196
|
-
|
|
197
|
-
expect(meta.sessionId).toBe('scaffold-test-001');
|
|
198
|
-
expect(meta.featureSpec).toBe('002-poc-generation');
|
|
199
|
-
expect(meta.ideaTitle).toBe('AI Route Optimizer');
|
|
200
|
-
expect(meta.generatedAt).toBeDefined();
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
it('README.md contains idea title and session ID', async () => {
|
|
204
|
-
const session = makeSession();
|
|
205
|
-
const scaffolder = new PocScaffolder();
|
|
206
|
-
const ctx = PocScaffolder.buildContext(session, tmpDir);
|
|
207
|
-
await scaffolder.scaffold(ctx);
|
|
208
|
-
|
|
209
|
-
const content = await readFile(join(tmpDir, 'README.md'), 'utf-8');
|
|
210
|
-
expect(content).toContain('AI Route Optimizer');
|
|
211
|
-
expect(content).toContain('scaffold-test-001');
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
it('creates src/ and tests/ directories', async () => {
|
|
215
|
-
const session = makeSession();
|
|
216
|
-
const scaffolder = new PocScaffolder();
|
|
217
|
-
const ctx = PocScaffolder.buildContext(session, tmpDir);
|
|
218
|
-
await scaffolder.scaffold(ctx);
|
|
219
|
-
|
|
220
|
-
const srcEntries = await readdir(join(tmpDir, 'src'));
|
|
221
|
-
const testEntries = await readdir(join(tmpDir, 'tests'));
|
|
222
|
-
|
|
223
|
-
expect(srcEntries.some((f) => f.endsWith('.ts'))).toBe(true);
|
|
224
|
-
expect(testEntries.some((f) => f.endsWith('.test.ts'))).toBe(true);
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
it('returns list of created files', async () => {
|
|
228
|
-
const session = makeSession();
|
|
229
|
-
const scaffolder = new PocScaffolder();
|
|
230
|
-
const ctx = PocScaffolder.buildContext(session, tmpDir);
|
|
231
|
-
const result = await scaffolder.scaffold(ctx);
|
|
232
|
-
|
|
233
|
-
expect(result.createdFiles.length).toBeGreaterThan(0);
|
|
234
|
-
expect(result.skippedFiles).toEqual([]);
|
|
235
|
-
});
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
describe('skip-if-exists behavior', () => {
|
|
239
|
-
it('skips package.json if it already exists (skipIfExists: true)', async () => {
|
|
240
|
-
const session = makeSession();
|
|
241
|
-
const scaffolder = new PocScaffolder();
|
|
242
|
-
const ctx = PocScaffolder.buildContext(session, tmpDir);
|
|
243
|
-
|
|
244
|
-
// Create a custom package.json first
|
|
245
|
-
const { writeFile, mkdir } = await import('node:fs/promises');
|
|
246
|
-
await mkdir(tmpDir, { recursive: true });
|
|
247
|
-
await writeFile(
|
|
248
|
-
join(tmpDir, 'package.json'),
|
|
249
|
-
JSON.stringify({ name: 'custom', scripts: { test: 'echo custom' } }),
|
|
250
|
-
'utf-8',
|
|
251
|
-
);
|
|
252
|
-
|
|
253
|
-
// Scaffold should skip existing package.json
|
|
254
|
-
const result = await scaffolder.scaffold(ctx);
|
|
255
|
-
expect(result.skippedFiles).toContain('package.json');
|
|
256
|
-
|
|
257
|
-
// Custom content should be preserved
|
|
258
|
-
const content = await readFile(join(tmpDir, 'package.json'), 'utf-8');
|
|
259
|
-
const pkg = JSON.parse(content) as { name: string };
|
|
260
|
-
expect(pkg.name).toBe('custom');
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
it('always overwrites .gitignore (skipIfExists: false)', async () => {
|
|
264
|
-
const session = makeSession();
|
|
265
|
-
const scaffolder = new PocScaffolder();
|
|
266
|
-
const ctx = PocScaffolder.buildContext(session, tmpDir);
|
|
267
|
-
|
|
268
|
-
// Create a custom .gitignore first
|
|
269
|
-
const { writeFile } = await import('node:fs/promises');
|
|
270
|
-
await writeFile(join(tmpDir, '.gitignore'), '# custom\n', 'utf-8');
|
|
271
|
-
|
|
272
|
-
// Scaffold should overwrite .gitignore
|
|
273
|
-
await scaffolder.scaffold(ctx);
|
|
274
|
-
|
|
275
|
-
const content = await readFile(join(tmpDir, '.gitignore'), 'utf-8');
|
|
276
|
-
expect(content).toContain('node_modules/'); // Generated content
|
|
277
|
-
});
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
describe('getTemplateFiles', () => {
|
|
281
|
-
it('returns list of template file paths', () => {
|
|
282
|
-
const scaffolder = new PocScaffolder();
|
|
283
|
-
const files = scaffolder.getTemplateFiles();
|
|
284
|
-
expect(files).toContain('package.json');
|
|
285
|
-
expect(files).toContain('README.md');
|
|
286
|
-
expect(files).toContain('.gitignore');
|
|
287
|
-
expect(files).toContain('src/index.ts');
|
|
288
|
-
expect(files).toContain('tests/index.test.ts');
|
|
289
|
-
});
|
|
290
|
-
});
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
describe('validatePocOutput', () => {
|
|
294
|
-
let tmpDir: string;
|
|
295
|
-
|
|
296
|
-
beforeEach(async () => {
|
|
297
|
-
tmpDir = await mkdtemp(join(tmpdir(), 'sofia-validate-test-'));
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
afterEach(async () => {
|
|
301
|
-
await rm(tmpDir, { recursive: true, force: true });
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
it('returns valid=true for a complete scaffold', async () => {
|
|
305
|
-
const session = makeSession();
|
|
306
|
-
const scaffolder = new PocScaffolder();
|
|
307
|
-
const ctx = PocScaffolder.buildContext(session, tmpDir);
|
|
308
|
-
await scaffolder.scaffold(ctx);
|
|
309
|
-
|
|
310
|
-
const result = await validatePocOutput(tmpDir);
|
|
311
|
-
expect(result.valid).toBe(true);
|
|
312
|
-
expect(result.missingFiles).toEqual([]);
|
|
313
|
-
expect(result.errors).toEqual([]);
|
|
314
|
-
});
|
|
315
|
-
|
|
316
|
-
it('reports missing required files', async () => {
|
|
317
|
-
const result = await validatePocOutput(tmpDir);
|
|
318
|
-
expect(result.valid).toBe(false);
|
|
319
|
-
expect(result.missingFiles).toContain('package.json');
|
|
320
|
-
expect(result.missingFiles).toContain('README.md');
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
it('reports error when package.json lacks test script', async () => {
|
|
324
|
-
const { writeFile, mkdir } = await import('node:fs/promises');
|
|
325
|
-
await mkdir(join(tmpDir, 'src'), { recursive: true });
|
|
326
|
-
await mkdir(join(tmpDir, 'tests'), { recursive: true });
|
|
327
|
-
await writeFile(join(tmpDir, 'package.json'), JSON.stringify({ name: 'test', scripts: {} }), 'utf-8');
|
|
328
|
-
await writeFile(join(tmpDir, 'README.md'), '# Test', 'utf-8');
|
|
329
|
-
await writeFile(join(tmpDir, 'tsconfig.json'), JSON.stringify({}), 'utf-8');
|
|
330
|
-
await writeFile(join(tmpDir, '.gitignore'), 'node_modules/', 'utf-8');
|
|
331
|
-
await writeFile(join(tmpDir, '.sofia-metadata.json'), JSON.stringify({ sessionId: 'x' }), 'utf-8');
|
|
332
|
-
await writeFile(join(tmpDir, 'src/index.ts'), 'export function main() {}', 'utf-8');
|
|
333
|
-
await writeFile(join(tmpDir, 'tests/index.test.ts'), 'import { test } from "vitest"', 'utf-8');
|
|
334
|
-
|
|
335
|
-
const result = await validatePocOutput(tmpDir);
|
|
336
|
-
expect(result.errors).toContain('package.json is missing "test" script');
|
|
337
|
-
});
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
// ── Template entry construction (T036) ────────────────────────────────────
|
|
341
|
-
|
|
342
|
-
describe('PocScaffolder with TemplateEntry', () => {
|
|
343
|
-
let tmpDir: string;
|
|
344
|
-
|
|
345
|
-
beforeEach(async () => {
|
|
346
|
-
tmpDir = await mkdtemp(join(tmpdir(), 'scaffolder-template-'));
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
afterEach(async () => {
|
|
350
|
-
await rm(tmpDir, { recursive: true, force: true });
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
it('uses TemplateEntry.files when constructed with a template entry (T036)', async () => {
|
|
354
|
-
const { PYTHON_PYTEST_TEMPLATE } = await import('../../../src/develop/templateRegistry.js');
|
|
355
|
-
const scaffolder = new PocScaffolder(PYTHON_PYTEST_TEMPLATE);
|
|
356
|
-
|
|
357
|
-
// The template should contain Python-specific file paths
|
|
358
|
-
const filePaths = scaffolder.getTemplateFiles();
|
|
359
|
-
expect(filePaths).toContain('requirements.txt');
|
|
360
|
-
expect(filePaths).toContain('src/main.py');
|
|
361
|
-
expect(filePaths).toContain('tests/test_main.py');
|
|
362
|
-
// Should NOT contain TypeScript files
|
|
363
|
-
expect(filePaths).not.toContain('tsconfig.json');
|
|
364
|
-
expect(filePaths).not.toContain('src/index.ts');
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
it('uses TemplateEntry.techStack in buildContext (T010)', async () => {
|
|
368
|
-
const { PYTHON_PYTEST_TEMPLATE } = await import('../../../src/develop/templateRegistry.js');
|
|
369
|
-
const session = makeSession();
|
|
370
|
-
const ctx = PocScaffolder.buildContext(session, tmpDir, PYTHON_PYTEST_TEMPLATE);
|
|
371
|
-
expect(ctx.techStack.language).toBe('Python');
|
|
372
|
-
expect(ctx.techStack.runtime).toBe('Python 3.11');
|
|
373
|
-
});
|
|
374
|
-
|
|
375
|
-
it('falls back to default TypeScript techStack when no template entry provided', () => {
|
|
376
|
-
const session = makeSession();
|
|
377
|
-
const ctx = PocScaffolder.buildContext(session, tmpDir);
|
|
378
|
-
expect(ctx.techStack.language).toBe('TypeScript');
|
|
379
|
-
expect(ctx.techStack.runtime).toBe('Node.js 20');
|
|
380
|
-
});
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
// ── T072: TODO marker scanning records totalInitial, remaining, markers ───
|
|
384
|
-
|
|
385
|
-
describe('PocScaffolder.scanAndRecordTodos (T072)', () => {
|
|
386
|
-
let tmpDir: string;
|
|
387
|
-
|
|
388
|
-
beforeEach(async () => {
|
|
389
|
-
tmpDir = await mkdtemp(join(tmpdir(), 'todo-scan-'));
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
afterEach(async () => {
|
|
393
|
-
await rm(tmpDir, { recursive: true, force: true });
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
it('scans scaffold files for TODO markers and records counts in .sofia-metadata.json', async () => {
|
|
397
|
-
const { writeFile, mkdir } = await import('node:fs/promises');
|
|
398
|
-
|
|
399
|
-
// Create a minimal project with TODO markers
|
|
400
|
-
await mkdir(join(tmpDir, 'src'), { recursive: true });
|
|
401
|
-
await writeFile(
|
|
402
|
-
join(tmpDir, 'src/index.ts'),
|
|
403
|
-
'// TODO: Implement the core functionality\nexport function main() {}\n// TODO: Add error handling\n',
|
|
404
|
-
);
|
|
405
|
-
await writeFile(
|
|
406
|
-
join(tmpDir, 'src/utils.ts'),
|
|
407
|
-
'export function helper() { return 42; }\n',
|
|
408
|
-
);
|
|
409
|
-
await writeFile(
|
|
410
|
-
join(tmpDir, '.sofia-metadata.json'),
|
|
411
|
-
JSON.stringify({ sessionId: 'test-001', scaffoldedAt: new Date().toISOString() }),
|
|
412
|
-
);
|
|
413
|
-
|
|
414
|
-
const result = await PocScaffolder.scanAndRecordTodos(tmpDir);
|
|
415
|
-
|
|
416
|
-
// Verify return value
|
|
417
|
-
expect(result.totalInitial).toBe(2);
|
|
418
|
-
expect(result.remaining).toBe(2);
|
|
419
|
-
expect(result.markers).toHaveLength(2);
|
|
420
|
-
expect(result.markers[0]).toContain('src/index.ts:1');
|
|
421
|
-
expect(result.markers[0]).toContain('TODO:');
|
|
422
|
-
expect(result.markers[1]).toContain('src/index.ts:3');
|
|
423
|
-
|
|
424
|
-
// Verify .sofia-metadata.json was updated
|
|
425
|
-
const metaRaw = await readFile(join(tmpDir, '.sofia-metadata.json'), 'utf-8');
|
|
426
|
-
const metadata = JSON.parse(metaRaw);
|
|
427
|
-
expect(metadata.todos).toBeDefined();
|
|
428
|
-
expect(metadata.todos.totalInitial).toBe(2);
|
|
429
|
-
expect(metadata.todos.remaining).toBe(2);
|
|
430
|
-
expect(metadata.todos.markers).toHaveLength(2);
|
|
431
|
-
});
|
|
432
|
-
|
|
433
|
-
it('records zero TODOs when no markers exist', async () => {
|
|
434
|
-
const { writeFile } = await import('node:fs/promises');
|
|
435
|
-
|
|
436
|
-
await writeFile(
|
|
437
|
-
join(tmpDir, 'index.ts'),
|
|
438
|
-
'export function main() { return "clean"; }\n',
|
|
439
|
-
);
|
|
440
|
-
await writeFile(
|
|
441
|
-
join(tmpDir, '.sofia-metadata.json'),
|
|
442
|
-
JSON.stringify({ sessionId: 'test-002' }),
|
|
443
|
-
);
|
|
444
|
-
|
|
445
|
-
const result = await PocScaffolder.scanAndRecordTodos(tmpDir);
|
|
446
|
-
|
|
447
|
-
expect(result.totalInitial).toBe(0);
|
|
448
|
-
expect(result.remaining).toBe(0);
|
|
449
|
-
expect(result.markers).toHaveLength(0);
|
|
450
|
-
});
|
|
451
|
-
});
|