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

41 statements  

« prev     ^ index     » next       coverage.py v7.13.3, created at 2026-02-04 21:55 +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 as _RENDERERS 

30from .base import ExplicitTargetMode as _ExplicitTargetMode 

31from .base import RendererBase as _RendererBase 

32 

33 

34class ClaudeRenderer( _RendererBase ): 

35 ''' Renderer for Claude Code coder. 

36 

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

38 Per-user mode respects CLAUDE_CONFIG_DIR environment variable 

39 with fallback to configuration overrides and default location. 

40 ''' 

41 

42 name = 'claude' 

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

44 mode_default = 'per-project' 

45 memory_filename = 'CLAUDE.md' 

46 item_types_available = frozenset( ( 'commands', 'agents', 'skills' ) ) 

47 

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

49 ''' Determines template flavor for Claude Code. 

50 

51 Claude uses markdown format for both commands and agents, 

52 so always returns 'claude' flavor. 

53 ''' 

54 return 'claude' 

55 

56 def provide_project_symlinks( 

57 self, target: __.Path 

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

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

60 

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

62 the shared MCP server configuration file. 

63 ''' 

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

65 mcp_source = ( 

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

67 mcp_link = target / '.mcp.json' 

68 symlinks.append( ( mcp_source, mcp_link ) ) 

69 return symlinks 

70 

71 def resolve_base_directory( 

72 self, 

73 mode: _ExplicitTargetMode, 

74 target: __.Path, 

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

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

77 ) -> __.Path: 

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

79 

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

81 For per-user mode, respects precedence: CLAUDE_CONFIG_DIR 

82 environment variable, configuration file override, or default 

83 ~/.claude location. 

84 ''' 

85 self.validate_mode( mode ) 

86 if mode == 'per-project': 86 ↛ 88line 86 didn't jump to line 88 because the condition on line 86 was always true

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

88 if mode == 'per-user': 

89 return self._resolve_user_directory( configuration, environment ) 

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

91 

92 def _resolve_user_directory( 

93 self, 

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

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

96 ) -> __.Path: 

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

98 

99 Precedence order: 

100 1. CLAUDE_CONFIG_DIR environment variable 

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

102 3. Default ~/.claude location 

103 ''' 

104 if 'CLAUDE_CONFIG_DIR' in environment: 

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

106 return directory.expanduser( ) 

107 coder_configuration = self._extract_coder_configuration( 

108 configuration ) 

109 if 'directory' in coder_configuration: 

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

111 return directory.expanduser( ) 

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

113 

114 def _extract_coder_configuration( 

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

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

117 ''' Extracts configuration for this specific coder. 

118 

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

120 ''' 

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

122 for coder in coders: 

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

124 return coder 

125 return { } 

126 

127 

128_RENDERERS[ 'claude' ] = ClaudeRenderer( )