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
« 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 -*-
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#============================================================================#
21''' Memory file symlink management for coder configurations.
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'''
29from . import __
32_scribe = __.provide_scribe( __name__ )
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.
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
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
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.
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.
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 )