lifeloop/host_assets/
status.rs1use super::merge::{
4 codex_hooks_feature_is_enabled, merge_claude_settings_text_with_profile,
5 merge_codex_hooks_text_with_profile,
6};
7use super::model::{AssetStatus, RenderedAsset};
8use super::profiles::{CCD_COMPAT_PROFILE, LifecycleProfile};
9use super::{CLAUDE_TARGET_SETTINGS, CODEX_TARGET_CONFIG, CODEX_TARGET_HOOKS};
10
11pub fn byte_equal_asset_status(
23 asset: &RenderedAsset,
24 existing_content: Option<&str>,
25 existing_mode: Option<u32>,
26) -> AssetStatus {
27 let Some(content) = existing_content else {
28 return AssetStatus::Missing;
29 };
30 if content != asset.contents {
31 return AssetStatus::Drifted;
32 }
33 if let Some(expected) = asset.mode {
34 match existing_mode {
35 Some(actual) if actual == expected => {}
36 _ => return AssetStatus::Drifted,
37 }
38 }
39 AssetStatus::Present
40}
41
42pub fn claude_settings_status(existing: Option<&str>) -> AssetStatus {
47 claude_settings_status_with_profile(existing, &CCD_COMPAT_PROFILE)
48}
49
50pub fn claude_settings_status_with_profile(
53 existing: Option<&str>,
54 profile: &LifecycleProfile,
55) -> AssetStatus {
56 let Some(content) = existing else {
57 return AssetStatus::Missing;
58 };
59 match merge_claude_settings_text_with_profile(Some(content), false, profile) {
60 Ok(Some(merged)) if merged.rendered == content => AssetStatus::Present,
61 Ok(_) | Err(_) => AssetStatus::Drifted,
62 }
63}
64
65pub fn codex_hooks_status(existing: Option<&str>) -> AssetStatus {
68 codex_hooks_status_with_profile(existing, &CCD_COMPAT_PROFILE)
69}
70
71pub fn codex_hooks_status_with_profile(
74 existing: Option<&str>,
75 profile: &LifecycleProfile,
76) -> AssetStatus {
77 let Some(content) = existing else {
78 return AssetStatus::Missing;
79 };
80 match merge_codex_hooks_text_with_profile(Some(content), false, profile) {
81 Ok(Some(merged)) if merged.rendered == content => AssetStatus::Present,
82 Ok(_) | Err(_) => AssetStatus::Drifted,
83 }
84}
85
86pub fn codex_config_status(existing: Option<&str>) -> AssetStatus {
90 let Some(content) = existing else {
91 return AssetStatus::Missing;
92 };
93 match content.parse::<toml::Value>() {
94 Ok(parsed) if codex_hooks_feature_is_enabled(&parsed) => AssetStatus::Present,
95 Ok(_) | Err(_) => AssetStatus::Drifted,
96 }
97}
98
99pub fn asset_status(
103 asset: &RenderedAsset,
104 existing_content: Option<&str>,
105 existing_mode: Option<u32>,
106) -> AssetStatus {
107 match asset.relative_path {
108 CLAUDE_TARGET_SETTINGS => claude_settings_status(existing_content),
109 CODEX_TARGET_CONFIG => codex_config_status(existing_content),
110 CODEX_TARGET_HOOKS => codex_hooks_status(existing_content),
111 _ => byte_equal_asset_status(asset, existing_content, existing_mode),
112 }
113}
114
115pub fn aggregate_status<I: IntoIterator<Item = AssetStatus>>(statuses: I) -> AssetStatus {
119 let mut saw_missing = false;
120 let mut saw_drift = false;
121 let mut saw_any = false;
122 for s in statuses {
123 saw_any = true;
124 match s {
125 AssetStatus::Drifted => saw_drift = true,
126 AssetStatus::Missing => saw_missing = true,
127 AssetStatus::InvalidMode => return AssetStatus::InvalidMode,
128 AssetStatus::Present | AssetStatus::NotApplicable => {}
129 }
130 }
131 if !saw_any {
132 return AssetStatus::NotApplicable;
133 }
134 if saw_drift {
135 AssetStatus::Drifted
136 } else if saw_missing {
137 AssetStatus::Missing
138 } else {
139 AssetStatus::Present
140 }
141}