feat(project-context): merge global AGENTS.md with project AGENTS.md (#1157)#1399
feat(project-context): merge global AGENTS.md with project AGENTS.md (#1157)#1399linzhiqin2003 wants to merge 1 commit into
Conversation
…mbown#1157) travel into every session, ideally merged with a project's local AGENTS.md when both exist. Maintainer agreed: > yes that makes sense! am working on getting this organizational > structure better today so that worktrees etc can feel like an > intended way of using this. The fallback path already loaded the global file when no workspace context existed, but dropped it silently the moment a project AGENTS.md showed up. After this PR: * Both files present → merged. The global block is prepended with a labelled HTML-style fence (`<!-- global: /home/u/.deepseek/AGENTS.md -->`), then the project block follows with its own fence (`<!-- project (overrides global where they conflict) -->`). Order is global-first so workspace rules read last and win "last word" precedence with the model when they disagree. * Only project file present → unchanged from before. * Only global file present → unchanged from before (still acts as a fallback). The merge framing is suppressed in the global-only case so the prompt stays minimal. `source_path` continues to point at the more-specific file (project > global > nothing) because that's the path the user is likely to edit when they want to override something. Two tests: * `test_local_and_global_agents_merge_when_both_exist` — the actual Hmbown#1157 scenario. Asserts both blocks are present, global precedes project, and the merge-framing label appears between them. * `test_global_agents_only_no_project_unchanged_fallback` — sanity check that the global-only path doesn't accidentally inherit the merge framing. The pre-existing `test_load_global_agents_when_project_has_no_context` still passes, so the global-as-fallback contract is preserved. Refs Hmbown#1157 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request updates the project context loading logic to merge global user-wide instructions from ~/.deepseek/AGENTS.md with project-local instructions, rather than treating the global file only as a fallback. The global instructions are prepended to the project instructions, ensuring that project-specific rules take precedence. A helper function merge_global_and_project_instructions was added to handle the concatenation and labeling of these instruction blocks. Feedback was provided to refine the string trimming logic in the merge function to avoid accidentally stripping Markdown indentation, suggesting a switch from trim() to specifically matching newline characters.
| global.trim_end(), | ||
| project.trim_start(), |
There was a problem hiding this comment.
Using trim_end() and trim_start() removes all leading and trailing whitespace, including spaces used for indentation. This can break Markdown formatting if the AGENTS.md file starts or ends with indented content, such as a code block or an indented list item. It is safer to only trim leading and trailing newlines to preserve indentation while avoiding excessive vertical space between the merged blocks.
| global.trim_end(), | |
| project.trim_start(), | |
| global.trim_end_matches(['\n', '\r']), | |
| project.trim_start_matches(['\n', '\r']), |
|
Thanks @linzhiqin2003 — your fix landed on Credit lives in the commit message and the |
Summary
Closes #1157. Reporter:
Maintainer:
The fallback half already shipped — `load_global_agents_context` loads `~/.deepseek/AGENTS.md` when no workspace context exists. The merge half ("if both a global and a local AGENTS.md exist…") didn't: the moment a project AGENTS.md was found, the global file was silently ignored.
Fix
In `load_project_context_with_parents_and_home`, always probe the global file. The behaviour now splits cleanly:
The merged block is wrapped in labelled HTML-style fences so the model can tell layers apart when it explains which rule it followed:
`source_path` continues to point at the more-specific file (project > global) so the user knows where to edit when something looks wrong.
Tests
```
$ cargo test -p deepseek-tui -- project_context
test project_context::tests::test_local_and_global_agents_merge_when_both_exist ... ok ← new
test project_context::tests::test_global_agents_only_no_project_unchanged_fallback ... ok ← new
test project_context::tests::test_load_global_agents_when_project_has_no_context ... ok ← unchanged
test project_context::tests::test_invalid_global_agents_warns_and_falls_back_to_generated_context ... ok
…
test result: ok. 19 passed; 0 failed
```
The historical `test_local_agents_takes_priority_over_global_agents` test (which asserted the old drop-global behaviour) was rewritten — it now asserts the merge contract instead.
Acceptance gates
Closes #1157