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 .git-ai/lancedb.tar.gz
Git LFS file not shown
76 changes: 58 additions & 18 deletions docs/zh-CN/mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,43 @@ git-ai ai serve
- `list_files({ path, pattern?, limit? })`:按 glob 列文件(默认忽略 node_modules, .git 等)
- `read_file({ path, file, start_line?, end_line? })`:按行读取文件片段

### DSR (Deterministic Semantic Record)
- `dsr_context({ path })`:获取仓库 Git 上下文和 DSR 目录状态
- 返回:commit_hash, repo_root, branch, detached, dsr_directory_state
- `dsr_generate({ path, commit })`:为指定提交生成 DSR
- 返回:commit_hash, file_path, existed, counts, semantic_change_type, risk_level
- `dsr_rebuild_index({ path })`:从 DSR 文件重建索引,加速查询
- 返回:enabled, engine, dbPath, counts
- `dsr_symbol_evolution({ path, symbol, start?, all?, limit?, contains? })`:追溯符号变更历史
- 返回:ok, hits(包含 commit_hash, semantic_change_type, risk_level, operations)

## DSR 使用示例

获取仓库 Git 状态和 DSR 情况:

```js
dsr_context({ path: "/ABS/PATH/TO/REPO" })
```

为最近几次提交生成 DSR:

```js
dsr_generate({ path: "/ABS/PATH/TO/REPO", commit: "HEAD" })
dsr_generate({ path: "/ABS/PATH/TO/REPO", commit: "HEAD~1" })
```

查询某个函数的变更历史:

```js
dsr_symbol_evolution({ path: "/ABS/PATH/TO/REPO", symbol: "handleRequest", limit: 50 })
```

模糊匹配符号名称:

```js
dsr_symbol_evolution({ path: "/ABS/PATH/TO/REPO", symbol: "Request", contains: true, limit: 100 })
```

## AST 图查询示例

列出指定文件里的顶层符号(推荐:无需手动算 file_id):
Expand Down Expand Up @@ -119,37 +156,40 @@ git-ai ai agent install --agent trae

根据 `skill.yaml`,推荐的工作流程:

1. **绑定仓库** (`bind_repo`) - 确保仓库已绑定
2. **确保索引新鲜** (`ensure_index`) - 必要时重建索引
3. **定位符号** (`locate_symbols`) - 使用 `search_symbols` 精确查找
4. **语义搜索** (`semantic_search`) - 自然语言描述搜索
5. **浏览文件** (`browse_files`) - 使用 `list_files` 查找文件
6. **AST 查询** (`ast_query`) - 递归/关系类查询
7. **读取代码** (`read_code`) - 使用 `read_file` 读取关键片段
1. **首次接触仓库** - 使用 `repo_map` 获取全局视图
2. **检查索引状态** - 使用 `check_index`,必要时 `rebuild_index`
3. **定位目标代码** - 使用 `search_symbols` 或 `semantic_search`
4. **理解代码关系** - 使用 `ast_graph_callers/callees/chain`
5. **追溯变更历史** - 使用 `dsr_symbol_evolution`
6. **精读代码** - 使用 `read_file` 读取关键片段
7. **提供建议** - 基于完整理解给出修改建议

### Rule 约束概览

根据 `rule.yaml`,Agent 必须遵守:

- **bind_repo_first**: 先绑定仓库再操作
- **index_before_search**: 搜索无结果时先重建索引
- **evidence_based_conclusion**: 结论必须有证据(文件+行号)
- **path_safety**: 禁止读取仓库外路径
- **storage_cost_evaluation**: 评估存储成本
- **explicit_path**: 每次调用必须显式传 `path` 参数
- **check_index_first**: 符号搜索前必须检查索引
- **understand_before_modify**: 修改前必须先理解实现
- **use_dsr_for_history**: 追溯历史必须使用 DSR 工具
- **respect_dsr_risk**: 重视 DSR 报告的 high 风险变更

禁止事项包括:
- 默认使用外部 embedding 服务
- 直接提交 `.git-ai/lancedb/` 目录
- 使用 `../` 读取外部文件
- 假设索引是最新的而不检查
- 假设符号位置而不搜索
- 直接修改未读过的文件
- 手动解析 git 历史
- 在索引缺失时进行符号搜索
- 省略 `path` 参数

## DSR 与 MCP 的关系

- MCP tools 主要覆盖“索引(.git-ai)构建与检索”,用于让 Agent 低成本定位证据
- DSR 是“按提交的语义工件(.git-ai/dsr)”,用于语义历史/演化类查询与可重建缓存
- **MCP tools** 主要覆盖"索引(.git-ai)构建与检索",用于让 Agent 低成本定位证据
- **DSR** 是"按提交的语义工件(.git-ai/dsr)",用于语义历史/演化类查询
- DSR 是 per-commit、immutable、deterministic 的,可用于精确追溯符号变更
- 任何历史遍历都必须从 Git DAG 出发(DSR 只 enrich 节点,不定义边)

DSR 相关命令见:[DSR 文档](./dsr.md)

## 输出要求

Agent 使用 git-ai MCP 工具时应遵循:
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "git-ai",
"version": "1.1.2",
"version": "2.0.0",
"main": "dist/index.js",
"bin": {
"git-ai": "dist/bin/git-ai.js"
Expand Down
118 changes: 117 additions & 1 deletion src/mcp/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ import { toPosixPath } from '../core/paths';
import { createLogger } from '../core/log';
import { checkIndex, resolveLangs } from '../core/indexCheck';
import { generateRepoMap, type FileRank } from '../core/repoMap';
import { detectRepoGitContext } from '../core/dsr/gitContext';
import { generateDsrForCommit } from '../core/dsr/generate';
import { materializeDsrIndex } from '../core/dsr/indexMaterialize';
import { symbolEvolution } from '../core/dsr/query';
import { getDsrDirectoryState } from '../core/dsr/state';

export interface GitAIV2MCPServerOptions {
disableAccessLog?: boolean;
Expand All @@ -33,7 +38,7 @@ export class GitAIV2MCPServer {
this.startDir = path.resolve(startDir);
this.options = options;
this.server = new Server(
{ name: 'git-ai-v2', version: '1.1.2' },
{ name: 'git-ai-v2', version: '2.0.0' },
{ capabilities: { tools: {} } }
);
this.setupHandlers();
Expand Down Expand Up @@ -322,6 +327,56 @@ export class GitAIV2MCPServer {
required: ['path', 'name'],
},
},
{
name: 'dsr_context',
description: 'Get repository Git context and DSR directory state. Risk: low (read-only).',
inputSchema: {
type: 'object',
properties: {
path: { type: 'string', description: 'Repository root path' },
},
required: ['path'],
},
},
{
name: 'dsr_generate',
description: 'Generate DSR (Deterministic Semantic Record) for a specific commit. Risk: medium (writes .git-ai/dsr).',
inputSchema: {
type: 'object',
properties: {
path: { type: 'string', description: 'Repository root path' },
commit: { type: 'string', description: 'Commit hash or ref' },
},
required: ['path', 'commit'],
},
},
{
name: 'dsr_rebuild_index',
description: 'Rebuild DSR index from DSR files for faster queries. Risk: medium (writes .git-ai/dsr-index).',
inputSchema: {
type: 'object',
properties: {
path: { type: 'string', description: 'Repository root path' },
},
required: ['path'],
},
},
{
name: 'dsr_symbol_evolution',
description: 'Query symbol evolution history across commits using DSR. Risk: low (read-only).',
inputSchema: {
type: 'object',
properties: {
path: { type: 'string', description: 'Repository root path' },
symbol: { type: 'string', description: 'Symbol name to query' },
start: { type: 'string', description: 'Start commit (default: HEAD)' },
all: { type: 'boolean', default: false, description: 'Traverse all refs instead of just HEAD' },
limit: { type: 'number', default: 200, description: 'Max commits to traverse' },
contains: { type: 'boolean', default: false, description: 'Match by substring instead of exact' },
},
required: ['path', 'symbol'],
},
},
],
};
});
Expand All @@ -347,6 +402,67 @@ export class GitAIV2MCPServer {
};
}

if (name === 'dsr_context') {
const repoRoot = await this.resolveRepoRoot(callPath);
const ctx = await detectRepoGitContext(repoRoot);
const state = await getDsrDirectoryState(ctx.repo_root);
return {
content: [{ type: 'text', text: JSON.stringify({
ok: true,
commit_hash: ctx.head_commit,
repo_root: ctx.repo_root,
branch: ctx.branch,
detached: ctx.detached,
dsr_directory_state: state,
}, null, 2) }],
};
}

if (name === 'dsr_generate') {
const repoRoot = await this.resolveRepoRoot(callPath);
const commit = String((args as any).commit ?? 'HEAD');
const res = await generateDsrForCommit(repoRoot, commit);
return {
content: [{ type: 'text', text: JSON.stringify({
ok: true,
commit_hash: res.dsr.commit_hash,
file_path: res.file_path,
existed: res.existed,
counts: {
affected_symbols: res.dsr.affected_symbols.length,
ast_operations: res.dsr.ast_operations.length,
},
semantic_change_type: res.dsr.semantic_change_type,
risk_level: res.dsr.risk_level,
}, null, 2) }],
};
}

if (name === 'dsr_rebuild_index') {
const repoRoot = await this.resolveRepoRoot(callPath);
const res = await materializeDsrIndex(repoRoot);
return {
content: [{ type: 'text', text: JSON.stringify({ repoRoot, ...res }, null, 2) }],
isError: !res.enabled,
};
}

if (name === 'dsr_symbol_evolution') {
const repoRoot = await this.resolveRepoRoot(callPath);
const symbol = String((args as any).symbol ?? '');
const opts = {
start: (args as any).start ? String((args as any).start) : undefined,
all: Boolean((args as any).all ?? false),
limit: Number((args as any).limit ?? 200),
contains: Boolean((args as any).contains ?? false),
};
const res = await symbolEvolution(repoRoot, symbol, opts);
return {
content: [{ type: 'text', text: JSON.stringify({ repoRoot, symbol, ...res }, null, 2) }],
isError: !res.ok,
};
}

if (name === 'check_index') {
const repoRoot = await this.resolveRepoRoot(callPath);
const res = await checkIndex(repoRoot);
Expand Down
69 changes: 59 additions & 10 deletions templates/agents/common/rules/git-ai-mcp/RULE.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,64 @@
# git-ai-mcp
# git-ai-mcp Rule

Use `git-ai` as the single entry point for indexing and semantic queries.
Agent 使用 git-ai MCP 工具的行为约束。

## Hard rules
## 必须遵守

- Determine history and branches only from Git (never from semantic artifacts)
- Treat `.git-ai/dsr/<commit_hash>.json` as immutable canonical artifacts
- Treat databases as rebuildable caches (derivable from DSR + Git)
- If DSR is missing for a commit needed by a query, report and stop (do not infer)
### explicit_path
- **级别**: error
- **规则**: 每次 MCP 工具调用必须显式传 `path` 参数。禁止依赖进程状态或工作目录隐式推断仓库位置。

## Practical defaults
### check_index_first
- **级别**: error
- **规则**: 使用 `search_symbols`、`semantic_search`、`ast_graph_*` 前,必须先调用 `check_index` 确认索引就绪。索引不兼容时必须重建,禁止在索引缺失时进行符号搜索。

- Prefer read-only operations unless explicitly asked to modify the repository
- For repository understanding, use MCP search tools first, then read code with line ranges
### understand_before_modify
- **级别**: error
- **规则**: 修改代码前必须先理解现有实现。流程:`search_symbols` 定位 → `read_file` 精读 → `ast_graph_callers` 确认影响范围 → 修改。禁止直接修改未读过的文件。

### use_dsr_for_history
- **级别**: warning
- **规则**: 追溯符号变更历史必须使用 `dsr_symbol_evolution`。禁止手动解析 git log 或 diff 来推断符号变更。

### repo_map_before_large_change
- **级别**: warning
- **规则**: 大型变更前必须使用 `repo_map` 确认影响范围。了解项目结构、关键文件、主要符号后再规划修改。

### respect_dsr_risk
- **级别**: warning
- **规则**: DSR 报告 `risk_level` 为 `high` 的变更必须谨慎对待。涉及 `delete`、`rename` 操作的变更需要额外审查。

## 推荐策略

- **优先语义搜索**: 理解功能意图时优先使用 `semantic_search`,精确定位时使用 `search_symbols`。
- **使用调用链**: 复杂调用链路使用 `ast_graph_chain` 追踪,避免手动递归查找 callers/callees。
- **按需生成 DSR**: 需要历史分析时,按需生成 DSR,避免一次性生成所有历史提交的 DSR。
- **附带上下文**: 复杂查询可附带 `repo_map`,帮助建立全局上下文。

## 禁止事项

| 行为 | 原因 |
|------|------|
| 假设符号位置而不搜索 | 必须通过 `search_symbols` 或 `semantic_search` 确认符号位置 |
| 直接修改未读过的文件 | 必须先 `read_file` 理解实现,避免破坏性变更 |
| 手动解析 git 历史 | 必须使用 `dsr_symbol_evolution` 追溯符号变更 |
| 在索引缺失时进行符号搜索 | 索引是符号搜索的前提,缺失时必须重建 |
| 忽略 DSR 风险等级 | high 风险变更需要额外审查,不能盲目应用 |
| 省略 `path` 参数 | 每次调用必须显式传 path,保证原子性和可复现性 |

## 工具使用约束

| 工具 | 使用时机 | 必传参数 | 前置检查 |
|------|----------|----------|----------|
| `repo_map` | 首次接触仓库、大型变更前 | `path` | - |
| `check_index` | 任何符号搜索前 | `path` | - |
| `rebuild_index` | 索引不兼容或缺失时 | `path` | - |
| `search_symbols` | 按名称查找符号 | `path`, `query` | `check_index` 通过 |
| `semantic_search` | 按语义查找代码 | `path`, `query` | `check_index` 通过 |
| `ast_graph_callers` | 查找调用者 | `path`, `name` | `check_index` 通过 |
| `ast_graph_callees` | 查找被调用者 | `path`, `name` | `check_index` 通过 |
| `ast_graph_chain` | 追踪调用链 | `path`, `name` | `check_index` 通过 |
| `dsr_context` | 了解仓库 Git 状态和 DSR 情况 | `path` | - |
| `dsr_generate` | 为特定提交生成 DSR | `path`, `commit` | - |
| `dsr_symbol_evolution` | 追溯符号变更历史 | `path`, `symbol` | 相关 DSR 已生成 |
| `read_file` | 精读代码 | `path`, `file` | - |
Loading