Add or refresh tracedecay's global steering resource for default Kiro sessions. When the marker is present but the block content is stale (an older tracedecay version wrote it), the block is replaced in place: a marker-to-end-marker splice when the owned end marker exists, otherwise the generic marker-to-next-heading strip plus a fresh append.
(path: &Path)
| 532 | /// marker-to-end-marker splice when the owned end marker exists, otherwise |
| 533 | /// the generic marker-to-next-heading strip plus a fresh append. |
| 534 | fn install_steering_rules(path: &Path) -> Result<()> { |
| 535 | let existing = if path.exists() { |
| 536 | std::fs::read_to_string(path).unwrap_or_default() |
| 537 | } else { |
| 538 | String::new() |
| 539 | }; |
| 540 | let block = prompt_rules_text(); |
| 541 | if existing.contains(&block) { |
| 542 | eprintln!(" Kiro steering already contains tracedecay rules, skipping"); |
| 543 | return Ok(()); |
| 544 | } |
| 545 | if contains_prompt_marker(&existing) { |
| 546 | if let Some(range) = tracedecay_prompt_block_range(&existing) { |
| 547 | let mut new_contents = String::with_capacity(existing.len() + block.len()); |
| 548 | new_contents.push_str(&existing[..range.start]); |
| 549 | new_contents.push_str(&block); |
| 550 | new_contents.push_str(&existing[range.end..]); |
| 551 | std::fs::write(path, new_contents).map_err(|e| TraceDecayError::Config { |
| 552 | message: format!("failed to write {}: {e}", path.display()), |
| 553 | })?; |
| 554 | eprintln!( |
| 555 | "\x1b[32m✔\x1b[0m Refreshed tracedecay rules in {}", |
| 556 | path.display() |
| 557 | ); |
| 558 | return Ok(()); |
| 559 | } |
| 560 | // Marker present but no owned end marker: fall back to the heading-based |
| 561 | // strip the other hosts use (trying both the current and legacy |
| 562 | // heading), then append fresh rules. |
| 563 | let marker = if existing.contains(PROMPT_MARKER) { |
| 564 | PROMPT_MARKER |
| 565 | } else { |
| 566 | PROMPT_MARKER_LEGACY |
| 567 | }; |
| 568 | let stripped = |
| 569 | super::prompt_rules::strip_heading_block(&existing, marker).unwrap_or_default(); |
| 570 | return super::prompt_rules::write_refreshed(path, &stripped, &block); |
| 571 | } |
| 572 | if let Some(parent) = path.parent() { |
| 573 | std::fs::create_dir_all(parent).map_err(|e| TraceDecayError::Config { |
| 574 | message: format!("failed to create {}: {e}", parent.display()), |
| 575 | })?; |
| 576 | } |
| 577 | let mut f = std::fs::OpenOptions::new() |
| 578 | .create(true) |
| 579 | .append(true) |
| 580 | .open(path) |
| 581 | .map_err(|e| TraceDecayError::Config { |
| 582 | message: format!("failed to open {}: {e}", path.display()), |
| 583 | })?; |
| 584 | let separator = if existing.trim().is_empty() { |
| 585 | "" |
| 586 | } else { |
| 587 | "\n\n" |
| 588 | }; |
| 589 | writeln!(f, "{separator}{block}").map_err(|e| TraceDecayError::Config { |
| 590 | message: format!("failed to write {}: {e}", path.display()), |
| 591 | })?; |
no test coverage detected