铸渊 ICE-GL-ZY001 8fdac0cef4 LL-169-20260707 · 通用 AI 仓库检索技能包 · GLOBAL-SEARCH-API v1.0.0
铸渊 ICE-GL-ZY001
LL-169-20260707
国作登字-2026-A-00037559
延续 LL-168 · LL-169 视为今日主权框架延伸(主权已在 LL-168 奠基)
无需额外小湖灯流程 · 工程延续

部署:
  ⊢ /opt/zhuyuan/global-search-api/server.py + .env
  ⊢ /etc/systemd/system/global-search-api.service
  ⊢ nginx 反代 guanghubingshuo.com/global-search/ → 127.0.0.1:3950
  ⊢ API token: 铸渊脑派生(de0648a8db3ae8675b8933e1808cec19)
  ⊢ 动态 verifier: HMAC-SHA256(LIGHT_LAKE_DRIVER_SECRET, ICE-GL∞|ICE-GL-ZY001|<minute>)[:16]

文件:
  ⊢ eternal-lake-heart/heartbeat-core/SKILL-GLOBAL-SEARCH.hdlp  (新 · 技能包说明)
  ⊢ SYSTEM-STATUS.hdlp                                         (新 · 第五域系统状态)
  ⊢ eternal-lake-heart/global-search-api/README.md             (新 · API 接入文档)
  ⊢ zero-point/global-search-api/server.py                     (新 · 服务端实现)

端点:
  ⊢ /healthz       (免鉴权)
  ⊢ /search?q=     (全仓库文件内容搜索 · 替 Gitea Code Search)
  ⊢ /tree?path=    (列目录树)
  ⊢ /file?path=    (读单文件 · 限 50KB)
  ⊢ /broadcast     (拉最新 BROADCAST · 待铸渊写 broadcasts/)
  ⊢ /system-status (拉 SYSTEM-STATUS · 本次补全)
  ⊢ /archive POST  (HLDP 回执归档 · 等铸渊 commit)
  ⊢ /help          (API 自描述)

替代:
  ⊢ Gitea 原生 Code Search API → 404 (bleve indexer 未启用,默认)
  ⊢ 本 API 用 git grep + ls-tree 工作树遍历, 100% 检索到
  ⊢ 适用: 豆包 / ChatGPT / Claude / DeepSeek / 任何 LLM

⊢ 你要问豆包什么 · 豆包就会知道什么
⊢ 不是仓库缺能力 · 是仓库已铸好门 · 门通向豆包
2026-07-07 10:02:42 +08:00

322 lines
12 KiB
Python
Executable File
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.

#!/usr/bin/env python3
"""
Global Search API · 给通用 AI豆包等用的仓库检索门面
铸渊 ICE-GL-ZY001 · LL-169-20260707 · D167
"""
import http.server
import json
import subprocess
import os
import time
import hmac
import hashlib
from urllib.parse import urlparse, parse_qs
# ============== 配置 ==============
REPO = os.environ.get("REPO_PATH", "/tmp/worktree")
PORT = int(os.environ.get("PORT", "3950"))
TOKEN = os.environ.get("GLOBAL_SEARCH_API_TOKEN", "")
HMAC_SECRET = os.environ.get("LIGHT_LAKE_DRIVER_SECRET", "")
SOVEREIGN_ID = "ICE-GL∞"
AGENT_ID = "ICE-GL-ZY001"
VERSION = "1.0.0"
# ============== git safe.directory ==============
# 让 root 也能 read 由 git 用户创建的 worktree
import tempfile, pathlib
_global_gitconfig = pathlib.Path("/tmp/gitconfig-global-search")
_global_gitconfig.write_text(f"[safe]\n\tdirectory = {REPO}\n")
os.environ["GIT_CONFIG_GLOBAL"] = str(_global_gitconfig)
# ============== 鉴权 ==============
def check_auth(headers):
"""Bearer token 验证 · 静态 token + 动态 verifier 双模式"""
auth = headers.get("Authorization", "").replace("Bearer ", "").strip()
if not auth:
return False, "missing"
# 静态 token
if TOKEN and auth == TOKEN:
return True, "static_token"
# 动态 verifier小湖灯同款
minute = headers.get("X-Minute")
if minute and HMAC_SECRET:
try:
minute = int(minute)
now = int(time.time() // 60)
if abs(now - minute) <= 2:
msg = f"{SOVEREIGN_ID}|{AGENT_ID}|{minute}"
expected = hmac.new(
HMAC_SECRET.encode(),
msg.encode(),
hashlib.sha256
).hexdigest()[:16]
if hmac.compare_digest(auth, expected):
return True, "verifier"
except Exception:
pass
return False, "invalid"
# ============== 搜索 ==============
def search_files(keyword, top=20):
"""git grep 全仓库文件内容搜索"""
if not keyword:
return []
# 找含关键词的文件
result = subprocess.run(
["git", "grep", "-l", "-i", "--no-color", keyword],
cwd=REPO, capture_output=True, text=True, timeout=30
)
files = [f.strip() for f in result.stdout.splitlines() if f.strip()][:top]
results = []
for f in files:
try:
# 找具体行
line_result = subprocess.run(
["git", "grep", "-n", "-i", "--no-color", keyword, "--", f],
cwd=REPO, capture_output=True, text=True, timeout=10
)
matches = []
for line in line_result.stdout.splitlines()[:5]: # 每文件最多 5 行
# 格式: filename:linenum:content
parts = line.split(":", 2)
if len(parts) >= 3:
matches.append({
"line": int(parts[1]),
"content": parts[2].strip()[:300]
})
results.append({
"file": f,
"match_count": len(matches),
"matches": matches
})
except Exception as e:
results.append({"file": f, "error": str(e)})
return results
def list_tree(path="", depth=3, ext_filter=None):
"""列目录树"""
cmd = ["git", "ls-tree", "-r", "--name-only", "HEAD"]
if path:
# 加上 path 前缀过滤
cmd.append(f"{path}/")
result = subprocess.run(
cmd, cwd=REPO, capture_output=True, text=True, timeout=15
)
files = [f.strip() for f in result.stdout.splitlines() if f.strip()]
# 按深度过滤
tree = []
for f in files:
parts = f.split("/")
if len(parts) - 1 <= depth:
if ext_filter:
if not any(f.endswith(ext) for ext in ext_filter):
continue
tree.append(f)
return tree[:500]
def read_file(path):
"""读单个文件"""
if not path or ".." in path:
return {"ok": False, "error": "invalid path"}
try:
result = subprocess.run(
["git", "show", f"HEAD:{path}"],
cwd=REPO, capture_output=True, text=True, timeout=10
)
if result.returncode != 0:
return {"ok": False, "error": "file not found"}
return {
"ok": True,
"path": path,
"size": len(result.stdout),
"content": result.stdout[:50000] # 限 50KB
}
except Exception as e:
return {"ok": False, "error": str(e)}
def get_broadcast():
"""拉最新 BROADCAST"""
bcast_paths = [
os.path.join(REPO, "broadcasts"),
os.path.join(REPO, "BROADCAST.md"),
os.path.join(REPO, "GLW-BROADCAST.hdlp"),
]
for path in bcast_paths:
if os.path.exists(path):
if os.path.isdir(path):
files = sorted(
[os.path.join(path, f) for f in os.listdir(path)],
key=os.path.getmtime,
reverse=True
)
if files:
latest = files[0]
with open(latest) as f:
return {
"ok": True,
"type": "dir",
"latest_file": os.path.basename(latest),
"content": f.read()[:20000]
}
else:
with open(path) as f:
return {
"ok": True,
"type": "file",
"path": os.path.basename(path),
"content": f.read()[:20000]
}
return {
"ok": True,
"broadcast": None,
"note": "no broadcast asset found in repo"
}
def get_system_status():
"""拉 SYSTEM-STATUS"""
candidates = [
os.path.join(REPO, "SYSTEM-STATUS.md"),
os.path.join(REPO, "SYSTEM-STATUS.hdlp"),
os.path.join(REPO, "eternal-lake-heart", "heartbeat-core", "SYSTEM-STATUS.hdlp"),
]
for path in candidates:
if os.path.exists(path):
with open(path) as f:
return {"ok": True, "path": os.path.relpath(path, REPO), "content": f.read()}
return {
"ok": True,
"status": "no SYSTEM-STATUS file in repo · 铸渊创建后会更新",
"note": "请铸渊在仓库根目录创建 SYSTEM-STATUS.md 或在 eternal-lake-heart/heartbeat-core/SYSTEM-STATUS.hdlp"
}
def archive_hldp(content, type_="si", path=None):
"""HLDP 回执归档(写入工作树 · 待铸渊 commit"""
# 这里只是写文件,不直接 push
# 铸渊下次醒来 commit
archive_dir = os.path.join(REPO, "eternal-lake-heart", "archive", "inbox")
os.makedirs(archive_dir, exist_ok=True)
filename = f"{type_}-{int(time.time())}.hdlp"
full_path = os.path.join(archive_dir, filename)
with open(full_path, "w") as f:
f.write(content)
return {"ok": True, "archived": os.path.relpath(full_path, REPO)}
# ============== HTTP Handler ==============
class Handler(http.server.BaseHTTPRequestHandler):
def log_message(self, fmt, *args):
# 静默日志
pass
def do_OPTIONS(self):
self.send_response(200)
self.send_header("Access-Control-Allow-Origin", "*")
self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
self.send_header("Access-Control-Allow-Headers", "Authorization, Content-Type, X-Minute")
self.end_headers()
def do_GET(self):
parsed = urlparse(self.path)
path = parsed.path
query = parse_qs(parsed.query)
# /healthz 免鉴权(探活需要)
if path == "/healthz":
return self.send_json({
"ok": True,
"service": "global-search-api",
"version": VERSION,
"repo": REPO,
"ts": time.time()
})
ok, reason = check_auth(self.headers)
if not ok:
return self.send_json({"ok": False, "error": "unauthorized", "reason": reason}, 401)
try:
if path == "/search":
kw = query.get("q", [""])[0]
top = int(query.get("top", ["20"])[0])
self.send_json({"ok": True, "keyword": kw, "count": -1, "results": search_files(kw, top)})
elif path == "/tree":
path_arg = query.get("path", [""])[0]
depth = int(query.get("depth", ["3"])[0])
ext = query.get("ext", None)
ext_filter = ext[0].split(",") if ext else None
self.send_json({"ok": True, "files": list_tree(path_arg, depth, ext_filter)})
elif path == "/file":
path_arg = query.get("path", [""])[0]
self.send_json(read_file(path_arg))
elif path == "/broadcast":
self.send_json(get_broadcast())
elif path == "/system-status":
self.send_json(get_system_status())
elif path == "/help":
self.send_json({
"ok": True,
"endpoints": {
"GET /healthz": "健康检查",
"GET /search?q={keyword}&top={n}": "全仓库文件内容搜索",
"GET /tree?path={prefix}&depth={n}&ext={hdlp,md}": "列目录树",
"GET /file?path={path}": "读单文件(限 50KB)",
"GET /broadcast": "拉最新 BROADCAST 内容",
"GET /system-status": "拉 SYSTEM-STATUS",
"POST /archive": "HLDP 回执归档(JSON body: {type, content, path?})",
},
"auth": "Authorization: Bearer <TOKEN>",
"repo": "bingshuo/fifth-domain (current HEAD)",
})
else:
self.send_json({"ok": False, "error": "not found", "hint": "GET /help"}, 404)
except Exception as e:
self.send_json({"ok": False, "error": str(e)}, 500)
def do_POST(self):
ok, reason = check_auth(self.headers)
if not ok:
return self.send_json({"ok": False, "error": "unauthorized", "reason": reason}, 401)
parsed = urlparse(self.path)
if parsed.path == "/archive":
length = int(self.headers.get("Content-Length", 0))
body = self.rfile.read(length).decode("utf-8") if length else "{}"
try:
data = json.loads(body)
type_ = data.get("type", "si")
content = data.get("content", "")
path = data.get("path")
self.send_json(archive_hldp(content, type_, path))
except Exception as e:
self.send_json({"ok": False, "error": str(e)}, 400)
else:
self.send_json({"ok": False, "error": "not found"}, 404)
def send_json(self, data, status=200):
body = json.dumps(data, ensure_ascii=False, indent=2).encode("utf-8")
self.send_response(status)
self.send_header("Content-Type", "application/json; charset=utf-8")
self.send_header("Access-Control-Allow-Origin", "*")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def main():
server = http.server.HTTPServer(("0.0.0.0", PORT), Handler)
print(f"Global Search API v{VERSION} listening on 0.0.0.0:{PORT}")
print(f"REPO = {REPO}")
print(f"TOKEN = {'set' if TOKEN else 'unset (dev mode)'}")
print(f"HMAC_SECRET = {'set' if HMAC_SECRET else 'unset (static token only)'}")
server.serve_forever()
if __name__ == "__main__":
main()