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
31 changes: 17 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ Supports multiple mainstream programming languages:
| Go | `.go` |
| Rust | `.rs` |
| C | `.c`, `.h` |
| Markdown | `.md`, `.mdx` |
| YAML | `.yml`, `.yaml` |

---

Expand Down Expand Up @@ -168,23 +170,22 @@ Claude will automatically invoke git-ai tools to provide deep analysis. *Enablin

```mermaid
graph TB
A[Git Repository] -->|On Commit| B["DSR (Deterministic Semantic Record)"]
B --> C[".git-ai/dsr/<commit>.json<br/>Semantic Snapshot"]
C -->|Index Rebuild| D[LanceDB Vector DB]
C -->|Index Rebuild| E[CozoDB Graph DB]
A[Git Repository] -->|On Commit| B[DSR\nDeterministic Semantic Record]
B --> C[.git-ai/dsr/commit.json\nSemantic Snapshot]
C -->|Index Rebuild| D[LanceDB\nVector Database]
C -->|Index Rebuild| E[CozoDB\nGraph Database]
D --> F[MCP Server]
E --> F
F -->|Tool Call| G["AI Agent<br/>Claude Desktop / Trae"]
F -->|Tool Call| G[AI Agent\nClaude Desktop / Trae]
F -->|CLI| H[Developer]
C -->|Cross-Version| I{"Semantic Timeline<br/>Traceable, Comparable, Evolvable"}

style B fill:#e1f5ff
style C fill:#e8f5e9
style D fill:#fff4e1
style E fill:#fff4e1
style F fill:#e8f5e9
style G fill:#f3e5f5
style I fill:#fce4ec
C -->|Cross-Version| I[Semantic Timeline\nTraceable · Comparable · Evolvable]
style B fill:#e1f5ff,stroke:#333
style C fill:#e8f5e9,stroke:#333
style D fill:#fff4e1,stroke:#333
style E fill:#fff4e1,stroke:#333
style F fill:#e8f5e9,stroke:#333
style G fill:#f3e5f5,stroke:#333
style I fill:#fce4ec,stroke:#333
```

**Core Components**:
Expand Down Expand Up @@ -296,6 +297,8 @@ We provide carefully designed Agent templates to help AI use git-ai better:
- [Skill Template](./templates/agents/common/skills/git-ai-mcp/SKILL.md): Guides Agents on how to use tools
- [Rule Template](./templates/agents/common/rules/git-ai-mcp/RULE.md): Constrains Agent behavior

Skills/Rules docs (Markdown/YAML) are indexed as part of semantic search, so agents can retrieve MCP guidance via `semantic_search`.

One-click install to your project:

```bash
Expand Down
4 changes: 4 additions & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ git-ai ai dsr context
| Go | `.go` |
| Rust | `.rs` |
| C | `.c`, `.h` |
| Markdown | `.md`, `.mdx` |
| YAML | `.yml`, `.yaml` |

---

Expand Down Expand Up @@ -296,6 +298,8 @@ Claude 会自动调用 git-ai 的工具,为你提供深入的分析。
- [Skill 模版](./templates/agents/common/skills/git-ai-mcp/SKILL.md):指导 Agent 如何使用工具
- [Rule 模版](./templates/agents/common/rules/git-ai-mcp/RULE.md):约束 Agent 的行为

Skills/Rules 文档(Markdown/YAML)会被纳入语义索引,便于通过 `semantic_search` 检索 MCP 指南。

一键安装到你的项目:

```bash
Expand Down
9 changes: 7 additions & 2 deletions docs/zh-CN/mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ git-ai ai serve
- `unpack_index({ path })`:解包索引归档

### 检索
- `search_symbols({ path, query, mode?, case_insensitive?, max_candidates?, limit?, lang?, with_repo_map?, repo_map_max_files?, repo_map_max_symbols?, wiki_dir? })`:符号检索(lang: auto/all/java/ts;可选附带 repo_map)
- `semantic_search({ path, query, topk?, lang?, with_repo_map?, repo_map_max_files?, repo_map_max_symbols?, wiki_dir? })`:基于 LanceDB + SQ8 的语义检索(lang: auto/all/java/ts;可选附带 repo_map)
- `search_symbols({ path, query, mode?, case_insensitive?, max_candidates?, limit?, lang?, with_repo_map?, repo_map_max_files?, repo_map_max_symbols?, wiki_dir? })`:符号检索(lang: auto/all/java/ts/python/go/rust/c/markdown/yaml;可选附带 repo_map)
- `semantic_search({ path, query, topk?, lang?, with_repo_map?, repo_map_max_files?, repo_map_max_symbols?, wiki_dir? })`:基于 LanceDB + SQ8 的语义检索(lang: auto/all/java/ts/python/go/rust/c/markdown/yaml;可选附带 repo_map)
- `repo_map({ path, max_files?, max_symbols?, wiki_dir? })`:生成 repo map(重要文件/符号排名、引导 Wiki 阅读)
- `ast_graph_find({ path, prefix, limit?, lang? })`:按名字前缀查找符号定义(大小写不敏感;lang: auto/all/java/ts)
- `ast_graph_children({ path, id, as_file? })`:列出包含关系的直接子节点(文件→顶层符号、类→方法等)
Expand Down Expand Up @@ -122,6 +122,8 @@ semantic_search({ path: "/ABS/PATH/TO/REPO", query: "where is auth handled", top

本仓库提供了 Agent 可直接复用的 Skill/Rule 模板,旨在让 Agent 能够遵循最佳实践来使用上述工具。

这些模板文档(Markdown/YAML)会被索引,便于 Agent 通过 `semantic_search` 检索 MCP 规则与技能说明。

### YAML 格式模板

- **Skill**: [`templates/agents/common/skills/git-ai/skill.yaml`](../../templates/agents/common/skills/git-ai/skill.yaml) - 指导 Agent 如何使用 git-ai 的 Git-native 语义体系(包含 DSR 约束)与 MCP 工具
Expand Down Expand Up @@ -174,6 +176,9 @@ git-ai ai agent install --agent trae
- **use_dsr_for_history**: 追溯历史必须使用 DSR 工具
- **respect_dsr_risk**: 重视 DSR 报告的 high 风险变更

文档/规则检索建议:
- 当问题涉及 MCP/Skill/Rule 时,先用 `semantic_search` 定位对应文档片段,再给出结论。

禁止事项包括:
- 假设符号位置而不搜索
- 直接修改未读过的文件
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": "2.0.0",
"version": "2.1.0",
"main": "dist/index.js",
"bin": {
"git-ai": "dist/bin/git-ai.js"
Expand Down
10 changes: 5 additions & 5 deletions src/commands/graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const graphCommand = new Command('graph')
.description('Find symbols by name prefix')
.argument('<prefix>', 'Name prefix (case-insensitive)')
.option('-p, --path <path>', 'Path inside the repository', '.')
.option('--lang <lang>', 'Language: auto|all|java|ts|python|go|rust|c', 'auto')
.option('--lang <lang>', 'Language: auto|all|java|ts|python|go|rust|c|markdown|yaml', 'auto')
.action(async (prefix, options) => {
const log = createLogger({ component: 'cli', cmd: 'ai graph find' });
const startedAt = Date.now();
Expand Down Expand Up @@ -77,7 +77,7 @@ export const graphCommand = new Command('graph')
.argument('<name>', 'Symbol name')
.option('-p, --path <path>', 'Path inside the repository', '.')
.option('--limit <n>', 'Limit results', '200')
.option('--lang <lang>', 'Language: auto|all|java|ts|python|go|rust|c', 'auto')
.option('--lang <lang>', 'Language: auto|all|java|ts|python|go|rust|c|markdown|yaml', 'auto')
.action(async (name, options) => {
const log = createLogger({ component: 'cli', cmd: 'ai graph refs' });
const startedAt = Date.now();
Expand Down Expand Up @@ -108,7 +108,7 @@ export const graphCommand = new Command('graph')
.argument('<name>', 'Callee name')
.option('-p, --path <path>', 'Path inside the repository', '.')
.option('--limit <n>', 'Limit results', '200')
.option('--lang <lang>', 'Language: auto|all|java|ts|python|go|rust|c', 'auto')
.option('--lang <lang>', 'Language: auto|all|java|ts|python|go|rust|c|markdown|yaml', 'auto')
.action(async (name, options) => {
const log = createLogger({ component: 'cli', cmd: 'ai graph callers' });
const startedAt = Date.now();
Expand Down Expand Up @@ -139,7 +139,7 @@ export const graphCommand = new Command('graph')
.argument('<name>', 'Caller name')
.option('-p, --path <path>', 'Path inside the repository', '.')
.option('--limit <n>', 'Limit results', '200')
.option('--lang <lang>', 'Language: auto|all|java|ts|python|go|rust|c', 'auto')
.option('--lang <lang>', 'Language: auto|all|java|ts|python|go|rust|c|markdown|yaml', 'auto')
.action(async (name, options) => {
const log = createLogger({ component: 'cli', cmd: 'ai graph callees' });
const startedAt = Date.now();
Expand Down Expand Up @@ -173,7 +173,7 @@ export const graphCommand = new Command('graph')
.option('--depth <n>', 'Max depth', '3')
.option('--limit <n>', 'Limit results', '500')
.option('--min-name-len <n>', 'Filter out edges with very short names (default: 1)', '1')
.option('--lang <lang>', 'Language: auto|all|java|ts|python|go|rust|c', 'auto')
.option('--lang <lang>', 'Language: auto|all|java|ts|python|go|rust|c|markdown|yaml', 'auto')
.action(async (name, options) => {
const log = createLogger({ component: 'cli', cmd: 'ai graph chain' });
const startedAt = Date.now();
Expand Down
4 changes: 3 additions & 1 deletion src/commands/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const queryCommand = new Command('query')
.option('--mode <mode>', 'Mode: substring|prefix|wildcard|regex|fuzzy (default: auto)')
.option('--case-insensitive', 'Case-insensitive matching', false)
.option('--max-candidates <n>', 'Max candidates to fetch before filtering', '1000')
.option('--lang <lang>', 'Language: auto|all|java|ts|python|go|rust|c', 'auto')
.option('--lang <lang>', 'Language: auto|all|java|ts|python|go|rust|c|markdown|yaml', 'auto')
.option('--with-repo-map', 'Attach a lightweight repo map (ranked files + top symbols + wiki links)', false)
.option('--repo-map-files <n>', 'Max repo map files', '20')
.option('--repo-map-symbols <n>', 'Max repo map symbols per file', '5')
Expand Down Expand Up @@ -110,6 +110,8 @@ function resolveWikiDir(repoRoot: string, wikiOpt: string): string {

function inferLangFromFile(file: string): IndexLang {
const f = String(file);
if (f.endsWith('.md') || f.endsWith('.mdx')) return 'markdown';
if (f.endsWith('.yml') || f.endsWith('.yaml')) return 'yaml';
if (f.endsWith('.java')) return 'java';
if (f.endsWith('.c') || f.endsWith('.h')) return 'c';
if (f.endsWith('.go')) return 'go';
Expand Down
2 changes: 1 addition & 1 deletion src/commands/semantic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const semanticCommand = new Command('semantic')
.argument('<text>', 'Query text')
.option('-p, --path <path>', 'Path inside the repository', '.')
.option('-k, --topk <k>', 'Top K results', '10')
.option('--lang <lang>', 'Language: auto|all|java|ts|python|go|rust|c', 'auto')
.option('--lang <lang>', 'Language: auto|all|java|ts|python|go|rust|c|markdown|yaml', 'auto')
.option('--with-repo-map', 'Attach a lightweight repo map (ranked files + top symbols + wiki links)', false)
.option('--repo-map-files <n>', 'Max repo map files', '20')
.option('--repo-map-symbols <n>', 'Max repo map symbols per file', '5')
Expand Down
12 changes: 12 additions & 0 deletions src/core/dsr/snapshotParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { CAdapter } from '../parser/c';
import { GoAdapter } from '../parser/go';
import { PythonAdapter } from '../parser/python';
import { RustAdapter } from '../parser/rust';
import { parseMarkdown } from '../parser/markdown';
import { parseYaml } from '../parser/yaml';

export interface ParsedSymbolSnapshot {
symbol: SymbolInfo;
Expand All @@ -31,6 +33,8 @@ export class SnapshotCodeParser {
}

parseContent(filePath: string, content: string): ParseResult {
if (isMarkdownFile(filePath)) return parseMarkdown(content, filePath);
if (isYamlFile(filePath)) return parseYaml(content, filePath);
const adapter = this.pickAdapter(filePath);
if (!adapter) return { symbols: [], refs: [] };
try {
Expand Down Expand Up @@ -59,3 +63,11 @@ export class SnapshotCodeParser {
return null;
}
}

function isMarkdownFile(filePath: string): boolean {
return filePath.endsWith('.md') || filePath.endsWith('.mdx');
}

function isYamlFile(filePath: string): boolean {
return filePath.endsWith('.yml') || filePath.endsWith('.yaml');
}
2 changes: 1 addition & 1 deletion src/core/indexCheck.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export function resolveLangs(meta: IndexMetaV21 | null, selector: LangSelector):
const available = filtered.length > 0 ? filtered : (['java', 'ts'] as IndexLang[]);
if (selector === 'all') return available;
if (selector !== 'auto') return available.includes(selector) ? [selector] : [];
const preferred: IndexLang[] = ['java', 'ts', 'python', 'go', 'rust', 'c'];
const preferred: IndexLang[] = ['java', 'ts', 'python', 'go', 'rust', 'c', 'markdown', 'yaml'];
for (const lang of preferred) {
if (available.includes(lang)) return [lang];
}
Expand Down
4 changes: 3 additions & 1 deletion src/core/indexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ function buildChunkText(file: string, symbol: { name: string; kind: string; sign
}

function inferIndexLang(file: string): IndexLang {
if (file.endsWith('.md') || file.endsWith('.mdx')) return 'markdown';
if (file.endsWith('.yml') || file.endsWith('.yaml')) return 'yaml';
if (file.endsWith('.java')) return 'java';
if (file.endsWith('.c') || file.endsWith('.h')) return 'c';
if (file.endsWith('.go')) return 'go';
Expand Down Expand Up @@ -73,7 +75,7 @@ export class IndexerV2 {

const aiIgnore = await loadIgnorePatterns(this.repoRoot, '.aiignore');
const gitIgnore = await loadIgnorePatterns(this.repoRoot, '.gitignore');
const files = await glob('**/*.{ts,tsx,js,jsx,java,c,h,go,py,rs}', {
const files = await glob('**/*.{ts,tsx,js,jsx,java,c,h,go,py,rs,md,mdx,yml,yaml}', {
cwd: this.scanRoot,
nodir: true,
ignore: [
Expand Down
4 changes: 3 additions & 1 deletion src/core/indexerIncremental.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ function buildChunkText(file: string, symbol: { name: string; kind: string; sign
}

function inferIndexLang(file: string): IndexLang {
if (file.endsWith('.md') || file.endsWith('.mdx')) return 'markdown';
if (file.endsWith('.yml') || file.endsWith('.yaml')) return 'yaml';
if (file.endsWith('.java')) return 'java';
if (file.endsWith('.c') || file.endsWith('.h')) return 'c';
if (file.endsWith('.go')) return 'go';
Expand All @@ -34,7 +36,7 @@ function inferIndexLang(file: string): IndexLang {
}

function isIndexableFile(file: string): boolean {
return /\.(ts|tsx|js|jsx|java|c|h|go|py|rs)$/i.test(file);
return /\.(ts|tsx|js|jsx|java|c|h|go|py|rs|md|mdx|yml|yaml)$/i.test(file);
}

function escapeQuotes(s: string): string {
Expand Down
4 changes: 2 additions & 2 deletions src/core/lancedb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { Field, Float32, Int32, Schema, Utf8 } from 'apache-arrow';
import fs from 'fs-extra';
import path from 'path';

export type IndexLang = 'java' | 'ts' | 'c' | 'go' | 'python' | 'rust';
export type IndexLang = 'java' | 'ts' | 'c' | 'go' | 'python' | 'rust' | 'markdown' | 'yaml';

export const ALL_INDEX_LANGS: IndexLang[] = ['java', 'ts', 'c', 'go', 'python', 'rust'];
export const ALL_INDEX_LANGS: IndexLang[] = ['java', 'ts', 'c', 'go', 'python', 'rust', 'markdown', 'yaml'];

export interface LanceTables {
db: lancedb.Connection;
Expand Down
12 changes: 12 additions & 0 deletions src/core/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { CAdapter } from './parser/c';
import { GoAdapter } from './parser/go';
import { PythonAdapter } from './parser/python';
import { RustAdapter } from './parser/rust';
import { parseMarkdown } from './parser/markdown';
import { parseYaml } from './parser/yaml';

export class CodeParser {
private parser: Parser;
Expand All @@ -28,6 +30,8 @@ export class CodeParser {

async parseFile(filePath: string): Promise<ParseResult> {
const content = await fs.readFile(filePath, 'utf-8');
if (isMarkdownFile(filePath)) return parseMarkdown(content, filePath);
if (isYamlFile(filePath)) return parseYaml(content, filePath);
const adapter = this.pickAdapter(filePath);
if (!adapter) return { symbols: [], refs: [] };

Expand Down Expand Up @@ -58,3 +62,11 @@ export class CodeParser {
return null;
}
}

function isMarkdownFile(filePath: string): boolean {
return filePath.endsWith('.md') || filePath.endsWith('.mdx');
}

function isYamlFile(filePath: string): boolean {
return filePath.endsWith('.yml') || filePath.endsWith('.yaml');
}
Loading