From 90a44d7140c8f99e447c2214c528c1f2b8751289 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=95=85=E7=92=83?= Date: Thu, 23 Jul 2026 15:31:57 +0800 Subject: [PATCH] feat: llm wiki sync --- packages/cli/postinstall.js | 32 ++-- packages/core/src/advisor/sync.ts | 44 ++---- skills-lock.json | 251 ++++++++++++++++++++++++++++++ 3 files changed, 277 insertions(+), 50 deletions(-) create mode 100644 skills-lock.json diff --git a/packages/cli/postinstall.js b/packages/cli/postinstall.js index 9210efc..9e799ee 100644 --- a/packages/cli/postinstall.js +++ b/packages/cli/postinstall.js @@ -5,12 +5,11 @@ * 保证用户首次使用 `bl advisor recommend` 时数据已就位。 * * 流程: - * 1. fetch FC 签名函数 → 拿 OSS 预签名 URL - * 2. 下载 manifest.json + wiki-doc-full.tar.br(~2.15MB) - * 3. 校验 sha256 - * 4. Node 原生 brotli 解压 + tar-stream 解包到同盘临时目录 - * 5. renameSync 原子替换到 ~/.bailian/skills/bailian-docs-llm-wiki/ - * 6. 写 ~/.bailian/wiki-sync-state.json + * 1. 从公共读 OSS 直链下载 manifest.json + wiki-doc-full.tar.br(~2.15MB) + * 2. 校验 sha256 + * 3. Node 原生 brotli 解压 + tar-stream 解包到同盘临时目录 + * 4. renameSync 原子替换到 ~/.bailian/skills/bailian-docs-llm-wiki/ + * 5. 写 ~/.bailian/wiki-sync-state.json * * 设计约束: * - 无条件覆盖:每次 install 都全量替换,不比对已有版本 @@ -34,7 +33,7 @@ import { pipeline } from "node:stream/promises"; import { createBrotliDecompress } from "node:zlib"; import tar from "tar-stream"; -const FC_SIGN_URL = "https://signature-gmqkxxozrl.cn-hangzhou.fcapp.run"; +const OSS_BASE_URL = "https://bailian-wiki.oss-cn-hangzhou.aliyuncs.com/bailian-docs-llm-wiki"; const CONFIG_DIR_NAME = ".bailian"; const SKILL_DIR_NAME = "skills/bailian-docs-llm-wiki"; const STATE_FILE_NAME = "wiki-sync-state.json"; @@ -106,25 +105,18 @@ function atomicSwap(tmpDir, catalogDir) { } async function main() { - // 1. 拿全部签名 URL - const signResp = await fetchJson(FC_SIGN_URL, MANIFEST_TIMEOUT_MS); - const urls = signResp?.urls; - if (!urls?.[MANIFEST_KEY] || !urls?.[ASSET_KEY]) { - throw new Error("签名函数未返回所需 URL"); - } - - // 2. 下载 manifest - const manifest = await fetchJson(urls[MANIFEST_KEY], MANIFEST_TIMEOUT_MS); + // 1. 下载 manifest + const manifest = await fetchJson(`${OSS_BASE_URL}/${MANIFEST_KEY}`, MANIFEST_TIMEOUT_MS); if (!manifest?.version) throw new Error("manifest 无 version"); - // 3. 下载 tar.br + 校验 - const tarBuf = await downloadBuffer(urls[ASSET_KEY]); + // 2. 下载 tar.br + 校验 + const tarBuf = await downloadBuffer(`${OSS_BASE_URL}/${ASSET_KEY}`); const sha256 = createHash("sha256").update(tarBuf).digest("hex"); if (manifest.asset?.sha256 && sha256 !== manifest.asset.sha256) { throw new Error("sha256 校验失败"); } - // 4. 解包到同盘临时目录 + 原子替换 + // 3. 解包到同盘临时目录 + 原子替换 const catalogDir = getCatalogDir(); const tmpDir = `${catalogDir}.tmp-${process.pid}-${Date.now()}`; try { @@ -136,7 +128,7 @@ async function main() { throw err; } - // 5. 写 state + // 4. 写 state try { writeFileSync( getStatePath(), diff --git a/packages/core/src/advisor/sync.ts b/packages/core/src/advisor/sync.ts index 382b8c9..3a30f51 100644 --- a/packages/core/src/advisor/sync.ts +++ b/packages/core/src/advisor/sync.ts @@ -3,7 +3,7 @@ * * `bl advisor recommend` 执行时调用 `maybeSyncWikiData()`: * 1. 12h throttle:距上次检查不足 12h 直接跳过 - * 2. 拉 FC 签名 URL → 下载 manifest.json 比对版本 + * 2. 从公共读 OSS 下载 manifest.json 比对版本 * 3. 版本相同 → 仅刷新 lastChecked * 4. 版本不同 → 下载 tar.br → 校验 sha256 → brotli 解压 + tar-stream 解包 * 到同盘临时目录 → renameSync 原子替换 → 写 state @@ -30,8 +30,8 @@ import { createBrotliDecompress } from "node:zlib"; import tar from "tar-stream"; import { getConfigDir } from "../config/paths.ts"; -/** FC 匿名签名函数入口,返回 OSS 预签名 URL(硬编码,不走 env)。 */ -const FC_SIGN_URL = "https://signature-gmqkxxozrl.cn-hangzhou.fcapp.run"; +/** 公共读 OSS 目录,直链下载(硬编码,不走 env)。 */ +const OSS_BASE_URL = "https://bailian-wiki.oss-cn-hangzhou.aliyuncs.com/bailian-docs-llm-wiki"; const SKILL_DIR_NAME = "skills/bailian-docs-llm-wiki"; const STATE_FILE_NAME = "wiki-sync-state.json"; const MODELS_FILE = "models.jsonl"; @@ -93,20 +93,6 @@ function writeState(state: SyncState): void { } } -/** 拉全部 OSS 预签名 URL:`{ urls: { "manifest.json": "...", "wiki-doc-full.tar.br": "..." } }` */ -async function fetchSignedUrls(): Promise | null> { - try { - const res = await fetch(FC_SIGN_URL, { - signal: AbortSignal.timeout(MANIFEST_TIMEOUT_MS), - }); - if (!res.ok) return null; - const data = (await res.json()) as { urls?: Record }; - return data.urls ?? null; - } catch { - return null; - } -} - async function fetchManifest(url: string): Promise { try { const res = await fetch(url, { signal: AbortSignal.timeout(MANIFEST_TIMEOUT_MS) }); @@ -182,25 +168,23 @@ export async function maybeSyncWikiData(): Promise { return false; } - // 2. 拿签名 URL + manifest - const urls = await fetchSignedUrls(); - if (!urls?.[MANIFEST_KEY]) return false; // 失败不写 lastChecked,下次重试 - - const manifest = await fetchManifest(urls[MANIFEST_KEY]); - if (!manifest?.version) return false; + // 2. 拉 manifest + const manifest = await fetchManifest(`${OSS_BASE_URL}/${MANIFEST_KEY}`); + if (!manifest?.version) return false; // 失败不写 lastChecked,下次重试 // 3. 版本相同且本地数据存在:仅刷新 lastChecked,无需重新下载。 - // 若数据缺失,即便版本相同也要继续走下载补齐(落到第 4 步)。 - if (state && manifest.version === state.version && catalogDataExists()) { - writeState({ lastChecked: now, version: state.version }); + // 覆盖两种状态:(a) state.version === manifest.version → 直接命中; + // (b) state 缺失但数据完好(用户或意外只删了 state)→ 用 manifest 版本 + // 写回 state,避免无谓的 2MB 下载+解压。 + // 数据缺失或版本落后时落到第 4 步全量下载补齐。 + const dataOk = catalogDataExists(); + if (dataOk && (!state || state.version === manifest.version)) { + writeState({ lastChecked: now, version: manifest.version }); return false; } // 4. 版本不同:下载 + 校验 + 解包 + 原子替换 - const assetUrl = urls[ASSET_KEY]; - if (!assetUrl) return false; - - const tarBuf = await downloadBuffer(assetUrl); + const tarBuf = await downloadBuffer(`${OSS_BASE_URL}/${ASSET_KEY}`); if (!tarBuf) return false; // sha256 校验 diff --git a/skills-lock.json b/skills-lock.json new file mode 100644 index 0000000..a1ffcc7 --- /dev/null +++ b/skills-lock.json @@ -0,0 +1,251 @@ +{ + "version": 1, + "skills": { + "ask-matt": { + "source": "mattpocock/skills", + "sourceType": "github", + "skillPath": "skills/engineering/ask-matt/SKILL.md", + "computedHash": "0f843160e34a24f5bd12cdc7de7d40951e77fbdc05ce8f891b37ca32eac2c44d" + }, + "batch-grill-me": { + "source": "mattpocock/skills", + "sourceType": "github", + "skillPath": "skills/in-progress/batch-grill-me/SKILL.md", + "computedHash": "f448831b8f04518b1527408f1d5024384ad5f543bdbaac6fabb7aa053ad60489" + }, + "claude-handoff": { + "source": "mattpocock/skills", + "sourceType": "github", + "skillPath": "skills/in-progress/claude-handoff/SKILL.md", + "computedHash": "40a7f4ec80b9826ad7d0c47b0778c99686d6d9d662112b3e175a816f3aca4c39" + }, + "code-review": { + "source": "mattpocock/skills", + "sourceType": "github", + "skillPath": "skills/engineering/code-review/SKILL.md", + "computedHash": "31d149a480eaa68c11e32f5ee77f0fd0b98a906834d531d881d502352edd0b8e" + }, + "codebase-design": { + "source": "mattpocock/skills", + "sourceType": "github", + "skillPath": "skills/engineering/codebase-design/SKILL.md", + "computedHash": "6d9d51d8caa01633fd00cf87089c5618c982321f942ac69f62565dc001c6b22f" + }, + "design-an-interface": { + "source": "mattpocock/skills", + "sourceType": "github", + "skillPath": "skills/deprecated/design-an-interface/SKILL.md", + "computedHash": "aef348a9af675b771635623b5aa7790da4d40adc1ee88b58b6fd116eda2047d8" + }, + "diagnosing-bugs": { + "source": "mattpocock/skills", + "sourceType": "github", + "skillPath": "skills/engineering/diagnosing-bugs/SKILL.md", + "computedHash": "fd6c99466b7ba43be624e6e66ed6d7af2796ded7218f24c83820823977819e22" + }, + "domain-modeling": { + "source": "mattpocock/skills", + "sourceType": "github", + "skillPath": "skills/engineering/domain-modeling/SKILL.md", + "computedHash": "363cb0f53b0b431e7c00086ad1f823500b7e1b70b5616ee969c979f0934e9e6e" + }, + "edit-article": { + "source": "mattpocock/skills", + "sourceType": "github", + "skillPath": "skills/personal/edit-article/SKILL.md", + "computedHash": "cb2a13b2acb6c8a0706eb3a973a2681f30fd74c00784c74ceaa98e12eb2b2fc3" + }, + "git-guardrails-claude-code": { + "source": "mattpocock/skills", + "sourceType": "github", + "skillPath": "skills/misc/git-guardrails-claude-code/SKILL.md", + "computedHash": "8c29552c621da0121427306afd92f8e02f47f446cdb0b55ceae01de6879fcb3a" + }, + "grill-me": { + "source": "mattpocock/skills", + "sourceType": "github", + "skillPath": "skills/productivity/grill-me/SKILL.md", + "computedHash": "f361db4e15e6bfd562a9282b1dccda513910a50061f9e838ce017be9c69dde3f" + }, + "grill-with-docs": { + "source": "mattpocock/skills", + "sourceType": "github", + "skillPath": "skills/engineering/grill-with-docs/SKILL.md", + "computedHash": "9c460cbd94fd3c63cdef967dbdb6e66ca687103cdc380cd37834e4d10b738f78" + }, + "grilling": { + "source": "mattpocock/skills", + "sourceType": "github", + "skillPath": "skills/productivity/grilling/SKILL.md", + "computedHash": "368d3dd7251247c69f3656d93dc83c8f1577792eacac2111f1e7981db2ece49b" + }, + "handoff": { + "source": "mattpocock/skills", + "sourceType": "github", + "skillPath": "skills/productivity/handoff/SKILL.md", + "computedHash": "ad03e8d4ea3cbbff66420eb7ba3cc375b5cbe1821a2449b53e863256cf5b5cde" + }, + "implement": { + "source": "mattpocock/skills", + "sourceType": "github", + "skillPath": "skills/engineering/implement/SKILL.md", + "computedHash": "2139cfedf24791adbc839aaab6019cff158af1e28bfead020ec6e0ce01b3e74d" + }, + "improve-codebase-architecture": { + "source": "mattpocock/skills", + "sourceType": "github", + "skillPath": "skills/engineering/improve-codebase-architecture/SKILL.md", + "computedHash": "66e8a50c83c3c724fcfe0769701b665c56cec220cc4f49cc1aee8bdfc07de94a" + }, + "loop-me": { + "source": "mattpocock/skills", + "sourceType": "github", + "skillPath": "skills/in-progress/loop-me/SKILL.md", + "computedHash": "e1fcab9b531b338eb540c9d57ee089e2660e4bba1d7623ec566c6f32f77f2d26" + }, + "migrate-to-shoehorn": { + "source": "mattpocock/skills", + "sourceType": "github", + "skillPath": "skills/misc/migrate-to-shoehorn/SKILL.md", + "computedHash": "6397731ced114f3657aa88b55ed13d1344a56d77ca449c568e3200c21740fa99" + }, + "obsidian-vault": { + "source": "mattpocock/skills", + "sourceType": "github", + "skillPath": "skills/personal/obsidian-vault/SKILL.md", + "computedHash": "5c08eda96fd76a71793c0a8cb91bef41d518586654a14af4f2edf8f8fd0f96a7" + }, + "prototype": { + "source": "mattpocock/skills", + "sourceType": "github", + "skillPath": "skills/engineering/prototype/SKILL.md", + "computedHash": "faba901c53a6ca245174c4ba5db3929d14253232b3050fdbd46539720adf1ab8" + }, + "qa": { + "source": "mattpocock/skills", + "sourceType": "github", + "skillPath": "skills/deprecated/qa/SKILL.md", + "computedHash": "223de2b02aa9ab36cfa4fe93e80e527b56e912e7ae725f9d596f6d2457afab77" + }, + "request-refactor-plan": { + "source": "mattpocock/skills", + "sourceType": "github", + "skillPath": "skills/deprecated/request-refactor-plan/SKILL.md", + "computedHash": "08c06e587d0f979b8731f6a00c67fb8d00e563614daf80bf648d41148e553473" + }, + "research": { + "source": "mattpocock/skills", + "sourceType": "github", + "skillPath": "skills/engineering/research/SKILL.md", + "computedHash": "bd3e2c6826671d82c86ed0da3dac3370ebcf63b0fe847f91bb444e1fb7dac21b" + }, + "resolving-merge-conflicts": { + "source": "mattpocock/skills", + "sourceType": "github", + "skillPath": "skills/engineering/resolving-merge-conflicts/SKILL.md", + "computedHash": "28aad6f8b1b7025abc8892fa8890f68fe1533499b28a565f416b159131d22fad" + }, + "scaffold-exercises": { + "source": "mattpocock/skills", + "sourceType": "github", + "skillPath": "skills/misc/scaffold-exercises/SKILL.md", + "computedHash": "354c91f6dbc9b058632f30594aacb4edc6d25012596585abb00402af8d7ec5e9" + }, + "setup-matt-pocock-skills": { + "source": "mattpocock/skills", + "sourceType": "github", + "skillPath": "skills/engineering/setup-matt-pocock-skills/SKILL.md", + "computedHash": "74e894a3509e2676d4cdb771c8eace087092430635e845e02a9cc2f757c552a4" + }, + "setup-pre-commit": { + "source": "mattpocock/skills", + "sourceType": "github", + "skillPath": "skills/misc/setup-pre-commit/SKILL.md", + "computedHash": "1b140af948d0a6330c4cf52d46ca2e06a0e96fdc34d22ec8888d2b9751b99b62" + }, + "setup-ts-deep-modules": { + "source": "mattpocock/skills", + "sourceType": "github", + "skillPath": "skills/in-progress/setup-ts-deep-modules/SKILL.md", + "computedHash": "bd5cbaa455454c2d6c27a1b734e0d820606da6c482bf3c79ec55fbc3f3e03ef3" + }, + "tdd": { + "source": "mattpocock/skills", + "sourceType": "github", + "skillPath": "skills/engineering/tdd/SKILL.md", + "computedHash": "81eca2a5b53a63f481c0849be7a663a8cd43d5cf53f32b644ec0a2f50cf91aa2" + }, + "teach": { + "source": "mattpocock/skills", + "sourceType": "github", + "skillPath": "skills/productivity/teach/SKILL.md", + "computedHash": "68999bb1a241384b2f921f3321d779b7d05eb6089077b70d88cee2aee3d75ccc" + }, + "to-questionnaire": { + "source": "mattpocock/skills", + "sourceType": "github", + "skillPath": "skills/in-progress/to-questionnaire/SKILL.md", + "computedHash": "d8938509f3400d9977343e11e876fc6e1982b0b9c133a2f2e9d3d730a67a103e" + }, + "to-spec": { + "source": "mattpocock/skills", + "sourceType": "github", + "skillPath": "skills/engineering/to-spec/SKILL.md", + "computedHash": "0f544cd0c099c06f0dd0b7b9ee98b4237218e7e95fd3d3c02e791efbaf74bacb" + }, + "to-tickets": { + "source": "mattpocock/skills", + "sourceType": "github", + "skillPath": "skills/engineering/to-tickets/SKILL.md", + "computedHash": "5d79577541b5cf6dade61c69844432ea20ce7b12527cdda1dd4d6fe8d59a135d" + }, + "triage": { + "source": "mattpocock/skills", + "sourceType": "github", + "skillPath": "skills/engineering/triage/SKILL.md", + "computedHash": "7c923b6a677cfe689500721f08307e2a4c46797ff169dc55ef6c34a431a0d533" + }, + "ubiquitous-language": { + "source": "mattpocock/skills", + "sourceType": "github", + "skillPath": "skills/deprecated/ubiquitous-language/SKILL.md", + "computedHash": "0395170031b57ea63f2eb22561542894c03e3047430a2f1118a06b411054429b" + }, + "wayfinder": { + "source": "mattpocock/skills", + "sourceType": "github", + "skillPath": "skills/engineering/wayfinder/SKILL.md", + "computedHash": "c9e18cefd77b6b5b0ee35f59ce2fd96359aa2d2f02e3479e12c873c5402d9d43" + }, + "wizard": { + "source": "mattpocock/skills", + "sourceType": "github", + "skillPath": "skills/in-progress/wizard/SKILL.md", + "computedHash": "dab67fb9fdcc70f3a06cf2facecf58f405f478504812103b53d4c5a84765254d" + }, + "writing-beats": { + "source": "mattpocock/skills", + "sourceType": "github", + "skillPath": "skills/in-progress/writing-beats/SKILL.md", + "computedHash": "7581e49a991e45e4e486b128c6391b82bd112c143cea3cc4426c23530dd545af" + }, + "writing-fragments": { + "source": "mattpocock/skills", + "sourceType": "github", + "skillPath": "skills/in-progress/writing-fragments/SKILL.md", + "computedHash": "972ab831c7a39dab971439fa4718b668114d3063d347d8ba29b2a19765b3f911" + }, + "writing-great-skills": { + "source": "mattpocock/skills", + "sourceType": "github", + "skillPath": "skills/productivity/writing-great-skills/SKILL.md", + "computedHash": "4deb21855fb4deeeb1b8217b041faff003fd0c915d24a22f636851c520bc9c5c" + }, + "writing-shape": { + "source": "mattpocock/skills", + "sourceType": "github", + "skillPath": "skills/in-progress/writing-shape/SKILL.md", + "computedHash": "90899c7b3853a80da0d9ef634844b22871cc3ea6aa1bc9e4299f40484ac05392" + } + } +}