feat(project): enrich repo constitution (invariants, branch policy, escalation)

Per the layered-authority clarification (base myth → global Constitution → repo
constitution = local law → task packet → runtime policy), extend
.codewhale/constitution.json beyond authority+verification with optional:

- protected_invariants — repo invariants the agent must not break
- branch_policy — branch/release policy in effect
- escalate_when — conditions to stop and escalate to the user

All optional; rendered as concise model-facing prose. The global Brother Whale
identity anchor and Constitution in prompts/base.md are unchanged (verified
untouched on this branch). Dogfood constitution.json filled with CodeWhale's
real invariants (prefix-cache byte-stability, transcript replay, stable Rust,
cli/tui parity), branch policy (codex/v0.8.53), and escalation rules. Docs note
the layered hierarchy.

cargo test -p codewhale-tui --bins → 3946 passed; clippy clean.
This commit is contained in:
Hunter Bown
2026-06-03 12:16:06 -07:00
parent 9d9616e898
commit fc8ad7b3a8
3 changed files with 74 additions and 8 deletions
+42 -4
View File
@@ -194,6 +194,15 @@ struct RepoConstitution {
/// (highest authority first).
#[serde(default)]
authority: Option<Vec<String>>,
/// Repo invariants the agent must not break.
#[serde(default)]
protected_invariants: Option<Vec<String>>,
/// Branch / release policy in effect (e.g. "PRs target codex/v0.8.53").
#[serde(default)]
branch_policy: Option<String>,
/// Conditions under which the agent should stop and escalate to the user.
#[serde(default)]
escalate_when: Option<Vec<String>>,
#[serde(default)]
verification_policy: Option<VerificationPolicy>,
}
@@ -209,7 +218,14 @@ impl RepoConstitution {
/// True when the file carried no usable policy (so we can skip emitting an
/// empty block).
fn is_empty(&self) -> bool {
self.authority.as_ref().is_none_or(Vec::is_empty)
let list_empty = |l: &Option<Vec<String>>| l.as_ref().is_none_or(Vec::is_empty);
list_empty(&self.authority)
&& list_empty(&self.protected_invariants)
&& list_empty(&self.escalate_when)
&& self
.branch_policy
.as_ref()
.is_none_or(|s| s.trim().is_empty())
&& self
.verification_policy
.as_ref()
@@ -217,7 +233,8 @@ impl RepoConstitution {
.is_none_or(Vec::is_empty)
}
/// Render a model-facing authority block.
/// Render a model-facing authority block (concise prose, per the layered
/// model: base myth → global constitution → repo constitution = local law).
fn render_block(&self, source: &Path) -> String {
let mut body = String::new();
if let Some(authority) = self.authority.as_ref().filter(|a| !a.is_empty()) {
@@ -228,6 +245,15 @@ impl RepoConstitution {
body.push_str(&format!("{}. {item}\n", idx + 1));
}
}
if let Some(invariants) = self.protected_invariants.as_ref().filter(|i| !i.is_empty()) {
body.push_str("\nProtected invariants — do not break:\n");
for item in invariants {
body.push_str(&format!("- {item}\n"));
}
}
if let Some(policy) = self.branch_policy.as_ref().filter(|s| !s.trim().is_empty()) {
body.push_str(&format!("\nBranch / release policy: {}\n", policy.trim()));
}
if let Some(steps) = self
.verification_policy
.as_ref()
@@ -239,8 +265,14 @@ impl RepoConstitution {
body.push_str(&format!("- {step}\n"));
}
}
if let Some(conditions) = self.escalate_when.as_ref().filter(|c| !c.is_empty()) {
body.push_str("\nStop and escalate to the user when:\n");
for item in conditions {
body.push_str(&format!("- {item}\n"));
}
}
format!(
"<codewhale_repo_constitution source=\"{}\">\nCodeWhale-specific repo authority policy (takes precedence over a legacy WHALE.md).\n\n{}</codewhale_repo_constitution>",
"<codewhale_repo_constitution source=\"{}\">\nCodeWhale-specific repo authority policy (local law: subordinate to the global Constitution and the current user request, but above memory and old handoffs; takes precedence over a legacy WHALE.md).\n\n{}</codewhale_repo_constitution>",
source.display(),
body.trim_end()
)
@@ -1182,7 +1214,10 @@ mod tests {
r#"{
"schema_version": 1,
"authority": ["current user request", "live code and tests", "AGENTS.md"],
"verification_policy": { "before_claiming_done": ["run focused tests"] }
"protected_invariants": ["keep the tool-catalog head byte-stable"],
"branch_policy": "PRs target codex/v0.8.53, not main",
"verification_policy": { "before_claiming_done": ["run focused tests"] },
"escalate_when": ["a destructive action was not authorized"]
}"#,
)
.expect("write constitution");
@@ -1195,6 +1230,9 @@ mod tests {
assert!(block.contains("<codewhale_repo_constitution"));
assert!(block.contains("current user request"));
assert!(block.contains("run focused tests"));
assert!(block.contains("keep the tool-catalog head byte-stable"));
assert!(block.contains("PRs target codex/v0.8.53"));
assert!(block.contains("a destructive action was not authorized"));
assert!(block.contains("takes precedence over a legacy WHALE.md"));
// It also surfaces through the system block.
assert!(