pub const FRIGG_DIRECTIVE_VERSION: &str = "2026-07-08";
pub const FRIGG_FIRST_DIRECTIVE: &str = include_str!("../assets/frigg-directive.md");
pub const FRIGG_FIRST_DIRECTIVE_EXPANDED: &str =
include_str!("../assets/frigg-directive-expanded.md");
pub const MANAGED_BLOCK_START: &str = "<!-- frigg-directive:start version=2026-07-08 -->";
pub const MANAGED_BLOCK_END: &str = "<!-- frigg-directive:end -->";
pub const HOOK_NUDGE: &str = "\
Frigg is the default for code discovery, file listing, navigation, exact code search, and bounded source reads. \
Advisory context only — does not block or auto-approve shell tools.
Preferred next step (indexed source while Frigg is registered):
- exact string/regex → search_text
- several guesses → search_batch (else parallel search_text/search_symbol)
- known symbol → search_symbol → go_to_definition
- vague \"where is X?\" → search_hybrid → exact proof (not rank-1 alone)
- list/outline → list_files / document_symbols; proof → read_match / read_file
Shell still OK: Frigg missing from tools/list; ignored/generated/unindexed path; live-disk when workspace advised; git/build/test output; non-source artifact.
";
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum AgentsPolicy {
#[default]
Lightweight,
Expanded,
}
impl AgentsPolicy {
pub fn directive_body(self) -> &'static str {
match self {
Self::Lightweight => FRIGG_FIRST_DIRECTIVE,
Self::Expanded => FRIGG_FIRST_DIRECTIVE_EXPANDED,
}
}
}
pub fn render_managed_block() -> String {
render_managed_block_for_policy(AgentsPolicy::Lightweight)
}
pub fn render_managed_block_for_policy(policy: AgentsPolicy) -> String {
format!(
"{MANAGED_BLOCK_START}\n{}\n{MANAGED_BLOCK_END}",
policy.directive_body().trim()
)
}
pub fn mcp_instructions(runtime_tail: &str) -> String {
let directive = FRIGG_FIRST_DIRECTIVE.trim();
let runtime_tail = runtime_tail.trim();
if runtime_tail.is_empty() {
directive.to_owned()
} else {
format!("{directive}\n\n{runtime_tail}")
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
const CORE_SENTENCES: [&str; 3] = [
"Frigg is the default for code discovery, file listing, navigation, exact code search, and bounded source reads.",
"Before using shell `rg`, `grep`, `find`, `fd`, `cat`, or `sed` for code exploration, use the matching Frigg MCP tool in attached or attachable code repositories.",
"Shell tools are fallback only for git state and diffs, non-code files, build/test output, generated or unindexed files, explicit live-disk verification, or when Frigg is unavailable.",
];
fn assert_core_sentences(surface_name: &str, text: &str) {
for sentence in CORE_SENTENCES {
assert!(
text.contains(sentence),
"{surface_name} should contain canonical sentence: {sentence}"
);
}
}
fn workspace_file(relative_path: &str) -> Option<String> {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../..")
.join(relative_path);
if !path.exists() {
return None;
}
Some(std::fs::read_to_string(&path).expect("workspace file should be readable"))
}
#[test]
fn render_managed_block_wraps_canonical_directive_with_markers() {
let rendered = render_managed_block();
assert!(rendered.starts_with(MANAGED_BLOCK_START));
assert!(rendered.ends_with(MANAGED_BLOCK_END));
assert!(rendered.contains(FRIGG_FIRST_DIRECTIVE.trim()));
assert_eq!(
rendered,
format!(
"{MANAGED_BLOCK_START}\n{}\n{MANAGED_BLOCK_END}",
FRIGG_FIRST_DIRECTIVE.trim()
)
);
}
#[test]
fn render_managed_block_for_policy_selects_lightweight_or_expanded_body() {
let lightweight = render_managed_block_for_policy(AgentsPolicy::Lightweight);
let expanded = render_managed_block_for_policy(AgentsPolicy::Expanded);
assert_eq!(lightweight, render_managed_block());
assert!(lightweight.contains("frigg-first-code-search"));
assert!(!lightweight.contains("## Compact scenario picker"));
assert!(!lightweight.contains("Hard anti-patterns"));
assert!(!lightweight.contains("Known string or regex -> search_text"));
assert!(expanded.contains(FRIGG_FIRST_DIRECTIVE_EXPANDED.trim()));
assert!(expanded.contains("## Compact scenario picker"));
assert!(expanded.contains("Known string or regex -> search_text"));
assert!(expanded.contains("Shell → Frigg"));
assert!(!expanded.contains("BAD: hybrid -> grep"));
assert_core_sentences("expanded directive", &expanded);
}
#[test]
fn hook_nudge_teaches_next_steps_and_stays_soft() {
assert!(
HOOK_NUDGE.contains(CORE_SENTENCES[0]),
"hook nudge should open with the canonical default sentence"
);
for tool in [
"search_text",
"search_batch",
"search_symbol",
"search_hybrid",
"go_to_definition",
"list_files",
"document_symbols",
"read_match",
"read_file",
] {
assert!(
HOOK_NUDGE.contains(tool),
"hook nudge should teach preferred tool `{tool}`"
);
}
assert!(
HOOK_NUDGE.contains("Advisory context only"),
"hook must state advisory/soft-only (no product hard deny)"
);
assert!(
HOOK_NUDGE.contains("Shell still OK"),
"hook must keep positive shell fallback boundary"
);
assert!(
!HOOK_NUDGE.contains("permissionDecision"),
"hook nudge must not reference permissionDecision"
);
assert!(
HOOK_NUDGE.len() < 800,
"hook nudge should stay compact (got {} chars)",
HOOK_NUDGE.len()
);
}
#[test]
fn default_managed_block_is_lightweight_pointer() {
let rendered = render_managed_block();
assert_core_sentences("default managed block", &rendered);
assert!(rendered.contains("frigg-first-code-search"));
assert!(
!rendered.contains("## Compact scenario picker"),
"default AGENTS policy must not embed the expanded picker table"
);
assert!(
!rendered.contains("Hard anti-patterns"),
"default AGENTS policy must not dump skill hard anti-patterns"
);
assert!(
!rendered.contains("| Shell habit | Frigg call |"),
"default AGENTS policy must not embed the full shell translation card"
);
}
#[test]
fn mcp_instructions_preserves_runtime_tail_after_directive() {
let rendered =
mcp_instructions("Runtime profile is `extended`.\nResource: `frigg://policy`.");
assert!(rendered.starts_with(FRIGG_FIRST_DIRECTIVE.trim()));
assert!(rendered.contains("\n\nRuntime profile is `extended`."));
assert!(rendered.ends_with("Resource: `frigg://policy`."));
}
#[test]
fn mcp_instructions_omits_blank_tail_spacing() {
assert_eq!(mcp_instructions(" \n\t "), FRIGG_FIRST_DIRECTIVE.trim());
}
#[test]
fn agent_directive_core_sentences_are_in_canonical_asset() {
assert_core_sentences("canonical directive", FRIGG_FIRST_DIRECTIVE);
assert_core_sentences("expanded directive asset", FRIGG_FIRST_DIRECTIVE_EXPANDED);
}
#[test]
fn agent_directive_core_sentences_are_in_skill_and_openai_prompt() {
let skill = workspace_file("skills/frigg-first-code-search/SKILL.md");
let openai_prompt = workspace_file("skills/frigg-first-code-search/agents/openai.yaml");
let (Some(skill), Some(openai_prompt)) = (skill.as_deref(), openai_prompt.as_deref())
else {
assert!(
skill.is_none() && openai_prompt.is_none(),
"bundled skill and OpenAI prompt drift files should be present together"
);
return;
};
assert_core_sentences("bundled skill", skill);
assert_core_sentences("OpenAI prompt", openai_prompt);
assert!(
!skill.contains("Prefer local shell tools"),
"bundled skill should not return to shell-first code-search guidance"
);
assert!(
!openai_prompt.contains("Use shell tools for quick local inspection"),
"OpenAI prompt should not return to shell-first code-search guidance"
);
}
#[test]
fn skill_scenario_tools_are_subset_of_public_tool_names() {
use crate::mcp::types::PUBLIC_TOOL_NAMES;
let skill = workspace_file("skills/frigg-first-code-search/SKILL.md").expect(
"bundled skill skills/frigg-first-code-search/SKILL.md must exist for SSOT guard",
);
const SCENARIO_TOOLS: &[&str] = &[
"workspace",
"list_files",
"read_file",
"read_match",
"search_text",
"search_hybrid",
"search_symbol",
"search_batch",
"find_references",
"go_to_definition",
"find_implementations",
"incoming_calls",
"outgoing_calls",
"document_symbols",
"inspect_syntax_tree",
"search_structural",
"impact_bundle",
"explore",
"find_declarations",
];
for tool_name in SCENARIO_TOOLS {
let cited = skill.contains(&format!("`{tool_name}`"))
|| skill.contains(&format!("`{tool_name}("));
assert!(
cited,
"bundled skill should cite scenario tool `{tool_name}` in a markdown code span"
);
assert!(
PUBLIC_TOOL_NAMES.contains(tool_name),
"skill scenario tool `{tool_name}` must be listed in PUBLIC_TOOL_NAMES (live SSOT)"
);
}
for phantom in [
"workspace_index",
"workspace_attach",
"workspace_reindex",
"deep_search",
"bridge_health",
"subagent_mcp_status",
] {
assert!(
!PUBLIC_TOOL_NAMES.contains(&phantom),
"phantom `{phantom}` must stay off PUBLIC_TOOL_NAMES"
);
}
for bridge_phantom in ["bridge_health", "subagent_mcp_status"] {
assert!(
skill.contains(bridge_phantom),
"skill should name rejected `{bridge_phantom}` so agents do not invent it"
);
}
}
#[test]
fn managed_block_version_matches_directive_version_constant() {
assert!(MANAGED_BLOCK_START.contains(FRIGG_DIRECTIVE_VERSION));
assert_eq!(FRIGG_DIRECTIVE_VERSION, "2026-07-08");
}
}