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.
- 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 +3 -3
- 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 -329
- 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,80 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for workshopCommand client creation — silent mock fallback bug.
|
|
3
|
-
*
|
|
4
|
-
* Verifies:
|
|
5
|
-
* - When createCopilotClient() throws, the error is logged (not swallowed)
|
|
6
|
-
* - The fake client is never silently substituted in production mode
|
|
7
|
-
* - The user receives a clear error message when the SDK is unavailable
|
|
8
|
-
*/
|
|
9
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
10
|
-
// Mock copilotClient module so createCopilotClient always rejects
|
|
11
|
-
vi.mock('../../../src/shared/copilotClient.js', async (importOriginal) => {
|
|
12
|
-
const orig = await importOriginal();
|
|
13
|
-
return {
|
|
14
|
-
...orig,
|
|
15
|
-
createCopilotClient: vi.fn().mockRejectedValue(new Error('SDK not available')),
|
|
16
|
-
};
|
|
17
|
-
});
|
|
18
|
-
// Mock the logger so we can assert error logging
|
|
19
|
-
vi.mock('../../../src/logging/logger.js', () => {
|
|
20
|
-
const fakeLogger = {
|
|
21
|
-
error: vi.fn(),
|
|
22
|
-
warn: vi.fn(),
|
|
23
|
-
info: vi.fn(),
|
|
24
|
-
debug: vi.fn(),
|
|
25
|
-
fatal: vi.fn(),
|
|
26
|
-
child: vi.fn().mockReturnThis(),
|
|
27
|
-
};
|
|
28
|
-
return {
|
|
29
|
-
getLogger: vi.fn(() => fakeLogger),
|
|
30
|
-
createLogger: vi.fn(() => fakeLogger),
|
|
31
|
-
initGlobalLogger: vi.fn(() => fakeLogger),
|
|
32
|
-
};
|
|
33
|
-
});
|
|
34
|
-
import { workshopCommand } from '../../../src/cli/workshopCommand.js';
|
|
35
|
-
import { getLogger } from '../../../src/logging/logger.js';
|
|
36
|
-
describe('workshopCommand — client creation failure', () => {
|
|
37
|
-
let exitCodeBefore;
|
|
38
|
-
beforeEach(() => {
|
|
39
|
-
exitCodeBefore = process.exitCode;
|
|
40
|
-
process.exitCode = undefined;
|
|
41
|
-
});
|
|
42
|
-
afterEach(() => {
|
|
43
|
-
process.exitCode = exitCodeBefore;
|
|
44
|
-
vi.restoreAllMocks();
|
|
45
|
-
});
|
|
46
|
-
it('logs the error when createCopilotClient fails', async () => {
|
|
47
|
-
// Run with --new-session --non-interactive so no menus block
|
|
48
|
-
await workshopCommand({
|
|
49
|
-
newSession: true,
|
|
50
|
-
nonInteractive: true,
|
|
51
|
-
json: true,
|
|
52
|
-
});
|
|
53
|
-
const logger = getLogger();
|
|
54
|
-
expect(logger.error).toHaveBeenCalledWith(expect.objectContaining({ err: expect.any(Error) }), expect.stringContaining('Copilot'));
|
|
55
|
-
});
|
|
56
|
-
it('sets non-zero exit code when SDK is unavailable', async () => {
|
|
57
|
-
await workshopCommand({
|
|
58
|
-
newSession: true,
|
|
59
|
-
nonInteractive: true,
|
|
60
|
-
json: true,
|
|
61
|
-
});
|
|
62
|
-
expect(process.exitCode).not.toBe(0);
|
|
63
|
-
expect(process.exitCode).toBeDefined();
|
|
64
|
-
});
|
|
65
|
-
it('does not silently fall back to fake client', async () => {
|
|
66
|
-
// Capture stdout to check there is no fake "Welcome" response
|
|
67
|
-
const writeSpy = vi.spyOn(process.stdout, 'write').mockImplementation(() => true);
|
|
68
|
-
await workshopCommand({
|
|
69
|
-
newSession: true,
|
|
70
|
-
nonInteractive: true,
|
|
71
|
-
json: true,
|
|
72
|
-
});
|
|
73
|
-
const allOutput = writeSpy.mock.calls.map(c => String(c[0])).join('');
|
|
74
|
-
// Should NOT contain the canned fake response
|
|
75
|
-
expect(allOutput).not.toContain('Welcome to the AI Discovery Workshop');
|
|
76
|
-
// Should contain an error indication
|
|
77
|
-
expect(allOutput).toContain('error');
|
|
78
|
-
writeSpy.mockRestore();
|
|
79
|
-
});
|
|
80
|
-
});
|
|
@@ -1,329 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for workshopCommand — session name display, Plan→Develop
|
|
3
|
-
* transition guidance (T052), and auto-transition prompt (T053).
|
|
4
|
-
*/
|
|
5
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
6
|
-
// ── Mocks ───────────────────────────────────────────────────────────────────
|
|
7
|
-
const mockStore = {
|
|
8
|
-
load: vi.fn(),
|
|
9
|
-
save: vi.fn().mockResolvedValue(undefined),
|
|
10
|
-
exists: vi.fn(),
|
|
11
|
-
list: vi.fn(),
|
|
12
|
-
};
|
|
13
|
-
vi.mock('../../../src/sessions/sessionStore.js', () => ({
|
|
14
|
-
createDefaultStore: () => mockStore,
|
|
15
|
-
SessionStore: vi.fn(),
|
|
16
|
-
}));
|
|
17
|
-
// Minimal fake CopilotClient
|
|
18
|
-
const fakeClient = {
|
|
19
|
-
async createSession() {
|
|
20
|
-
return {
|
|
21
|
-
send: async function* () {
|
|
22
|
-
yield { type: 'TextDelta', text: 'Hello!', timestamp: new Date().toISOString() };
|
|
23
|
-
},
|
|
24
|
-
getHistory: () => [],
|
|
25
|
-
};
|
|
26
|
-
},
|
|
27
|
-
};
|
|
28
|
-
vi.mock('../../../src/shared/copilotClient.js', async (importOriginal) => {
|
|
29
|
-
const orig = await importOriginal();
|
|
30
|
-
return {
|
|
31
|
-
...orig,
|
|
32
|
-
createCopilotClient: vi.fn().mockResolvedValue(fakeClient),
|
|
33
|
-
};
|
|
34
|
-
});
|
|
35
|
-
vi.mock('../../../src/logging/logger.js', () => {
|
|
36
|
-
const fakeLogger = {
|
|
37
|
-
error: vi.fn(),
|
|
38
|
-
warn: vi.fn(),
|
|
39
|
-
info: vi.fn(),
|
|
40
|
-
debug: vi.fn(),
|
|
41
|
-
fatal: vi.fn(),
|
|
42
|
-
child: vi.fn().mockReturnThis(),
|
|
43
|
-
};
|
|
44
|
-
return {
|
|
45
|
-
getLogger: vi.fn(() => fakeLogger),
|
|
46
|
-
createLogger: vi.fn(() => fakeLogger),
|
|
47
|
-
initGlobalLogger: vi.fn(() => fakeLogger),
|
|
48
|
-
};
|
|
49
|
-
});
|
|
50
|
-
// Track LoopIO writes — decision gate is configurable per test
|
|
51
|
-
let ioWrites = [];
|
|
52
|
-
let ioReadResponses = [];
|
|
53
|
-
let ioReadIndex = 0;
|
|
54
|
-
let ioReadInputPrompts = [];
|
|
55
|
-
let decisionGateResponses = [];
|
|
56
|
-
let decisionGateIndex = 0;
|
|
57
|
-
vi.mock('../../../src/cli/ioContext.js', () => ({
|
|
58
|
-
createLoopIO: () => ({
|
|
59
|
-
write: (text) => {
|
|
60
|
-
ioWrites.push(text);
|
|
61
|
-
},
|
|
62
|
-
writeActivity: () => { },
|
|
63
|
-
readInput: async (prompt) => {
|
|
64
|
-
if (prompt)
|
|
65
|
-
ioReadInputPrompts.push(prompt);
|
|
66
|
-
if (ioReadIndex >= ioReadResponses.length)
|
|
67
|
-
return null;
|
|
68
|
-
return ioReadResponses[ioReadIndex++];
|
|
69
|
-
},
|
|
70
|
-
showDecisionGate: async () => {
|
|
71
|
-
if (decisionGateIndex < decisionGateResponses.length) {
|
|
72
|
-
return decisionGateResponses[decisionGateIndex++];
|
|
73
|
-
}
|
|
74
|
-
return { choice: 'exit' };
|
|
75
|
-
},
|
|
76
|
-
isJsonMode: false,
|
|
77
|
-
isTTY: true,
|
|
78
|
-
}),
|
|
79
|
-
}));
|
|
80
|
-
// Mock phaseHandlers to avoid loading prompts
|
|
81
|
-
vi.mock('../../../src/phases/phaseHandlers.js', async (importOriginal) => {
|
|
82
|
-
const orig = await importOriginal();
|
|
83
|
-
return {
|
|
84
|
-
...orig,
|
|
85
|
-
createPhaseHandler: () => ({
|
|
86
|
-
phase: 'Discover',
|
|
87
|
-
buildSystemPrompt: () => 'system prompt',
|
|
88
|
-
getReferences: () => [],
|
|
89
|
-
extractResult: () => ({}),
|
|
90
|
-
isComplete: () => false,
|
|
91
|
-
_preload: async () => { },
|
|
92
|
-
}),
|
|
93
|
-
};
|
|
94
|
-
});
|
|
95
|
-
describe('workshopCommand session name display (T064b)', () => {
|
|
96
|
-
beforeEach(() => {
|
|
97
|
-
ioWrites = [];
|
|
98
|
-
ioReadResponses = [];
|
|
99
|
-
ioReadIndex = 0;
|
|
100
|
-
ioReadInputPrompts = [];
|
|
101
|
-
decisionGateResponses = [];
|
|
102
|
-
decisionGateIndex = 0;
|
|
103
|
-
vi.clearAllMocks();
|
|
104
|
-
process.exitCode = undefined;
|
|
105
|
-
});
|
|
106
|
-
afterEach(() => {
|
|
107
|
-
process.exitCode = undefined;
|
|
108
|
-
});
|
|
109
|
-
it('displays session name on new session creation message', async () => {
|
|
110
|
-
// Simulate: main menu → choose "new" (option 1) → session starts → exit
|
|
111
|
-
ioReadResponses = ['1']; // Pick new session
|
|
112
|
-
mockStore.list.mockResolvedValue([]);
|
|
113
|
-
const { workshopCommand } = await import('../../../src/cli/workshopCommand.js');
|
|
114
|
-
await workshopCommand({});
|
|
115
|
-
const allOutput = ioWrites.join(' ');
|
|
116
|
-
// New session should show session ID (timestamp format)
|
|
117
|
-
expect(allOutput).toContain('New session');
|
|
118
|
-
});
|
|
119
|
-
it('displays session name when resuming a named session', async () => {
|
|
120
|
-
const session = {
|
|
121
|
-
sessionId: '2026-01-01_120000',
|
|
122
|
-
schemaVersion: '1.0.0',
|
|
123
|
-
createdAt: '2026-01-01T12:00:00Z',
|
|
124
|
-
updatedAt: '2026-01-01T12:30:00Z',
|
|
125
|
-
phase: 'Discover',
|
|
126
|
-
status: 'Active',
|
|
127
|
-
participants: [],
|
|
128
|
-
artifacts: { generatedFiles: [] },
|
|
129
|
-
turns: [],
|
|
130
|
-
name: 'Logistics AI Workshop',
|
|
131
|
-
};
|
|
132
|
-
mockStore.exists.mockResolvedValue(true);
|
|
133
|
-
mockStore.load.mockResolvedValue(session);
|
|
134
|
-
const { workshopCommand } = await import('../../../src/cli/workshopCommand.js');
|
|
135
|
-
await workshopCommand({ session: '2026-01-01_120000' });
|
|
136
|
-
// The session name should appear in the output somewhere
|
|
137
|
-
const allOutput = ioWrites.join(' ');
|
|
138
|
-
expect(allOutput).toContain('Logistics AI Workshop');
|
|
139
|
-
});
|
|
140
|
-
it('displays session name in pause message when session has a name', async () => {
|
|
141
|
-
const session = {
|
|
142
|
-
sessionId: '2026-01-01_120000',
|
|
143
|
-
schemaVersion: '1.0.0',
|
|
144
|
-
createdAt: '2026-01-01T12:00:00Z',
|
|
145
|
-
updatedAt: '2026-01-01T12:30:00Z',
|
|
146
|
-
phase: 'Discover',
|
|
147
|
-
status: 'Active',
|
|
148
|
-
participants: [],
|
|
149
|
-
artifacts: { generatedFiles: [] },
|
|
150
|
-
turns: [],
|
|
151
|
-
name: 'Retail Insights',
|
|
152
|
-
};
|
|
153
|
-
mockStore.exists.mockResolvedValue(true);
|
|
154
|
-
mockStore.load.mockResolvedValue(session);
|
|
155
|
-
const { workshopCommand } = await import('../../../src/cli/workshopCommand.js');
|
|
156
|
-
// Direct session mode triggers run → decision gate returns exit → pauses
|
|
157
|
-
await workshopCommand({ session: '2026-01-01_120000' });
|
|
158
|
-
const allOutput = ioWrites.join(' ');
|
|
159
|
-
expect(allOutput).toContain('Retail Insights');
|
|
160
|
-
});
|
|
161
|
-
it('shows only session ID when no name is set', async () => {
|
|
162
|
-
const session = {
|
|
163
|
-
sessionId: '2026-01-01_120000',
|
|
164
|
-
schemaVersion: '1.0.0',
|
|
165
|
-
createdAt: '2026-01-01T12:00:00Z',
|
|
166
|
-
updatedAt: '2026-01-01T12:30:00Z',
|
|
167
|
-
phase: 'Discover',
|
|
168
|
-
status: 'Active',
|
|
169
|
-
participants: [],
|
|
170
|
-
artifacts: { generatedFiles: [] },
|
|
171
|
-
turns: [],
|
|
172
|
-
};
|
|
173
|
-
mockStore.exists.mockResolvedValue(true);
|
|
174
|
-
mockStore.load.mockResolvedValue(session);
|
|
175
|
-
const { workshopCommand } = await import('../../../src/cli/workshopCommand.js');
|
|
176
|
-
await workshopCommand({ session: '2026-01-01_120000' });
|
|
177
|
-
const allOutput = ioWrites.join(' ');
|
|
178
|
-
expect(allOutput).toContain('2026-01-01_120000');
|
|
179
|
-
});
|
|
180
|
-
it('shows only named sessions in the resume list', async () => {
|
|
181
|
-
const namedSession = {
|
|
182
|
-
sessionId: '2026-02-28_165120',
|
|
183
|
-
schemaVersion: '1.0.0',
|
|
184
|
-
createdAt: '2026-02-28T16:51:20Z',
|
|
185
|
-
updatedAt: '2026-02-28T16:55:00Z',
|
|
186
|
-
phase: 'Discover',
|
|
187
|
-
status: 'Active',
|
|
188
|
-
participants: [],
|
|
189
|
-
artifacts: { generatedFiles: [] },
|
|
190
|
-
turns: [],
|
|
191
|
-
name: 'Inventory Insights',
|
|
192
|
-
};
|
|
193
|
-
const unnamedSession = {
|
|
194
|
-
sessionId: '2026-02-28_170457',
|
|
195
|
-
schemaVersion: '1.0.0',
|
|
196
|
-
createdAt: '2026-02-28T17:04:57Z',
|
|
197
|
-
updatedAt: '2026-02-28T17:05:00Z',
|
|
198
|
-
phase: 'Discover',
|
|
199
|
-
status: 'Active',
|
|
200
|
-
participants: [],
|
|
201
|
-
artifacts: { generatedFiles: [] },
|
|
202
|
-
turns: [],
|
|
203
|
-
};
|
|
204
|
-
ioReadResponses = ['2', '1']; // Resume flow, choose first session
|
|
205
|
-
mockStore.list.mockResolvedValue([namedSession.sessionId, unnamedSession.sessionId]);
|
|
206
|
-
mockStore.load.mockImplementation(async (id) => {
|
|
207
|
-
if (id === namedSession.sessionId)
|
|
208
|
-
return namedSession;
|
|
209
|
-
if (id === unnamedSession.sessionId)
|
|
210
|
-
return unnamedSession;
|
|
211
|
-
throw new Error('Unknown session');
|
|
212
|
-
});
|
|
213
|
-
const { workshopCommand } = await import('../../../src/cli/workshopCommand.js');
|
|
214
|
-
await workshopCommand({});
|
|
215
|
-
const allOutput = ioWrites.join(' ');
|
|
216
|
-
expect(allOutput).toContain('Available sessions');
|
|
217
|
-
// Named session appears in the list
|
|
218
|
-
expect(allOutput).toContain('Inventory Insights');
|
|
219
|
-
expect(allOutput).toContain('2026-02-28_165120');
|
|
220
|
-
// Unnamed session is filtered out
|
|
221
|
-
expect(allOutput).not.toContain('2026-02-28_170457');
|
|
222
|
-
});
|
|
223
|
-
});
|
|
224
|
-
// ── T052: Plan→Develop transition displays "sofia dev --session {id}" ─────
|
|
225
|
-
describe('workshopCommand Plan→Develop transition (T052)', () => {
|
|
226
|
-
beforeEach(() => {
|
|
227
|
-
ioWrites = [];
|
|
228
|
-
ioReadResponses = [];
|
|
229
|
-
ioReadIndex = 0;
|
|
230
|
-
ioReadInputPrompts = [];
|
|
231
|
-
decisionGateResponses = [];
|
|
232
|
-
decisionGateIndex = 0;
|
|
233
|
-
vi.clearAllMocks();
|
|
234
|
-
process.exitCode = undefined;
|
|
235
|
-
});
|
|
236
|
-
afterEach(() => {
|
|
237
|
-
process.exitCode = undefined;
|
|
238
|
-
});
|
|
239
|
-
it('displays "sofia dev --session {id}" after Plan phase completes (FR-020)', async () => {
|
|
240
|
-
const session = {
|
|
241
|
-
sessionId: 'plan-test-001',
|
|
242
|
-
schemaVersion: '1.0.0',
|
|
243
|
-
createdAt: '2026-01-01T12:00:00Z',
|
|
244
|
-
updatedAt: '2026-01-01T12:30:00Z',
|
|
245
|
-
phase: 'Plan',
|
|
246
|
-
status: 'Active',
|
|
247
|
-
participants: [],
|
|
248
|
-
artifacts: { generatedFiles: [] },
|
|
249
|
-
turns: [],
|
|
250
|
-
};
|
|
251
|
-
mockStore.exists.mockResolvedValue(true);
|
|
252
|
-
mockStore.load.mockResolvedValue(session);
|
|
253
|
-
// First gate (Plan): continue → triggers transition to Develop
|
|
254
|
-
// Second gate (Develop): exit → stops the loop
|
|
255
|
-
decisionGateResponses = [{ choice: 'continue' }, { choice: 'exit' }];
|
|
256
|
-
const { workshopCommand } = await import('../../../src/cli/workshopCommand.js');
|
|
257
|
-
await workshopCommand({ session: 'plan-test-001' });
|
|
258
|
-
const allOutput = ioWrites.join(' ');
|
|
259
|
-
expect(allOutput).toContain('sofia dev --session plan-test-001');
|
|
260
|
-
expect(allOutput).toContain('Ready for PoC Generation');
|
|
261
|
-
});
|
|
262
|
-
});
|
|
263
|
-
// ── T053: Workshop offers auto-transition prompt in interactive mode ──────
|
|
264
|
-
describe('workshopCommand auto-transition prompt (T053)', () => {
|
|
265
|
-
beforeEach(() => {
|
|
266
|
-
ioWrites = [];
|
|
267
|
-
ioReadResponses = [];
|
|
268
|
-
ioReadIndex = 0;
|
|
269
|
-
ioReadInputPrompts = [];
|
|
270
|
-
decisionGateResponses = [];
|
|
271
|
-
decisionGateIndex = 0;
|
|
272
|
-
vi.clearAllMocks();
|
|
273
|
-
process.exitCode = undefined;
|
|
274
|
-
});
|
|
275
|
-
afterEach(() => {
|
|
276
|
-
process.exitCode = undefined;
|
|
277
|
-
});
|
|
278
|
-
it('offers auto-transition prompt in interactive mode after Plan phase (FR-021)', async () => {
|
|
279
|
-
const session = {
|
|
280
|
-
sessionId: 'transition-test-001',
|
|
281
|
-
schemaVersion: '1.0.0',
|
|
282
|
-
createdAt: '2026-01-01T12:00:00Z',
|
|
283
|
-
updatedAt: '2026-01-01T12:30:00Z',
|
|
284
|
-
phase: 'Plan',
|
|
285
|
-
status: 'Active',
|
|
286
|
-
participants: [],
|
|
287
|
-
artifacts: { generatedFiles: [] },
|
|
288
|
-
turns: [],
|
|
289
|
-
};
|
|
290
|
-
mockStore.exists.mockResolvedValue(true);
|
|
291
|
-
mockStore.load.mockResolvedValue(session);
|
|
292
|
-
// Continue from Plan → Develop, then exit
|
|
293
|
-
decisionGateResponses = [{ choice: 'continue' }, { choice: 'exit' }];
|
|
294
|
-
const { workshopCommand } = await import('../../../src/cli/workshopCommand.js');
|
|
295
|
-
await workshopCommand({ session: 'transition-test-001' });
|
|
296
|
-
const allOutput = ioWrites.join(' ');
|
|
297
|
-
// FR-021: The transition message includes tech stack info and scaffolding description
|
|
298
|
-
expect(allOutput).toContain('scaffold a project');
|
|
299
|
-
expect(allOutput).toContain('technology stack');
|
|
300
|
-
expect(allOutput).toContain('install dependencies');
|
|
301
|
-
// FR-021: readInput was called with the auto-transition prompt
|
|
302
|
-
expect(ioReadInputPrompts.some((p) => p.includes('Would you like to start PoC development'))).toBe(true);
|
|
303
|
-
});
|
|
304
|
-
it('exits workshop when user accepts auto-transition prompt (FR-021)', async () => {
|
|
305
|
-
const session = {
|
|
306
|
-
sessionId: 'auto-transition-001',
|
|
307
|
-
schemaVersion: '1.0.0',
|
|
308
|
-
createdAt: '2026-01-01T12:00:00Z',
|
|
309
|
-
updatedAt: '2026-01-01T12:30:00Z',
|
|
310
|
-
phase: 'Plan',
|
|
311
|
-
status: 'Active',
|
|
312
|
-
participants: [],
|
|
313
|
-
artifacts: { generatedFiles: [] },
|
|
314
|
-
turns: [],
|
|
315
|
-
};
|
|
316
|
-
mockStore.exists.mockResolvedValue(true);
|
|
317
|
-
mockStore.load.mockResolvedValue(session);
|
|
318
|
-
// Continue from Plan → user answers 'y' to auto-transition
|
|
319
|
-
decisionGateResponses = [{ choice: 'continue' }];
|
|
320
|
-
// First null exits the conversation loop; 'y' answers the auto-transition prompt
|
|
321
|
-
ioReadResponses = [null, 'y'];
|
|
322
|
-
const { workshopCommand } = await import('../../../src/cli/workshopCommand.js');
|
|
323
|
-
await workshopCommand({ session: 'auto-transition-001' });
|
|
324
|
-
const allOutput = ioWrites.join(' ');
|
|
325
|
-
// Workshop should show transition message and exit (returning early)
|
|
326
|
-
expect(allOutput).toContain('Starting PoC development');
|
|
327
|
-
expect(allOutput).toContain('sofia dev --session auto-transition-001');
|
|
328
|
-
});
|
|
329
|
-
});
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import liveConfig from '../../../vitest.live.config';
|
|
3
|
-
import defaultConfig from '../../../vitest.config';
|
|
4
|
-
describe('Vitest env setup', () => {
|
|
5
|
-
it('declares a setup file for default tests', () => {
|
|
6
|
-
const config = defaultConfig;
|
|
7
|
-
expect(config.test?.setupFiles).toContain('tests/setup/loadEnv.ts');
|
|
8
|
-
});
|
|
9
|
-
it('declares a setup file for live tests', () => {
|
|
10
|
-
const config = liveConfig;
|
|
11
|
-
expect(config.test?.setupFiles).toContain('tests/setup/loadEnv.ts');
|
|
12
|
-
});
|
|
13
|
-
});
|