Repository navigation
[feat] One descriptor per tool, and one resolver for where teamai writes and where the tool reads #1221
Description
Problem
teamai writes a file, doctor reports ✔ delivered, and the tool never reads the file. #946 found this for rules in most tools. #1197 was the latest case: Cursor reads ~/.cursor/rules only by walking up from the workspace, so a workspace outside $HOME got no user rules. #1198 fixed it for Cursor, through its session-start hook. Nothing stops the next tool from having the same gap.
Most delivery checks compare the file teamai wrote with what teamai renders. A few also ask where the tool reads: relocated tool roots (toolRootRejection), CodeBuddy's first-existing MCP file (shadowedMcpRecords) and Cursor's session-start fallback (#1198). Each is written for one tool, so the next tool with the same gap gets no check. The code makes that gap likely:
- Per-tool facts sit in about 75 tables, Sets and branch chains across about 30 files. Only the
ToolNameunion fails the build when a tool is missing, and it covers only the 18 tools with subagents. Adding Muse touched 26 source files (feat(muse): support Muse Code across skills, rules, hooks, MCP, agents, models and sessions #1067). - Path resolution is written by hand in many places.
toolPathscovers most of it, but Hermes (getHermesHome), OpenClaw (resolveOpenclawWorkspaceDir), dsh (resolveDshHome), Copilot (getCopilotHome), CodeBuddy's MCP lookup and the HTTP local agent (resolveToolSettingsPath) each resolve their own. Several of the bugs below live in exactly those functions. - The tool lists disagree: 24
toolPathskeys, 18ALL_SUPPORTED_TOOLS, 38KNOWN_AGENTS, 8ModelAgentSchema, 7LOCAL_WORKFLOW_TOOLS. Three alias tables map the internal builds differently. - Several defaults fail open.
detectFormatfalls back to Claude-shaped hooks,instructionHookChannelreturnsready: truefor an unknown tool, anddetectMcpFormatreturnsnull, which skips MCP with no doctor check. - CI runs on Ubuntu and macOS only. The real-parser tests in
rule-parsers.test.tsnever run in CI. [bug] Windows: env 注入到 shell profile 时写的是原生路径,source 静默失败而 doctor 仍报通过 #661, [bug] ZCode on Windows: SessionStart hook output is discarded, so project rules never reach the session #1030, [bug] Codex hook entries on Windows are rendered in bash syntax and silently never run #1074 and [bug] Windows: session-start dispatch blocks 18 s on the process-table query, so Codex gets no team rules #1173 were Windows bugs with a green doctor.
To size the gap I built a matrix, at the end of this issue: 19 tools, user, workspace and self scope, and the conditions that change a cell.
Proposal
The goal is two things at once: a test fails when teamai writes where a tool does not read, and adding a tool, or filling in a feature for one teamai already supports, means choosing from what exists.
flowchart LR
subgraph callers ["callers"]
pull["pull, uninstall<br/>(writers)"]
doctor["doctor"]
misc["models switch, status,<br/>local agent"]
test["matrix test"]:::added
end
subgraph core ["one module"]
locate["locate(tool, resource, install, host)<br/>→ writes, reads, gates"]:::added
desc["TOOLS descriptor<br/>home, skillLoad, channels, support"]:::added
end
subgraph adapters ["existing adapters"]
rf["RULE_FORMATS"]
hf["hook formats and plugins"]
mf["MCP formats"]
af["renderForTool"]
end
host{{"Host<br/>realHost() or a test host (path.win32)"}}:::added
page["docs/guide/tool-support.md<br/>en + zh-CN"]:::added
old["per-tool resolvers today:<br/>skillsDirForTool, instructionTargetPath, resolveMcpTargets,<br/>getHermesHome, resolveDshHome, resolveToolSettingsPath, ..."]:::removed
pull & doctor & misc & test --> locate
locate --> desc
locate --> host
desc --> rf & hf & mf & af
desc --> page
pull -.-> old
doctor -.-> old
classDef added fill:#2f8f4f,stroke:#1f6b39,color:#fff;
classDef removed fill:#a33a3a,stroke:#7a2a2a,color:#fff,stroke-dasharray:4 3;
Green is new. The red resolvers are deleted as each resource moves.
One descriptor per tool
Each tool gets one entry. The entry says where the tool's home is and which existing adapter each resource uses. It also records what the tool itself does, with evidence.
export const TOOLS = {
cursor: {
home: { dir: '.cursor' },
skillLoad: { calls: [] }, // Cursor loads a skill by reading SKILL.md
rules: { channel: 'file', format: CURSOR_MDC_FORMAT },
blocks: { user: 'file', workspace: 'file' },
hooks: { channel: 'settings', format: 'cursor' },
mcp: { format: 'claude' },
agents: { format: 'cursor' },
support: { /* tool-side facts per feature and scope, with evidence */ },
},
// one entry per tool
} satisfies Record<ToolId, ToolDescriptor>;ToolIdis the key set of the defaulttoolPaths, lifted out of the zod default into aconst. A default tool without an entry failstsc. Custom tool ids in a team'stoolPathskeep working as today.- The adapters already exist:
RULE_FORMATS(8 formats), the hook formats and plugins, the MCP formats,renderForTool, and the instruction-block channels. The descriptor picks one per resource. A new adapter is needed only for a format that is new, as Trae's rule format was. A per-tool adapter class with one method per feature would make every new feature touch every tool, so this proposal does not use one. - "teamai builds it" is derived from the descriptor: a resource with a channel is built, and one without is not.
supportrecords only what the tool does: a status, an evidence class, a version, a note, and for a broken cell the issue that tracks it. A cell left out means unknown. The data cannot claim✓where teamai writes nothing. - Every default tool states each channel: an adapter,
'none'(JoyCode has no hooks, Pi and Hermes have no subagents), or'unknown'. Leaving it out failstsc, so a tool cannot fall back to Claude-shaped hooks or a silent MCP skip, and nobody has to invent an adapter for a feature the tool lacks. skillLoadsays how the tool's agent loads a skill: its dedicated skill tool (Skill,skill,read_skill), each with an evidence class (verified,source,docs,assumed), and a prompt mention that loads a skill with no tool call (Codex$name, Pi/skill:name). Reading aSKILL.mdpath also counts;classifyToolCall, which recall already runs, detects that for every tool, shell reads included. Tracking runs on the wildcardpost-tool-usepass every tool already installs, and one skill counts once per turn however the agent loaded it. That replacesSKILL_TOOL_MATCHERand the per-toolSkillhook. Copilot, Pi, OMP, dsh, OpenCode 2.x and Codex record no usage today.
Filling in a feature becomes a descriptor edit. For example, Muse's workspace instruction blocks would use the session-hook channel Codex already uses.
One module resolves where teamai writes and the tool reads
export async function locate(tool: string, resource: Resource, install: Install, host: Host = realHost()): Promise<Location>;
type Install = { toolPaths; agents: { enabledAgents, disabledAgents }; toolRoots } & ({ scope: 'user' } | { scope: 'project'; projectRoot: string });
type Host = { platform; path: path.PlatformPath; home; env; toolHomes; cwd(): string; exists(path): Promise<boolean>; readJson(path) };
type Location = { own: string | null; shared: string | null; reads: Read[] | 'unknown'; gates: Gate[] };
type Read = { path; when: 'always' } | { path; when: 'workspaceUnder' | 'cwdOnly'; root };Install is what the member's config decides: the team's toolPaths, the scope, self mode (repo.kind), the recorded tool roots, and the main checkout that self-mode hooks go to. Host is what the machine decides. Keeping them apart preserves today's rule that a recorded root wins over the current shell's env, while doctor can still compare the two.
A home moved by a variable is a machine fact. init and the first pull that sees the move record it in ~/.teamai/tool-homes.json, and Host.toolHomes carries it. The record wins over a shell without the variable, so a hook-run pull and a terminal pull agree. When a tool's skills directory moves, pull stops updating teamai's copies in the old one and says so once; it never deletes them, because a reader in another scope, shell or untracked project can't be seen from a pull. A2 adds the skills case (#1252). Self mode and the main checkout join Install with B4.
locate absorbs the tool home resolution (env vars, XDG, profiles, the Windows default), the per-tool toolPaths merge, the special cases (Hermes, OpenClaw, dsh, Copilot, CodeBuddy's MCP lookup, the local agent), and the read rules (walk-up, cwd only, git root). pull, uninstall, models switch, status, doctor and the tests all call it. Writing and reading share one home resolution, so the class "teamai writes ~/.config/opencode while OpenCode reads $XDG_CONFIG_HOME/opencode" goes away by construction.
Host has two implementations: realHost() (process.env, os, fs), the default, and literal values in tests. That is what lets a test on Linux check C:\Work with path.win32. It covers path resolution only. The other process.env reads stay as they are.
Tests through the same functions
- Matrix test, every PR. It calls
locatefor each tool, resource and scope on darwin, linux and win32, with the cwd inside$HOME, outside it and in a project subdirectory, and with each env override set and unset. Each write must be in the tool's reads, or the cell must be marked broken with its issue. The expected values come from the matrix at the end, not from a run oflocate. It takes seconds and downloads nothing. - Skill tool test. For each tool with skills, a skill call in that tool's payload shape goes through the dispatcher and records one usage event, credited to that tool.
- Tool list test.
ALL_SUPPORTED_TOOLS,ModelAgentSchema,LOCAL_WORKFLOW_TOOLS,SELF_MODE_AGENT_CHOICESand the alias tables are each a subset ofToolIdthat matches the descriptor's channel for that feature.KNOWN_AGENTSkeeps its discovery-only entries. Its paths for default tools must matchlocate, except pi, omp and opencode, which keep the project path.
What the matrix test sees, for two cells from the findings:
host tool resource writes today tool reads result
linux, XDG unset opencode skills ~/.config/opencode/skills ~/.config/opencode/skills pass
linux, XDG=/x opencode skills ~/.config/opencode/skills /x/opencode/skills fail ← A2 makes this pass
win32, cwd C:\Work cursor rules ~\.cursor\rules walk-up from C:\Work only not reached, so the session-start hook adds them (#1198)
The matrix test checks paths only. Delivery that happens at run time, such as Cursor's session-start fallback outside $HOME (#1198), keeps its behavior tests (cursor-user-rules-hook.test.ts), and they move with the channel. When a resource moves to locate, its old resolver and the path tests that duplicate the matrix are deleted.
doctor
doctor keeps its wording. Each delivery check calls locate with the member's real env, platform and home, and adds a line when that says more:
✖when, under the member's env, the tool does not read the path (XDG_CONFIG_HOMEset with OpenCode).⚠when the read depends on the workspace or the session cwd ("Pi reads project skills only from the session cwd"). Where teamai already covers the gap at run time, as Cursor's session-start fallback does, doctor keeps checking that channel instead.✖when a declared gate is not met: workspace trust, project trust, MCP approval,hooks.enabled,plugins.enabled.
A support page generated from the descriptor
docs/guide/tool-support.md and docs/guide/zh-CN/tool-support.md are generated from TOOLS with a snapshot test, as skill-data/core/references/commands.md is. The generator translates the legend and headers. Cell notes stay in English on both pages, and AGENTS.md records that exception. The README does not change.
Evidence, and the rule for reviewers
verified needs a recorded run: the tool version, the date and the command, in the PR. A check is committed under scripts/tool-checks/ only when it is cheap, offline and covers several cells of a tool that has had this kind of bug: Cursor's loaders, copilot instruction|skill|mcp list, codebuddy mcp list. CI never runs those scripts and never downloads a tool.
One bullet in AGENTS.md's Code Review Rules: a PR that adds a tool, changes where teamai writes for a tool, or changes what a tool supports updates its descriptor entry. A missing update is at most [P2 non-blocking]. A cell marked verified with no recorded run is [P3 nit]. Unknown is never a finding. What the matrix test checks stays out of review.
Windows
A windows-latest job runs on every PR, separate from E2E. The standard hosted runners are free for public repositories. It runs npm ci, npm run build, the matrix test, the hook rendering and path tests (a list of files, not the whole suite: 40 test files call bash today), and one real teamai hook-dispatch session-start for each hook shell (bash through Git Bash, PowerShell, node). It is not a required check at first, and becomes required once it stays green. A direct hook-dispatch run proves the shell, not which command field a tool reads. Codex's Windows hooks are tracked in #1209, #1210 and #1215, and the descriptor records that field with tool-side evidence.
Plan
flowchart LR
fix1185["#1185<br/>(fix: #1204)"]:::main
ca["#1218"]:::main
dsh["#1219"]:::main
la["#1220"]:::main
A1["A1<br/>descriptor, skillLoad"] --> A2["A2<br/>Host + locate: skills"]
fix1185 --> A2
A2 --> B1["B1<br/>rules, blocks"]
A2 --> B2["B2<br/>agents"]
A2 --> B3["B3<br/>MCP"]
A2 --> B4["B4<br/>hooks, self scope"]
A2 --> E["E<br/>Windows job"]
ca --> B2
dsh --> B4
la --> B4
A1 --> C["C<br/>runtime and AI rows"]
B1 & B2 & B3 & B4 & C --> D["D<br/>support page, review rule"]
classDef main fill:#2f5f8f,stroke:#1f4569,color:#fff;
Blue issues are fixed on main first. B1 to B4 depend only on A2, so they can land in any order. The list gives each PR's check.
in parallel, on main
#1204 and the three bugs in the next section
A1 descriptor skeleton: ToolId, home, skillLoad, tool list, home and channel tests; skill usage on the wildcard pass
→ verify: the skill-load test, fed payloads captured from real runs, fails for copilot, pi, omp, dsh, opencode 2.x and codex, and passes once tracking reads skillLoad; each skill counts once per turn
A2 Host and locate for skills: writers, doctor check, matrix test
→ verify: the matrix test fails on OpenCode's and Muse's XDG case, and passes after; Hermes and OpenClaw resolve through locate
B1 rules and instruction blocks
→ verify: the matrix covers Cursor's walk-up (#1197) and the subdirectory cases; the fallback's behavior tests still fail when the fallback is removed
B2 agents
B3 MCP, with the CodeBuddy and WorkBuddy lookup, ZCode, OpenCode's plugin and relocation branches, and the required format
→ verify: removing a tool's mcp format fails tsc
B4 hooks, with self scope, the main-checkout hooks, the local agent's hooks and the settings files `models switch` edits
→ verify: ZCode's self-scope case fails, then passes; doctor's fix text no longer loops
C runtime and AI rows, data only
D the generated support page in English and Chinese, and the AGENTS.md rule
E the Windows job, any time after A2
Every B PR moves the resource's writers, its doctor check and its path tests to locate, and deletes the old resolver. Behavior tests of run-time channels stay.
The steps ship in three PRs, after the three bugs:
- PR 1: A1 (feat(tools): per-tool descriptor and skill-use tracking for every tool (#1221) #1245, merged)
- PR 1b: A2 and E (feat(locate): resolve where every tool takes skills, with a Windows job (#1221) #1252, merged)
- PR 2: B1, B2 and B3 (feat(locate): rules, blocks, agents, and MCP go through locate (#1221) #1265, merged)
- PR 3: B4, C and D
What this fixes, and what is fixed separately
Bugs that cause harm today are fixed on main without waiting for this proposal:
- codebuddy: project-scope MCP written to .codebuddy/mcp.json instead of .mcp.json (mcpProject unused) #1185:
toolPathsreplaces the whole default table instead of merging per tool (also docs/diagnostics: a named toolPaths entry replaces that tool's defaults whole, and nothing surfaces the dropped keys #1115). Fixed in fix(config): merge custom toolPaths tool by tool over defaults (#1185) #1204. fix(doctor): report the default paths a custom toolPaths entry drops #1119 was closed unmerged.locatereadstoolPathsas parsed, so it works with either semantics. - [bug] Cursor CLI never loads user agents from ~/.cursor/agents, and doctor reports them delivered #1218: Cursor CLI never loads
~/.cursor/agents. Fixed in fix(cursor): use direct recall for user-scope CLI sessions #1222. - [bug] DeepSeek Harness never loads teamai's hooks unless the member runs dsh --patch #1219: dsh never loads teamai's hooks without
--patch. Fixed in fix(dsh): mount hooks from the default home patch #1223. - [bug] local agent: agent hooks go to files the tool does not read, and the ZCode one makes ZCode reject its user config #1220: agent hooks go to files the tool does not read, and the ZCode one makes ZCode reject its user config. Fixed in fix(local-agent): write agent hooks where each tool reads them #1224.
These are fixed by the proposal, in the PR named:
- Skill usage is never recorded on Copilot (its builtin check compares host
skillwith--matcher Skill), Pi and OMP (read, wildcard only), dsh (skill), OpenCode 2.x (the skill arrives asid) and Codex (no skill tool: it readsSKILL.mdthrough the shell or takes a$namemention). A1. -
initlooks for pi, omp and opencode in the wrong home directory.KNOWN_AGENTS.skillsPathholds the project path, which is right, because it is also the no-config project fallback. The home check now readsTOOLS[id].home. A1. - OpenCode and Muse ignore
XDG_CONFIG_HOME. A2 moved skills (feat(locate): resolve where every tool takes skills, with a Windows job (#1221) #1252). Rules, blocks, agents and MCP moved in feat(locate): rules, blocks, agents, and MCP go through locate (#1221) #1265. Hooks andmodels switchsettings stay at the default home until B4. - Tool homes moved by
PI_CODING_AGENT_DIR,DSH_HOME,OPENCLAW_PROFILEand Hermes's Windows default. A2 moved skills (feat(locate): resolve where every tool takes skills, with a Windows job (#1221) #1252). Rules, blocks, agents and MCP moved in feat(locate): rules, blocks, agents, and MCP go through locate (#1221) #1265. Hooks stay until B4. Hermes, Copilot, dsh and OpenClaw hooks and rules already follow the recorded home in A2. - CodeBuddy's home moved by
CODEBUDDY_CONFIG_DIR. A recorded run of CodeBuddy 2.168.0 (codebuddy mcp list) shows it replaces~/.codebuddy,.codebuddy.jsonincluded. Skills, rules, blocks, agents and MCP follow it since feat(locate): rules, blocks, agents, and MCP go through locate (#1221) #1265, and init records it. Hooks stay until B4. - Tool homes moved by OMP profiles,
OPENCLAW_HOMEand Hermes profiles (HERMES_DATA_DIR_SUFFIX). No tool-side source yet, solocatedoes not follow them. Each needs a recorded run first. - Muse, dsh and ZCode get no instruction blocks in workspace scope. B1 (feat(locate): rules, blocks, agents, and MCP go through locate (#1221) #1265).
- WorkBuddy's MCP: project servers in
.workbuddy/mcp.jsonare never read, and~/.workbuddy/.mcp.jsonhides teamai's user file. B3 (feat(locate): rules, blocks, agents, and MCP go through locate (#1221) #1265). - The ZCode CLI never reads
~/.agents/mcp.json. B3 (feat(locate): rules, blocks, agents, and MCP go through locate (#1221) #1265). teamai writes ZCode's own config, and doctor warns while that file still holds servers teamai does not own. - ZCode hooks in self scope go to a file ZCode never reads, and doctor's fix loops. B4. (fix(hooks): use the HOME ZCode config in self scope #1330)
- doctor does not check workspace trust (Claude, Muse), project trust (Codex agents, Pi), folder trust (Copilot) or MCP approval. B1 to B4. feat(locate): rules, blocks, agents, and MCP go through locate (#1221) #1265 checks Codex project trust for agents and MCP, and pull still writes. The other gates stay.
Done when
- Every default tool has a descriptor entry.
tscfails for a tool without one, or without a stated hook channel, MCP format or agent format. - Skills, rules, instruction blocks, agents, MCP and hooks resolve through
locate, and the old resolvers are gone. - The matrix test runs on every PR over darwin, linux and win32, the cwd inside
$HOME, outside it and in a subdirectory, and each env override. - doctor reports the conditions and gates from
locate. -
docs/guide/tool-support.mdand its Chinese page are generated from the descriptor. - The Windows job runs on every PR.
- Adding a tool starts with its descriptor entry, and the compiler and the tests list what else it needs.
Matrix
Tool support by scope, with evidence (19 tools)
How the matrix was built
teamai fe3b9067, rechecked against 8dc60b08 for the changes since. Each run used an isolated HOME.
- Real CLIs against a local mock model, with the request body captured: Claude Code 2.1.301 and 2.1.303, Codex 0.163.0, OpenCode 1.18.23 and 2.0.30, Pi 0.99.2, Oh My Pi 18.2.1. Self mode was run for Claude and Codex in a main checkout and a linked worktree.
- Offline commands: Copilot CLI 1.0.98 (
instruction list,skill list,mcp list), CodeBuddy 2.168.0 (mcp list), Muse 1.4.10 (skills list,exec --provider echoand its session log, with and without--trust-workspace), Pi (mcp list). - The tool's own loader, parser or schema, taken from its bundle or source and run on teamai's output: Cursor CLI 2026.09.21, .22 and .28 and Cursor IDE 3.16.29, CodeBuddy 2.168.0, Qoder CLI 1.1.73, Oh My Pi, Hermes (7e9eb8d9), OpenClaw (884a96d1), DeepSeek Harness (687f282e), ZCode v3.15.1's config schema.
- Source only: ZCode v3.15.1, JoyCode 3.8.71, the WorkBuddy engine inside CodeBuddy 2.168.0.
- Docs only: Kiro, Trae.
Not observed: any Windows run, any IDE session, and real Hermes, OpenClaw, dsh or ZCode sessions.
Status: ✓ works. ◐ works by default, and a condition in "Conditions that change a cell" breaks it while doctor still reports it delivered. ✗ broken in the default setup. + the tool supports it and teamai does not build it. — the tool has no such feature. ? unknown. ¹ the rules reach the tool but always on, as #946's ✓*.
Evidence: v a real tool run, or the tool's own loader, parser or schema run on teamai's output. s the tool's source or bundle. d docs (d3 third-party). t teamai's code only.
Defaults: macOS or Linux, the tool's default env, a session at the project root, the project inside $HOME.
User scope
| Tool | Skills | Rules | Instruction blocks | Agents | Hooks | MCP | Env | Workflows | Recall | Sessions | Stats |
|---|---|---|---|---|---|---|---|---|---|---|---|
| Claude Code | ✓v | ✓v | ✓v | ✓v | ✓v | ✓v | ✓t | ✓v | ✓v | ✓v | ✓v |
| Codex | ✓v | ✓v¹ | ✓v | ✓v | ◐v | ✓v | ✓t | ✓t | ✓v | ✓v | ✗v |
| Muse | ◐v | ◐v | ◐v | ◐v | ◐v | ◐v | ✓t | +v | ✓t | +s | ✓v |
| Cursor IDE | ✓s | ✓v | ✓v | ✓s | ✓s | ✓s | ? | — | ✓v | ?s | ✓t |
| Cursor CLI | ✓v | ✓v | ✓v | ✗v | ✓s | ✓s | ? | +s | ✗v | ?s | ✓v |
| Copilot CLI | ✓v | ✓v | ✓v | ?s | ✓s | ✓v | ? | +s | ✓v | +s | ✗v |
| OpenCode | ◐v | ◐v | ◐v | ◐v | ◐s | ◐v | ? | ✓t | ✓v | ✓t | ✗v |
| Pi | ◐v | ✓v¹ | ◐v | —s | ◐v | ◐v | ✓s | +v | ✓v | +d | ✗v |
| Oh My Pi | ◐v | ◐v | ◐v | ◐v | ◐v | ◐v | ✓s | +s | ✓v | +s | ✗v |
| Hermes | ◐v | ◐v | ◐v | —s | ◐v | +s | ◐s | +s | ✓v | +s | +s |
| OpenClaw | ✓s | ✓s | ✓s | +d | ◐v | +d | ◐d | +d | ✓s | +d | +s |
| DeepSeek Harness | ◐s | ✓s¹ | ✓s | +s | ✗s | +s | ✗s | +s | ✓s | +s | ✗v |
| ZCode | ✓s | ✓s¹ | ✓s | ✓s | ✓s | ✗s | ✓t | +s | ✓s | +s | ✓s |
| CodeBuddy | ◐s | ◐v | ◐s | ◐s | ◐s | ◐v | ? | +t | ✓s | ✓s | ✓v |
| WorkBuddy | ✓d3 | ✓t | ✓t | ✓t | ✓t | ◐s | ? | ? | ✓t | ✓t | ? |
| JoyCode | ✓s | ✓s¹ | ✓s | ✗s | —s | +s | ? | ? | ◐s | ? | ? |
| Kiro | ✓d | ◐d | +d | ✓d | ◐d | ✓d | ? | ? | ◐d | ? | ? |
| Qoder, Qoder CN (CLI) | ✓s | ✓v | +s | ✓s | ✓s | ✓s | ? | ? | ◐s | ? | ✓s |
| Trae, Trae CN | ✓d3 | ?t | +d3 | ?t | ?t | +t | ? | ? | ? | ? | ? |
Codex and OpenCode workflows hung or ran as the wrong agent at fe3b9067. #1192 fixed both (#1193), and they were not rerun.
Env reaches a tool only when the tool starts from a shell that sources teamai's profile block. A GUI, IDE or service launch gets nothing. Pi and Hermes have no subagents. teamai could render a team agent as a Pi prompt template (~/.pi/agent/prompts/<name>.md) or a Hermes skill, and a workflow step on Pi can pass the agent body with --append-system-prompt, which reaches the request.
Workspace scope
Hooks are user-level in workspace scope (resolveHookScope), so this column means "user hooks fire in this project".
| Tool | Skills | Rules | Instruction blocks | Agents | Hooks | MCP | Recall | Stats |
|---|---|---|---|---|---|---|---|---|
| Claude Code | ✓v | ✓v | ✓v | ✓v | ✓v | ◐v | ✓t | ✓v |
| Codex | ✓v | ✓v¹ | ✓v | ◐v | ◐v | ◐v | ◐t | ✗v |
| Muse | ◐v | ✓v¹ | +v | ?v | ✓v | ◐v | +v | ✓v |
| Cursor (IDE and CLI) | ✓v | ✓v | ✓v | ✓v | ✓s | ◐s | ✓v | ✓t |
| Copilot CLI | ✓v | ✓v | ✓v | ?s | ✓s | ◐v | ✓v | ✗v |
| OpenCode | ✓v | ✓v¹ | ✓v | ✓v | ✓s | ✓v | ✓v | ✗v |
| Pi | ◐v | ✓v¹ | ✓v | —s | ✓v | ◐v | ✓v | ✗v |
| Oh My Pi | ✓v | ◐v | ✓v | ✓v | ✓v | ◐v | ✓v | ✗v |
| Hermes | ◐v | +v | ◐v | —s | ◐s | —s | ◐v | +s |
| OpenClaw | ◐s | —s | —s | +d | —s | +d | —s | +s |
| DeepSeek Harness | ✓s | ✗s | +v | +s | ✗s | +s | +v | ✗v |
| ZCode | ✓s | ◐v | +v | ◐s | ✓s | +s | +v | ✓s |
| CodeBuddy | ◐s | ◐v | ◐s | ◐s | ◐s | ◐v | ◐s | ✓v |
| WorkBuddy | ?s | ?t | ?t | ?s | ?s | ✗s | ?t | ? |
| JoyCode | ✓s | ✓v | +s | ✗s | —s | +s | ◐s | ? |
| Kiro | ✓d | ◐d | +d | ✓d | ◐d | ✓d | ◐d | ? |
| Qoder, Qoder CN (CLI) | ✓s | ✓v | +s | ✓s | ✓s | ◐s | ◐s | ✓s |
| Trae, Trae CN | ✓d3 | ✓t | +d3 | ?t | ?t | ◐d3 | ? | ? |
WorkBuddy's cells are ? because teamai contradicts itself. The engine uses one projectDirName for project rules, skills, agents and settings. teamai writes project rules to .codebuddy/ and the rest to .workbuddy/. The WorkBuddy app's product.json settles it.
claude-internal, tclaude, codex-internal and tcodex are not installed. teamai writes their project files under <root>/.claude-internal/ and <root>/.codex-internal/. Claude Code keeps its project directory at .claude when CLAUDE_CONFIG_DIR moves (verified), and Codex's project layer is fixed at .codex (source). Their project files are probably unread unless the base tool is also enabled.
Self scope
In self mode, skills, rules, instruction blocks, agents and MCP use the same project paths as workspace scope, and a linked worktree gets every file through the post-checkout delivery (verified). Only hooks move, from $HOME into the project:
| Tool | Hooks in self scope |
|---|---|
| Claude Code | ◐v. .claude/settings.local.json. Claude 2.1.303 skips every hook until workspace trust is accepted (source). -p runs them (verified) |
| Codex | ◐v. The HOME dispatcher plus .codex/hooks.json. A linked worktree runs the main checkout's .codex/hooks.json and ignores its own (verified) |
| Muse | ◐v. <root>/.muse/hooks.json runs only in a trusted workspace. No hook ran without --trust-workspace |
| Cursor | ✓s. <git root>/.cursor/hooks.json |
| Copilot CLI | ?s. .github/hooks/teamai.json. Whether repo hooks wait for folder trust is not traced |
| Qoder | ◐s. <cwd>/.qoder/settings.json, skipped when the cwd is $HOME, shared with Qoder CN |
| CodeBuddy | ◐s. Project settings are read from the session cwd only |
| WorkBuddy | ?t |
| ZCode | ✗v. teamai writes <root>/.zcode/cli/config.json, which ZCode never reads. doctor's fix (teamai hooks inject) writes nothing in self mode |
| Kiro | ◐d. Inside teamai-rendered agents only, as in workspace scope |
| claude-internal, tclaude | ?t. teamai writes <root>/.<build>/settings.json. Stock Claude keeps .claude as its project directory when its user config moves. These builds' own lookup is not verified |
| OpenCode, Oh My Pi, Pi, dsh, Hermes, OpenClaw | as user scope: the HOME plugin, extension or hook |
Self mode also appends team hooks into tracked hook files for Cursor, Qoder, CodeBuddy, WorkBuddy, Muse, ZCode and the internal builds, so the working tree is dirty after every pull (verified).
teamai's own AI
| Tool | AI backend (ai-client) |
Votes judge | HTTP local agent hooks |
|---|---|---|---|
| Claude Code | ✓v | ✓v | ✓v |
| Codex | ✓v | +v | ✗v |
| Muse | +v | +v | ✗t |
| Cursor | +s | +s | — (refused) |
| Copilot CLI | +s | ?s | — |
| OpenCode | +v | ?t | ◐t |
| Pi | +v | ?t | ?t |
| Oh My Pi | +s | ?t | — |
| ZCode | +s | +s | ✗v |
| CodeBuddy | ?s | +v | ✓s |
| Qoder | ?s | ?s | ✓s |
| Qoder CN | ? | ? | ✗t |
| Hermes | +s | — | ✗v |
| OpenClaw | ✗s | — | ◐s |
| DeepSeek Harness | +s | ?s | — |
The AI backend is the CLI teamai runs for enrich, codebase, import, maintenance and the votes judge. ai-client tries claude, the internal builds, codex, codebuddy, workbuddy and openclaw. openclaw -p is not an OpenClaw command (its headless mode is openclaw agent --message). The votes judge parses Claude transcripts only. Codex, Muse and ZCode send last_assistant_message in their Stop payload, which the judge could read instead.
Conditions that change a cell
In each case doctor still reports the resource delivered, unless the row says otherwise.
| Condition | What stops being read | Evidence |
|---|---|---|
| Session started in a project subdirectory | Pi: skills, MCP. OMP: rules, MCP. CodeBuddy and WorkBuddy: rules, blocks, skills, agents, hooks, MCP. ZCode: agents | v for Pi, OMP and CodeBuddy MCP. s for the rest |
XDG_CONFIG_HOME set |
OpenCode and Muse: every user resource. Copilot 0.0.4xx: every user resource | v, s for Copilot |
Tool home moved by env or profile: PI_CODING_AGENT_DIR; OMP_PROFILE or --profile; CODEBUDDY_CONFIG_DIR; DSH_HOME (skills); OPENCLAW_HOME; OPENCLAW_PROFILE without --profile; a Hermes profile |
every user resource of that tool, and only skills for DSH_HOME |
v for Pi, OMP and CodeBuddy. s for the rest |
CLAUDE_CONFIG_DIR outside $HOME |
every Claude user resource. doctor already reports it (toolRootRejection) |
v |
| Project or workspace not trusted, or project MCP not approved | Codex: project agents. Muse: project skills, MCP, delegation, self-scope hooks. Pi: project skills and MCP. Claude: self-scope hooks in an interactive session. Copilot: project MCP. Claude, CodeBuddy, Qoder, Cursor: project MCP until approved | v for Codex, Muse, Pi, Copilot, Claude -p, CodeBuddy. s for the rest |
$HOME spelled differently from the path teamai trusted (a symlink, /tmp against /private/tmp) |
Codex: every teamai hook | v |
| The bash login profile prints to stdout | ZCode and dsh: the hook reply. doctor's ZCode probe accepts a substring | v |
| Native Windows | Codex: hooks, if it ignores commandWindows (#1215, reported on 0.142.5). Hermes: its home is %LOCALAPPDATA%\hermes. OpenClaw: spawn('teamai') cannot start teamai.cmd. Claude without Git Bash: hooks. CodeBuddy and WorkBuddy: session directory encoding. HOME different from %USERPROFILE%: Pi, OMP, ZCode, Codex and Claude use os.homedir() |
s, d |
The team's teamai.yaml sets toolPaths for any tool |
every tool not listed loses its paths, and the listed tools freeze (#1185, fix in #1204) | v |
| Other Hermes plugins fill the shared 8,000-character section budget | Hermes workspace blocks | v |
~/.pi/agent/AGENTS.override.md exists |
Pi user blocks | v |
| OpenClaw's gateway runs as a launchd or systemd service | hooks when teamai is off the service PATH, and env |
s, d |
Other findings
These are not covered above. Each becomes an issue when someone picks it up.
- Muse rewrites
settings.jsonand drops"type": "stdio", so the next pull stops managing the server. (fix(mcp): retain Muse ownership after inferred stdio saves #1319) - Hermes: a hook path with a space breaks, because Hermes runs
shlex.spliton the unquoted command. (fix(hermes): quote managed hook script paths #1311) - OpenClaw: the hook runs
spawn('teamai')without a shell, which fails on Windows and whenteamaiis off a service's PATH. (fix(openclaw): launch hooks through the installed Node entry #1307) - CodeBuddy and WorkBuddy session directories are encoded differently on Windows (
C:\Work\projbecomesC-Work-projin CodeBuddy andWork-projin teamai). (fix(session): preserve Buddy drive letters in project directories #1315) - Kiro hooks fire only inside agents teamai rendered. Kiro CLI V3 also reads standalone
~/.kiro/hooks/*.json. (in review: fix(kiro): use standalone V3 session-start hooks #1341) - JoyCode agents need the
.joycode/agent.jsonindex. - Pi user instruction blocks can go to
AGENTS.mdwhile an existingAGENTS.override.mdtakes precedence. (fix(pi): deliver user context to active override files #1340) - Self scope leaves the working tree dirty, because team hooks go into tracked hook files. Claude moves them to
settings.local.jsonwhensharing.gitExcludeis on (feat: keep the resources teamai delivers in project scope out of git (#915) #1000). The other tools do not. - Self scope leaves
.claude-internal/CLAUDE.mdand.tclaude/CLAUDE.mdneither committed nor excluded. (fix(git-exclude): classify internal Claude instruction files #1318) -
ai-clientrunsopenclaw -p, which OpenClaw does not accept. (fix(ai): invoke OpenClaw through its agent command #1308) - The internal builds' project files are probably unread.
-
teamai pullfrom a directory that was removed throwsuv_cwdinmaybeMigrate(src/index.ts) before anything else runs. This is also onmain. Hook dispatch from such a directory works ([bug] hook events after a worktree is deleted fall back to the user scope #810). (fix(cli): explain unavailable working directories before migration #1309) - A file where Hermes's
skillsdirectory should be stops the user scope withENOTDIR. The old copies are kept. (fix(skills): isolate files blocking tool skill directories #1310) - ZCode and dsh drop the hook reply when the login profile prints to stdout, and doctor's ZCode probe accepts a substring. (in review: fix(hooks): keep ZCode replies free of login shell output #1316, fix(hooks): run POSIX DSH hooks without login profiles #1317)
- doctor checks no shared budget: Hermes's 8,000-character plugin sections, SOUL.md and OpenClaw
AGENTS.mdtruncation at 20,000, ZCode's 32 KB hook stdout. Covered: OpenClawAGENTS.md(fix(doctor): report OpenClaw bootstrap file truncation #1332). In review: Hermes SOUL.md (fix(doctor): check the complete Hermes SOUL read budget #1331) and ZCode hook stdout (fix(doctor): detect oversized ZCode hook rule replies #1343). - Stale comments: OpenCode 2.0.30 does apply
instructions(instruction-targets.ts:646), Hermes reads trusted<root>/.hermes/skills(source.ts~1494), Pi has noPI_CONFIG_DIR(pi-hooks.ts:19). - OpenCode: the plugin runs
spawn('teamai')without an absolute path (src/opencode-hooks.ts:119), so it fails whenteamaiis off the service's PATH. fix(openclaw): launch hooks through the installed Node entry #1307 fixed the same thing for OpenClaw. (in review: fix(hooks): launch OpenCode through the installed Node entry #1336) - Pi and Hermes still start
teamaifrom PATH: Pi's extension spawnsteamai(src/pi-hooks.ts:85,:122), and Hermes's plugin usesshutil.which("teamai") or "teamai"(src/hermes-hooks.ts:89). Both fail whenteamaiis off the PATH of the process that launched the tool. OpenClaw (fix(openclaw): launch hooks through the installed Node entry #1307) and OpenCode (fix(hooks): launch OpenCode through the installed Node entry #1336) embed the Node binary and CLI entry instead. - With a file where Hermes's
skillsdirectory should be,teamai doctorprints the skip warning but exits 0 with "All checks passed!" and still shows "hermes skills are where the last pull wrote them". It reads the samedeliveryTargets, which skip the blocked tool.teamai uninstall --forcestops withENOTDIRon the same file (src/uninstall.ts:651). (in review: fix(doctor): report blocked skill directories and preserve them on uninstall #1335) -
ai-clientbuilds its error from stderr only (src/utils/ai-client.ts:154), so a failureopenclaw agentreports on stdout surfaces asAI call failed:with no reason. Each call also leaves a new OpenClaw session behind (fix(ai): invoke OpenClaw through its agent command #1308). Error diagnostics fixed in fix(ai): preserve stdout errors and exit diagnostics #1342; session cleanup remains open. -
teamai hook-dispatchfrom a directory that was removed exits 0 but only warnsuv_cwdand runs no handler. Theuv_cwditem above (fix(cli): explain unavailable working directories before migration #1309) coverspullonly. (in review: fix(hooks): recover dispatch from a removed working directory #1337) - Muse: after fix(mcp): retain Muse ownership after inferred stdio saves #1319, any pull that writes
settings.jsonalso adds"type": "stdio"to Muse-saved entries it did not change, becauseapplyJsonnormalizes the map it then writes (src/mcp-reconcile.ts). The ownership hash (entryHash,src/resources/mcp-format.ts:361) also depends on key order. If a real Muse save reorders keys,uninstallstill keeps the server. Not checked with a real Muse save. (fix(mcp): preserve Muse-saved entries during reconciliation #1345, fix(mcp): keep Muse ownership across JSON key reordering #1346) - CodeBuddy replaces a project directory name over 255 bytes with a 180-byte prefix,
-and a djb2 hash (compressWorkspacePathNamein its bundle).encodeCwdCodeBuddy(src/session-flow/fs.ts) does not, so sessions of a project with a long path are not found. Not run with a long path. (fix(session): match CodeBuddy long project directory names #1347) -
skill-data/setup/references/manage-admin.md("Keep delivered files out of git") does not list.claude-internal/CLAUDE.mdand.tclaude/CLAUDE.md, which pull excludes since fix(git-exclude): classify internal Claude instruction files #1318.
Activity
SaulMoro commentedon Oct 20, 2026
A1 is up as a draft: #1245 (after #1204).
TOOLS(src/tools.ts) holds each tool's home and how its agent loads a skill, each rule with its evidence:verified,source,docsorassumed.post-tool-usepass for every tool: the skill tool, a read of an installedSKILL.md(shell reads included),/name, Codex$name, and the block Pi expands/skill:nameinto. Each skill counts once per turn.Skillhook andSKILL_TOOL_MATCHERare gone. OpenCode sends raw tool names.New evidence, reflected in the Matrix above:
cat <path>/SKILL.mdor a$namemention, both in one turn under the sameturn_id.ReadofSKILL.md.skillwith{ id }./skill:nameinto the prompt before extensions see it.Open: Cursor may count once per conversation (
generation_idequaledconversation_idin the one capture), and hosts with no turn id can lose a credit when a detached hook lands after the next prompt.feat(tools): add a per-tool descriptor with home and skill-load evide…
feat(tools): track skill use from tool calls, path reads, and mentions (
feat(tools): track skill use from tool calls, path reads, and mentions (
feat(tools): add a per-tool descriptor with home and skill-load evide…
feat(tools): track skill use from tool calls, path reads, and mentions (
feat(tools): per-tool descriptor and skill-use tracking for every tool (
feat(locate): resolve where every tool takes skills, and record homes…
ci: run the locate matrix, Windows-safe tests and hook shells on a Wi…
SaulMoro commentedon Oct 21, 2026
A1 merged in #1245. A2 and E are in #1252, on top of it.
A2.
locatefor skills.locate(tool, 'skills', install, host)answers where each tool takes skills, for every tool, scope and platform.skillsDirForToolis gone.~/.teamai/tool-homes.json:XDG_CONFIG_HOMEfor OpenCode and Muse,OPENCODE_CONFIG_DIR,HERMES_HOME,PI_CODING_AGENT_DIR,COPILOT_HOME,DSH_HOME, OpenClaw's state dir and profile, and Hermes's Windows default.E. Windows job.
windows-latestjob runs the matrix and an allow-list of Windows-safe tests. A guard test fails a listed file that gains a POSIX-only construct.hook-dispatch session-startper hook shell.Verified. Every check below used the built CLI with an isolated
HOME:$XDG.Not verified.
windows-latestrunner: 632 unit tests and one real session-start per hook shell. No Windows desktop host has run it.gitlabandgithubproviders are left to CI.Updated in the body above.
locatesignatures as shipped.uv_cwd, and a file at Hermes's skills path stops the scope withENOTDIR.33 remaining items
fix(ai): invoke OpenClaw through its agent command (#1308)
fix(cli): explain unavailable working directories before migration (#…
fix(openclaw): launch hooks through the installed Node entry (#1307)
fix(skills): isolate files blocking tool skill directories (#1310)
fix(session): preserve Buddy drive letters in project directories (#1315
fix(git-exclude): classify internal Claude instruction files (#1318)
fix(hermes): quote managed hook script paths (#1311)
fix(mcp): retain Muse ownership after inferred stdio saves (#1319)
fix(session): match CodeBuddy long project directory names
perf(ci): cut the E2E critical path by 51% and the macOS unit leg by …
feat(locate): rules, blocks, agents, and MCP go through locate (#1221) (
fix(mcp): preserve Muse-saved entries during reconciliation (#1345)
fix(session): match CodeBuddy long project directory names (#1347)