Coverage for sources / agentsmgr / renderers / claude.py: 26%

38 statements  

« prev     ^ index     » next       coverage.py v7.12.0, created at 2025-11-30 00:03 +0000

1# vim: set filetype=python fileencoding=utf-8: 

2# -*- coding: utf-8 -*- 

3 

4#============================================================================# 

5# # 

6# Licensed under the Apache License, Version 2.0 (the "License"); # 

7# you may not use this file except in compliance with the License. # 

8# You may obtain a copy of the License at # 

9# # 

10# http://www.apache.org/licenses/LICENSE-2.0 # 

11# # 

12# Unless required by applicable law or agreed to in writing, software # 

13# distributed under the License is distributed on an "AS IS" BASIS, # 

14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 

15# See the License for the specific language governing permissions and # 

16# limitations under the License. # 

17# # 

18#============================================================================# 

19 

20 

21''' Claude Code renderer implementation. 

22 

23 Provides path resolution and targeting mode validation for Claude Code, 

24 which supports both per-user and per-project configuration. 

25''' 

26 

27 

28from . import __ 

29from .base import RENDERERS, ExplicitTargetMode, RendererBase 

30 

31 

32class ClaudeRenderer( RendererBase ): 

33 ''' Renderer for Claude Code coder. 

34 

35 Supports both per-user and per-project configuration modes. 

36 Per-user mode respects CLAUDE_CONFIG_DIR environment variable 

37 with fallback to configuration overrides and default location. 

38 ''' 

39 

40 name = 'claude' 

41 modes_available = frozenset( ( 'per-user', 'per-project' ) ) 

42 mode_default = 'per-project' 

43 memory_filename = 'CLAUDE.md' 

44 

45 def get_template_flavor( self, item_type: str ) -> str: 

46 ''' Determines template flavor for Claude Code. 

47 

48 Claude uses markdown format for both commands and agents, 

49 so always returns 'claude' flavor. 

50 ''' 

51 return 'claude' 

52 

53 def provide_project_symlinks( 

54 self, target: __.Path 

55 ) -> __.cabc.Sequence[ tuple[ __.Path, __.Path ] ]: 

56 ''' Provides symlinks required for Claude Code in per-project mode. 

57 

58 Claude requires base symlink plus .mcp.json symlink linking to 

59 the shared MCP server configuration file. 

60 ''' 

61 symlinks = list( super( ).provide_project_symlinks( target ) ) 

62 mcp_source = ( 

63 target / '.auxiliary' / 'configuration' / 'mcp-servers.json' ) 

64 mcp_link = target / '.mcp.json' 

65 symlinks.append( ( mcp_source, mcp_link ) ) 

66 return symlinks 

67 

68 def resolve_base_directory( 

69 self, 

70 mode: ExplicitTargetMode, 

71 target: __.Path, 

72 configuration: __.cabc.Mapping[ str, __.typx.Any ], 

73 environment: __.cabc.Mapping[ str, str ], 

74 ) -> __.Path: 

75 ''' Resolves base output directory for Claude Code. 

76 

77 For per-project mode, returns .claude in project root. 

78 For per-user mode, respects precedence: CLAUDE_CONFIG_DIR 

79 environment variable, configuration file override, or default 

80 ~/.claude location. 

81 ''' 

82 self.validate_mode( mode ) 

83 if mode == 'per-project': 

84 return target / ".auxiliary/configuration/coders/claude" 

85 if mode == 'per-user': 

86 return self._resolve_user_directory( configuration, environment ) 

87 raise __.TargetModeNoSupport( self.name, mode ) 

88 

89 def _resolve_user_directory( 

90 self, 

91 configuration: __.cabc.Mapping[ str, __.typx.Any ], 

92 environment: __.cabc.Mapping[ str, str ], 

93 ) -> __.Path: 

94 ''' Resolves per-user directory following precedence rules. 

95 

96 Precedence order: 

97 1. CLAUDE_CONFIG_DIR environment variable 

98 2. Configuration file override (directory for this coder) 

99 3. Default ~/.claude location 

100 ''' 

101 if 'CLAUDE_CONFIG_DIR' in environment: 

102 directory = __.Path( environment[ 'CLAUDE_CONFIG_DIR' ] ) 

103 return directory.expanduser( ) 

104 coder_configuration = self._extract_coder_configuration( 

105 configuration ) 

106 if 'directory' in coder_configuration: 

107 directory = __.Path( coder_configuration[ 'directory' ] ) 

108 return directory.expanduser( ) 

109 return __.Path.home( ) / '.claude' 

110 

111 def _extract_coder_configuration( 

112 self, configuration: __.cabc.Mapping[ str, __.typx.Any ] 

113 ) -> __.cabc.Mapping[ str, __.typx.Any ]: 

114 ''' Extracts configuration for this specific coder. 

115 

116 Looks for coder entry in configuration coders array by name. 

117 ''' 

118 coders = configuration.get( 'coders', ( ) ) 

119 for coder in coders: 

120 if coder.get( 'name' ) == self.name: 

121 return coder 

122 return { } 

123 

124 

125RENDERERS[ 'claude' ] = ClaudeRenderer( )