Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ All notable changes to this project will be documented in this file. See [standa

### 🐛 Bug Fixes

- Team rules reach Codex, `codex-internal` and `tcodex`. Pull used to copy them to `.codex/rules/<name>.md`, which Codex does not read, and `teamai doctor` reported them delivered. In user scope they now go into a team-rules block of the tool's own `AGENTS.md` (`~/.codex/AGENTS.md`), beside the culture, shared-instructions and recall blocks. In a project the session-start hook adds the project's rules and those blocks to each session, and to a spawned subagent through a new `SubagentStart` entry, with `additionalContextLimit: 0` so Codex keeps them whole; pull leaves the project `AGENTS.md`, which other tools read too, unchanged. Pull removes the `.md` copies earlier pulls left in `.codex/rules/`, keeping an edited one with a warning. Pull and uninstall keep a removed rule's legacy copy unless it matches its recorded delivery hash; a missing record proves nothing about local edits. A path-scoped rule is rendered without frontmatter after an `Applies to files matching: <globs>` line, in Hermes' `SOUL.md` too. `teamai doctor` checks the user-scope block and, in a project, both hook entries' limit. The public Codex asks once to approve the changed teamai hooks (for [#938](https://github.com/Tencent/teamai-cli/issues/938)).
- Team rules reach Codex, `codex-internal` and `tcodex`. Pull used to copy them to `.codex/rules/<name>.md`, which Codex does not read, and `teamai doctor` reported them delivered. In user scope they now go into a team-rules block of the tool's own `AGENTS.md` (`~/.codex/AGENTS.md`), beside the culture, shared-instructions and recall blocks. In a project the session-start hook adds the project's rules and those blocks to each session, and to a spawned subagent through a new `SubagentStart` entry, with `additionalContextLimit: 0` so Codex keeps them whole; pull leaves the project `AGENTS.md`, which other tools read too, unchanged. The hook omits blocks already present in the active project instructions file, reading `AGENTS.override.md` before `AGENTS.md`. Pull removes the `.md` copies earlier pulls left in the Codex rules directory at its recorded `toolRoots` location, including a publisher's bare local copy, keeping an edited one with a warning. Pull and uninstall keep a removed rule's legacy copy unless it matches its recorded delivery hash; a missing record proves nothing about local edits. A path-scoped rule is rendered without frontmatter after an `Applies to files matching: <globs>` line, in Hermes' `SOUL.md` too. `teamai doctor` checks the user-scope block and, in a project, both hook entries' limit. The public Codex asks once to approve the changed teamai hooks (for [#938](https://github.com/Tencent/teamai-cli/issues/938)).
- `teamai pull` names the skills it removes because they are no longer delivered here, in one line, instead of a `debug` line calling them excluded and a summary that says `No resources to sync`. Picking a role or project, or an admin adding `manifest/projects.yaml`, takes root skills away, since the root `skills/` is then the tag catalog; the line then says that `teamai tags subscribe <tag>` brings one back. A namespace skill removed because its namespace is no longer active is named too. The usage guide and the multi-project design no longer say a member with no project still gets `common` or every root item (for [#911](https://github.com/Tencent/teamai-cli/issues/911)).
- `teamai pull` keeps the `teamai tags subscribe <tag>` recovery line when the skill directory it removes is byte-identical to an inactive namespace copy: that copy made the namespace cleanup phase remove the directory first, and the hint was lost, because pull inferred whether a root skill had left from which phase did the removing. The hint now follows the repo — it appears exactly when the team repo holds the removed skill at the root, the copy a tag delivers — so a namespace-only skill removed on deactivation is still named without the hint. The usage guide no longer says a member with no role gets no skills at all, in English or Chinese: root skills still arrive through a tag (review of [#917](https://github.com/Tencent/teamai-cli/pull/917)).
- `teamai pull` keeps a skill, rule or agent you changed since teamai delivered it instead of overwriting it, and names it: `Kept <path>: you changed it since teamai delivered it`, with a warning when the team version has changed since, which `teamai push` repeats for that copy, since the SessionStart pull is silent. Pull records the sha256 of what it writes at each path in the checkout's record, and the pre-push sync records its writes too, so a copy counts as changed only against that record; a skill counts as one copy, and files only you added do not count. The copies of other tools still update. `--force` keeps these copies, `--dry-run` prints `Would keep <path>`, and a copy of an item the team removed stays when you changed it. To take the team version, delete your copy and run `teamai pull --force`. There is no record before the first full pull on this version, or in a new worktree, so that pull overwrites as before. A file you added to a skill at a path another team version of it has is no longer named on every pull, and `teamai doctor` no longer fails a rules or agents check, or points at `teamai pull --force`, for a kept copy: it lists one as `changed by you (kept by pull)` beside any other problem (for [#822](https://github.com/Tencent/teamai-cli/issues/822)).
Expand Down
6 changes: 3 additions & 3 deletions docs/usage-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -962,11 +962,11 @@ teamai push

Most tools get one file per rule in their rules directory. Codex, `codex-internal` and `tcodex` read no rules directory (`.codex/rules/` holds Codex's own `*.rules` command policies), so `pull` writes no rule file for them. In user scope the team rules go into a `<!-- [teamai:team-rules:start] -->` block of the tool's own `AGENTS.md` (`~/.codex/AGENTS.md`, `~/.codex-internal/AGENTS.md`, `~/.tcodex/AGENTS.md`; a `toolRoots` entry moves it), which only that tool reads. In a project their session-start hook adds the project's team rules to each session instead: the project `AGENTS.md` is the owners' file, and other tools with a rules format of their own read it too. Hermes gets the same text in its `SOUL.md` block. Frontmatter is dropped, so a rule with `paths:` applies everywhere there, led by an `Applies to files matching: <globs>` line. Codex runs the hook again after a compaction or a clear, and adds nothing when it resumes a session, which already holds the rules. A subagent Codex spawns gets them through the `SubagentStart` hook. The public Codex runs a new or changed hook only after you approve it in `/hooks`, and until then it gets no project rules.

The culture, shared-instructions and recall blocks follow the same split. In user scope they go to that same `AGENTS.md`, and your own content outside the markers is kept. In a project the session-start hook adds them with the rules, and `pull` leaves the project `AGENTS.md` unchanged. When another tool, such as Pi, already wrote a block into the project `AGENTS.md`, which Codex reads too, the hook leaves that block out.
The culture, shared-instructions and recall blocks follow the same split. In user scope they go to that same `AGENTS.md`, and your own content outside the markers is kept. In a project the session-start hook adds them with the rules, and `pull` leaves the project `AGENTS.md` unchanged. The hook leaves out a block already present in the active project instructions file. Codex reads `AGENTS.override.md` when it exists, otherwise `AGENTS.md`, so a block in a shadowed `AGENTS.md` still reaches Codex through the hook.

> A `toolPaths` in the team `teamai.yaml` replaces the built-in defaults whole. A team that sets it should give each Codex-family entry `userScope.claudemd: .codex/AGENTS.md` (`.codex-internal/…`, `.tcodex/…`) for the user-scope rules and blocks, and drop its `rules` path, since Codex never reads that directory. A top-level `claudemd` would put the blocks back in the project `AGENTS.md`, so leave it out. In a project the hook needs only the entry's `settings` path, where it is installed.

> Upgrading from a release that copied rules to `.codex/rules/`: the next `pull` removes the `.md` copies teamai delivered there, including `teamai-recall.md`. A copy you edited is kept and named in a warning, and the `*.rules` files are never touched. A copy of a rule the team has since removed is deleted only if it matches its recorded delivery hash; without that record, it is kept and named too. The same pull adds `additionalContextLimit: 0` and a `SubagentStart` entry to the teamai hooks in `hooks.json`, so the public Codex asks you once to approve the changed hooks.
> Upgrading from a release that copied rules to `.codex/rules/`: the next `pull` removes the `.md` copies teamai delivered there, including `teamai-recall.md`. Cleanup follows the recorded `toolRoots` location and checks both a publisher's bare local filename and its namespaced copy. A copy you edited is kept and named in a warning, and the `*.rules` files are never touched. A copy of a rule the team has since removed is deleted only if it matches its recorded delivery hash; without that record, it is kept and named too. The same pull adds `additionalContextLimit: 0` and a `SubagentStart` entry to the teamai hooks in `hooks.json`, so the public Codex asks you once to approve the changed hooks.

### Env, hooks and MCP servers by namespace

Expand Down Expand Up @@ -2762,7 +2762,7 @@ What gets removed:
- teamai hooks in AI tool settings
- The teamai blocks in CLAUDE.md and AGENTS.md (your own content is preserved)
- Team-synced skills, including OpenClaw workspace skills (your own skills are preserved)
- Team-synced rules, including the copies older releases left in `.codex/rules/`, also of rules the team has since removed. A copy there you edited is kept and named in a warning. A removed rule's copy is deleted only if it matches its recorded delivery hash; without that record, it is kept and named too. Codex's `*.rules` files are kept
- Team-synced rules, including the copies older releases left in `.codex/rules/`, also of rules the team has since removed. Cleanup follows the recorded `toolRoots` location and the publisher's local filenames. A copy there you edited is kept and named in a warning. A removed rule's copy is deleted only if it matches its recorded delivery hash; without that record, it is kept and named too. Codex's `*.rules` files are kept
- Team-synced custom agents and CLI built-in agents (your own agents are preserved)
- The env block in your shell profile — every candidate file (`.zshrc`, `.bashrc`, `.bash_profile`, `.bash_login`, `.profile`) carrying a block that sources this scope's own `env.sh` is cleaned, not only the one file `pull` would choose today; a block sourcing a different scope's `env.sh` is left alone
- The `~/.teamai/` directory
Expand Down
6 changes: 3 additions & 3 deletions docs/usage-guide.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -883,11 +883,11 @@ teamai push

大多数工具在自己的 rules 目录中为每条 rule 得到一个文件。Codex、`codex-internal` 和 `tcodex` 不读取 rules 目录(`.codex/rules/` 存放的是 Codex 自己的 `*.rules` 命令策略文件),因此 `pull` 不为它们写任何 rule 文件。user scope 下,团队 rule 写入该工具自己的 `AGENTS.md`(`~/.codex/AGENTS.md`、`~/.codex-internal/AGENTS.md`、`~/.tcodex/AGENTS.md`;`toolRoots` 条目可改变其位置)中的 `<!-- [teamai:team-rules:start] -->` 区块,只有该工具读取这个文件。在项目中,改由它们的 session-start hook 把项目的团队 rule 加入每个会话:项目 `AGENTS.md` 属于项目维护者,其他拥有自己 rules 格式的工具也会读取它。Hermes 的 `SOUL.md` 区块得到同样的内容。frontmatter 会被去掉,所以带 `paths:` 的 rule 在这里对所有文件生效,并以一行 `Applies to files matching: <globs>` 开头。Codex 在压缩上下文或 clear 之后会再次运行该 hook;恢复会话时不添加任何内容,因为会话中已包含这些 rule。Codex 启动的子 agent 通过 `SubagentStart` hook 获得它们。公开版 Codex 只在你于 `/hooks` 中批准新增或改动的 hook 后才运行它,在此之前不会得到项目的 rule。

culture、共享指令和 recall 区块采用同样的划分。user scope 下它们写入同一个 `AGENTS.md`,标记之外你自己的内容保持不变。在项目中,session-start hook 把它们与 rule 一起加入会话,`pull` 不改动项目 `AGENTS.md`。如果其他工具(如 Pi)已把某个区块写入项目 `AGENTS.md`,而 Codex 也会读取该文件,hook 就不再重复添加这个区块。
culture、共享指令和 recall 区块采用同样的划分。user scope 下它们写入同一个 `AGENTS.md`,标记之外你自己的内容保持不变。在项目中,session-start hook 把它们与 rule 一起加入会话,`pull` 不改动项目 `AGENTS.md`。hook 会省略项目当前生效的指令文件中已有的区块。Codex 在 `AGENTS.override.md` 存在时读取它,否则读取 `AGENTS.md`,因此被遮蔽的 `AGENTS.md` 中的区块仍会通过 hook 送达。

> 团队 `teamai.yaml` 中的 `toolPaths` 会整体替换内置默认值。设置了它的团队应为每个 Codex 系条目加上 `userScope.claudemd: .codex/AGENTS.md`(`.codex-internal/…`、`.tcodex/…`),用于 user scope 的 rule 和区块,并去掉其 `rules` 路径,因为 Codex 从不读取该目录。顶层的 `claudemd` 会把区块重新写进项目 `AGENTS.md`,所以不要设置。在项目中,hook 只需要该条目的 `settings` 路径,它安装在那里。

> 从把 rule 复制到 `.codex/rules/` 的旧版本升级后,下一次 `pull` 会删除 teamai 投递到那里的 `.md` 副本,包括 `teamai-recall.md`。你改过的副本会保留,并在警告中点名;`*.rules` 文件从不改动。团队此后已删除的 rule,其副本只有与记录的投递哈希一致时才会删除;没有该记录时也会保留并点名。同一次 pull 会为 `hooks.json` 中的 teamai hook 加上 `additionalContextLimit: 0` 和一个 `SubagentStart` 条目,因此公开版 Codex 会请你批准一次改动后的 hook。
> 从把 rule 复制到 `.codex/rules/` 的旧版本升级后,下一次 `pull` 会删除 teamai 投递到那里的 `.md` 副本,包括 `teamai-recall.md`。清理使用记录的 `toolRoots` 位置,同时检查发布者本地的无命名空间文件名及命名空间副本。你改过的副本会保留,并在警告中点名;`*.rules` 文件从不改动。团队此后已删除的 rule,其副本只有与记录的投递哈希一致时才会删除;没有该记录时也会保留并点名。同一次 pull 会为 `hooks.json` 中的 teamai hook 加上 `additionalContextLimit: 0` 和一个 `SubagentStart` 条目,因此公开版 Codex 会请你批准一次改动后的 hook。

### Env、hooks 与 MCP server 按 namespace 划分

Expand Down Expand Up @@ -2573,7 +2573,7 @@ teamai uninstall --agent claude
- AI 工具 settings 中的 teamai hooks
- CLAUDE.md 和 AGENTS.md 中的 teamai 块(保留用户自写内容)
- 团队同步的 skills,包括 OpenClaw workspace skills(保留用户自建 skills)
- 团队同步的 rules,包括旧版本留在 `.codex/rules/` 中的副本,团队此后已删除的 rule 的副本也包括在内。其中你改过的副本会保留,并在警告中点名。已删除 rule 的副本只有与记录的投递哈希一致时才会删除;没有该记录时也会保留并点名。Codex 的 `*.rules` 文件保留
- 团队同步的 rules,包括旧版本留在 `.codex/rules/` 中的副本,团队此后已删除的 rule 的副本也包括在内。清理使用记录的 `toolRoots` 位置和发布者本地的文件名。其中你改过的副本会保留,并在警告中点名。已删除 rule 的副本只有与记录的投递哈希一致时才会删除;没有该记录时也会保留并点名。Codex 的 `*.rules` 文件保留
- 团队同步的自定义 agents 和 CLI 内置 agents(保留用户自建 agents)
- Shell profile 中的 env 块——会清理每一个候选文件(`.zshrc`、`.bashrc`、`.bash_profile`、`.bash_login`、`.profile`)中、代码块指向本作用域自身 `env.sh` 的那些,而不仅仅是当前 `pull` 会选中的那一个;指向其他作用域 `env.sh` 的代码块不受影响
- `~/.teamai/` 目录
Expand Down
4 changes: 3 additions & 1 deletion skill-data/setup/references/uninstall.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ and give it your team repo URL."*

## Notes

- Uninstall keeps edited legacy Codex rule copies. For a rule the team has
- Uninstall cleans legacy Codex rule copies at the recorded `toolRoots`
location, including publishers' bare local filenames. It keeps edited copies.
For a rule the team has
removed, it deletes the copy only if its hash matches the recorded delivery.
Without that record, it keeps the copy and names it in a warning. Save any
changes you need, then delete the copy manually.
Expand Down
29 changes: 29 additions & 0 deletions src/__tests__/codex-hook-rules.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,35 @@ projects:
expect(text).toContain('Shared team instructions.');
});

it.each(['# Owners\n', ''])('adds blocks from a shadowed AGENTS.md when AGENTS.override.md contains %j', async (override) => {
await fse.outputFile(path.join(repoPath, 'culture.md'), '---\ncompany:\n name: Acme\n---\n\nBe kind to teammates.\n');
await fse.outputFile(path.join(repoPath, 'claudemd', 'shared.md'), 'Shared team instructions.\n');
localConfig = { ...localConfig, recallEnabled: true } as LocalConfig;
const agents = [
'<!-- [teamai:culture:start] -->', 'Be kind to teammates.', '<!-- [teamai:culture:end] -->',
'<!-- [teamai:claudemd:start] -->', 'Shared team instructions.', '<!-- [teamai:claudemd:end] -->',
'<!-- [teamai:recall-rules:start] -->', 'Team Knowledge Recall (teamai)', '<!-- [teamai:recall-rules:end] -->',
].join('\n');
await fse.outputFile(path.join(tmpDir, 'project', 'AGENTS.md'), agents);
await fse.writeFile(path.join(tmpDir, 'project', 'AGENTS.override.md'), override);

const text = await context({ hook_event_name: 'SessionStart', source: 'startup' });

expect(text).toContain('Be kind to teammates.');
expect(text).toContain('Shared team instructions.');
expect(text).toContain('Team Knowledge Recall (teamai)');
expect(await fse.readFile(path.join(tmpDir, 'project', 'AGENTS.md'), 'utf8')).toBe(agents);
expect(await fse.readFile(path.join(tmpDir, 'project', 'AGENTS.override.md'), 'utf8')).toBe(override);
});

it('skips a block already present in the active AGENTS.override.md', async () => {
await fse.outputFile(path.join(repoPath, 'culture.md'), '---\ncompany:\n name: Acme\n---\n\nBe kind to teammates.\n');
await fse.outputFile(path.join(tmpDir, 'project', 'AGENTS.override.md'),
'<!-- [teamai:culture:start] -->\nBe kind to teammates.\n<!-- [teamai:culture:end] -->\n');

expect(await context({ hook_event_name: 'SessionStart', source: 'startup' })).not.toContain('Be kind to teammates.');
});

it('adds no rules from a scope that does not enable the tool', async () => {
await userScope({ 'personal.md': 'The personal codeword is WREN-5.\n' }, 'enabledAgents: [claude]\n');
localConfig = { ...localConfig, enabledAgents: ['claude'] } as LocalConfig;
Expand Down
Loading
Loading