Scan a repository to surface actionable findings about agent performance. Analyzes source code, git history, GitHub data, agent logs, and agent context, then synthesizes cross-referenced findings with targeted actions informed by Tessl product awareness. Supports incremental multi-developer contributions and produces a self-contained HTML report.
70
88%
Does it follow best practices?
Impact
—
No eval scenarios have been run
Advisory
Suggest reviewing before use
Produce a structured, per-file inventory of the agent context surface of a repository. Unlike analyze-agent-context — which emits qualitative insights about context quality — this skill emits a catalogue: one entry per file with category, agent scope, and usefulness. The catalogue renders in the HTML report as a filterable file tree.
The skill also emits APEX insights (prefix INV) using the standard insight report schema, so it plugs into synthesis like the other analyzers.
Read the shared reference files:
Resolving reference paths: The links above use relative paths (
../../references/...) that work when this skill is read from its tile directory. If those paths do not resolve (e.g. when activated via a.claude/skills/symlink), find the shared references at.tessl/tiles/*/agent-insight-experiment/references/relative to the repository root.
Your insight prefix is INV (e.g., INV-001, INV-002).
Several files in scope — .mcp.json, .cursor/mcp.json, .claude/settings.json, .claude/settings.local.json — routinely contain secrets: API keys, bearer tokens, OAuth tokens, database URLs with embedded passwords, and env-var credentials. These values must never appear in the inventory output, including in the purpose field of mcp and hook entries, in quoted evidence inside INV insights, or in any log/console output you produce.
When emitting a purpose string or insight evidence derived from these files:
env blocks: do not read values. If you need to reference them, name the key only (e.g. "purpose": "GitHub MCP server (requires GITHUB_TOKEN env var)")./token|key|secret|password|bearer|auth|credential/i, any KEY=value segment whose KEY matches that pattern, any JWT-shaped string, and any opaque string longer than ~20 characters that looks like random base64/hex. Replace with [REDACTED].user:password@ credentials and query-string tokens before storing.The bundled scripts/build-inventory.mjs applies these redactions automatically to the MCP and hook purpose fields it generates. If you build the inventory by hand (e.g. on a host without Node), you must apply the same rules yourself. In either path, review the finished report once more before saving to confirm no raw secret slipped through the linked-file content you quote in INV insights.
The structured part of the output — context_inventory.files — is produced by the bundled script at scripts/build-inventory.mjs. Run it first; it handles glob discovery, link following, bidirectional reference tracking, classification, validation (symmetry + no self-loops), and writes the JSON report. This replaces hand-parsing and guarantees the link graph is correct on every run.
# From the target repo root:
node <skill-dir>/scripts/build-inventory.mjs
# Or specify explicit paths:
node <skill-dir>/scripts/build-inventory.mjs \
--root /path/to/repo \
--out .tessl-insights-poc/reports/context-inventory.jsonWhen an output file already exists, the script preserves the existing executive_summary, insights, and summary_statistics (the qualitative sections that you produce) and overwrites only the structured context_inventory.files and matching scope.metrics. After running the script:
context_inventory.files array.INV- insights.executive_summary and summary_statistics sections.If Node is unavailable on the target host, use the Discovery and Classification sections below to build the inventory by hand — the script and the documentation implement the same rules.
The goal is completeness: miss nothing that a coding agent would load. Monorepos typically have agent config at multiple depths (root and per-app under apps/*/, packages/*/), so every pattern below must be matched at any depth — never limit to the repo root.
Use your harness's built-in file-search tool (e.g. Glob in Claude Code / Cursor). It is portable across macOS, Linux, and Windows and does not depend on the presence of Unix find. Run each of these patterns and union the results — no depth cap, ** is required so per-app configs are matched:
| Pattern | What it captures |
|---|---|
**/AGENTS.md | Agent entry points at any depth |
**/CLAUDE.md | Claude Code entry points at any depth |
**/.cursorrules | Legacy Cursor rules file |
**/.github/copilot-instructions.md | Copilot entry point |
**/tessl.json | Tessl manifests (root and per-app) |
**/.mcp.json | Cross-tool MCP configs (root and per-app) |
**/.cursor/mcp.json | Cursor-specific MCP configs |
**/.cursor/settings.json | Cursor project settings |
**/.cursor/rules/*.md | Cursor rule files |
**/.cursor/rules/*.mdc | Cursor rule files |
**/.cursor/commands/*.md | Cursor slash commands |
**/.claude/settings.json | Claude Code project settings (hook source) |
**/.claude/settings.local.json | Claude Code local settings (hook + MCP enablement) |
**/.claude/commands/*.md | Claude Code custom slash commands |
**/.claude/skills/*/SKILL.md | Local Claude Code skills |
**/.tessl/RULES.md | Tessl-managed rule index |
**/.tessl/tiles/**/SKILL.md | Installed Tessl skills |
**/.tessl/tiles/**/rules/*.md | Installed Tessl rules |
Ignore anything under **/node_modules/**, **/.git/**, **/dist/**, **/build/**, **/.next/**, **/.turbo/**, and any **/test-fixtures/** / golden-run directories — those aren't real agent context.
If a Glob tool isn't available, fall back to this shell command. Do not use it directly on native Windows cmd.exe / PowerShell — Windows' find is a different utility. Use WSL, Git Bash, MSYS, or Cygwin.
find . \
-path "*/node_modules/*" -prune -o \
-path "*/.git/*" -prune -o \
-path "*/dist/*" -prune -o \
-path "*/build/*" -prune -o \
-path "*/.next/*" -prune -o \
-path "*/.turbo/*" -prune -o \
-path "*/test-fixtures/*" -prune -o \
-type f \( \
-name "AGENTS.md" -o \
-name "CLAUDE.md" -o \
-name ".cursorrules" -o \
-name "copilot-instructions.md" -o \
-name "tessl.json" -o \
-name ".mcp.json" -o \
-path "*/.cursor/mcp.json" -o \
-path "*/.cursor/settings.json" -o \
-path "*/.cursor/rules/*.md" -o \
-path "*/.cursor/rules/*.mdc" -o \
-path "*/.cursor/commands/*.md" -o \
-path "*/.claude/settings.json" -o \
-path "*/.claude/settings.local.json" -o \
-path "*/.claude/commands/*.md" -o \
-path "*/.claude/skills/*/SKILL.md" -o \
-path "*/.tessl/tiles/*/SKILL.md" -o \
-path "*/.tessl/tiles/*/*/SKILL.md" -o \
-path "*/.tessl/tiles/*/*/*/SKILL.md" -o \
-path "*/.tessl/tiles/*/*/*/*/SKILL.md" -o \
-path "*/.tessl/tiles/*/rules/*.md" -o \
-path "*/.tessl/tiles/*/*/rules/*.md" -o \
-path "*/.tessl/RULES.md" \
\) -printNever apply a -maxdepth cap — per-app .claude/ and .cursor/ configs commonly sit at depth 3+ (e.g. apps/frontend/.claude/settings.json).
For every discovered .claude/settings.json and .claude/settings.local.json — each one, including the nested per-app ones — parse the JSON and enumerate one hook entry per (hooks.<Event>, command) pair. A monorepo may easily have three or more of these settings files; missing any of them means missing hooks that actually run on that app.
Similarly, for every .mcp.json and .cursor/mcp.json found at any depth, parse and enumerate one mcp entry per server. Never assume the root config is canonical — sub-apps often add their own servers (e.g. apps/frontend/.cursor/mcp.json with a Figma server).
Apply the redaction rules from Sensitive Value Handling to the command strings and args you store in each entry's purpose field. Do not read or record MCP server env values.
Glob-based discovery alone misses context pulled in by reference — a repo may link to docs/architecture.md, CONTRIBUTING.md, or a shared rules/*.md bundle from inside AGENTS.md or a skill, and the agent that reads the referrer will follow or inline that content. Miss those, and the inventory under-reports the context surface.
After the initial glob pass, for every discovered markdown-style file (.md, .mdc, and the body of SKILL.md files), parse out two kinds of links and follow them:
@path imports — Claude-style context inclusion (e.g. @.tessl/RULES.md, @AGENTS.md). Content at the target is pulled into the model's context at load time, so linked targets behave like always-on rules.
(^|[\s(])@([^\s)]+) — a @ preceded by start-of-line or whitespace, followed by a path-like token.@ after the path segment), decorators / mentions in code fences, and anything that isn't a plausible filesystem path.[text](path) style references. Content is not auto-loaded, but the agent follows them when it decides it needs them, so they are part of the reachable context.
\[[^\]]*\]\(([^)\s]+)(?:\s+"[^"]*")?\) — captures the URL portion and tolerates an optional title.http://, https://, mailto:, anchor-only fragments (#...), javascript:, image references (.png, .jpg, .gif, .svg, .webp), and any target that clearly isn't a file path.Skip fenced code blocks (``` ... ```) and inline code (`...`) when scanning — examples in docs are not real references.
Path resolution:
/foo/bar.md) resolve against the repo root../ and ../ segments; drop any URL query (?...) or fragment (#...) suffix..md, .mdc, .json, .jsonc, .toml, .yaml, .yml, .txt, or a bare file with no extension that contains text). Binary targets and missing paths are dropped.Follow transitively: add each resolved target to a work queue, parse its links, and repeat until no new files are discovered. Track visited paths so cycles (A → B → A) terminate.
Classification for link-discovered files: if the target already matches a glob-category pattern (e.g. a linked SKILL.md), use that category. Otherwise:
@ → always_on_rule (the content is inlined into the agent's context).[text](path) → referenced_doc (available on demand).@ and [text](path) from different sources, prefer always_on_rule.Record both directions of every edge so the UI can show "X links to these" and "X is linked from these". See the links / linked_from fields in the per-file schema below.
Ignore globs still apply. Do not follow links into node_modules/, .git/, dist/, build/, .next/, .turbo/, or test-fixtures/ — those targets are excluded from the inventory even if referenced.
If tessl doctor is available, run it once and capture the installed tile + skill list — it is authoritative for skill entries and supplements what you find on disk.
After the initial pass, verify you have not missed multi-location configs. Run these as Glob patterns (preferred) or the equivalent find (fallback):
**/.claude/settings.json**/.claude/settings.local.json**/.mcp.json**/.cursor/mcp.jsonEach result must produce at least one inventory entry. If any are missing, fix the inventory before saving.
For each discovered file, emit one entry with these fields:
| Field | Type | Meaning |
|---|---|---|
path | string | Repo-relative path; the renderer groups by this path's directory and shows the basename in the tree |
category | enum | entry_point | always_on_rule | hook | skill | mcp | referenced_doc |
agents | string[] | Subset of ["claude", "cursor"], or ["cross-agent"] |
usefulness | enum | high | medium | low |
usefulness_reasoning | string | Short (≤ 1 sentence) justification |
scope | string[] | Optional tags from path: backend, frontend, etc. (omit root) |
purpose | string | Optional short description (required for hooks, skills, MCP servers) |
links | string[] | Optional. Repo-relative paths this file references (forward edges). Include both @ imports and [text](path) markdown links, normalized and deduplicated. Omit or use [] when the file references nothing. |
linked_from | string[] | Optional. Repo-relative paths of files that reference this one (reverse edges). Must be the inverse of links — every entry in links implies a matching entry in the target's linked_from. |
tokens | number | Optional. Rough token estimate (ceil(content_length / 4)) for the file this entry represents. Only emitted for .md / .mdc files — JSON/YAML/TOML config files are structured data whose character count is not a meaningful proxy for the prose-like context the agent reads. Also omitted on hook and mcp entries (synthetic records that sit inside a config file). |
The renderer treats path as a real filesystem-style path and groups by dirname(path). This produces a natural file tree without any hardcoded category buckets.
apps/backend/AGENTS.md, .cursor/rules/typescript.mdc..claude/settings.json): use <settings-file>/<HookEvent> — e.g. .claude/settings.json/SessionStart. This puts each hook as a "leaf" under the settings file in the tree..mcp.json / .cursor/mcp.json): use <config-file>/<server-name> — e.g. .mcp.json/tessl, apps/frontend/.cursor/mcp.json/figma.SKILL.md path under .tessl/tiles/<workspace>/<tile>/skills/<skill>/SKILL.md.Do not invent synthetic group directories like hooks/, skills/, or mcp-servers/ — those obscure where context actually lives in the repo.
entry_point — files that are auto-loaded by an agent at session start:
AGENTS.md (any depth)CLAUDE.md (any depth).cursorrules.github/copilot-instructions.mdalways_on_rule — rule files that are injected into every prompt:
.cursor/rules/*.mdc with alwaysApply: true in frontmatter@path imports from an entry_point (scan for @ import syntax, e.g. .tessl/RULES.md pattern)hook — each entry under hooks.* in .claude/settings.json / .claude/settings.local.json. One entry per (event, command) pair. The path is the command string; the group directory is hooks/.skill — entries under:
.claude/skills/*/SKILL.md.cursor/commands/**.tessl/tiles/**/skills/**/SKILL.mdtiles/**/skills/**/SKILL.mdmcp — each server entry in .mcp.json or .cursor/mcp.json. The path is the server name; the group directory is mcp-servers/.referenced_doc — a text/markdown file that is only reachable via a [text](path) markdown link from another inventoried file (never via @ import, never via a glob category pattern). These are consulted on demand by the agent and are part of the reachable context surface even though they aren't auto-loaded.If a file matches multiple categories (rare), pick the most specific — e.g. a .mdc with alwaysApply: true is always_on_rule, not entry_point. A file matched by both a glob pattern and a link always uses the glob category (e.g. a SKILL.md that is also linked from AGENTS.md is skill, not referenced_doc).
Any path containing the substring tessl__ (e.g. .claude/skills/tessl__tile-creator/**, .cursor/rules/tessl__rule__*.mdc) is excluded from the inventory entirely. These are vendored skills, rules, and references installed by tessl install — they are not part of the repo's own agent surface and they inflate the inventory with noise. Both the script and the manual path must drop them. Link-following must also stop at a tessl__ boundary: if AGENTS.md links into .claude/skills/tessl__foo/references/bar.md, neither the link edge nor the target file appears in the inventory.
.tessl/tiles/** paths (installed tile contents that aren't under a tessl__ directory) remain in the inventory — those are the tile-level skills and rules the repo has opted into and wants visible.
claude only: CLAUDE.md, .claude/**, hooks, .claude/skills/**cursor only: .cursor/**, .cursorrules, .cursor/commands/**, MCP servers that appear only in .cursor/mcp.jsoncross-agent: AGENTS.md, root .mcp.json servers, .tessl/** loaded via @ from AGENTS.md, copilot-instructions.md (read by multiple tools)A .mdc rule under .cursor/rules/ is always ["cursor"], even if the same topic is covered in AGENTS.md — record both entries and flag the duplication as an INV insight.
A referenced_doc inherits its agents from the set of files that link to it — if it is only reached from AGENTS.md, it is ["cross-agent"]; if only from a .cursor/rules/*.mdc, it is ["cursor"]; if reached from multiple harnesses, union them.
high — specific, actionable, concrete examples or conventions; current references; ≥ ~20 non-trivial lines. Example: an AGENTS.md with build commands, conventions, and architecture diagram.medium — exists and relevant but generic, short, or partially stale. Example: a rule that restates general TypeScript advice without project-specific detail.low — near-empty (< 10 lines of substance), clearly abandoned, superseded, or contradicted by another higher-usefulness file.Use judgment. The goal is actionable signal, not a strict formula. Record a short usefulness_reasoning so a human can audit.
Derive from the file's path:
apps/backend/** → ["backend"]apps/frontend/** → ["frontend"]["root"].cursor/rules/<area>.mdc where <area> matches a known app → tag with that areaWhile building the inventory, look for:
AGENTS.md / CLAUDE.md (or vice versa). Emit an INV insight with subcategory TCG-5 (cross-tool inconsistency).CLAUDE.md copies AGENTS.md). Emit INV insight with subcategory KCG-4 (redundant context wastes the context window).tessl doctor). Emit INV insight with subcategory TCG-2.low usefulness rules suggest cleanup. Emit INV insight with subcategory KCG-5 (stale documentation).Keep INV insights focused on facts the inventory makes visible. Don't repeat insights that analyze-agent-context already emits.
Produce a JSON report conforming to the insight report schema with one additional top-level field: context_inventory.
{
"metadata": {
"scan_id": "...",
"data_source": "context_inventory",
"repository": "...",
"analysis_timestamp": "...",
"analyzer_model": "...",
"scope": {
"description": "Inventory of agent context files: entry points, always-on rules, hooks, skills, MCP servers.",
"metrics": {
"files_inventoried": 38,
"by_category": {
"entry_point": 5,
"always_on_rule": 24,
"hook": 4,
"skill": 1,
"mcp": 1,
"referenced_doc": 3
},
"link_edges": 12,
"total_tokens": 42800
}
}
},
"executive_summary": "...",
"summary_statistics": {
/* standard */
},
"insights": [
/* INV-001, INV-002, ... */
],
"context_inventory": {
"file_tokens": {
"AGENTS.md": 705,
"CLAUDE.md": 15,
"docs/architecture.md": 312
},
"files": [
{
"path": "AGENTS.md",
"category": "entry_point",
"agents": ["claude", "cursor"],
"usefulness": "high",
"usefulness_reasoning": "~80 lines covering build commands, architecture, and cross-app type sync",
"purpose": "Top-level agent entry point with monorepo conventions",
"tokens": 705,
"links": [".tessl/RULES.md", "docs/architecture.md"],
"linked_from": ["CLAUDE.md"]
},
{
"path": "docs/architecture.md",
"category": "referenced_doc",
"agents": ["cross-agent"],
"usefulness": "high",
"usefulness_reasoning": "Referenced from AGENTS.md; describes service boundaries and data flow",
"purpose": "Architecture overview agents follow on demand",
"linked_from": ["AGENTS.md"]
},
{
"path": ".cursor/rules/typescript.mdc",
"category": "always_on_rule",
"agents": ["cursor"],
"usefulness": "medium",
"usefulness_reasoning": "Generic TS guidance, not project-specific",
"scope": ["backend"]
},
{
"path": ".claude/settings.json/PostToolUse",
"category": "hook",
"agents": ["claude"],
"usefulness": "high",
"usefulness_reasoning": "Auto-formats on Write/Edit — prevents format-related CI failures",
"purpose": "auto-formats on Write/Edit"
},
{
"path": ".mcp.json/tessl",
"category": "mcp",
"agents": ["cross-agent"],
"usefulness": "medium",
"usefulness_reasoning": "Stdio server via `tessl mcp start`",
"purpose": "Tessl MCP (tile docs, skill lookup)"
}
]
}
}If output_file is provided, use it exactly. Otherwise save to .tessl-insights-poc/reports/context-inventory.json.
The scripts/build-inventory.mjs script enforces the structural invariants and exits non-zero on violation; if you take the manual path, apply the same checks by hand.
context_inventory.filespath, category, agents, usefulness, usefulness_reasoningcategory and agents only use the allowed valuesscope.metrics.by_category counts match context_inventory.files countstessl__ paths: no entry's path, links, or linked_from contains the substring tessl__ in any segment.target in a file's links, the target file's linked_from must contain the source path. Drop edges whose target doesn't exist on disk or was filtered out by the ignore globs — do not emit dangling references.links or linked_from..claude/settings.json / .claude/settings.local.json returned by the self-check find, there is at least one hook entry whose path starts with that file. Same for every .mcp.json / .cursor/mcp.json and mcp entries. A nested per-app settings file with zero inventory entries means discovery silently failed — re-run before saving.INV insights if the inventory is non-trivial (≥ 10 files); fewer is acceptable for small repos if justifieddata_source_exclusive: true on INV insights that only this skill could surface (e.g. cross-tool asymmetries grounded in file-by-file inventory)