Skip to content

Commit 4ae0b61

Browse files
authored
Merge branch 'main' into feature/fix-claude-code-env-varaible-command
2 parents 0ee3e4d + ea42521 commit 4ae0b61

16 files changed

Lines changed: 1348 additions & 153 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,4 @@ bundled/
108108
/.mcpregistry_registry_token
109109
/key.pem
110110
.mcpli
111+
.factory

docs/session_management_plan.md

Lines changed: 485 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/**
2+
* Tests for session-management workflow metadata
3+
*/
4+
import { describe, it, expect } from 'vitest';
5+
import { workflow } from '../index.ts';
6+
7+
describe('session-management workflow metadata', () => {
8+
describe('Workflow Structure', () => {
9+
it('should export workflow object with required properties', () => {
10+
expect(workflow).toHaveProperty('name');
11+
expect(workflow).toHaveProperty('description');
12+
expect(workflow).toHaveProperty('platforms');
13+
expect(workflow).toHaveProperty('targets');
14+
expect(workflow).toHaveProperty('capabilities');
15+
});
16+
17+
it('should have correct workflow name', () => {
18+
expect(workflow.name).toBe('session-management');
19+
});
20+
21+
it('should have correct description', () => {
22+
expect(workflow.description).toBe(
23+
'Manage session defaults for projectPath/workspacePath, scheme, configuration, simulatorName/simulatorId, deviceId, useLatestOS and arch. These defaults are required by many tools and must be set before attempting to call tools that would depend on these values.',
24+
);
25+
});
26+
27+
it('should have correct platforms array', () => {
28+
expect(workflow.platforms).toEqual(['iOS', 'macOS', 'tvOS', 'watchOS', 'visionOS']);
29+
});
30+
31+
it('should have correct targets array', () => {
32+
expect(workflow.targets).toEqual(['simulator', 'device']);
33+
});
34+
35+
it('should have correct capabilities array', () => {
36+
expect(workflow.capabilities).toEqual(['configuration', 'state-management']);
37+
});
38+
});
39+
40+
describe('Workflow Validation', () => {
41+
it('should have valid string properties', () => {
42+
expect(typeof workflow.name).toBe('string');
43+
expect(typeof workflow.description).toBe('string');
44+
expect(workflow.name.length).toBeGreaterThan(0);
45+
expect(workflow.description.length).toBeGreaterThan(0);
46+
});
47+
48+
it('should have valid array properties', () => {
49+
expect(Array.isArray(workflow.platforms)).toBe(true);
50+
expect(Array.isArray(workflow.targets)).toBe(true);
51+
expect(Array.isArray(workflow.capabilities)).toBe(true);
52+
53+
expect(workflow.platforms.length).toBeGreaterThan(0);
54+
expect(workflow.targets.length).toBeGreaterThan(0);
55+
expect(workflow.capabilities.length).toBeGreaterThan(0);
56+
});
57+
});
58+
});
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2+
import { sessionStore } from '../../../../utils/session-store.ts';
3+
import plugin, { sessionClearDefaultsLogic } from '../session_clear_defaults.ts';
4+
5+
describe('session-clear-defaults tool', () => {
6+
beforeEach(() => {
7+
sessionStore.clear();
8+
sessionStore.setDefaults({
9+
scheme: 'MyScheme',
10+
projectPath: '/path/to/proj.xcodeproj',
11+
simulatorName: 'iPhone 16',
12+
deviceId: 'DEVICE-123',
13+
useLatestOS: true,
14+
arch: 'arm64',
15+
});
16+
});
17+
18+
afterEach(() => {
19+
sessionStore.clear();
20+
});
21+
22+
describe('Export Field Validation (Literal)', () => {
23+
it('should have correct name', () => {
24+
expect(plugin.name).toBe('session-clear-defaults');
25+
});
26+
27+
it('should have correct description', () => {
28+
expect(plugin.description).toBe('Clear selected or all session defaults.');
29+
});
30+
31+
it('should have handler function', () => {
32+
expect(typeof plugin.handler).toBe('function');
33+
});
34+
35+
it('should have schema object', () => {
36+
expect(plugin.schema).toBeDefined();
37+
expect(typeof plugin.schema).toBe('object');
38+
});
39+
});
40+
41+
describe('Handler Behavior', () => {
42+
it('should clear specific keys when provided', async () => {
43+
const result = await sessionClearDefaultsLogic({ keys: ['scheme', 'deviceId'] });
44+
expect(result.isError).toBe(false);
45+
expect(result.content[0].text).toContain('Session defaults cleared');
46+
47+
const current = sessionStore.getAll();
48+
expect(current.scheme).toBeUndefined();
49+
expect(current.deviceId).toBeUndefined();
50+
expect(current.projectPath).toBe('/path/to/proj.xcodeproj');
51+
expect(current.simulatorName).toBe('iPhone 16');
52+
expect(current.useLatestOS).toBe(true);
53+
expect(current.arch).toBe('arm64');
54+
});
55+
56+
it('should clear all when all=true', async () => {
57+
const result = await sessionClearDefaultsLogic({ all: true });
58+
expect(result.isError).toBe(false);
59+
expect(result.content[0].text).toBe('Session defaults cleared');
60+
61+
const current = sessionStore.getAll();
62+
expect(Object.keys(current).length).toBe(0);
63+
});
64+
65+
it('should clear all when no params provided', async () => {
66+
const result = await sessionClearDefaultsLogic({});
67+
expect(result.isError).toBe(false);
68+
const current = sessionStore.getAll();
69+
expect(Object.keys(current).length).toBe(0);
70+
});
71+
72+
it('should validate keys enum', async () => {
73+
const result = (await plugin.handler({ keys: ['invalid' as any] })) as any;
74+
expect(result.isError).toBe(true);
75+
expect(result.content[0].text).toContain('Parameter validation failed');
76+
expect(result.content[0].text).toContain('keys');
77+
});
78+
});
79+
});
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { describe, it, expect, beforeEach } from 'vitest';
2+
import { sessionStore } from '../../../../utils/session-store.ts';
3+
import plugin, { sessionSetDefaultsLogic } from '../session_set_defaults.ts';
4+
5+
describe('session-set-defaults tool', () => {
6+
beforeEach(() => {
7+
sessionStore.clear();
8+
});
9+
10+
describe('Export Field Validation (Literal)', () => {
11+
it('should have correct name', () => {
12+
expect(plugin.name).toBe('session-set-defaults');
13+
});
14+
15+
it('should have correct description', () => {
16+
expect(plugin.description).toBe(
17+
'Set the session defaults needed by many tools. Most tools require one or more session defaults to be set before they can be used. Agents should set the relevant defaults at the beginning of a session.',
18+
);
19+
});
20+
21+
it('should have handler function', () => {
22+
expect(typeof plugin.handler).toBe('function');
23+
});
24+
25+
it('should have schema object', () => {
26+
expect(plugin.schema).toBeDefined();
27+
expect(typeof plugin.schema).toBe('object');
28+
});
29+
});
30+
31+
describe('Handler Behavior', () => {
32+
it('should set provided defaults and return updated state', async () => {
33+
const result = await sessionSetDefaultsLogic({
34+
scheme: 'MyScheme',
35+
simulatorName: 'iPhone 16',
36+
useLatestOS: true,
37+
arch: 'arm64',
38+
});
39+
40+
expect(result.isError).toBe(false);
41+
expect(result.content[0].text).toContain('Defaults updated:');
42+
43+
const current = sessionStore.getAll();
44+
expect(current.scheme).toBe('MyScheme');
45+
expect(current.simulatorName).toBe('iPhone 16');
46+
expect(current.useLatestOS).toBe(true);
47+
expect(current.arch).toBe('arm64');
48+
});
49+
50+
it('should validate parameter types via Zod', async () => {
51+
const result = await plugin.handler({
52+
useLatestOS: 'yes' as unknown as boolean,
53+
});
54+
55+
expect(result.isError).toBe(true);
56+
expect(result.content[0].text).toContain('Parameter validation failed');
57+
expect(result.content[0].text).toContain('useLatestOS');
58+
});
59+
60+
it('should clear workspacePath when projectPath is set', async () => {
61+
sessionStore.setDefaults({ workspacePath: '/old/App.xcworkspace' });
62+
await sessionSetDefaultsLogic({ projectPath: '/new/App.xcodeproj' });
63+
const current = sessionStore.getAll();
64+
expect(current.projectPath).toBe('/new/App.xcodeproj');
65+
expect(current.workspacePath).toBeUndefined();
66+
});
67+
68+
it('should clear projectPath when workspacePath is set', async () => {
69+
sessionStore.setDefaults({ projectPath: '/old/App.xcodeproj' });
70+
await sessionSetDefaultsLogic({ workspacePath: '/new/App.xcworkspace' });
71+
const current = sessionStore.getAll();
72+
expect(current.workspacePath).toBe('/new/App.xcworkspace');
73+
expect(current.projectPath).toBeUndefined();
74+
});
75+
76+
it('should clear simulatorName when simulatorId is set', async () => {
77+
sessionStore.setDefaults({ simulatorName: 'iPhone 16' });
78+
await sessionSetDefaultsLogic({ simulatorId: 'SIM-UUID' });
79+
const current = sessionStore.getAll();
80+
expect(current.simulatorId).toBe('SIM-UUID');
81+
expect(current.simulatorName).toBeUndefined();
82+
});
83+
84+
it('should clear simulatorId when simulatorName is set', async () => {
85+
sessionStore.setDefaults({ simulatorId: 'SIM-UUID' });
86+
await sessionSetDefaultsLogic({ simulatorName: 'iPhone 16' });
87+
const current = sessionStore.getAll();
88+
expect(current.simulatorName).toBe('iPhone 16');
89+
expect(current.simulatorId).toBeUndefined();
90+
});
91+
92+
it('should reject when both projectPath and workspacePath are provided', async () => {
93+
const res = await plugin.handler({
94+
projectPath: '/app/App.xcodeproj',
95+
workspacePath: '/app/App.xcworkspace',
96+
});
97+
expect(res.isError).toBe(true);
98+
expect(res.content[0].text).toContain('Parameter validation failed');
99+
expect(res.content[0].text).toContain('projectPath and workspacePath are mutually exclusive');
100+
});
101+
102+
it('should reject when both simulatorId and simulatorName are provided', async () => {
103+
const res = await plugin.handler({
104+
simulatorId: 'SIM-1',
105+
simulatorName: 'iPhone 16',
106+
});
107+
expect(res.isError).toBe(true);
108+
expect(res.content[0].text).toContain('Parameter validation failed');
109+
expect(res.content[0].text).toContain('simulatorId and simulatorName are mutually exclusive');
110+
});
111+
});
112+
});
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2+
import { sessionStore } from '../../../../utils/session-store.ts';
3+
import plugin from '../session_show_defaults.ts';
4+
5+
describe('session-show-defaults tool', () => {
6+
beforeEach(() => {
7+
sessionStore.clear();
8+
});
9+
10+
afterEach(() => {
11+
sessionStore.clear();
12+
});
13+
14+
describe('Export Field Validation (Literal)', () => {
15+
it('should have correct name', () => {
16+
expect(plugin.name).toBe('session-show-defaults');
17+
});
18+
19+
it('should have correct description', () => {
20+
expect(plugin.description).toBe('Show current session defaults.');
21+
});
22+
23+
it('should have handler function', () => {
24+
expect(typeof plugin.handler).toBe('function');
25+
});
26+
27+
it('should have empty schema', () => {
28+
expect(plugin.schema).toEqual({});
29+
});
30+
});
31+
32+
describe('Handler Behavior', () => {
33+
it('should return empty defaults when none set', async () => {
34+
const result = await plugin.handler({});
35+
expect(result.isError).toBe(false);
36+
const parsed = JSON.parse(result.content[0].text);
37+
expect(parsed).toEqual({});
38+
});
39+
40+
it('should return current defaults when set', async () => {
41+
sessionStore.setDefaults({ scheme: 'MyScheme', simulatorId: 'SIM-123' });
42+
const result = await plugin.handler({});
43+
expect(result.isError).toBe(false);
44+
const parsed = JSON.parse(result.content[0].text);
45+
expect(parsed.scheme).toBe('MyScheme');
46+
expect(parsed.simulatorId).toBe('SIM-123');
47+
});
48+
});
49+
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export const workflow = {
2+
name: 'session-management',
3+
description:
4+
'Manage session defaults for projectPath/workspacePath, scheme, configuration, simulatorName/simulatorId, deviceId, useLatestOS and arch. These defaults are required by many tools and must be set before attempting to call tools that would depend on these values.',
5+
platforms: ['iOS', 'macOS', 'tvOS', 'watchOS', 'visionOS'],
6+
targets: ['simulator', 'device'],
7+
capabilities: ['configuration', 'state-management'],
8+
};
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { z } from 'zod';
2+
import { sessionStore } from '../../../utils/session-store.ts';
3+
import { createTypedTool } from '../../../utils/typed-tool-factory.ts';
4+
import { getDefaultCommandExecutor } from '../../../utils/execution/index.ts';
5+
import type { ToolResponse } from '../../../types/common.ts';
6+
7+
const keys = [
8+
'projectPath',
9+
'workspacePath',
10+
'scheme',
11+
'configuration',
12+
'simulatorName',
13+
'simulatorId',
14+
'deviceId',
15+
'useLatestOS',
16+
'arch',
17+
] as const;
18+
19+
const schemaObj = z.object({
20+
keys: z.array(z.enum(keys)).optional(),
21+
all: z.boolean().optional(),
22+
});
23+
24+
type Params = z.infer<typeof schemaObj>;
25+
26+
export async function sessionClearDefaultsLogic(params: Params): Promise<ToolResponse> {
27+
if (params.all || !params.keys) sessionStore.clear();
28+
else sessionStore.clear(params.keys);
29+
return { content: [{ type: 'text', text: 'Session defaults cleared' }], isError: false };
30+
}
31+
32+
export default {
33+
name: 'session-clear-defaults',
34+
description: 'Clear selected or all session defaults.',
35+
schema: schemaObj.shape,
36+
handler: createTypedTool(schemaObj, sessionClearDefaultsLogic, getDefaultCommandExecutor),
37+
};

0 commit comments

Comments
 (0)