Coverage for sources / agentsmgr / memorylinks.py: 8%
46 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-03 23:00 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-03 23:00 +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 __
30from . import exceptions as _exceptions
33_scribe = __.provide_scribe( __name__ )
36def create_memory_symlink(
37 source: __.Path,
38 link_path: __.Path,
39 simulate: bool = False,
40) -> tuple[ bool, str ]:
41 ''' Creates symlink from coder memory file to project conventions.
43 Follows patterns from .auxiliary/scripts/prepare-agents:
44 - If link is symlink to correct target: Skip silently
45 - If link is symlink to wrong target: Update it
46 - If link is broken symlink: Remove and recreate
47 - If link is regular file/directory: Warn and skip
48 - If link doesn't exist: Create symlink
50 Returns tuple of (created, symlink_name) where created indicates
51 if symlink was created/updated and symlink_name is the name
52 relative to parent directory.
53 '''
54 symlink_name = link_path.name
55 try:
56 relative_source = __.os.path.relpath(
57 source, start = link_path.parent )
58 except ValueError:
59 relative_source = str( source.resolve( ) )
60 if link_path.is_symlink( ):
61 try: current_target = __.os.readlink( link_path )
62 except OSError as exception:
63 _scribe.warning(
64 f"Cannot read symlink {link_path}: {exception}." )
65 return ( False, symlink_name )
66 if current_target == relative_source:
67 return ( False, symlink_name )
68 _scribe.info(
69 f"Updating symlink {link_path.name}: "
70 f"{current_target} → {relative_source}" )
71 if not simulate: link_path.unlink( )
72 elif link_path.exists( ):
73 _scribe.warning(
74 f"File or directory already exists at {link_path}. Skipping." )
75 return ( False, symlink_name )
76 elif not link_path.exists( ) and link_path.is_symlink( ):
77 _scribe.info( f"Fixing broken symlink: {link_path.name}" )
78 if not simulate: link_path.unlink( )
79 if not simulate:
80 link_path.symlink_to( relative_source )
81 _scribe.info( f"Created memory symlink: {link_path.name}" )
82 else:
83 _scribe.info(
84 f"[SIMULATE] Would create symlink: "
85 f"{link_path.name} → {relative_source}" )
86 return ( True, symlink_name )
89def create_memory_symlinks_for_coders(
90 coders: __.cabc.Sequence[ str ],
91 target: __.Path,
92 renderers: __.cabc.Mapping[ str, __.typx.Any ],
93 simulate: bool = False,
94) -> tuple[ int, int, tuple[ str, ... ] ]:
95 ''' Creates memory symlinks for all configured coders.
97 Memory symlinks are always created at project root, pointing to
98 project-specific conventions file. They are created regardless
99 of targeting mode since memory files are project-specific.
101 Returns tuple of (attempted, created, symlink_names) where
102 symlink_names contains names of all symlinks (both newly created
103 and pre-existing).
104 '''
105 source = target / '.auxiliary' / 'configuration' / 'AGENTS.md'
106 if not source.exists( ):
107 raise _exceptions.MemoryFileAbsence( source )
108 attempted = 0
109 created = 0
110 symlink_names: list[ str ] = [ ]
111 for coder_name in coders:
112 try: renderer = renderers[ coder_name ]
113 except KeyError as exception:
114 raise _exceptions.CoderAbsence( coder_name ) from exception
115 link_path = target / renderer.memory_filename
116 attempted += 1
117 was_created, symlink_name = create_memory_symlink(
118 source, link_path, simulate )
119 if was_created: created += 1
120 symlink_names.append( symlink_name )
121 return ( attempted, created, tuple( symlink_names ) )