Coverage for sources/agentsmgr/commands/memorylinks.py: 7%

42 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2025-10-13 00:43 +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''' Memory file symlink management for coder configurations. 

22 

23 Provides functionality to create symlinks from coder-specific memory 

24 filenames to shared project conventions file. Follows patterns from 

25 .auxiliary/scripts/prepare-agents for consistent behavior. 

26''' 

27 

28 

29from . import __ 

30 

31 

32_scribe = __.provide_scribe( __name__ ) 

33 

34 

35def create_memory_symlink( 

36 source: __.Path, 

37 link_path: __.Path, 

38 simulate: bool = False, 

39) -> bool: 

40 ''' Creates symlink from coder memory file to project conventions. 

41 

42 Follows patterns from .auxiliary/scripts/prepare-agents: 

43 - If link is symlink to correct target: Skip silently 

44 - If link is symlink to wrong target: Update it 

45 - If link is broken symlink: Remove and recreate 

46 - If link is regular file/directory: Warn and skip 

47 - If link doesn't exist: Create symlink 

48 

49 Returns True if symlink created/updated, False if skipped. 

50 ''' 

51 try: 

52 relative_source = __.os.path.relpath( 

53 source, start = link_path.parent ) 

54 except ValueError: 

55 relative_source = str( source.resolve( ) ) 

56 if link_path.is_symlink( ): 

57 try: current_target = __.os.readlink( link_path ) 

58 except OSError as exception: 

59 _scribe.warning( 

60 f"Cannot read symlink {link_path}: {exception}." ) 

61 return False 

62 if current_target == relative_source: 

63 return False 

64 _scribe.info( 

65 f"Updating symlink {link_path.name}: " 

66 f"{current_target} → {relative_source}" ) 

67 if not simulate: link_path.unlink( ) 

68 elif link_path.exists( ): 

69 _scribe.warning( 

70 f"File or directory already exists at {link_path}. Skipping." ) 

71 return False 

72 elif not link_path.exists( ) and link_path.is_symlink( ): 

73 _scribe.info( f"Fixing broken symlink: {link_path.name}" ) 

74 if not simulate: link_path.unlink( ) 

75 if not simulate: 

76 link_path.symlink_to( relative_source ) 

77 _scribe.info( f"Created memory symlink: {link_path.name}" ) 

78 else: 

79 _scribe.info( 

80 f"[SIMULATE] Would create symlink: " 

81 f"{link_path.name} → {relative_source}" ) 

82 return True 

83 

84 

85def create_memory_symlinks_for_coders( 

86 coders: __.cabc.Sequence[ str ], 

87 target: __.Path, 

88 renderers: __.cabc.Mapping[ str, __.typx.Any ], 

89 simulate: bool = False, 

90) -> tuple[ int, int ]: 

91 ''' Creates memory symlinks for all configured coders. 

92 

93 Memory symlinks are always created at project root, pointing to 

94 project-specific conventions file. They are created regardless 

95 of targeting mode since memory files are project-specific. 

96 

97 Returns tuple of (attempted, created) counts. 

98 ''' 

99 source = target / '.auxiliary' / 'configuration' / 'conventions.md' 

100 if not source.exists( ): 

101 raise __.MemoryFileAbsence( source ) 

102 attempted = 0 

103 created = 0 

104 for coder_name in coders: 

105 try: renderer = renderers[ coder_name ] 

106 except KeyError as exception: 

107 raise __.CoderAbsence( coder_name ) from exception 

108 link_path = target / renderer.memory_filename 

109 attempted += 1 

110 if create_memory_symlink( source, link_path, simulate ): 

111 created += 1 

112 return ( attempted, created )