Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .config/nextest.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
[profile.default]
test-threads = 8

[profile.ci]
fail-fast = false
retries = 1
flaky-result = "fail"
test-threads = 8
slow-timeout = { period = "10s", terminate-after = 36 }
final-status-level = "slow"

Expand All @@ -22,6 +26,7 @@ filter = '(binary(=daemon_suite) & test(/^git_watch_test::(fifty_commit_rebase_n
slow-timeout = { period = "60s", terminate-after = 10 }

[test-groups]
cli-subprocess = { max-threads = 1 }
windows-tracedecay-init = { max-threads = 32 }
windows-init-heavy = { max-threads = 32 }
windows-profile-storage = { max-threads = 32 }
Expand All @@ -47,6 +52,16 @@ threads-required = "num-cpus"
filter = 'binary(=core_cli_suite) & test(/^tool_daemon_test::(daemon_socket_is_owner_only|daemon_sigterm_exits_while_project_client_is_connected)$/)'
threads-required = "num-cpus"

[[profile.ci.overrides]]
filter = '(binary(=core_cli_suite) & test(/^cli_non_interactive_test::/)) | (binary(=mcp_suite) & test(/^(mcp_cli_serve_test|serve_degraded_mode_test|serve_template_path_test)::/))'
test-group = 'cli-subprocess'
threads-required = "num-cpus"

[[profile.default.overrides]]
filter = '(binary(=core_cli_suite) & test(/^cli_non_interactive_test::/)) | (binary(=mcp_suite) & test(/^(mcp_cli_serve_test|serve_degraded_mode_test|serve_template_path_test)::/))'
test-group = 'cli-subprocess'
threads-required = "num-cpus"

[[profile.ci.overrides]]
filter = 'binary(=agent_suite) | (binary(=storage_suite) & test(/^branch_db_safety_test::/)) | (binary(=core_cli_suite) & test(/^cli_non_interactive_test::/)) | (binary(=mcp_suite) & test(/^(mcp_cli_serve_test|serve_template_path_test)::/))'
platform = { host = 'cfg(windows)' }
Expand Down
10 changes: 10 additions & 0 deletions src/agents/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ pub struct ManagedSkillExportReport {
pub error: Option<String>,
}

pub(crate) fn uses_default_user_profile(home: &Path, profile_root: &Path) -> bool {
profile_root == home.join(".tracedecay")
}

/// Re-runs the managed-skill overlay/prompt-index export for every agent
/// integration that already has tracedecay installed under `home`, so a
/// lifecycle change (approve/disable/archive/restore) deploys without
Expand All @@ -106,6 +110,9 @@ pub fn export_managed_skills_to_agents(
home: &Path,
profile_root: &Path,
) -> Vec<ManagedSkillExportReport> {
if !uses_default_user_profile(home, profile_root) {
return Vec::new();
}
let mut reports = Vec::new();
for ag in all_integrations() {
match ag.export_managed_skills(home, profile_root) {
Expand Down Expand Up @@ -136,6 +143,9 @@ pub fn export_managed_skills_to_agent_hosts(
project_root: &Path,
profile_root: &Path,
) -> Vec<ManagedSkillExportReport> {
if !uses_default_user_profile(home, profile_root) {
return Vec::new();
}
let mut reports = Vec::new();
for ag in all_integrations() {
let mut exports = Vec::new();
Expand Down
3 changes: 3 additions & 0 deletions src/automation/skill_materialization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1723,6 +1723,9 @@ pub fn reconcile_detected_scopes(
home: &Path,
project_root: &Path,
) -> (Vec<ScopeReconcileResult>, Vec<String>) {
if !crate::agents::uses_default_user_profile(home, profile_root) {
return (Vec::new(), Vec::new());
}
let mut results = Vec::new();
let mut errors = Vec::new();
let skills = match load_active_managed_skills(profile_root) {
Expand Down
17 changes: 8 additions & 9 deletions src/automation/skill_writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,7 @@ fn refresh_managed_skill_exports_after_auto_enable(profile_root: &Path) -> Value
let Some(home) = crate::agents::home_dir() else {
return json!({"status": "skipped", "reason": "home_unavailable"});
};
if !should_refresh_managed_skill_exports(profile_root, &home) {
if !crate::agents::uses_default_user_profile(&home, profile_root) {
return json!({"status": "skipped", "reason": "non_default_profile_root"});
}
let start = std::env::current_dir().unwrap_or_else(|_| home.clone());
Expand Down Expand Up @@ -582,10 +582,6 @@ fn refresh_managed_skill_exports_after_auto_enable(profile_root: &Path) -> Value
})
}

fn should_refresh_managed_skill_exports(profile_root: &Path, home: &Path) -> bool {
profile_root == home.join(".tracedecay")
}

fn accepted_skill_approval_status(
action: SkillProposalAction,
auto_enable_skills: bool,
Expand Down Expand Up @@ -688,6 +684,9 @@ fn skill_update_from_proposal(
let existing = existing_skills
.get(&id)
.ok_or_else(|| format!("managed skill id '{id}' does not exist"))?;
if existing.metadata.provenance.source != ManagedSkillSource::AutomationRun {
return Err(format!("managed skill '{id}' is not automation-owned"));
}
let base_checksum = required_proposal_string(object.get("base_checksum"), "base_checksum")?;
if base_checksum != existing.metadata.checksum {
return Err(format!(
Expand Down Expand Up @@ -870,13 +869,13 @@ mod tests {
#[test]
fn managed_skill_exports_only_refresh_for_the_user_profile() {
let home = Path::new("/home/test-user");
assert!(should_refresh_managed_skill_exports(
Path::new("/home/test-user/.tracedecay"),
assert!(crate::agents::uses_default_user_profile(
home,
Path::new("/home/test-user/.tracedecay"),
));
assert!(!should_refresh_managed_skill_exports(
Path::new("/tmp/tracedecay-test-profile"),
assert!(!crate::agents::uses_default_user_profile(
home,
Path::new("/tmp/tracedecay-test-profile"),
));
}

Expand Down
21 changes: 16 additions & 5 deletions src/automation/skill_writer/consolidation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,8 @@ fn consolidation_guard<'a>(
"managed skill '{id}' is pinned and exempt from consolidation"
));
}
if skill.metadata.provenance.source == ManagedSkillSource::UserDraft {
return Err(format!(
"managed skill '{id}' is user-authored and exempt from consolidation"
));
if skill.metadata.provenance.source != ManagedSkillSource::AutomationRun {
return Err(format!("managed skill '{id}' is not automation-owned"));
}
if skill.metadata.state == ManagedSkillState::Archived {
return Err(format!("managed skill '{id}' is already archived"));
Expand Down Expand Up @@ -302,6 +300,7 @@ mod tests {
fixture_skill("workflow-b", ManagedSkillSource::AutomationRun, false),
fixture_skill("pinned-skill", ManagedSkillSource::AutomationRun, true),
fixture_skill("user-skill", ManagedSkillSource::UserDraft, false),
fixture_skill("imported-skill", ManagedSkillSource::Import, false),
archived,
]
.into_iter()
Expand Down Expand Up @@ -392,7 +391,19 @@ mod tests {
}),
&skills,
),
"managed skill 'user-skill' is user-authored and exempt from consolidation",
"managed skill 'user-skill' is not automation-owned",
);
assert_err_eq(
skill_archive_from_proposal(
&json!({
"action": "archive",
"id": "imported-skill",
"base_checksum": checksum(&skills, "imported-skill"),
"reason": "x"
}),
&skills,
),
"managed skill 'imported-skill' is not automation-owned",
);
assert_err_eq(
skill_archive_from_proposal(
Expand Down
Loading
Loading