closing rule: acts without a commit close Recorded, Fixed names the merge or squash commit, the reminder bounds Recorded; each flush names its own reason
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
e47eec7e85
commit
122a968f30
4 changed files with 35 additions and 13 deletions
|
|
@ -214,17 +214,17 @@ The run claims `{id}`, which the mind does not hold: its task is unknown.")),
|
||||||
Ok(task)
|
Ok(task)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// What the child is told once, when its session must end: the ceiling, the cap or the cache.
|
/// What the child is told once, when its session must end, after the reason the flush names for itself.
|
||||||
pub const FLUSH_MESSAGE: &str = "The context ceiling is reached. Admit to the mind what it lacks, close your run, and end.";
|
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.
|
/// The flush as written: the one message, and what she does with the briefs she holds.
|
||||||
fn flush_text(why: Why) -> String {
|
fn flush_text(why: Why) -> String {
|
||||||
let cause = match why {
|
let cause = match why {
|
||||||
Why::Ceiling => "",
|
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::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::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.
|
/// The session id of a run: UUIDv5 over the run's id. Derived, never stored.
|
||||||
|
|
|
||||||
|
|
@ -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
|
/// 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.
|
/// 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.
|
/// The reminder under each told brief: the closing rule in short, for this brief's id.
|
||||||
pub fn resolving_sentence(id: &str) -> String {
|
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)]
|
#[cfg(test)]
|
||||||
|
|
|
||||||
|
|
@ -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}");
|
assert!(text.contains("eureka-body:follow_up:b1") && text.contains("Wire the thing."), "{text}");
|
||||||
let reminder = resolving_sentence("eureka-body:follow_up:b1");
|
let reminder = resolving_sentence("eureka-body:follow_up:b1");
|
||||||
assert!(text.contains(&reminder), "{text}");
|
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!(reminder.contains(word), "{word}: {reminder}");
|
||||||
}
|
}
|
||||||
assert!(!text.contains(CLOSING_RULE), "the rule is told once, at start: {text}");
|
assert!(!text.contains(CLOSING_RULE), "the rule is told once, at start: {text}");
|
||||||
|
|
|
||||||
|
|
@ -237,7 +237,7 @@ fn go(steps: Vec<Step>) -> Ran {
|
||||||
type Seed = Box<dyn Fn(&TestMind)>;
|
type Seed = Box<dyn Fn(&TestMind)>;
|
||||||
|
|
||||||
fn flushes(ran: &Ran) -> usize {
|
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 -----------------------------------------------------
|
// ---- 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() {
|
fn the_flush_keeps_a_brief_whose_change_has_not_landed_open() {
|
||||||
for why in [Why::Ceiling, Why::Rotation, Why::Idle] {
|
for why in [Why::Ceiling, Why::Rotation, Why::Idle] {
|
||||||
let text = flush_text(why);
|
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("stays open") && text.contains("default branch") && text.contains("closing rule"), "{text}");
|
||||||
assert!(!text.contains("Recorded, your answer"), "{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]
|
#[test]
|
||||||
fn a_brief_open_at_start_is_in_the_start_message_with_how_to_resolve_it_and_is_not_told_again() {
|
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();
|
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 ran = mind_run(&mind, steps);
|
||||||
let written = ran.written();
|
let written = ran.written();
|
||||||
assert_eq!(written.len(), 2, "{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_eq!(ran.spawns.len(), 1);
|
||||||
assert!(ran.spawns[0].iter().all(|arg| arg != "--settings"), "no hooks on argv: {:?}", ran.spawns[0]);
|
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 entry: Value = serde_json::from_str(entry).unwrap();
|
||||||
let at = crate::testkit::at((entry["t"].as_f64().unwrap() * 1000.0) as i64);
|
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) {
|
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);
|
let (next, action) = decide(state, fact, at);
|
||||||
state = next;
|
state = next;
|
||||||
trace.push((label.to_string(), action));
|
trace.push((label.to_string(), action));
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue