chore(docs): 文档库并入代码仓(R4 选 a)+ 索引/台账跟进
1) dsh-server-docs/ 从工作区(原 E:\...\aliyun-dsh-server\dsh-server-docs)**整体并入本仓**,
保留目录名 ⇒ 仓库内 dsh-server-docs/... 的相对引用天然继续有效;旧目录(含其 .git)已归档到
工作区 _中间产物_待清理/,未随本提交带入。
2) .gitattributes:新增 `dsh-server-docs/** -text` —— 原文档库是 `* -text` + autocrlf=false,
必须保持纯 LF,否则会被本仓的 CRLF 规则翻掉。
3) 活引用里的绝对路径已全部改到新位置(docs 的 INDEX / README / scripts / skills + 用户级 skills
+ ~/.workbuddy/settings.json 的 hooks);历史档案(04-调整方案/、archive/)按「只增不改」未动。
⚠️ hooks 路径改动需「完全重启会话」才生效(配置是会话启动快照)。
4) 交接单/T08:新增 §16「生产整体切换执行记录」(形态 / 落地动作 / **4 个只有真上线才暴露的真 bug** /
验收证据 / 回滚命令 / 残留项);台账 T08 行 → 已完成并归档;03-路线图 §二 登记 T08 收尾项。
5) 统一称谓:**「本机」只指跑 WorkBuddy 的开发机**,47 / 106 一律写「远程服务器」。
This commit is contained in:
1 parent
c70d5d860e
commit
5ad755116e
173 files changed
+27632
No files matched your search
@@ -0,0 +1,133 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* 插件兼容性预检 v2 —— PoC(只读)
|
||||
*
|
||||
* A. 平台内置 @deepseek-ai/* 的「真实导出符号」清单(判据来源)
|
||||
* B. 候选池每个 tgz:解包 → 依赖声明 + bundle 内 import 语句
|
||||
* C. 逐条比对 → 判定「兼容 / 不兼容 / 需装后复核」
|
||||
*
|
||||
* 用法:node plugin-compat-check.mjs
|
||||
*/
|
||||
import { readFileSync, existsSync, readdirSync, mkdtempSync, rmSync, statSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { execFileSync } from 'node:child_process'
|
||||
|
||||
const DSH_ROOT = process.env.DSH_ROOT || '/usr/local/lib/node_modules/@deepseek-ai/dsh'
|
||||
const NESTED = join(DSH_ROOT, 'node_modules', '@deepseek-ai')
|
||||
const POOL = process.env.POOL || '/var/lib/dshs/business-plugins'
|
||||
|
||||
/* ---------- A. 平台 exports 真值 ---------- */
|
||||
const platform = new Map() // pkgName -> { version, keys:Set }
|
||||
for (const name of readdirSync(NESTED)) {
|
||||
if (name.startsWith('.')) continue
|
||||
const pj = join(NESTED, name, 'package.json')
|
||||
if (!existsSync(pj)) continue
|
||||
const meta = JSON.parse(readFileSync(pj, 'utf8'))
|
||||
let entry = null
|
||||
for (const cand of ['lib/index.js', 'dist/index.js', 'index.js']) {
|
||||
const f = join(NESTED, name, cand)
|
||||
if (existsSync(f)) { entry = f; break }
|
||||
}
|
||||
let keys = null
|
||||
if (entry) { try { keys = new Set(Object.keys(await import(entry))) } catch { /* ignore */ } }
|
||||
platform.set(meta.name, { version: meta.version, keys })
|
||||
}
|
||||
console.log(`[A] 平台内置 @deepseek-ai 包 ${platform.size} 个(其中有导出符号清单的 ${[...platform.values()].filter((v) => v.keys).length} 个)`)
|
||||
|
||||
const llm = platform.get('@deepseek-ai/dsh-llm')
|
||||
console.log(` · dsh-llm@${llm?.version} 导出 ${llm?.keys?.size ?? 0} 个符号;含 assertNever? ${llm?.keys?.has('assertNever') ? '✅' : '❌(= anysearch 崩溃判据成立)'}`)
|
||||
|
||||
/* ---------- B+C. 逐个 tgz 预检 ---------- */
|
||||
const IMPORT_RE = /(?:import|export)\s*(?:\*\s*as\s*[\w$]+|\{([^}]*)\})\s*from\s*["'](@deepseek-ai\/[^"'/]+(?:\/[^"']*)?)["']/g
|
||||
|
||||
function scanFileImports(file, base) {
|
||||
const text = readFileSync(file, 'utf8')
|
||||
const hits = []
|
||||
let m
|
||||
IMPORT_RE.lastIndex = 0
|
||||
while ((m = IMPORT_RE.exec(text))) {
|
||||
const spec = m[1] || ''
|
||||
// 具名导入:取每个符号(去掉 as 别名)
|
||||
const named = spec.split(',').map((s) => s.trim().split(/\s+as\s+/)[0].trim()).filter((s) => s && !s.startsWith('type '))
|
||||
hits.push({ pkg: m[2], named, file: file.slice(base.length + 1) })
|
||||
}
|
||||
return hits
|
||||
}
|
||||
|
||||
function walkJs(dir, base, out = []) {
|
||||
for (const e of readdirSync(dir, { withFileTypes: true })) {
|
||||
const p = join(dir, e.name)
|
||||
if (e.isDirectory()) { if (e.name !== 'node_modules') walkJs(p, base, out) }
|
||||
else if (/\.(js|mjs|cjs)$/.test(e.name)) out.push(p)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
const tgzs = existsSync(POOL) ? readdirSync(POOL).filter((f) => f.endsWith('.tgz')) : []
|
||||
console.log(`\n[B] 候选池 tgz ${tgzs.length} 个:${tgzs.join(', ') || '(无)'}\n`)
|
||||
|
||||
for (const tgz of tgzs) {
|
||||
const full = join(POOL, tgz)
|
||||
const tmp = mkdtempSync(join(tmpdir(), 'compat-'))
|
||||
console.log('━'.repeat(72))
|
||||
console.log(`📦 ${tgz} (${(statSync(full).size / 1024).toFixed(0)} KB)`)
|
||||
try {
|
||||
execFileSync('tar', ['-xzf', full, '-C', tmp], { stdio: 'pipe' })
|
||||
} catch (e) {
|
||||
console.log(` ✗ 解包失败: ${String(e.message).slice(0, 100)}`)
|
||||
rmSync(tmp, { recursive: true, force: true })
|
||||
continue
|
||||
}
|
||||
|
||||
// package.json
|
||||
const pkgRoot = join(tmp, 'package')
|
||||
const pj = join(pkgRoot, 'package.json')
|
||||
let name = tgz, version = '?'
|
||||
const deps = {}
|
||||
if (existsSync(pj)) {
|
||||
const meta = JSON.parse(readFileSync(pj, 'utf8'))
|
||||
name = meta.name || name
|
||||
version = meta.version || '?'
|
||||
Object.assign(deps, meta.dependencies || {}, meta.peerDependencies || {})
|
||||
}
|
||||
console.log(` 包名 ${name}@${version}`)
|
||||
|
||||
// 依赖里的 @deepseek-ai
|
||||
const dsDeps = Object.entries(deps).filter(([k]) => k.startsWith('@deepseek-ai/'))
|
||||
if (dsDeps.length) {
|
||||
console.log(` 声明的 @deepseek-ai 依赖 ${dsDeps.length} 个:`)
|
||||
for (const [d, rng] of dsDeps) {
|
||||
const p = platform.get(d)
|
||||
const verdict = !p ? '⚠️ 平台无此包' : (p.version === rng.replace(/^[\^~=]/, '') ? '✅ 版本一致' : `⚠️ 平台为 ${p.version},插件要求 ${rng}`)
|
||||
console.log(` ${d} ${rng} → ${verdict}`)
|
||||
}
|
||||
} else {
|
||||
console.log(' 未声明任何 @deepseek-ai 依赖')
|
||||
}
|
||||
|
||||
// bundle 内 import
|
||||
const files = walkJs(pkgRoot, pkgRoot)
|
||||
const hits = files.flatMap((f) => scanFileImports(f, pkgRoot))
|
||||
console.log(` 扫描 ${files.length} 个 js 文件,命中 ${hits.length} 条 @deepseek-ai 导入`)
|
||||
|
||||
const problems = []
|
||||
for (const h of hits) {
|
||||
const p = platform.get(h.pkg)
|
||||
if (!p || !p.keys) continue
|
||||
const missing = h.named.filter((n) => n && !p.keys.has(n))
|
||||
if (missing.length) problems.push({ ...h, missing })
|
||||
}
|
||||
if (problems.length) {
|
||||
console.log(' 🔴 不兼容命中:')
|
||||
for (const pr of problems) console.log(` ${pr.pkg} 缺少导出 ${pr.missing.join(', ')} ← ${pr.file}`)
|
||||
} else if (hits.length) {
|
||||
console.log(' 🟢 静态可判部分:全部符号在平台包中存在')
|
||||
} else {
|
||||
console.log(' ⚪ 静态无法判定(插件未直接 import 平台包 → 需「装后扫 node_modules」复核)')
|
||||
}
|
||||
rmSync(tmp, { recursive: true, force: true })
|
||||
}
|
||||
console.log('\n' + '━'.repeat(72))
|
||||
console.log('说明:静态判定只覆盖「插件自身 bundle 里的 import」。若插件把平台 API 的调用藏在依赖包内')
|
||||
console.log(' (如 anysearch 的 @deepseek-ai/dsh-tool-web),必须做「装后扫 profile node_modules」才能抓到。')
|
||||
Reference in new issue
Block a user