dsh-metaphysics 0.13.5 → 0.14.2

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/engine/ziwei.js CHANGED
@@ -745,10 +745,10 @@ export function enrichLiunian(chart) {
745
745
  // 流日命宫起子时 → 顺数至该时辰。四化一律取落点宫的原局宫干。
746
746
  // 这条链是紫微看「这个月/这一天/这个时辰」的标准入口,缺了它就只能看到流年。
747
747
 
748
- function asOfParts(v) {
748
+ export function asOfParts(v) {
749
749
  if (!v) return null;
750
750
  if (typeof v === 'object') return v;
751
- const m = String(v).match(/(\d{4})-(\d{1,2})-(\d{1,2})(?:[T ](\d{1,2}):(\d{1,2}))?/);
751
+ const m = String(v).match(/^(\d{4})-(\d{1,2})-(\d{1,2})(?:[T ](\d{1,2}):(\d{1,2}))?$/);
752
752
  if (!m) return null;
753
753
  return {
754
754
  year: Number(m[1]), month: Number(m[2]), day: Number(m[3]),
package/index.js CHANGED
@@ -9,14 +9,71 @@ export const inject = ['tools'];
9
9
  const TOOL_NAME = 'metaphysics_chart';
10
10
 
11
11
  const DESCRIPTION = [
12
- '排中国传统命理盘与塔罗:四柱八字(bazi)、紫微斗数(ziwei)、六爻纳甲(liuyao)、梅花易数(meihua)、大六壬(daliuren)或塔罗抽牌(tarot)。',
13
- '当用户明确要求“排盘 / 算八字 / 看四柱 / 排紫微盘 / 起卦 / 摇一卦 / 抽塔罗 / 看运势”时调用。',
14
- 'bazi 与 ziwei 需要公历出生时间;liuyao 可给定六爻值或随机起卦;meihua 可按时间或数字起卦;tarot 可指定张数与问题。',
12
+ '排中国传统命理盘与塔罗:四柱八字(bazi)、紫微斗数(ziwei)、六爻纳甲(liuyao)、梅花易数(meihua)、大六壬(daliuren)、奇门遁甲(qimen)、合盘(hepan)或塔罗抽牌(tarot)。',
13
+ '当用户明确要求“排盘 / 算八字 / 看四柱 / 排紫微盘 / 起卦 / 摇一卦 / 抽塔罗 / 看运势 / 合盘”时调用。',
14
+ 'bazi 与 ziwei 需要公历出生时间与性别;hepan 需要双方的时间与性别;liuyao 可给定六爻值或随机起卦;meihua 可按时间或数字起卦;tarot 可指定张数与问题。',
15
15
  '要给「结合所问之事的解读」时,把 withInterpret 设为 true,并尽量带上 question(所问之事)与 direction(询问方向)。',
16
+ '结果可复现:tarot / liuyao 传 seed,紫微 / 八字 / 塔罗传 asOf;输出摘要会写明晚子时与真太阳时实际采用的口径。',
16
17
  '结果用于传统文化与娱乐参考,不得用于医疗、投资、法律、生死等确定性断言。',
17
18
  ].join('');
18
19
 
19
20
  export function apply(ctx) {
21
+ const bad = (message) => {
22
+ const e = new Error(message);
23
+ e.code = 'E_INPUT';
24
+ return e;
25
+ };
26
+
27
+ /**
28
+ * 地址解析缓存:同一次会话里反复排盘(尤其是合盘两侧)不必重复请求。
29
+ * 只缓存联网结果;离线查表本身是常数级开销。
30
+ */
31
+ const GEO_TTL_MS = 6 * 60 * 60 * 1000;
32
+ const geoCache = new Map();
33
+ async function geocodeCached(address, opts) {
34
+ const key = `${String(address).trim()}|${opts.online ? 'online' : 'offline'}`;
35
+ const hit = geoCache.get(key);
36
+ const now = Date.now();
37
+ if (hit && now - hit.at < GEO_TTL_MS) return hit.value;
38
+ const value = await geocodeAddress(address, opts);
39
+ if (geoCache.size >= 200) geoCache.delete(geoCache.keys().next().value);
40
+ geoCache.set(key, { at: now, value });
41
+ return value;
42
+ }
43
+
44
+ /** 工具主体的实际流程:参数整形 → 地址解析(默认离线) → 排盘 */
45
+ async function run(args) {
46
+ if (!args || typeof args !== 'object' || Array.isArray(args)) throw bad('参数必须是对象');
47
+ if (args.geocode !== undefined && args.geocode !== 'offline' && args.geocode !== 'online') {
48
+ throw bad(`geocode 只能是 offline / online,收到「${args.geocode}」`);
49
+ }
50
+ const next = { ...args };
51
+ // 默认离线:内置表覆盖到地级市,足够真太阳时用;联网是把出生地原文发给第三方,必须显式开启
52
+ const wantsOnline = next.geocode === 'online';
53
+ const resolveGeo = async (person) => {
54
+ if (!wantsOnline) return; // 离线:引擎自己会查内置表
55
+ if (typeof person.address !== 'string' || !person.address.trim()) return;
56
+ if (typeof person.longitude === 'number') return;
57
+ try {
58
+ const geo = await geocodeCached(person.address, { online: true });
59
+ if (geo && typeof geo.lon === 'number') {
60
+ person.longitude = geo.lon;
61
+ person.geo = geo;
62
+ }
63
+ } catch (e) {
64
+ // 联网失败不影响排盘,退回内置表
65
+ }
66
+ };
67
+ if (next.system === 'bazi' || next.system === 'ziwei' || next.system === 'hepan') {
68
+ await resolveGeo(next);
69
+ }
70
+ if (next.system === 'hepan' && next.partner && typeof next.partner === 'object') {
71
+ next.partner = { ...next.partner }; // 不改调用方传进来的对象
72
+ await resolveGeo(next.partner);
73
+ }
74
+ return compute(next);
75
+ }
76
+
20
77
  const definition = {
21
78
  name: TOOL_NAME,
22
79
  description: DESCRIPTION,
@@ -25,8 +82,8 @@ export function apply(ctx) {
25
82
  properties: {
26
83
  system: {
27
84
  type: 'string',
28
- enum: ['bazi', 'ziwei', 'liuyao', 'tarot', 'meihua', 'daliuren'],
29
- description: 'bazi = 四柱八字;ziwei = 紫微斗数;liuyao = 六爻;tarot = 塔罗;meihua = 梅花易数;daliuren = 大六壬。',
85
+ enum: ['bazi', 'ziwei', 'liuyao', 'tarot', 'meihua', 'daliuren', 'hepan', 'qimen'],
86
+ description: 'bazi = 四柱八字;ziwei = 紫微斗数;liuyao = 六爻;tarot = 塔罗;meihua = 梅花易数;daliuren = 大六壬;hepan = 合盘(合婚),需同时给 partner;qimen = 奇门遁甲(时家转盘,拆补法)。',
30
87
  },
31
88
  datetime: {
32
89
  type: 'string',
@@ -36,12 +93,18 @@ export function apply(ctx) {
36
93
  gender: {
37
94
  type: 'string',
38
95
  enum: ['male', 'female'],
39
- description: '性别,决定大运与大限顺逆。bazi / ziwei 必填。',
96
+ description: '性别,决定大运与大限顺逆。bazi / ziwei / hepan 必填,不能用默认值代替。',
40
97
  },
41
98
  address: {
42
99
  type: 'string',
43
100
  description:
44
- '出生地地址,如「河北石家庄」「成都」「新疆喀什」。用于推算经度并按真太阳时排盘(bazi 与 ziwei 都适用);只写到省市即可,内置地级市坐标表。与 longitude 同时给出时以 longitude 为准。',
101
+ '出生地地址,如「河北石家庄」「成都」「新疆喀什」。用于推算经度并按真太阳时排盘(bazi / ziwei / hepan 都适用,合盘的 partner.address 同理);只写到省市即可,内置地级市坐标表。与 longitude 同时给出时以 longitude 为准。',
102
+ },
103
+ geocode: {
104
+ type: 'string',
105
+ enum: ['offline', 'online'],
106
+ description:
107
+ '地址解析方式,默认 offline:只用内置坐标表(最细到地级市,够真太阳时用)。online 会向 OpenStreetMap Nominatim 发一次查询,可精确到区县/路名;出生地原文会发给第三方,仅在用户明确要求精确到区县时开启。',
45
108
  },
46
109
  longitude: {
47
110
  type: 'number',
@@ -50,7 +113,8 @@ export function apply(ctx) {
50
113
  },
51
114
  lateZi: {
52
115
  type: 'boolean',
53
- description: '23:00 后是否算次日(晚子时换日),默认 true。',
116
+ description:
117
+ '23:00 后是否算次日(晚子时换日)。八字/大六壬默认 true(换日);奇门默认 false(归当日),也可用 lateZiHourMode 指定。输出摘要会写明实际采用的口径。',
54
118
  },
55
119
  trueSolar: {
56
120
  type: 'boolean',
@@ -77,7 +141,32 @@ export function apply(ctx) {
77
141
  },
78
142
  asOf: {
79
143
  type: 'string',
80
- description: '紫微流月/流日/流时的基准时刻,形如 2026-10-22T16:00;省略则按调用时的本机当前时刻。要复现某一时刻的推演结果时请显式给出。',
144
+ description: '基准时刻,形如 2026-10-22T16:00。紫微用它定流月/流日/流时;八字用它钉死「流年从哪年起排」;塔罗用它记录 castAt(抽牌时刻)。省略则按调用时的本机当前时刻——要复现某一次结果就显式给出。',
145
+ },
146
+ seed: {
147
+ type: 'number',
148
+ description: '随机种子,仅 tarot / liuyao 使用。给了同一个 seed,抽牌或摇卦的结果可完整复现(便于回放与对拍);不传则真随机。',
149
+ },
150
+ juMethod: {
151
+ type: 'string',
152
+ enum: ['chaibu', 'chaoshen', 'zhirun'],
153
+ description: '奇门取局流派:chaibu 拆补(默认)、chaoshen 超神接气、zhirun 置闰。仅 qimen 使用。',
154
+ },
155
+ lateZiHourMode: {
156
+ type: 'string',
157
+ enum: ['same-day', 'next-day'],
158
+ description: '奇门晚子时(23:00–24:00)归当日还是次日,默认 same-day。仅 qimen 使用。',
159
+ },
160
+ partner: {
161
+ type: 'object',
162
+ description: '合盘时的对方信息:datetime 与 gender 必填,address 可选(与主方一样按出生地修正真太阳时)。仅 hepan 使用。',
163
+ properties: {
164
+ datetime: { type: 'string', description: '对方公历本地时间,如 1998-05-20T10:30' },
165
+ gender: { type: 'string', enum: ['male', 'female'], description: '对方性别(决定对方大运/大限顺逆)' },
166
+ address: { type: 'string', description: '对方出生地(省/市即可)' },
167
+ trueSolar: { type: 'boolean', description: '对方是否启用真太阳时' },
168
+ },
169
+ required: ['datetime', 'gender'],
81
170
  },
82
171
  mode: {
83
172
  type: 'string',
@@ -104,25 +193,20 @@ export function apply(ctx) {
104
193
  render: (_args, value) => [{ type: 'text', text: renderText(value) }],
105
194
  },
106
195
  async execute(args) {
107
- if (!args || typeof args !== 'object') throw new Error('参数必须是对象');
108
- const next = { ...args };
109
- // 地址 -> 经度:先查内置表,地址更细或未命中时联网(Nominatim)
110
- if ((next.system === 'bazi' || next.system === 'ziwei')
111
- && typeof next.address === 'string' && next.address.trim()
112
- && typeof next.longitude !== 'number') {
113
- try {
114
- const geo = await geocodeAddress(next.address, { online: true });
115
- if (geo && typeof geo.lon === 'number') {
116
- next.longitude = geo.lon;
117
- next.geo = geo;
118
- }
119
- } catch (e) {
120
- // 联网失败不影响排盘,退回内置表
121
- }
196
+ try {
197
+ // 必须 await:直接 `return run(args)` 会让异步拒绝绕过下面的 catch
198
+ return await run(args);
199
+ } catch (e) {
200
+ // 统一错误面:带错误码的入参错误直接透传文案,内部异常也带上码,避免堆栈式消息直出给模型
201
+ const code = (e && e.code) || 'E_INTERNAL';
202
+ const message = (e && e.message) || String(e);
203
+ throw new Error(`[${code}] ${message}`);
122
204
  }
123
- return compute(next);
124
205
  },
125
206
  };
126
207
 
127
208
  ctx.effect(() => ctx.tools.register(definition));
128
209
  }
210
+
211
+
212
+
package/locale/en.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "meta": {
3
3
  "title": "Metaphysics Charts",
4
- "description": "Six systems in one plugin: BaZi (hidden stems, ten gods, 12 stages, 42 shensha, strength, pattern and useful-god, luck scoring), Zi Wei Dou Shu (49 stars with brightness, four 12-god cycles, 34 patterns, decade and annual/monthly/daily/hourly mutagen), LiuYao, MeiHua YiShu, Da Liu Ren and Tarot, plus an ask-driven interpretation layer. Computed locally and exposed as an agent tool with a Web panel."
4
+ "description": "Eight systems in one plugin: BaZi (hidden stems, ten gods, 12 stages, 42 shensha, strength, pattern and useful-god, luck scoring), Zi Wei Dou Shu (49 stars with brightness, four 12-god cycles, 34 patterns, decade and annual/monthly/daily/hourly mutagen), LiuYao, MeiHua YiShu, Da Liu Ren, Qi Men Dun Jia (three ju-selection schools), compatibility charts (BaZi and Zi Wei), and Tarot, plus an ask-driven interpretation layer. Computed locally with networking off by default, exposed as an agent tool with a Web panel."
5
5
  }
6
6
  }
package/locale/zh.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "meta": {
3
- "title": "命理排盘 · 八字紫微六爻梅花六壬塔罗",
4
- "description": "六套体系一站排盘:八字(藏干十神、十二长生、42 神煞、旺衰、格局用神、大运流年吉凶评分)、紫微斗数(49 星曜与主星庙旺、四层十二神、34 格局、大限、流年流月流日流时四化)、六爻纳甲、梅花易数、大六壬(天地盘/四课/三传)与塔罗;另有所问之事驱动的解析与详解。纯本地计算,作为 Agent 工具供对话调用,并提供可切换本命/大限/流年视图的 Web 面板。"
3
+ "title": "命理排盘 · 八字紫微六爻梅花六壬奇门合盘塔罗",
4
+ "description": "八套体系一站排盘:八字(藏干十神、十二长生、42 神煞、旺衰、格局用神、大运流年吉凶评分)、紫微斗数(49 星曜与主星庙旺、四层十二神、34 格局、大限、流年流月流日流时四化)、六爻纳甲、梅花易数、大六壬(天地盘/四课/三传)、奇门遁甲(拆补/超神接气/置闰三派取局)、合盘(八字与紫微两套)与塔罗;另有所问之事驱动的解析与详解。纯本地计算、默认不联网,作为 Agent 工具供对话调用,并提供可切换本命/大限/流年视图的 Web 面板。"
5
5
  }
6
6
  }
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "dsh-metaphysics",
3
- "version": "0.13.5",
3
+ "version": "0.14.2",
4
4
  "type": "module",
5
- "description": "命理排盘 for DeepSeek Harness: BaZi four pillars (42 shensha, pattern / useful-god / luck scoring), Zi Wei Dou Shu (34 patterns, 大限, 流年四化), LiuYao with real line rendering, MeiHua YiShu, Da Liu Ren (heaven-earth plate, four lessons, three transmissions) and Tarot, Ask(所问之事 + 询问方向)driven interpretation, plus a plain-language 解析与详解 module (five dimensions + hexagram/card sections) — as an agent tool plus a Web panel.",
5
+ "description": "命理排盘 for DeepSeek Harness: eight systems — BaZi four pillars (42 shensha, pattern / useful-god / luck scoring), Zi Wei Dou Shu (34 patterns, 大限, 流年/流月/流日/流时四化), LiuYao with real line rendering, MeiHua YiShu, Da Liu Ren, Qi Men Dun Jia (chaibu / chaoshen / zhirun), compatibility charts (BaZi + Zi Wei) and Tarot; Ask(所问之事 + 询问方向)driven interpretation, plus a plain-language 解析与详解 module. Reproducible via seed/asOf. All computed locally, networking off by default — as an agent tool plus a Web panel.",
6
6
  "keywords": [
7
7
  "dsh-plugin",
8
8
  "dsh-bundle",
@@ -42,12 +42,6 @@
42
42
  "node": ">=18",
43
43
  "dsh": ">=0.2.0"
44
44
  },
45
- "scripts": {
46
- "build": "node tools/build-client.mjs",
47
- "build:html": "node tools/build-html.mjs",
48
- "test": "node tools/test-all.mjs",
49
- "release": "node tools/release.mjs"
50
- },
51
45
  "exports": {
52
46
  ".": "./index.js",
53
47
  "./client": "./client.js",
@@ -75,5 +69,15 @@
75
69
  "immediately": true,
76
70
  "inject": []
77
71
  }
72
+ },
73
+ "scripts": {
74
+ "build": "node tools/build-client.mjs",
75
+ "build:html": "node tools/build-html.mjs",
76
+ "test": "node tools/test-all.mjs",
77
+ "test:ci": "node tools/test-all.mjs && node tools/check-assertions.mjs && node tools/verify-tests-can-fail.mjs && node tools/check-html-sinks.mjs && node tools/check-patterns.mjs",
78
+ "test:baseline": "node tools/check-assertions.mjs --update",
79
+ "check:html": "node tools/check-html-sinks.mjs",
80
+ "check:patterns": "node tools/check-patterns.mjs",
81
+ "release": "node tools/release.mjs"
78
82
  }
79
- }
83
+ }