mcp-casual-interview
Version:
MCP server for casual interview preparation workflow management
162 lines • 5.64 kB
JavaScript
import { ok, err } from 'neverthrow';
import path from 'path';
import { z } from 'zod';
import { WorkflowPolicies } from '../domain/term/interview-workflow/index.js';
import { getDataDirectory, saveJsonFile, readJsonFile, fileExists } from './filesystem.js';
const WorkflowStateSchema = z.enum([
'not_started',
'information_initially_registered',
'career_summary_created',
'match_position_determined',
'match_degree_prediagnosed',
'attract_plan_determined',
'completed'
]);
const WorkflowStateDataSchema = z.object({
state: WorkflowStateSchema,
notionPageUrl: z.string().nullable(),
lastUpdated: z.string()
});
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Storage Implementation
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
const getFilePath = () => {
const dataDir = getDataDirectory();
return path.join(dataDir, 'workflow-state.json');
};
const getDefaultState = () => ({
state: 'not_started',
notionPageUrl: null,
lastUpdated: new Date().toISOString()
});
const mapFileSystemError = (error) => ({
type: 'FileSystemError',
message: error.message,
cause: error.cause
});
/**
* Get current workflow state
*/
export const getCurrentState = async () => {
const filePath = getFilePath();
const existsResult = await fileExists(filePath);
if (existsResult.isErr()) {
return err(mapFileSystemError(existsResult.error));
}
if (!existsResult.value) {
// File doesn't exist, create with default state
const defaultState = getDefaultState();
const saveResult = await saveJsonFile(filePath, defaultState);
if (saveResult.isErr()) {
return err(mapFileSystemError(saveResult.error));
}
return ok(defaultState);
}
const readResult = await readJsonFile(filePath);
if (readResult.isErr()) {
return err(mapFileSystemError(readResult.error));
}
// Validate data structure
const parseResult = WorkflowStateDataSchema.safeParse(readResult.value);
if (!parseResult.success) {
return err({
type: 'ValidationError',
message: `ワークフロー状態データの検証に失敗しました: ${parseResult.error.message}`,
cause: parseResult.error
});
}
return ok(parseResult.data);
};
/**
* Update workflow state with transition validation
*/
export const updateState = async (newState) => {
const currentResult = await getCurrentState();
if (currentResult.isErr()) {
return err(currentResult.error);
}
const currentData = currentResult.value;
// Validate state transition
if (!WorkflowPolicies.StateTransitions.canTransition(currentData.state, newState)) {
return err({
type: 'InvalidStateTransition',
message: `無効な状態遷移です: ${currentData.state} から ${newState} への遷移は許可されていません`
});
}
const updatedData = {
...currentData,
state: newState,
lastUpdated: new Date().toISOString()
};
const filePath = getFilePath();
const saveResult = await saveJsonFile(filePath, updatedData);
if (saveResult.isErr()) {
return err(mapFileSystemError(saveResult.error));
}
return ok(updatedData);
};
/**
* Force set workflow state (bypasses transition validation)
*/
export const forceSetState = async (newState) => {
const currentResult = await getCurrentState();
if (currentResult.isErr()) {
return err(currentResult.error);
}
const currentData = currentResult.value;
const updatedData = {
...currentData,
state: newState,
lastUpdated: new Date().toISOString()
};
const filePath = getFilePath();
const saveResult = await saveJsonFile(filePath, updatedData);
if (saveResult.isErr()) {
return err(mapFileSystemError(saveResult.error));
}
return ok(updatedData);
};
/**
* Update notion page URL
*/
export const updateNotionPageUrl = async (url) => {
const currentResult = await getCurrentState();
if (currentResult.isErr()) {
return err(currentResult.error);
}
const currentData = currentResult.value;
const updatedData = {
...currentData,
notionPageUrl: url,
lastUpdated: new Date().toISOString()
};
const filePath = getFilePath();
const saveResult = await saveJsonFile(filePath, updatedData);
if (saveResult.isErr()) {
return err(mapFileSystemError(saveResult.error));
}
return ok(updatedData);
};
/**
* Get notion page URL
*/
export const getNotionPageUrl = async () => {
const currentResult = await getCurrentState();
if (currentResult.isErr()) {
return err(currentResult.error);
}
return ok(currentResult.value.notionPageUrl ?? "未登録です");
};
/**
* Clear workflow state (reset to default)
*/
export const clearState = async () => {
const defaultState = getDefaultState();
const filePath = getFilePath();
const saveResult = await saveJsonFile(filePath, defaultState);
if (saveResult.isErr()) {
return err(mapFileSystemError(saveResult.error));
}
return ok(defaultState);
};
//# sourceMappingURL=workflow-state-storage.js.map