feat(plan): display rulespec.yaml and envelope.yaml in plan_read/plan_write output

- Add format_envelope_markdown() function in invariants.rs for rich markdown
  formatting of ActionEnvelope facts
- Add format_yaml_value_markdown() helper for recursive YAML value display
- Update execute_plan_read() to append rulespec and envelope sections
- Update execute_plan_write() to append envelope section alongside rulespec
- Add 3 tests for format_envelope_markdown (empty, with facts, null values)

When plan_read or plan_write is called, the output now includes:
- Plan YAML (as before)
- Rulespec section (if rulespec.yaml exists) with invariants grouped by source
- Envelope section (if envelope.yaml exists) with facts in readable format

Missing files show placeholder text rather than errors.
This commit is contained in:
Dhanji R. Prasanna
2026-02-05 19:08:55 +11:00
parent bc5c1bdf61
commit 06d75f613c
2 changed files with 147 additions and 7 deletions

View File

@@ -20,7 +20,7 @@ use crate::ToolCall;
use super::executor::ToolContext;
use super::invariants::{format_rulespec_markdown, get_envelope_path, get_rulespec_path, read_rulespec};
use super::invariants::{format_envelope_markdown, format_rulespec_markdown, get_envelope_path, get_rulespec_path, read_envelope, read_rulespec};
// ============================================================================
// Plan Schema
@@ -798,11 +798,27 @@ pub async fn execute_plan_read<W: UiWriter>(
Some(plan) => {
let yaml = serde_yaml::to_string(&plan)?;
ctx.ui_writer.print_plan_compact(Some(&yaml), Some(&plan_path_str), false);
Ok(format!(
// Build output with plan
let mut output = format!(
"📋 {}\n\n```yaml\n{}```",
plan.status_summary(),
yaml
))
);
// Append rulespec if present
match read_rulespec(session_id) {
Ok(Some(rulespec)) => output.push_str(&format_rulespec_markdown(&rulespec)),
_ => output.push_str("\n\n_No rulespec generated._\n"),
}
// Append envelope if present
match read_envelope(session_id) {
Ok(Some(envelope)) => output.push_str(&format_envelope_markdown(&envelope)),
_ => output.push_str("\n_No envelope generated._\n"),
}
Ok(output)
}
None => {
ctx.ui_writer.print_plan_compact(None, None, false);
@@ -885,22 +901,31 @@ pub async fn execute_plan_write<W: UiWriter>(
Err(_) => "\n_No rulespec generated._\n".to_string(),
};
// Read and format envelope if it exists
let envelope_section = match read_envelope(session_id) {
Ok(Some(envelope)) => format_envelope_markdown(&envelope),
Ok(None) => "\n_No envelope generated._\n".to_string(),
Err(_) => "\n_No envelope generated._\n".to_string(),
};
// Check if plan is now complete and trigger verification
if plan.is_complete() && plan.is_approved() {
let verification = plan_verify(&plan, ctx.working_dir);
let verification_output = format_verification_results(&verification, ctx.session_id);
return Ok(format!(
"✅ Plan updated: {}\n{}\n{}",
"✅ Plan updated: {}\n{}\n{}\n{}",
plan.status_summary(),
verification_output,
rulespec_section
rulespec_section,
envelope_section
));
}
Ok(format!(
"✅ Plan updated: {}\n{}",
"✅ Plan updated: {}\n{}\n{}",
plan.status_summary(),
rulespec_section
rulespec_section,
envelope_section
))
}