1jehuang / 1jehuang/jcode

Load AGENTS.md from the repository root down to the session directory

Open
#697 1 comment 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

autonomous: likely enhancement priority: medium triage: needs-decision
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.md plus every AGENTS.md on the path from the git root to the current directory, concatenated in that order, capped at project_doc_max_bytes (default 32 KiB). Files closer to cwd come later so they refine broader guidance.
  • opencode injects all parent AGENTS.md files on the path, with the nearest file taking precedence. From apps/web/components it loads the root AGENTS.md then apps/web/AGENTS.md, and leaves sibling branches alone.
  • Claude Code walks CLAUDE.md up 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:

  1. test_agents_md_chain_loads_repo_root_from_subdirectory - tempdir with .git/, AGENTS.md at root and at crates/x/, call with Some(root/crates/x), assert both appear and the root content precedes the nested content.
  2. test_agents_md_chain_without_git_root_matches_legacy_behavior - no .git anywhere, assert only the working-directory file loads and project_agents_md_files == 1.
  3. test_agents_md_chain_dedupes_when_started_at_repo_root - .git/ and AGENTS.md both at root, call with Some(root), assert the content appears exactly once.
  4. 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_truncated is true, and the total stays at or under the cap.
  5. test_repository_root_detects_worktree_git_file - .git written as a file containing gitdir: ..., 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.