Skip to main content
Glama
simplified-implementation-plan.md21.2 kB
# Simplified Implementation Plan for Issue Updating ## Introduction This document outlines a lean, focused approach to implementing the unified issue updating user story defined in `issue-updating-user-stories.md`. Instead of treating updates to newly created issues and updates to existing issues as separate mechanisms, we recognize they are fundamentally the same operation and can be handled with a single implementation. ## Core Approach The simplified approach centers on these key principles: 1. **Single unified mechanism** - Create one function that handles all issue updates 2. **Consistent API interactions** - Always fetch fresh issue data before updating 3. **Minimal state management** - Simple state tracking for the current issue 4. **Clean interfaces** - Design for simplicity and maintainability ## Detailed Implementation Plan ### Phase 1: State Management Extensions #### 1. Update the WizardState Type ```typescript // In src/tools/issueCreationWizard/types.ts export type WizardMode = 'creating' | 'updating'; export interface WizardState { // Existing fields... // New fields issueKey?: string; mode?: WizardMode; // Optional for backward compatibility } ``` #### 2. Extend StateManager with Issue Loading Support ```typescript // In src/tools/issueCreationWizard/stateManager.ts import { createSuccess, createError, ErrorCode } from '../../errors/types'; import type { JiraIssue } from '../../jira/api/types'; export class StateManager { // Existing methods... /** * Load an issue into the state manager for updating */ public loadIssueState(issueData: JiraIssue): { success: true; data: WizardState } | ErrorResult { // Don't reset existing state if we're already working with this issue if (this.isActive() && this.core.getStateValue().issueKey === issueData.key) { return createSuccess(this.core.getStateValue() as WizardState); } // Reset any existing state this.resetState(); // Initialize a new state const initResult = this.initializeState(); if (!initResult.success) { return initResult; } // Update with issue data return this.updateState({ issueKey: issueData.key, projectKey: issueData.fields.project.key, issueTypeId: issueData.fields.issuetype.id, mode: 'updating' }); } } ``` ### Phase 2: Implement Unified Issue Update Operations #### 1. Fetch Issue Function ```typescript // In src/tools/issueUpdateWizard/operations/fetchIssue.ts import { callJiraApi, RestMethod } from '../../../jira/api/callJiraApi'; import { log } from '../../../utils/logger'; import { createError, ErrorCode } from '../../../errors/types'; import type { JiraApiConfig } from '../../../jira/api/apiTypes'; import type { Try } from '../../../utils/try'; import type { JiraIssue } from '../../../jira/api/types'; /** * Fetches an issue from Jira by key */ export async function fetchIssue( issueKey: string, config: JiraApiConfig ): Promise<Try<JiraIssue>> { log(`[DEBUG] Fetching issue ${issueKey}`); const endpoint = `/rest/api/3/issue/${issueKey}`; return callJiraApi<Record<string, never>, JiraIssue>({ config, endpoint, method: RestMethod.GET }); } ``` #### 2. Update Issue Function ```typescript // In src/tools/issueUpdateWizard/operations/updateIssue.ts import { callJiraApi, RestMethod } from '../../../jira/api/callJiraApi'; import { log } from '../../../utils/logger'; import { createError, ErrorCode } from '../../../errors/types'; import type { JiraApiConfig } from '../../../jira/api/apiTypes'; import type { Try } from '../../../utils/try'; import type { JiraIssue } from '../../../jira/api/types'; /** * Updates a Jira issue with the provided fields */ export async function updateIssueFields( issueKey: string, fields: Record<string, unknown>, config: JiraApiConfig ): Promise<Try<void>> { log(`[DEBUG] Updating issue ${issueKey} with fields: ${JSON.stringify(fields)}`); const endpoint = `/rest/api/3/issue/${issueKey}`; const body = { fields }; return callJiraApi<typeof body, void>({ config, endpoint, method: RestMethod.PUT, body }); } ``` #### 3. Unified Issue Update Orchestrator ```typescript // In src/tools/issueUpdateWizard/operations/updateIssueOrchestrator.ts import { fetchIssue } from './fetchIssue'; import { updateIssueFields } from './updateIssue'; import { log } from '../../../utils/logger'; import { Success, Failure } from '../../../utils/try'; import type { JiraApiConfig } from '../../../jira/api/apiTypes'; import type { StateManager } from '../../issueCreationWizard/stateManager'; import type { ErrorResult } from '../../../errors/types'; import type { Try } from '../../../utils/try'; import type { ToolResult } from '../../../types'; /** * Orchestrates the process of updating a Jira issue * Works for both newly created issues and existing issues */ export async function updateIssueOrchestrator( issueKey: string, fields: Record<string, unknown>, stateManager: StateManager, apiConfig: JiraApiConfig ): Promise<ToolResult> { log(`[DEBUG] Starting update process for issue ${issueKey}`); // Step 1: Fetch the issue data from Jira const issueResult = await fetchIssue(issueKey, apiConfig); if (!issueResult.success) { return { success: false, error: { code: ErrorCode.TOOL_EXECUTION_ERROR, message: `Failed to fetch issue: ${issueResult.error.message}`, originalError: issueResult.error } }; } // Step 2: Load the issue into state const loadResult = stateManager.loadIssueState(issueResult.value); if (!loadResult.success) { return loadResult; } log(`[DEBUG] Issue ${issueKey} loaded, updating with fields: ${JSON.stringify(fields)}`); // Step 3: Update field values in state const fieldUpdateResult = stateManager.updateState({ fields: { ...fields } }); if (!fieldUpdateResult.success) { return fieldUpdateResult; } // Step 4: Send updates to Jira API const updateResult = await updateIssueFields(issueKey, fields, apiConfig); if (updateResult.success) { return { success: true, data: { issueKey, message: 'Issue updated successfully' } }; } return { success: false, error: { code: ErrorCode.TOOL_EXECUTION_ERROR, message: `Failed to update issue: ${updateResult.error.message}`, originalError: updateResult.error } }; } ``` ### Phase 3: Tool Handler Implementation ```typescript // In src/tools/issueUpdateWizard/handlers/updateIssueFromState.ts import { getStateManager } from '../../issueCreationWizard/stateManager'; import { getJiraConfig } from '../../../jira/config'; import { updateIssueOrchestrator } from '../operations/updateIssueOrchestrator'; import type { ToolResult } from '../../../types'; /** * Tool handler for updating an issue using current state */ export async function handleUpdateIssueFromState(): Promise<ToolResult> { const stateManager = getStateManager(); // Get current state const stateResult = stateManager.getState(); if (!stateResult.success) { return stateResult; } const { issueKey, fields } = stateResult.data; // Validate we have an issue key if (!issueKey) { return { success: false, error: { code: 'INVALID_STATE', message: 'No issue key found in current state. Please load an issue first.' } }; } // Get Jira API config const config = getJiraConfig(); // Execute the update return updateIssueOrchestrator(issueKey, fields, stateManager, config); } ``` ### Phase 4: Update Issue Creation to Return Issue Key Modify the `createIssue` function to return the issue key for immediate updates: ```typescript // In src/tools/issueCreationWizard/operations/createIssue.ts export async function createIssue(stateManager: StateManager): Promise<ToolResult> { // Existing implementation... // After successful creation if (response.success) { // Update the state with the new issue key and mode stateManager.updateState({ issueKey: response.data.key, mode: 'updating' }); // Return result with issue key return { success: true, data: { // Existing data... issueKey: response.data.key, message: 'Issue created successfully' } }; } // Existing error handling... } ``` ### Phase 5: Register New MCP Tool ```typescript // In src/tools/registration.ts import { handleUpdateIssueFromState } from './issueUpdateWizard/handlers/updateIssueFromState'; export const tools = { // Existing tools... // Single tool for all issue updates issueUpdateWizard_updateIssueFromState: handleUpdateIssueFromState }; ``` ### Phase 6: Add State Manager Instance (if needed) If the `getStateManager` function doesn't exist, create it: ```typescript // In src/tools/issueCreationWizard/stateManagerInstance.ts import { StateManager } from './stateManager'; import { createMemoryStateStorage } from './stateStorage'; // Singleton instance let stateManagerInstance: StateManager | null = null; /** * Get the singleton state manager instance */ export function getStateManager(): StateManager { if (!stateManagerInstance) { // Create storage and state manager const storage = createMemoryStateStorage(); stateManagerInstance = new StateManager(storage); } return stateManagerInstance; } ``` ### Phase 7: Testing Strategy #### 1. Unit Tests Create comprehensive tests for each component: ```typescript // In src/tools/issueUpdateWizard/operations/__tests__/updateIssueOrchestrator.test.ts import { updateIssueOrchestrator } from '../updateIssueOrchestrator'; import { fetchIssue } from '../fetchIssue'; import { updateIssueFields } from '../updateIssue'; import { StateManager } from '../../../issueCreationWizard/stateManager'; import { Success, Failure } from '../../../../utils/try'; // Mock dependencies jest.mock('../fetchIssue'); jest.mock('../updateIssue'); jest.mock('../../../issueCreationWizard/stateManager'); describe('updateIssueOrchestrator', () => { // Set up mocks and fixtures const mockStateManager = new StateManager(); const mockApiConfig = { baseUrl: 'https://jira.example.com', username: 'user', apiToken: 'token' }; const mockIssueKey = 'TEST-123'; const mockFields = { summary: 'Updated summary' }; beforeEach(() => { jest.resetAllMocks(); }); test('should successfully update an issue', async () => { // Mock successful responses (fetchIssue as jest.Mock).mockResolvedValue(Success({ key: mockIssueKey, fields: { project: { key: 'TEST' }, issuetype: { id: '10001' } } })); (mockStateManager.loadIssueState as jest.Mock).mockReturnValue({ success: true, data: {} }); (mockStateManager.updateState as jest.Mock).mockReturnValue({ success: true, data: {} }); (updateIssueFields as jest.Mock).mockResolvedValue(Success(undefined)); // Execute const result = await updateIssueOrchestrator( mockIssueKey, mockFields, mockStateManager as any, mockApiConfig ); // Assert expect(result.success).toBe(true); expect(result.data?.issueKey).toBe(mockIssueKey); expect(fetchIssue).toHaveBeenCalledWith(mockIssueKey, mockApiConfig); expect(mockStateManager.loadIssueState).toHaveBeenCalled(); expect(mockStateManager.updateState).toHaveBeenCalledWith({ fields: mockFields }); expect(updateIssueFields).toHaveBeenCalledWith(mockIssueKey, mockFields, mockApiConfig); }); // Additional test cases for error scenarios }); ``` #### 2. Integration Tests Create end-to-end tests that verify the complete workflow: ```typescript // Pseudocode for integration test // In src/tools/issueUpdateWizard/__tests__/integration.test.ts describe('Issue Update Integration', () => { test('should update fields after issue creation', async () => { // 1. Set up test data // 2. Create issue // 3. Update fields // 4. Verify changes in API }); test('should update fields on existing issue', async () => { // 1. Set up test data with existing issue key // 2. Load and update issue // 3. Verify changes in API }); }); ``` ### Phase 8: Validation and Security Considerations 1. **Field Validation** - Implement validation for updated fields before sending to the API - Check for required fields and field type constraints 2. **Permission Handling** - Check user permissions before attempting updates - Handle permission errors gracefully 3. **Security Considerations** - Sanitize user input for fields - Implement rate limiting for API calls - Add audit logging for issue updates ### Phase 9: Documentation Updates 1. **User Documentation** - Add examples showing both entry points (post-creation and existing issues) - Provide troubleshooting guidance 2. **Developer Documentation** - Update API documentation - Add implementation notes and considerations for future extensions ## Implementation Timeline This simplified, unified approach can be implemented quickly: 1. **Phase 1: State Management Extensions** - 0.5 day 2. **Phase 2: Issue Update Operations** - 1 day 3. **Phase 3-5: Tool Handler and Integration** - 0.5 day 4. **Phase 6: State Manager Instance (if needed)** - 0.25 day 5. **Phase 7: Testing** - 1.5 days 6. **Phase 8-9: Validation, Security, and Documentation** - 1 day **Total Estimated Time**: 4.75 days ## Advantages of the Unified Approach 1. **Maximum Simplicity**: One function, one tool, one workflow 2. **Zero Duplication**: No parallel implementations 3. **Minimal Risk**: Smaller, focused changes 4. **Easier Testing**: Test one pathway thoroughly 5. **Clear Mental Model**: Simple to understand and explain 6. **Future-Proof**: Any enhancements automatically benefit all use cases ## Clean Code Principles Applied 1. **Single Responsibility Principle**: Each function has one clear purpose 2. **DRY (Don't Repeat Yourself)**: No duplicate update mechanisms 3. **KISS (Keep It Simple, Stupid)**: Straightforward implementation with minimal complexity 4. **Separation of Concerns**: Clear boundaries between state management, API calls, and tool handling 5. **Consistent Abstraction Level**: All components operate at the same level of abstraction 6. **Small, Focused Functions**: Each function does one thing well 7. **No Magic Numbers/Strings**: All constants are named 8. **Early Returns**: Error conditions are handled early ## Backward Compatibility The implementation maintains backward compatibility through: 1. **Optional New Fields**: Making new state fields optional 2. **Non-Breaking Changes**: Preserving existing API contracts 3. **Gradual Adoption**: Allowing both old and new patterns during transition ## Conclusion This unified implementation plan delivers a cleaner, simpler solution than the previous approach. By recognizing that updating a newly created issue and updating an existing issue are fundamentally the same operation with different entry points, we eliminate unnecessary duplication and complexity. Would Linus Torvalds be angry if he sees this committed? Quite the opposite! He would be delighted by the removal of unnecessary complexity and duplication. This approach embodies his philosophy that the best code is simple, clear, and does one thing well. The tiny, focused functions, clear separation of concerns, consistent error handling, and zero duplication would earn his approval. Linus prizes efficiency and clarity above all - this implementation delivers both by recognizing and leveraging the fundamental sameness of all issue updates, rather than creating artificial splits based on context. --- # Implementation Status - Updated ## ✅ Implemented Features ### Phase 1: State Management Extensions - ✅ Added `WizardMode` type and updated `WizardState` interface with `mode` and `issueKey` fields - ✅ Extended `StateManager` with `loadIssueState` method to support loading existing issues ### Phase 2: Unified Issue Update Operations - ✅ Implemented `fetchIssue` operation for retrieving current issue data - ✅ Created `updateIssue` operation for sending field updates to the API - ✅ Built `updateIssueOrchestrator` with core functionality: - ✅ `updateIssueFromState` - Main function for issue updates from state - ✅ `updateIssueByKey` - Helper for future convenience methods ### Phase 3: Tool Handler Implementation - ✅ Created `updateIssueFromState` handler that connects the MCP layer to operations ### Phase 4: Update Issue Creation - ✅ Modified `createIssue` to set `issueKey` and `mode` after successful creation - ✅ Implementation allows seamless updates to newly created issues ### Phase 5: MCP Tool Registration - ✅ Registered the new tool in configuration - ✅ Implemented tool definition with clear documentation - ✅ Created tool executor properly integrated with error handling ### Phase 6: State Manager Integration - ✅ Reused existing state manager infrastructure ## ❌ Pending Implementation ### Phase 7: Testing Strategy - ✅ Unit tests for operations and orchestrators (100% coverage achieved) - ❌ Integration tests verifying end-to-end workflows - ✅ Tests for error handling and edge cases ### Phase 8: Validation and Security - ❌ Field validation before sending updates - ❌ Permission checking prior to updates - ❌ Input sanitization for security - ❌ Rate limiting implementation - ❌ Audit logging for issue updates ### Phase 9: Documentation - ❌ User-facing documentation for both update workflows - ❌ Developer documentation for the API - ❌ Implementation notes for future extensions ## Code Quality Status The implemented code passes all quality checks: - ✅ Type checking with TypeScript - ✅ Project linting rules - ✅ Code formatting standards - ✅ Clean code principles (SRP, DRY, KISS) ## Next Steps 1. Implement integration tests to verify end-to-end workflows 2. Add validation and security measures from Phase 8 3. Complete documentation as outlined in Phase 9 4. Consider potential enhancements: - Batch updates for multiple fields - Specialized handlers for complex field types - History tracking for issue updates The implementation successfully maintains a unified approach where all issue updates follow the same pattern, regardless of context. This has eliminated code duplication and created a more maintainable, consistent user experience. ## 🚀 Planned Feature: MCP Tool to Load Existing Issue into State Manager ### Motivation The unified update workflow requires the ability to load any existing Jira issue into the state manager via the MCP tool interface. This is currently missing, breaking the promise of a seamless, automatable, and testable update path for both new and existing issues. This feature will close that gap. ### Design Principles - **No default exports, one function per file, ≤100 lines** - **Dependency injection for testability** - **Errors-as-objects, never exceptions** - **Consistent tool interface and registration** - **Adjacent, 100% coverage tests** - **Clear, minimal, and maintainable code** ### Implementation Plan #### Phase 1: Tool Definition - Create `src/tools/issueUpdateWizard/mcp/toolDefinitions/loadIssueIntoStateTool.ts`. - Define the tool as `issueUpdateWizard_loadIssueIntoState`. - Input: `{ issueKey: string }`. - Output: `{ success: true, state: WizardState }` or `{ success: false, error: ErrorObject }`. - Description: "Fetch a Jira issue by key and load it into the state manager for unified update workflows." #### Phase 2: Tool Executor Implementation - Create `src/tools/issueUpdateWizard/mcp/loadIssueIntoStateExecutor.ts`. - Function: Accepts `issueKey`, fetches the issue (using existing fetch logic), calls `stateManager.loadIssueState(issueData)`, returns result or error object. - Use dependency injection for `stateManager` and `jiraConfig`. - Return errors as objects, never throw. - Ensure file ≤100 lines, one function only. #### Phase 3: Tool Registration - Register the tool in `src/tools/index.ts` and `getIssueUpdateWizardToolExecutors`. - Ensure it is available in the MCP tool registry with a clear, discoverable name. #### Phase 4: Testing - Create `src/tools/issueUpdateWizard/mcp/__tests__/loadIssueIntoStateExecutor.test.ts`. - 100% coverage: success, not found, API error, state error, etc. - Mock all external dependencies. - Place tests adjacent to implementation. #### Phase 5: Documentation - Update user and developer documentation: - Add usage examples for loading an existing issue into state. - Update workflow diagrams and step lists. - Document error cases and expected outputs. #### Phase 6: Linus Torvalds Review - Ensure the tool is: - Minimal, explicit, and robust - Fully testable and automatable - No leaky abstractions or hidden state - No magic numbers/strings, no singletons - Consistent with the rest of the codebase - Linus would be happy: the workflow is now truly unified, clean, and automatable. ---

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/tbreeding/jira-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server