docs(comments): rewrite comments across all crates to the guidelines
Sweep every first-party crate source (1956 .rs files) to the project comment guidelines: delete redundant restatements, decorative banners, change narration, and end-of-line comments; keep and tighten the crucial ones (invariants, bug rationale, SAFETY blocks, ported-source attribution). No functional code changed. Every edit is proven comment-only against the prior tree by a comment-stripping lexer (string/char/raw-string aware) plus a separate doctest-fence check. Where removing a comment made rustfmt or clippy want to re-lay-out adjacent code, the minimal triggering comment is restored so code tokens stay byte-identical. Gates green: cargo fmt --all --check (0 diffs), cargo check and cargo clippy --workspace --all-targets (0 warnings). Adds scripts/check_codegen_comment_guidelines.py — the enforcement gate for these guidelines (flags banners, end-of-line comments, change narration, and commented-out code).
This commit is contained in:
@@ -1,14 +1,9 @@
|
||||
//! Subagent role and persona configuration types.
|
||||
//!
|
||||
//! These are the canonical definitions for `SubagentRole`, `SubagentPersona`,
|
||||
//! and `PersonaIOField`. The shell re-exports them via
|
||||
//! `kigi_shell::config::{SubagentRole, SubagentPersona, PersonaIOField}`.
|
||||
//!
|
||||
//! Methods that remain in `kigi-shell` (on `SubagentsConfig`):
|
||||
//! - `discover_personas()` / `discover_roles()` — filesystem discovery
|
||||
//! coupled to the shell's config resolution pipeline.
|
||||
//! - `resolve()` — config layering (CLI > env > TOML > remote) is
|
||||
//! shell-specific. This crate receives already-resolved maps.
|
||||
//! These are the canonical definitions; the shell re-exports them via
|
||||
//! `kigi_shell::config`. Filesystem discovery and config layering
|
||||
//! (CLI > env > TOML > remote) stay in `kigi-shell` on `SubagentsConfig` —
|
||||
//! this crate only ever sees already-resolved maps.
|
||||
|
||||
use kigi_tools::implementations::skills::discovery::extract_first_paragraph;
|
||||
use std::path::PathBuf;
|
||||
@@ -17,38 +12,29 @@ use serde::Deserialize;
|
||||
|
||||
/// A declarative subagent role definition from config.
|
||||
///
|
||||
/// Roles provide named presets that callers can reference via the
|
||||
/// `subagent_type` field in the task tool. Each role can specify
|
||||
/// a default capability mode, model override, and custom prompt.
|
||||
/// Roles are named presets callers reference via the `subagent_type` field in
|
||||
/// the task tool. Every default here can be overridden per-spawn.
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct SubagentRole {
|
||||
/// Human-readable description of what this role does.
|
||||
pub description: String,
|
||||
/// Default capability mode for agents using this role.
|
||||
/// One of: "read-only", "read-write", "execute", "all".
|
||||
/// Can be overridden per-spawn via `capability_mode` in the task tool.
|
||||
#[serde(default)]
|
||||
pub default_capability_mode: Option<String>,
|
||||
/// Model override for this role. If set, agents using this role
|
||||
/// default to this model unless the spawn-time `model` override
|
||||
/// is provided.
|
||||
#[serde(default)]
|
||||
pub model: Option<String>,
|
||||
/// Default reasoning effort for this role (e.g. "low", "medium", "high").
|
||||
/// Can be overridden per-spawn via `reasoning_effort` in the task tool.
|
||||
/// One of: "low", "medium", "high".
|
||||
#[serde(default)]
|
||||
pub reasoning_effort: Option<String>,
|
||||
/// Path to a prompt/instruction file (relative to workspace root).
|
||||
/// Loaded at spawn time and prepended to the child's prompt as a
|
||||
/// `<role-instructions>` block.
|
||||
#[serde(default)]
|
||||
pub prompt_file: Option<String>,
|
||||
/// Default isolation mode ("none" or "worktree").
|
||||
/// One of: "none", "worktree".
|
||||
#[serde(default)]
|
||||
pub default_isolation: Option<String>,
|
||||
/// Base directory for resolving relative `prompt_file` references.
|
||||
/// Set to the parent dir of the source `.toml` file during discovery.
|
||||
/// Base directory for resolving a relative `prompt_file`: the parent dir
|
||||
/// of the source `.toml`, filled in during discovery.
|
||||
#[serde(skip)]
|
||||
pub source_dir: Option<PathBuf>,
|
||||
}
|
||||
@@ -61,60 +47,49 @@ pub struct SubagentRole {
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct SubagentPersona {
|
||||
/// Inline instruction text applied as a persona layer.
|
||||
pub instructions: Option<String>,
|
||||
/// Optional short description shown in persona summaries.
|
||||
/// Falls back to first-paragraph extraction from `instructions`.
|
||||
/// When absent, summaries fall back to the first paragraph of
|
||||
/// `instructions`.
|
||||
pub description: Option<String>,
|
||||
/// Path to an instruction file (relative to workspace root).
|
||||
/// Content is loaded at spawn time and merged with `instructions`.
|
||||
/// If both are set, `instructions` is prepended before file content.
|
||||
/// Loaded at spawn time and merged with `instructions`, which is prepended
|
||||
/// before the file content when both are set.
|
||||
pub instructions_file: Option<String>,
|
||||
/// Declared inputs this persona expects. The parent agent reads these
|
||||
/// to know what file paths or context to provide in the prompt.
|
||||
#[serde(default)]
|
||||
pub inputs: Vec<PersonaIOField>,
|
||||
/// Declared outputs this persona produces. The parent agent reads
|
||||
/// these to know what artifacts to expect and pass to the next agent.
|
||||
#[serde(default)]
|
||||
pub outputs: Vec<PersonaIOField>,
|
||||
/// Default isolation mode when this persona is used.
|
||||
/// One of: "none", "worktree".
|
||||
#[serde(default)]
|
||||
pub default_isolation: Option<String>,
|
||||
/// Model override when this persona is used.
|
||||
#[serde(default)]
|
||||
pub model: Option<String>,
|
||||
/// Default reasoning effort for this persona (e.g. "low", "medium", "high").
|
||||
/// One of: "low", "medium", "high".
|
||||
#[serde(default)]
|
||||
pub reasoning_effort: Option<String>,
|
||||
/// Base directory for resolving relative file references.
|
||||
/// Set to the parent dir of the source `.toml` file during discovery.
|
||||
/// When `None`, relative paths resolve against the workspace cwd.
|
||||
/// Base directory for resolving relative file references: the parent dir
|
||||
/// of the source `.toml`, filled in during discovery. When `None`,
|
||||
/// relative paths resolve against the workspace cwd.
|
||||
#[serde(skip)]
|
||||
pub source_dir: Option<PathBuf>,
|
||||
/// Absolute path to the source file this persona was loaded from.
|
||||
/// Populated during discovery; `None` for inline config personas.
|
||||
/// Absolute path of the source file, filled in during discovery. `None`
|
||||
/// for personas declared inline in config.
|
||||
#[serde(skip)]
|
||||
pub source_path: Option<String>,
|
||||
}
|
||||
|
||||
/// A declared input or output for a persona.
|
||||
///
|
||||
/// Enables the parent agent to discover what a persona needs (inputs)
|
||||
/// and what it produces (outputs) without hardcoded knowledge of the
|
||||
/// persona's protocol.
|
||||
/// Lets the parent agent discover what a persona needs and what it produces
|
||||
/// without hardcoded knowledge of the persona's protocol.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, serde::Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct PersonaIOField {
|
||||
/// Short identifier (e.g. "review_file", "summary_file").
|
||||
pub name: String,
|
||||
/// What kind of artifact: "file", "text", etc.
|
||||
/// Kind of artifact: "file", "text", etc.
|
||||
#[serde(default = "PersonaIOField::default_io_type")]
|
||||
pub io_type: String,
|
||||
/// Whether this input/output is required.
|
||||
#[serde(default)]
|
||||
pub required: bool,
|
||||
/// Human-readable description shown in the task tool help.
|
||||
pub description: String,
|
||||
}
|
||||
|
||||
@@ -125,8 +100,8 @@ impl PersonaIOField {
|
||||
}
|
||||
|
||||
impl SubagentPersona {
|
||||
/// Render a human-readable summary of this persona's IO contract
|
||||
/// for inclusion in the task tool description.
|
||||
/// Renders this persona's IO contract as Markdown for the task tool
|
||||
/// description.
|
||||
pub fn render_io_summary(&self, name: &str) -> String {
|
||||
let fallback;
|
||||
let desc = if let Some(d) = self.description.as_deref().filter(|s| !s.trim().is_empty()) {
|
||||
|
||||
@@ -22,11 +22,13 @@ const MAX_VERBATIM_TURNS: usize = 3;
|
||||
/// strips a related (but different) tag set for compaction.
|
||||
const FORK_NOISE_TAGS: &[&str] = &[
|
||||
"system-reminder",
|
||||
"system_reminder", // Cursor wire format uses underscore
|
||||
// Cursor wire format uses underscore
|
||||
"system_reminder",
|
||||
"user_info",
|
||||
"git_status",
|
||||
"project_layout",
|
||||
"attached_files", // Alternate-agent file context; child reads files itself
|
||||
// Alternate-agent file context; child reads files itself
|
||||
"attached_files",
|
||||
];
|
||||
|
||||
/// Normalize a forked parent conversation into the shape:
|
||||
@@ -56,7 +58,7 @@ pub fn normalize_forked_context(items: Vec<ConversationItem>) -> (Vec<Conversati
|
||||
// Collect non-system items as the parent context to render.
|
||||
let parent_items: Vec<&ConversationItem> = items
|
||||
.iter()
|
||||
.skip(1) // skip System
|
||||
.skip(1)
|
||||
.filter(|i| !matches!(i, ConversationItem::System(_)))
|
||||
.collect();
|
||||
|
||||
@@ -135,7 +137,8 @@ fn count_complete_turns(items: &[&ConversationItem]) -> Vec<usize> {
|
||||
if i >= items.len() || !matches!(items[i], ConversationItem::Assistant(_)) {
|
||||
break;
|
||||
}
|
||||
i += 1; // skip past Assistant
|
||||
// skip past Assistant
|
||||
i += 1;
|
||||
// Consume the post-assistant run: ToolResults plus interleaved
|
||||
// Reasoning / BackendToolCall siblings, until the next User/Assistant.
|
||||
while i < items.len()
|
||||
@@ -374,7 +377,8 @@ fn render_summary(out: &mut String, items: &[&ConversationItem]) {
|
||||
fn truncate_str(s: &str, max_chars: usize) -> &str {
|
||||
match s.char_indices().nth(max_chars) {
|
||||
Some((byte_offset, _)) => &s[..byte_offset],
|
||||
None => s, // string has <= max_chars characters
|
||||
// string has <= max_chars characters
|
||||
None => s,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -716,8 +720,8 @@ mod tests {
|
||||
let refs: Vec<&ConversationItem> = items.iter().collect();
|
||||
let turns = count_complete_turns(&refs);
|
||||
assert_eq!(turns.len(), 2);
|
||||
assert_eq!(turns[0], 2); // after A1
|
||||
assert_eq!(turns[1], 4); // after A2
|
||||
assert_eq!(turns[0], 2);
|
||||
assert_eq!(turns[1], 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -733,7 +737,8 @@ mod tests {
|
||||
let refs: Vec<&ConversationItem> = items.iter().collect();
|
||||
let turns = count_complete_turns(&refs);
|
||||
assert_eq!(turns.len(), 2);
|
||||
assert_eq!(turns[0], 4); // after 2 tool results
|
||||
// after 2 tool results
|
||||
assert_eq!(turns[0], 4);
|
||||
assert_eq!(turns[1], 6);
|
||||
}
|
||||
|
||||
@@ -742,7 +747,8 @@ mod tests {
|
||||
let items = [
|
||||
user_item("U1"),
|
||||
assistant_item("A1"),
|
||||
user_item("U2"), // trailing User with no Assistant
|
||||
// trailing User with no Assistant
|
||||
user_item("U2"),
|
||||
];
|
||||
let refs: Vec<&ConversationItem> = items.iter().collect();
|
||||
let turns = count_complete_turns(&refs);
|
||||
@@ -765,8 +771,10 @@ mod tests {
|
||||
let refs: Vec<&ConversationItem> = items.iter().collect();
|
||||
let turns = count_complete_turns(&refs);
|
||||
assert_eq!(turns.len(), 2);
|
||||
assert_eq!(turns[0], 3); // after reasoning + A1
|
||||
assert_eq!(turns[1], 6); // after reasoning + A2
|
||||
// after reasoning + A1
|
||||
assert_eq!(turns[0], 3);
|
||||
// after reasoning + A2
|
||||
assert_eq!(turns[1], 6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -797,14 +805,14 @@ mod tests {
|
||||
#[test]
|
||||
fn truncate_str_multibyte_emoji() {
|
||||
// Each emoji is 4 bytes. Truncating at 2 chars should yield 2 emojis (8 bytes).
|
||||
let s = "\u{1F600}\u{1F601}\u{1F602}\u{1F603}"; // 4 emojis
|
||||
let s = "\u{1F600}\u{1F601}\u{1F602}\u{1F603}";
|
||||
assert_eq!(truncate_str(s, 2), "\u{1F600}\u{1F601}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn truncate_str_multibyte_cjk() {
|
||||
// CJK chars are 3 bytes each. Truncating at 3 chars should yield 3 chars (9 bytes).
|
||||
let s = "\u{4F60}\u{597D}\u{4E16}\u{754C}"; // 4 CJK chars
|
||||
let s = "\u{4F60}\u{597D}\u{4E16}\u{754C}";
|
||||
assert_eq!(truncate_str(s, 3), "\u{4F60}\u{597D}\u{4E16}");
|
||||
}
|
||||
|
||||
@@ -813,7 +821,7 @@ mod tests {
|
||||
assert_eq!(strip_fork_noise(""), "");
|
||||
}
|
||||
|
||||
// --- strip_xml_block tests ---
|
||||
// strip_xml_block tests
|
||||
|
||||
#[test]
|
||||
fn strip_xml_block_removes_system_reminder() {
|
||||
@@ -866,7 +874,7 @@ mod tests {
|
||||
assert_eq!(result, input);
|
||||
}
|
||||
|
||||
// --- strip_skill_instructions tests ---
|
||||
// strip_skill_instructions tests
|
||||
|
||||
#[test]
|
||||
fn strip_skill_instructions_preserves_command_metadata() {
|
||||
@@ -906,7 +914,7 @@ mod tests {
|
||||
assert_eq!(result, "prefix </command-args>");
|
||||
}
|
||||
|
||||
// --- collapse_blank_lines tests ---
|
||||
// collapse_blank_lines tests
|
||||
|
||||
#[test]
|
||||
fn collapse_blank_lines_reduces_runs() {
|
||||
@@ -922,7 +930,7 @@ mod tests {
|
||||
assert_eq!(result, input);
|
||||
}
|
||||
|
||||
// --- strip_fork_noise integration tests ---
|
||||
// strip_fork_noise integration tests
|
||||
|
||||
#[test]
|
||||
fn strip_fork_noise_strips_user_info() {
|
||||
@@ -1017,7 +1025,7 @@ This is a very long skill body with many lines of instructions.\n\n\
|
||||
assert!(!result.contains("**ARGUMENTS:**"));
|
||||
}
|
||||
|
||||
// --- normalize_forked_context integration tests ---
|
||||
// normalize_forked_context integration tests
|
||||
|
||||
#[test]
|
||||
fn normalize_forked_context_empty_after_strip() {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
//! Subagent configuration resolution crate.
|
||||
//!
|
||||
//! Extracts the pure-logic "resolution" phase of subagent spawning from
|
||||
//! `kigi-shell` into a reusable library. Given a spawn request and a
|
||||
//! resolution context (roles, personas, parent state), this crate resolves:
|
||||
//! The pure-logic "resolution" phase of subagent spawning. Given a spawn
|
||||
//! request and a resolution context (roles, personas, parent state), this
|
||||
//! crate resolves:
|
||||
//!
|
||||
//! - Effective runtime config (model, persona, capability mode, isolation)
|
||||
//! via precedence: explicit override > role > persona > parent.
|
||||
@@ -10,20 +10,16 @@
|
||||
//! - Role prompt file loading.
|
||||
//! - Resume identity validation (type/persona match checks; model is soft-ignored).
|
||||
//!
|
||||
//! This crate has no dependency on session, coordinator, or transport types.
|
||||
//! Designed to be consumed by local hosts (e.g. `kigi-shell`) and any
|
||||
//! future remote spawn path that only needs pure resolution logic.
|
||||
//! Nothing here may depend on session, coordinator, or transport types: local
|
||||
//! hosts (e.g. `kigi-shell`) and any remote spawn path must both be able to
|
||||
//! consume it.
|
||||
//!
|
||||
//! ## Planned composition API
|
||||
//!
|
||||
//! Future work may add a higher-level composition helper once shell call sites
|
||||
//! are refactored onto this crate:
|
||||
//!
|
||||
//! - `resolve_subagent_spec()` composition function
|
||||
//! - `SubagentSpec`, `ResolveSubagentRequest`, `ResolutionContext` boundary types
|
||||
//! - Optional deps for `AgentDefinition` lookup and worktree creation
|
||||
//! - Model override resolution chain (global > per-type > role > parent)
|
||||
//! - Capability mode filtering (delegates to `SubagentCapabilityMode::filter_tool_config()`)
|
||||
//! TODO: add a `resolve_subagent_spec()` composition entry point once shell
|
||||
//! call sites move onto this crate. It needs `SubagentSpec` /
|
||||
//! `ResolveSubagentRequest` / `ResolutionContext` boundary types, optional deps
|
||||
//! for `AgentDefinition` lookup and worktree creation, the global > per-type >
|
||||
//! role > parent model override chain, and capability mode filtering via
|
||||
//! `SubagentCapabilityMode::filter_tool_config()`.
|
||||
|
||||
pub mod config;
|
||||
pub mod context;
|
||||
|
||||
@@ -45,19 +45,19 @@ pub fn resolve_effective_overrides(
|
||||
cwd: Option<&Path>,
|
||||
role_name: Option<String>,
|
||||
) -> EffectiveRuntimeConfig {
|
||||
// ── Model resolution ─────────────────────────────────────────
|
||||
// Model resolution
|
||||
let model_from_override_or_role = overrides
|
||||
.model
|
||||
.clone()
|
||||
.or_else(|| role.and_then(|r| r.model.clone()));
|
||||
|
||||
// ── Reasoning effort resolution ──────────────────────────────
|
||||
// Reasoning effort resolution
|
||||
let reasoning_from_override_or_role = overrides
|
||||
.reasoning_effort
|
||||
.clone()
|
||||
.or_else(|| role.and_then(|r| r.reasoning_effort.clone()));
|
||||
|
||||
// ── Capability mode resolution ───────────────────────────────
|
||||
// Capability mode resolution
|
||||
let capability_mode = overrides.capability_mode.or_else(|| {
|
||||
role.and_then(|r| {
|
||||
r.default_capability_mode
|
||||
@@ -66,7 +66,7 @@ pub fn resolve_effective_overrides(
|
||||
})
|
||||
});
|
||||
|
||||
// ── Persona resolution ───────────────────────────────────────
|
||||
// Persona resolution
|
||||
let persona = overrides.persona.clone();
|
||||
let resolved_persona = persona.as_deref().and_then(|name| personas.get(name));
|
||||
|
||||
@@ -76,7 +76,7 @@ pub fn resolve_effective_overrides(
|
||||
let reasoning_effort = reasoning_from_override_or_role
|
||||
.or_else(|| resolved_persona.and_then(|p| p.reasoning_effort.clone()));
|
||||
|
||||
// ── Persona instructions loading ─────────────────────────────
|
||||
// Persona instructions loading
|
||||
// Fail-closed: if persona resolution produces an error (file unreadable,
|
||||
// not found, empty), return early with only persona + error populated.
|
||||
// All other fields are defaulted. This matches the shell's behavior where
|
||||
@@ -96,7 +96,7 @@ pub fn resolve_effective_overrides(
|
||||
};
|
||||
}
|
||||
|
||||
// ── Role prompt file loading (soft degradation) ──────────────
|
||||
// Role prompt file loading (soft degradation)
|
||||
let mut role_prompt_warning = None;
|
||||
let role_prompt = role.and_then(|r| {
|
||||
let file_path = r.prompt_file.as_deref()?;
|
||||
@@ -112,7 +112,7 @@ pub fn resolve_effective_overrides(
|
||||
}
|
||||
});
|
||||
|
||||
// ── Isolation resolution ─────────────────────────────────────
|
||||
// Isolation resolution
|
||||
let isolation = overrides
|
||||
.isolation
|
||||
.or_else(|| {
|
||||
@@ -156,7 +156,8 @@ fn resolve_persona_instructions(
|
||||
return (
|
||||
None,
|
||||
Some(format!("persona \"{name}\" not found in config")),
|
||||
false, // not fatal — config error, other fields still resolve
|
||||
// not fatal — config error, other fields still resolve
|
||||
false,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -176,7 +177,8 @@ fn resolve_persona_instructions(
|
||||
"persona \"{name}\": failed to read instructions_file \
|
||||
\"{file_path}\": {e}"
|
||||
);
|
||||
return (None, Some(err), true); // fatal — file I/O error
|
||||
// fatal — file I/O error
|
||||
return (None, Some(err), true);
|
||||
}
|
||||
},
|
||||
None => {
|
||||
@@ -184,7 +186,8 @@ fn resolve_persona_instructions(
|
||||
"persona \"{name}\": cannot resolve instructions_file \
|
||||
\"{file_path}\": no source_dir or cwd available"
|
||||
);
|
||||
return (None, Some(err), true); // fatal — unresolvable path
|
||||
// fatal — unresolvable path
|
||||
return (None, Some(err), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -195,7 +198,8 @@ fn resolve_persona_instructions(
|
||||
Some(format!(
|
||||
"persona \"{name}\" has no instructions or instructions_file"
|
||||
)),
|
||||
false, // not fatal — config error, other fields still resolve
|
||||
// not fatal — config error, other fields still resolve
|
||||
false,
|
||||
)
|
||||
} else {
|
||||
(Some(parts.join("\n\n")), None, false)
|
||||
@@ -232,7 +236,7 @@ mod tests {
|
||||
HashMap::new()
|
||||
}
|
||||
|
||||
// ── Precedence tests ─────────────────────────────────────────
|
||||
// Precedence tests
|
||||
|
||||
#[test]
|
||||
fn explicit_model_overrides_role() {
|
||||
@@ -329,7 +333,7 @@ mod tests {
|
||||
assert!(result.capability_mode.is_none());
|
||||
}
|
||||
|
||||
// ── Reasoning effort precedence ──────────────────────────────
|
||||
// Reasoning effort precedence
|
||||
|
||||
#[test]
|
||||
fn explicit_reasoning_effort_overrides_role_and_persona() {
|
||||
@@ -387,7 +391,7 @@ mod tests {
|
||||
assert_eq!(result.reasoning_effort.as_deref(), Some("medium"));
|
||||
}
|
||||
|
||||
// ── Isolation precedence ─────────────────────────────────────
|
||||
// Isolation precedence
|
||||
|
||||
#[test]
|
||||
fn explicit_isolation_overrides_role() {
|
||||
@@ -426,7 +430,7 @@ mod tests {
|
||||
assert_eq!(result.isolation, SubagentIsolationMode::None);
|
||||
}
|
||||
|
||||
// ── Persona instruction loading ──────────────────────────────
|
||||
// Persona instruction loading
|
||||
|
||||
#[test]
|
||||
fn persona_inline_instructions_only() {
|
||||
@@ -543,7 +547,7 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
// ── Role prompt file loading ─────────────────────────────────
|
||||
// Role prompt file loading
|
||||
|
||||
#[test]
|
||||
fn role_prompt_file_loaded_on_success() {
|
||||
@@ -581,7 +585,7 @@ mod tests {
|
||||
assert!(result.role_prompt_warning.is_some());
|
||||
}
|
||||
|
||||
// ── No persona requested ─────────────────────────────────────
|
||||
// No persona requested
|
||||
|
||||
#[test]
|
||||
fn no_persona_no_instructions() {
|
||||
@@ -592,7 +596,7 @@ mod tests {
|
||||
assert!(result.persona_error.is_none());
|
||||
}
|
||||
|
||||
// ── Persona with cwd fallback for instructions_file ──────────
|
||||
// Persona with cwd fallback for instructions_file
|
||||
|
||||
#[test]
|
||||
fn persona_instructions_file_uses_cwd_when_no_source_dir() {
|
||||
@@ -619,7 +623,7 @@ mod tests {
|
||||
assert!(result.persona_error.is_none());
|
||||
}
|
||||
|
||||
// ── Persona error early-return (fail-closed) ─────────────────
|
||||
// Persona error early-return (fail-closed)
|
||||
|
||||
#[test]
|
||||
fn persona_not_found_error_is_non_fatal() {
|
||||
@@ -679,7 +683,7 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
// ── instructions_file with no base dir ────────────────────────
|
||||
// instructions_file with no base dir
|
||||
|
||||
#[test]
|
||||
fn persona_instructions_file_no_base_dir_returns_error() {
|
||||
@@ -704,7 +708,7 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
// ── Persona isolation fallback (role has no isolation, persona does) ──
|
||||
// Persona isolation fallback (role has no isolation, persona does)
|
||||
|
||||
#[test]
|
||||
fn persona_isolation_used_when_no_explicit_or_role() {
|
||||
@@ -723,7 +727,7 @@ mod tests {
|
||||
assert_eq!(result.isolation, SubagentIsolationMode::Worktree);
|
||||
}
|
||||
|
||||
// ── Role prompt file cwd fallback ─────────────────────────────
|
||||
// Role prompt file cwd fallback
|
||||
|
||||
#[test]
|
||||
fn role_prompt_file_uses_cwd_when_no_source_dir() {
|
||||
@@ -751,7 +755,7 @@ mod tests {
|
||||
assert!(result.role_prompt_warning.is_none());
|
||||
}
|
||||
|
||||
// ── role_name parameter is threaded through ───────────────────
|
||||
// role_name parameter is threaded through
|
||||
|
||||
#[test]
|
||||
fn role_name_parameter_threaded_through() {
|
||||
|
||||
@@ -92,7 +92,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Matching cases ───────────────────────────────────────────
|
||||
// Matching cases
|
||||
|
||||
#[test]
|
||||
fn matching_type_no_persona() {
|
||||
@@ -131,7 +131,7 @@ mod tests {
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
// ── Mismatching cases ────────────────────────────────────────
|
||||
// Mismatching cases
|
||||
|
||||
#[test]
|
||||
fn type_mismatch_rejected() {
|
||||
@@ -168,7 +168,7 @@ mod tests {
|
||||
));
|
||||
}
|
||||
|
||||
// ── Validation order ─────────────────────────────────────────
|
||||
// Validation order
|
||||
|
||||
#[test]
|
||||
fn type_mismatch_checked_before_persona() {
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::resume::ResumeValidationError;
|
||||
pub enum ContextSource {
|
||||
/// Fresh session with no inherited history.
|
||||
New,
|
||||
/// Resumed from a previously completed peer subagent. The child inherits
|
||||
/// Resumed from a earlier completed peer subagent. The child inherits
|
||||
/// the source's raw transcript, tool state, and model. System prompt and
|
||||
/// prompt context are freshly rendered.
|
||||
Resumed,
|
||||
|
||||
Reference in New Issue
Block a user