diff --git a/README.md b/README.md index 97afa1ea..58d55815 100644 --- a/README.md +++ b/README.md @@ -227,7 +227,7 @@ Matched: conflict | Missing: port teamai import --from-repo https://github.com/org/repo teamai import --from-org myorg # batch import all repos teamai codebase --extract /path/to/repo # local extract into teamwiki/ -teamai codebase --lint # health check +teamai codebase --lint --output /path/to/repo # check the locally extracted graph ``` The graph stores components, interfaces, configs, and cross-repo import edges. `teamai recall` uses it for graph-boosted re-ranking. diff --git a/README.zh-CN.md b/README.zh-CN.md index 2556f462..b008c2d6 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -227,7 +227,7 @@ Matched: conflict | Missing: port teamai import --from-repo https://github.com/org/repo teamai import --from-org myorg # 批量导入所有仓库 teamai codebase --extract /path/to/repo # 本地提取到 teamwiki/ -teamai codebase --lint # 健康检查 +teamai codebase --lint --output /path/to/repo # 检查本地提取的图谱 ``` 图谱存储组件、接口、配置和跨仓库依赖边。`teamai recall` 利用图谱进行增强排名。 diff --git a/docs/usage-guide.md b/docs/usage-guide.md index 1c8c560f..60712342 100644 --- a/docs/usage-guide.md +++ b/docs/usage-guide.md @@ -1074,8 +1074,11 @@ Dependency edges are extracted by two parallel tracks: a WASM tree-sitter **AST # Extract code facts and the graph from a local repo (writes /teamwiki/) teamai codebase --extract /path/to/repo --project my-service -# Graph health check -teamai codebase --lint +# Incremental refresh: reuse the original repository path and project slug +teamai codebase --extract /path/to/repo --project my-service --incremental + +# Check the local graph; --output is the repository root, not teamwiki/ +teamai codebase --lint --output /path/to/repo ``` ### Dashboard diff --git a/docs/usage-guide.zh-CN.md b/docs/usage-guide.zh-CN.md index 24384beb..2422482d 100644 --- a/docs/usage-guide.zh-CN.md +++ b/docs/usage-guide.zh-CN.md @@ -1062,8 +1062,11 @@ teamai import --from-repo https://github.com/org/repo --skip-enrich # 从本地仓库提取代码事实与图谱(写入 /teamwiki/) teamai codebase --extract /path/to/repo --project my-service -# 图谱健康检查 -teamai codebase --lint +# 增量刷新:复用首次提取的仓库路径和项目名 +teamai codebase --extract /path/to/repo --project my-service --incremental + +# 检查本地提取的图谱;--output 指向仓库根目录,而非 teamwiki/ +teamai codebase --lint --output /path/to/repo ``` ### Dashboard diff --git a/skills/team-wiki-codebase/SKILL.md b/skills/team-wiki-codebase/SKILL.md index 896e470f..ad6f0863 100644 --- a/skills/team-wiki-codebase/SKILL.md +++ b/skills/team-wiki-codebase/SKILL.md @@ -892,8 +892,8 @@ _review/ ← 过程文件(不入知识库) | K3 后编译进 wiki | Skip. TeamAI does not ship a separate team-wiki CLI. Continue with this skill using `teamai` and the files under this skill directory. No extra plugin is required. | | 产品文档入图 | Skip. Same English note as above. | | 产品↔代码桥接 | Skip. Same English note as above. | -| 一键刷新 | Use `teamai codebase --extract --incremental` when updating a repo already extracted. Do not look for another CLI. | -| 质量评估 | Use `scripts/validate_kb.py` and `teamai codebase --lint` on `teamwiki/`. Skip any extra evaluate binary. | +| 一键刷新 | Use `teamai codebase --extract --project --incremental`, reusing the Phase 0 repository path and project slug even when running from another directory. Do not look for another CLI. | +| 质量评估 | Use `scripts/validate_kb.py` and `teamai codebase --lint --output ` to check `/teamwiki/` (`--output` takes the repository root, not the `teamwiki/` directory). Skip any extra evaluate binary. | **路径约定**(本 skill 安装后): diff --git a/src/__tests__/e2e/codebase-extract-cli.test.ts b/src/__tests__/e2e/codebase-extract-cli.test.ts index b0ac8c6e..04193509 100644 --- a/src/__tests__/e2e/codebase-extract-cli.test.ts +++ b/src/__tests__/e2e/codebase-extract-cli.test.ts @@ -77,4 +77,47 @@ describe('teamai codebase extract CLI (issue #360 slice 1)', () => { fs.rmSync(fixture, { recursive: true, force: true }); } }); + + it('refreshes and lints the same local graph using the skill commands from another directory', async () => { + const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'teamai-local-workflow-')); + const caller = fs.mkdtempSync(path.join(os.tmpdir(), 'teamai-workflow-caller-')); + try { + const skill = fs.readFileSync(path.join(ROOT, 'skills/team-wiki-codebase/SKILL.md'), 'utf8'); + const commands = [...skill.matchAll(/`(teamai codebase [^`]+)`/g)].map(match => match[1]); + const refresh = commands.find(command => command.includes('--incremental')); + const lint = commands.find(command => command.includes('--lint')); + expect(refresh).toBeDefined(); + expect(lint).toBeDefined(); + const args = (command: string) => command.split(/\s+/).slice(1) + .map(arg => arg === '' ? fixture : arg === '' ? 'custom-service' : arg); + const source = path.join(fixture, 'greet.ts'); + fs.writeFileSync(source, 'export function originalGreeting() { return "hello"; }\n'); + const initial = await runCLI( + ['codebase', '--extract', fixture, '--project', 'custom-service', '--json'], caller, + ); + expect(initial.code, initial.output).toBe(0); + fs.writeFileSync(source, 'export function updatedGreeting() { return "updated"; }\n'); + + const refreshed = await runCLI([...args(refresh!), '--json'], caller); + expect(refreshed.code, refreshed.output).toBe(0); + expect(JSON.parse(refreshed.stdout)).toMatchObject({ project: 'custom-service', incremental: true }); + const wiki = path.join(fixture, 'teamwiki'); + expect(fs.readdirSync(path.join(wiki, 'evidence', 'code'))).toEqual(['custom-service']); + const evidence = fs.readFileSync(path.join(wiki, 'evidence', 'code', 'custom-service', 'component.md'), 'utf8'); + expect(evidence).toContain('updatedGreeting'); + expect(evidence).not.toContain('originalGreeting'); + expect(fs.readFileSync(path.join(wiki, 'router.md'), 'utf8')).toContain('evidence/code/custom-service/index'); + expect(fs.existsSync(path.join(caller, 'teamwiki'))).toBe(false); + + const checked = await runCLI([...args(lint!), '--json'], caller); + expect(checked.code, checked.output).toBe(0); + const report = JSON.parse(checked.stdout); + const graph = JSON.parse(fs.readFileSync(path.join(wiki, '.indices', 'graph-index.json'), 'utf8')); + expect(report.graphHealth.nodeCount).toBe(graph.nodes.length); + expect(report.graphHealth.nodeCount).toBeGreaterThan(0); + } finally { + fs.rmSync(fixture, { recursive: true, force: true }); + fs.rmSync(caller, { recursive: true, force: true }); + } + }); });