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
4 changes: 2 additions & 2 deletions docs/designs/dashboard-unified.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ The logo's charcoal/blue palette, four-module navigation, English/Simplified Chi
| View | Retained functionality |
| --- | --- |
| Overview | Local session status/counts, six current/prior 7-day metrics, KB coverage, maintenance candidate counts, session list |
| Team Execution | All supported tools, exact working-directory/tool filters, active and recently ended sessions, first/latest secret-redacted prompt summaries and output previews, duration, last activity/tool, intervention and token counts; keyboard-accessible Details opens all captured prompt summaries (capped at 200 characters), safely rendered Markdown output and full breakdowns |
| Team Execution | All supported tools, repository (all worktrees of a repo as one)/tool filters, active and recently ended sessions, first/latest secret-redacted prompt summaries and output previews, duration, last activity/tool, intervention and token counts; keyboard-accessible Details opens all captured prompt summaries (capped at 200 characters), safely rendered Markdown output and full breakdowns |
| Team Context | Original KB totals, coverage by type, top recalled chart, expandable never-recalled groups, last-recall month chart, author contribution table, reported data scope and report generation time |
| Team Improvement | Local trends, original promotion/archive/stale candidate guidance and commands, digest/session-save/share-learnings command references |

Expand Down Expand Up @@ -45,6 +45,6 @@ The existing `avgRequestCostMicros`, request-day daily buckets and digest statis

### Workspace selection and missing data

The sidebar switches between all local sessions, user scope, and installed project scopes. Project partitions are discovered from their anchor files; legacy installs are discovered from the startup directory and recorded session directories. Linked Git worktrees share a project. User scope contains sessions outside installed projects. In the all-workspaces view, knowledge uses the startup scope; this is labeled explicitly. Individual scopes select their own knowledge configuration, with separate report caches and stale-response protection. Restart the dashboard after installing a new scope.
The sidebar switches between all local sessions, user scope, and installed project scopes. Project partitions are discovered from their anchor files; legacy installs are discovered from the startup directory, the repo each event recorded (`projectAnchor`), and recorded session directories. Linked Git worktrees share a project, and a removed worktree's sessions stay with it. User scope contains sessions outside installed projects. In the all-workspaces view, knowledge uses the startup scope; this is labeled explicitly. Individual scopes select their own knowledge configuration, with separate report caches and stale-response protection. Restart the dashboard after installing a new scope.

The complete health report is split between Team Context (coverage, recalls, entries, authors) and Team Improvement (maintenance), with internal navigation. The legacy report URL remains compatible, but is not linked from the dashboard. Missing prior-period samples are labeled as no ended sessions, no priced sessions, or no usage data rather than collecting.
4 changes: 2 additions & 2 deletions docs/designs/dashboard-unified.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
| 页面 | 保留功能 |
| --- | --- |
| 总览 | 本机会话状态与计数、六项近 7 天/前 7 天指标、知识覆盖率、维护候选计数、会话列表 |
| 团队执行 | 全部已支持工具、按完整工作目录/工具筛选、活跃和最近结束会话、首条/最近经密钥脱敏的输入摘要及输出预览、时长、最近活动/工具、干预与 Token 计数;支持键盘访问的详情保留全部已捕获输入摘要(最长 200 个字符)、安全渲染的 Markdown 输出及完整明细 |
| 团队执行 | 全部已支持工具、按仓库(同一仓库的所有 worktree 合为一项)/工具筛选、活跃和最近结束会话、首条/最近经密钥脱敏的输入摘要及输出预览、时长、最近活动/工具、干预与 Token 计数;支持键盘访问的详情保留全部已捕获输入摘要(最长 200 个字符)、安全渲染的 Markdown 输出及完整明细 |
| 团队上下文 | 原有知识库总量、按类型覆盖率、高频召回图表、可折叠从未召回分组、最近召回月份图表、作者贡献表、数据来源与报告生成时间 |
| 团队改进 | 本机趋势,原有晋升/归档/过期知识候选说明和命令,以及 digest/session-save/share-learnings 命令说明 |

Expand Down Expand Up @@ -45,6 +45,6 @@

### 工作区切换与缺失数据

侧栏支持全部本机会话、用户范围和已安装项目范围。项目分区从 anchor 文件发现;旧安装从启动目录及已有会话目录发现。同一项目的 Git worktree 共享项目范围。用户范围包含不属于已安装项目的会话。全部工作区模式的知识库使用启动时范围,并明确标注。单独范围使用自己的知识库配置、独立报告缓存并阻止旧请求覆盖新范围数据。安装新范围后需重启仪表盘。
侧栏支持全部本机会话、用户范围和已安装项目范围。项目分区从 anchor 文件发现;旧安装从启动目录、事件记录的仓库(`projectAnchor`)及已有会话目录发现。同一项目的 Git worktree 共享项目范围,已删除的 worktree 的会话仍归属该项目。用户范围包含不属于已安装项目的会话。全部工作区模式的知识库使用启动时范围,并明确标注。单独范围使用自己的知识库配置、独立报告缓存并阻止旧请求覆盖新范围数据。安装新范围后需重启仪表盘。

完整健康报告分别放在团队上下文(覆盖率、召回、条目、贡献者)和团队改进(维护)中,通过内部导航访问。旧报告 URL 保持兼容,仪表盘不再提供跳转入口。上期缺少样本时显示无已结束会话、无已定价会话或无用量数据,不再显示正在收集。
12 changes: 9 additions & 3 deletions docs/designs/data-directory-layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -345,12 +345,18 @@ read. Local votes followed for the same reason (#787): `<dataHome>/votes/`, and
`~/.teamai/user-votes/` (`getUserVotesDir()`) for the user scope, so a scope
pushes only the votes cast where it is set up. The old shared `~/.teamai/votes/`
is never read, and its pending deltas are not pushed. The dashboard stays an A2
singleton: `teamai dashboard`, `stats --by-repo`, `session save` and the
contribute check read across scopes. Each event instead
singleton: `teamai dashboard`, `session save` and the contribute check read
across scopes; `stats --by-repo` reads only the current scope's events, as the
rest of `stats` does (#795). Each event instead
carries `dataHome`, the `getDataHome()` of the scope the hook resolved (#785), and a
scope's report keeps only its own. An event written before that field existed is
attributed by its `cwd`, realpath'd, to the project whose root holds it, never to
the user scope. The snapshots of what was already reported are per scope too
the user scope. Inside git an event also carries `projectAnchor`, the repo's
main checkout, which all of its worktrees share (#809). `stats --by-repo`,
`session save` and the dashboard's Repository filter key a session by the last
anchor it recorded, else by its `cwd`, and the dashboard gives an event to the
project rooted at its anchor, so a worktree counts as its repo, also after it
is removed. The snapshots of what was already reported are per scope too
(#786), because a session can record events in two scopes (a `cd` mid-session):
`<dataHome>/dashboard/reported-*.json`, and `~/.teamai/dashboard/user-reported-*.json`
for the user scope. The first time a scope needs one it copies the shared
Expand Down
6 changes: 4 additions & 2 deletions docs/usage-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -1435,14 +1435,16 @@ teamai codebase --lint --output /path/to/repo

When extract finds components, it writes `teamwiki/evidence/code/<project>/_manifest.json` even if AI enrichment is skipped or produces nothing, so `--deep-enrich` can start.

Without `--project`, `<project>` is the directory's name. At the root of a linked git worktree it is the repo's name: the main checkout's, or a bare repo's (`repo/.bare` or `repo.git` → `repo`). Every worktree of a repo writes the same entry. `teamai import --dir` picks its slug the same way.

### Dashboard

```bash
teamai dashboard # Start the web dashboard (default port 3721)
teamai dashboard --port 8080
```

The sidebar contains **Overview**, **Team Execution**, **Team Context** and **Team Improvement**. Overview summarizes the three modules. Execution shows this machine's sessions, filters by working directory and AI tool, and opens complete session details. Context contains KB Health (including author contributions and never-recalled entries); Improvement contains local trends and the original promotion/archive/quality-update maintenance commands. Commands are displayed for use in your terminal; the dashboard does not execute them.
The sidebar contains **Overview**, **Team Execution**, **Team Context** and **Team Improvement**. Overview summarizes the three modules. Execution shows this machine's sessions, filters by repository (every worktree of a repo is one entry) and AI tool, and opens complete session details. Context contains KB Health (including author contributions and never-recalled entries); Improvement contains local trends and the original promotion/archive/quality-update maintenance commands. Commands are displayed for use in your terminal; the dashboard does not execute them.

Use the header to select English or Simplified Chinese and light, dark, or system theme. Preferences are saved in browser storage when available. User prompts, AI output, knowledge titles and commands are not translated. The standalone `/kb-report` remains available as the original complete report.

Expand Down Expand Up @@ -1507,7 +1509,7 @@ teamai session save --push --force # push even a trivial session
teamai session save --push --include-prompt # also include the (redacted) first-ask line
```

**Local (always):** appends to `~/.teamai/session-logs/<year-month>.md`. Idempotent per session (a session already recorded that month is skipped), and logs older than 90 days are pruned automatically.
**Local (always):** appends to `~/.teamai/session-logs/<year-month>.md`. Idempotent per session (a session already recorded that month is skipped), and logs older than 90 days are pruned automatically. Each entry names the session's repo as `Project:`, the same for every worktree of the repo, and its working directory as `Directory:`.

**Team (`--push`, opt-in):** commits the summary directly (no PR) to `sessions/<user>/<year-month>.md` on the `teamai-reports` branch — the exact path `teamai digest` reads, so the session shows up under **Session Highlights**. Only a **valuable** session is pushed by default: one that shows friction (an interrupt / tool-reject / correction) or substantial tool use (≥ 3 distinct tools). Trivial sessions stay local unless you pass `--force`. On a read-only (HTTP-mode) team, `--push` fails gracefully and the local log is still kept.

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 @@ -1374,14 +1374,16 @@ teamai codebase --lint --output /path/to/repo

只要 extract 发现了组件,就会写入 `teamwiki/evidence/code/<project>/_manifest.json`(包括跳过 AI 增强或增强没有产出的情况),因此 `--deep-enrich` 可以接着跑。

不传 `--project` 时,`<project>` 取目录名;在 git 链接 worktree 的根目录下取仓库名:主检出的目录名,或 bare 仓库的名称(`repo/.bare` 或 `repo.git` → `repo`)。同一仓库的所有 worktree 写入同一个条目。`teamai import --dir` 用同样的方式确定 slug。

### Dashboard

```bash
teamai dashboard # 启动 Web 面板(默认端口 3721)
teamai dashboard --port 8080
```

侧栏包含 **Overview(总览)**、**Team Execution(团队执行)**、**Team Context(团队上下文)**、**Team Improvement(团队改进)**。总览汇总三模块;执行页展示本机会话,支持工作目录和 AI 工具筛选及完整详情;上下文页保留 KB Health(含作者贡献和从未召回条目);改进页保留本机趋势及晋升、归档、质量更新维护命令。命令需在终端使用,页面不执行维护操作。
侧栏包含 **Overview(总览)**、**Team Execution(团队执行)**、**Team Context(团队上下文)**、**Team Improvement(团队改进)**。总览汇总三模块;执行页展示本机会话,支持按仓库(同一仓库的所有 worktree 合为一项)和 AI 工具筛选及完整详情;上下文页保留 KB Health(含作者贡献和从未召回条目);改进页保留本机趋势及晋升、归档、质量更新维护命令。命令需在终端使用,页面不执行维护操作。

页头支持英文/简体中文及日间/夜间/跟随系统主题,浏览器存储可用时记住偏好。用户输入、AI 输出、知识标题和命令保持原文。独立 `/kb-report` 继续提供原有完整报告。

Expand Down Expand Up @@ -1446,7 +1448,7 @@ teamai session save --push --force # 即便是琐碎会话也推送
teamai session save --push --include-prompt # 额外带上(脱敏后的)首个 prompt 行
```

**本地(始终执行):** 追加到 `~/.teamai/session-logs/<年-月>.md`。按会话幂等(当月已记录的会话会跳过),且超过 90 天的日志会自动清理。
**本地(始终执行):** 追加到 `~/.teamai/session-logs/<年-月>.md`。按会话幂等(当月已记录的会话会跳过),且超过 90 天的日志会自动清理。每条记录用 `Project:` 标出会话所属的仓库(同一仓库的所有 worktree 相同),用 `Directory:` 标出其工作目录。

**团队(`--push`,需显式开启):** 直接提交(不走 PR)到 `teamai-reports` 分支的 `sessions/<user>/<年-月>.md`——正是 `teamai digest` 读取的路径,于是该会话会出现在 **Session Highlights** 板块。默认只推送**有价值**的会话:出现摩擦(interrupt / tool-reject / correction)或工具使用充分(≥ 3 种不同工具)。琐碎会话除非加 `--force`,否则只留本地。对只读(HTTP 模式)的团队,`--push` 会优雅失败并保留本地日志。

Expand Down
2 changes: 1 addition & 1 deletion skill-data/core/references/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ Generated: do not edit by hand. Regenerate with
- `teamai codebase` — Inspect and maintain team-codebase outputs
- `--extract [path]` — Extract code knowledge and build graph from source
- `--incremental` (hidden) — Only re-extract changed files (requires prior manifest)
- `--project <name>` (hidden) — Project slug for --extract (defaults to directory name) and required for --deep-enrich
- `--project <name>` (hidden) — Project slug for --extract (defaults to the directory name; a linked worktree's root uses the repo's name) and required for --deep-enrich
- `--max-files <n>` (hidden) — Max source files to scan (default: 200)
- `--upgrade-wiki` (hidden) — Migrate docs/team-codebase/ to teamwiki/ graph format
- `--lint` — Run global consistency lint over the teamwiki knowledge graph
Expand Down
85 changes: 85 additions & 0 deletions src/__tests__/anchors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { realpathSync } from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { resolveAnchors, listWorktrees } from '../utils/git.js';
import { defaultProjectSlug } from '../codebase-extract.js';

// ─── Real-git tests for resolveAnchors (issue #374 P0) ──────────────────────
//
Expand Down Expand Up @@ -121,6 +122,90 @@ describe('resolveAnchors', () => {
});
});

describe('resolveAnchors memo (#809)', () => {
const freshRepo = (name: string) => {
const dir = path.join(base, name);
fs.mkdirSync(dir);
git(dir, 'init', '-q');
git(dir, 'config', 'user.email', 'test@example.com');
git(dir, 'config', 'user.name', 'Test');
git(dir, 'commit', '--allow-empty', '-q', '-m', 'init');
return dir;
};

it('answers a directory it resolved before without running git again', async () => {
const dir = freshRepo('memo-hit');
const first = await resolveAnchors(dir);
expect(first).not.toBeNull();
// With .git gone, git would find no repository here.
fs.renameSync(path.join(dir, '.git'), path.join(dir, 'git-moved'));
expect(await resolveAnchors(dir)).toEqual(first);
});

it('does not remember a directory that was not a repository', async () => {
const dir = path.join(base, 'memo-miss');
fs.mkdirSync(dir);
expect(await resolveAnchors(dir)).toBeNull();
git(dir, 'init', '-q');
git(dir, 'config', 'user.email', 'test@example.com');
git(dir, 'config', 'user.name', 'Test');
git(dir, 'commit', '--allow-empty', '-q', '-m', 'init');
expect(await resolveAnchors(dir)).toEqual({ workspaceRoot: dir, projectAnchor: dir });
});

it('keeps one entry per directory', async () => {
const a = freshRepo('memo-a');
const b = freshRepo('memo-b');
expect((await resolveAnchors(a))?.projectAnchor).toBe(a);
expect((await resolveAnchors(b))?.projectAnchor).toBe(b);
const sub = path.join(worktreeRoot, 'memo-sub');
fs.mkdirSync(sub);
expect((await resolveAnchors(sub))?.workspaceRoot).toBe(worktreeRoot);
});
});

describe('defaultProjectSlug (#809)', () => {
it('names a linked worktree\'s root after its main checkout', async () => {
expect(await defaultProjectSlug(worktreeRoot)).toBe('main-repo');
});

it('keeps the directory\'s own name everywhere else', async () => {
const mainSub = path.join(repoRoot, 'pkg', 'api');
const worktreeSub = path.join(worktreeRoot, 'pkg', 'web');
fs.mkdirSync(mainSub, { recursive: true });
fs.mkdirSync(worktreeSub, { recursive: true });
expect(await defaultProjectSlug(repoRoot)).toBe('main-repo');
expect(await defaultProjectSlug(mainSub)).toBe('api');
expect(await defaultProjectSlug(worktreeSub)).toBe('web');
expect(await defaultProjectSlug(nonGitDir)).toBe('plain');
const file = path.join(repoRoot, 'README.md');
fs.writeFileSync(file, '# readme\n');
expect(await defaultProjectSlug(file)).toBe('README.md');
});

it('names every worktree of a bare repo after the repo, not its git directory', async () => {
// repo/.bare + repo/<worktree>
const bare = path.join(base, 'bare-layout', '.bare');
execFileSync('git', ['clone', '-q', '--bare', repoRoot, bare]);
for (const wt of ['main', 'feature']) {
const checkout = path.join(base, 'bare-layout', wt);
git(bare, 'worktree', 'add', '-q', checkout);
expect((await resolveAnchors(checkout))?.projectAnchor).toBe(bare);
expect(await defaultProjectSlug(checkout)).toBe('bare-layout');
}
const sub = path.join(base, 'bare-layout', 'main', 'pkg');
fs.mkdirSync(sub);
expect(await defaultProjectSlug(sub)).toBe('pkg');

// proj.git + a worktree beside it
const dotGit = path.join(base, 'proj.git');
const checkout = path.join(base, 'proj-wt');
execFileSync('git', ['clone', '-q', '--bare', repoRoot, dotGit]);
git(dotGit, 'worktree', 'add', '-q', checkout);
expect(await defaultProjectSlug(checkout)).toBe('proj');
});
});

describe('listWorktrees', () => {
it('lists the main checkout and every linked worktree (realpath\'d)', async () => {
const roots = await listWorktrees(repoRoot);
Expand Down
Loading
Loading