-
Notifications
You must be signed in to change notification settings - Fork 19
fix: improve assistant instruction file handling #159
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -557,10 +557,11 @@ def remove_skill(self, dest_path: Path, skill_name: str) -> bool: | |
|
|
||
|
|
||
| class ManagedInstructionsTarget: | ||
| """Mixin for targets that use managed sections for module instructions. | ||
| """Mixin for targets that write module instructions to assistant files. | ||
|
|
||
| This provides shared logic for inserting/removing module instructions | ||
| into markdown files like CLAUDE.md, GEMINI.md, AGENTS.md. | ||
| New installs replace files like CLAUDE.md, GEMINI.md, and AGENTS.md in full. | ||
| Removal still understands legacy managed sections so older installations can | ||
| be cleaned up. | ||
| """ | ||
|
|
||
| INSTRUCTIONS_START_MARKER: str = "<!-- lola:instructions:start -->" | ||
|
|
@@ -580,72 +581,13 @@ def generate_instructions( | |
| dest_path: Path, | ||
| module_name: str, | ||
| ) -> bool: | ||
| """Generate/update module instructions in a managed section.""" | ||
| """Replace the assistant instructions file with module instructions.""" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would anyone ever actually use this? I can't imagine wanting lola to nuke my
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the insertion method might be required for Gemini and other CLI tools that don't auto-read the installed files. In those cases, it would make more sense to me to use a pointer to an installed file instead of updating huge chunks of text.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agree here, this would be a regression in Moreover:
@trevor-vaughan Thats mostly for what |
||
| instructions_content = _resolve_source_content(source) | ||
| if not instructions_content: | ||
| return False | ||
|
|
||
| # Read existing file content | ||
| if dest_path.exists(): | ||
| content = dest_path.read_text() | ||
| else: | ||
| dest_path.parent.mkdir(parents=True, exist_ok=True) | ||
| content = "" | ||
|
|
||
| module_start, module_end = self._get_module_markers(module_name) | ||
|
|
||
| # Build the module block | ||
| module_block = f"{module_start}\n{instructions_content}\n{module_end}" | ||
|
|
||
| # Check if managed section exists | ||
| if ( | ||
| self.INSTRUCTIONS_START_MARKER in content | ||
| and self.INSTRUCTIONS_END_MARKER in content | ||
| ): | ||
| start_idx = content.index(self.INSTRUCTIONS_START_MARKER) | ||
| end_idx = content.index(self.INSTRUCTIONS_END_MARKER) + len( | ||
| self.INSTRUCTIONS_END_MARKER | ||
| ) | ||
| existing_section = content[start_idx:end_idx] | ||
| section_content = existing_section[ | ||
| len(self.INSTRUCTIONS_START_MARKER) : -len(self.INSTRUCTIONS_END_MARKER) | ||
| ] | ||
|
|
||
| # Remove existing module section if present | ||
| if module_start in section_content: | ||
| mod_start_idx = section_content.index(module_start) | ||
| mod_end_idx = section_content.index(module_end) + len(module_end) | ||
| section_content = ( | ||
| section_content[:mod_start_idx] + section_content[mod_end_idx:] | ||
| ) | ||
|
|
||
| # Collect all module blocks and sort them alphabetically | ||
| module_blocks = self._extract_module_blocks(section_content) | ||
| module_blocks[module_name] = module_block | ||
|
|
||
| # Build new section with sorted modules | ||
| sorted_blocks = [ | ||
| module_blocks[name] for name in sorted(module_blocks.keys()) | ||
| ] | ||
| new_section_content = "\n\n".join(sorted_blocks) | ||
| if new_section_content: | ||
| new_section_content = "\n" + new_section_content + "\n" | ||
|
|
||
| new_section = ( | ||
| self.INSTRUCTIONS_START_MARKER | ||
| + new_section_content | ||
| + self.INSTRUCTIONS_END_MARKER | ||
| ) | ||
| content = content[:start_idx] + new_section + content[end_idx:] | ||
| else: | ||
| # Create new managed section at the end | ||
| new_section = ( | ||
| f"\n\n{self.INSTRUCTIONS_START_MARKER}\n{module_block}\n" | ||
| f"{self.INSTRUCTIONS_END_MARKER}\n" | ||
| ) | ||
| content = content.rstrip() + new_section | ||
|
|
||
| dest_path.write_text(content) | ||
| dest_path.parent.mkdir(parents=True, exist_ok=True) | ||
| dest_path.write_text(instructions_content) | ||
| return True | ||
|
|
||
| def _extract_module_blocks(self, section_content: str) -> dict[str, str]: | ||
|
|
@@ -661,14 +603,14 @@ def _extract_module_blocks(self, section_content: str) -> dict[str, str]: | |
| def remove_instructions(self, dest_path: Path, module_name: str) -> bool: | ||
| """Remove a module's instructions from the managed section.""" | ||
| if not dest_path.exists(): | ||
| return True | ||
| return False | ||
|
|
||
| content = dest_path.read_text() | ||
| if ( | ||
| self.INSTRUCTIONS_START_MARKER not in content | ||
| or self.INSTRUCTIONS_END_MARKER not in content | ||
| ): | ||
| return True | ||
| return False | ||
|
|
||
| module_start, module_end = self._get_module_markers(module_name) | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks more as
--overwrite. We can have it with-o|--overwritebut it must be default toFALSEand if using as a bool, the--no-instructionsmight not be needed anymore.Also, prefer to keep it fully separated from
--append-context. The append context was designed to have an instruction inside a "main.spec (CLAUDE.md, AGENTS.md, GEMINI.md)" telling where a "sub project" main instructions can be found. With that, we should have a clear separation of concerns here, ensuringoverwriteis exactly the opposite ofappend, the code should not touch each other's functions, aside from the mutually exclusive flags.