For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: 将 809 篇菌群文章从 health_knowledge_base 并入 dan_knowledge_base,成为唯一知识条目表,管理端统一入口,langgraph 三源仍按 metadata.source 分流。
Architecture: dan_knowledge_base 增加 source_type='article' 标记(该字段已存在且 39 条案例全为 'file',无 title 唯一约束,809 篇零冲突);后端 list 接口增加 sourceType/excludeSourceType 参数;langgraph dan_knowledge 源用 excludeSourceType='article' 排除、microbiome 源用 sourceType='article' 拉取;前端知识库管理页增强后删除健康知识库页。
Tech Stack: Spring Boot 2.7.18 + MyBatis-Plus(lambda wrapper)+ Vue 2.7 / Element UI + Python FastAPI / Chroma / httpx + MySQL 8.0.34
Spec: docs/superpowers/specs/2026-10-20-knowledge-unify-design.md
关键事实(已勘察确认):
dan_knowledge_base 唯一键:仅 PRIMARY(id),title varchar(200),content mediumtext,现有 39 条全 source_type='file'DanKnowledgeBaseService.list 的 wrapper.orderByAsc(getSort) 无 id tie-break——所有文章 sort=0,重建索引可能漏块(与旧 microbiome_article 分页 bug 同类)knowledge_sync 首次延迟 120s、之后每 3600s 定时全量重建三源,容器重启即重置计时111.228.6.214:/app/cfc(git cfclub 分支),部署按 AGENTS.md:备份 → 构建 → 部署 → 验证 → 失败回滚mysql -h mysql-internet-cn-north-1-23feae22680e4dfa.rds.jdcloud.com -P 3306 -u cfc -pcfc@1314 zxyjcfc-backend(9082),生产 jar /app/pro/cfc/backend/cfc-backend-1.0.0.jar;langgraph docker compose(9000,容器 cfc-langgraph-langgraph-svc-1)import request from '@/utils/request'(不要用 this.$axios,项目未注册该原型)health_butler_graph 读 category 走 /api/health/knowledge/query(读原表,不读 Chroma)created_at/updated_at 原始值(INSERT 显式指定列,不依赖默认 CURRENT_TIMESTAMP)/app/pro/cfc 改动前必须先备份 jar;验证失败必须回滚 + 复验git add <本任务文件>,禁止 git add -A / git stash / git checkout . / git restore . / git clean;构建在隔离 worktree 进行:git worktree add --detach /tmp/ku-build HEAD → 在其中 mvn package,确保生产 jar 不含 WIP;任务结束 git worktree remove /tmp/ku-build --forcedan_knowledge ≈ 1885。wrapper.orderByAsc(getSort) 缺 id tie-break,809 篇 sort 全 0 → 重建漏块或重复。→ Task 1 加 .orderByAsc(DanKnowledgeBase::getId)。验证:重建后 microbiome 块数与 809 篇实际分块数一致,无重复 id。excludeSourceType 必须排除的是 article,不是 file。验证:/api/admin/knowledge-base/list 不传 sourceType 返回 848 条;传 excludeSourceType='article' 返回 39 条。DELETE 必须限定 item_type='article'(表保留给未来指标条目)。验证:清理后表存在且 COUNT(*)=0,其他 item_type 行未动。knowledge:base perm,删路由不应影响「知识库管理」页访问。验证:Layout.vue 与 router 均无 health-knowledge,知识库管理页仍可进入。Files:
cfc-backend/src/main/java/com/etotem/cfc/service/DanKnowledgeBaseService.java:58-90cfc-backend/src/main/java/com/etotem/cfc/controller/admin/KnowledgeBaseController.java:39-50Interfaces:
DanKnowledgeBaseService.list(String keyword, Integer status, int page, int size, Long tagId, Long dimensionId, String sourceType, String excludeSourceType) — 8 参重载;保留 4 参/6 参重载委托 8 参(传 null),向后兼容现有调用方POST /api/admin/knowledge-base/list 接受 sourceType(精确匹配)与 excludeSourceType(排除匹配),不传则返回全部Steps:
在 DanKnowledgeBaseService.java:6 参 list 改为委托新 8 参方法(list(keyword, status, page, size, tagId, dimensionId, null, null))。8 参方法内新增:
if (sourceType != null && !sourceType.isEmpty()) {
wrapper.eq(DanKnowledgeBase::getSourceType, sourceType);
}
if (excludeSourceType != null && !excludeSourceType.isEmpty()) {
wrapper.ne(DanKnowledgeBase::getSourceType, excludeSourceType);
}
排序改为 wrapper.orderByAsc(DanKnowledgeBase::getSort).orderByAsc(DanKnowledgeBase::getId)(id 作为稳定 tie-break)。
KnowledgeBaseController.list 在解析 dimensionId 后追加:
String sourceType = (String) params.get("sourceType");
String excludeSourceType = (String) params.get("excludeSourceType");
调用改为 danKnowledgeBaseService.list(keyword, status, page, size, tagId, dimensionId, sourceType, excludeSourceType)。
langgraph 改用 /api/admin/knowledge-base/list 后,HealthKnowledgeBaseController.article/list 无人调用。同文件删除该端点方法及 HealthKnowledgeBaseService.pageArticles(保留 /query /list /import /delete 等其他端点——health_butler_graph 依赖 /query)。
[ ] Step 4: 提交本任务改动(只 add 这 4 个文件)
cd /app/cfc && git add cfc-backend/src/main/java/com/etotem/cfc/service/DanKnowledgeBaseService.java cfc-backend/src/main/java/com/etotem/cfc/service/HealthKnowledgeBaseService.java cfc-backend/src/main/java/com/etotem/cfc/controller/admin/KnowledgeBaseController.java cfc-backend/src/main/java/com/etotem/cfc/controller/HealthKnowledgeBaseController.java
git commit -m "feat(knowledge): list 接口支持 sourceType/excludeSourceType 过滤 + id 稳定分页,移除废弃 /article/list 端点"
git rev-parse HEAD
⚠️ 禁止 git add -A / git stash / git checkout . / git restore .(工作树含他人 WIP)。
[ ] Step 5: 隔离 worktree 编译打包 + 备份 + 部署 + 验证
cd /app/cfc && git worktree add --detach /tmp/ku-task1-build HEAD
cd /tmp/ku-task1-build/cfc-backend && mvn compile -DskipTests -q && mvn package -DskipTests -q
BACKUP=/app/pro/cfc/backend/cfc-backend-$(date +%Y%m%d_%H%M%S)_b6_knowledge_unify.jar
cp /app/pro/cfc/backend/cfc-backend-1.0.0.jar $BACKUP
cp /tmp/ku-task1-build/cfc-backend/target/cfc-backend-1.0.0.jar /app/pro/cfc/backend/
systemctl restart cfc-backend
sleep 20 && systemctl is-active cfc-backend
cd /app/cfc && git worktree remove /tmp/ku-task1-build --force
Expected: BUILD SUCCESS + jar 产出 + active=active。
验证(全部通过后才能进 Task 2):
curl -s -X POST http://localhost:9082/api/admin/knowledge-base/list -H 'Content-Type: application/json' -d '{"page":1,"size":1}' | jq '.data.total' # 期望 39
curl -s -X POST http://localhost:9082/api/admin/knowledge-base/list -H 'Content-Type: application/json' -d '{"page":1,"size":1,"excludeSourceType":"article"}' | jq '.data.total' # 期望 39
Expected: 均 39(INSERT 前 article 为 0 条)。同时确认 systemctl is-active cfc-backend = active。
提交已在 Step 4 完成。确认 cd /app/cfc && git log --oneline -1 为本任务提交,且 git status --short 中本任务 4 个文件已不在未暂存列表(他人 WIP 文件仍在,属预期)。
Files:
cfc-langgraph/app/tools/java_client.py:146-166(get_dan_knowledge_base)cfc-langgraph/app/tools/java_client.py:145-172(get_microbiome_articles,当前调 /api/health/knowledge/article/list)Interfaces:
POST /api/admin/knowledge-base/list(sourceType / excludeSourceType)get_dan_knowledge_base() → 仅案例报告(排除 article);get_microbiome_articles() → 仅菌群文章(article),返回结构与 _build_chunks 期望一致:{id, title, content, category, source_url, sourceUrl, pubDate, createdAt, updatedAt}Steps:
请求体改为 {"page": page, "size": size, "status": 1, "excludeSourceType": "article"},size 保持 1000。docstring 补注:案例报告源,排除 article(菌群文章)。
改为分页拉 POST /api/admin/knowledge-base/list,请求体 {"page": page, "size": size, "status": 1, "sourceType": "article"}(size 50,ORDER BY id 由后端保证)。字段映射对齐 _build_chunks 期望键(title/content/category 为空串/source_url 留空/pubDate 取 createdAt 或 updatedAt)。
[ ] Step 3: 镜像构建(后台,不部署)
cd /app/cfc/cfc-langgraph && nohup bash -c 'docker compose build langgraph-svc > /tmp/langgraph-build5.log 2>&1' &
等待完成(检查 /tmp/langgraph-build5.log 末行含 Successfully built 或 DONE,docker images 确认新镜像 tag)。不要执行 docker compose up。
[ ] Step 4: 提交
cd /app/cfc && git add cfc-langgraph/app/tools/java_client.py && git commit -m "feat(langgraph): microbiome 源改拉 dan_knowledge_base(source_type=article),dan 源排除 article"
验收(进 Task 3 前): 后端新接口可用 + langgraph 镜像已构建但容器未替换。
Files:
/tmp/migrate_articles.sql(迁移脚本,上传远程执行)Interfaces:
dan_knowledge_base 含 848 条(39 file + 809 article),source_type 区分Steps:
[ ] Step 1: 备份源表
MY="mysql -h mysql-internet-cn-north-1-23feae22680e4dfa.rds.jdcloud.com -P 3306 -u cfc -pcfc@1314 zxyj"
$MY -e "DROP TABLE IF EXISTS dan_knowledge_base_bak_20261020; CREATE TABLE dan_knowledge_base_bak_20261020 LIKE dan_knowledge_base; INSERT INTO dan_knowledge_base_bak_20261020 SELECT * FROM dan_knowledge_base;"
$MY -e "DROP TABLE IF EXISTS health_knowledge_base_bak_20261020; CREATE TABLE health_knowledge_base_bak_20261020 LIKE health_knowledge_base; INSERT INTO health_knowledge_base_bak_20261020 SELECT * FROM health_knowledge_base;"
Expected: 两备份表行数 = 39 / 809。
[ ] Step 2: INSERT 809 篇(保留原始时间戳)
INSERT INTO dan_knowledge_base (title, content, sort, status, source_url, source_type, created_at, updated_at)
SELECT item_name, description, 0, 1, source, 'article', created_at, updated_at
FROM health_knowledge_base
WHERE item_type = 'article';
Expected: INSERT 影响 809 行。
[ ] Step 3: 校验
$MY -e "SELECT source_type, COUNT(*) FROM dan_knowledge_base GROUP BY source_type;" # 期望 file=39, article=809
$MY -e "SELECT COUNT(*) FROM dan_knowledge_base WHERE CHAR_LENGTH(title) > 200;" # 期望 0
$MY -e "SELECT MIN(CHAR_LENGTH(content)), MAX(CHAR_LENGTH(content)) FROM dan_knowledge_base WHERE source_type='article';"
$MY -e "SELECT MIN(created_at), MAX(created_at) FROM dan_knowledge_base WHERE source_type='article';" # 期望为原始时间,非 2026-10-20
curl -s -X POST http://localhost:9082/api/admin/knowledge-base/list -H 'Content-Type: application/json' -d '{"page":1,"size":1}' | jq '.data.total' # 期望 848
若任一校验不符,回滚:$MY -e "DELETE FROM dan_knowledge_base WHERE source_type='article';" 后修正脚本重跑。
[ ] Step 4: 提交迁移脚本
mkdir -p /app/cfc/docs/migrations && cp /tmp/migrate_articles.sql /app/cfc/docs/migrations/20261020_articles_to_dan_knowledge.sql
cd /app/cfc && git add docs/migrations && git commit -m "docs(knowledge): 809 篇菌群文章迁移至 dan_knowledge_base 脚本"
Files: 无源码改动(仅部署 + 触发)
Interfaces:
cfc_knowledge collection 三源重建完成,metadata 键 id/source/post_id/title/category/tags/source_url/pub_date/chunk_index/chunk_totalSteps:
[ ] Step 1: 替换容器
cd /app/cfc/cfc-langgraph && docker compose up -d --force-recreate langgraph-svc
docker ps --filter name=langgraph -f status=healthy
Expected: 容器 healthy 且使用 Task 2 的新镜像 digest。
[ ] Step 2: 触发全量重建
curl -s -m 60 -X POST http://localhost:9000/api/v1/knowledge-base/sync
Expected: 同步任务启动(首次延迟 120s 后执行)。
[ ] Step 3: 监控重建进度
docker logs --tail -50 cfc-langgraph-langgraph-svc-1 2>&1 | grep -E "知识库同步|同步完成|ERROR"
Expected: 依次看到 article / microbiome / dan_knowledge 三源同步日志,microbiome 为 809 篇,无 ERROR。重建约 20–30 分钟(SiliconFlow 429 退避)。
[ ] Step 4: 验证三源统计
curl -s -X POST http://localhost:9000/api/v1/knowledge-base/stats | python3 -m json.tool
Expected: by_source 三源均有数据,microbiome 块数 ≈ 原 25369 量级,dan_knowledge ≈ 1885,无 source='microbiome' 出现在 article 或反之(窗口期双重索引检查)。
验收(进 Task 5 前): 三源重建完成 + 计数符合预期 + 无 ERROR 日志。
Files:
cfc-web/src/components/KnowledgeTable/index.vue:10-25(加来源方式列)cfc-web/src/views/admin/knowledge/index.vue:40-50(加 sourceType 筛选下拉)+ fetch 参数cfc-web/src/components/KnowledgeDialog/index.vue(查看长文详情对话框,如现有 content 列已支持展开则跳过)cfc-web/src/views/admin/health-knowledge.vuecfc-web/src/router/index.js:772-776(删 health-knowledge 路由)cfc-web/src/Layout.vue:235(删菜单项)Interfaces:
sourceType 参数Steps:
在 sort 列前插入:
<el-table-column prop="sourceType" label="来源方式" width="110" align="center">
<template slot-scope="{ row }">
<el-tag size="mini" :type="row.sourceType === 'article' ? 'success' : 'info'">{{ row.sourceType === 'article' ? '菌群文章' : (row.sourceType || '案例报告') }}</el-tag>
</template>
</el-table-column>
筛选区加下拉:
<el-select v-model="filters.sourceType" placeholder="全部来源" clearable style="width:140px">
<el-option label="案例报告" value="file" />
<el-option label="菌群文章" value="article" />
</el-select>
fetch 时传入 sourceType(不传则返回全部)。
[ ] Step 3: 删除健康知识库页
cd /app/cfc/cfc-web
rm src/views/admin/health-knowledge.vue
# 删 router/index.js 772-776 的 health-knowledge 路由对象
# 删 Layout.vue:235 的菜单项
grep -rn "health-knowledge\|健康知识库" src/ # 期望无残留(api 文件若有引用一并清理)
[ ] Step 4: 构建 + 部署 + 验证
cd /app/cfc/cfc-web && npm run build 2>&1 | tail -3
rsync -a --delete dist/ /app/pro/cfc/frontend/
curl -s -k https://cfc.etotem.com.cn/admin/ -o /dev/null -w "%{http_code}\n" # 期望 200
Expected: Build complete;前端 200;产物 js 中 grep 无 health/knowledge 残留。
[ ] Step 5: Commit
cd /app/cfc && git add -A cfc-web/src cfc-web/.last_build_commit && git commit -m "feat(knowledge): 知识库管理页统一展示案例报告与菌群文章(sourceType 筛选/标识),删除健康知识库页与路由"
Files: 无源码改动
Steps:
[ ] Step 1: 全链路验证
# 后端
systemctl is-active cfc-backend
curl -s -o /dev/null -w "%{http_code}" -X POST http://localhost:9082/api/cart/list # 期望 200
# 前端
curl -s -o /dev/null -w "%{http_code}" -k https://cfc.etotem.com.cn/admin/ # 期望 200
# 后端日志无 ERROR
grep -c " ERROR " /app/pro/cfc/backend/cfc-backend.log
# langgraph 三源统计
curl -s -X POST http://localhost:9000/api/v1/knowledge-base/stats | python3 -m json.tool
# 磁盘
du -sh /var/lib/docker/volumes/cfc-langgraph_langgraph-data/_data/
[ ] Step 2: 清理源表文章数据(表保留)
MY="mysql -h mysql-internet-cn-north-1-23feae22680e4dfa.rds.jdcloud.com -P 3306 -u cfc -pcfc@1314 zxyj"
$MY -e "SELECT item_type, COUNT(*) FROM health_knowledge_base GROUP BY item_type;" # 确认仅 article
$MY -e "DELETE FROM health_knowledge_base WHERE item_type='article';" # 期望删 809
$MY -e "SHOW TABLES LIKE 'health_knowledge_base';" # 期望表仍存在
$MY -e "SELECT COUNT(*) FROM health_knowledge_base;" # 期望 0
Expected: 表存在且 COUNT(*)=0,其他 item_type 行未被删除。
[ ] Step 3: 推送 + 工作区干净
cd /app/cfc && git push origin cfclub && git status --short # 期望空输出
最终验收(全部通过): 管理端「知识库管理」页 848 条统一展示且可筛选;「健康知识库」菜单/路由消失;向量库三源 by_source 计数符合设计(microbiome ≈ 25369、dan_knowledge ≈ 1885、article 不变);无 ERROR 日志;磁盘不膨胀;health_knowledge_base 表保留空置。