Skip to content
4 changes: 3 additions & 1 deletion rust/crates/api/src/providers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,9 @@ pub fn detect_provider_kind(model: &str) -> ProviderKind {
pub const fn model_family_identity_for_kind(kind: ProviderKind) -> runtime::ModelFamilyIdentity {
match kind {
ProviderKind::Anthropic => runtime::ModelFamilyIdentity::Claude,
ProviderKind::Xai | ProviderKind::OpenAi => runtime::ModelFamilyIdentity::Generic,
ProviderKind::Xai | ProviderKind::OpenAi | ProviderKind::Ollama => {
runtime::ModelFamilyIdentity::Generic
}
}
}

Expand Down
5 changes: 4 additions & 1 deletion rust/crates/runtime/src/session_control.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ impl SessionStore {
/// The on-disk layout becomes `<cwd>/.hackcode/sessions/<workspace_hash>/`.
pub fn from_cwd(cwd: impl AsRef<Path>) -> Result<Self, SessionControlError> {
let cwd = cwd.as_ref();
let sessions_root = cwd
// #151: canonicalize cwd for consistent fingerprinting across
// equivalent path representations.
let canonical_cwd = fs::canonicalize(cwd).unwrap_or_else(|_| cwd.to_path_buf());
let sessions_root = canonical_cwd
.join(".hackcode")
.join("sessions")
.join(workspace_fingerprint(&canonical_cwd));
Expand Down
102 changes: 100 additions & 2 deletions rust/crates/rusty-claude-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ error: {message}
Run `hackcode --help` for usage."
);
}
}
std::process::exit(1);
}
}
Expand Down Expand Up @@ -391,7 +392,10 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
allow_broad_cwd,
)?;
}
CliAction::HelpTopic(topic) => print_help_topic(topic),
CliAction::HelpTopic {
topic,
output_format,
} => print_help_topic(topic, output_format)?,
CliAction::Help { output_format } => print_help(output_format)?,
CliAction::Setup => setup::run_setup()?,
CliAction::Scan => {
Expand Down Expand Up @@ -502,7 +506,10 @@ enum CliAction {
reasoning_effort: Option<String>,
allow_broad_cwd: bool,
},
HelpTopic(LocalHelpTopic),
HelpTopic {
topic: LocalHelpTopic,
output_format: CliOutputFormat,
},
Setup,
Scan,
Update,
Expand Down Expand Up @@ -6131,6 +6138,20 @@ fn collect_sessions_from_dir(
if !directory.exists() {
return Ok(());
}
// #148: classify the workspace lifecycle once (saved-only / idle-shell /
// running-process, plus dirty-worktree and abandoned flags) and share it
// across the sessions in this store — they all belong to the same
// workspace-fingerprinted directory.
let lifecycle = env::current_dir()
.map(|cwd| classify_session_lifecycle_for(&cwd))
.unwrap_or(SessionLifecycleSummary {
kind: SessionLifecycleKind::SavedOnly,
pane_id: None,
pane_command: None,
pane_path: None,
workspace_dirty: false,
abandoned: false,
});
for entry in fs::read_dir(directory)? {
let entry = entry?;
let path = entry.path();
Expand Down Expand Up @@ -6180,6 +6201,7 @@ fn collect_sessions_from_dir(
message_count,
parent_session_id,
branch_name,
lifecycle: lifecycle.clone(),
});
}
Ok(())
Expand Down Expand Up @@ -6426,6 +6448,82 @@ fn print_status_snapshot(
Ok(())
}

/// #148: where the resolved model string originated from.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ModelSource {
Flag,
Env,
Config,
Default,
}

impl ModelSource {
fn as_str(self) -> &'static str {
match self {
ModelSource::Flag => "flag",
ModelSource::Env => "env",
ModelSource::Config => "config",
ModelSource::Default => "default",
}
}
}

/// #148: provenance of the model selection — the resolved (alias-expanded)
/// name, the raw user/env input before resolution, and which source won.
#[derive(Debug, Clone)]
struct ModelProvenance {
resolved: String,
raw: Option<String>,
source: ModelSource,
}

impl ModelProvenance {
/// Probe the model-selection env vars; attribute to env when one is set,
/// otherwise treat the resolved model as a built-in default.
fn from_env_or_config_or_default(model: &str) -> Self {
for key in ["HACKCODE_MODEL", "CLAW_MODEL", "ANTHROPIC_MODEL"] {
if let Ok(value) = std::env::var(key) {
let trimmed = value.trim();
if !trimmed.is_empty() {
return Self {
resolved: model.to_string(),
raw: Some(trimmed.to_string()),
source: ModelSource::Env,
};
}
}
}
Self {
resolved: model.to_string(),
raw: None,
source: ModelSource::Default,
}
}
}

/// #148: compute the stale-base commit state for `cwd`, honoring an optional
/// `--base-commit` flag value (falls back to the `.hackcode-base` file).
fn stale_base_state_for(cwd: &Path, flag_value: Option<&str>) -> BaseCommitState {
let source = resolve_expected_base(flag_value, cwd);
check_base_commit(cwd, source.as_ref())
}

/// #148: render a [`BaseCommitState`] as a JSON object with a stable `status`
/// token and a `fresh` boolean (false only when the worktree has diverged).
fn stale_base_json_value(state: &BaseCommitState) -> serde_json::Value {
match state {
BaseCommitState::Matches => json!({ "status": "matches", "fresh": true }),
BaseCommitState::NoExpectedBase => json!({ "status": "no_expected_base", "fresh": true }),
BaseCommitState::NotAGitRepo => json!({ "status": "not_a_git_repo", "fresh": true }),
BaseCommitState::Diverged { expected, actual } => json!({
"status": "diverged",
"fresh": false,
"expected": expected,
"actual": actual,
}),
}
}

fn status_json_value(
model: Option<&str>,
usage: StatusUsage,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ fn status_command_applies_model_and_permission_mode_flags() {
fs::create_dir_all(&temp_dir).expect("temp dir should exist");

// when
let output = Command::new(env!("CARGO_BIN_EXE_claw"))
let output = Command::new(env!("CARGO_BIN_EXE_hackcode"))
.current_dir(&temp_dir)
.args([
"--model",
Expand Down Expand Up @@ -45,7 +45,7 @@ fn resume_flag_loads_a_saved_session_and_dispatches_status() {
let session_path = write_session(&temp_dir, "resume-status");

// when
let output = Command::new(env!("CARGO_BIN_EXE_claw"))
let output = Command::new(env!("CARGO_BIN_EXE_hackcode"))
.current_dir(&temp_dir)
.args([
"--resume",
Expand Down Expand Up @@ -73,12 +73,12 @@ fn slash_command_names_match_known_commands_and_suggest_nearby_unknown_ones() {
fs::create_dir_all(&temp_dir).expect("temp dir should exist");

// when
let help_output = Command::new(env!("CARGO_BIN_EXE_claw"))
let help_output = Command::new(env!("CARGO_BIN_EXE_hackcode"))
.current_dir(&temp_dir)
.arg("/help")
.output()
.expect("claw should launch");
let unknown_output = Command::new(env!("CARGO_BIN_EXE_claw"))
let unknown_output = Command::new(env!("CARGO_BIN_EXE_hackcode"))
.current_dir(&temp_dir)
.arg("/zstats")
.output()
Expand Down Expand Up @@ -109,7 +109,7 @@ fn omc_namespaced_slash_commands_surface_a_targeted_compatibility_hint() {
let temp_dir = unique_temp_dir("slash-dispatch-omc");
fs::create_dir_all(&temp_dir).expect("temp dir should exist");

let output = Command::new(env!("CARGO_BIN_EXE_claw"))
let output = Command::new(env!("CARGO_BIN_EXE_hackcode"))
.current_dir(&temp_dir)
.arg("/oh-my-claudecode:hud")
.output()
Expand Down Expand Up @@ -259,7 +259,7 @@ fn local_subcommand_help_does_not_fall_through_to_runtime_or_provider_calls() {
}

fn command_in(cwd: &Path) -> Command {
let mut command = Command::new(env!("CARGO_BIN_EXE_claw"));
let mut command = Command::new(env!("CARGO_BIN_EXE_hackcode"));
command.current_dir(cwd);
command
}
Expand Down
2 changes: 1 addition & 1 deletion rust/crates/rusty-claude-cli/tests/compact_output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ fn run_claw(
base_url: &str,
args: &[&str],
) -> Output {
let mut command = Command::new(env!("CARGO_BIN_EXE_claw"));
let mut command = Command::new(env!("CARGO_BIN_EXE_hackcode"));
command
.current_dir(cwd)
.env_clear()
Expand Down
2 changes: 1 addition & 1 deletion rust/crates/rusty-claude-cli/tests/compact_repl_panic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ fn run_claw_repl(
home: &std::path::Path,
stdin: &str,
) -> Output {
let mut command = python_pty_command(env!("CARGO_BIN_EXE_claw"));
let mut command = python_pty_command(env!("CARGO_BIN_EXE_hackcode"));
let mut child = command
.current_dir(cwd)
.env_clear()
Expand Down
2 changes: 1 addition & 1 deletion rust/crates/rusty-claude-cli/tests/mock_parity_harness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ struct ScenarioReport {
}

fn run_case(case: ScenarioCase, workspace: &HarnessWorkspace, base_url: &str) -> ScenarioRun {
let mut command = Command::new(env!("CARGO_BIN_EXE_claw"));
let mut command = Command::new(env!("CARGO_BIN_EXE_hackcode"));
command
.current_dir(&workspace.root)
.env_clear()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,7 @@ fn assert_json_command_with_env(current_dir: &Path, args: &[&str], envs: &[(&str
}

fn run_claw(current_dir: &Path, args: &[&str], envs: &[(&str, &str)]) -> Output {
let mut command = Command::new(env!("CARGO_BIN_EXE_claw"));
let mut command = Command::new(env!("CARGO_BIN_EXE_hackcode"));
command.current_dir(current_dir).args(args);
for (key, value) in envs {
command.env(key, value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -543,7 +543,7 @@ fn workspace_session(root: &Path) -> Session {
}

fn run_claw_with_env(current_dir: &Path, args: &[&str], envs: &[(&str, &str)]) -> Output {
let mut command = Command::new(env!("CARGO_BIN_EXE_claw"));
let mut command = Command::new(env!("CARGO_BIN_EXE_hackcode"));
command.current_dir(current_dir).args(args);
for (key, value) in envs {
command.env(key, value);
Expand Down