Initial commit: company-kb framework

- 完整的知识库框架(P0-P5 路线图)
- 12篇治理文档
- 工具脚本(kb-init.sh, kb-lint-fm.py, feishu-sync.py, kb-bot)
- 7个 Claude 命令
- 示例项目和测试项目(wanniu-l1)
- 契约文件(meta/kb-contract.yaml)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
yangqianqian
2026-07-16 18:24:39 +08:00
commit 84f7950589
60 changed files with 4410 additions and 0 deletions

114
tools/feishu-render.py Executable file
View File

@@ -0,0 +1,114 @@
#!/usr/bin/env python3
"""feishu-render.py —— 知识库 → 飞书呈现(单向 md→飞书不回写
两种载体:
--bitable 扫 projects 下页的 frontmatter每页一条记录 upsert 到多维表格(结构型呈现,可筛选/看板)
--docs 代号 把某项目的 _index + 决策同步为飞书云文档(叙事型呈现)
飞书只读呈现,写路径仍是 Obsidian/Git防双写冲突。
凭据由 lark-cli 自管,不入库。字段严格对齐 meta/kb-contract.yaml。
用法:
python3 tools/feishu-render.py --bitable [--dry-run]
python3 tools/feishu-render.py --docs <代号> [--dry-run]
注意本文件是接口骨架。bitable app/table 的创建与 record 写入留 TODO
接入时填 _bitable_upsert()docs 同步复用 lark-doc-manager。
"""
import sys
import os
import re
import glob
import argparse
try:
import yaml
except ImportError:
print("ERROR: 需要 PyYAML。请运行: pip install pyyaml", file=sys.stderr)
sys.exit(2)
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
FM_RE = re.compile(r"^---\s*\n(.*?)\n---\s*\n", re.DOTALL)
# 呈现给飞书多维表格的字段(对齐 kb-contract.yaml
BITABLE_FIELDS = ["type", "title", "status", "source", "created", "source_link"]
def parse_fm(fp):
text = open(fp, encoding="utf-8").read()
m = FM_RE.match(text)
if not m:
return None
try:
data = yaml.safe_load(m.group(1))
return data if isinstance(data, dict) else None
except yaml.YAMLError:
return None
def collect_pages():
"""扫 projects 下所有知识页,返回 (相对路径, frontmatter) 列表。"""
rows = []
for fp in glob.glob(os.path.join(ROOT, "projects", "**", "*.md"), recursive=True):
fm = parse_fm(fp)
if fm and fm.get("type"):
rel = os.path.relpath(fp, ROOT)
project = rel.split(os.sep)[1] if os.sep in rel else ""
rows.append((rel, project, fm))
return rows
def _bitable_upsert(rows, dry_run):
"""接入时实现:通用 api 写多维表格记录。
唯一键 = 页相对路径(防重复插入,做 upsert
命令参考:
lark-cli api POST /open-apis/bitable/v1/apps/{app}/tables/{table}/records \
--data '{"fields": {...}}'
"""
print(f"[bitable] 采集到 {len(rows)} 页知识:")
for rel, project, fm in rows:
fields = {k: fm.get(k, "") for k in BITABLE_FIELDS}
fields["项目"] = project
fields["页路径"] = rel
if dry_run:
print(f" - {project}/{fm.get('type')}: {fm.get('title', rel)}")
else:
# TODO: lark-cli api POST .../records含 app_token/table_id 配置)
pass
if not dry_run:
raise NotImplementedError(
"Bitable 写入尚未接入P2-d。需先创建多维表并配置 app_token/table_id。"
"当前 --dry-run 可预览将同步的记录。"
)
def _docs_sync(code, dry_run):
idx = os.path.join(ROOT, "projects", code, "_index.md")
if not os.path.isfile(idx):
print(f"ERROR: 找不到 projects/{code}/_index.md", file=sys.stderr)
sys.exit(1)
print(f"[docs] 将同步 projects/{code}/ 的 _index + 决策为飞书云文档")
if dry_run:
print(" dry-run实际同步走 lark-doc-manager / lark-cli docs +create/+update")
return
raise NotImplementedError(
"云文档同步走 lark-doc-manager skill已激活。接入时在此调用其创建/更新流程。"
)
def main():
ap = argparse.ArgumentParser(description="知识库 → 飞书呈现")
g = ap.add_mutually_exclusive_group(required=True)
g.add_argument("--bitable", action="store_true", help="同步全库到多维表格")
g.add_argument("--docs", metavar="代号", help="同步某项目为云文档")
ap.add_argument("--dry-run", action="store_true", help="只预览不写飞书")
args = ap.parse_args()
if args.bitable:
_bitable_upsert(collect_pages(), args.dry_run)
else:
_docs_sync(args.docs, args.dry_run)
if __name__ == "__main__":
main()