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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ All notable changes to this project will be documented in this file. See [standa
### 🐛 Bug Fixes

- `teamai stats` no longer counts a session twice, and no longer counts another project's sessions. Its dashboard section added the whole machine's local `events.jsonl` metrics to the scope's already-reported totals from the team repo, so every session that a `pull` had reported — and that stays in the event log until compaction — was counted once by the team total and once again locally, and sessions whose `cwd` belonged to a different project were added to this scope's as well. It now filters the event log the way `teamai pull` reports it (a project scope keeps only the sessions under its own root; the user scope resolves no root, so nothing is excluded and the log passes through unfiltered) and adds only what that scope has not reported yet, derived from the same per-session `reported-*` snapshots the report path advances — so the local figure agrees with the team's instead of exceeding it. When the reported totals could not be read at all (no stats file, an unreadable one, a reports worktree that is not there), or when they exist but hold nothing yet, nothing is subtracted — the local `reported-*` snapshots are machine-global while the team file is per-scope, so a non-empty team total is what licenses trusting them, and a session the member can see happening is never hidden. The per-repo and by-hour breakdowns read that same filtered log, so they stay inside the scope; they answer a different question from the headline — what this machine's retained event log holds, per repo — and both the headings and the `--by-repo` / `--by-time` flag descriptions now name that source instead of presenting them as a split of the headline (for [#768](https://github.com/Tencent/teamai-cli/issues/768)).
- The data-partition migration no longer retires a legacy `<repo>/.teamai/` while the partition's `config.yaml` cannot be read. `teamai init`, `pull` and `push` took a partition config that merely existed as a finished migration and renamed the legacy directory to `.teamai.bak`, although it held the only config that still loaded. A partition `config.yaml` that is empty or cannot be opened, does not parse, does not validate, or is not `scope: project` now leaves the legacy directory in place and names the file to fix; once it is fixed, the next of those commands retires it as before. A partition directory whose `config.yaml` was moved aside is no longer replaced by a fresh copy of the legacy directory, which deleted what the partition held; the migration waits and says to restore the file or move the directory aside (for [#797](https://github.com/Tencent/teamai-cli/issues/797)).
- The legacy `teamai dashboard-report` command no longer records dashboard events in a directory that never set up teamai. A current install writes only `teamai hook-dispatch`, whose dashboard-report handler declares `requiresConfig` and is dropped when no config resolves for the hook's `cwd`; the old subcommand stayed ungated, so a hook left behind by an earlier install kept recording events for every project it fired in, and those sessions were then reported by whichever scope pulled next. It now applies the same gate `teamai contribute-check` was given, asked about the session's `cwd` — or, for a host that sends none, the directory the hook runs in (for [#768](https://github.com/Tencent/teamai-cli/issues/768)).
- The lock behind `pull`, `push`, the reports and learnings worktrees, learnings publishing, migration, self-mode bootstrap and the update check no longer hands one lock to two live processes. Reclaiming a stale lock renamed over whatever file was there once it had judged the lock stale, and it judged live locks stale: one that had just been released (and could be re-created by a third process before the rename), one whose owner had created it but not yet written it, one owned by a process running as another user (for example a `sudo teamai` run), and one it could not read. Under 16 processes contending on one lock, about 1% of acquisitions overlapped another holder, enough for two pulls to report the same usage twice. Now only a lock whose owner is provably gone is reclaimed; a lock that vanished gets one more exclusive create, and a new lock is published with its content already in place (written to a temp file, then hard-linked to the lock name; a filesystem without hard links falls back to the previous create). A lock that names no owner (empty, partly written, unreadable) is never reclaimed: if a crash left one, `pull` and `push` report busy until it is removed, and a warning names the file. Migration skips the lock's temporary files, which a contending pull creates and removes while the copy runs. With the change, the same stress run shows no overlap (for [#760](https://github.com/Tencent/teamai-cli/issues/760)).
- Team hooks stay out of projects that never set up teamai. A project-scope install puts its hooks in the home directory, so they fire in every project on the machine, and with no config for the directory they used to run anyway: the end-of-session share reminder (shown there even with recall off, a case a configured team never sees), the TodoWrite recall nudge, and the local recording of sessions and skill usage that a later report from another project pushed to its team. A handler that needs a team now declares `requiresConfig`, and the dispatcher drops it when neither a project nor a user config resolves for the hook's `cwd`; only machine-level work runs there (CLI update check, session-start pull, local agent, package hints the pull stashed). A config that exists but fails to parse reads the same way, so it withholds team prompts rather than running all of them, and for team hooks and skill usage an unreadable project config never falls back to the user scope, nor to a lower-priority project config such as a legacy `.teamai/` behind a broken partition (the session-start pull still resolves its project on its own). A host that sends no `cwd` (OpenClaw) resolves the project from the directory it runs the hook in, a `cwd` that no longer exists resolves to the user scope instead of failing the hook, and the legacy `teamai contribute-check` command that older installs still call follows the same rule (for [#748](https://github.com/Tencent/teamai-cli/issues/748)).
Expand Down
18 changes: 13 additions & 5 deletions docs/designs/data-directory-layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,10 +189,18 @@ effect, both of which would mask the raw legacy state). Act iff:
so moving it would break "knowledge on main".

The plan's **mode** then depends on the partition: a full copy when
`<partition>/config.yaml` does not exist yet, or **retire-only** when it does (a prior
run built the partition but was interrupted before retiring the source — see Interrupt
recovery). retire-only never re-copies onto the authoritative partition; it only cleans
up the leftover legacy dir.
`<partition>/config.yaml` does not exist yet, or **retire-only** when it exists and
detection can read it (a prior run built the partition but was interrupted before
retiring the source — see Interrupt recovery). retire-only never re-copies onto the
authoritative partition; it only cleans up the leftover legacy dir. A partition
`config.yaml` that exists but that detection cannot read (it is empty or cannot be
opened, does not parse, does not validate, or is not `scope: project`) plans nothing:
the legacy dir holds the only config that still loads, so it stays in place (a warning
names the file) until the member fixes the partition file, and the next write command
then gets the retire-only cleanup. A partition dir with no `config.yaml` at all (say,
one moved aside by hand) plans nothing either, with a warning: the full copy replaces
the whole dir, so it would take that dir's data with it. The full copy's re-check under
the lock in `runMigration` applies the same rules, warning included.

**Steps** (`runMigration`) — copy → verify → atomic rename, so an interruption never
leaves data half-in-both-places:
Expand Down Expand Up @@ -226,7 +234,7 @@ leaves data half-in-both-places:
Interrupt recovery: staging is a separate sibling dir, so a crash before step 3 leaves
the partition absent and the source intact — a rerun discards `.staging/` and starts
clean. A crash between steps 3 and 5 leaves the partition built with the legacy dir
still present; the next write command's `planMigration` sees "partition exists AND
still present; the next write command's `planMigration` sees "readable partition AND
legacy lingers" and returns a **retire-only** plan that finishes the job — it retires
the leftover legacy dir to `.teamai.bak/` WITHOUT re-copying onto the now-authoritative
partition. This closes the gap where the legacy dir (including its plaintext `env`)
Expand Down
6 changes: 5 additions & 1 deletion docs/usage-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,11 @@ for tools you have not opened in this project.
> **Upgrading from an older teamai?** The first `teamai init` / `pull` / `push` after
> upgrading automatically migrates an existing `<repo>/.teamai/` into the partition
> (copy → verify → atomic switch), then leaves the old directory as `<repo>/.teamai.bak/`
> for you to delete once you've confirmed everything works. Read-only commands and the
> for you to delete once you've confirmed everything works. If the partition already
> exists but its `config.yaml` cannot be read, or is missing, the migration keeps
> `<repo>/.teamai/` and warns with the path: fix or restore that file (or move the
> config-less partition aside), and the next `init` / `pull` / `push` finishes the job.
> Read-only commands and the
> `hook-dispatch` path never migrate; `teamai --dry-run pull` previews the move.
> **Downgrading afterwards is not supported** — an older teamai would treat the project
> as uninitialized; `.teamai.bak/` is the manual rollback path.
Expand Down
6 changes: 4 additions & 2 deletions docs/usage-guide.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,10 @@ teamai init https://github.com/yourorg/yourrepo

> **从旧版 teamai 升级?** 升级后首次执行 `teamai init` / `pull` / `push` 会自动把已有的
> `<repo>/.teamai/` 迁移进分区(复制 → 校验 → 原子切换),并把旧目录保留为
> `<repo>/.teamai.bak/`,待你确认一切正常后自行删除。只读命令与 `hook-dispatch` 路径
> 永不触发迁移;`teamai --dry-run pull` 可预演。**迁移后不支持降级**——旧版会把项目判定为
> `<repo>/.teamai.bak/`,待你确认一切正常后自行删除。若分区已存在但其 `config.yaml`
> 无法读取或缺失,迁移会保留 `<repo>/.teamai/` 并给出带路径的警告:修复或恢复该文件
> (或把缺少 config 的分区移开)后,下一次 `init` / `pull` / `push` 会完成迁移。
> 只读命令与 `hook-dispatch` 路径永不触发迁移;`teamai --dry-run pull` 可预演。**迁移后不支持降级**——旧版会把项目判定为
> 未初始化;`.teamai.bak/` 是人工回滚路径。

如果仓库启用了角色化 skills(存在 `manifest/roles.yaml`),`teamai init` 还会交互式要求你选择:
Expand Down
Loading
Loading