Skip to content

fix: improve assistant instruction file handling#159

Open
SecKatie wants to merge 1 commit into
mainfrom
issue-158-instructions-replacement
Open

fix: improve assistant instruction file handling#159
SecKatie wants to merge 1 commit into
mainfrom
issue-158-instructions-replacement

Conversation

@SecKatie
Copy link
Copy Markdown
Collaborator

@SecKatie SecKatie commented May 25, 2026

Summary

Fixes #158.

This changes Lola instruction installation so assistant instruction files are no longer appended to with Lola marker blocks. New instruction installs replace the assistant instruction file in full, while existing files are protected by default.

What changed

  • Added --instructions/--no-instructions to control assistant instruction file installation.
  • Prompt before replacing an existing instruction file in interactive installs.
  • Skip existing instruction files in non-interactive installs and print a stderr hint to pass --instructions.
  • Remember skipped instruction installs so future updates do not replace files unexpectedly.
  • Treat legacy install records conservatively so old users do not get full-file replacements on update.
  • Delete full-file instruction installs on uninstall, while still cleaning legacy managed marker blocks.
  • Removed new writes of <!-- lola:instructions:* --> blocks for assistant instruction files.

Validation

  • pytest
  • ruff check src tests
  • ty check
  • Commit hooks ran successfully during commit/amend.

AI disclosure

This contribution was implemented with assistance from Codex.

Summary by CodeRabbit

  • New Features

    • Added --instructions / --no-instructions flags to the install command for explicit control over assistant instruction file handling.
    • Interactive installs now prompt before replacing existing instruction files.
    • Non-interactive installs skip existing instruction files with clear error messaging.
    • Installation preferences are persisted across updates.
  • Bug Fixes

    • Fixed behavior for legacy installation records without explicit instruction preferences.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 25, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 19a3307d-688f-40a9-9a6d-5f07305c9c02

📥 Commits

Reviewing files that changed from the base of the PR and between 560efa3 and 338c106.

📒 Files selected for processing (6)
  • src/lola/cli/install.py
  • src/lola/models.py
  • src/lola/prompts.py
  • src/lola/targets/base.py
  • src/lola/targets/install.py
  • tests/test_instructions.py

📝 Walkthrough

Walkthrough

This PR converts Lola's instruction installation from automatic (opt-out) to opt-in with user preference persistence. Changes include a new --instructions/--no-instructions CLI flag, tri-state installation logic with interactive prompting for existing files, target-level full-file replacement semantics, and comprehensive test coverage for all new scenarios.

Changes

Instructions opt-in migration with interactive state management

Layer / File(s) Summary
Installation data model with instruction preference
src/lola/models.py
Installation dataclass adds install_instructions: bool field with round-trip YAML serialization via to_dict() and from_dict() (defaults to False for legacy records).
Target replacement semantics for full-file instructions
src/lola/targets/base.py
ManagedInstructionsTarget now implements full-file replacement (not managed sections), returns False when markers absent during removal, and updated docstring describes new contract.
Interactive confirmation helper for instruction replacement
src/lola/prompts.py, src/lola/targets/install.py (imports)
New confirm_replace_instructions helper prompts before replacing existing files; prompts import expanded to include it.
Tri-state installation logic with interactive handling
src/lola/targets/install.py (_install_instructions)
_install_instructions accepts tri-state install_instructions parameter: skips when disabled, validates project scope, and prompts (interactive) or rejects (non-interactive) when destination exists and append_context not used.
Registry state tracking and persistence
src/lola/targets/install.py (install_to_assistant)
install_to_assistant accepts install_instructions parameter, matches existing installations by project path, computes persistence state from tri-state override and prior record, and stores keep_installing_instructions and has_instructions in registry.
Full-file instruction deletion during uninstall
src/lola/targets/install.py (_uninstall_instructions)
Early unlink-and-return path when registry indicates full-file managed instructions were installed and destination is a non-directory file.
CLI flag and validation for instruction preference
src/lola/cli/install.py
Adds --instructions/--no-instructions Click option, enforces compatibility (rejects --append-context with --no-instructions, auto-enables with --append-context), wires parameter to install_to_assistant.
Update command refactor to respect instruction preference
src/lola/cli/install.py
Moves _install_instructions import to module scope, refactors _update_instructions to short-circuit when preference is disabled, unifies generation through _install_instructions, updates has_instructions persistence to respect preference.
Comprehensive test coverage for instruction state management
tests/test_instructions.py
Tests cover serialization defaults, target file-replacement semantics, interactive/non-interactive flows, preference persistence, uninstall deletion, legacy record handling, append-context replacement, and update regression cases.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes


A rabbit hops through instruction forests,
"Opt-in, oh how sweet!" it boasts,
No more uninvited sneaking files,
The prompts now ask before it compiles,
Registry keeps the preference for trials. 🐰✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'fix: improve assistant instruction file handling' directly addresses the main objective of fixing issue #158 by implementing improved instruction file handling with proper safeguards.
Linked Issues check ✅ Passed All core requirements from #158 are met: instruction injection is now opt-in via --instructions/--no-instructions flags, existing files are protected by default, user choices are persisted in installation records, and file replacement (not append) is the new behavior.
Out of Scope Changes check ✅ Passed All changes align with the PR objectives and #158 requirements. The modifications to CLI options, Installation model, prompt handling, target behavior, and test coverage are all directly necessary to implement opt-in instruction handling.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-158-instructions-replacement

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@SecKatie SecKatie force-pushed the issue-158-instructions-replacement branch from 11b5f47 to 338c106 Compare May 25, 2026 16:46
@SecKatie SecKatie marked this pull request as ready for review May 25, 2026 16:48
Comment thread src/lola/targets/base.py
module_name: str,
) -> bool:
"""Generate/update module instructions in a managed section."""
"""Replace the assistant instructions file with module instructions."""
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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 AGENTS.md.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Collaborator

@mrbrandao mrbrandao May 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree here, this would be a regression in GEMINI.md instructions. As for "nuke AGENTS.md" I agree this can be necessary sometimes when you want a full bootstrap project, such as when using the context module or plugin; however, it must not be the default. Perhaps it will be clearer to call this overwrite.
An use:
lola install my-mod my-project --overwrite or something along those lines.

Moreover:

it would make more sense to me to use a pointer to an installed file instead of updating huge chunks of text

@trevor-vaughan Thats mostly for what --append-context is created for. @SecKatie, With the current PR, we are also creating some regression in this behavior. That's aligned with my comment here: https://github.com/LobsterTrap/lola/pull/159/changes#r3299635672
Please check out, and let us know.

install_instructions is None
and instructions_dest.exists()
and not instructions_dest.is_dir()
and not append_context
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be dangerous if CLAUDE.md exists, and I pass --append-context this can bypass and replace entirely the file.

Comment thread src/lola/cli/install.py
"(e.g., module/AGENTS.md).",
)
@click.option(
"--instructions/--no-instructions",
Copy link
Copy Markdown
Collaborator

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|--overwrite but it must be default to FALSE and if using as a bool, the --no-instructions might 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, ensuring overwrite is exactly the opposite of append, the code should not touch each other's functions, aside from the mutually exclusive flags.

Comment thread src/lola/targets/base.py
module_name: str,
) -> bool:
"""Generate/update module instructions in a managed section."""
"""Replace the assistant instructions file with module instructions."""
Copy link
Copy Markdown
Collaborator

@mrbrandao mrbrandao May 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree here, this would be a regression in GEMINI.md instructions. As for "nuke AGENTS.md" I agree this can be necessary sometimes when you want a full bootstrap project, such as when using the context module or plugin; however, it must not be the default. Perhaps it will be clearer to call this overwrite.
An use:
lola install my-mod my-project --overwrite or something along those lines.

Moreover:

it would make more sense to me to use a pointer to an installed file instead of updating huge chunks of text

@trevor-vaughan Thats mostly for what --append-context is created for. @SecKatie, With the current PR, we are also creating some regression in this behavior. That's aligned with my comment here: https://github.com/LobsterTrap/lola/pull/159/changes#r3299635672
Please check out, and let us know.

@mrbrandao mrbrandao added the enhancement New feature or request label May 25, 2026
Copy link
Copy Markdown
Collaborator

@sergio-correia sergio-correia left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not in this diff, but closely related: uninstall_cmd at src/lola/cli/install.py:1191-1199 has its own inline instruction-removal logic that calls target.remove_instructions() directly. Since generate_instructions no longer writes marker blocks, that call finds no markers and returns False -- the file is never deleted.

The correct full-file deletion logic exists in _uninstall_instructions() (targets/install.py:768-771) but uninstall_cmd does not call it. The test test_uninstall_deletes_full_file_instructions tests _uninstall_instructions directly (not via uninstall_cmd), so it passes despite the CLI path being broken.

Either have uninstall_cmd call _uninstall_instructions(target, inst), or add the full-file branch inline:

if inst.has_instructions:
    instructions_dest = target.get_instructions_path(path_context, inst_scope)
    if inst.install_instructions and instructions_dest.exists() \
            and not instructions_dest.is_dir():
        instructions_dest.unlink()
        removed_count += 1
    elif target.remove_instructions(instructions_dest, module_name):
        removed_count += 1

keep_installing_instructions = existing_installation.install_instructions
has_instructions = existing_installation.has_instructions
else:
keep_installing_instructions = not module.has_instructions
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This expression is inverted: when a module has no instructions (module.has_instructions is False), not module.has_instructions stores install_instructions=True in the registry.

If the module later adds an AGENTS.md and the user runs lola update, _update_instructions sees install_instructions=True and calls _install_instructions(install_instructions=True), which bypasses the existing-file protection check (lines 379-393 only fire when install_instructions is None). The user's hand-written CLAUDE.md gets silently overwritten.

Suggested change
keep_installing_instructions = not module.has_instructions
keep_installing_instructions = False

A module without instructions cannot infer user consent to manage instruction files. If instructions appear in a future version, the normal existence-check flow should apply.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] - Lola writes excessive, and usually unnecessary, material into AGENTS.md and friends

4 participants