diff --git a/crates/mind-body/src/self_run.rs b/crates/mind-body/src/self_run.rs index ecd560c..2c457ad 100644 --- a/crates/mind-body/src/self_run.rs +++ b/crates/mind-body/src/self_run.rs @@ -214,17 +214,17 @@ The run claims `{id}`, which the mind does not hold: its task is unknown.")), Ok(task) } -/// What the child is told once, when its session must end: the ceiling, the cap or the cache. -pub const FLUSH_MESSAGE: &str = "The context ceiling is reached. Admit to the mind what it lacks, close your run, and end."; +/// What the child is told once, when its session must end, after the reason the flush names for itself. +pub const FLUSH_MESSAGE: &str = "Admit to the mind what it lacks, close your run, and end."; /// The flush as written: the one message, and what she does with the briefs she holds. fn flush_text(why: Why) -> String { let cause = match why { - Why::Ceiling => "", - Why::Rotation => " (This process ends soon: the unit's time is nearly up. If your next brief comes within the hour, the next process continues this conversation.)", - Why::Idle => " (This session ends because nothing is waiting and its cache would lapse.)", + Why::Ceiling => "The context ceiling is reached.", + Why::Rotation => "This process ends soon: the unit's time is nearly up. If your next brief comes within the hour, the next process continues this conversation.", + Why::Idle => "This session ends because nothing is waiting and its cache would lapse.", }; - format!("{FLUSH_MESSAGE}{cause} Close each brief that is done by the closing rule; a brief whose change is not on its default branch stays open, or is superseded by your own follow-up when it waits on someone else, and the next session continues it.") + format!("{cause} {FLUSH_MESSAGE} Close each brief that is done by the closing rule; a brief whose change is not on its default branch stays open, or is superseded by your own follow-up when it waits on someone else, and the next session continues it.") } /// The session id of a run: UUIDv5 over the run's id. Derived, never stored. diff --git a/crates/mind-body/src/self_run/inbox.rs b/crates/mind-body/src/self_run/inbox.rs index 750c03b..ea56e75 100644 --- a/crates/mind-body/src/self_run/inbox.rs +++ b/crates/mind-body/src/self_run/inbox.rs @@ -123,11 +123,11 @@ pub fn render(briefs: &[Brief], plan: &str) -> String { /// How a brief closes: the one owner of what done means for her Self. The start message tells it /// once; every told brief and the flush point back to it. -pub(crate) const CLOSING_RULE: &str = "A brief is done when what it asks for exists. A brief that asks for an answer, a review or a read is done when you answer it: resolve it Recorded with the answer as rationale. A brief that asks for a change is done when the change is on its repo's default branch, and live where the brief asks for a deploy: resolve it Fixed with that commit. An open PR, a pushed branch or a local commit is not done, and the brief stays open. In a repo you steward the merge is yours and needs no one's leave: have your Soul check the work, then merge it, then resolve the brief (rulings steward-is-maintainer and merge-gate-own-soul). Land what you have open before you start more. When what is left waits on someone else (a review in a repo you do not steward, a ruling, a deploy you do not hold), admit a follow-up of your own whose item names the PR or branch and whom it waits on, resolve the brief Superseded by it, and say so in your reply. If you decline a brief, resolve it Withdrawn with why."; +pub(crate) const CLOSING_RULE: &str = "A brief is done when what it asks for exists. A brief that asks for an answer, a review or a read is done when you answer it: resolve it Recorded with the answer as rationale. A brief that asks for an act with no commit (admitting records, a forge setting, a post, an ops step) is done when the act is done: resolve it Recorded with what you did and where it can be seen. A brief that asks for a change is done when the change is on its repo's default branch, and live where the brief asks for a deploy: resolve it Fixed with the commit on the default branch that contains the change, the merge or squash commit and not the PR head. An open PR, a pushed branch or a local commit is not done, and the brief stays open. In a repo you steward the merge is yours and needs no one's leave: have your Soul check the work, then merge it, then resolve the brief (rulings steward-is-maintainer and merge-gate-own-soul). Land what you have open before you start more. When what is left waits on someone else (a review in a repo you do not steward, a ruling, a deploy you do not hold), admit a follow-up of your own whose item names the PR or branch and whom it waits on, resolve the brief Superseded by it, and say so in your reply. If you decline a brief, resolve it Withdrawn with why."; /// The reminder under each told brief: the closing rule in short, for this brief's id. pub fn resolving_sentence(id: &str) -> String { - format!("Close follow-up {id} by the closing rule: Recorded with your answer, Fixed by its commit on the default branch, Superseded by your own follow-up while it waits on someone else, or Withdrawn with why. An open PR is not done.") + format!("Close follow-up {id} by the closing rule: Recorded only if it asked just for an answer or for an act with no commit, with what you did; Fixed by the merge or squash commit on the default branch if it asked for a change; Superseded by your own follow-up while it waits on someone else; Withdrawn with why. An open PR, a pushed branch or a local commit is not done.") } #[cfg(test)] diff --git a/crates/mind-body/src/self_run/inbox/tests.rs b/crates/mind-body/src/self_run/inbox/tests.rs index 86e50a2..18e1bfb 100644 --- a/crates/mind-body/src/self_run/inbox/tests.rs +++ b/crates/mind-body/src/self_run/inbox/tests.rs @@ -106,7 +106,7 @@ fn a_tell_names_each_brief_the_plan_and_how_she_resolves_it() { assert!(text.contains("eureka-body:follow_up:b1") && text.contains("Wire the thing."), "{text}"); let reminder = resolving_sentence("eureka-body:follow_up:b1"); assert!(text.contains(&reminder), "{text}"); - for word in ["eureka-body:follow_up:b1", "Recorded", "Fixed", "default branch", "Superseded", "Withdrawn", "An open PR is not done"] { + for word in ["eureka-body:follow_up:b1", "Recorded", "Fixed", "default branch", "Superseded", "Withdrawn", "An open PR, a pushed branch or a local commit is not done"] { assert!(reminder.contains(word), "{word}: {reminder}"); } assert!(!text.contains(CLOSING_RULE), "the rule is told once, at start: {text}"); diff --git a/crates/mind-body/src/self_run/tests.rs b/crates/mind-body/src/self_run/tests.rs index d3e3c44..fd77512 100644 --- a/crates/mind-body/src/self_run/tests.rs +++ b/crates/mind-body/src/self_run/tests.rs @@ -237,7 +237,7 @@ fn go(steps: Vec) -> Ran { type Seed = Box; fn flushes(ran: &Ran) -> usize { - ran.written().iter().filter(|text| text.starts_with(FLUSH_MESSAGE)).count() + ran.written().iter().filter(|text| text.contains(FLUSH_MESSAGE)).count() } // ---- who is carried ----------------------------------------------------- @@ -1432,12 +1432,34 @@ fn the_start_message_carries_the_closing_rule_once_and_names_what_closes_a_chang fn the_flush_keeps_a_brief_whose_change_has_not_landed_open() { for why in [Why::Ceiling, Why::Rotation, Why::Idle] { let text = flush_text(why); - assert!(text.starts_with(FLUSH_MESSAGE), "{text}"); + assert!(text.contains(FLUSH_MESSAGE), "{text}"); + assert_eq!(text.contains("context ceiling"), why == Why::Ceiling, "each flush names its own reason: {text}"); assert!(text.contains("stays open") && text.contains("default branch") && text.contains("closing rule"), "{text}"); assert!(!text.contains("Recorded, your answer"), "{text}"); } } +#[test] +fn the_closing_rule_covers_an_act_without_a_commit_and_names_the_commit_fixed_takes() { + let rule = inbox::CLOSING_RULE; + for word in ["no commit", "Recorded with what you did and where it can be seen", "merge or squash commit", "not the PR head"] { + assert!(rule.contains(word), "{word}: {rule}"); + } + let reminder = inbox::resolving_sentence("x"); + for word in ["Recorded only if it asked just for an answer", "an act with no commit", "a pushed branch or a local commit is not done"] { + assert!(reminder.contains(word), "{word}: {reminder}"); + } +} + +#[test] +fn no_source_of_the_actuator_tells_her_a_brief_closes_by_recorded_with_her_answer() { + for source in [include_str!("../self_run.rs"), include_str!("inbox.rs")] { + for old in ["Recorded with your answer", "Recorded, your answer", "Resolve each brief you answered"] { + assert!(!source.contains(old), "{old}"); + } + } +} + #[test] fn a_brief_open_at_start_is_in_the_start_message_with_how_to_resolve_it_and_is_not_told_again() { let mind = live_mind(); @@ -1560,7 +1582,7 @@ fn no_stdin_line_is_written_after_the_flush_and_nothing_but_the_writer_writes() let ran = mind_run(&mind, steps); let written = ran.written(); assert_eq!(written.len(), 2, "{written:?}"); - assert!(written[1].starts_with(FLUSH_MESSAGE), "{}", written[1]); + assert!(written[1].contains(FLUSH_MESSAGE), "{}", written[1]); assert_eq!(ran.spawns.len(), 1); assert!(ran.spawns[0].iter().all(|arg| arg != "--settings"), "no hooks on argv: {:?}", ran.spawns[0]); } @@ -1588,7 +1610,7 @@ mod live_session { let entry: Value = serde_json::from_str(entry).unwrap(); let at = crate::testkit::at((entry["t"].as_f64().unwrap() * 1000.0) as i64); if let Some(text) = entry.get("want").and_then(Value::as_str) { - let (label, fact) = if text.starts_with(FLUSH_MESSAGE) { ("flush-asked", Fact::Tick) } else { ("want", Fact::Inbox { pending: true, other: false, other_first: false }) }; + let (label, fact) = if text.contains(FLUSH_MESSAGE) { ("flush-asked", Fact::Tick) } else { ("want", Fact::Inbox { pending: true, other: false, other_first: false }) }; let (next, action) = decide(state, fact, at); state = next; trace.push((label.to_string(), action));