use std::path::Path;
use crate::agents_md::{
replace_scc_section_in, resolve_omp_agents_path, AgentsTarget,
SCC_OMP_SECTION_CLOSE, SCC_OMP_SECTION_OPEN,
};
const EXTENSION_TS: &str = include_str!("../embed/plugins/omp/scc/index.ts");
const EXTENSION_PACKAGE: &str = include_str!("../embed/plugins/omp/scc/package.json");
const SKILL_MD: &str = include_str!("../embed/plugins/omp/scc/skills/scc-system-context/SKILL.md");
const UPDATE_CHECK_TS: &str = include_str!("../embed/plugins/omp/scc/update-check.ts");
const AGENTS_RULES: &str = "<!-- SCC-OMP-SECTION -->\n\
# SCC (System Context Compiler)\n\
This repository is indexed by SCC. Durable rules:\n\
- The native SCC extension injects the startup architecture once per session\n\
and a task-specific context pack per prompt. Work within the injected task\n\
context; it is the authoritative system slice for the current goal.\n\
- For the fused startup architecture (Atlas + Surface + coverage),\n\
`scc context startup` is the canonical command; `scc atlas` is only its\n\
Level-0 Atlas component.\n\
- Authority ordering: source/runtime > SCC System IR > checkpoint > Hindsight\n\
> model assumption.\n\
- Drift and invariants: `scc drift`, `scc ci check`, and `scc impact <files>`\n\
before cross-layer edits.\n\
<!-- /SCC-OMP-SECTION -->\n";
pub fn cmd_setup_omp(root: &Path) -> crate::Result<()> {
let omp_dir = root.join(".omp");
let ext_dir = omp_dir.join("extensions/scc");
std::fs::create_dir_all(&ext_dir)?;
std::fs::write(ext_dir.join("index.ts"), installable_extension_ts())?;
std::fs::write(ext_dir.join("update-check.ts"), installable_aux_ts(UPDATE_CHECK_TS))?;
std::fs::write(ext_dir.join("package.json"), extension_package())?;
println!("wrote {}", ext_dir.join("index.ts").display());
merge_mcp_json(&omp_dir)?;
let skill_dir = omp_dir.join("skills/scc-system-context");
std::fs::create_dir_all(&skill_dir)?;
std::fs::write(skill_dir.join("SKILL.md"), skill_md())?;
println!("wrote {}", skill_dir.join("SKILL.md").display());
let agents_path = write_agents_rules(root)?;
println!();
println!("OMP integration installed:");
println!(" extension -> {}", ext_dir.join("index.ts").display());
println!(" mcp.json -> {}", omp_dir.join("mcp.json").display());
println!(" skill -> {}", skill_dir.join("SKILL.md").display());
println!(" AGENTS.md -> {}", agents_path.display());
println!();
println!("Restart OMP, then run `/extensions` to verify the SCC extension");
println!("is loaded (`/extensions` is an inspector, not a reload).");
println!("Verify MCP with `/mcp reload` then `/mcp test scc`.");
println!(
"The `scc` binary must be on PATH, or set SCC_BIN (the extension reads process.env.SCC_BIN)."
);
Ok(())
}
fn installable_extension_ts() -> String {
strip_authoring_markers(EXTENSION_TS)
}
pub(crate) fn strip_authoring_markers(src: &str) -> String {
let mut out = String::with_capacity(src.len());
for line in src.split('\n') {
let t = line.trim_start();
let style = if t.starts_with("// trace:v1 ") {
Some("//")
} else if t.starts_with("# trace:v1 ") {
Some("#")
} else if t.starts_with("-- trace:v1 ") {
Some("--")
} else if t.starts_with("<!-- trace:v1 ") {
Some("<!--")
} else {
None
};
if let Some(c) = style {
let indent = &line[..line.len() - t.len()];
let close = if c == "<!--" { " -->" } else { "" };
out.push_str(indent);
out.push_str(c);
out.push_str(" trace:exempt reason=scc-installed-tooling (authoring marker from the SCC source repo removed at install)");
out.push_str(close);
} else {
out.push_str(line);
}
out.push('\n');
}
if src.ends_with('\n') {
out.pop();
}
out
}
fn installable_aux_ts(src: &str) -> String {
strip_authoring_markers(src)
}
pub fn cmd_setup_pi(root: &Path) -> crate::Result<()> {
let ext_dir = root.join(".pi").join("extensions").join("scc");
std::fs::create_dir_all(&ext_dir)?;
std::fs::write(ext_dir.join("index.ts"), installable_pi_ts(EXTENSION_TS))?;
std::fs::write(ext_dir.join("update-check.ts"), installable_aux_ts(UPDATE_CHECK_TS))?;
std::fs::write(ext_dir.join("package.json"), extension_package())?;
println!("wrote {}", ext_dir.join("index.ts").display());
println!();
println!("Pi integration installed (project-local .pi/extensions/scc).");
println!("Restart Pi; the extension hot-reloads with /reload.");
println!("The `scc` binary must be on PATH, or set SCC_BIN.");
Ok(())
}
fn installable_pi_ts(src: &str) -> String {
strip_authoring_markers(src).replace(
"@oh-my-pi/pi-coding-agent",
"@mariozechner/pi-coding-agent",
)
}
fn extension_package() -> &'static str {
EXTENSION_PACKAGE
}
fn skill_md() -> &'static str {
SKILL_MD
}
fn scc_bin() -> String {
if let Ok(v) = std::env::var("SCC_BIN") {
let v = v.trim().to_string();
if !v.is_empty() {
return v;
}
}
if let Ok(exe) = std::env::current_exe() {
if exe.file_stem().map(|n| n == "scc").unwrap_or(false) {
return exe.to_string_lossy().into_owned();
}
}
"scc".to_string()
}
fn merge_mcp_json(omp_dir: &Path) -> crate::Result<()> {
let path = omp_dir.join("mcp.json");
let mut v: serde_json::Value = if path.exists() {
let text = std::fs::read_to_string(&path)?;
serde_json::from_str(&text).unwrap_or(serde_json::json!({}))
} else {
serde_json::json!({})
};
let servers = v
.get_mut("mcpServers")
.and_then(|m| m.as_object_mut())
.cloned()
.unwrap_or_default();
let mut servers = servers;
let cmd = scc_bin();
match servers.get_mut("scc") {
Some(existing) => {
if std::env::var("SCC_BIN").map(|s| !s.trim().is_empty()).unwrap_or(false) {
existing["command"] = serde_json::Value::String(cmd);
}
}
None => {
servers.insert(
"scc".to_string(),
serde_json::json!({"command": cmd, "args": ["mcp"]}),
);
}
}
v["mcpServers"] = serde_json::Value::Object(servers);
std::fs::write(&path, serde_json::to_string_pretty(&v)?)?;
Ok(())
}
fn write_agents_rules(root: &Path) -> crate::Result<std::path::PathBuf> {
let resolution = resolve_omp_agents_path(root);
if resolution.both_exist {
eprintln!(
"warning: both .omp/AGENTS.md and AGENTS.md exist; OMP prefers .omp/AGENTS.md \
(higher-priority same-scope context) and will shadow the root file. \
Patching .omp/AGENTS.md. Remove one of the files if that precedence is unintended."
);
}
let path = match resolution.target {
AgentsTarget::Omp => root.join(".omp").join("AGENTS.md"),
AgentsTarget::Root => root.join("AGENTS.md"),
};
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
let existing = std::fs::read_to_string(&path).unwrap_or_default();
let out = replace_scc_section_in(
&existing,
AGENTS_RULES,
SCC_OMP_SECTION_OPEN,
SCC_OMP_SECTION_CLOSE,
);
std::fs::write(&path, out)?;
Ok(path)
}
#[cfg(test)]
mod tests {
#[test]
fn pi_setup_installs_retargeted_extension() {
let dir = tempfile::TempDir::new().unwrap();
let root = dir.path().join("repo");
std::fs::create_dir_all(&root).unwrap();
cmd_setup_pi(&root).unwrap();
let ext = root.join(".pi").join("extensions").join("scc");
assert!(ext.join("index.ts").exists());
assert!(ext.join("update-check.ts").exists());
assert!(ext.join("package.json").exists());
let ts = std::fs::read_to_string(ext.join("index.ts")).unwrap();
assert!(ts.contains("@mariozechner/pi-coding-agent"), "pi scope retargeted");
assert!(!ts.contains("@oh-my-pi/"), "no fork scope remains");
assert!(ts.contains("maybeNotifyUpdate"), "notifier wired");
assert!(!ts.contains("trace:v1 id="), "authoring markers stripped");
}
use super::*;
use std::sync::{Mutex, MutexGuard};
static ENV_LOCK: Mutex<()> = Mutex::new(());
struct SccBinGuard {
_lock: MutexGuard<'static, ()>,
prev: Option<String>,
}
impl Drop for SccBinGuard {
fn drop(&mut self) {
match &self.prev {
Some(v) => std::env::set_var("SCC_BIN", v),
None => std::env::remove_var("SCC_BIN"),
}
}
}
fn lock_scc_bin(value: Option<&str>) -> SccBinGuard {
let lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let prev = std::env::var("SCC_BIN").ok();
match value {
Some(v) => std::env::set_var("SCC_BIN", v),
None => std::env::remove_var("SCC_BIN"),
}
SccBinGuard { _lock: lock, prev }
}
fn installed_extension(root: &Path) -> String {
std::fs::read_to_string(root.join(".omp/extensions/scc/index.ts")).unwrap()
}
#[test]
fn installs_extension_and_mcp() {
let _env = lock_scc_bin(None);
let dir = tempfile::TempDir::new().unwrap();
let root = dir.path().join("repo");
std::fs::create_dir_all(&root).unwrap();
cmd_setup_omp(&root).unwrap();
assert!(root.join(".omp/extensions/scc/index.ts").exists());
assert!(root.join(".omp/extensions/scc/update-check.ts").exists());
assert!(root.join(".omp/extensions/scc/package.json").exists());
let installed_ts = std::fs::read_to_string(root.join(".omp/extensions/scc/index.ts")).unwrap();
assert!(!installed_ts.contains("trace:v1 id="), "authoring markers stripped at install");
let ts = installed_extension(&root);
assert!(ts.contains("@oh-my-pi/pi-coding-agent"), "extension must import the canonical resolvable package");
assert!(ts.contains("before_agent_start"), "extension must wire before_agent_start");
assert!(ts.contains("session_start"), "extension must wire session_start");
let mcp: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(root.join(".omp/mcp.json")).unwrap()).unwrap();
assert_eq!(mcp["mcpServers"]["scc"]["command"], "scc");
assert_eq!(mcp["mcpServers"]["scc"]["args"][0], "mcp");
assert!(root.join("AGENTS.md").exists(), "canonical instruction file is root AGENTS.md");
assert!(
!root.join(".omp/AGENTS.md").exists(),
"must not create shadowing .omp/AGENTS.md when root did not exist"
);
let agents = std::fs::read_to_string(root.join("AGENTS.md")).unwrap();
assert!(agents.contains("SCC (System Context Compiler)"), "AGENTS.md must carry SCC durable rules");
let skill = std::fs::read_to_string(root.join(".omp/skills/scc-system-context/SKILL.md")).unwrap();
assert!(skill.contains("`system_context`"), "skill must teach system_context");
assert!(skill.contains("`surface_map`"), "skill must teach surface_map");
assert!(skill.contains("`structural_source`"), "skill must teach structural_source");
assert!(!skill.contains("| `system_overview` |"), "skill table must not lead with the retired startup tool");
}
#[test]
fn mcp_merge_preserves_existing_servers() {
let _env = lock_scc_bin(None);
let dir = tempfile::TempDir::new().unwrap();
let root = dir.path().join("repo");
std::fs::create_dir_all(&root).unwrap();
let omp = root.join(".omp");
std::fs::create_dir_all(&omp).unwrap();
std::fs::write(
omp.join("mcp.json"),
r#"{"mcpServers":{"existing":{"command":"foo","args":["bar"]}}}"#,
)
.unwrap();
merge_mcp_json(&omp).unwrap();
let mcp: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(omp.join("mcp.json")).unwrap()).unwrap();
assert_eq!(mcp["mcpServers"]["existing"]["command"], "foo", "existing server must be preserved");
assert_eq!(mcp["mcpServers"]["scc"]["command"], "scc", "scc server must be added");
}
#[test]
fn agents_idempotent_preserves_user_content() {
let dir = tempfile::TempDir::new().unwrap();
let root = dir.path().join("repo");
std::fs::create_dir_all(&root).unwrap();
let omp = root.join(".omp");
std::fs::create_dir_all(&omp).unwrap();
std::fs::write(omp.join("AGENTS.md"), "USER CONTENT\n").unwrap();
write_agents_rules(&root).unwrap();
let first = std::fs::read_to_string(omp.join("AGENTS.md")).unwrap();
assert!(first.starts_with("USER CONTENT"), "user content must be preserved");
assert!(first.contains("SCC (System Context Compiler)"), "SCC rules must be appended");
write_agents_rules(&root).unwrap();
let second = std::fs::read_to_string(omp.join("AGENTS.md")).unwrap();
assert_eq!(
first.matches("SCC (System Context Compiler)").count(),
second.matches("SCC (System Context Compiler)").count(),
"SCC rules must not duplicate on reinstall"
);
}
#[test]
fn agents_rewrite_preserves_text_after_closing_marker() {
let dir = tempfile::TempDir::new().unwrap();
let root = dir.path().join("repo");
std::fs::create_dir_all(root.join(".omp")).unwrap();
std::fs::write(
root.join(".omp/AGENTS.md"),
"BEFORE\n<!-- SCC-OMP-SECTION -->\nold managed\n<!-- /SCC-OMP-SECTION -->\nKEEP AFTER\n",
)
.unwrap();
write_agents_rules(&root).unwrap();
let text = std::fs::read_to_string(root.join(".omp/AGENTS.md")).unwrap();
assert!(text.contains("BEFORE"), "{text}");
assert!(text.contains("KEEP AFTER"), "text after the closing marker must survive: {text}");
assert!(!text.contains("old managed"), "{text}");
}
#[test]
fn agents_patches_existing_root_and_does_not_create_shadow() {
let dir = tempfile::TempDir::new().unwrap();
let root = dir.path().join("repo");
std::fs::create_dir_all(&root).unwrap();
std::fs::write(root.join("AGENTS.md"), "# project rules\nkeep me\n").unwrap();
let path = write_agents_rules(&root).unwrap();
assert_eq!(path, root.join("AGENTS.md"));
assert!(!root.join(".omp/AGENTS.md").exists(), "must not create shadowing .omp/AGENTS.md");
let text = std::fs::read_to_string(root.join("AGENTS.md")).unwrap();
assert!(text.contains("keep me"), "{text}");
assert!(text.contains("SCC (System Context Compiler)"), "{text}");
}
#[test]
fn omp_and_codex_sections_coexist_in_one_file() {
let dir = tempfile::TempDir::new().unwrap();
let root = dir.path().join("repo");
std::fs::create_dir_all(&root).unwrap();
std::fs::write(
root.join("AGENTS.md"),
"# user\n<!-- SCC-SECTION -->\ncodex capsule\n<!-- /SCC-SECTION -->\n",
)
.unwrap();
write_agents_rules(&root).unwrap();
let text = std::fs::read_to_string(root.join("AGENTS.md")).unwrap();
assert!(text.contains("codex capsule"), "codex section must survive OMP setup: {text}");
assert!(text.contains("SCC-OMP-SECTION"), "OMP section must be installed: {text}");
write_agents_rules(&root).unwrap();
let again = std::fs::read_to_string(root.join("AGENTS.md")).unwrap();
assert_eq!(again.matches("SCC-OMP-SECTION").count(), 2, "one OMP section: {again}");
assert!(again.contains("codex capsule"), "codex section survives reinstall: {again}");
}
#[test]
fn generated_extension_invokes_index_paths_and_does_not_treat_failure_as_success() {
let _env = lock_scc_bin(None);
let dir = tempfile::TempDir::new().unwrap();
let root = dir.path().join("repo");
std::fs::create_dir_all(&root).unwrap();
cmd_setup_omp(&root).unwrap();
let ts = installed_extension(&root);
assert!(
ts.contains("\"--paths\"") || ts.contains("[\"index\", \"--paths\""),
"generated extension must invoke `scc index --paths` (plural): {ts}"
);
assert!(
!ts.contains("\"--path\""),
"must not pass the rejected singular --path flag: {ts}"
);
assert!(
ts.contains("index --paths failed") || ts.contains("did not succeed"),
"failed index must be reported, not treated as success: {ts}"
);
assert!(
ts.contains("session.compacting") && ts.contains("session_before_compact"),
"compaction must save on session_before_compact and inject on session.compacting: {ts}"
);
assert!(
ts.contains("checkpoint") && ts.contains("load") && ts.contains("--inject"),
"compacting must load the checkpoint with --inject: {ts}"
);
assert!(
ts.contains("process.env.SCC_BIN") || ts.contains("SCC_BIN"),
"extension must honor SCC_BIN: {ts}"
);
assert!(
ts.contains("session_switch") && ts.contains("session_branch") && ts.contains("session_tree"),
"injection marker must reset on switch/branch/tree: {ts}"
);
assert!(
ts.contains("hash-object") || ts.contains("porcelain"),
"opaque mutations must snapshot dirty files: {ts}"
);
assert!(
ts.contains("--porcelain=v1") && ts.contains("-z"),
"porcelain must be NUL-delimited (-z): {ts}"
);
assert!(
ts.contains("\"--name-only\"") || ts.contains("diff\", \"--name-only\""),
"HEAD revision diffs must use --name-only: {ts}"
);
assert!(
ts.contains("split(\"\\0\")") || ts.contains("split('\\0')") || ts.contains(".split(\"\\0\")"),
"revision diffs must split on NUL, not newlines: {ts}"
);
assert!(
ts.contains("rev-parse") && ts.contains("HEAD"),
"snapshots must record HEAD so clean-to-clean mutations refresh: {ts}"
);
assert!(
!ts.contains("toolName}:${Date.now()") && !ts.contains("toolName}:${Date.now()}"),
"dirtySnapshots must not use a Date.now() fallback id: {ts}"
);
assert!(
ts.contains("if (!id) return"),
"snapshots without toolCallId must be skipped: {ts}"
);
assert!(
ts.contains("startupOk"),
"compaction must track startup injection separately from checkpoint: {ts}"
);
}
#[test]
fn mcp_json_honors_scc_bin_at_setup() {
let _env = lock_scc_bin(Some("/opt/custom/scc"));
let dir = tempfile::TempDir::new().unwrap();
let root = dir.path().join("repo");
std::fs::create_dir_all(&root).unwrap();
cmd_setup_omp(&root).unwrap();
let mcp: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(root.join(".omp/mcp.json")).unwrap()).unwrap();
assert_eq!(mcp["mcpServers"]["scc"]["command"], "/opt/custom/scc");
let ts = installed_extension(&root);
assert!(ts.contains("process.env.SCC_BIN"), "{ts}");
}
#[test]
fn setup_instructions_do_not_claim_extensions_reloads() {
let src = include_str!("plugin_omp.rs");
assert!(
src.contains("Restart OMP, then run `/extensions` to verify"),
"instructions must say restart, then /extensions to verify"
);
assert!(
src.contains("`/extensions` is an inspector, not a reload"),
"must say /extensions is an inspector"
);
let installer = src.split("#[cfg(test)]").next().unwrap();
assert!(
!installer.contains("or run `/extensions` to reload"),
"/extensions is an inspector, not a reload"
);
}
}