|
|
@@ -678,12 +678,13 @@ def cmd_guide(args):
|
|
|
3. 补料不许编,取材必标出处 —— 这是两条方向相反的规则,见 AGENTS.md
|
|
|
|
|
|
【该走哪条路】
|
|
|
+ 开工第一件事 → kb.py pull (对齐远端,报出别人推了什么)
|
|
|
丢进了新资料 / 删了源文件 → 30-技能/kb-sync (先 scan 再分流)
|
|
|
查点东西 → 30-技能/kb-query (好答案记得沉淀回库)
|
|
|
要做一门课 → 30-技能/course-outline → course-slides
|
|
|
库看着有点乱 → kb.py check (结构 + 内容一起过)
|
|
|
改页面格式或加规则 → 10-知识库/_meta/约定.md(改完登记起因)
|
|
|
- 改完要留档 → GIT.md (一次同步一个 commit)
|
|
|
+ 收工 → GIT.md 的提交规范 (不推等于白干)
|
|
|
manifest 被 git 搞冲突了 → kb.py repair (从磁盘和页面反推)
|
|
|
|
|
|
【格式硬约束】(细节见 约定.md)
|
|
|
@@ -721,6 +722,115 @@ def cmd_guide(args):
|
|
|
print("=" * 60)
|
|
|
|
|
|
|
|
|
+# ---------------------------------------------------------------- pull
|
|
|
+
|
|
|
+def git(*args):
|
|
|
+ """在仓库根跑 git。Windows 下 git 输出可能是 GBK,两种都试。"""
|
|
|
+ r = subprocess.run(["git", *args], cwd=str(ROOT), capture_output=True,
|
|
|
+ text=True, encoding="utf-8", errors="replace")
|
|
|
+ if r.returncode != 0 and r.stderr and "codec" in r.stderr.lower():
|
|
|
+ r = subprocess.run(["git", *args], cwd=str(ROOT), capture_output=True,
|
|
|
+ text=True, encoding="gbk", errors="replace")
|
|
|
+ return r
|
|
|
+
|
|
|
+
|
|
|
+def cmd_pull(args):
|
|
|
+ """远端优先:先拉最新,再告诉你有多少新料要整理。
|
|
|
+
|
|
|
+ 设计取向:这个库是要写入的,所以不能像只读知识库那样"拉临时副本用完就扔"。
|
|
|
+ 正确做法是本地留全量,每次开工先 pull —— pull 只传增量,比全量下载快得多。
|
|
|
+ """
|
|
|
+ if git("rev-parse", "--is-inside-work-tree").returncode != 0:
|
|
|
+ print("[ERR] 这里不是 git 仓库,无法从远端拉取")
|
|
|
+ print(" 如果是新建的库,先 git init 并配置 origin")
|
|
|
+ return 1
|
|
|
+
|
|
|
+ remotes = git("remote").stdout.split()
|
|
|
+ if not remotes:
|
|
|
+ print("[!] 没有配置远端,当前只有本地版本控制")
|
|
|
+ print(" 配置:git remote add origin <你的本地 git 服务地址>")
|
|
|
+ print(" 然后照常跑 kb.py scan")
|
|
|
+ return 0
|
|
|
+
|
|
|
+ remote = args.remote or remotes[0]
|
|
|
+ branch = git("rev-parse", "--abbrev-ref", "HEAD").stdout.strip() or "main"
|
|
|
+
|
|
|
+ # 工作区不干净就别拉,免得跟 rebase 打架
|
|
|
+ dirty = [x for x in git("status", "--porcelain").stdout.splitlines() if x.strip()]
|
|
|
+ if dirty and not args.force:
|
|
|
+ print(f"[!] 工作区有 {len(dirty)} 处未提交改动,先处理再拉:")
|
|
|
+ for d in dirty[:8]:
|
|
|
+ print(f" {d}")
|
|
|
+ if len(dirty) > 8:
|
|
|
+ print(f" ... 还有 {len(dirty)-8} 处")
|
|
|
+ print("\n 提交:git add -A && git commit -m \"sync: ...\"")
|
|
|
+ print(" 或暂存:git stash")
|
|
|
+ print(f" 或强制拉(会 rebase 上去):kb.py pull --force")
|
|
|
+ return 1
|
|
|
+
|
|
|
+ before = git("rev-parse", "HEAD").stdout.strip()
|
|
|
+ print(f"从 {remote}/{branch} 拉取…")
|
|
|
+ r = git("pull", "--rebase", remote, branch)
|
|
|
+ out = (r.stdout + r.stderr).strip()
|
|
|
+ if r.returncode != 0:
|
|
|
+ print("[ERR] 拉取失败:")
|
|
|
+ for line in out.splitlines()[:12]:
|
|
|
+ print(" ", line)
|
|
|
+ if "conflict" in out.lower() or "CONFLICT" in out:
|
|
|
+ print("\n 有冲突。解法见 GIT.md「冲突怎么解」:")
|
|
|
+ print(" manifest.json → git checkout --ours 后跑 kb.py repair")
|
|
|
+ print(" */_index.md → git checkout --theirs 后跑 kb.py graph")
|
|
|
+ return 1
|
|
|
+
|
|
|
+ after = git("rev-parse", "HEAD").stdout.strip()
|
|
|
+ if before == after:
|
|
|
+ print("已是最新,没有新提交。")
|
|
|
+ return cmd_scan(argparse.Namespace(json=False))
|
|
|
+
|
|
|
+ # 报远端这次带来了什么
|
|
|
+ diff = git("diff", "--name-status", "-M", f"{before}..{after}", "--", "00-原材料/").stdout
|
|
|
+ add, mod, dele, mov = [], [], [], []
|
|
|
+ for line in diff.splitlines():
|
|
|
+ parts = line.split("\t")
|
|
|
+ if len(parts) < 2:
|
|
|
+ continue
|
|
|
+ st, path = parts[0], parts[-1]
|
|
|
+ if st.startswith("A"):
|
|
|
+ add.append(path)
|
|
|
+ elif st.startswith("M"):
|
|
|
+ mod.append(path)
|
|
|
+ elif st.startswith("D"):
|
|
|
+ dele.append(path)
|
|
|
+ elif st.startswith("R"):
|
|
|
+ mov.append((parts[1], parts[2]))
|
|
|
+
|
|
|
+ n_commits = git("rev-list", "--count", f"{before}..{after}").stdout.strip()
|
|
|
+ print(f"拉到 {n_commits} 个新提交。原材料变化:"
|
|
|
+ f"新增 {len(add)} · 修改 {len(mod)} · 删除 {len(dele)} · 移动 {len(mov)}")
|
|
|
+ for x in add:
|
|
|
+ print(f" [NEW] {x}")
|
|
|
+ for x in mod:
|
|
|
+ print(f" [CHANGED] {x}")
|
|
|
+ for x in dele:
|
|
|
+ print(f" [DELETED] {x}")
|
|
|
+ for a, b in mov:
|
|
|
+ print(f" [MOVED] {a} -> {b}")
|
|
|
+
|
|
|
+ if any(x.lower().endswith((".pdf", ".mp4", ".mov", ".zip")) for x in add):
|
|
|
+ print("\n 提示:新增了可能很大的文件。如果仓库用了 git-lfs,")
|
|
|
+ print(" 确认 lfs 对象已拉下来(git lfs pull),否则整理时读不到内容。")
|
|
|
+
|
|
|
+ # 页面上可能也带了变化(别人整理的结果)
|
|
|
+ page_diff = git("diff", "--stat", f"{before}..{after}", "--", "10-知识库/").stdout
|
|
|
+ if page_diff.strip():
|
|
|
+ print(f"\n 知识库页面也有更新:")
|
|
|
+ for line in page_diff.strip().splitlines()[-6:]:
|
|
|
+ print(f" {line.strip()}")
|
|
|
+
|
|
|
+ print("\n 下一步:让模型按 kb-sync 的流程处理上面这些变化。")
|
|
|
+ return 0
|
|
|
+
|
|
|
+
|
|
|
# ---------------------------------------------------------------- repair
|
|
|
|
|
|
def cmd_repair(args):
|
|
|
@@ -841,10 +951,15 @@ def main():
|
|
|
|
|
|
sub.add_parser("repair", help="从磁盘和页面反推 manifest(git 冲突后用)")
|
|
|
|
|
|
+ pl = sub.add_parser("pull", help="从远端拉最新并报告原材料变化")
|
|
|
+ pl.add_argument("--remote", default=None)
|
|
|
+ pl.add_argument("--force", action="store_true", help="工作区有改动也拉")
|
|
|
+
|
|
|
args = ap.parse_args()
|
|
|
fn = {"status": cmd_status, "guide": cmd_guide, "scan": cmd_scan, "mark": cmd_mark,
|
|
|
"retract": cmd_retract, "move": cmd_move, "graph": cmd_graph,
|
|
|
- "lint": cmd_lint, "check": cmd_check, "repair": cmd_repair}.get(args.cmd)
|
|
|
+ "lint": cmd_lint, "check": cmd_check, "repair": cmd_repair,
|
|
|
+ "pull": cmd_pull}.get(args.cmd)
|
|
|
if not fn:
|
|
|
ap.print_help()
|
|
|
return 0
|