Skip to content

feat(project-context): merge global AGENTS.md with project AGENTS.md (#1157)#1399

Closed
linzhiqin2003 wants to merge 1 commit into
Hmbown:mainfrom
linzhiqin2003:feat/1157-merge-global-agents
Closed

feat(project-context): merge global AGENTS.md with project AGENTS.md (#1157)#1399
linzhiqin2003 wants to merge 1 commit into
Hmbown:mainfrom
linzhiqin2003:feat/1157-merge-global-agents

Conversation

@linzhiqin2003

Copy link
Copy Markdown
Contributor

Summary

Closes #1157. Reporter:

Currently, deepseek-tui only loads the AGENTS.md file from the current project directory … I would like deepseek-tui to support loading a global AGENTS.md file (e.g., located at ~/.deepseek/AGENTS.md) upon startup. Ideally, if both a global and a local AGENTS.md exist, the application could merge them, or at least use the global one as a fallback when a local one is not found.

Maintainer:

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 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:

Files present Behaviour
Only project Unchanged from before
Only global Unchanged from before (fallback path)
Both Merged — global first, then project (so project wins last-word precedence with the model)
Neither Unchanged — falls through to the existing `auto_generate_context` path

The merged block is wrapped in labelled HTML-style fences so the model can tell layers apart when it explains which rule it followed:

<!-- global: /home/user/.deepseek/AGENTS.md -->
…global content…

<!-- project (overrides global where they conflict) -->
…project content…

`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
```

  • `test_local_and_global_agents_merge_when_both_exist` — the actual [Feature Request] Support loading a global AGENTS.md (~/.deepseek/AGENTS.md) #1157 scenario. Asserts both blocks are present, global precedes project, and the labelled separator appears between them.
  • `test_global_agents_only_no_project_unchanged_fallback` — sanity that the global-only case doesn't pick up the merge framing (no `project (overrides global` label when there's nothing to override).
  • The pre-existing `test_load_global_agents_when_project_has_no_context` still passes, so the fallback contract is preserved.

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

  • `cargo fmt --all -- --check`
  • `cargo clippy --workspace --all-targets -- -D warnings` — clean
  • `cargo build --workspace`
  • `cargo test -p deepseek-tui -- project_context` — 19/19

Closes #1157

…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>

@gemini-code-assist gemini-code-assist Bot left a comment

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.

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.

Comment on lines +450 to +451
global.trim_end(),
project.trim_start(),

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.

medium

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.

Suggested change
global.trim_end(),
project.trim_start(),
global.trim_end_matches(['\n', '\r']),
project.trim_start_matches(['\n', '\r']),

@Hmbown

Hmbown commented May 12, 2026

Copy link
Copy Markdown
Owner

Thanks @linzhiqin2003 — your fix landed on main as a5c4a21c9 and shipped in v0.8.29. Closing this PR now that the code is on main. Sorry for the lag — the v0.8.31 release ships an auto-close workflow that catches this case automatically from now on.

Credit lives in the commit message and the CHANGELOG.md entry for v0.8.29.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature Request] Support loading a global AGENTS.md (~/.deepseek/AGENTS.md)

2 participants