Skip to content

fix: auto-discover KNOWN_AGENTS for resource injection during pull - #394

Open
jeff-r2026 wants to merge 7 commits into
mainfrom
fix/pull-auto-discover-agents
Open

fix: auto-discover KNOWN_AGENTS for resource injection during pull#394
jeff-r2026 wants to merge 7 commits into
mainfrom
fix/pull-auto-discover-agents

Conversation

@jeff-r2026

Copy link
Copy Markdown
Collaborator

Summary

  • pullItem() in skills/rules/agents handlers only iterated scopedToolPaths() (= teamai.yaml toolPaths), missing tools like tclaude/tcodex that are in KNOWN_AGENTS and installed on disk
  • Added effectiveToolPaths() in types.ts that merges scopedToolPaths() with auto-discovered KNOWN_AGENTS entries whose install root (e.g. .tclaude/) exists
  • Updated all pull/sync call sites (skills, rules, builtin-skills, builtin-rules, builtin-agents, pull.ts) to use effectiveToolPaths()
  • Added tclaude and tcodex to SELF_MODE_AGENT_CHOICES for teamai init single-repo mode detection

Test plan

  • npx tsc --noEmit passes (no new type errors)
  • npx vitest run — all previously-passing tests still pass, updated 3 test files for new SELF_MODE_AGENT_CHOICES size
  • E2E: mkdir -p /root/gpu/.tclaude && teamai pull --force → skills/rules injected into .tclaude/skills/ and .tclaude/rules/
  • Build succeeds

🤖 Generated with Claude Code

@jeff-r2026 jeff-r2026 mentioned this pull request Sep 2, 2026
3 tasks
@jeff-r2026

Copy link
Copy Markdown
Collaborator Author

建议把这个问题提升为更明确的产品模型:不再由 teamai.yaml 决定支持哪些 Agent,而以当前作用域中实际安装的 Agent 为同步目标。

建议的职责划分:

  • user scope 扫描 HOME,project scope 扫描项目根目录,自动发现所有已安装的已知 Agent;
  • KNOWN_AGENTS / Agent Registry 只负责安装检测、资源能力声明,以及经过验证的 user/project 路径;
  • toolPaths 不再作为 Agent 白名单,可逐步废弃,或仅保留为企业内部发行版、自定义安装目录的 path override;
  • 新增 Agent 时只更新 Registry,不要求每个团队修改 teamai.yaml

当前 PR 的 auto-discovery 方向符合这个目标,但实现上建议补齐几个边界:

  1. 不要只从 skillsPath 推导 ${root}/rules${root}/agents${root}/CLAUDE.md。Registry 当前只保证 skills 路径,这些额外路径需要逐 Agent 验证并声明能力,不支持的资源应跳过。
  2. pull 可以面向所有检测到的有效 Agent;push 应单独定义边界,避免把 Agent 自带或个人 Skill 自动纳入团队候选。
  3. remove 只应删除 TeamAI 有管理记录的资源,不能仅因名称相同就在所有检测到的 Agent 中删除。
  4. enabledAgents / disabledAgents 需要同步简化:如果不再提供“选择支持哪些 Agent”的能力,可以只保留本地排除机制,或明确迁移语义。

建议增加以下回归测试:

  • 未配置 toolPaths 时,已安装的 .gemini.tclaude 等能够自动收到资源;
  • 每个 Agent 只收到 Registry 明确声明支持的资源类型;
  • 自定义安装路径仍可通过 override 工作;
  • 自动发现不会导致个人 Skill 被无提示上传或删除。

这样可以彻底解决“新增 Agent 需要更新团队配置”的问题,同时避免把路径猜测和双向同步安全边界混在一起。

@jeff-r2026

Copy link
Copy Markdown
Collaborator Author

复查最新提交 531eabc:之前提到的 push 扫描越界和未验证路径推导已经修复,但仍有一个可本地稳定复现的 P1。

[P1] pull 的 tombstone 清理仍会删除自动发现目录中的个人 Skill

SkillsHandler.removeItem() 虽然已经恢复使用 scopedToolPaths(),但 pullForScope() 的 tombstone 清理仍遍历 effectiveToolPaths()src/pull.ts 约 600 行),然后仅凭资源名称删除目录。

本地复现:

  1. teamConfig.toolPaths 只配置 Claude;
  2. 创建未由 TeamAI 管理的 ~/.gemini/skills/private-skill/SKILL.md
  3. 团队仓库的 skills/.removed 包含 private-skill
  4. 执行 pull({})
  5. ~/.gemini/skills/private-skill 整个目录被删除。

也就是说,直接调用 removeItem() 暂时保留了自动发现目录,但下一次 pull 仍会通过 tombstone 删除它。这里需要 managed-resource 记录,或者 tombstone 清理继续限制在明确可证明由 TeamAI 管理的路径,不能仅凭同名删除。

另外还有两个 auto-discovery 功能遗漏:

  • OpenCode user scope 的实际安装根是 ~/.config/opencode,但 effectiveToolPaths() 使用 KNOWN_AGENTS.skillsPath 的首段检测 ~/.opencode,因此当 team config 没有 OpenCode 条目时,正常用户级安装无法被自动发现;即使发现,registry 路径也没有经过 user-scope override。
  • AgentsHandler.pullItem()pullLegacyMd() 仍使用 scopedToolPaths()。例如 team config 只包含 Claude、但本地存在 .tclaude 时,skills/rules/builtin-agent 会自动下发,团队自定义 agent 却不会下发到 TClaude。

建议在合并前至少修复 tombstone 删除问题,并为“自动发现目录中存在同名个人 Skill”加入回归测试。

jeff-r2026 and others added 3 commits September 7, 2026 11:44
The pull flow (skills, rules, agents, builtin resources) only iterated
over tools explicitly listed in teamai.yaml toolPaths via scopedToolPaths().
Tools like tclaude/tcodex that are registered in KNOWN_AGENTS and installed
on disk (.tclaude/ exists) were silently skipped — seedProjectAgentRoot()
created their root directory, but pullItem() never wrote resources into it.

Add effectiveToolPaths() that merges scopedToolPaths() with auto-discovered
KNOWN_AGENTS entries whose install root exists on disk. Convention-based
paths (skills, rules, agents, claudemd) are derived from the agent's
skillsPath. Use this in all pull/sync call sites so any installed tool
receives team resources without requiring explicit teamai.yaml config.

Also add tclaude and tcodex to SELF_MODE_AGENT_CHOICES so they are
detected during `teamai init` in single-repo mode.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…anaged tools

Address PR review feedback:

1. Replace convention-based path derivation (deriveToolPaths) with a lookup
   into DEFAULT_TOOL_PATHS — a verified per-tool registry extracted from
   the schema defaults. Agents without a registry entry (e.g. gemini) receive
   only their skills path; rules/agents/claudemd are never guessed.

2. Restrict push (scanLocalForPush) and remove (removeItem) to
   scopedToolPaths() — only team-managed tools. This prevents personal
   skills in auto-discovered tool dirs from being silently uploaded, and
   ensures remove only deletes resources teamai has management records for.

3. Pull and sync paths (pullItem, pullAllRules, builtin-*) continue using
   effectiveToolPaths() so installed tools receive team resources.

4. Add regression tests covering: auto-discovery with verified paths,
   skills-only fallback for unregistered agents, custom path override
   precedence, disabledAgents, codebuddy CODEBUDDY.md preservation, and
   scopedToolPaths isolation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ull path

Address second round of PR review:

1. [P1] Tombstone cleanup and stale-skill cleanup in pull.ts now use
   scopedToolPaths() instead of effectiveToolPaths(). A personal skill
   in an auto-discovered dir (e.g. ~/.gemini/skills/private-skill) that
   shares a name with a tombstoned team skill is no longer deleted.
   Same fix applied to stale-rule cleanup in rules.ts.

2. OpenCode user-scope detection: effectiveToolPaths() now resolves the
   install-detection root from userScope paths when in user scope, so
   ~/.config/opencode is correctly detected (not just ~/.opencode).
   userScope path overrides are also applied to the returned paths.

3. AgentsHandler.pullItem() and pullLegacyMd() now use effectiveToolPaths()
   so auto-discovered tools (e.g. tclaude) receive team agent definitions.

4. Added regression tests for OpenCode user-scope auto-discovery.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@jeff-r2026
jeff-r2026 force-pushed the fix/pull-auto-discover-agents branch from bd9bc73 to c63b293 Compare September 7, 2026 05:13
@jeff-r2026

Copy link
Copy Markdown
Collaborator Author

本地 review 确认两个 P1 数据损失问题,建议修复后再合并。

审查版本:c63b293ffc1a61e933786c4958ad549ad9379493;对照基线:a991038b3104d45248767a218e9e003f8d38d55f

1. [P1] 角色清理会删除未被团队管理的个人技能

位置:src/pull.ts:229–242

复现条件与步骤:

  1. teamai.yamltoolPaths 仅配置 Claude。
  2. 配置 devops 两个角色,分别订阅 skills/devskills/ops;用户选择 dev
  3. 团队仓库存在 skills/ops/private-ops/SKILL.md
  4. 在隔离 HOME 下创建个人文件 ~/.gemini/skills/private-ops/SKILL.md,内容为 PERSONAL OPS NEVER SYNCED。该目录从未被 TeamAI 同步。
  5. 运行普通 teamai pull,不加 --force

实际结果:整个个人 private-ops 目录被删除。 相同脚本在基线版本运行后文件仍然存在。

原因:cleanupInactiveNamespaceSkills() 改为遍历 effectiveToolPaths(),将未配置的 Gemini 纳入删除范围,随后仅凭技能名称匹配 inactiveSkillNames 就递归删除,没有判断资源归属。建议保持原有清理范围,或先记录并验证资源确实由 TeamAI 部署。

2. [P1] 自动发现后的首次同步会直接覆盖同名个人技能

位置:src/resources/skills.ts:442–472

复现条件与步骤:

  1. toolPaths 仅配置 Claude,团队仓库存在 skills/deploy/SKILL.md,正文为 TEAM CONTENT
  2. 在隔离 HOME 下创建 ~/.gemini/skills/deploy/SKILL.md,内容为 PERSONAL UNIQUE CONTENT
  3. 运行普通 teamai pull,不加 --force

实际结果:个人文件被团队版本覆盖,正文变为 TEAM CONTENT 相同脚本在基线版本运行后仍为 PERSONAL UNIQUE CONTENT

原因:自动发现将 Gemini 纳入写入目标,随后 copyDir() 使用 overwrite: true,没有首次接管同名个人资源的冲突检查或备份。建议在自动发现目录首次接管资源时检查归属与冲突,避免直接覆盖个人内容。

验证情况

  • PR 与基线均执行 npm run build 成功。
  • 使用同一份 Python fixture 脚本,创建隔离 HOME、本地 Git upstream 和 clone,然后调用各版本构建出的真实 node dist/index.js pull;没有 mock,也没有使用 --force
  • 两个问题在 PR 版本复现,在基线版本均不复现。
  • npx vitest run src/__tests__/effective-tool-paths.test.ts:9 个测试通过,但未覆盖上述数据损失场景。

Address PR review P1 data-loss findings on effectiveToolPaths() pull:

1. [P1] Role cleanup (cleanupInactiveNamespaceSkills in pull.ts) iterated
   effectiveToolPaths(), so a personal skill in an auto-discovered dir
   (e.g. ~/.gemini/skills/private-ops) sharing a name with an inactive
   team namespace was deleted. Restrict it to scopedToolPaths(), matching
   the tombstone/stale cleanup scoping already applied — deletion only
   touches team-managed tools.

2. [P1] First-time takeover (SkillsHandler.pullItem in skills.ts) used
   copyDir(overwrite) unconditionally, clobbering a personal same-named
   skill in an auto-discovered dir. Skip the write when the tool is only
   auto-discovered (not in scopedToolPaths) and the destination already
   exists; team-managed tools still overwrite as before.

pull into installed tools still uses effectiveToolPaths(), so tclaude
etc. keep receiving team resources. Added regression tests for both
data-loss scenarios in pull-tombstone.test.ts.

Note: rules/agents pullItem have the analogous overwrite pattern; left
for a follow-up per reviewer scoping.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@jeff-r2026

Copy link
Copy Markdown
Collaborator Author

已针对本轮 review 的两个 P1 数据丢失问题修复,并 rebase 到最新 main(head 现为 8dfd356)。

修复

P1-1 角色清理删除个人技能src/pull.ts cleanupInactiveNamespaceSkills
改为遍历 scopedToolPaths() 而非 effectiveToolPaths()——删除操作只作用于团队管理的工具目录,与本 PR 第 3 个提交已对 tombstone / stale 清理做的处理保持一致(当时漏了角色清理这一处)。自动发现目录(如 ~/.gemini/skills)中的个人技能不再被按名删除。

P1-2 首次接管覆盖个人技能src/resources/skills.ts pullItem
下发仍用 effectiveToolPaths()(保证已安装的 tclaude 等仍能收到资源)。新增判断:对仅由自动发现纳入(不在 scopedToolPaths() 中)的工具,若目标同名资源已存在,则跳过写入不覆盖;团队管理的工具行为不变,仍正常覆盖更新。由于代码中没有资源归属记录,无法可靠区分个人 / 团队资源,故采用「同名已存在则跳过」这一最小且安全的策略(用户如需强制接管可用 --force)。

验证

  • 回归测试:在 pull-tombstone.test.ts 新增两个用例覆盖上述两个数据丢失场景,并逐一验证有效性(回退修复后用例转红)。
  • 全量 vitest run --no-file-parallelism 全绿。(说明:push-pending-prpush-role 并行同跑时会因既有的测试隔离问题相互污染,在 origin/main 上同样存在,与本 PR 无关;串行运行全部通过。)
  • 真实 CLI 端到端(本地 bare git provider,pull 不加 --force)复现 reviewer 的三个场景,均已不再复现:
    • P1-1:个人 .gemini/skills/private-ops pull 后内容完好,未被角色清理删除;
    • P1-2:个人 .gemini/skills/deploy pull 后仍为 PERSONAL UNIQUE CONTENT,未被 TEAM CONTENT 覆盖;
    • 回归:干净安装的 .tclaude/skills/deploy 正常收到团队 TEAM CONTENT

待跟进

rules / agentspullItem 存在同类的覆盖模式(copyFile / copyDir 覆盖)。本次按 review 的范围只处理了 skills 的两个 P1,rules/agents 是否需要同样的「自动发现目录不覆盖个人资源」保护,想听你的意见再决定是否本 PR 一并处理。

@jeff-r2026

Copy link
Copy Markdown
Collaborator Author

复查最新提交 8dfd3561c08fec3c97dca601650821a05334e830:之前报告的两个个人 skills 数据损失用例已通过,但仍有以下问题,建议修复后再合并。

1. [P1] 个人 rules 仍会被自动同步覆盖

位置:src/resources/rules.ts:168–195

此次防覆盖逻辑只加到了 skills,rules 仍直接写入自动发现的工具目录。

真实 CLI 复现:

  1. toolPaths 仅配置 Claude。
  2. 创建个人文件 ~/.tclaude/rules/safety.md,内容为 PERSONAL RULE CONTENT
  3. 团队仓库添加同名 rules/safety.md,内容为 TEAM RULE CONTENT
  4. 执行普通 teamai pull,不加 --force

结果:个人文件被覆盖为 TEAM RULE CONTENT。相同脚本在 PR 基线 a991038 上运行后仍为 PERSONAL RULE CONTENT

建议:将个人资源冲突保护覆盖到 rules;首次遇到未归属 TeamAI 的同名文件时保留文件并给出可见提示。也建议检查 agents 的对应写入路径是否需要相同保护。

2. [P2] “目标存在就跳过”会阻断团队技能后续更新

位置:src/resources/skills.ts:469–473

真实 CLI 复现:

  1. toolPaths 仅配置 Claude,安装目录 ~/.tclaude/ 存在,但尚无 deploy 技能。
  2. 首次 pull 将团队 deploy 技能部署到 TClaude,正文为 TEAM CONTENT
  3. 团队将该技能更新为 TEAM VERSION TWO 并提交。
  4. 再次执行普通 pull

结果:Claude 正常更新为 TEAM VERSION TWO,TClaude 仍为 TEAM CONTENT。这是 TeamAI 自己部署的资源,但由于目标目录已经存在,会被当成个人资源永久跳过。

建议:区分“预先存在的个人资源”和“由 TeamAI 部署的资源”,不能仅用目录是否存在来判断。可以在本地状态中记录部署目标及上次同步内容摘要:未记录的同名资源保留并提示;已记录且未被本地修改的资源允许更新;存在本地修改时保留并提示冲突。后续删除也应以实际部署归属为依据。

建议补充的回归测试

  • 自动发现目录中同名个人 rule 不被覆盖。
  • 自动发现目录首次部署成功后,团队第二次更新仍能同步。
  • 已部署资源被用户本地修改后,后续同步不会静默覆盖。

验证情况:最新版本 npm run build 成功;effective-tool-paths.test.tspull-tombstone.test.ts 共 24 个测试通过。上述两个问题使用隔离 HOME、本地 Git upstream/clone 和构建出的真实 CLI 复现,没有 mock,也没有使用 --force

…gents + updates)

Address round-2 review on the auto-discovery personal-resource protection:

1. [P1] Extend the personal-resource guard from skills to RULES and AGENTS
   (both the YAML-render and legacy-md agent paths). Previously only skills
   were protected; a personal ~/.<tool>/rules/<name> could still be
   overwritten on pull.

2. [P2] The prior skills guard skipped ANY pre-existing destination, which
   permanently blocked team UPDATES to a resource teamai itself deployed
   into an auto-discovered dir (2nd pull after a team edit never landed).
   Content comparison cannot fix this: after a team update the local copy
   is the old team version, indistinguishable by content from a personal
   resource. So track ownership instead.

Add state.autoDiscoveredManaged (keys <tool>:<type>:<name>) recording what
teamai deployed into auto-discovered tool dirs, plus a small helper module
(auto-discovered-ownership.ts). pullItem rule for non-scoped tools:
  - destination absent OR key tracked  -> write (and record ownership)
  - destination present but untracked  -> preserve personal resource + warn
Team-managed tools (scopedToolPaths) are unaffected and always overwrite.
State is loaded/saved lazily (only when a non-scoped tool is involved),
load-modify-save like the coAuthorManaged reconciler.

Regression tests: personal skill/rule preserved; teamai-deployed skill in
an auto-discovered dir still receives team updates on a later pull.

Note: gemini has no rules path in DEFAULT_TOOL_PATHS (skills-only auto-
discovery), so the rules guard is exercised via tclaude, not gemini.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@jeff-r2026

Copy link
Copy Markdown
Collaborator Author

已修复本轮的两个问题(head c36933f),并对方案做了修正说明。

方案修正:从「内容比对」改为「归属记录」

上一版我曾考虑用「本地内容 vs 团队源」比对来判断是否可覆盖。这个思路是错的,回归测试直接证伪了它:团队更新后,自动发现目录里的本地副本是旧的团队版,它既不等于新团队源、也和「个人资源」在内容上无法区分——单凭内容判不出「这是不是 teamai 部署的」。所以采用你最初建议的归属记录方案。

修复

[P2] 已部署资源后续更新被阻断 —— 新增 state.autoDiscoveredManaged(键 <tool>:<type>:<name>),记录 teamai 部署到自动发现目录的资源。pullItem 对非 scoped 工具的规则:

  • 目标不存在 已在归属记录里 → 写入(并记录归属)——团队更新能持续下发;
  • 目标存在但不在记录里 → 个人资源,保留 + log.warn 可见提示。

团队管理的工具(scopedToolPaths)行为不变,始终覆盖。state 仅在涉及非 scoped 工具时惰性 load/save,采用 load-modify-save,与 coAuthorManaged reconciler 同构。

[P1] rules / agents 同类覆盖 —— 把上述保护从 skills 扩展到 rules 和 agents(agents 覆盖 YAML 渲染路径和 legacy .md 路径)。个人同名 rule/agent 不再被覆盖。

新增 helper 模块 src/resources/auto-discovered-ownership.ts 承载归属判断/记录,三处 pullItem 共用。

验证

  • 回归测试:个人 skill / rule 保留;teamai 部署到自动发现目录的 skill,团队二次更新仍能同步(P2 核心);每个用例都通过「回退修复→测试转红」验证有效性。
  • 串行全量 vitest run --no-file-parallelism 全绿(2681)。(并行同跑时 push-pending-pr 与 push-role 因既有测试隔离问题相互污染,与本 PR 无关。)
  • 真实 CLI 端到端(本地 git provider,不加 --force)三个场景均 PASS:
    1. 个人 skill 保留、团队 skill 正常下发到 claude;
    2. 个人 rule 保留、团队 rule 下发到 claude;
    3. P2:首次部署到 gemini → 团队改为 TEAM VERSION TWO → 二次 pull,gemini 副本确实更新为 TEAM VERSION TWO,且 state.autoDiscoveredManaged 记录了 gemini:skills:tool1

一个说明

DEFAULT_TOOL_PATHS 里 gemini 只有 skills 路径、没有 rules,所以自动发现的 gemini 根本不会被 target 到 .gemini/rules/——该场景下个人 rule 是「从未被触碰」而非「经归属逻辑保留」。真正会走到 rules 归属逻辑的是有 rules 路径的工具(如 tclaude)。行为正确,一并说明。

@jeff-r2026

Copy link
Copy Markdown
Collaborator Author

复查 c36933f:此前报告的复现用例均已通过,个人 skills / TClaude rules 得到保留,自动发现工具的技能后续更新也恢复正常。但仍有一个 P1 个人文件删除问题,建议修复后再合并。

[P1] Cursor 旧格式清理绕过个人文件保护

位置:src/resources/rules.ts:194–213

复现步骤:

  1. teamai.yamltoolPaths 仅配置 Claude,未配置 Cursor。
  2. 在隔离 HOME 下创建个人文件 ~/.cursor/rules/safety.md,内容为 PERSONAL RULE CONTENT;不存在 safety.mdc,也没有该文件的 TeamAI 归属记录。
  3. 团队仓库添加 rules/safety.md,内容为 TEAM RULE CONTENT 并提交。
  4. 执行普通 teamai pull,不加 --force

实际结果:个人 ~/.cursor/rules/safety.md 被删除。 相同脚本在 PR 基线 a991038 上运行后,文件仍存在且内容为 PERSONAL RULE CONTENT

原因:mayWriteAutoDiscovered(dest, key, state) 检查的是 safety.mdc。该目标不存在,因此检查通过;随后写入 .mdc,并在第 213 行无条件删除同名 .md。对写入目标的归属检查没有保护实际被删除的另一个文件。

建议:对于自动发现的工具目录,旧格式清理也必须验证被删除文件本身的归属。不能仅因 .mdc 可以写入,就认为同名 .md 属于 TeamAI。对未记录归属的个人 .md 应保留,并补充“只有个人 .md、没有 .mdc”的回归用例。

验证:最新版本构建成功,effective-tool-paths.test.tspull-tombstone.test.ts 共 26 个测试通过。上述问题使用隔离 HOME、本地 Git upstream/clone 和构建后的真实 CLI,在最新版本与基线完成对照;没有 mock,也没有使用 --force

…ursor dir

Round-3 review P1: the old-layout `.md` cleanup after writing a cursor
`.mdc` rule ran unconditionally. In an auto-discovered (non-team-managed)
cursor-family dir, a personal `safety.md` with no `safety.mdc` was deleted
on pull — the ownership check gated the `.mdc` write target, not the `.md`
that actually got removed.

Only delete the same-named `.md` for team-managed tools (scopedToolPaths).
In an auto-discovered dir the `.md` may be a personal file, so it is never
removed there. Added a regression test (personal .md, no .mdc, cursor
auto-discovered → .md preserved after pull).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@jeff-r2026

Copy link
Copy Markdown
Collaborator Author

已修复该 Cursor .md 删除问题(head 062de8a)。

修复

[P1] Cursor 旧格式清理绕过个人文件保护 —— 根因如你所述:写 .mdc 后那句删同名 .md无条件的,而归属检查 mayWriteAutoDiscovered 只 gate 了写入目标 .mdc,没保护到实际被删的 .md

改法:那句删旧 .md 只对团队管理的工具(scopedToolPaths)执行。自动发现目录里的同名 .md 可能是个人文件,一律不删。

await writeFile(dest, teamRuleToCursorMdc(raw));
// 只有团队管理的工具才清理旧布局遗留的 .md;
// 自动发现目录里的同名 .md 可能是个人文件,不删。
if (scopedKeys.has(tool)) {
  await remove(path.join(destDir, `${item.name}.md`));
}

验证

  • 新增回归测试:自动发现的 cursor 目录,存在个人 safety.md、无 safety.mdc → pull 后个人 .md 保留;并验证有效性(回退成无条件删则测试转红)。
  • 串行全量 vitest run --no-file-parallelism 全绿(2682)。
  • 真实 CLI 端到端(本地 git provider,不加 --force)复现你的场景,PASS:
    • 个人 <work>/.cursor/rules/safety.md 保留 PERSONAL RULE CONTENT;
    • 团队 rule 作为 safety.mdc 额外下发(自动发现 cursor 收到团队 rule,符合预期);
    • 团队管理的 claude 侧正常收到 safety.md = TEAM RULE CONTENT

顺带确认:pullAllRules 里的 stale/legacy 清理本就只遍历 scopedToolPaths,自动发现的 cursor 不会在那条路径被触碰,无同类残留问题。

Round-4 review — extend the auto-discovered ownership record to the two
remaining paths that bypassed it:

1. [P1] Built-in skill deployment (builtin-skills.ts) copied with
   overwrite:true and no ownership check, so a personal same-named skill
   in an auto-discovered dir (e.g. .tclaude/skills/team-wiki-codebase) was
   clobbered. Now: in a non-team-managed tool dir, only overwrite a skill
   teamai deployed itself (tracked); an untracked pre-existing skill is
   preserved with a warning. Record ownership on deploy. (No localConfig =
   reporting-only path keeps legacy behavior; state is scope-bound.)

2. [P2] Tombstone cleanup (pull.ts step 3) only iterated scopedToolPaths,
   so a resource teamai deployed into an auto-discovered dir was never
   withdrawn when the team recalled it — a tombstoned (possibly dangerous)
   skill lingered in e.g. .tclaude and could still be loaded. Now, for
   auto-discovered tools, also remove tombstoned resources that teamai
   deployed (tracked in the ownership record) and drop them from the record.
   Untracked same-named personal files are still never deleted.

Add unmarkAutoDiscovered/isAutoDiscoveredTracked helpers. Regression tests:
personal built-in-named skill preserved; withdrawn teamai-deployed skill
removed from the auto-discovered dir on tombstone; personal same-named
skill NOT removed on tombstone.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@jeff-r2026

Copy link
Copy Markdown
Collaborator Author

已修复本轮两个问题(head 7a4d94c)。这两处都是把之前建立的归属记录贯彻到尚未覆盖的路径。

修复

[P1] 内置 skill 无提示覆盖个人文件 —— 内置 skill 部署(builtin-skills.ts deployBuiltinSkills)是独立于 pullItem 的代码路径,之前 copyBuiltinSkillDir(overwrite:true) 没接归属检查。现在对非 scoped 工具应用同样的规则:仅覆盖 teamai 自己部署过(有归属记录)的同名 skill;未记录的个人 skill 保留 + log.warn。部署后记录归属。(纯 reporting 路径无 localConfig、无 state 可查,保持原行为。)

[P2] 撤回的资源残留在自动发现目录(安全) —— tombstone 清理(pull.ts step 3)之前只遍历 scopedToolPaths。现在对自动发现工具也清理——但只删有归属记录的(teamai 确实部署过的),删后从归属记录移除;未记录的个人同名文件仍不动。这样团队撤回的(可能危险的)指令不会残留在 tclaude 等目录。

helper 补充 isAutoDiscoveredTracked / unmarkAutoDiscovered

验证

  • 回归测试:个人占用内置名的 skill 被保留;teamai 部署到自动发现目录的 skill 被撤回时能清除;个人同名(无记录)在撤回时被删。每个都验证有效性(回退→转红)。
  • 串行全量 vitest run --no-file-parallelism 全绿(2685)。
  • 真实 CLI 端到端(本地 git provider,pull --force)两个复现均 PASS:
    • P1:个人 .tclaude/skills/team-wiki-codebase 保留 PERSONAL WIKI CONTENT(有保留 warning),team-managed claude 侧正常收到内置 skill;
    • P2:首次部署 withdrawn 到 claude + tclaude → 团队从仓库删除并加入 .removed → 二次 pull,withdrawnclaude 和 tclaude 双双清除,state.autoDiscoveredManaged 清空;个人 team-wiki-codebase 全程未被触碰。

关于这几轮 review

这个 PR 的核心不变量是「自动发现的工具目录必须尊重个人资源(不删/不覆盖),但 teamai 自己部署的要能更新/撤回」。前几轮我是被逐个路径地指出问题、逐个修的。这轮我已把所有读/写/删自动发现目录的路径梳理了一遍(普通 pullItem、rules .mdc/.md、内置 skill 部署、tombstone 清理、stale/role 清理),归属记录现在在这些路径上一致生效。如果还有遗漏欢迎继续指出。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant