ktworkflow-mcp/src/tools/docSync.ts

140 lines
6.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { existsSync } from 'node:fs';
import path from 'node:path';
import type { ChangeDocSyncInput } from '../types.js';
import { detectRepoType, parseGitStatusFiles } from '../core/project.js';
import { tryExecFile } from '../core/exec.js';
import { resolveProject, toPosix, workspaceRoot } from '../core/workspace.js';
interface DocSyncRule {
docs: string[];
match: RegExp;
reason: string;
}
const docSyncRules: DocSyncRule[] = [
{
docs: ['Node/kt-template-online-api/README.md', 'Node/kt-template-online-api/API.md', 'docs/obsidian/modules/KT 模块 - API Backend.md'],
match: /^(src\/|Node\/kt-template-online-api\/src\/).*(controller|service|dto|entity|module|guard|interceptor|filter)|^Node\/kt-template-online-api\/src\//,
reason: 'API 接口、DTO/Entity、模块或服务能力变更需要同步后端 README/API 和模块索引。',
},
{
docs: ['Node/kt-template-online-api/API.md', 'docs/qqbot-nas-runtime.md', 'docs/obsidian/modules/KT 模块 - QQBot BangDream FFLogs.md'],
match: /qqbot|bangDream|fflogs|ff14Market|napcat/i,
reason: 'QQBot/BangDream/FFLogs/NapCat 链路变更,需要同步命令、运行态和 smoke 文档。',
},
{
docs: ['Vue/kt-template-admin/README.md', 'docs/obsidian/modules/KT 模块 - Admin.md'],
match: /Vue\/kt-template-admin\/|apps\/web-antdv-next\/src\/views\/|apps\/web-antdv-next\/src\/api\//,
reason: 'Admin 页面、API 客户端或业务视图变更,需要同步 Admin README 和模块页。',
},
{
docs: ['Vue/kt-template-admin/README.md', 'docs/obsidian/modules/KT 模块 - KtTable.md', 'skills/kt-component-specialist/SKILL.md'],
match: /ktTable|KtTable|components\/ktTable/i,
reason: 'KtTable 组件或迁移规则变更,需要同步专项模块和 skill。',
},
{
docs: ['Vue/kt-blog-web/README.md', 'docs/obsidian/modules/KT 模块 - Blog Web.md', 'skills/kt-blog-argon-workflow/SKILL.md'],
match: /Vue\/kt-blog-web\/|blog|argon/i,
reason: 'Blog/Argon 能力变更,需要同步博客 README、模块页和专项 skill。',
},
{
docs: ['Plugins/fnos-k8s-dashboard-fpk/README.md', 'Plugins/fnos-k8s-dashboard-fpk/AGENTS.md', 'docs/obsidian/modules/KT 模块 - Plugins.md'],
match: /fnos-k8s-dashboard|fnosK8s|fpk|Kubernetes/i,
reason: 'fnOS K8s 插件变更,需要同步插件 README/AGENTS 和 Obsidian 模块。',
},
{
docs: ['mcp/ktWorkflow/README.md', 'AGENTS.md', 'TASKS.md', 'docs/kt-testing-workflows.md', 'docs/obsidian/KT 工作流工程化.md'],
match: /mcp\/ktWorkflow|src\/tools\/|src\/registerTools|src\/core|workflow|cleanup|review|selfTest/i,
reason: 'ktWorkflow 或自动化规则变更,需要同步 README、根规则、测试流程和当前任务记录。',
},
{
docs: ['AGENTS.md', 'SKILLS.md', 'docs/kt-context-semantics.md', 'docs/obsidian/modules/KT 模块 - Root Governance.md'],
match: /AGENTS\.md|SKILLS\.md|skills\/.*\/SKILL\.md|docs\/kt-context-semantics\.md/,
reason: '上下文治理或 skill 规则变更,需要同步根治理文档和 Obsidian 模块。',
},
{
docs: ['docs/obsidian/KT 文档矩阵.md', 'docs/obsidian/KT Obsidian 工程化.md', 'docs/obsidian/KT 关系图谱.canvas'],
match: /\.obsidian|docs\/obsidian|\.canvas$|\.base$/,
reason: 'Obsidian vault、Canvas、Base 或文档矩阵变更,需要同步图谱和校验。',
},
{
docs: ['docs/kt-testing-workflows.md', 'mcp/ktWorkflow/README.md'],
match: /\.kt-workspace|test-artifacts|test-logs|verify|playwright|smoke|Jenkinsfile|k8s\/|deploy\//i,
reason: '测试、验证、发布或运行态产物流程变更,需要同步测试流程和 ktWorkflow 文档。',
},
];
async function inferChangedFiles(projectKey: string): Promise<string[]> {
const project = resolveProject(projectKey);
if (detectRepoType(project.path) !== 'git') return [];
const status = await tryExecFile('git', ['status', '--short'], project.path);
if (!status.ok) return [];
return parseGitStatusFiles(status.stdout).map((file) =>
project.alias && project.alias !== 'root'
? `${project.relativePath}/${file.replaceAll('\\', '/')}`
: file.replaceAll('\\', '/'),
);
}
function normalizeChangedFile(file: string, projectKey?: string): string {
const project = resolveProject(projectKey || 'root');
const normalized = file.replaceAll('\\', '/');
if (project.alias && project.alias !== 'root' && !normalized.startsWith(`${project.relativePath}/`)) {
return `${project.relativePath}/${normalized}`;
}
return normalized;
}
export async function buildChangeDocSync(input: ChangeDocSyncInput = {}): Promise<Record<string, unknown>> {
const projectKey = input.project || 'root';
const changedFiles = (input.changedFiles?.length ?? 0) > 0
? input.changedFiles!.map((file) => normalizeChangedFile(file, projectKey))
: await inferChangedFiles(projectKey);
const recommendations = docSyncRules
.map((rule) => {
const matchedFiles = changedFiles.filter((file) => rule.match.test(file));
return matchedFiles.length > 0
? {
docs: rule.docs.map((file) => ({
exists: existsSync(path.resolve(workspaceRoot, file)),
path: file,
})),
matchedFiles,
reason: rule.reason,
}
: null;
})
.filter((item): item is NonNullable<typeof item> => Boolean(item));
const requiredDocs = Array.from(
new Set(recommendations.flatMap((item) => item.docs.map((doc) => doc.path))),
).map((file) => ({
exists: existsSync(path.resolve(workspaceRoot, file)),
path: file,
}));
return {
changedFiles,
commands: [
'pnpm --dir mcp/ktWorkflow run obsidian-context -- --query <module>',
'pnpm --dir mcp/ktWorkflow run obsidian-validate',
'pnpm --dir mcp/ktWorkflow run global-review',
],
requiredDocs,
recommendations,
summary: {
changedFileCount: changedFiles.length,
recommendationCount: recommendations.length,
requiredDocCount: requiredDocs.length,
},
verification: [
'确认 requiredDocs 中需要更新的文档已同步,未更新时在最终说明原因。',
'涉及 Obsidian 索引时运行 obsidian-validate。',
'涉及 ktWorkflow/规则/测试流程时运行 self-test 和 global-review。',
`当前项目:${toPosix(resolveProject(projectKey).relativePath)}`,
],
};
}