feat: 首次推送到Gitea - 完整项目代码 + 安全加固 + 知识库

This commit is contained in:
ZhuiGuangAI Dev
2026-10-02 18:52:49 +08:00
parent b44601bb66
commit 8205ae709c
1185 changed files with 49841 additions and 12802 deletions
+364
View File
@@ -0,0 +1,364 @@
#!/usr/bin/env node
/**
* update-knowledge.mjs — 开发完成后自动记录与迭代脚本
*
* 捕获三类信息并沉淀知识库(幂等,可重复运行):
* 1. 能力特征 → .trae/knowledge/capabilities.json (代码结构 / 依赖 / 任务 / 角色 全貌快照)
* 2. 开发过程 → .trae/knowledge/dev_process.jsonl (按提交追加,不覆盖历史)
* 3. 模型成果 → .trae/knowledge/artifacts.jsonl (本次交付物:迁移 / 脚本 / 提交统计)
* 并把上述事实增量 upsert 进知识图谱:
* - .trae/knowledge_graph.json (渲染版,权威)
* - .trae/knowledge_graph.jsonl (MCP Knowledge Graph Memory 读取的源文件)
*
* 知识目录解析顺序:
* 1. <repo>/.trae (所有 .trae 内容均在代码仓库内,跟着 git 走)
*
* 用法:
* node scripts/update-knowledge.mjs # 正常写入
* node scripts/update-knowledge.mjs --dry-run # 只预览,不写文件
* node scripts/update-knowledge.mjs --quiet # 静默(post-commit 钩子用)
*
* 设计约束:
* - 永不删除实体:同名实体整条替换(其 observations 是派生数据),未出现的旧实体原样保留
* - 任何扫描失败只告警不中断,最终仍写出文件
*/
import fs from "fs";
import path from "path";
import { execFileSync } from "child_process";
import { fileURLToPath } from "url";
import { createLogger } from "./lib/logger.mjs";
const log = createLogger("update-knowledge");
const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const DRY_RUN = process.argv.includes("--dry-run");
const QUIET = process.argv.includes("--quiet");
const PROJECT_NAME = "追光AI";
const CAP_ENTITY = "追光AI:能力快照";
const TODAY = new Date().toISOString().slice(0, 10);
const NOW = new Date().toISOString().slice(0, 19);
const DEV_RECORD_ENTITY = `开发记录:${TODAY}`;
/* ------------------------------------------------------------------ 路径 */
const TRAE_DIR = path.join(REPO_ROOT, ".trae");
const KNOW_DIR = path.join(TRAE_DIR, "knowledge");
const KG_JSON = path.join(TRAE_DIR, "knowledge_graph.json");
const KG_JSONL = path.join(TRAE_DIR, "knowledge_graph.jsonl");
const CAP_FILE = path.join(KNOW_DIR, "capabilities.json");
const PROC_FILE = path.join(KNOW_DIR, "dev_process.jsonl");
const ART_FILE = path.join(KNOW_DIR, "artifacts.jsonl");
/* ------------------------------------------------------------- 基础工具 */
function readJson(file, fallback = null) {
try {
return JSON.parse(fs.readFileSync(file, "utf8"));
} catch (err) {
if (fs.existsSync(file)) log.warn("读取 JSON 失败", { file, error: err.message });
return fallback;
}
}
function writeJson(file, data) {
if (DRY_RUN) return;
fs.mkdirSync(path.dirname(file), { recursive: true });
fs.writeFileSync(file, JSON.stringify(data, null, 2) + "\n", "utf8");
}
function appendJsonl(file, rows) {
if (DRY_RUN || rows.length === 0) return;
fs.mkdirSync(path.dirname(file), { recursive: true });
fs.appendFileSync(file, rows.map((r) => JSON.stringify(r)).join("\n") + "\n", "utf8");
}
function readJsonl(file) {
if (!fs.existsSync(file)) return [];
return fs
.readFileSync(file, "utf8")
.split("\n")
.map((l) => l.trim())
.filter(Boolean)
.map((l) => {
try {
return JSON.parse(l);
} catch {
return null;
}
})
.filter(Boolean);
}
function walk(dir, filter) {
const out = [];
if (!fs.existsSync(dir)) return out;
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) out.push(...walk(full, filter));
else if (filter(full)) out.push(full);
}
return out;
}
function rel(p) {
return path.relative(REPO_ROOT, p).replace(/\\/g, "/");
}
function git(args) {
try {
return execFileSync("git", args, {
cwd: REPO_ROOT,
encoding: "utf8",
timeout: 15000,
stdio: ["ignore", "pipe", "ignore"],
}).trim();
} catch {
return "";
}
}
/* ------------------------------------------------------ 1. 能力特征扫描 */
function scanCapabilities() {
const apiRoutes = walk(path.join(REPO_ROOT, "src", "app", "api"), (f) => f.endsWith("route.ts"));
const pages = walk(path.join(REPO_ROOT, "src", "app"), (f) => f.endsWith("page.tsx"));
const components = walk(path.join(REPO_ROOT, "src", "components"), (f) => f.endsWith(".tsx"));
const hooks = walk(path.join(REPO_ROOT, "src", "hooks"), (f) => /\.(ts|tsx)$/.test(f));
const libFiles = walk(path.join(REPO_ROOT, "src", "lib"), (f) => /\.(ts|tsx)$/.test(f));
const scripts = walk(path.join(REPO_ROOT, "scripts"), (f) => /\.(mjs|sh)$/.test(f));
const migrations = fs.existsSync(path.join(REPO_ROOT, "prisma", "migrations"))
? fs
.readdirSync(path.join(REPO_ROOT, "prisma", "migrations"), { withFileTypes: true })
.filter((d) => d.isDirectory())
.map((d) => d.name)
.sort()
: [];
const schema = fs.existsSync(path.join(REPO_ROOT, "prisma", "schema.prisma"))
? fs.readFileSync(path.join(REPO_ROOT, "prisma", "schema.prisma"), "utf8")
: "";
const models = [...schema.matchAll(/^model\s+(\w+)/gm)].map((m) => m[1]);
const crontabPath = path.join(REPO_ROOT, "crontab.txt");
const crontabLines = fs.existsSync(crontabPath) ? fs.readFileSync(crontabPath, "utf8").split("\n") : [];
const activeCron = crontabLines.filter((l) => /^\s*\d/.test(l));
const activeCronKeys = activeCron
.map((l) => (l.match(/scripts\/([\w-]+)\.mjs/) || [])[1])
.filter(Boolean);
const botFile = path.join(REPO_ROOT, "data", "bot-characters.json");
const botData = readJson(botFile, null);
const botCharacters = Array.isArray(botData?.characters) ? botData.characters.map((c) => c.key) : [];
const pkg = readJson(path.join(REPO_ROOT, "package.json"), {}) || {};
return {
scanned_at: NOW,
version: pkg.version || "unknown",
api_routes: apiRoutes.length,
pages: pages.length,
components: components.length,
hooks: hooks.length,
lib_modules: libFiles.length,
scripts: scripts.length,
prisma_models: models.length,
prisma_model_names: models,
migrations: migrations.length,
latest_migrations: migrations.slice(-5),
cron_active_tasks: activeCron.length,
cron_active_scripts: activeCronKeys,
bot_characters: botCharacters,
tech_stack: {
dependencies: Object.keys(pkg.dependencies || {}).length,
dev_dependencies: Object.keys(pkg.devDependencies || {}).length,
next: (pkg.dependencies || {}).next || "-",
prisma: (pkg.dependencies || {}).prisma || "-",
react: (pkg.dependencies || {}).react || "-",
},
};
}
/* ------------------------------------------------------ 2. 开发过程提取 */
function collectDevProcess() {
const raw = git(["log", "-n", "5", "--pretty=format:%H%x1f%cI%x1f%s"]);
if (!raw) return [];
const rows = [];
for (const line of raw.split("\n")) {
const [hash, date, subject] = line.split("\x1f");
if (!hash) continue;
const files = git(["diff-tree", "--no-commit-id", "--name-only", "-r", "-M", hash])
.split("\n")
.filter(Boolean);
const shortstat = git(["show", "--shortstat", "--oneline", "--format=", hash]).trim();
rows.push({
ts: NOW,
kind: "commit",
hash: hash.slice(0, 8),
committed_at: date,
subject,
files_changed: files.length,
areas: [...new Set(files.map((f) => f.split("/").slice(0, 2).join("/")))].slice(0, 12),
shortstat,
});
}
return rows;
}
/* ------------------------------------------------------ 3. 模型成果捕获 */
function collectArtifacts(cap, commits) {
const head = commits[0] || {};
const artifacts = {
ts: NOW,
date: TODAY,
kind: "delivery",
head_commit: head.hash || "-",
head_subject: head.subject || "-",
files_changed: head.files_changed ?? 0,
shortstat: head.shortstat || "",
capabilities_delta: {
api_routes: cap.api_routes,
pages: cap.pages,
components: cap.components,
prisma_models: cap.prisma_models,
migrations: cap.migrations,
scripts: cap.scripts,
cron_active_tasks: cap.cron_active_tasks,
},
latest_migrations: cap.latest_migrations,
version: cap.version,
recorded_by: "scripts/update-knowledge.mjs",
};
return artifacts;
}
/* --------------------------------------------------- 知识图谱 upsert */
function buildEntities(cap, commits, artifacts) {
const capObs = [
`版本: ${cap.version}`,
`代码规模: API 路由 ${cap.api_routes} / 页面 ${cap.pages} / 组件 ${cap.components} / hooks ${cap.hooks} / lib ${cap.lib_modules}`,
`数据层: Prisma 模型 ${cap.prisma_models} 个、迁移 ${cap.migrations} 个(最新 ${cap.latest_migrations.join("、") || "无"})`,
`脚本: ${cap.scripts} 个(含定时任务与运维脚本)`,
`定时任务: 启用 ${cap.cron_active_tasks} 个 —— ${cap.cron_active_scripts.join("、")}`,
`数字人角色: ${cap.bot_characters.length} 个(${cap.bot_characters.join("、") || "无"})`,
`依赖: dependencies ${cap.tech_stack.dependencies} / devDependencies ${cap.tech_stack.dev_dependencies}(next ${cap.tech_stack.next}、prisma ${cap.tech_stack.prisma}、react ${cap.tech_stack.react})`,
`快照时间: ${cap.scanned_at}(由 scripts/update-knowledge.mjs 自动生成,勿手工编辑)`,
];
const devObs = [
`记录时间: ${NOW}`,
`本次交付: ${artifacts.head_commit} ${artifacts.head_subject}(改动文件 ${artifacts.files_changed} 个${artifacts.shortstat ? "、" + artifacts.shortstat : ""})`,
`能力基线: API ${cap.api_routes} / 页面 ${cap.pages} / 组件 ${cap.components} / 模型 ${cap.prisma_models} / 迁移 ${cap.migrations} / 脚本 ${cap.scripts} / 定时任务 ${cap.cron_active_tasks}`,
...commits.slice(0, 5).map((c) => `${c.hash} ${c.committed_at?.slice(0, 10) || "-"} ${c.subject}`),
"维护: 运行 npm run knowledge:update 刷新本记录",
];
return [
{ type: "entity", entityType: "Capability", name: CAP_ENTITY, observations: capObs },
{ type: "entity", entityType: "fact", name: DEV_RECORD_ENTITY, observations: devObs },
];
}
function upsertKnowledgeGraph(entities, relations) {
const kg = readJson(KG_JSON, null) || {
project: PROJECT_NAME,
version: "V2.1.2",
updated_at: NOW,
entities: [],
relations: [],
};
const fresh = new Map(entities.map((e) => [e.name, e]));
const kept = (kg.entities || []).filter((e) => e.name && !fresh.has(e.name));
const relSeen = new Set();
const mergedRelations = [];
for (const r of [...(kg.relations || []), ...relations]) {
if (!r?.from || !r?.to) continue;
const key = `${r.relationType}|${r.from}|${r.to}`;
if (relSeen.has(key)) continue;
relSeen.add(key);
mergedRelations.push(r);
}
const mergedEntities = [...kept, ...entities];
const nextKg = {
project: kg.project || PROJECT_NAME,
version: kg.version || "V2.1.2",
updated_at: NOW,
entities: mergedEntities,
relations: mergedRelations,
};
writeJson(KG_JSON, nextKg);
// 同步 JSONL(MCP 记忆服务读取的源文件):同名实体整条替换,其余原样保留
const oldLines = readJsonl(KG_JSONL);
const keptLines = oldLines.filter((o) => o.type === "entity" && o.name && !fresh.has(o.name));
const jsonlRows = [...keptLines, ...entities, ...mergedRelations];
if (!DRY_RUN) {
fs.mkdirSync(path.dirname(KG_JSONL), { recursive: true });
fs.writeFileSync(KG_JSONL, jsonlRows.map((o) => JSON.stringify(o)).join("\n") + "\n", "utf8");
}
return { entities: mergedEntities.length, relations: mergedRelations.length, jsonl: jsonlRows.length };
}
/* ------------------------------------------------------------------ main */
function main() {
const cap = scanCapabilities();
const commits = collectDevProcess();
const artifacts = collectArtifacts(cap, commits);
writeJson(CAP_FILE, cap);
// 模型成果:按 (日期 + head 提交) 去重,避免同日多次运行产生重复记录
const existingArtifacts = readJsonl(ART_FILE);
const alreadyRecorded = existingArtifacts.some(
(a) => a.date === artifacts.date && a.head_commit === artifacts.head_commit
);
if (!alreadyRecorded) appendJsonl(ART_FILE, [artifacts]);
// 开发过程:按提交 hash 去重(追加,不覆盖历史)
const existingProc = readJsonl(PROC_FILE);
const seenHashes = new Set(existingProc.map((r) => r.hash));
const newCommits = commits.filter((c) => !seenHashes.has(c.hash));
appendJsonl(PROC_FILE, newCommits);
const entities = buildEntities(cap, commits, artifacts);
const relations = [
{ type: "relation", relationType: "HAS_CAPABILITY", from: PROJECT_NAME, to: CAP_ENTITY },
{ type: "relation", relationType: "HAS_DEV_RECORD", from: PROJECT_NAME, to: DEV_RECORD_ENTITY },
];
const stats = upsertKnowledgeGraph(entities, relations);
if (!QUIET) {
log.info("知识库已更新", {
mode: DRY_RUN ? "dry-run" : "write",
trae_dir: TRAE_DIR,
capabilities: {
api_routes: cap.api_routes,
pages: cap.pages,
components: cap.components,
models: cap.prisma_models,
migrations: cap.migrations,
cron_active: cap.cron_active_tasks,
},
new_dev_records: newCommits.length,
artifacts_written: !alreadyRecorded,
graph: stats,
});
if (DRY_RUN) {
log.info("dry-run:未写入任何文件(含 capabilities/artifacts/knowledge_graph)");
}
}
}
try {
main();
} catch (err) {
log.error("更新知识库失败", { error: err.message, stack: err.stack });
process.exitCode = 1;
}