Skip to content

Commit ea21e84

Browse files
authored
Fallback behavior for Paths & Env Variables (#3360)
- Path fall back .amazonq to .kiro migration functionality - Support workspace and global path migration - Maintain backward compatibility with existing .amazonq configs - Follow proper precedence rules (kiro > amazonq when both exist) - Add comprehensive unit tests covering all migration scenarios
1 parent 97773f1 commit ea21e84

File tree

10 files changed

+424
-75
lines changed

10 files changed

+424
-75
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ manual_ok_or = "warn"
178178
map_err_ignore = "warn"
179179
map_flatten = "warn"
180180
map_unwrap_or = "warn"
181-
match_on_vec_items = "warn"
181+
182182
# match_same_arms = "warn"
183183
match_wild_err_arm = "warn"
184184
match_wildcard_for_single_variants = "warn"

crates/agent/src/agent/agent_config/mod.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,6 @@ use tracing::{
3434
use super::util::directories::{
3535
global_agents_path,
3636
legacy_global_mcp_config_path,
37-
};
38-
use crate::agent::util::directories::{
3937
legacy_workspace_mcp_config_path,
4038
local_agents_path,
4139
};

crates/agent/src/agent/util/directories.rs

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ use super::error::{
1414
use crate::agent::util::consts::env_var::CLI_DATA_DIR;
1515

1616
const DATA_DIR_NAME: &str = "amazon-q";
17-
const AWS_DIR_NAME: &str = "amazonq";
1817

1918
type Result<T, E = UtilError> = std::result::Result<T, E>;
2019

@@ -57,28 +56,50 @@ pub fn settings_schema_path(base: impl AsRef<Path>) -> PathBuf {
5756
base.as_ref().join("settings_schema.json")
5857
}
5958

59+
fn resolve_migrated_path(is_global: bool, subpath: &str) -> Result<PathBuf> {
60+
let (kiro_base, amazonq_base) = if is_global {
61+
let home = home_dir()?;
62+
(home.join(".aws/kiro"), home.join(".aws/amazonq"))
63+
} else {
64+
let cwd = env::current_dir().context("unable to get the current directory")?;
65+
(cwd.join(".kiro"), cwd.join(".amazonq"))
66+
};
67+
68+
let scope = if is_global { "global" } else { "workspace" };
69+
70+
match (kiro_base.exists(), amazonq_base.exists()) {
71+
(true, false) => {
72+
warn!("Using .kiro {} configuration", scope);
73+
Ok(kiro_base.join(subpath))
74+
},
75+
(false, true) => {
76+
warn!("Migration notice: Using .amazonq {} configs", scope);
77+
Ok(amazonq_base.join(subpath))
78+
},
79+
(true, true) => {
80+
warn!("Both .amazonq and .kiro {} configs exist, using .amazonq", scope);
81+
Ok(amazonq_base.join(subpath))
82+
},
83+
(false, false) => Ok(kiro_base.join(subpath)), // Default to kiro
84+
}
85+
}
86+
6087
/// Path to the directory containing local agent configs.
6188
pub fn local_agents_path() -> Result<PathBuf> {
62-
Ok(env::current_dir()
63-
.context("unable to get the current directory")?
64-
.join(format!(".{AWS_DIR_NAME}"))
65-
.join("cli-agents"))
89+
resolve_migrated_path(false, "cli-agents")
6690
}
6791

6892
/// Path to the directory containing global agent configs.
6993
pub fn global_agents_path() -> Result<PathBuf> {
70-
Ok(home_dir()?.join(".aws").join(AWS_DIR_NAME).join("cli-agents"))
94+
resolve_migrated_path(true, "cli-agents")
7195
}
7296

7397
/// Legacy workspace MCP server config path
7498
pub fn legacy_workspace_mcp_config_path() -> Result<PathBuf> {
75-
Ok(env::current_dir()
76-
.context("unable to get the current directory")?
77-
.join(format!(".{AWS_DIR_NAME}"))
78-
.join("mcp.json"))
99+
resolve_migrated_path(false, "mcp.json")
79100
}
80101

81102
/// Legacy global MCP server config path
82103
pub fn legacy_global_mcp_config_path() -> Result<PathBuf> {
83-
Ok(home_dir()?.join(".aws").join(AWS_DIR_NAME).join("mcp.json"))
104+
resolve_migrated_path(true, "mcp.json")
84105
}

crates/chat-cli/src/cli/agent/mod.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,6 @@ impl Default for Agent {
197197
resources: {
198198
let mut resources = Vec::new();
199199
resources.extend(paths::workspace::DEFAULT_AGENT_RESOURCES.iter().map(|&s| s.into()));
200-
resources.push(format!("file://{}", paths::workspace::RULES_PATTERN).into());
201200
resources
202201
},
203202
hooks: Default::default(),
@@ -740,6 +739,13 @@ impl Agents {
740739

741740
all_agents.push({
742741
let mut agent = Agent::default();
742+
743+
// Add rules pattern using dynamic path resolution
744+
if let Ok(rules_dir) = resolver.workspace().rules_dir() {
745+
let rules_pattern = paths::workspace::RULES_PATTERN.replace("{}", &rules_dir.display().to_string());
746+
agent.resources.push(rules_pattern.into());
747+
}
748+
743749
if mcp_enabled {
744750
'load_legacy_mcp_json: {
745751
if global_mcp_config.is_none() {

crates/chat-cli/src/cli/agent/root_command_args.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ use super::{
2525
use crate::database::settings::Setting;
2626
use crate::os::Os;
2727
use crate::theme::StyledText;
28-
use crate::util::paths;
2928
use crate::util::paths::PathResolver;
3029

3130
#[derive(Clone, Debug, Subcommand, PartialEq, Eq)]
@@ -336,7 +335,7 @@ pub async fn create_agent(
336335
bail!("Path must be a directory");
337336
}
338337

339-
path.join(paths::workspace::AGENTS_DIR)
338+
PathResolver::new(os).workspace().agents_dir()?
340339
} else {
341340
PathResolver::new(os).global().agents_dir()?
342341
};

crates/chat-cli/src/cli/chat/cli/prompts.rs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -733,10 +733,14 @@ impl PromptsArgs {
733733
}
734734

735735
if !global_prompts.is_empty() {
736+
let global_dir = PathResolver::new(os)
737+
.global()
738+
.prompts_dir()
739+
.map_or_else(|_| "global prompts".to_string(), |p| p.display().to_string());
736740
queue!(
737741
session.stderr,
738742
style::SetAttribute(Attribute::Bold),
739-
style::Print(&format!("Global ({}):", crate::util::paths::global::PROMPTS_DIR)),
743+
style::Print(&format!("Global ({global_dir}):")),
740744
StyledText::reset_attributes(),
741745
style::Print("\n"),
742746
)?;
@@ -750,10 +754,14 @@ impl PromptsArgs {
750754
if !global_prompts.is_empty() {
751755
queue!(session.stderr, style::Print("\n"))?;
752756
}
757+
let local_dir = PathResolver::new(os)
758+
.workspace()
759+
.prompts_dir()
760+
.map_or_else(|_| "local prompts".to_string(), |p| p.display().to_string());
753761
queue!(
754762
session.stderr,
755763
style::SetAttribute(Attribute::Bold),
756-
style::Print(&format!("Local ({}):", crate::util::paths::workspace::PROMPTS_DIR)),
764+
style::Print(&format!("Local ({local_dir}):")),
757765
StyledText::reset_attributes(),
758766
style::Print("\n"),
759767
)?;
@@ -2069,8 +2077,8 @@ mod tests {
20692077
let temp_dir = TempDir::new().unwrap();
20702078

20712079
// Create test prompts in temp directory structure
2072-
let global_dir = temp_dir.path().join(crate::util::paths::global::PROMPTS_DIR);
2073-
let local_dir = temp_dir.path().join(crate::util::paths::workspace::PROMPTS_DIR);
2080+
let global_dir = temp_dir.path().join(".aws/amazonq/prompts");
2081+
let local_dir = temp_dir.path().join(".amazonq/prompts");
20742082

20752083
create_prompt_file(&global_dir, "global_only", "Global content");
20762084
create_prompt_file(&global_dir, "shared", "Global shared");
@@ -2090,8 +2098,8 @@ mod tests {
20902098
let temp_dir = TempDir::new().unwrap();
20912099

20922100
// Create global and local directories
2093-
let global_dir = temp_dir.path().join(crate::util::paths::global::PROMPTS_DIR);
2094-
let local_dir = temp_dir.path().join(crate::util::paths::workspace::PROMPTS_DIR);
2101+
let global_dir = temp_dir.path().join(".aws/amazonq/prompts");
2102+
let local_dir = temp_dir.path().join(".amazonq/prompts");
20952103

20962104
// Create prompts: one with same name in both directories, one unique to each
20972105
create_prompt_file(&global_dir, "shared", "Global version");

crates/chat-cli/src/cli/chat/tool_manager.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2213,8 +2213,8 @@ mod tests {
22132213
let path = workspace_mcp_config_path(&os)?;
22142214
let path_str = path.to_string_lossy();
22152215

2216-
// Should end with .amazonq/mcp.json
2217-
assert!(path_str.ends_with(".amazonq/mcp.json"));
2216+
// Should end with .kiro/mcp.json (default fallback)
2217+
assert!(path_str.ends_with(".kiro/mcp.json"));
22182218

22192219
Ok(())
22202220
}

crates/chat-cli/src/util/consts.rs

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,69 +35,93 @@ pub mod env_var {
3535

3636
/// The current parent socket to connect to
3737
Q_PARENT = "Q_PARENT",
38+
KIRO_PARENT = "KIRO_PARENT",
3839

39-
/// Set the [`Q_PARENT`] parent socket to connect to
40+
/// Set the parent socket to connect to
4041
Q_SET_PARENT = "Q_SET_PARENT",
42+
KIRO_SET_PARENT = "KIRO_SET_PARENT",
4143

42-
/// Guard for the [`Q_SET_PARENT`] check
44+
/// Guard for the set parent check
4345
Q_SET_PARENT_CHECK = "Q_SET_PARENT_CHECK",
46+
KIRO_SET_PARENT_CHECK = "KIRO_SET_PARENT_CHECK",
4447

4548
/// Set if qterm is running, contains the version
4649
Q_TERM = "Q_TERM",
50+
KIRO_TERM = "KIRO_TERM",
4751

4852
/// Sets the current log level
4953
Q_LOG_LEVEL = "Q_LOG_LEVEL",
54+
KIRO_LOG_LEVEL = "KIRO_LOG_LEVEL",
5055

5156
/// Overrides the ZDOTDIR environment variable
5257
Q_ZDOTDIR = "Q_ZDOTDIR",
58+
KIRO_ZDOTDIR = "KIRO_ZDOTDIR",
5359

5460
/// Indicates a process was launched by Kiro
5561
PROCESS_LAUNCHED_BY_Q = "PROCESS_LAUNCHED_BY_Q",
62+
PROCESS_LAUNCHED_BY_KIRO = "PROCESS_LAUNCHED_BY_KIRO",
5663

5764
/// The shell to use in qterm
5865
Q_SHELL = "Q_SHELL",
66+
KIRO_SHELL = "KIRO_SHELL",
5967

6068
/// Indicates the user is debugging the shell
6169
Q_DEBUG_SHELL = "Q_DEBUG_SHELL",
70+
KIRO_DEBUG_SHELL = "KIRO_DEBUG_SHELL",
6271

6372
/// Indicates the user is using zsh autosuggestions which disables Inline
6473
Q_USING_ZSH_AUTOSUGGESTIONS = "Q_USING_ZSH_AUTOSUGGESTIONS",
74+
KIRO_USING_ZSH_AUTOSUGGESTIONS = "KIRO_USING_ZSH_AUTOSUGGESTIONS",
6575

6676
/// Overrides the path to the bundle metadata released with certain desktop builds.
6777
Q_BUNDLE_METADATA_PATH = "Q_BUNDLE_METADATA_PATH",
78+
KIRO_BUNDLE_METADATA_PATH = "KIRO_BUNDLE_METADATA_PATH",
6879

6980
/// Identifier for the client application or service using the chat-cli
7081
Q_CLI_CLIENT_APPLICATION = "Q_CLI_CLIENT_APPLICATION",
82+
KIRO_CLI_CLIENT_APPLICATION = "KIRO_CLI_CLIENT_APPLICATION",
7183

7284
/// Shows continuation IDs in chat output for debugging/development
7385
Q_SHOW_CONTINUATION_IDS = "Q_SHOW_CONTINUATION_IDS",
74-
75-
/// Flag for running integration tests
76-
CLI_IS_INTEG_TEST = "Q_CLI_IS_INTEG_TEST",
77-
78-
/// Enable logging to stdout
79-
Q_LOG_STDOUT = "Q_LOG_STDOUT",
80-
81-
/// Disable telemetry collection
82-
Q_DISABLE_TELEMETRY = "Q_DISABLE_TELEMETRY",
86+
KIRO_SHOW_CONTINUATION_IDS = "KIRO_SHOW_CONTINUATION_IDS",
8387

8488
/// Mock chat response for testing
8589
Q_MOCK_CHAT_RESPONSE = "Q_MOCK_CHAT_RESPONSE",
90+
KIRO_MOCK_CHAT_RESPONSE = "KIRO_MOCK_CHAT_RESPONSE",
8691

87-
/// Disable truecolor terminal support
92+
/// Disable truecolor output
8893
Q_DISABLE_TRUECOLOR = "Q_DISABLE_TRUECOLOR",
94+
KIRO_DISABLE_TRUECOLOR = "KIRO_DISABLE_TRUECOLOR",
95+
96+
/// Log to stdout
97+
Q_LOG_STDOUT = "Q_LOG_STDOUT",
98+
KIRO_LOG_STDOUT = "KIRO_LOG_STDOUT",
99+
100+
/// Disable telemetry
101+
Q_DISABLE_TELEMETRY = "Q_DISABLE_TELEMETRY",
102+
KIRO_DISABLE_TELEMETRY = "KIRO_DISABLE_TELEMETRY",
89103

90-
/// Fake remote environment for testing
104+
/// Fake remote environment
91105
Q_FAKE_IS_REMOTE = "Q_FAKE_IS_REMOTE",
106+
KIRO_FAKE_IS_REMOTE = "KIRO_FAKE_IS_REMOTE",
92107

93-
/// Codespaces environment indicator
108+
/// Codespaces environment
94109
Q_CODESPACES = "Q_CODESPACES",
110+
KIRO_CODESPACES = "KIRO_CODESPACES",
95111

96-
/// CI environment indicator
112+
/// CI environment
97113
Q_CI = "Q_CI",
114+
KIRO_CI = "KIRO_CI",
98115

99116
/// Telemetry client ID
100117
Q_TELEMETRY_CLIENT_ID = "Q_TELEMETRY_CLIENT_ID",
118+
KIRO_TELEMETRY_CLIENT_ID = "KIRO_TELEMETRY_CLIENT_ID",
119+
120+
/// Chat shell for Unix systems
121+
KIRO_CHAT_SHELL = "KIRO_CHAT_SHELL",
122+
123+
/// Flag for running integration tests
124+
CLI_IS_INTEG_TEST = "Q_CLI_IS_INTEG_TEST",
101125

102126
/// Amazon Q SigV4 authentication
103127
AMAZON_Q_SIGV4 = "AMAZON_Q_SIGV4",

crates/chat-cli/src/util/env_var.rs

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,62 @@
11
use crate::os::Env;
22
use crate::util::consts::env_var::*;
33

4-
/// Get log level from environment
4+
/// Get log level from environment with fallback
55
pub fn get_log_level(env: &Env) -> Result<String, std::env::VarError> {
6-
env.get(Q_LOG_LEVEL)
6+
env.get(KIRO_LOG_LEVEL).or_else(|_| env.get(Q_LOG_LEVEL))
77
}
88

99
/// Get chat shell with default fallback
1010
#[cfg(unix)]
1111
pub fn get_chat_shell() -> String {
12-
Env::new()
13-
.get(AMAZON_Q_CHAT_SHELL)
12+
let env = Env::new();
13+
env.get(KIRO_CHAT_SHELL)
14+
.or_else(|_| env.get(AMAZON_Q_CHAT_SHELL))
1415
.unwrap_or_else(|_| "bash".to_string())
1516
}
1617

1718
/// Check if stdout logging is enabled
1819
pub fn is_log_stdout_enabled() -> bool {
19-
Env::new().get_os(Q_LOG_STDOUT).is_some()
20+
let env = Env::new();
21+
env.get_os(KIRO_LOG_STDOUT).is_some() || env.get_os(Q_LOG_STDOUT).is_some()
2022
}
2123

2224
/// Check if telemetry is disabled
2325
pub fn is_telemetry_disabled() -> bool {
24-
Env::new().get_os(Q_DISABLE_TELEMETRY).is_some()
26+
let env = Env::new();
27+
env.get_os(KIRO_DISABLE_TELEMETRY).is_some() || env.get_os(Q_DISABLE_TELEMETRY).is_some()
2528
}
2629

2730
/// Get mock chat response for testing
2831
pub fn get_mock_chat_response(env: &Env) -> Option<String> {
29-
env.get(Q_MOCK_CHAT_RESPONSE).ok()
32+
env.get(KIRO_MOCK_CHAT_RESPONSE)
33+
.or_else(|_| env.get(Q_MOCK_CHAT_RESPONSE))
34+
.ok()
3035
}
3136

3237
/// Check if truecolor is disabled
3338
pub fn is_truecolor_disabled() -> bool {
34-
Env::new().get_os(Q_DISABLE_TRUECOLOR).is_some_and(|s| !s.is_empty())
39+
let env = Env::new();
40+
env.get_os(KIRO_DISABLE_TRUECOLOR).is_some_and(|s| !s.is_empty())
41+
|| env.get_os(Q_DISABLE_TRUECOLOR).is_some_and(|s| !s.is_empty())
3542
}
3643

3744
/// Check if remote environment is faked
3845
pub fn is_remote_fake() -> bool {
39-
Env::new().get_os(Q_FAKE_IS_REMOTE).is_some()
46+
let env = Env::new();
47+
env.get_os(KIRO_FAKE_IS_REMOTE).is_some() || env.get_os(Q_FAKE_IS_REMOTE).is_some()
4048
}
4149

4250
/// Check if running in Codespaces
4351
pub fn in_codespaces() -> bool {
4452
let env = Env::new();
45-
env.get_os(CODESPACES).is_some() || env.get_os(Q_CODESPACES).is_some()
53+
env.get_os(CODESPACES).is_some() || env.get_os(KIRO_CODESPACES).is_some() || env.get_os(Q_CODESPACES).is_some()
4654
}
4755

4856
/// Check if running in CI
4957
pub fn in_ci() -> bool {
5058
let env = Env::new();
51-
env.get_os(CI).is_some() || env.get_os(Q_CI).is_some()
59+
env.get_os(CI).is_some() || env.get_os(KIRO_CI).is_some() || env.get_os(Q_CI).is_some()
5260
}
5361

5462
pub fn is_integ_test() -> bool {
@@ -57,7 +65,10 @@ pub fn is_integ_test() -> bool {
5765

5866
/// Get CLI client application
5967
pub fn get_cli_client_application() -> Option<String> {
60-
Env::new().get(Q_CLI_CLIENT_APPLICATION).ok()
68+
let env = Env::new();
69+
env.get(KIRO_CLI_CLIENT_APPLICATION)
70+
.or_else(|_| env.get(Q_CLI_CLIENT_APPLICATION))
71+
.ok()
6172
}
6273

6374
/// Get editor with default fallback
@@ -92,5 +103,6 @@ pub fn get_all_env_vars() -> std::env::Vars {
92103

93104
/// Get telemetry client ID
94105
pub fn get_telemetry_client_id(env: &Env) -> Result<String, std::env::VarError> {
95-
env.get(Q_TELEMETRY_CLIENT_ID)
106+
env.get(KIRO_TELEMETRY_CLIENT_ID)
107+
.or_else(|_| env.get(Q_TELEMETRY_CLIENT_ID))
96108
}

0 commit comments

Comments
 (0)