use std::fmt::Write as _;
use std::path::{Path, PathBuf};
use clap::CommandFactory;
use crate::cli::Cli;
use trusty_mpm::core::delegation_authority::deployed_agent_dirs_from;
use trusty_mpm::core::harness_root::{HARNESS_DIR, SESSIONS_DIR};
use trusty_mpm::core::instruction_pipeline::COMPILED_PROMPT_FILE;
use trusty_mpm::core::paths::FrameworkPaths;
use trusty_mpm::core::skill_deploy_tiers::skill_deploy_tiers;
use trusty_mpm::core::trusty_tools_config::managed_claude_config_dir_at;
const HOME: &str = "~";
const PROJECT: &str = "<project>";
const REPO_DOCS: &[(&str, &str)] = &[
(
"docs/adr/INDEX.md",
"Index of every architecture decision record — the ADR is the authority on why a mechanism is shaped the way it is.",
),
(
"docs/specs/README.md",
"Index of the numbered DOC-* specs (behaviour contracts). A spec, not an ADR, is what a source file's `# Spec References` block links back to.",
),
(
"docs/reference/",
"Operational reference: release workflow, worktree discipline, SLOC cap, environment variables, threat model.",
),
(
"docs/SUMMARY.md",
"mdBook table of contents spanning every crate's docs.",
),
];
const LIVE_STATE_COMMANDS: &[(&[&str], &str)] = &[
(
&["doctor"],
"Whether each tier is actually populated on this machine, and which check failed.",
),
(
&["sessions", "instructions"],
"The composed prompt this project's session would receive, verbatim — the ground truth when the prompt and this catalog disagree.",
),
(
&["install"],
"Re-materialise the bundled agent/skill/hook artifacts into the layout above.",
),
];
pub(crate) fn render(start: &Path) -> String {
let home = Path::new(HOME);
let paths = FrameworkPaths::under(home);
let managed = managed_claude_config_dir_at(home);
let mut out = String::new();
out.push_str("# Framework Model — Layout, Tiers, and Where the Answers Live\n\n");
out.push_str(
"Generated by `tm generate capabilities` from the harness's own path \
constants and tier resolvers (`core::paths::FrameworkPaths`, \
`core::delegation_authority::deployed_agent_dirs_from`, \
`core::skill_deploy_tiers::skill_deploy_tiers`, \
`core::harness_root`). Nothing here is restated by hand: move a \
directory or reorder a tier and this file drifts, failing \
`scripts/check_capabilities.sh`. Paths are shown against `~` because \
the real layout is home-relative.\n\n",
);
render_layout(&mut out, &paths, &managed);
render_agent_tiers(&mut out, &paths, &managed);
render_skill_tiers(&mut out, &paths);
render_session_state(&mut out);
render_docs(&mut out, &repo_root(start));
render_live_state(&mut out);
out
}
fn render_layout(out: &mut String, paths: &FrameworkPaths, managed: &Path) {
out.push_str("## Install Layout\n\n");
out.push_str("| Path | What lives there |\n|---|---|\n");
let rows: &[(&PathBuf, &str)] = &[
(&paths.root, "Framework install root."),
(
&paths.framework,
"tm-owned, auto-materialised bundled tree. Never hand-edit — `tm install` and session prep overwrite it.",
),
(
&paths.agents,
"Bundled agent SOURCE files (pre-composition).",
),
(
&paths.skills,
"Bundled skill SOURCE files, re-materialised whenever the binary's embedded bundle fingerprint changes.",
),
(
&paths.user_skills,
"USER-authored skill source. Sits outside `framework/` precisely because that tree is overwritten. Deployed into every session, outranking a same-named bundled skill.",
),
(&paths.hooks, "Optimizer / overseer hook policy."),
(
&paths.instructions,
"Framework instruction assets. The prompt a session actually ran with is NOT here — it is per-session (see below).",
),
(&paths.registry, "Shared project registry."),
];
for (path, what) in rows {
let _ = writeln!(out, "| `{}` | {what} |", path.display());
}
let _ = writeln!(
out,
"| `{}` | The tm-managed `CLAUDE_CONFIG_DIR` (see below). |",
managed.display()
);
let _ = writeln!(
out,
"| `{}` | The operator's OWN Claude Code install. tm reads it as the lowest agent tier and never writes framework-owned agents into it. |\n",
paths.claude_home_dir().join(".claude").display()
);
out.push_str("## Why `CLAUDE_CONFIG_DIR` Is Relocated\n\n");
let _ = writeln!(
out,
"Managed sessions launch `claude` with `CLAUDE_CONFIG_DIR` pointed at \
`{}`, not the operator's `~/.claude`. Two things follow. Framework-owned \
agents and skills land in a tm-controlled directory, so they cannot \
contaminate — or be contaminated by — a Claude Code install the operator \
maintains for their own use. And because the relocated directory is \
global rather than per-workspace, one roster serves every session instead \
of each project carrying a mutable copy that can silently shadow the real \
agent.",
managed.display()
);
out.push_str(
"\nA session that has this variable set is reading the managed tier. A \
session that does not is reading the operator's own.\n\n",
);
}
fn render_agent_tiers(out: &mut String, paths: &FrameworkPaths, managed: &Path) {
out.push_str("## Agent Tier Precedence\n\n");
out.push_str(
"Highest precedence first — on a name collision the earlier tier wins, \
case-insensitively. Rendered from `deployed_agent_dirs_from`, the same \
function the runtime resolves against.\n\n",
);
let dirs = deployed_agent_dirs_from(
Path::new(PROJECT),
Some(managed),
&paths.claude_agents_dir(),
);
let notes = [
"Hand-placed and project-custom agents only. tm does not deploy bundled agents here.",
"Where every BUNDLED agent deploys. The `CLAUDE_CONFIG_DIR` env var overrides this path when set.",
"The operator's own Claude Code agents. Read, never written by tm.",
];
for (i, dir) in dirs.iter().enumerate() {
let note = notes.get(i).copied().unwrap_or("");
let _ = writeln!(out, "{}. `{}` — {note}", i + 1, dir.display());
}
out.push_str(
"\nThere is no `~/.trusty-mpm/agents/` tier. No code reads that path; a \
copy placed there is loaded by nothing.\n\n",
);
}
fn render_skill_tiers(out: &mut String, paths: &FrameworkPaths) {
out.push_str("## Skill Deploy Tiers\n\n");
out.push_str(
"Skills, unlike agents, have no single destination — a bundled skill is \
deployed into each of these independently, so one can be stale while \
another is current. Rendered from `skill_deploy_tiers`.\n\n",
);
out.push_str("| Tier | Directory |\n|---|---|\n");
for tier in skill_deploy_tiers(paths, Some(Path::new(PROJECT))) {
let _ = writeln!(out, "| {} | `{}` |", tier.label, tier.dir.display());
}
out.push_str(
"\nSource precedence when the same skill name exists in more than one \
source: project-custom, then user-custom (`~/.trusty-mpm/skills/`), then \
bundled. A deployed skill you hand-edit is frozen — its checksum no \
longer matches, so redeploy skips it rather than overwriting your edit. \
A skill whose slug contains `mcp` is never deployed; it would shadow \
Claude Code's built-in `/mcp`.\n\n",
);
}
fn render_session_state(out: &mut String) {
out.push_str("## Per-Session State\n\n");
let session_dir = Path::new(PROJECT)
.join(HARNESS_DIR)
.join(SESSIONS_DIR)
.join("<session-id>");
let _ = writeln!(
out,
"Session records live under `{}`, and the prompt a session was actually \
launched with is `{}` inside it. Both are keyed by session id, so two \
concurrent sessions in one project cannot overwrite each other.\n",
session_dir.display(),
COMPILED_PROMPT_FILE
);
let _ = writeln!(
out,
"The `{PROJECT}` here is the checkout that OWNS the project — the main \
checkout, never one of its git worktrees. Harness state belongs to the \
project; a worktree carries only code.\n"
);
}
fn render_docs(out: &mut String, root: &Path) {
out.push_str("## Authoritative Documentation\n\n");
out.push_str(
"This catalog answers layout and tier questions. Design rationale, \
behaviour contracts, and operational procedure live in documents — but \
only some of them travel with the installed binary.\n\n",
);
out.push_str("### Ships with the instructional content\n\n");
out.push_str(
"`tm install` writes these into `~/.trusty-mpm/framework/docs/` from the \
content `tm content install` pinned (or from a checkout's `content/`).\n\n",
);
let content_docs = crate_doc_files(root);
if content_docs.is_empty() {
out.push_str("- (none found under `content/instructions/docs/`)\n");
} else {
for name in &content_docs {
let _ = writeln!(out, "- `content/instructions/docs/{name}`");
}
}
out.push_str("\n### Ships in the published crate\n\n");
out.push_str("- `crates/trusty-mpm/README.md`, `crates/trusty-mpm/CHANGELOG.md`\n\n");
out.push_str("### Repo-only — NOT in the published crate\n\n");
out.push_str(
"`cargo package` includes only files under `crates/trusty-mpm/`, so \
everything below requires a checkout of \
`github.com/bobmatnyc/trusty-tools`. A session running against an \
installed binary alone cannot open these.\n\n",
);
out.push_str("| Path | Holds | Present in this checkout |\n|---|---|---|\n");
for (rel, what) in REPO_DOCS {
let present = if root.join(rel).exists() { "yes" } else { "NO" };
let _ = writeln!(out, "| `{rel}` | {what} | {present} |");
}
out.push('\n');
}
fn render_live_state(out: &mut String) {
out.push_str("## Resolving Live State\n\n");
out.push_str(
"This file describes the layout a correct install has. To learn what \
THIS machine actually resolved, run one of these rather than \
guessing — the `about` text is pulled from the CLI itself.\n\n",
);
for (path, why) in LIVE_STATE_COMMANDS {
let about = command_about(path);
let _ = writeln!(out, "- `tm {}` — {why}", path.join(" "));
let _ = writeln!(out, " - CLI: {about}");
}
out.push_str(
"\nWhen the compiled prompt and this catalog disagree about a path, the \
prompt is the thing the session is actually obeying and this catalog is \
the thing derived from code. Report the disagreement; do not silently \
pick one.\n",
);
}
fn command_about(path: &[&str]) -> String {
let mut current = Cli::command();
for segment in path {
let next = current.find_subcommand(segment).cloned();
match next {
Some(sub) => current = sub,
None => {
return format!(
"(no such command — `tm {}` was renamed or removed)",
path.join(" ")
);
}
}
}
current
.get_about()
.map(|about| about.to_string())
.unwrap_or_else(|| "(no description)".to_string())
}
fn repo_root(start: &Path) -> PathBuf {
start
.ancestors()
.find(|d| d.join(".git").exists())
.unwrap_or(start)
.to_path_buf()
}
fn crate_doc_files(root: &Path) -> Vec<String> {
let dir = root.join("content/instructions/docs");
let Ok(entries) = std::fs::read_dir(&dir) else {
return Vec::new();
};
let mut names: Vec<String> = entries
.flatten()
.filter_map(|e| {
let name = e.file_name().to_string_lossy().into_owned();
name.ends_with(".md").then_some(name)
})
.collect();
names.sort();
names
}
#[cfg(test)]
mod tests {
use super::*;
fn checkout() -> PathBuf {
trusty_common::test_harness::test_repo_root().expect("resolve the checkout")
}
fn render() -> String {
super::render(&checkout())
}
#[test]
fn framework_render_lists_the_real_agent_tier_order() {
let rendered = render();
let project = rendered
.find("1. `<project>/.claude/agents`")
.expect("project tier ranks first");
let managed = rendered
.find("claude-config/agents`")
.expect("managed tier is listed");
assert!(project < managed, "project tier must precede managed tier");
}
#[test]
fn framework_render_never_names_the_phantom_agent_tier() {
let rendered = render();
let mentions = rendered.matches("~/.trusty-mpm/agents/").count();
assert_eq!(
mentions, 1,
"the phantom tier may appear once, in the sentence refuting it"
);
assert!(
rendered.contains("There is no `~/.trusty-mpm/agents/` tier"),
"{rendered}"
);
}
#[test]
fn framework_render_resolves_clap_about_text() {
let rendered = render();
assert!(
!rendered.contains("no such command"),
"a documented live-state command was renamed or removed: {rendered}"
);
}
#[test]
fn framework_render_lists_crate_docs() {
let docs = crate_doc_files(&checkout());
assert!(
!docs.is_empty(),
"content/instructions/docs/ should hold the bundled docs"
);
let rendered = render();
for name in docs {
assert!(rendered.contains(&name), "{name} missing from {rendered}");
}
}
#[test]
fn framework_render_marks_repo_only_docs_present() {
let rendered = render();
assert!(
!rendered.contains("| NO |"),
"a repo-only doc pointer no longer resolves: {rendered}"
);
}
#[test]
fn framework_render_has_no_blank_line_runs() {
let rendered = render();
assert!(!rendered.contains("\n\n\n"), "{rendered}");
}
#[test]
fn framework_render_is_deterministic() {
assert_eq!(render(), render());
}
#[test]
fn framework_render_probes_the_checkout_holding_start_9298() {
let tmp = tempfile::tempdir().expect("tempdir");
let fake = tmp.path().join("fake");
let docs = fake.join("content/instructions/docs");
std::fs::create_dir_all(&docs).expect("mkdir docs");
std::fs::create_dir_all(fake.join(".git")).expect("mkdir .git");
std::fs::write(docs.join("only-in-the-fake-9298.md"), "x").expect("write doc");
let start = fake.join("crates/trusty-mpm/src");
std::fs::create_dir_all(&start).expect("mkdir start");
assert_eq!(repo_root(&start), fake);
let rendered = super::render(&start);
assert!(
rendered.contains("content/instructions/docs/only-in-the-fake-9298.md"),
"{rendered}"
);
assert!(rendered.contains("| `docs/adr/INDEX.md` |"), "{rendered}");
assert!(!rendered.contains("| yes |"), "{rendered}");
}
}