Skip to content

feat(git): route independent-clone reports onto teamai-reports - #489

Open
jimpablo wants to merge 2 commits into
Tencent:mainfrom
jimpablo:feat/484-git-reports
Open

feat(git): route independent-clone reports onto teamai-reports#489
jimpablo wants to merge 2 commits into
Tencent:mainfrom
jimpablo:feat/484-git-reports

Conversation

@jimpablo

Copy link
Copy Markdown
Contributor

Summary

Independent git clones currently write members/ sessions/ votes/ stats/ onto the default branch via pushRepoDirectly, so every member needs write access on main. This reuses the existing teamai-reports orphan-branch worktree writer that single-repo mode already uses.

  • Git-kind reports checkout is a sibling of the clone (<dirname(localPath)>/reports-wt), so clone reset --hard cannot nest-destroy it.
  • kind: self keeps today's nested <knowledgeDir>/reports-wt.
  • kind: http is unchanged (no git reports branch).
  • Empty-repo init may still push teamai.yaml + gitkeeps to the default branch so the knowledge tree exists; member YAML goes to teamai-reports.
  • Leftover report files already on main are not copied, not deleted, and not used as the source of truth after the switch.
  • No new CLI command. Learnings stay on the default branch (Write learnings/ to a teamai-learnings branch (direct push, no PR) #485 is separate).

Closes #484

Type of Change

  • Bug fix (non-breaking change that fixes an existing issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature causing existing behavior to change)
  • Documentation only
  • Refactor / internal cleanup

Test Plan

  • npx tsc --noEmit — clean
  • npx vitest run focused suites — 117 tests passed, including:
    • src/__tests__/git-kind-reports.test.ts (real git: write to origin/teamai-reports, leftover clone members/ ignored, empty-repo skeleton on default branch, dedicated-root guard)
    • src/__tests__/self-mode-no-business-reset.test.ts (git-kind no longer reset --hards the clone)
    • src/__tests__/members.test.ts (listMembers reads reports worktree; leftover clone YAML is not listed)
    • src/__tests__/team-push-interventions.test.ts (git-kind auto-report uses commitAndPushReports, not pushRepoDirectly)
  • npm run build then built CLI (dist/index.js) against a local bare remote whose update hook rejects default-branch pushes:
    1. teamai init https://git.example.test/acme/team.git --scope user --force — English success; Member registered on the teamai-reports branch
    2. teamai session save --push --force --session-id s1 --scope userPushed: sessions/alice/2026-09.md
    3. teamai pull — auto-report wrote stats/alice.yaml
    • git ls-tree origin/teamai-reports contained members/alice.yaml, sessions/alice/2026-09.md, stats/alice.yaml
    • default-branch tree still only had teamai.yaml + knowledge gitkeeps (no new report files)
    • reports worktree was a sibling of the clone (~/.teamai/reports-wt); clone had no members/ stats/ sessions/
  • EN/ZH docs/usage-guide.md updated so independent git clones also write reports to teamai-reports (learnings still on the default branch)

Related Issues

Closes #484

Notes for Reviewers

Independent git clones currently push members/sessions/votes/stats onto the
default branch, so every member needs write access on main. Reuse the existing
orphan-branch worktree writer: git-kind reports live beside the clone on
teamai-reports; self stays nested under .teamai/; HTTP is unchanged. Empty-repo
init may still seed teamai.yaml on the default branch; leftover report files
on main are ignored.

Closes Tencent#484
@jeff-r2026
jeff-r2026 self-requested a review September 10, 2026 09:22
@jeff-r2026

Copy link
Copy Markdown
Collaborator

Review: one blocking bug — stale sibling reports worktree causes silent data loss

Nice work overall — tsc/build clean, the focused suites + full test run pass, the no-copy/no-delete semantics are tested both directions, and usesReportsBranch() is applied consistently across every reader/writer. One reproducible issue should be fixed before merge, though.

Where: src/utils/reports-branch.ts ensureReportsWorktree

// Already a valid worktree — nothing to do.
if (await isGitRepo(wt)) {
  return wt;
}

Root cause: for git-kind, reports-wt is now a sibling of the clone, linked to the clone's .git. isGitRepo(wt) only checks that a .git file exists — not that the worktree's backing gitdir (<clone>/.git/worktrees/reports-wt) is still alive. When the clone is removed and re-cloned, that link dangles but isGitRepo still returns true, so this early-returns the dead worktree and skips its own remove+recreate self-heal path just below.

Trigger — a routine flow: teamai init against a different --repo URL removes and re-clones localPath (init.ts:1136 / :1149, plus the not-a-git-repo case at :1160). Because the reports worktree is deliberately a sibling (not nested), it survives the clone delete as a dangling husk.

Impact: every git op in the stale worktree throws fatal: not a git repository: .../.git/worktrees/reports-wt. commitAndPushReports catches it and returns false with a debug-only log — so member registration, pull auto-report, session save --push, and vote sync silently never reach origin, and reader commands show stale checkout data. No error surfaces to the user.

Reproduction (real git, run against this PR head — fails):

  1. git-kind config → ensureReportsWorktree → write members/alice.yamlcommitAndPushReports pushes to origin/teamai-reports
  2. rm -rf <clone> then re-clone into the same localPath (mirrors init switching repo URL)
  3. ensureReportsWorktree returns the same stale path (no rebuild); a member write → commitAndPushReports returns false, and origin/teamai-reports never gets bob.yaml. Manually rm -rf reports-wt and it heals — confirming the missing staleness check is the sole cause.

Suggested fix (verified: turns the repro green, full suite still passes):

if (await isGitRepo(wt)) {
  try {
    await createGit(wt).revparse(['--is-inside-work-tree']);
    return wt;
  } catch {
    // stale/dangling worktree link (clone was re-cloned/pruned) — recreate below.
  }
}

The existing pathExists(wt) → fse.remove(wt) + worktree prune lines right below already handle the rebuild once you fall through. A regression test around the re-clone path would be worth adding alongside git-kind-reports.test.ts.

isGitRepo only checks that reports-wt has a .git file. After init replaces
the clone, that file still exists but the backing gitdir is gone, so
ensureReportsWorktree returned the husk and commitAndPushReports failed
silently. Probe revparse --is-inside-work-tree and fall through to the
existing remove+recreate path.
@jimpablo

Copy link
Copy Markdown
Contributor Author

Thanks for the review and the repro.

Fixed in d11e101: ensureReportsWorktree now probes revparse --is-inside-work-tree after isGitRepo, and falls through to the existing remove + prune + recreate path when the sibling gitdir is dangling.

Added a real-git regression in git-kind-reports.test.ts that matches your sequence (write alice → delete and re-clone → write bob); both files land on origin/teamai-reports after the rebuild.

CI on that commit is green.

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.

Route report data (members/sessions/votes/stats) to teamai-reports in independent git clones

2 participants