@dsh-bio/dsh-bio-gem 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/python.js ADDED
@@ -0,0 +1,64 @@
1
+ // python.js — dsh-bio-gem Python 子进程调用器(JSON stdin 协议)
2
+ // bridge 契约同 dsh-bio-genie:stdout 最后一行是 JSON;stderr 含
3
+ // "Traceback (most recent call last)" 头 = 代码级失败(恒 ok:true 时靠它判定)。
4
+ import { spawn } from 'node:child_process'
5
+ import { dirname, join } from 'node:path'
6
+ import { fileURLToPath } from 'node:url'
7
+ import { existsSync } from 'node:fs'
8
+
9
+ const PYTHON_DIR = join(dirname(fileURLToPath(import.meta.url)), '..', 'python')
10
+
11
+ // 运行时探测 Python:优先 miniconda(本机分析环境,cobra 已装),回退 env GEM_PYTHON / PATH
12
+ function pythonExe() {
13
+ const cands = [
14
+ process.env.GEM_PYTHON,
15
+ 'C:/Users/shuai/miniconda3/python.exe',
16
+ 'python',
17
+ ]
18
+ for (const c of cands) {
19
+ if (!c) continue
20
+ try {
21
+ if (c === 'python' || existsSync(c)) return c
22
+ } catch { /* ignore */ }
23
+ }
24
+ return 'python'
25
+ }
26
+
27
+ /** 调用 gem_ops.py(op 协议):{op, args} -> result;异常/代码级失败抛 Error。 */
28
+ export function callGem(op, args, opts = {}) {
29
+ return new Promise((resolve, reject) => {
30
+ const py = pythonExe()
31
+ const script = join(PYTHON_DIR, 'gem_ops.py')
32
+ const cp = spawn(py, ['-I', script], { cwd: PYTHON_DIR, windowsHide: true })
33
+ let out = ''
34
+ let err = ''
35
+ cp.stdout.on('data', (d) => { out += d })
36
+ cp.stderr.on('data', (d) => { err += d })
37
+ cp.on('error', (e) => reject(new Error(`python spawn failed: ${e.message}`)))
38
+ const timer = opts.timeoutMs
39
+ ? setTimeout(() => { cp.kill(); reject(new Error(`gem op ${op} timeout after ${opts.timeoutMs}ms`)) }, opts.timeoutMs)
40
+ : null
41
+ cp.on('close', (code) => {
42
+ if (timer) clearTimeout(timer)
43
+ const lines = out.trim().split(/\r?\n/).filter(Boolean)
44
+ if (!lines.length) {
45
+ return reject(new Error(`gem_ops.py produced no output (op=${op}); stderr: ${err.slice(-400)}`))
46
+ }
47
+ if (err.includes('Traceback (most recent call last)')) {
48
+ return reject(new Error(`gem op ${op} code-level failure: ${err.slice(-400)}`))
49
+ }
50
+ let parsed
51
+ try {
52
+ parsed = JSON.parse(lines[lines.length - 1])
53
+ } catch (e) {
54
+ return reject(new Error(`gem op ${op} bad JSON: ${lines[lines.length - 1].slice(0, 300)}`))
55
+ }
56
+ if (parsed.ok === false) return reject(new Error(parsed.error || `gem op ${op} failed (ok:false)`))
57
+ resolve(parsed.result)
58
+ })
59
+ cp.stdin.write(JSON.stringify({ op, args }))
60
+ cp.stdin.end()
61
+ })
62
+ }
63
+
64
+ export { pythonExe, PYTHON_DIR }
package/src/skills.js ADDED
@@ -0,0 +1,29 @@
1
+ // skills.js — dsh-bio-gem skill 注册(M1:gem-expert 主 skill)
2
+ // 工具选择决策树 + 工作流 + 实测坑位;遵循 dsh-bio-genie 注册模式(ctx.skills.register)。
3
+ import { readFileSync } from 'node:fs'
4
+ import { join, dirname } from 'node:path'
5
+ import { fileURLToPath } from 'node:url'
6
+
7
+ const SKILLS_DIR = join(dirname(fileURLToPath(import.meta.url)), '..', 'skills')
8
+
9
+ export function registerSkills(ctx) {
10
+ const disposers = []
11
+ let content = ''
12
+ try {
13
+ content = readFileSync(join(SKILLS_DIR, 'gem-expert.md'), 'utf8')
14
+ } catch {
15
+ content = `Skill body missing from plugin package (skills/gem-expert.md)。`
16
+ }
17
+ disposers.push(ctx.skills.register({
18
+ name: 'gem-expert',
19
+ description:
20
+ '基因组尺度代谢模型(GEM)主指引:工具分层选择(gem_report/validate/gapfind/gapfill/build)、构建→验证→补洞→报告工作流、' +
21
+ '跨引擎培养基自然名规则、CarveMe M9 介质边界、C58 回归锚。任何代谢模型需求先加载本 skill。',
22
+ whenToUse:
23
+ '用户提出代谢模型/GEM/基因组建模型/模型验证/模型补洞/为什么模型不长/FBA 模型准备/底盘代谢分析等需求时。',
24
+ source: 'custom',
25
+ provider: 'dsh-bio-gem',
26
+ content,
27
+ }))
28
+ return () => disposers.forEach((d) => d())
29
+ }