Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
15db129
feat(session): cross-platform session migration, IDE sync, and team sync
Sep 14, 2026
f794826
docs(session): design for user-repo session sync (M1-M3)
Sep 15, 2026
0976765
feat(session): cross-project consumption, native archive key, and cor…
Sep 16, 2026
bc14a86
feat(session): split CodeBuddy into cli and ide platforms
Sep 15, 2026
13b22b7
test(session)+docs: M3 — cross-repo coverage, guides, and E2E-discove…
Sep 16, 2026
f8be609
fix(session): write a summary record when migrating into claude-code
Sep 16, 2026
4532759
fix(session): QA sweep — crashes, fidelity, and robustness across 8 c…
Sep 16, 2026
00229f9
fix(session): make migrated sessions actually visible and migration i…
Sep 22, 2026
9c0a2a3
feat(session): redact secrets during migration (--scrub)
Sep 22, 2026
57d4a0e
feat(session): warn on unredacted push; --scrub redacts before archiving
Sep 22, 2026
3ed4a85
fix(session): review sweep — injection, dry-run, archive correctness
Sep 22, 2026
12ec704
fix(session): treat a failed git commit as a failed push
Sep 22, 2026
b4b9d0c
chore(session): English-only comments in new modules
Sep 22, 2026
90abe1e
chore(session): English-only comments in cursor-store
Sep 22, 2026
48d9408
chore(session): English-only comments in codex adapter and new modules
Sep 22, 2026
a1c6ecb
chore(session): English comments in cursor adapter and scrub/doc touc…
Sep 22, 2026
f9aee92
chore(session): delete the leftover empty codebuddy.ts shell
Sep 23, 2026
873c73b
fix(cursor): synthesize placeholder tool results so targets don't ren…
Sep 23, 2026
3993a1c
fix(session): strip injected wrappers from IDE-bound user text; title…
Sep 23, 2026
1c10cd4
fix(cursor): synthesize stable callIds for id-less tool_use; prefer u…
Sep 23, 2026
2a3526d
fix(session): harden archive trust boundary, git reporting and rollba…
Sep 24, 2026
2f4cfbb
docs(session): changelog, product overview row and regenerated comman…
Sep 24, 2026
092f026
fix(session): review follow-up — index repair, id derivation, temp SQ…
Sep 24, 2026
74cf5a5
feat(session): tell the interactive picker there are more sessions th…
Sep 29, 2026
a5d185a
feat(session): paginate the interactive session picker
Sep 29, 2026
05ad839
fix(session): index Cursor bubble deletes and load session commands o…
Sep 29, 2026
4320630
fix(session): keep archives off the source session and off the workin…
Sep 29, 2026
3f90946
Merge origin/main into feat/session-user-repo
Sep 29, 2026
bb1aee5
fix(session): register subcommands behind global flags and do not pub…
Sep 29, 2026
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
46 changes: 46 additions & 0 deletions CHANGELOG.md

Large diffs are not rendered by default.

139 changes: 139 additions & 0 deletions docs/designs/session-user-repo-sync.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# Design: Session User-Repo Sync — cross-project consumption and correctness fixes

> Status: Approved · Branch: `feat/session-user-repo` · Phasing: M1 → M2 → M3 (one PR each)

## Problem

`teamai session` commands store sessions in a team repo under `sessions/repos/<repoIdentity>/<author>/`.
Project-level repos (repo cloned inside the project) form a closed loop: push reads only the
project cwd, and list/pull/resume filter by the project's `git remote` identity. User-level repos
(the clone lives anywhere, e.g. under `~/.teamai/`) can already **receive** sessions from any
directory — but nothing can **consume** them back across projects, and several correctness bugs
undermine the whole flow.

| # | Problem | Evidence |
|---|---------|----------|
| P1 | `session search --all` never searches other projects: the loop body is dead code, and `listRepos()` returns encoded dir names with no decoder — although each repo's `_index.json` stores the canonical identity | `session-cmd.ts:494-517`, `sync.ts:108-110`, `sync.ts:451-457` |
| P2 | `list` / `pull` / `resume` have no cross-project view; `_unattributed` sessions are invisible from any git directory | `session-cmd.ts:410-474`, `sync.ts:234-239` |
| P3 | `migrate --push` archives under the cwd where the command ran, not the session's native project | `session-cmd.ts:301-324` |
| P4 | `migrate --push` re-reads "the N most recent" target sessions, which can push unrelated pre-existing sessions instead of the migrated ones | `session-cmd.ts:306-307` |
| P5 | `push` is single-cwd only; no cross-directory batch | `session-cmd.ts:344-390` |
| P6 | 14 Chinese user-facing strings violate the English-output rule (3 console + 11 throw) | `session-cmd.ts:279,562-563,573`; `codex.ts:240`, `sync.ts:372/375/408/417`, `claude-code.ts:379`, `codebuddy-ide.ts:172/307`, `codebuddy.ts:263`, `workbuddy.ts:264`, `cursor.ts:229` |
| P7 | Meta lies: `fidelityScore` hardcoded 1.0; `createdAt` records push time, breaking search time-decay ordering | `session-cmd.ts:323`, `sync.ts:168` vs `search.ts:104-111` |
| P8 | Repeated pushes of one session create `xxx` and `xxx_1` duplicates; no dedup key | `sync.ts:329-330` |
| P9 | `resume` in the wrong directory throws an uncaught Chinese error with no hint the session lives under another identity | `session-cmd.ts:455-474`, `sync.ts:408` |
| P10 | `gitCommit` returns HEAD even when nothing was committed → "✓ Pushed N" on empty pushes | `sync.ts:546-549` |

Additional aggravator for P3: when `codebuddy-ide` `readSession` falls back to a global search and
finds a conversation from another workspace, it silently rewrites `session.cwd` to the passed-in
project path (`codebuddy-ide.ts:163-170,223`) — the archive key is wrong *deterministically*,
not just incidentally.

## Solution

### Key invariant

**A session's archive key (repoIdentity) is derived from the session's own native cwd whenever
that cwd is knowable, never from the directory where the CLI happened to run.** When the native
cwd is unknowable (codebuddy-ide without an explicit path), warn and fall back to `_unattributed`.

Native-cwd knowability per adapter:

| Adapter | Source | Effort |
|---------|--------|--------|
| codex | `session_meta.payload.cwd` — real absolute path | none |
| workbuddy | `meta.json` cwd | none |
| claude-code | each JSONL record carries `cwd`; adapter must read the first record | small |
| codebuddy CLI | each record carries `cwd` (written by `writeSession`) | small |
| cursor | same pattern as claude-code | small |
| codebuddy-ide | workspace dir = md5(cwd), irreversible | impossible — caller must pass real cwd, else `_unattributed` + warning |

### Data model (no schema changes)

`SessionSyncMeta.origin` gains reliable values: `repoIdentity` from native cwd (fallback:
caller-provided, then `_unattributed`), `createdAt` from `session.createdAt` (not push time),
`fidelityScore` from the migration preview score. Dedup key = `origin.sessionId` + author:
re-pushing updates the existing entry instead of generating `_1` suffixes.

### Entry points

- `session list --all` / `session pull --all` — iterate every repo dir via `_index.json` canonical
identities plus `_unattributed`.
- `session search --all` — replace the dead loop with the same cross-repo iteration.
- `session push --all` — `--source` stays required; enumerates every workspace directory of that
one platform (bounded blast radius, consistent with `status --all`). Confirmation list when
more than 5 sessions would be pushed; `-y` skips.
- `migrate --push` — re-read exactly the `result.targetSessionId`s recorded during the migration
loop; archive under the session's native identity.
- `resume` failure — append a hint: the session may be archived under another project; try
`session search --all` or `--cwd <project path>`.

Deliberately **not** done: new top-level commands (Occam's razor — every change extends an
existing command's options); branch/MR flow for session push (current behavior pushes the
current branch; aligning with `teamai push`'s branch+MR flow is a separate discussion).

## Affected surface

**New** (this PR series):
- `docs/designs/session-user-repo-sync.md` — this document
- `src/__tests__/session-sync.test.ts` — SyncManager: index, encoding, archive layout, dedup, cross-repo listing
- `src/__tests__/session-cmd.test.ts` — command layer: search --all, push --all, archive key, English output

**Modified**:
- `src/session-flow/sync.ts` — native-identity validation, dedup, `listAllRepoIdentities()`, cross-repo `listSessionsAcrossRepos()`, empty-commit detection
- `src/session-flow/session-cmd.ts` — `--all` options, precise re-read, resume hint, English strings
- `src/session-flow/adapters/claude-code.ts`, `codebuddy.ts`, `cursor.ts` — read native cwd from first record; English errors
- `src/session-flow/adapters/codebuddy-ide.ts`, `codex.ts`, `workbuddy.ts` — no silent cwd rewrite; English errors
- `docs/usage-guide.md` / `docs/usage-guide.zh-CN.md` — new `### Session Sync & Migration` section between Session Save and Hooks (+ both TOCs)
- `README.md` / `README.zh-CN.md` — Sessions row and command cheat-sheet
- `CHANGELOG.md`

## Phasing

| Phase | Scope | PR |
|-------|-------|-----|
| M1 correctness | P3 P4 P6 P7 P9 P10 (small fixes, no API change) | 1 |
| M2 user-repo capability | P1 P2 P5 P8 + SyncManager cross-repo API + archive-key rework | 1 |
| M3 tests & docs | both test files, six doc touchpoints, real-CLI E2E report | 1 |

M1 and M2 are independent in code but share the same branch; M3 lands last and validates both.

## Out of scope

- Branch/MR flow for session push (separate discussion)
- Streaming/lazy loading for search (known limitation: full sessions are read into memory; recorded, not fixed)
- SessionSave/`teamai session save` (different system — digest summaries)

## Known limitations (QA sweep, recorded — not fixed by design)

Verified by `src/__tests__/fidelity-sweep.test.ts` (roundtrip matrix, kept as the fidelity
regression suite):

- **fidelityScore is a proxy metric.** It only measures IR-block-level degradations. Content
deformation (dropped empty messages, timestamp collapse, sessionId regeneration, title
drift, message splitting in codex) is invisible to it. Do not treat 100% as "byte-perfect".
- **codex message splitting**: `[thinking, text, tool_call]` assistant turns are written as
separate codex response_items and read back as more messages than went in.
- **Empty-content messages** are dropped by several adapters' writers/readers (semantic
choice per adapter; unifying would change existing behavior).
- **sessionId is platform-native.** v4 (claude-code), v7 (codex), 32-hex (codebuddy-ide)
each regenerate on write; a cross-platform chain therefore accumulates one archive per
platform. The session id you resume with is always the target platform's.
- **claude-code flattenDag** drops sidechain branches and can promote orphan nodes early;
fork branches interleave into the main timeline.
- **cursor has no stored title** — the title is derived from the first real user text;
sessions whose only real content is tool output may title from that snippet.
- **codex has no on-disk title mechanism** — roundtrip titles degrade to `Session <ts>`.

## End-to-end test plan (real CLI, per AGENTS.md — type-check/unit tests don't count)

1. `node dist/index.js session platforms` — all 6 platforms listed, `codebuddy` and `codebuddy-ide` both `✓ installed`
2. In a non-git temp dir: `session push --source codebuddy --repo-root <user-repo>` — session lands under `sessions/_unattributed/`, output is English
3. In the user repo: `session list --all` — the unattributed session is visible; `session list` (no flag) — not visible (current-project filter intact)
4. `session search --all <keyword>` — matches sessions from at least two different repoIdentity dirs
5. From project A: `session migrate <B-session-id> -s codebuddy-ide -t claude-code --push` — archive lands under B's identity (check meta.json), fidelityScore equals preview score, no `_1` duplicate on re-push
6. Re-run step 5 push — same entry updated, no duplicate file
7. `session resume` in a wrong project dir — error is English and mentions `search --all` / `--cwd`
8. Empty repo push (`session push --source codebuddy` with no local sessions) — prints "No sessions found", never "✓ Pushed"
9. `npx vitest run src/__tests__/session-sync.test.ts src/__tests__/session-cmd.test.ts` — all green
10. `npx vitest run` — no new failures vs. the pre-change baseline
1 change: 1 addition & 0 deletions docs/product-overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,5 +158,6 @@ Insight into how the team actually uses its AI tools, and a starting point for t
|------------|---------|---------------|
| **Usage** | `teamai digest` | Weekly team digest — 7-day success, prompt, active-time, estimated cost, cache, and correction trends, plus lifetime totals. |
| **Sessions** | `teamai session save` | Privacy-scrubbed per-session summaries (tool sequence, prompt turns, interventions) that feed the digest's Session Highlights. |
| **Session Sync** | `teamai session migrate` | Move full transcripts between AI tools; archive, search, and restore team sessions (`push` / `pull` / `list` / `resume` / `search`). |
| **Dashboard** | `teamai dashboard` | Unified Overview / Team Execution / Team Context / Team Improvement views with local live sessions, 7-day trends, estimated cost per session, English/Chinese, and light/dark/system themes. |
| **KB Health** | `teamai dashboard` → Team Context / Team Improvement | Coverage by type, top-recalled and silent entries, last-recall month distribution, author contributions, and maintenance; the full `/kb-report` remains available. |
1 change: 1 addition & 0 deletions docs/product-overview.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,5 +158,6 @@ teamai recall maintenance --update-quality # 为过时 skills / docs 生
|------|------|----------|
| **用量(Usage)** | `teamai digest` | 团队周报——近 7 天成功率、对话、活跃时长、估算成本、缓存与纠偏趋势,以及历史累计数据。 |
| **会话(Sessions)** | `teamai session save` | 脱敏的单会话摘要(工具序列、对话轮次、干预次数),喂给周报的 Session Highlights。 |
| **会话同步(Session Sync)** | `teamai session migrate` | 在 AI 工具之间迁移完整会话记录;归档、检索并恢复团队会话(`push` / `pull` / `list` / `resume` / `search`)。 |
| **看板(Dashboard)** | `teamai dashboard` | 统一的 Overview / Team Execution / Team Context / Team Improvement 界面,保留本机实时会话、近 7 天趋势、每会话估算费用,支持中英文及日间/夜间/跟随系统主题。 |
| **知识库健康(KB Health)** | `teamai dashboard` → Team Context / Team Improvement | 保留各类型覆盖率、高频召回与沉默条目、最近召回月份统计、作者贡献及维护控制台;完整 `/kb-report` 报告仍可访问。 |
30 changes: 30 additions & 0 deletions docs/usage-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -1719,6 +1719,36 @@ teamai session save --push --include-prompt # also include the (redacted) first

> Privacy: the team-pushed payload is **counts + tool names only** by default. The first-ask prompt line is opt-in via `--include-prompt`, and even then it is run through the same secret redaction (`ghp_…` → `<REDACTED:…>`) used elsewhere. Local logs keep the redacted first-ask line since they never leave your machine.

### Session Sync & Migration

Beyond summaries, `teamai session` can move full conversation transcripts between AI tools and archive them in the team repo. Where `session save` records a privacy-scrubbed summary, these commands carry every message.

Supported platforms: `claude-code` (plus `claude-internal` / `tclaude`), `codex` (plus `codex-internal` / `tcodex`), `codebuddy` (CLI), `codebuddy-ide` (the IDE sidebar), `cursor`, and `workbuddy`. `teamai session platforms` shows which are installed locally.

```bash
teamai session platforms # supported vs installed
teamai session migrate <sessionId> -s codebuddy-ide -t claude-code # one session across tools
teamai session migrate --all -s codebuddy -t claude-code # every session of the source (--limit to cap)
teamai session rollback <targetSessionId> --platform claude-code # undo a migration
teamai session push --source codebuddy # archive this directory's sessions
teamai session push --source codebuddy --all # every workspace of that platform
teamai session pull # pull + re-index team sessions
teamai session list # this project's team sessions
teamai session list --all # every archived project
teamai session search <query> [--all] # full-text search of archived content
teamai session resume <sessionName> --platform claude-code # restore into a local tool
```

All of these accept `--dry-run` and `-v`. `migrate --push` migrates and archives in one step; `resume` prints the new session id — continue it with your tool's own resume flag.

**Confirmations.** `--all` asks for confirmation once it passes 10 sessions (push: more than 5) and refuses outright in a non-interactive run, so a script cannot silently skip the batch — pass `-y` to confirm. Migrating a platform onto itself reuses the native session id and overwrites the source transcript, so it needs `--target-cwd <dir>` (write a copy elsewhere) or `-y`. `push` also asks before archiving unredacted transcripts; `--scrub` redacts them first.

**Picking a session.** `migrate` without a session id lists 10 at a time: `n` / `p` move to the next / previous page, a number selects (numbering is global, so page 2 starts at 11), Enter cancels. Use `--all` (optionally `--limit <n>`) to migrate without picking, or pass a session-id prefix to select one directly.

**Archive layout.** Sessions are archived under the git identity of their working directory: `sessions/repos/<repo>/<author>/` in the team repo. Sessions from non-git directories land under `_unattributed`. The archive key comes from the session's own workspace — not from where you run the command — so migrating from another directory still archives under the right project. CodeBuddy IDE sessions whose workspace cannot be resolved fall back to `_unattributed` with a warning.

**Project-level vs user-level repos.** `list` / `pull` / `resume` filter by the current directory's git remote, so a project-level team repo shows exactly that project's sessions. Pass `--repo-root <any clone>` — for example a personal repo — and use `--all` to read across every archived project.

### Hooks

Hooks automatically injected by `teamai init`:
Expand Down
30 changes: 30 additions & 0 deletions docs/usage-guide.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -1609,6 +1609,36 @@ teamai session save --push --include-prompt # 额外带上(脱敏后的)首

> 隐私:推送到团队的内容默认**只含计数 + 工具名**。首个 prompt 行需通过 `--include-prompt` 显式开启,且即便开启也会经过与别处一致的密钥脱敏(`ghp_…` → `<REDACTED:…>`)。本地日志因为不出本机,会保留脱敏后的首个 prompt 行。

### Session 同步与迁移(Session Sync & Migration)

除了摘要之外,`teamai session` 还能在不同 AI 工具之间迁移完整会话,并把会话归档到团队仓库。`session save` 记录的是脱敏摘要,而这一组命令搬运的是会话的全部消息。

支持的平台:`claude-code`(含 `claude-internal` / `tclaude` 变体)、`codex`(含 `codex-internal` / `tcodex`)、`codebuddy`(CLI)、`codebuddy-ide`(IDE 侧边栏)、`cursor`、`workbuddy`。运行 `teamai session platforms` 查看本机已安装哪些。

```bash
teamai session platforms # 支持 vs 已安装
teamai session migrate <sessionId> -s codebuddy-ide -t claude-code # 跨工具迁移单条会话
teamai session migrate --all -s codebuddy -t claude-code # 该源的全部会话(--limit 可限量)
teamai session rollback <targetSessionId> --platform claude-code # 撤销一次迁移
teamai session push --source codebuddy # 归档当前目录的会话
teamai session push --source codebuddy --all # 该平台的全部工作区
teamai session pull # 拉取并重建团队会话索引
teamai session list # 当前项目的团队会话
teamai session list --all # 全部归档项目
teamai session search <query> [--all] # 全文搜索归档内容
teamai session resume <sessionName> --platform claude-code # 恢复到本地工具
```

以上命令均支持 `--dry-run` 与 `-v`。`migrate --push` 一步完成迁移 + 归档;`resume` 会打印新的会话 id,用工具自身的 resume 参数继续。

**确认提示。** `--all` 超过 10 条(push 为超过 5 条)会先列清单要求确认;非交互运行(CI / 脚本)直接拒绝并以非 0 退出,避免脚本以为整批都跑完了——用 `-y` 明确确认。同平台迁移会复用原生会话 id 覆盖源会话,必须给 `--target-cwd <dir>`(写到别处)或 `-y`。`push` 在归档未脱敏原文前也会先问一次,`--scrub` 可先脱敏。

**选择会话。** `migrate` 不带会话 id 时每屏列出 10 条:`n` / `p` 翻到下一页 / 上一页,数字直接选择(编号是全局的,所以第二页从 11 开始),回车取消。用 `--all`(可配 `--limit <n>`)无需挑选直接迁移,或给出会话 id 前缀指定某一条。

**归档布局。** 会话按其工作目录的 git 标识归档到团队仓库的 `sessions/repos/<repo>/<author>/`;非 git 目录的会话落入 `_unattributed`。归档键取自会话自身的工作区——而不是执行命令时所在的目录——从别的目录迁入也会归到正确的项目名下。CodeBuddy IDE 中无法还原工作区的会话会带警告归入 `_unattributed`。

**项目级 vs 用户级仓库。** `list` / `pull` / `resume` 按当前目录的 git remote 过滤,项目级团队仓库因此只显示本项目的会话。传入 `--repo-root <任意 clone>`(例如个人仓库)并配合 `--all`,即可跨全部归档项目读取。

### Hooks

`teamai init` 自动注入的 Hooks:
Expand Down
Loading
Loading