@furongjun1999/dsh-memory 0.5.0 → 0.6.0
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/README.md +572 -465
- package/docs/README.md +1 -0
- package/docs/eval/DSH/346/227/245/345/277/227/347/264/242/345/274/225v2_/345/217/202/350/200/203dsh-TUI_v1.0.md +248 -0
- package/docs/eval/DSH/346/227/245/345/277/227/347/264/242/345/274/225/346/225/210/346/236/234/351/252/214/350/257/201_v1.0.md +209 -0
- package/docs/eval/DSH/347/253/257/347/274/272/351/231/267/344/270/223/351/241/271_v1.0.md +254 -0
- package/docs/eval/P1b2_/350/257/273/351/235/242/344/273/243/351/231/205/344/277/256/345/244/215_v1.0.md +124 -0
- package/docs/eval/bench_lingshu_self/bench_self.py +140 -0
- package/docs/eval/bench_lingshu_self/self_bench_result.json +404 -0
- package/docs/eval/bench_lingshu_self//347/201/265/346/236/242/350/207/252/345/272/223/347/253/257/345/210/260/347/253/257/346/243/200/347/264/242/345/256/236/346/265/213_v1.0.md +38 -0
- package/docs/eval//344/270/215/345/217/257/351/235/240/346/200/247/350/220/275/345/234/260_P0_v1.0.md +185 -0
- package/docs/eval//345/217/221/345/270/20306_/344/270/200/351/224/256/351/205/215/347/275/256/344/270/216DSH0172_v1.0.md +171 -0
- package/docs/eval//346/225/205/351/232/234/346/263/250/345/205/245/345/256/236/346/265/213_v1.0.md +422 -0
- package/docs/eval//347/253/257/345/210/260/347/253/257LoCoMoQA/345/220/214/345/217/243/345/276/204/345/257/271/347/205/247_v1.0.md +100 -0
- package/docs/eval//347/253/257/345/210/260/347/253/257/345/271/262/346/211/260/346/261/240/350/257/204/346/265/213_/347/241/256/345/256/232/346/200/247/350/243/201/345/206/263vsLLM_judge_v1.1.md +197 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v1.md +156 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v10.md +210 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v11.md +227 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v12.md +203 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v13.md +233 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v14.md +191 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v15.md +213 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v16.md +214 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v17.md +199 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v18.md +183 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v19.md +207 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v2.md +156 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v3.md +152 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v4.md +128 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v5.md +114 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v6.md +192 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v7.md +187 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v8.md +207 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v9.md +203 -0
- package/docs/hive//345/256/211/345/205/250/345/256/241/350/256/241/345/256/236/351/224/232_v0.1.md +202 -0
- package/docs/hive//350/234/202/345/267/242/345/217/214/345/256/236/344/276/213/344/272/222/351/252/214_/350/256/276/350/256/241/345/256/232/347/250/277.md +19 -3
- package/docs/images/lingshu-moonlight-covenant-poster-preview.jpg +0 -0
- package/docs/images/lingshu-moonlight-covenant-poster.png +0 -0
- package/docs/mdcg//345/212/237/350/203/275/350/260/203/347/224/250/346/230/240/345/260/204/350/241/250_v0.1.md +40 -40
- package/docs/theory//344/270/215/345/217/257/351/235/240/345/256/232/347/220/206/344/270/216/345/244/261/346/225/210/344/274/230/345/205/210/346/241/206/346/236/266_v0.3.md +365 -0
- package/docs//344/270/215/345/217/257/351/235/240/346/200/247/347/220/206/350/256/272_v0.1.md +343 -0
- package/dsh/cordis-patch-profile-web.example.yml +35 -0
- package/lib/bridge.d.ts +9 -0
- package/lib/bridge.js +35 -0
- package/lib/cli.d.ts +3 -0
- package/lib/cli.js +66 -0
- package/lib/index.js +7 -1
- package/lib/init.d.ts +88 -0
- package/lib/init.js +287 -0
- package/lib/lib/roleplay_web.js +530 -443
- package/lib/lib/token_store.d.ts +7 -1
- package/lib/lib/token_store.js +12 -3
- package/md_cg/audit.py +12 -1
- package/md_cg/backfill.py +16 -15
- package/md_cg/backfill_bucket_zh.py +35 -0
- package/md_cg/bench_e2e_judge.py +532 -0
- package/md_cg/bench_e2e_locomo_qa.py +368 -0
- package/md_cg/bench_e2e_qa.py +256 -0
- package/md_cg/branches.py +18 -2
- package/md_cg/ccgc.py +3 -2
- package/md_cg/chain.py +19 -4
- package/md_cg/consolidate.py +7 -6
- package/md_cg/crosscheck.py +4 -3
- package/md_cg/crypto.py +439 -437
- package/md_cg/datapath.py +395 -335
- package/md_cg/evidence.py +8 -3
- package/md_cg/export.py +3 -1
- package/md_cg/forgetting.py +2 -2
- package/md_cg/fsutil.py +48 -0
- package/md_cg/hotcache.py +255 -238
- package/md_cg/interop.py +161 -22
- package/md_cg/judgment_manifest.py +177 -0
- package/md_cg/links.py +655 -622
- package/md_cg/mcp_server.py +282 -31
- package/md_cg/mdcg.py +702 -126
- package/md_cg/mdcos.py +454 -68
- package/md_cg/mreview/govern.py +5 -4
- package/md_cg/postings.py +4 -2
- package/md_cg/readcache.py +76 -18
- package/md_cg/reconcile.py +228 -0
- package/md_cg/review_cli.py +215 -0
- package/md_cg/routing.py +28 -0
- package/md_cg/run_tests.py +211 -0
- package/md_cg/scrub.py +862 -852
- package/md_cg/security.py +385 -275
- package/md_cg/selfreport.py +3 -2
- package/md_cg/signer.py +565 -562
- package/md_cg/sources.py +3 -2
- package/md_cg/stg.py +6 -0
- package/md_cg/sustain.py +1168 -1138
- package/md_cg/test_access_hints.py +147 -0
- package/md_cg/test_branch_discard_tombstone.py +136 -0
- package/md_cg/test_branches.py +259 -249
- package/md_cg/test_chain_read_isolate.py +168 -0
- package/md_cg/test_datapath_device_name.py +203 -0
- package/md_cg/test_emit_negtail_cache.py +156 -0
- package/md_cg/test_en_pipeline.py +186 -166
- package/md_cg/test_govern_directread.py +421 -0
- package/md_cg/test_i32_hotcache_env_key.py +218 -0
- package/md_cg/test_identity_attribution.py +228 -147
- package/md_cg/test_index_crossprocess_reload.py +301 -0
- package/md_cg/test_index_durability.py +238 -224
- package/md_cg/test_interop.py +4 -2
- package/md_cg/test_interop_judgment.py +228 -0
- package/md_cg/test_issue39_utf8_stdio.py +273 -0
- package/md_cg/test_links_concurrent_write.py +188 -0
- package/md_cg/test_merge_upsert.py +168 -0
- package/md_cg/test_n123_derive_expiry_chain.py +205 -0
- package/md_cg/test_n130_verify_falsified_protect.py +185 -0
- package/md_cg/test_n131_merge_gate.py +205 -0
- package/md_cg/test_none_id_write_guard.py +165 -0
- package/md_cg/test_p1x_ref_root.py +160 -0
- package/md_cg/test_p27_docindex.py +774 -765
- package/md_cg/test_p2_mcp.py +3 -0
- package/md_cg/test_p32_backfill.py +304 -298
- package/md_cg/test_p39_verify_flow.py +90 -50
- package/md_cg/test_p47_session_view.py +60 -25
- package/md_cg/test_propose_tail_index.py +157 -0
- package/md_cg/test_read_scope_b27.py +277 -0
- package/md_cg/test_readcache_default_on.py +168 -0
- package/md_cg/test_readcache_precise_inval.py +270 -0
- package/md_cg/test_readcache_prodpath.py +55 -7
- package/md_cg/test_reconcile_v0.py +294 -0
- package/md_cg/test_retr_s1.py +6 -2
- package/md_cg/test_retr_s1b.py +67 -0
- package/md_cg/test_retr_s7.py +8 -0
- package/md_cg/test_retr_s9_entity_ctx.py +181 -175
- package/md_cg/test_retr_score_once.py +208 -0
- package/md_cg/test_review_cli_attribution.py +177 -0
- package/md_cg/test_review_cli_visibility.py +235 -0
- package/md_cg/test_review_onepass.py +170 -0
- package/md_cg/test_rrf_graph_seed_cache.py +154 -0
- package/md_cg/test_security_audit.py +155 -0
- package/md_cg/test_security_audit_b26.py +161 -0
- package/md_cg/test_security_audit_v21.py +250 -0
- package/md_cg/test_semantic_canonical.py +255 -241
- package/md_cg/test_session_isolation.py +168 -0
- package/md_cg/test_snapshot_autoclose.py +187 -0
- package/md_cg/test_tail_watermark_race.py +208 -0
- package/md_cg/test_tenant_env_override_warn.py +139 -0
- package/md_cg/test_tenant_registry_corrupt_warn.py +151 -0
- package/md_cg/test_v14_fixes.py +415 -397
- package/md_cg/test_verify_dirty_reconcile.py +157 -0
- package/md_cg/theory.py +276 -273
- package/md_cg/tokens.py +734 -677
- package/md_cg/units.py +3 -2
- package/md_cg/vision_evidence.py +4 -3
- package/md_cg/whitebox_kb/wisdom/code_solidified.json +6195 -6195
- package/md_cg/whitebox_kb/wisdom/multilang_ir.py +128 -124
- package/md_cg/writepipe.py +554 -550
- package/package.json +11 -2
- package/src/bridge.ts +434 -401
- package/src/cli.ts +65 -0
- package/src/index.ts +526 -518
- package/src/init.ts +347 -0
- package/src/lib/roleplay_web.ts +1019 -932
- package/src/lib/token_store.ts +202 -192
package/src/init.ts
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* init.ts —— 灵枢一键配置生成器(独立 CLI 入口:`lingshu-init`)
|
|
4
|
+
*
|
|
5
|
+
* 用户痛点:部署灵枢要手工做多步——选记忆根目录、手写各端 mcp.json 的
|
|
6
|
+
* env(MDCG_ROOT / MDCG_PYTHON 等)、理解令牌签发。本命令把「生成配置」
|
|
7
|
+
* 这一步自动化:三问(接入端 / 记忆根目录 / Python 解释器)后打印所选端
|
|
8
|
+
* 的 mcp.json 配置片段、把片段落盘成可复制的文件、并给出各端后续步骤
|
|
9
|
+
* (片段放哪 / 令牌签发命令——后者指向 README「写入凭据」节)。
|
|
10
|
+
*
|
|
11
|
+
* 边界(刻意设计,勿越界):
|
|
12
|
+
* · init **只生成配置**——不创建记忆目录、不启动 md_cg 服务、不写库;
|
|
13
|
+
* 重跑(同参数)输出逐字节一致(无时间戳等易变内容),可放心重复执行。
|
|
14
|
+
* · 与插件运行时的数据根缺省(`~/.dsh/.dsh-memory/data/mdcg`,见
|
|
15
|
+
* `src/lib/datapath.ts`)不同,本命令的缺省记忆根是 `~/.lingshu/memory`
|
|
16
|
+
* ——它会被**显式写进**片段的 MDCG_ROOT,两侧不靠默契、无歧义。
|
|
17
|
+
*
|
|
18
|
+
* env 片段口径与 `mdcgChildEnv()`(src/lib/mdcg_client.ts)对齐:
|
|
19
|
+
* PYTHONIOENCODING/PYTHONUTF8 两条编码注入是硬约束(中文记忆场景,
|
|
20
|
+
* 详见该函数头注的 2026-09-20 读线程崩溃现场);MDCG_MCP_SURFACE=kernel
|
|
21
|
+
* 与各端 mcp.json 样例(claude/lingshu-memory/mcp.json.example 等)同口径。
|
|
22
|
+
* MDCG_PYTHON 对直挂端是文档性键(解释器由 command 决定),对 DSH 插件端
|
|
23
|
+
* 真实生效(`defaultPython()` 读它,src/lib/python_path.ts:42)。
|
|
24
|
+
*
|
|
25
|
+
* 零运行时依赖:只用 Node 内置模块(node:readline/promises / node:fs /
|
|
26
|
+
* node:path)——与全仓「CLI 不加第三方依赖」纪律一致。
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { writeFileSync } from 'node:fs'
|
|
30
|
+
import { homedir } from 'node:os'
|
|
31
|
+
import { createInterface } from 'node:readline/promises'
|
|
32
|
+
import type { Readable, Writable } from 'node:stream'
|
|
33
|
+
import { join, resolve } from 'node:path'
|
|
34
|
+
import { pathToFileURL } from 'node:url'
|
|
35
|
+
import { repoRoot } from './lib/datapath.js'
|
|
36
|
+
import { defaultPython } from './lib/python_path.js'
|
|
37
|
+
|
|
38
|
+
/** 接入端:1=DSH 插件 2=Claude Code 3=Codex CLI 4=通用 MCP。 */
|
|
39
|
+
export type InitEnd = 'dsh' | 'claude' | 'codex' | 'generic'
|
|
40
|
+
|
|
41
|
+
interface EndSpec {
|
|
42
|
+
id: InitEnd
|
|
43
|
+
/** 问答菜单里的展示名。 */
|
|
44
|
+
label: string
|
|
45
|
+
/** MDCG_ACTOR(与既有各端样例/插件配置同口径;私有内容按 (tenant, actor) 派生 DEK)。 */
|
|
46
|
+
actor: string
|
|
47
|
+
/** 片段该放到哪(打印给用户)。 */
|
|
48
|
+
configTarget: string
|
|
49
|
+
/** 令牌签发指引里说明的角色语境(保持 README 口径,仅按端换 actor)。 */
|
|
50
|
+
tokenActor: string
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const ENDS: ReadonlyArray<EndSpec> = [
|
|
54
|
+
{
|
|
55
|
+
id: 'dsh',
|
|
56
|
+
label: 'DSH 插件',
|
|
57
|
+
actor: 'dsh-memory',
|
|
58
|
+
configTarget: 'DSH 无全局 mcp.json——推荐走插件形态(下方指引②);下方 JSON 为 MCP 直挂等价形态',
|
|
59
|
+
tokenActor: 'dsh-memory',
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: 'claude',
|
|
63
|
+
label: 'Claude Code',
|
|
64
|
+
actor: 'claude-code',
|
|
65
|
+
configTarget: '项目根 .mcp.json(把片段的 mcpServers 并入;或 ~/.claude.json 全局)',
|
|
66
|
+
tokenActor: 'claude-code',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: 'codex',
|
|
70
|
+
label: 'Codex CLI',
|
|
71
|
+
actor: 'codex',
|
|
72
|
+
configTarget: '~/.codex/config.toml 的 [mcp_servers.mdcg] 段(TOML 形态见 codex/config.toml.example:env 为 [mcp_servers.mdcg.env] 表)',
|
|
73
|
+
tokenActor: 'codex',
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: 'generic',
|
|
77
|
+
label: '通用 MCP 宿主',
|
|
78
|
+
actor: 'generic',
|
|
79
|
+
configTarget: '宿主的 MCP server 配置处(command/args/env 三元组,按宿主文档挂载 stdio server)',
|
|
80
|
+
tokenActor: 'generic',
|
|
81
|
+
},
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
/** --end 的宽松别名(含菜单数字)。未知值报错并列出允许集。 */
|
|
85
|
+
function matchEnd(value: string): EndSpec | null {
|
|
86
|
+
const v = value.trim().toLowerCase()
|
|
87
|
+
for (const e of ENDS) {
|
|
88
|
+
if (v === e.id || v === e.label) return e
|
|
89
|
+
}
|
|
90
|
+
const aliases: Record<string, InitEnd> = {
|
|
91
|
+
'1': 'dsh', dsh: 'dsh',
|
|
92
|
+
'2': 'claude', claude: 'claude', 'claude-code': 'claude', claudecode: 'claude',
|
|
93
|
+
'3': 'codex', codex: 'codex',
|
|
94
|
+
'4': 'generic', generic: 'generic', mcp: 'generic',
|
|
95
|
+
}
|
|
96
|
+
const id = aliases[v]
|
|
97
|
+
return id ? ENDS.find((e) => e.id === id) ?? null : null
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** 缺省记忆根:<home>/.lingshu/memory(home 可注入,供测试)。 */
|
|
101
|
+
export function defaultMemoryRoot(home: string = homedir()): string {
|
|
102
|
+
return join(home, '.lingshu', 'memory')
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** 生成的 mcp.json 片段(键序固定 → 序列化稳定 → 幂等)。 */
|
|
106
|
+
export function buildSnippet(end: EndSpec, root: string, python: string): {
|
|
107
|
+
mcpServers: { mdcg: { command: string, args: string[], env: Record<string, string> } }
|
|
108
|
+
} {
|
|
109
|
+
return {
|
|
110
|
+
mcpServers: {
|
|
111
|
+
mdcg: {
|
|
112
|
+
command: python,
|
|
113
|
+
args: ['-m', 'md_cg.mcp_server'],
|
|
114
|
+
env: {
|
|
115
|
+
// 随包 md_cg 的绝对路径(本包 files 自带 md_cg/):python -m 的模块
|
|
116
|
+
// 解析不依赖 cwd,靠 PYTHONPATH(口径同 src/lib/datapath.ts pythonPathValue,
|
|
117
|
+
// 此处为静态配置故取纯包根、不拼进程既有 PYTHONPATH)。
|
|
118
|
+
PYTHONPATH: repoRoot(),
|
|
119
|
+
PYTHONIOENCODING: 'utf-8',
|
|
120
|
+
PYTHONUTF8: '1',
|
|
121
|
+
MDCG_ROOT: root,
|
|
122
|
+
// 直挂端文档性键(解释器由 command 决定);DSH 插件端真实生效。
|
|
123
|
+
MDCG_PYTHON: python,
|
|
124
|
+
MDCG_MCP_SURFACE: 'kernel',
|
|
125
|
+
MDCG_ACTOR: end.actor,
|
|
126
|
+
MDCG_TENANT: 'default',
|
|
127
|
+
MDCG_CLEARANCE: 'private',
|
|
128
|
+
MDCG_TOKEN: '',
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** 令牌签发命令文案(与 README「写入凭据」节逐字同口径,仅按端换 actor)。 */
|
|
136
|
+
function tokenCommand(python: string, tokenActor: string, comment: string): string[] {
|
|
137
|
+
return [
|
|
138
|
+
`${comment}令牌签发(需 Python 环境 + 本包 md_cg;签发后把输出填进片段 MDCG_TOKEN,详见 README「写入凭据」节):`,
|
|
139
|
+
` ${python} -m md_cg.tokens issue --role designer --actor ${tokenActor} --clearance internal ^`,
|
|
140
|
+
` --ops-allow info,route,read,write,recent,goal,identity,whitebox,verify ^`,
|
|
141
|
+
` --layers-allow knowledge,contextual,structural,self,goals,unresolved,rejected`,
|
|
142
|
+
` (bash 把行尾 ^ 换成 \\;--role recorder 为最小权限版)`,
|
|
143
|
+
]
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** 各端后续步骤(片段放哪 / 纪律注入 / 自检)。 */
|
|
147
|
+
function nextSteps(end: EndSpec, python: string): string[] {
|
|
148
|
+
const selfCheck = `大脑自检:${python} -m md_cg.mcp_server 收到 initialize 应答即通(README「装后验证」)`
|
|
149
|
+
switch (end.id) {
|
|
150
|
+
case 'dsh':
|
|
151
|
+
return [
|
|
152
|
+
'② 推荐插件形态(README「快速开始」三步):git clone 本仓 → `dsh plugin --profile <name> add .` →',
|
|
153
|
+
' cordis.yml 启用 lingshu-memory;对应关系:config.python = 上面选的解释器,',
|
|
154
|
+
' config.mdcg.root = 上面选的记忆根(覆盖优先级 env MDCG_ROOT > paths.json > 本项,见 src/lib/datapath.ts)。',
|
|
155
|
+
`③ 纪律注入:~/.dsh/profiles/<name>/cordis.patch.yml 的 personaPrefix(样例 dsh/cordis.patch.yml)。`,
|
|
156
|
+
`④ ${selfCheck}`,
|
|
157
|
+
]
|
|
158
|
+
case 'claude':
|
|
159
|
+
return [
|
|
160
|
+
`② 纪律注入:项目根 CLAUDE.md(样例 claude/CLAUDE.md;插件形态可 /plugin marketplace add FuRongJun-1999/dsh-memory)。`,
|
|
161
|
+
`③ ${selfCheck}`,
|
|
162
|
+
]
|
|
163
|
+
case 'codex':
|
|
164
|
+
return [
|
|
165
|
+
`② 纪律注入:项目根 AGENTS.md(样例 codex/AGENTS.md)。`,
|
|
166
|
+
`③ ${selfCheck}`,
|
|
167
|
+
]
|
|
168
|
+
default:
|
|
169
|
+
return [
|
|
170
|
+
`② 纪律注入:按需把 docs/工作纪律 渲染产物放进系统提示(各端样例见 codebuddy/ zcode/ claude/ codex/)。`,
|
|
171
|
+
`③ ${selfCheck}`,
|
|
172
|
+
]
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** runInit 可注入的 IO(测试注入;缺省 = 真实 stdin/stdout/cwd/home/平台)。 */
|
|
177
|
+
export interface InitOptions {
|
|
178
|
+
/** 交互模式的 stdin(注入 PassThrough 即可 mock)。 */
|
|
179
|
+
input?: Readable
|
|
180
|
+
/** 输出收集处(缺省 process.stdout)。 */
|
|
181
|
+
output?: Writable
|
|
182
|
+
/** 片段文件落盘目录(缺省 process.cwd())。 */
|
|
183
|
+
cwd?: string
|
|
184
|
+
/** 缺省记忆根的 home(缺省 os.homedir())。 */
|
|
185
|
+
home?: string
|
|
186
|
+
/** 平台缺省解释器判定(缺省 process.platform)。 */
|
|
187
|
+
platform?: NodeJS.Platform
|
|
188
|
+
/** 覆盖 TTY 判定(缺省 process.stdin.isTTY):决定「无参数时」走交互还是
|
|
189
|
+
* 报错。测试注入 false 即可确定性触发非交互分支,不依赖测试宿主的 stdin
|
|
190
|
+
* 形态(管道/终端两态都会让测试结果漂移——显式注入消除这个自由度)。 */
|
|
191
|
+
tty?: boolean
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export interface InitResult {
|
|
195
|
+
end: InitEnd
|
|
196
|
+
endLabel: string
|
|
197
|
+
root: string
|
|
198
|
+
python: string
|
|
199
|
+
snippetPath: string
|
|
200
|
+
snippet: ReturnType<typeof buildSnippet>
|
|
201
|
+
/** 本次打印到 output 的完整文本(幂等断言用)。 */
|
|
202
|
+
output: string
|
|
203
|
+
/** 片段文件字节(= JSON.stringify(snippet, null, 2) + 换行)。 */
|
|
204
|
+
snippetFile: string
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const SNIPPET_FILENAME = 'lingshu-mcp-snippet.json'
|
|
208
|
+
|
|
209
|
+
class InitError extends Error {}
|
|
210
|
+
|
|
211
|
+
/** 从 argv 解析 --flag value / --flag=value(未知 flag 报错,防拼写静默走默认)。 */
|
|
212
|
+
function parseFlags(argv: string[]): { end?: string, root?: string, python?: string } {
|
|
213
|
+
const out: { end?: string, root?: string, python?: string } = {}
|
|
214
|
+
// npx 传参惯例:`npx <pkg> init -- --end x` 的 `--` 是「npx 自己的 flag 到此为止」,
|
|
215
|
+
// 不是本命令的参数——跳过一个前导 `--`(其后仍是本命令的 flag)。
|
|
216
|
+
const rest = argv[0] === '--' ? argv.slice(1) : argv
|
|
217
|
+
const known = new Set(['--end', '--root', '--python'])
|
|
218
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
219
|
+
const arg = rest[i] ?? ''
|
|
220
|
+
const eq = arg.indexOf('=')
|
|
221
|
+
const name = arg.startsWith('--') && eq > 0 ? arg.slice(0, eq) : arg
|
|
222
|
+
if (!known.has(name)) {
|
|
223
|
+
throw new InitError(`未知参数「${arg}」。支持:--end <dsh|claude|codex|generic> --root <dir> --python <interpreter>`)
|
|
224
|
+
}
|
|
225
|
+
const value = eq > 0 ? arg.slice(eq + 1) : rest[i + 1]
|
|
226
|
+
if (value === undefined || value === '' || (eq < 0 && String(value).startsWith('--'))) {
|
|
227
|
+
throw new InitError(`参数 ${name} 缺值。示例:${name} <值>`)
|
|
228
|
+
}
|
|
229
|
+
if (eq < 0) i += 1
|
|
230
|
+
out[name.slice(2) as 'end' | 'root' | 'python'] = String(value)
|
|
231
|
+
}
|
|
232
|
+
return out
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* 一键配置主流程:非交互(参数齐)跳过问答;无参数走交互三问。
|
|
237
|
+
* 只生成配置(打印 + 片段落盘),不创建目录、不启动服务、不写库。
|
|
238
|
+
*/
|
|
239
|
+
export async function runInit(argv: string[], opts: InitOptions = {}): Promise<InitResult> {
|
|
240
|
+
const out = opts.output ?? process.stdout
|
|
241
|
+
const cwd = opts.cwd ?? process.cwd()
|
|
242
|
+
let collected = ''
|
|
243
|
+
const say = (line = ''): void => {
|
|
244
|
+
collected += `${line}\n`
|
|
245
|
+
out.write(`${line}\n`)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const flags = parseFlags(argv)
|
|
249
|
+
let end: EndSpec | undefined = flags.end ? matchEnd(flags.end) ?? undefined : undefined
|
|
250
|
+
if (flags.end && !end) {
|
|
251
|
+
throw new InitError(`--end「${flags.end}」无法识别。允许:dsh | claude | codex | generic(或菜单号 1-4)`)
|
|
252
|
+
}
|
|
253
|
+
let root: string | undefined = flags.root
|
|
254
|
+
let python: string | undefined = flags.python
|
|
255
|
+
|
|
256
|
+
if (!end || !root || !python) {
|
|
257
|
+
// 交互模式。stdin 非 TTY(且未注入 input)时直接报错——宁缺毋滥,
|
|
258
|
+
// 不挂死等一行永远不会来的输入。
|
|
259
|
+
const interactive = opts.input !== undefined || (opts.tty ?? process.stdin.isTTY === true)
|
|
260
|
+
if (!interactive) {
|
|
261
|
+
const missing = [!end && '--end', !root && '--root', !python && '--python'].filter(Boolean)
|
|
262
|
+
throw new InitError(
|
|
263
|
+
`非交互环境(stdin 非 TTY)必须给齐参数:${missing.join(' ')}。`
|
|
264
|
+
+ '示例:lingshu-init --end claude --root ~/.lingshu/memory --python python3',
|
|
265
|
+
)
|
|
266
|
+
}
|
|
267
|
+
const rl = createInterface({ input: opts.input ?? process.stdin })
|
|
268
|
+
try {
|
|
269
|
+
const ask = async (prompt: string): Promise<string> => (await rl.question(prompt)).trim()
|
|
270
|
+
say('灵枢(Lingshu)一键配置——只生成配置:不创建记忆目录、不启动服务、不写库。')
|
|
271
|
+
if (!end) {
|
|
272
|
+
const picked = await ask('① 接入端 [1=DSH 插件 2=Claude Code 3=Codex CLI 4=通用 MCP](默认 1):')
|
|
273
|
+
const fallback = picked === '' ? '1' : picked
|
|
274
|
+
end = matchEnd(fallback) ?? undefined
|
|
275
|
+
if (!end) throw new InitError(`接入端「${fallback}」无法识别(允许 1-4)。`)
|
|
276
|
+
}
|
|
277
|
+
if (!root) {
|
|
278
|
+
const given = await ask(`② 记忆根目录 MDCG_ROOT(留空 = ${defaultMemoryRoot(opts.home)}):`)
|
|
279
|
+
root = given === '' ? defaultMemoryRoot(opts.home) : given
|
|
280
|
+
}
|
|
281
|
+
if (!python) {
|
|
282
|
+
const given = await ask(`③ Python 解释器(留空 = 按平台 ${defaultPython(opts.platform)}):`)
|
|
283
|
+
python = given === '' ? defaultPython(opts.platform) : given
|
|
284
|
+
}
|
|
285
|
+
} finally {
|
|
286
|
+
rl.close()
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const endSpec = end as EndSpec
|
|
291
|
+
const rootAbs = resolve(cwd, root as string)
|
|
292
|
+
const pythonName = (python as string).trim()
|
|
293
|
+
if (pythonName === '') throw new InitError('--python 不能为空串')
|
|
294
|
+
|
|
295
|
+
const snippet = buildSnippet(endSpec, rootAbs, pythonName)
|
|
296
|
+
const snippetJson = `${JSON.stringify(snippet, null, 2)}\n`
|
|
297
|
+
const snippetPath = join(cwd, SNIPPET_FILENAME)
|
|
298
|
+
writeFileSync(snippetPath, snippetJson, 'utf8')
|
|
299
|
+
|
|
300
|
+
say()
|
|
301
|
+
say(`灵枢(Lingshu)一键配置 · 端=${endSpec.label}`)
|
|
302
|
+
say(` 记忆根目录 MDCG_ROOT:${rootAbs}`)
|
|
303
|
+
say(` Python 解释器:${pythonName}`)
|
|
304
|
+
say()
|
|
305
|
+
say(`① 把下面的 mcp.json 片段放到:${endSpec.configTarget}`)
|
|
306
|
+
say(snippetJson.trimEnd())
|
|
307
|
+
say()
|
|
308
|
+
for (const line of nextSteps(endSpec, pythonName)) say(line)
|
|
309
|
+
for (const line of tokenCommand(pythonName, endSpec.tokenActor, '')) say(line)
|
|
310
|
+
say()
|
|
311
|
+
say(`片段已写入:${snippetPath}(mcpServers 键直接复制即可)`)
|
|
312
|
+
say('提示:PYTHONPATH 当前指向本命令所在包——若是 npx 临时包,建议换成 git clone 的常驻仓库绝对路径(免疫 npx 缓存清理)。')
|
|
313
|
+
say('init 只生成配置:未创建目录、未启动服务、未写库;重跑输出一致。')
|
|
314
|
+
|
|
315
|
+
return {
|
|
316
|
+
end: endSpec.id,
|
|
317
|
+
endLabel: endSpec.label,
|
|
318
|
+
root: rootAbs,
|
|
319
|
+
python: pythonName,
|
|
320
|
+
snippetPath,
|
|
321
|
+
snippet,
|
|
322
|
+
output: collected,
|
|
323
|
+
snippetFile: snippetJson,
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/** 直跑判定(node lib/init.js / npx 场景;import 作库时为 false)。 */
|
|
328
|
+
function isMainEntry(): boolean {
|
|
329
|
+
const entry = process.argv[1]
|
|
330
|
+
if (!entry) return false
|
|
331
|
+
try {
|
|
332
|
+
const entryUrl = pathToFileURL(resolve(entry)).href
|
|
333
|
+
return import.meta.url === entryUrl
|
|
334
|
+
|| (process.platform === 'win32' && import.meta.url.toLowerCase() === entryUrl.toLowerCase())
|
|
335
|
+
} catch {
|
|
336
|
+
return false
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (isMainEntry()) {
|
|
341
|
+
runInit(process.argv.slice(2))
|
|
342
|
+
.then(() => { process.exitCode = 0 })
|
|
343
|
+
.catch((e: unknown) => {
|
|
344
|
+
process.stderr.write(`lingshu-init:${e instanceof Error ? e.message : String(e)}\n`)
|
|
345
|
+
process.exitCode = 1
|
|
346
|
+
})
|
|
347
|
+
}
|