chore: import upstream snapshot with attribution
FreeBSD Smoke / FreeBSD Smoke (x86_64) (push) Has been cancelled
CI / Quality Guardrails (push) Has been cancelled
CI / Build & Test (macos-latest) (push) Has been cancelled
CI / Build & Test (ubuntu-latest) (push) Has been cancelled
CI / Build & Test (windows-latest) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / PowerShell Syntax (push) Has been cancelled
CI / Windows Cross-Target Check (Linux) (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:10:34 +08:00
commit a789495a98
1551 changed files with 718128 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
[package]
name = "jcode-swarm-core"
version = "0.1.0"
edition = "2024"
publish = false
[dependencies]
jcode-plan = { path = "../jcode-plan" }
serde = { version = "1", features = ["derive"] }
+814
View File
@@ -0,0 +1,814 @@
use jcode_plan::PlanItem;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::borrow::Cow;
use std::collections::{HashMap, HashSet};
use std::path::PathBuf;
pub const MAX_SWARM_COMPLETION_REPORT_CHARS: usize = 4000;
pub const SWARM_COMPLETION_REPORT_MARKER: &str = "SWARM COMPLETION REPORT REQUIRED";
/// Message/report bodies longer than this require a sender-provided `tldr`
/// so receiving UIs can render them collapsed to one line with an expand
/// control instead of dumping the full body into the transcript.
pub const SWARM_TLDR_REQUIRED_OVER_CHARS: usize = 240;
/// Upper bound for a sender-provided `tldr`. Anything longer defeats the
/// purpose of a one-line collapsed summary.
pub const MAX_SWARM_TLDR_CHARS: usize = 200;
/// Validate a sender-provided `tldr` against the message body it summarizes.
///
/// Returns the normalized (trimmed, whitespace-collapsed) tldr when present,
/// `Ok(None)` when the body is short enough to not need one, and a
/// human/model-actionable error when a long body is missing a tldr or the
/// tldr itself is malformed (too long or multi-line).
pub fn validate_swarm_tldr(
tldr: Option<&str>,
body: &str,
context: &str,
) -> Result<Option<String>, String> {
let normalized = tldr
.map(|t| t.split_whitespace().collect::<Vec<_>>().join(" "))
.filter(|t| !t.is_empty());
if let Some(ref tldr) = normalized {
let chars = tldr.chars().count();
if chars > MAX_SWARM_TLDR_CHARS {
return Err(format!(
"'tldr' for {context} is too long ({chars} chars, max {MAX_SWARM_TLDR_CHARS}). \
Provide a single short line summarizing the message."
));
}
return Ok(normalized);
}
let body_chars = body.chars().count();
if body_chars > SWARM_TLDR_REQUIRED_OVER_CHARS {
return Err(format!(
"'tldr' is required for {context} because the body is {body_chars} chars \
(over {SWARM_TLDR_REQUIRED_OVER_CHARS}). Add a one-line 'tldr' (under \
{MAX_SWARM_TLDR_CHARS} chars) summarizing it; recipients see the tldr \
collapsed with an expand control."
));
}
Ok(None)
}
/// Maximum number of live members (agents) in a single swarm. This is the sole
/// runaway-prevention cap for the task-graph model. There is intentionally no
/// spawn-depth limit and no per-node fan-out limit: the spawn tree may nest and
/// fan out freely until the swarm reaches this many live members, at which point
/// further spawns are refused.
pub const MAX_SWARM_MEMBERS: usize = 1000;
/// Upper bound for a member's derived task label, sized for one-line UI chips.
pub const MAX_SWARM_TASK_LABEL_CHARS: usize = 48;
/// Derive a short, stable task label from a spawn prompt or task assignment.
///
/// Takes the first non-empty line, strips common markdown/list prefixes,
/// collapses whitespace, and truncates on a char boundary with an ellipsis.
/// Returns `None` when the text has no usable content.
pub fn derive_swarm_task_label(text: &str) -> Option<String> {
let line = text.lines().map(str::trim).find(|line| !line.is_empty())?;
let line = line
.trim_start_matches(['#', '-', '*', '>', ' '])
.trim_end_matches(':')
.trim();
let collapsed = line.split_whitespace().collect::<Vec<_>>().join(" ");
if collapsed.is_empty() {
return None;
}
if collapsed.chars().count() <= MAX_SWARM_TASK_LABEL_CHARS {
return Some(collapsed);
}
let truncated: String = collapsed
.chars()
.take(MAX_SWARM_TASK_LABEL_CHARS.saturating_sub(1))
.collect();
Some(format!("{}", truncated.trim_end()))
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum SwarmRole {
Agent,
Coordinator,
Other(String),
}
impl SwarmRole {
pub fn as_str(&self) -> Cow<'_, str> {
match self {
Self::Agent => Cow::Borrowed("agent"),
Self::Coordinator => Cow::Borrowed("coordinator"),
Self::Other(value) => Cow::Borrowed(value.as_str()),
}
}
}
impl From<String> for SwarmRole {
fn from(value: String) -> Self {
match value.as_str() {
"agent" => Self::Agent,
"coordinator" => Self::Coordinator,
_ => Self::Other(value),
}
}
}
impl Serialize for SwarmRole {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.as_str().as_ref())
}
}
impl<'de> Deserialize<'de> for SwarmRole {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Ok(Self::from(String::deserialize(deserializer)?))
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum SwarmLifecycleStatus {
Spawned,
Ready,
Running,
RunningStale,
Completed,
Done,
Failed,
Stopped,
Crashed,
Queued,
Blocked,
Pending,
Todo,
Other(String),
}
impl SwarmLifecycleStatus {
pub fn as_str(&self) -> Cow<'_, str> {
match self {
Self::Spawned => Cow::Borrowed("spawned"),
Self::Ready => Cow::Borrowed("ready"),
Self::Running => Cow::Borrowed("running"),
Self::RunningStale => Cow::Borrowed("running_stale"),
Self::Completed => Cow::Borrowed("completed"),
Self::Done => Cow::Borrowed("done"),
Self::Failed => Cow::Borrowed("failed"),
Self::Stopped => Cow::Borrowed("stopped"),
Self::Crashed => Cow::Borrowed("crashed"),
Self::Queued => Cow::Borrowed("queued"),
Self::Blocked => Cow::Borrowed("blocked"),
Self::Pending => Cow::Borrowed("pending"),
Self::Todo => Cow::Borrowed("todo"),
Self::Other(value) => Cow::Borrowed(value.as_str()),
}
}
}
impl From<String> for SwarmLifecycleStatus {
fn from(value: String) -> Self {
match value.as_str() {
"spawned" => Self::Spawned,
"ready" => Self::Ready,
"running" => Self::Running,
"running_stale" => Self::RunningStale,
"completed" => Self::Completed,
"done" => Self::Done,
"failed" => Self::Failed,
"stopped" => Self::Stopped,
"crashed" => Self::Crashed,
"queued" => Self::Queued,
"blocked" => Self::Blocked,
"pending" => Self::Pending,
"todo" => Self::Todo,
_ => Self::Other(value),
}
}
}
impl Serialize for SwarmLifecycleStatus {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.as_str().as_ref())
}
}
impl<'de> Deserialize<'de> for SwarmLifecycleStatus {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Ok(Self::from(String::deserialize(deserializer)?))
}
}
/// Durable, persistable portion of a swarm member.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct SwarmMemberRecord {
pub session_id: String,
pub working_dir: Option<PathBuf>,
pub swarm_id: Option<String>,
pub swarm_enabled: bool,
pub status: SwarmLifecycleStatus,
pub detail: Option<String>,
/// Stable label of the task/role this member was spawned or assigned for.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub task_label: Option<String>,
pub friendly_name: Option<String>,
pub report_back_to_session_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub latest_completion_report: Option<String>,
pub role: SwarmRole,
pub is_headless: bool,
}
/// Bidirectional index for swarm channel subscriptions.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct ChannelIndex {
pub by_swarm_channel: HashMap<String, HashMap<String, HashSet<String>>>,
pub by_session: HashMap<String, HashMap<String, HashSet<String>>>,
}
impl ChannelIndex {
pub fn subscribe(&mut self, session_id: &str, swarm_id: &str, channel: &str) {
self.by_swarm_channel
.entry(swarm_id.to_string())
.or_default()
.entry(channel.to_string())
.or_default()
.insert(session_id.to_string());
self.by_session
.entry(session_id.to_string())
.or_default()
.entry(swarm_id.to_string())
.or_default()
.insert(channel.to_string());
}
pub fn unsubscribe(&mut self, session_id: &str, swarm_id: &str, channel: &str) {
let mut remove_swarm = false;
if let Some(swarm_subs) = self.by_swarm_channel.get_mut(swarm_id) {
if let Some(members) = swarm_subs.get_mut(channel) {
members.remove(session_id);
if members.is_empty() {
swarm_subs.remove(channel);
}
}
remove_swarm = swarm_subs.is_empty();
}
if remove_swarm {
self.by_swarm_channel.remove(swarm_id);
}
let mut remove_session_entry = false;
if let Some(session_subs) = self.by_session.get_mut(session_id) {
let mut remove_swarm_entry = false;
if let Some(channels) = session_subs.get_mut(swarm_id) {
channels.remove(channel);
remove_swarm_entry = channels.is_empty();
}
if remove_swarm_entry {
session_subs.remove(swarm_id);
}
remove_session_entry = session_subs.is_empty();
}
if remove_session_entry {
self.by_session.remove(session_id);
}
}
pub fn remove_session(&mut self, session_id: &str) {
if let Some(session_subscriptions) = self.by_session.remove(session_id) {
for (swarm_id, channels) in session_subscriptions {
let mut remove_swarm = false;
if let Some(swarm_subs) = self.by_swarm_channel.get_mut(&swarm_id) {
for channel_name in channels {
if let Some(members) = swarm_subs.get_mut(&channel_name) {
members.remove(session_id);
if members.is_empty() {
swarm_subs.remove(&channel_name);
}
}
}
remove_swarm = swarm_subs.is_empty();
}
if remove_swarm {
self.by_swarm_channel.remove(&swarm_id);
}
}
return;
}
let swarm_ids: Vec<String> = self.by_swarm_channel.keys().cloned().collect();
for swarm_id in swarm_ids {
let mut remove_swarm = false;
if let Some(swarm_subs) = self.by_swarm_channel.get_mut(&swarm_id) {
let channel_names: Vec<String> = swarm_subs.keys().cloned().collect();
for channel_name in channel_names {
if let Some(members) = swarm_subs.get_mut(&channel_name) {
members.remove(session_id);
if members.is_empty() {
swarm_subs.remove(&channel_name);
}
}
}
remove_swarm = swarm_subs.is_empty();
}
if remove_swarm {
self.by_swarm_channel.remove(&swarm_id);
}
}
}
pub fn members(&self, swarm_id: &str, channel: &str) -> Vec<String> {
let mut members = self
.by_swarm_channel
.get(swarm_id)
.and_then(|swarm_subs| swarm_subs.get(channel))
.map(|members| members.iter().cloned().collect::<Vec<_>>())
.unwrap_or_default();
members.sort();
members
}
#[cfg(test)]
pub fn channels_for_session(&self, session_id: &str, swarm_id: &str) -> Vec<String> {
let mut channels = self
.by_session
.get(session_id)
.and_then(|session_subs| session_subs.get(swarm_id))
.map(|channels| channels.iter().cloned().collect::<Vec<_>>())
.unwrap_or_default();
channels.sort();
channels
}
}
pub fn append_swarm_completion_report_instructions(message: &str) -> String {
if message.contains(SWARM_COMPLETION_REPORT_MARKER) {
return message.to_string();
}
let mut out = message.trim_end().to_string();
if !out.is_empty() {
out.push_str("\n\n");
}
out.push_str("<system-reminder>\n");
out.push_str(SWARM_COMPLETION_REPORT_MARKER);
out.push_str(
"\nBefore finishing, call the swarm tool with action=\"report\" to submit your completion report. \
Include a concise message, validation/tests performed, and blockers or follow-ups. \
After the report tool succeeds, also write a brief final assistant response. \
Do not finish with only tool output, a lifecycle status change, or no final response. \
Do not send a separate DM for the final report unless you need interactive coordination before finishing.\n",
);
out.push_str("</system-reminder>");
out
}
/// Idempotency marker for [`append_deep_node_instructions`].
pub const SWARM_DEEP_NODE_MARKER: &str = "DEEP TASK GRAPH NODE";
/// Append the deep-mode execution contract to a task-graph node assignment.
///
/// Deep mode's comprehensiveness is structural: it only materializes when every
/// worker knows it can decompose its node into parallel children and must close
/// its node with a typed artifact. A freshly spawned worker has none of that
/// context (the seeding session's `swarm-deep` directive is not inherited), so
/// without this the budget goes unused: workers grind through nodes serially
/// and auto-complete without artifacts, silently downgrading deep mode to
/// light. This directive travels with the assignment itself, so it reaches
/// every worker at any spawn depth. Idempotent via [`SWARM_DEEP_NODE_MARKER`].
pub fn append_deep_node_instructions(message: &str, node_id: &str) -> String {
if message.contains(SWARM_DEEP_NODE_MARKER) {
return message.to_string();
}
let mut out = message.trim_end().to_string();
if !out.is_empty() {
out.push_str("\n\n");
}
out.push_str("<system-reminder>\n");
out.push_str(SWARM_DEEP_NODE_MARKER);
out.push_str(&format!(
"\nYou are executing node '{node_id}' of a deep task graph with a large parallel agent \
budget (up to {MAX_SWARM_MEMBERS} live agents per swarm; using it is expected, not wasteful). \
Choose one of exactly two finishes for this node:\n\
1. Decompose for parallelism: if this node contains more than one independently checkable \
concern, do NOT work through it serially. Call the swarm tool with action=\"expand_node\", \
node_id=\"{node_id}\", and MANY independent children (add depends_on edges only for real data \
dependencies, so the ready set stays wide). Then finish your turn; the children fan out to \
parallel agents and you will be re-woken to synthesize their results.\n\
2. Execute atomically: do the work, then call the swarm tool with action=\"complete_node\", \
node_id=\"{node_id}\", and a typed artifact: findings, evidence (file:line refs), validation, \
open_questions, a REQUIRED confidence (low, medium, or high; report low honestly, it routes \
follow-up work to shore up your scope instead of counting against you), and an honest \
what_i_did_not_check (the critique gate turns those into new nodes, so listing them is how \
coverage grows).\n\
These are the ONLY two ways this node can close: a turn that ends without expand_node or \
complete_node gets the node re-queued to a fresh agent, and a repeat fails it.\n"
));
out.push_str("</system-reminder>");
out
}
/// Append the deep-mode gate contract to a critique/verify gate assignment.
///
/// Gates are the adversarial half of deep mode: they exist to spend budget on
/// gaps. A gate that just rubber-stamps its parent wastes the swarm's capacity,
/// so the directive names the two legal finishes (`inject_gap` with new nodes,
/// or `complete_node` when genuinely clean) and reminds the gate to mine the
/// children's `what_i_did_not_check` lists. `audited_ids` is the gate's audit
/// scope: the server rejects a pass whose artifact does not account for each of
/// these ids by name (enumerated accounting is what separates an audit from a
/// rubber stamp), so the directive lists them up front. `low_confidence_siblings`
/// are completed scope nodes whose artifacts self-reported low confidence: the
/// strictest debts, named as priority probe targets. Shares the idempotency
/// marker with [`append_deep_node_instructions`] since a single assignment gets
/// exactly one deep directive.
pub fn append_deep_gate_instructions(
message: &str,
gate_id: &str,
audited_ids: &[String],
low_confidence_siblings: &[String],
) -> String {
if message.contains(SWARM_DEEP_NODE_MARKER) {
return message.to_string();
}
let mut out = message.trim_end().to_string();
if !out.is_empty() {
out.push_str("\n\n");
}
out.push_str("<system-reminder>\n");
out.push_str(SWARM_DEEP_NODE_MARKER);
out.push_str(&format!(
"\nYou are executing critique/verify gate '{gate_id}' of a deep task graph. Your job is \
to find gaps, not to pass work through. Read every audited artifact, especially each \
what_i_did_not_check list, and probe them. Finish in one of exactly two ways:\n\
1. Gaps or failures found: call the swarm tool with action=\"inject_gap\", \
gate_id=\"{gate_id}\", and one new node per gap (they run in parallel and you re-run \
afterwards). The parent cannot close until they drain, so be thorough now. Injecting nodes \
is SUCCESS for a gate, not failure: a growing graph is the system working.\n\
2. Genuinely clean: call the swarm tool with action=\"complete_node\", node_id=\"{gate_id}\", \
and an artifact whose findings account for EVERY node you audited BY ID with what you \
checked and why no gaps remain. The server rejects a pass whose findings/open_questions \
do not name each audited node id.\n"
));
if !audited_ids.is_empty() {
out.push_str(&format!(
"AUDIT SCOPE: you are auditing node(s) [{}]. A passing artifact must address each \
of these ids explicitly.\n",
audited_ids.join(", ")
));
}
if !low_confidence_siblings.is_empty() {
out.push_str(&format!(
"PRIORITY: sibling node(s) [{}] completed with LOW confidence. The server will \
REJECT your pass unless you either inject follow-up nodes that shore up that work, or name \
each of those ids in your artifact findings with why the low confidence is acceptable. \
Injecting follow-ups adds breadth but does not erase the record: when you re-run after they \
drain, your passing artifact must STILL name each low-confidence id (e.g. 'X was shored up \
by Y').\n",
low_confidence_siblings.join(", ")
));
}
out.push_str("Do not pass the gate without doing one of these.\n");
out.push_str("</system-reminder>");
out
}
pub fn format_structured_completion_report(
message: &str,
validation: Option<&str>,
follow_up: Option<&str>,
) -> String {
let mut report = message.trim().to_string();
if let Some(validation) = validation.map(str::trim).filter(|value| !value.is_empty()) {
if !report.is_empty() {
report.push_str("\n\n");
}
report.push_str("Validation:\n");
report.push_str(validation);
}
if let Some(follow_up) = follow_up.map(str::trim).filter(|value| !value.is_empty()) {
if !report.is_empty() {
report.push_str("\n\n");
}
report.push_str("Follow-ups/blockers:\n");
report.push_str(follow_up);
}
report
}
pub fn normalize_completion_report(report: Option<String>) -> Option<String> {
let report = report?.trim().to_string();
if report.is_empty() {
return None;
}
let char_count = report.chars().count();
if char_count <= MAX_SWARM_COMPLETION_REPORT_CHARS {
return Some(report);
}
let suffix = "\n\n[Report truncated by jcode before delivery.]";
let keep_chars = MAX_SWARM_COMPLETION_REPORT_CHARS.saturating_sub(suffix.chars().count());
let mut truncated: String = report.chars().take(keep_chars).collect();
truncated.push_str(suffix);
Some(truncated)
}
fn completion_status_intro(name: &str, status: &str) -> String {
match status {
"ready" => format!("Agent {} finished their work and is ready for more.", name),
"failed" => format!("Agent {} finished with status failed.", name),
"stopped" => format!("Agent {} stopped.", name),
"crashed" => format!("Agent {} crashed while working.", name),
_ => format!("Agent {} completed their work.", name),
}
}
fn completion_followup(status: &str, has_report: bool) -> &'static str {
match (status, has_report) {
("ready", true) => {
"Use assign_task to give them more work, stop to remove them, or summary/read_context for full context."
}
("ready", false) => {
"Use summary/read_context to inspect results, assign_task for more work, or stop to remove them."
}
("failed", true) => {
"Use summary/read_context for full context, retry with guidance, or stop to remove them."
}
("failed", false) => {
"Use summary/read_context to inspect results, assign_task to retry with guidance, or stop to remove them."
}
("stopped", _) => "Use summary/read_context to inspect results or stop to remove them.",
("crashed", _) => {
"Any swarm task assignments they held are requeued automatically where possible. \
Check plan_status, and spawn a replacement or use retry/assign_task if work remains."
}
(_, true) => {
"Use assign_task to give them new work, stop to remove them, or summary/read_context for full context."
}
(_, false) => "Use assign_task to give them new work, or stop to remove them.",
}
}
pub fn completion_notification_message(name: &str, status: &str, report: Option<&str>) -> String {
let intro = completion_status_intro(name, status);
let followup = completion_followup(status, report.is_some());
match report {
Some(report) => format!("{intro}\n\nReport:\n{report}\n\n{followup}"),
None => format!("{intro}\n\nNo final textual report was produced. {followup}"),
}
}
pub fn truncate_detail(text: &str, max_len: usize) -> String {
let collapsed = text.split_whitespace().collect::<Vec<_>>().join(" ");
let trimmed = collapsed.trim();
let max_len = max_len.max(1);
if trimmed.chars().count() <= max_len {
return trimmed.to_string();
}
if max_len <= 3 {
return trimmed.chars().take(max_len).collect();
}
let mut out: String = trimmed.chars().take(max_len - 3).collect();
out.push_str("...");
out
}
pub fn summarize_plan_items(items: &[PlanItem], max_items: usize) -> String {
if items.is_empty() {
return "no items".to_string();
}
let mut parts: Vec<String> = Vec::new();
for item in items.iter().take(max_items.max(1)) {
parts.push(item.content.clone());
}
let mut summary = parts.join("; ");
if items.len() > max_items.max(1) {
summary.push_str(&format!(" (+{} more)", items.len() - max_items.max(1)));
}
summary
}
#[cfg(test)]
mod tests {
use super::*;
fn plan_item(id: &str, content: &str) -> PlanItem {
PlanItem {
id: id.to_string(),
content: content.to_string(),
status: "queued".to_string(),
priority: "normal".to_string(),
subsystem: None,
file_scope: Vec::new(),
blocked_by: Vec::new(),
assigned_to: None,
}
}
#[test]
fn truncate_detail_collapses_whitespace_and_ellipsizes() {
assert_eq!(truncate_detail("hello there\nworld", 11), "hello th...");
}
#[test]
fn validate_swarm_tldr_allows_short_body_without_tldr() {
assert_eq!(validate_swarm_tldr(None, "quick note", "this DM"), Ok(None));
}
#[test]
fn validate_swarm_tldr_requires_tldr_for_long_body() {
let body = "x".repeat(SWARM_TLDR_REQUIRED_OVER_CHARS + 1);
let err = validate_swarm_tldr(None, &body, "this DM").unwrap_err();
assert!(err.contains("'tldr' is required"), "{err}");
assert!(err.contains("this DM"), "{err}");
}
#[test]
fn validate_swarm_tldr_normalizes_whitespace() {
let body = "x".repeat(SWARM_TLDR_REQUIRED_OVER_CHARS + 1);
assert_eq!(
validate_swarm_tldr(Some(" did\nthe thing "), &body, "this report"),
Ok(Some("did the thing".to_string()))
);
}
#[test]
fn validate_swarm_tldr_rejects_overlong_tldr() {
let tldr = "y".repeat(MAX_SWARM_TLDR_CHARS + 1);
let err = validate_swarm_tldr(Some(&tldr), "body", "this message").unwrap_err();
assert!(err.contains("too long"), "{err}");
}
#[test]
fn validate_swarm_tldr_blank_tldr_counts_as_missing() {
let body = "x".repeat(SWARM_TLDR_REQUIRED_OVER_CHARS + 1);
assert!(validate_swarm_tldr(Some(" "), &body, "this DM").is_err());
assert_eq!(
validate_swarm_tldr(Some(" "), "short", "this DM"),
Ok(None)
);
}
#[test]
fn summarize_plan_items_limits_output() {
let items = vec![
plan_item("a", "first"),
plan_item("b", "second"),
plan_item("c", "third"),
];
assert_eq!(summarize_plan_items(&items, 2), "first; second (+1 more)");
}
#[test]
fn append_swarm_completion_report_instructions_is_idempotent() {
let prompt = "Do work";
let with_instructions = append_swarm_completion_report_instructions(prompt);
assert!(with_instructions.contains(SWARM_COMPLETION_REPORT_MARKER));
assert_eq!(
append_swarm_completion_report_instructions(&with_instructions),
with_instructions
);
}
#[test]
fn deep_node_instructions_carry_expand_and_artifact_contract() {
let out = append_deep_node_instructions("Investigate the parser", "explore.parser");
assert!(out.starts_with("Investigate the parser"));
assert!(out.contains(SWARM_DEEP_NODE_MARKER));
// The two legal finishes must both name the node id explicitly.
assert!(out.contains("action=\"expand_node\", node_id=\"explore.parser\""));
assert!(out.contains("action=\"complete_node\", node_id=\"explore.parser\""));
// The budget is advertised so workers know fan-out is expected.
assert!(out.contains(&MAX_SWARM_MEMBERS.to_string()));
assert!(out.contains("what_i_did_not_check"));
// Idempotent: re-appending (even with a different id) is a no-op.
assert_eq!(append_deep_node_instructions(&out, "other"), out);
}
#[test]
fn deep_gate_instructions_carry_inject_gap_contract() {
let out = append_deep_gate_instructions("Critique the work", "root::gate", &[], &[]);
assert!(out.contains(SWARM_DEEP_NODE_MARKER));
assert!(out.contains("action=\"inject_gap\", gate_id=\"root::gate\""));
assert!(out.contains("action=\"complete_node\", node_id=\"root::gate\""));
assert!(out.contains("what_i_did_not_check"));
// No audit scope / low-confidence siblings: no callouts.
assert!(!out.contains("AUDIT SCOPE"));
assert!(!out.contains("PRIORITY"));
// Shares the marker with the node directive: one deep directive per assignment.
assert_eq!(
append_deep_gate_instructions(&out, "root::gate", &[], &[]),
out
);
assert_eq!(append_deep_node_instructions(&out, "root::gate"), out);
}
#[test]
fn deep_gate_instructions_enumerate_audit_scope() {
let scope = vec!["root.a".to_string(), "root.b".to_string()];
let out = append_deep_gate_instructions("Critique the work", "root::gate", &scope, &[]);
assert!(out.contains("AUDIT SCOPE"));
assert!(out.contains("root.a, root.b"));
// The coverage contract is stated: each id must be addressed.
assert!(out.contains("address each"));
}
#[test]
fn deep_gate_instructions_name_low_confidence_probe_targets() {
let shaky = vec!["root.shaky".to_string(), "root.wobble".to_string()];
let out = append_deep_gate_instructions("Critique the work", "root::gate", &shaky, &shaky);
assert!(out.contains("PRIORITY"));
assert!(out.contains("root.shaky, root.wobble"));
assert!(out.contains("LOW confidence"));
// The enforcement is explained: pass is rejected unless addressed.
assert!(out.contains("REJECT"));
}
#[test]
fn completion_report_normalization_trims_and_truncates() {
assert_eq!(
normalize_completion_report(Some(" done ".to_string())),
Some("done".to_string())
);
assert_eq!(normalize_completion_report(Some(" ".to_string())), None);
let long = "x".repeat(MAX_SWARM_COMPLETION_REPORT_CHARS + 100);
let normalized = normalize_completion_report(Some(long)).unwrap();
assert_eq!(
normalized.chars().count(),
MAX_SWARM_COMPLETION_REPORT_CHARS
);
assert!(normalized.ends_with("[Report truncated by jcode before delivery.]"));
}
#[test]
fn channel_index_keeps_bidirectional_maps_in_sync() {
let mut index = ChannelIndex::default();
index.subscribe("worker-1", "swarm-a", "build");
index.subscribe("worker-1", "swarm-a", "tests");
index.subscribe("worker-2", "swarm-a", "build");
assert_eq!(
index.members("swarm-a", "build"),
vec!["worker-1", "worker-2"]
);
assert_eq!(
index.channels_for_session("worker-1", "swarm-a"),
vec!["build", "tests"]
);
index.unsubscribe("worker-1", "swarm-a", "build");
assert_eq!(index.members("swarm-a", "build"), vec!["worker-2"]);
index.remove_session("worker-1");
assert!(index.channels_for_session("worker-1", "swarm-a").is_empty());
assert_eq!(index.members("swarm-a", "tests"), Vec::<String>::new());
}
#[test]
fn task_label_takes_first_line_strips_prefixes_and_collapses_whitespace() {
assert_eq!(
derive_swarm_task_label("Fix the parser\n\nMore detail here"),
Some("Fix the parser".to_string())
);
assert_eq!(
derive_swarm_task_label("\n\n ## Investigate flaky test: \nbody"),
Some("Investigate flaky test".to_string())
);
assert_eq!(
derive_swarm_task_label("- review PR #42"),
Some("review PR #42".to_string())
);
}
#[test]
fn task_label_truncates_long_prompts_with_ellipsis() {
let long = "implement the entire authentication subsystem including oauth flows";
let label = derive_swarm_task_label(long).unwrap();
assert!(label.chars().count() <= MAX_SWARM_TASK_LABEL_CHARS);
assert!(label.ends_with('…'), "got: {label}");
}
#[test]
fn task_label_rejects_empty_or_marker_only_text() {
assert_eq!(derive_swarm_task_label(""), None);
assert_eq!(derive_swarm_task_label(" \n\t\n"), None);
assert_eq!(derive_swarm_task_label("###"), None);
}
}