diff --git a/README.md b/README.md
index c904c806..6fd45996 100644
--- a/README.md
+++ b/README.md
@@ -568,6 +568,22 @@ jcode dictate
jcode supports interactive TUI use, non-interactive runs, persistent server/client workflows,
and hotkey-friendly dictation without requiring a bundled speech-to-text stack.
+### Context File Control
+
+Skip loading project `AGENTS.md` and global `~/.AGENTS.md` context files for a session:
+
+```bash
+# CLI flag (preferred)
+jcode --no-context-files
+
+# Or via environment variable
+JCODE_NO_CONTEXT_FILES=1 jcode
+
+# Both work identically; the CLI flag sets the env var internally
+```
+
+This is useful when you want to test with a clean context or run sessions without project instructions.
+
diff --git a/src/cli/args.rs b/src/cli/args.rs
index 1d07efa5..92ab7028 100644
--- a/src/cli/args.rs
+++ b/src/cli/args.rs
@@ -94,6 +94,9 @@ pub(crate) struct Args {
/// Suppress non-error CLI/status output for scripting and wrappers
#[arg(long, global = true)]
pub(crate) quiet: bool,
+ /// Skip loading AGENTS.md project/global context files for this session
+ #[arg(long, global = true)]
+ pub(crate) no_context_files: bool,
/// Resume a session by ID, or list sessions if no ID provided
#[arg(long, global = true, num_args = 0..=1, default_missing_value = "")]
diff --git a/src/cli/startup.rs b/src/cli/startup.rs
index 8b1acae6..3c3643c6 100644
--- a/src/cli/startup.rs
+++ b/src/cli/startup.rs
@@ -131,6 +131,12 @@ fn parse_and_prepare_args() -> Result {
}
}
+ // --no-context-files: translate to JCODE_NO_CONTEXT_FILES so the prompt
+ // loading helpers can skip AGENTS.md without threading args.
+ if args.no_context_files {
+ crate::env::set_var("JCODE_NO_CONTEXT_FILES", "1");
+ }
+
if let Some(ref socket) = args.socket {
server::set_socket_path(socket);
}
diff --git a/src/prompt.rs b/src/prompt.rs
index 496df490..d1ccf6b7 100644
--- a/src/prompt.rs
+++ b/src/prompt.rs
@@ -3,6 +3,11 @@
use std::path::{Path, PathBuf};
use std::process::Command;
+/// Whether context files should be skipped (set by --no-context-files or JCODE_NO_CONTEXT_FILES env var).
+pub fn context_files_disabled() -> bool {
+ std::env::var("JCODE_NO_CONTEXT_FILES").is_ok()
+}
+
/// Default system prompt for jcode (embedded at compile time)
pub const DEFAULT_SYSTEM_PROMPT: &str = include_str!("prompt/system_prompt.md");
const SELFDEV_HINT_PROMPT: &str = include_str!("prompt/selfdev_hint.txt");
@@ -378,11 +383,13 @@ fn build_selfdev_hint_prompt() -> String {
}
/// Build self-dev tools prompt section (static version without dynamic socket path)
+#[allow(dead_code)]
fn build_selfdev_prompt_static() -> String {
build_selfdev_prompt_static_for_context(SelfDevProductContext::Tui)
}
/// Build self-dev tools prompt section
+#[allow(dead_code)]
fn build_selfdev_prompt() -> String {
build_selfdev_prompt_for_context(SelfDevProductContext::Tui)
}
@@ -626,6 +633,10 @@ fn gpu_summary() -> Option {
/// Load AGENTS.md files from a specific working directory
pub fn load_agents_md_files_from_dir(working_dir: Option<&Path>) -> (Option, ContextInfo) {
+ if context_files_disabled() {
+ eprintln!("Context files disabled (--no-context-files)");
+ return (None, ContextInfo::default());
+ }
let mut contents = vec![];
let mut info = ContextInfo::default();
diff --git a/src/prompt_tests.rs b/src/prompt_tests.rs
index 42c21849..c60d2f66 100644
--- a/src/prompt_tests.rs
+++ b/src/prompt_tests.rs
@@ -1,4 +1,6 @@
use super::*;
+use crate::cli::args::Args;
+use clap::Parser;
/// Verify the default system prompt does NOT identify as "Claude Code"
/// It's fine to say "powered by Claude" but not "Claude Code" (Anthropic's product)
@@ -311,3 +313,105 @@ fn build_system_prompt_full_uses_jcode_system_md_root() {
assert!(info.system_prompt_chars < 200);
assert!(!prompt.contains(crate::prompt::DEFAULT_SYSTEM_PROMPT));
}
+
+#[test]
+fn test_context_files_disabled_returns_false_by_default() {
+ let _guard = crate::storage::lock_test_env();
+ // Ensure the env var is NOT set
+ crate::env::remove_var("JCODE_NO_CONTEXT_FILES");
+ assert!(!context_files_disabled());
+}
+
+#[test]
+fn test_context_files_disabled_returns_true_when_env_set() {
+ let _guard = crate::storage::lock_test_env();
+ let prev_val = std::env::var("JCODE_NO_CONTEXT_FILES");
+ // Ensure the env var is set for this test
+ crate::env::set_var("JCODE_NO_CONTEXT_FILES", "1");
+ assert!(context_files_disabled());
+ // Restore previous state
+ match prev_val {
+ Ok(val) => crate::env::set_var("JCODE_NO_CONTEXT_FILES", val),
+ Err(_) => crate::env::remove_var("JCODE_NO_CONTEXT_FILES"),
+ }
+}
+
+#[test]
+fn test_load_agents_md_from_dir_returns_none_when_disabled() {
+ let _guard = crate::storage::lock_test_env();
+ let prev_home = std::env::var_os("JCODE_HOME");
+ let temp = tempfile::TempDir::new().unwrap();
+ crate::env::set_var("JCODE_HOME", temp.path());
+ crate::env::set_var("JCODE_NO_CONTEXT_FILES", "1");
+
+ // Even with a global AGENTS.md present, loading should be skipped
+ std::fs::create_dir_all(temp.path()).unwrap();
+ std::fs::write(temp.path().join("AGENTS.md"), "global agents instructions").unwrap();
+
+ let (content, _info) = load_agents_md_files_from_dir(None);
+ assert!(content.is_none());
+
+ if let Some(prev_home) = prev_home {
+ crate::env::set_var("JCODE_HOME", prev_home);
+ } else {
+ crate::env::remove_var("JCODE_HOME");
+ }
+ crate::env::remove_var("JCODE_NO_CONTEXT_FILES");
+}
+
+#[test]
+fn test_load_agents_md_from_dir_loads_files_when_not_disabled() {
+ let _guard = crate::storage::lock_test_env();
+ let prev_home = std::env::var_os("JCODE_HOME");
+ let temp = tempfile::TempDir::new().unwrap();
+ crate::env::set_var("JCODE_HOME", temp.path());
+
+ // Remove any leftover env var
+ crate::env::remove_var("JCODE_NO_CONTEXT_FILES");
+
+ std::fs::create_dir_all(temp.path().join("external")).unwrap();
+ std::fs::write(
+ temp.path().join("external/AGENTS.md"),
+ "global agents instructions",
+ )
+ .unwrap();
+
+ let (content, info) = load_agents_md_files_from_dir(None);
+ assert!(info.has_global_agents_md);
+ assert!(
+ content
+ .as_ref()
+ .map(|c| c.contains("global agents instructions"))
+ .unwrap_or(false)
+ );
+
+ if let Some(prev_home) = prev_home {
+ crate::env::set_var("JCODE_HOME", prev_home);
+ } else {
+ crate::env::remove_var("JCODE_HOME");
+ }
+}
+
+#[test]
+fn test_cli_flag_no_short_alias() {
+ // Verify that -c is NOT a valid alias for --no-context-files
+ let result = Args::try_parse_from(["jcode", "-c", "--provider", "openai"]);
+ assert!(
+ result.is_err(),
+ "-c should not be a valid short flag for --no-context-files"
+ );
+}
+
+#[test]
+fn test_cli_flag_no_context_files_parsed() {
+ let args = Args::parse_from(["jcode", "--no-context-files"]);
+ assert!(args.no_context_files);
+
+ // Without the flag, should be false
+ let args2 = Args::parse_from(["jcode"]);
+ assert!(!args2.no_context_files);
+
+ // With subcommand
+ let args3 = Args::parse_from(["jcode", "--no-context-files", "run", "hello"]);
+ assert!(args3.no_context_files);
+}