Load AGENTS.md from the repository root down to the session directory
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 19.9k
- Forks
- 2.3k
- Avg merge
- 2d 7h
- Merged PRs (30d)
- 30
Description
Problem
load_agents_md_files_from_dir in crates/jcode-base/src/prompt.rs:816 loads exactly two files:
let project_dir = working_dir.unwrap_or(Path::new("."));
if let Some((content, size)) = load_file(
&project_dir.join("AGENTS.md"),
"Project Instructions (AGENTS.md)",
) { ... }
if let Ok(global_agents_md) = crate::storage::user_home_path("AGENTS.md") { ... }
The session working directory and the home directory. Nothing in between.
The consequence: start jcode anywhere other than the repository root and the repository's own AGENTS.md is silently not loaded. cd crates/jcode-tui && jcode gets no project instructions at all, because crates/jcode-tui/AGENTS.md does not exist and the root AGENTS.md is never looked at. In a monorepo the same thing happens for every package directory. There is no warning; /info just reports project AGENTS.md: not loaded, which is easy to read as "this repo has no AGENTS.md" rather than "you started from the wrong directory".
Every comparable harness resolves this by walking up:
- Codex builds the instruction chain from the global
~/.codex/AGENTS.mdplus everyAGENTS.mdon the path from the git root to the current directory, concatenated in that order, capped atproject_doc_max_bytes(default 32 KiB). Files closer to cwd come later so they refine broader guidance. - opencode injects all parent
AGENTS.mdfiles on the path, with the nearest file taking precedence. Fromapps/web/componentsit loads the rootAGENTS.mdthenapps/web/AGENTS.md, and leaves sibling branches alone. - Claude Code walks
CLAUDE.mdup the directory tree from cwd.
jcode is the outlier, and it is the one that already advertises AGENTS.md compatibility: it imports MCP config from ~/.claude.json and ~/.codex/config.toml on first run, loads skills from ~/.claude/skills and ~/.codex/skills, and resumes sessions from Claude Code, Codex, opencode, and pi. Someone arriving from Codex reasonably expects the same AGENTS.md resolution and does not get it.
The fix is contained: one function, a .git walk-up, a byte cap, and one format string in /info.
This feature was proposed from code analysis without any hands-on usage.
Proposed approach
crates/jcode-base/src/prompt.rs (implementation)
Add near load_agents_md_files_from_dir (currently line 816):
/// Byte ceiling for the merged project AGENTS.md chain. Matches Codex's
/// `project_doc_max_bytes` default so a repo tuned for one harness behaves
/// the same in the other. Files are added root-first and the walk stops once
/// the next file would exceed this.
const PROJECT_AGENTS_MD_MAX_BYTES: usize = 32 * 1024;
/// Nearest ancestor of `start` (inclusive) containing a `.git` entry.
/// `.git` may be a directory or, in a linked worktree, a file.
fn repository_root(start: &Path) -> Option<PathBuf> { ... }
/// Every AGENTS.md from `root` down to `leaf` inclusive, root first.
fn agents_md_chain(root: &Path, leaf: &Path) -> Vec<PathBuf> { ... }
Rewrite the project half of load_agents_md_files_from_dir to call these, keeping the existing load_file closure and the existing global-home branch untouched. Keep the function signature (Option<&Path>) -> (Option<String>, ContextInfo) so both call sites compile unchanged.
Canonicalize with std::fs::canonicalize where available and fall back to the raw path on error, so a working directory behind a symlink still terminates the walk. Bound the walk at 64 levels so a pathological path cannot spin.
Label format, so a reader of the prompt can attribute a rule:
# Project Instructions (AGENTS.md) <- repository root, unchanged label
# Project Instructions (crates/jcode-tui/AGENTS.md)
# Global Instructions (~/AGENTS.md) <- unchanged
Keeping the root label byte-identical to today's string matters: crates/jcode-base/src/prompt_tests.rs and the /info report both assert on these strings.
crates/jcode-base/src/prompt.rs (ContextInfo, currently line 205)
has_project_agents_md and project_agents_md_chars keep their meaning, now describing the merged chain. Add two fields:
/// Number of AGENTS.md files merged from the repository root down to the
/// session working directory.
pub project_agents_md_files: usize,
/// True when the 32 KiB project-chain cap stopped the walk early.
pub project_agents_md_truncated: bool,
ContextInfo derives Default, so no other constructor changes.
crates/jcode-base/src/prompt.rs call sites (lines 397 and 496)
Both already do:
info.has_project_agents_md = md_info.has_project_agents_md;
info.project_agents_md_chars = md_info.project_agents_md_chars;
Add the two new field copies to both. Line 397 is the dynamic path, line 496 is the cacheable-static path; missing either makes /info disagree with itself depending on which prompt builder ran.
crates/jcode-tui/src/tui/app/state_ui.rs (wiring, /info report at line 2130)
The context report line is currently:
- project AGENTS.md: {} ({})
Change to report the chain, for example project AGENTS.md: loaded (3 files, 4812 chars) and append [truncated at 32 KiB] when project_agents_md_truncated. This is the only user-visible surface that names AGENTS.md, and leaving it saying "loaded" for a 3-file chain is the kind of silent ambiguity the current bug already causes.
crates/jcode-base/src/prompt_tests.rs (tests)
The existing test_load_agents_md_files_uses_sandboxed_global_files must keep passing unchanged; it asserts the exact # Global Instructions (~/AGENTS.md) label. Add:
test_agents_md_chain_loads_repo_root_from_subdirectory- tempdir with.git/,AGENTS.mdat root and atcrates/x/, call withSome(root/crates/x), assert both appear and the root content precedes the nested content.test_agents_md_chain_without_git_root_matches_legacy_behavior- no.gitanywhere, assert only the working-directory file loads andproject_agents_md_files == 1.test_agents_md_chain_dedupes_when_started_at_repo_root-.git/andAGENTS.mdboth at root, call withSome(root), assert the content appears exactly once.test_agents_md_chain_respects_byte_cap- root file over 32 KiB plus a nested file, assert the nested file is dropped,project_agents_md_truncatedis true, and the total stays at or under the cap.test_repository_root_detects_worktree_git_file-.gitwritten as a file containinggitdir: ..., assert the root is still found.
Tests must use crate::storage::lock_test_env() and set JCODE_HOME the way the existing test does, because ~/AGENTS.md lookup is process-global.
Estimated scope: roughly 90 lines of implementation across prompt.rs, 6 lines in state_ui.rs, and about 110 lines of tests.
Willingness to implement
I'm happy to open a PR implementing this if the direction works for you.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with load_agents_md_files_from_dir in crates/jcode-base/src/prompt.rs and review ContextInfo plus its call sites around lines 397 and 496. Then inspect the /info report in crates/jcode-tui/src/tui/app/state_ui.rs and the existing tests in crates/jcode-base/src/prompt_tests.rs. Done means repository-root-to-session AGENTS.md files load in order within the byte cap, ContextInfo reports the chain, /info shows file count and truncation, and the listed tests pass.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- cli, developer-experience
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100