Repository navigation
fix(pull): give every machine the same copy of a same-name skill, and take deleted copies off the record - #1584
Conversation
…without a role (Tencent#1577) Without a role or project, pull listed skills/<name> and skills/<ns>/<name> in readdir order and kept the last of each name, so two machines on one revision could get different copies. Keep the shared copy, then namespaces in name order, and say which copy was skipped and how to get it.
…o get the skipped one (Tencent#1577) The Resources page labelled a namespace skill skipped for the shared copy 'Replaced by an active namespace skill', which is the opposite of what happened for a member with no role. Name the copy delivered instead, the roles that would deliver the skipped one (or that none does), and show the skills table as cards below 1100px like the hand-off and settings tables, so the delivery column is readable on a tablet or phone. pull reads the same roles from the resolver, so its line names the role to set instead of a placeholder.
…and lint for it (Tencent#1577) Pull deleted a copy it delivered and took it off the delivery record in two calls written at each site. Three sweeps (top-level skills, inactive role agents, replaced root rules) had only the delete, and so did the Codex dedupe, the inactive-namespace skill sweep and the leftover-file sweep, each leaving a stale entry behind. teamai remove deleted copies without saving the record at all, and uninstall --agent kept the entries of the copies it deleted. removeDelivered(ledger, dest) does both. Every pull-side removal goes through it, teamai remove saves the checkout record after its deletions, and uninstall --agent drops the entries of that tool's deleted copies. A full uninstall takes the record away with the checkout's teamai data. oxlint-plugin-teamai holds the repository's own lint rules, one file and one real-oxlint test per rule. teamai/remove-delivered fails a bare remove( in pull.ts and the skills, rules and agents handlers; a path pull never records carries a disable with its reason.
…20 (Tencent#1577) oxlint imports a jsPlugin with plain Node, which strips TypeScript only from 22.18, and CI lints on Node 20. The rules stay type-checked through JSDoc and checkJs; the lint floor is back to ^20.19 or >=22.12.
…Tencent#1577) Reclaiming a reserved rule copy and the tombstone cleanup on the fast path took entries off a ledger nothing saved, so the record kept naming the deleted copy. Both now edit the checkout's record, which is written only when it changed.
…ncent#1577) When the workspace's members disagree on a same-name skill, no copy is delivered, so no copy wins. The skipped line now also names the copy the workspace actually delivers.
…ent#1577) A role that receives two namespaces holding the same skill name stops at a delivery conflict, so it delivers neither copy.
|
No confident findings in the specified PR diff. The PR description satisfies the testing requirement: it records a build followed by representative real-CLI verification at Review was read-only; I did not run, build, or install PR code. |
SaulMoro
left a comment
There was a problem hiding this comment.
This was generated by AI during triage.
Reviewed at 10735bb against #1577, both parts. I ran the built CLI at this head and at main (cfd798e) on the same fixture (git provider, Claude, project scope):
| Case | main | this head |
|---|---|---|
No role, pull --force |
delivers skills/zzz/release, no message |
delivers the shared skills/release, one Skipped line per copy |
Following the advice (teamai roles set backend, pull) |
delivery record keeps an entry for a deleted copy | delivers skills/dev/release, record clean |
Team with no roles.yaml |
delivers skills/zzz/release |
the line says to rename and does not suggest teamai roles set (which fails there) |
teamai remove of a skill and a rule |
record keeps both deleted files | both entries dropped |
| Machine that had the namespace copy | — | "Already synced" keeps it until the next team commit, as the docs say; then pull switches it and the record matches |
Criteria 1-6 and the definition of done are met: every pull-side removal goes through removeDelivered, teamai/remove-delivered fails npm run lint with a message naming it, and the three record tests are unchanged. The CI review reported no findings; I agree.
The workflow and package changes are needed and safe: ci.yml runs npm run typecheck (the old tsc --noEmit plus the plugin), npm run lint is the same command, and @oxlint/plugins is a pinned dev dependency that is not shipped. Lint with the plugin passes on Node 20, 22 and 24. The Windows job runs no lint, so the plugin is not exercised there.
Non-blocking:
- [P3 nit] The rule covers the four listed files and only a call written
remove(;fse.remove,fs.rmor a delete in a new pull-side file still passes. - [P3 nit]
oxlint-plugin-teamai/README.mdsays helpers go inutils.ts; the file isutils.js. - [P3 nit] "To get it instead, set a role" does not say that a role also stops the shared root skills from being delivered by default.
Approving; this merges once CI is green at this head.
E2E shard 1/3 fails on a test this PR's behavior change reaches: This holds the merge until the shard is green. |
…cent#1577) The test let a pull with no role deliver z-archive's copy, which held only while the filesystem listed z-archive first. Pull now delivers the same copy on every machine, so the test writes the copy an older CLI could leave, with its record entry, and still checks that push sends the edit back there.
The PR description satisfies the testing requirement: it records a build and representative real-CLI verification at Review was read-only; I did not run, build, or install PR code. |
SaulMoro
left a comment
There was a problem hiding this comment.
This was generated by AI during triage.
Re-approving at 5fc5a14. The only change is the test fix for the E2E failure: it now writes the legacy z-archive copy with its record entry instead of getting it from pull, so it still covers push resolving a duplicate delivered from a later namespace.
The CI review's [P2 non-blocking] is right: src/resources/desired.ts:266 compares relativePath, so with skills/dev/release and skills/dev-prod/release and no shared copy, dev-prod wins (- sorts before /), while the docs order by namespace name. Comparing namespace first and keeping relativePath as the tie-breaker fixes it. It only affects two namespaces whose names share a prefix, so it does not block.
Merges once CI is green at this head.
…t#1577) Comparing full paths put skills/dev-prod/ before skills/dev/, since '-' sorts before '/'. The guide says the namespace whose name sorts first wins.
|
Fixed in 48beca1: |
SaulMoro
left a comment
There was a problem hiding this comment.
This was generated by AI during triage.
Re-approving at 48beca1: sharedFirst now orders by namespace name before the path, so skills/dev/release wins over skills/dev-prod/release, with a test for it. That closes the CI review's [P2 non-blocking]. Merges once CI is green at this head.
|
No confident new findings in the specified PR diff. The earlier namespace-ordering finding is resolved: the comparator now compares namespace names before relative paths. The PR description satisfies the testing requirement: it records a build followed by representative real-CLI verification at Review was read-only; I did not run, build, or install PR code. |
Takes main's teamai- prefix for default workflows, so the benchmark default is workflows/teamai-benchmark.yaml and a team override uses that name. Main's removeDelivered (Tencent#1584) replaces this branch's forgetDelivered calls in pull, agents and rules; the net pull diff against main is now only the benchmarks resource and the suite cache. The benchmark help and commands.md now name the workflow teamai-benchmark. Checked: tsc, lint, build; unit shards 1/2 and 2/2 (only the GitLab timeout tests fail, as they do on origin/main here); e2e benchmark-, workflow-from and dashboard-workflow-runs (21 files, 159 tests); workflow conformance as local (63), skill (53) and skill-bare (53).
Summary
A member with no role gets every skill under
skills/, namespaces included. Whenskills/releaseandskills/dev/releaseshare a name, which copy they got depended on the order the filesystem listed directories, so two machines on the same team could run different skills. Now the shared copy always wins,pullnames each copy it skipped and how to get it, and the dashboard shows the same.The second part makes deleting a delivered copy one operation.
removeDelivered(ledger, dest)deletes the copy and its entry in the checkout's delivery record, so the record never names a file that is gone. Every pull-side sweep, the already-synced cleanups andteamai removego through it, and a repository lint rule,teamai/remove-delivered, failsnpm run linton a bareremove(in pull's files.src/pull.ts, src/resources/{skills,rules,agents}.ts - await remove(dest); - if (ledger) forgetDelivered(ledger.hashes, dest); + await removeDelivered(ledger, dest); oxlint-plugin-teamai/ +├── index.js # registers teamai/<rule> +├── rules/remove-delivered.js # + .test.ts, run through real oxlint +└── test/harness.ts # ruleHarness(rule).valid / .invalidDashboard, Resources → Skills: the skipped copy says which copy is delivered instead and which roles would deliver it. Below 1100 px the table becomes cards.
Type of Change
Test Plan
Tested commit
10735bb5on macOS, Node v24.21.0 (lint also on v20.19.5), 2026-10-30.npm run typecheck(now also checksoxlint-plugin-teamai/) passesnpm run lintpasses on Node 24 and Node 20.19.5npx vitest runpasses exceptgitlab-mr-timeout, which fails the same way onmainRed on
main, green here:New tests:
oxlint-plugin-teamai/rules/remove-delivered.test.ts: 7 cases through real oxlint (each pull file reported with a message namingremoveDelivered,removeDeliveredallowed, other files and a reasoned disable allowed).desired-skills/workspace-children/dashboard-resources-skills: both listing orders, namespace vs namespace, the roles named, exclusions, a workspace that holds the name back.shared-skill-wins-1577(2) andremove-delivered-record-1577(7): pull exclusion sweep, already-synced tombstone cleanup,uninstall --agent claudewith CodeBuddy kept, full uninstall,teamai removefor skills, rules and agents. The existing record tests pass unchanged.push-active-namespaces› "resolves a legacy delivered duplicate…" built its legacy copy by letting a pull with no role deliverz-archive's copy, which held only while the filesystem listedz-archivefirst. It now writes that copy and its record entry, as an older CLI could leave them, and still checks that push sends the edit toskills/z-archive/a-skill(100 passed, at5fc5a14f; test-only change after10735bb5).git-exclude-uninstall-915,managed-resources-uninstall,remove-force,user-uninstall-keeps-projects-1025,namespaced-entries: 31 passed.Real CLI verification
npm run build, then a throwaway sandbox (providergit, agent Claude, project scope) againstdist/index.jsat10735bb5:Before, on
mainwith the samerelease/zzz/releaselayout, the member with no role gotskills/zzz/releaseon this machine.Related Issues
Closes #1577
Notes for Reviewers
uninstall --agent <tool>takes that tool's deleted copies off every checkout's record, in either scope, and keeps entries under a root another installed tool shares. A full uninstall deletes the record together with.teamai, so it has nothing to edit.npm run typecheckchecks them withcheckJs. The rule covers the files inPULL_SIDE_FILES; a new file that deletes delivered copies has to be added there (the plugin README says so).teamai pull --forceswitches it now. The distribution guide says so. Every selection change the CLI ships behaves this way.gitlab/githuband agents Codex, CodeBuddy and OpenCode for the skill part; left to CI.