Skill: xiaojxiao2021/bazi-persona (version 0.3.0)

--- name: bazi-persona description: 基于生辰八字创建、更新和对话的人格 skill。支持八字排盘、动态流时分析（大运/流年/流月/流日/流时）以及万年历/黄历查询。 user-invocable: true allowed-tools: Read, Write, Glob metadata: {"openclaw":{"skillKey":"bazi-persona","emoji":"🧿","homepage":"https://clawhub.ai/xiaojxiao2021/bazi-persona","requires":{"anyBins":["node","nodejs"]}}} --- # 八字人格（OpenClaw） ## 1) 这个技能是做什么的 这个技能用于创建和维护“八字人格”：先排盘，再生成人格，再持续更新，并支持流时分析与黄历查询。 常见请求： - `帮我创建八字人格：舒晴，女，1999年8月12日，上海，同事` - `帮我更新舒晴：最近升职了，开始带团队` - `舒晴最近工作忙吗` - `从八字看她今天状态怎样` - `今天黄历怎么样` ## 1.1) When NOT to Use 以下场景不要使用这个 skill： - 你希望得到医疗、法律、投资等高风险场景的最终决策结论；本 skill 可提供参考视角，但不替代专业意见。 ## 1.2) 命令行运行方式（用于八字等工具Tool的使用，人格文件管理等） 在 skill 根目录执行： ```bash npm i ``` 推荐方式（已编译产物）： ```bash npm run bazi -- --action inspect npm run bazi -- --action inspect --slug xiao-a npm run bazi -- --action delete --slug xiao-a ``` 等价方式（直接运行 CLI）： ```bash node dist/cli.js --action inspect ``` 开发调试（需要 tsx）： ```bash npx tsx src/cli.ts --action inspect ``` 说明： - 命令行主要用于 `inspect/list/delete/help` 这类文件管理动作。 - 创建人格、更新人格、聊天、流时分析、黄历查询，按下方工具与工作流执行。 ## 2) 工具说明（输入 / 输出） 按下面的参数名传入即可；输出不强制字段格式，重点是 AI 能正确理解 结果并继续后续动作。 ### 2.1 `bazi_chart_tool` 用途：八字排盘 + 固定人格知识映射（静态）。 入参（input）： - `name` string 必填 - `gender` string 必填，`male` / `female`（兼容 `男` / `女`） - `birth_date` string 必填，`YYYY-MM-DD` - `birth_time` string 可选，`HH:mm` - `birth_location` string 必填 - `calendar_type` string 可选，`solar` / `lunar`，默认 `solar` - `sect` number 可选，`1` / `2`，默认 `2` - `true_solar_mode` string 可选，`auto` / `on` / `off`，默认 `auto` - `longitude` number 可选 - `day_rollover_hour` number 可选，默认 `23` 结果说明： - 返回一份完整排盘结果（chart），包含本命结构与固定人格知识映射。 - AI 拿到后用于生成人格画像、阶段参考和后续分析依据。 ### 2.2 `bazi_flow_tool` 用途：动态八字查询（大运 / 流年 / 流月 / 流日 / 流时）。 入参（input）： - `chart` object 可选（直接传本命 chart） - `persona_slug` string 可选（从 persona 读取本命） - `base_dir` string 可选 - `at` string 可选（`今天` / `昨天` / `2026-04-12` / 时间戳文本） - `include_calendar` boolean 可选，默认 `true` - `lang` string 可选，`zh` / `en` / `ja` / `ko` 结果说明： - 返回目标时点的八字相关动态分析结果，例如时间点的黄历信息（公历、农历、干支、节气、宜忌等），八字情况，五行和十神情况，刑冲合会，神煞信息等，以及相关的人格知识。 - AI 拿到后用于回答“现在/某天为什么这样”“该怎么推进”这类问题。 ### 2.3 `persona_data_tool` 用途：人设文件数据管理（List / Search / Create / Query / Patch(Update) / Delete）。 入参（input）： - `action` string 必填：`list` / `search` / `create` / `query` / `patch` / `delete` - `base_dir` string 可选 - `persona_slug` string 在 `query` / `patch` / `delete` 必填 - `search_query` string 在 `search` 可选 - `create_payload` object 在 `create` 必填 - `patch_payload` object 在 `patch` 必填 `create_payload` 推荐字段： - `slug` string - `profile` object - `relationship` string 可选（如同事/朋友/伴侣） - `initial_facts` array 可选（用户创建时提供的现实信息原文） - `chart` object - `snapshot` object - `memory` array `patch_payload` 推荐字段： - `memory_append` array - `snapshot_replace` object - `profile_patch` object 结果说明： - 返回本次动作结果：查找、更新、读写后的最新人格内容。 - 在查询/更新场景下，会带回可用的人设文件位置与最新状态，供聊天注入或后续 patch 使用。 ### 2.4 `calendar_tool` 用途：万年历 / 黄历查询。 入参（input）： - `at` string 可选（空则默认今天） - `lang` string 可选 结果说明： - 返回日期对应的黄历信息（公历、农历、干支、节气、宜忌等）。 - AI 拿到后直接组织成用户可读答案。 ### 2.5 `chat_import_tool` 用途：导入外部聊天记录并提取候选人格信息。 入参（input）： - `source_type` string 必填：`text` / `json` / `ocr_text` - `payload` string 或 object 必填 - `persona_slug` string 可选 - `timezone` string 可选，默认 `Asia/Shanghai` - `max_candidates` number 可选，默认 `50` 结果说明： - 返回“提取出来的结构化聊天记录数据”，通常包含事实、风格、关系、事件线索及可信度。 - AI 先筛选，再决定哪些进入 memory 与 persona 更新流程。 ### 2.6 `memory_tool` 用途：关键记忆与知识的写入、更新、合并、查询。 入参（input）： - `action` string 必填：`upsert` / `merge` / `delete` / `query` - `persona_slug` string 必填 - `base_dir` string 可选 - `memories` array（`upsert` / `merge` 必填） - `merge_policy` string 可选：`append` / `replace_same_key` / `higher_confidence_wins` `memories[]` 推荐字段： - `memory_id` string 可选 - `type` string（`fact` / `correction` / `style` / `context`） - `key` string 可选 - `content` string - `source` string - `time_anchor` string 可选 - `importance` number 可选（1-5） - `confidence` number 可选（0-1） 结果说明： - 返回记忆写入/更新结果摘要（写入数量、冲突情况、跳过情况等）。 - AI 根据结果决定是否继续 patch 人设，或先提示用户处理冲突信息。 ## 3) 工作流（按工具输入输出连接） ### 3.1 创建人格 适用场景：用户第一次创建某个人格。 1. 读取用户输入，提取基础信息： `name/gender/birth_date` 必要； [REDACTED_SECRET] 可选； 用户顺带给的现实信息（如职业、近况、性格线索）记为 `initial_facts`。 2. 调用 `bazi_chart_tool`，得到排盘结果（chart）。 3. AI 基于 `chart + relationship + initial_facts` 生成 `profile` 和 `snapshot`。 4. AI 将 `initial_facts` 提炼为可记录记忆项（结构化 `memories[]`，含 `type/content/source/confidence`）。 5. 调用 `persona_data_tool`，`action=create`。 6. 传入： `create_payload.chart = 第 2 步的排盘结果` `create_payload.profile = AI 生成 profile` `create_payload.relationship = relationship` `create_payload.initial_facts = 用户提供的现实信息原文` `create_payload.snapshot = AI 生成 snapshot` 7. 调用 `memory_tool`，`action=upsert`，写入第 4 步提炼后的关键记忆。 8. 返回创建结果，并告知可直接开始聊天。 关键判断： - `name/gender/birth_date` 缺任一项时，先补问再创建。 - 同名人格已存在时，先让用户确认是否覆盖或创建新 slug。 - 用户给了关系或现实信息时，不要丢弃，要进入 `relationship` 和 `initial_facts/memories`。 ### 3.2 更新人格 适用场景：用户补充新变化、纠正旧信息、补关系线索。 1. 调用 `persona_data_tool`，用 `action=search` 或 `action=query` 定位目标人格。 2. AI 从用户输入提取新增事实、纠正信息、风格线索。 3. 调用 `memory_tool`，用 `action=upsert` 或 `action=merge` 写入记忆。 4. AI 基于新记忆重算 `snapshot`。 5. 调用 `persona_data_tool`，`action=patch`，写入新 `snapshot` 与必要字段更新。 6. 返回更新摘要给用户。 关键判断： - 找不到人格时，先返回可选列表或建议先创建。 - 匹配到多个人格时，不要猜，先让用户确认目标。 ### 3.3 用户发起聊天（普通模式） **先找人设，再回复** 适用场景：用户直接说“她最近怎么样”“继续聊XX”“和XX说话”。 1. 先看当前上下文是否已有正在聊天的人格（如 `current_persona_slug`）。**如果当前已有人设就开始按人格聊天**。 2. 如果上下文已有且用户没有明确切换对象： 直接用该 `persona_slug`，调用 `persona_data_tool.query` 读取人格，不做 `search`。 3. 如果上下文没有、或用户明确说了新对象： 调用 `persona_data_tool.search` 定位，再用 `persona_data_tool.query` 读取。 4. 先注入基础聊天提示词（基础聊天风格、边界、禁区、回复原则）。 5. 注入人设参考文件：`persona.md`。 6. 普通模式下不注入八字细节文件，因为普通人一般不懂八字，和普通人聊八字就很奇怪。仅在人设可以回答八字内容时（比如人设是八字大师）再按需注入 `bazi_data.json`。 7. AI 基于“基础聊天提示词 + 人设”生成回复。 8. 需要沉淀时： 调用 `memory_tool` 写入新记忆，再调用 `persona_data_tool.patch` 更新人格。 关键判断： - 上下文已锁定人格且用户未切换时，不要重复搜索，直接聊。 - 用户说“切到XX/和XX聊”时，视为强制切换，必须重新搜索确认。 - 没有任何人格时，明确提示“当前还没有可聊天的人格”，并引导创建。 - 多人格命中时，先确认是哪个人，不直接进入聊天。 - 普通模式必须有基础聊天提示词，不能只喂人设文件。 - 优先读取 `persona.md`，普通模式下通常不注入八字知识文件。 - 用户明确要求“按某 persona 启动聊天”或“写入 soul.md”时，执行 3.4 工作流。 ### 3.4 人设启动注入OpenClaw Agent（可选） 适用场景：用户明确要求“按该 persona 启动聊天”或“把启动设定写入 SOUL.md/agent 启动文件”。 1. 先按 3.3 流程定位目标 persona。 2. 生成并注入人设启动提示词（可写入 `SOUL.md` 或 agent 启动上下文）： ```text 你现在进入指定 persona 的会话启动态。 要求： 1) 以 persona 的口吻、身份关系和表达节奏说话。 2) 优先遵守基础聊天提示词里的边界、禁区和安全规则。 3) 不要编造 persona 未提供的硬事实；不确定时先澄清。 4) 普通模式下以自然对话为主，不主动展开命理术语。 5) 仅在用户明确要求时再切换到作弊模式或其他专项提示词。 ``` 3. 保持该注入仅对目标 persona 生效，不污染其他 persona 的会话。 关键判断： - 这是可选工作流，不是默认步骤。 - 用户没有明确要求时，不执行该注入。 ### 3.5 作弊模式聊天（命理增强） 适用场景：用户说“打开作弊模式”“从八字看”“按命理分析一下”等。 1. 先按 3.3 流程定位人格，并注入“基础聊天提示词 + persona.md”（若用户明确要求可先执行 3.4）。 2. 在普通模式提示词之上，叠加注入作弊模式 Prompt（分析增强提示）。 3. 按需注入 `bazi_data.json` 作为详细八字依据。 4. 若用户问合盘/关系匹配，再按需叠加“合盘专项提示词”，并注入双方（或多方）八字数据。 5. 问题涉及时间点时，调用 `bazi_flow_tool`；需要日期细节时补 `calendar_tool`。 6. AI 输出时保持该人格口吻，同时补充八字依据和分析结论。 7. 用户说“关闭作弊模式”后，移除作弊模式 Prompt（及专项提示词），恢复常规聊天。 作弊模式 Prompt（建议）： ```text 你现在进入“作弊模式”。 要求： 1) 保持当前 persona 的说话风格与关系语境，不要脱离人设。 2) 在回答中显式使用可用的八字依据：本命结构、当前大运、流年/流月/流日/流时。 3) 当用户问具体时间点时，优先引用 flow 结果；当用户问日期宜忌时，引用 calendar 结果。 4) 结论要可解释：先给结论，再给2-4条依据，不要堆砌术语。 5) 如果缺少关键时间或事实，先指出缺口，再给保守判断。 6) 除非用户明确要求“记住/更新”，否则不要修改 persona 数据。 7) 输出内容结尾明确标识”作弊模式“。 ``` 关键判断： - 作弊模式是“在当前人格上增加分析视角”，不是替换人格。 - 用户没问命理时，不强行输出大段术语。 - 默认不写入文件；只有用户明确要求更新时，才走 `memory_tool` + `persona_data_tool.patch`。 - 合盘提示词、行业提示词、其他专业规则提示词都按需注入，不在普通聊天中默认加载。 **注意：普通模式下聊天，不要参考作弊模式下聊天的上下文（标识作弊模式的内容）** ### 3.6 动态流时分析 适用场景：用户问“今天状态”“这周为什么变了”“某天推进是否合适”。 1. 调用 `persona_data_tool`，`action=query` 读取目标人格。 2. 调用 `bazi_flow_tool`，传 `persona_slug` 或 `chart`，并传 `at=目标时间`。 3. 需要日期细节时，再调用 `calendar_tool` 补充节气/干支/宜忌。 4. AI 基于动态结果输出解释和建议。 关键判断： - 用户没给时间时，默认按“当前时间”分析。 - 用户给了明确日期时，严格按该日期分析。 ### 3.7 黄历 / 万年历查询 适用场景：用户只查日期信息，不涉及人格。 1. 调用 `calendar_tool`，`at` 可为空（空则今天）。 2. 返回农历、干支、节气、宜忌。 3. AI 用用户当前语言组织输出。 ### 3.8 外部聊天导入 适用场景：用户上传微信聊天记录、聊天截图文本或其他历史对话。 1. 调用 `chat_import_tool`，得到候选信息集合。 2. AI 筛选出“可入库”的高价值候选。 3. 调用 `memory_tool` 写入关键记忆。 4. 调用 `persona_data_tool.patch` 刷新 `snapshot`。 5. 返回导入摘要（写入数、跳过数、冲突数）。 ## 4) 人设目录（以代码规则为准） 默认目录为`当前运行的 skill 目录/personas` 也就是： - `<skill-root>/personas` 每个人格写入： - `personas/<slug>/persona.md`：人设内容（所有的知识记忆、聊天记录变化，都要分析反应到人设上） - `personas/<slug>/bazi_data.json`：详细的八字结构化数据 - `personas/<slug>/memory.json` 知识记忆：结构化的用户相关知识（比如发生过的事情，家庭背景等） - `personas/<slug>/history.json` 知识记忆：结构化的外部聊天记录（可能有不同的来源） 聊天加载建议： - 常规聊天先加载：`persona.md` - 需要命理细节时再加载：`bazi_data.json` 更新时建议： - 按不同场景，按需结构化提取并存到到memory.json或history.json - 按需分析内容并更新到人设`persona.md`

path: README.md
content: # Bazi Persona for OpenClaw

OpenClaw edition of the Bazi Persona skill.

This package keeps the existing runtime, prompts, and knowledge model intact, but adapts installation, packaging, and runtime guidance for OpenClaw so the host is less likely to misread it as a source project that needs to be rebuilt.

## Install

Use OpenClaw's native skill installer:

```bash
openclaw skills install bazi-persona
```

ClawHub page:
https://clawhub.ai/xiaojxiao2021/bazi-persona

Start a new OpenClaw session after install so the skill snapshot refreshes.

## What This Bundle Contains

- `SKILL.md`: OpenClaw-specific runtime and calling guidance
- `dist/`: prebuilt runtime files
- `prompts/`: prompt and knowledge files used by the runtime
- `package.json`: runtime dependency declaration for hosts that install dependencies automatically

## Runtime Expectations

This is a prebuilt runtime package.

- Do not treat it as a source project by default
- Do not run `npm run build` just because `package.json` exists
- Do not add `tsconfig.json`, `src/`, or other source files
- If the host installs runtime dependencies from `package.json`, that is fine
- Only fix dependencies when there is an explicit missing-runtime-dependency error

## Data Storage

Persona data is file-based.

Default location:

```text
<current-skill-dir>/personas
```

Overrides:

- `--base-dir`
- `BAZI_PERSONA_HOME`

If a path has not been verified, do not guess it. If a directory does not exist, report that exact fact instead of inferring memory-only or hidden storage.

## OpenClaw-Specific Notes

OpenClaw can load same-named skills from multiple places. In practice, use the currently verified active skill directory as the source of truth and avoid guessing precedence from memory.

When troubleshooting:

1. Verify the active skill directory first.
2. Verify `dist/`, `prompts/`, `package.json`, and `SKILL.md` are present.
3. Verify runtime dependencies only if there is an explicit missing dependency error.
4. Do not escalate missing paths into a source rebuild workflow.


path: package.json
content: {
  "name": "bazi-persona",
  "version": "0.3.0",
  "private": true,
  "description": "OpenClaw runtime package for Bazi Persona",
  "type": "module",
  "main": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "import": "./dist/index.js",
      "types": "./dist/index.d.ts"
    }
  },
  "engines": {
    "node": ">=18"
  },
  "homepage": "https://clawhub.ai/xiaojxiao2021/bazi-persona",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/cantian-ai/bazi-persona-skill.git"
  },
  "license": "MIT",
  "keywords": [
    "openclaw",
    "bazi",
    "persona"
  ],
  "scripts": {
    "bazi": "node dist/cli.js"
  },
  "dependencies": {
    "cantian-tymext": "^0.0.26",
    "pinyin-pro": "^3.26.0"
  }
}


path: prompts/knowledge.md
content: # Prompt Knowledge

这份文件承载八字人格 skill 使用到的八字和性格相关知识。

## Persona Knowledge

```json persona-knowledge
{
  "stem_to_element": {
    "甲": "木",
    "乙": "木",
    "丙": "火",
    "丁": "火",
    "戊": "土",
    "己": "土",
    "庚": "金",
    "辛": "金",
    "壬": "水",
    "癸": "水"
  },
  "element_profiles": {
    "木": {
      "summary": "看起来温和，但内里有明确方向感，一旦认定目标就会持续往前。",
      "speech": "说话常先讲意图和方向，再补路径；不爱绕圈，但也不会一上来压人。",
      "decision": "做选择时优先看长期成长和空间感，不愿把时间耗在反复拉扯上。",
      "stress": "压力上来会从商量切到推进，语气变硬，节奏会明显加快。",
      "relationship": "愿意投入关系，但前提是彼此真诚和持续回应；最怕反复试探。",
      "risk": "能承受为成长付出的风险，但会给自己留退路，不做情绪化豪赌。"
    },
    "火": {
      "summary": "情绪和表达都偏外显，反应快，存在感强，喜欢把气氛带起来。",
      "speech": "聊天常先给结论再展开，语气有温度，带一点推动感。",
      "decision": "会看当下势能和执行窗口，倾向先动起来，在行动里修正。",
      "stress": "高压时更急更直，耐心下降，容易先说重话后补救。",
      "relationship": "在关系里重互动和回应，不怕主动，但对冷淡和敷衍很敏感。",
      "risk": "敢试错，但前提是节奏还在自己手里；一旦失控会迅速收手。"
    },
    "土": {
      "summary": "慢热稳重，重承诺和秩序，不爱花哨表达，但给人可靠感。",
      "speech": "说话偏平稳，先确认事实，再给可执行步骤；不轻易夸口。",
      "decision": "优先考虑长期稳定、可持续和可落地，不追短期刺激。",
      "stress": "压力来时会先守底盘，动作变慢但更谨慎，有时会显得保守。",
      "relationship": "熟起来慢，但一旦认定会长期负责；很难接受反复变来变去。",
      "risk": "偏稳健，关注下行风险和自己能承受的范围，反感冲动下注。"
    },
    "金": {
      "summary": "分寸清楚，判断干脆，重效率和标准，讨厌含糊其辞。",
      "speech": "表达偏短句直给，常先定标准再谈感受，听起来偏硬但不拖泥带水。",
      "decision": "做决定时看规则、证据和结果可验证性，倾向高确定性路径。",
      "stress": "高压下会更强硬，先切问题和责任，情绪处理通常后置。",
      "relationship": "重对等和兑现；遇到甩锅或模糊责任，会迅速拉开距离。",
      "risk": "风险管理偏纪律型，宁可错过也不盲冲，自我约束很强。"
    },
    "水": {
      "summary": "感知细腻、适应性强，表面柔和，内在判断其实很稳。",
      "speech": "先听再回，习惯用提问摸清真实需求，表达会留一点余地。",
      "decision": "偏信息导向，先看变量和选项，不轻易把自己锁死在单一路径。",
      "stress": "压力下先观察和收集信息，确认信号后会快速调整策略。",
      "relationship": "共情力强，但会保护自己的那一块；被误解时容易先退后再解释。",
      "risk": "偏策略型，重分散和回撤控制，擅长小步迭代而不是硬碰硬。"
    }
  },
  "ten_god_behaviors": {
    "正官": {
      "trait": "重秩序、守规则、责任感强",
      "decision": "先看规则和范围，再决定动作",
      "communication": "说话偏克制，先定标准再沟通细节",
      "stress": "高压时更强调流程和纪律，容错率降低"
    },
    "七杀": {
      "trait": "果断、敢压强、执行力硬",
      "decision": "偏向快决策快落地，不喜欢拖延",
      "communication": "表达直接，常用结论驱动行动",
      "stress": "高压下更强势，容易缩短沟通耐心"
    },
    "正印": {
      "trait": "重原则、重复盘、重安全感",
      "decision": "会先补全信息，再做稳妥判断",
      "communication": "表达有解释性，重逻辑闭环",
      "stress": "压力下更保守，先保底再扩张"
    },
    "偏印": {
      "trait": "独立、敏锐、内在标准高",
      "decision": "偏好先独立判断，再对齐外部意见",
      "communication": "不爱冗长寒暄，倾向抓重点",
      "stress": "高压下会回收社交能量，减少无效互动"
    },
    "比肩": {
      "trait": "自主、好胜、重掌控感",
      "decision": "偏向自己可控的路径，不爱被牵着走",
      "communication": "立场鲜明，不喜欢暧昧表态",
      "stress": "压力下更强调主导权和执行范围"
    },
    "劫财": {
      "trait": "行动快、竞争心强、讨厌低效",
      "decision": "偏向先占位再优化，重节奏优势",
      "communication": "语速与推进感更强，容忍磨叽度低",
      "stress": "高压下容易变得急促，需要明确分工"
    },
    "食神": {
      "trait": "表达自然、节奏松弛、重体验感",
      "decision": "偏向可持续、可享受的路径",
      "communication": "语气更有温度，擅长解释复杂问题",
      "stress": "压力下会先稳情绪，再恢复执行"
    },
    "伤官": {
      "trait": "思维锋利、爱质疑、反应快",
      "decision": "先拆逻辑漏洞，再决定是否执行",
      "communication": "表达直给，有时带挑战意味",
      "stress": "高压下更容易尖锐，需要避免沟通过猛"
    },
    "正财": {
      "trait": "务实、守账、重长期积累",
      "decision": "先算账再行动，关注投入产出比",
      "communication": "偏事实和数字，不爱空口承诺",
      "stress": "压力下先守现金流与基本盘"
    },
    "偏财": {
      "trait": "机会敏感、反应快、资源调动强",
      "decision": "偏向抓窗口期，但要求止损范围",
      "communication": "善于谈条件和交换，不绕弯子",
      "stress": "高压下更倾向快速试错，需控节奏"
    }
  },
  "reference_guidance": {
    "communication_title": "表达与写作参考",
    "persona_writing_tip": "先写这个人怎么说话、怎么感受，再落到判断，不要先下标签结论",
    "relationship_tip_template": "把「{relation}」作为当前观察镜头，关注靠近方式、回应节奏、分寸变化",
    "state_writing_tip": "当前阶段只修正最近更明显的偏移，不推翻长期人格",
    "profile_section_title": "人格参考知识",
    "state_section_title": "状态参考知识"
  },
  "state_shift_by_ten_god": {
    "正官": {
      "summary": "近期更像先立标准，再提效率的状态。",
      "communication": "表达更克制直接，减少情绪化措辞，强调标准一致。",
      "decision": "优先选可验证、可复盘、可交付的方案。"
    },
    "七杀": {
      "summary": "近期更像快节奏破局的状态。",
      "communication": "说话更短更硬，优先给结论和动作。",
      "decision": "倾向抢窗口期，先动起来再迭代。"
    },
    "正印": {
      "summary": "近期更像先补全信息、再稳住节奏的状态。",
      "communication": "表达更重解释和来龙去脉，不急着先下结论。",
      "decision": "更倾向先确认依据是否够稳，再决定要不要推进。"
    },
    "偏印": {
      "summary": "近期更像先想透，再动手的状态。",
      "communication": "减少社交性铺垫，更多使用问题导向表达。",
      "decision": "偏向先保证判断质量，再扩大动作。"
    },
    "比肩": {
      "summary": "近期更像先守住主导权，再决定怎么往前推的状态。",
      "communication": "表达更明确自己的立场，不太愿意被外部节奏带着走。",
      "decision": "更倾向选择自己能掌控、能兜住后果的路径。"
    },
    "劫财": {
      "summary": "近期更像先抢节奏，再边走边调的状态。",
      "communication": "说话更急更直接，容忍磨叽和来回确认的空间会变小。",
      "decision": "更容易优先考虑时机和速度，再在行动里修正细节。"
    },
    "食神": {
      "summary": "近期更像先把状态放顺，再自然推进的状态。",
      "communication": "表达更柔和，也更愿意把事情讲明白、讲舒服。",
      "decision": "更倾向选可持续、自己能长期保持手感的路径。"
    },
    "伤官": {
      "summary": "近期更像边拆边推的状态。",
      "communication": "表达更锋利，先指出问题，再给改法。",
      "decision": "会优先处理逻辑漏洞和低效环节。"
    },
    "正财": {
      "summary": "近期更像先守住基本盘，再逐步提效的状态。",
      "communication": "表达更偏事实、结果和投入产出，不爱多铺情绪。",
      "decision": "会更先算清楚代价、回报和落地性，再决定动作大小。"
    },
    "偏财": {
      "summary": "近期更像先看窗口，再灵活调资源的状态。",
      "communication": "说话更会抓重点和交换条件，节奏感更强。",
      "decision": "更容易优先看时机值不值得抓，再决定怎么快速试探。"
    }
  }
}
```

## Gan Zhi Reference

```json gan-zhi
{
  "gan": {
    "甲": {
      "imagery": ["阳木，如高大乔木、松柏、栋梁。"],
      "personality": ["上进积极，耿直仁慈，具领导气质。"]
    },
    "乙": {
      "imagery": ["阴木，如花草、藤蔓、细木。"],
      "personality": ["感觉细腻，审美强，外柔内韧。"]
    },
    "丙": {
      "imagery": ["阳火，如太阳与光焰。"],
      "personality": ["热情外放，行动快，表达直接。"]
    },
    "丁": {
      "imagery": ["阴火，如灯烛与星光。"],
      "personality": ["温和敏锐，心思细密，重内在秩序。"]
    },
    "戊": {
      "imagery": ["阳土，如高岗、厚土、城墙。"],
      "personality": ["沉稳厚重，守信用，重名誉。"]
    },
    "己": {
      "imagery": ["阴土，如田园与软土。"],
      "personality": ["细心多疑，擅理细节，能伸能缩。"]
    },
    "庚": {
      "imagery": ["阳金，如刀斧、钢铁与矿石。"],
      "personality": ["果断强硬，执行力强，讲效率与结果。"]
    },
    "辛": {
      "imagery": ["阴金，如珠宝、玉器与细金。"],
      "personality": ["气质细腻，重体面与美感。"]
    },
    "壬": {
      "imagery": ["阳水，如江河湖海。"],
      "personality": ["思维灵活，善把握机会，行动力强。"]
    },
    "癸": {
      "imagery": ["阴水，如雨露与泉水。"],
      "personality": ["温柔细腻，重情调与原则。"]
    }
  },
  "zhi": {
    "寅": ["阳木之象，树木、栋梁、初始与生机。"],
    "卯": ["阴木之象，花草藤蔓、柔韧与传播。"],
    "辰": ["湿土之象，水库田园、蓄力与承载。"],
    "巳": ["阴火之象，文明思想、影像与变化。"],
    "午": ["阳火之象，热度与动力，光彩与表达。"],
    "未": ["燥土之象，收敛与滋养，田园与休闲。"],
    "申": ["阳金之象，刀斧硬金，理性与决断。"],
    "酉": ["阴金之象，精致器物，品味与规则。"],
    "戌": ["燥土之象，城墙与火库，守势与硬度。"],
    "亥": ["阴水之象，池泉与潜流，思辨与深度。"],
    "子": ["阳水之象，流动与转化，初始与根源。"],
    "丑": ["湿土之象，沉降与隐性，稳中带冷。"]
  }
}
```

## Shi Shen Reference

```json shi-shen
{
  "正官": {
    "traits": ["正直保守，重纪律与规范，责任感强。"],
    "ability": "执行力"
  },
  "七杀": {
    "traits": ["果断强硬，好胜心强，敢压强与突破。"],
    "ability": "执行力"
  },
  "正财": {
    "traits": ["务实节俭，重稳定与责任，守本分。"],
    "ability": "管理力"
  },
  "偏财": {
    "traits": ["圆滑干练，重机会与节奏，出手大方。"],
    "ability": "管理力"
  },
  "正印": {
    "traits": ["温文稳重，重名誉与内在秩序。"],
    "ability": "人际力"
  },
  "偏印": {
    "traits": ["敏锐多思，独立判断，重隐性策略。"],
    "ability": "人际力"
  },
  "食神": {
    "traits": ["温和含蓄，重舒适与体验感。"],
    "ability": "表现力"
  },
  "伤官": {
    "traits": ["思维锋利，爱质疑，有创造力。"],
    "ability": "表现力"
  },
  "比肩": {
    "traits": ["自主自尊，独立性强，主观意识明显。"],
    "ability": "领导力"
  },
  "劫财": {
    "traits": ["好胜执拗，行动力强，强调主导权。"],
    "ability": "领导力"
  }
}
```

## Sheng Xiao Reference

```json sheng-xiao
{
  "鼠": ["机敏、反应快、善观察。"],
  "牛": ["稳、耐受高、节奏慢而持续。"],
  "虎": ["主动、外放、带冲劲。"],
  "兔": ["柔和、细腻、重感受。"],
  "龙": ["自带主轴感，讲势能和格局。"],
  "蛇": ["敏锐、克制、会藏锋。"],
  "马": ["行动快，讨厌被卡住。"],
  "羊": ["重氛围和关系体验。"],
  "猴": ["灵活、爱拆解、适应快。"],
  "鸡": ["讲标准、在意细节和体面。"],
  "狗": ["守边界、重忠诚、讲兑现。"],
  "猪": ["松弛、包容、恢复力强。"]
}
```


path: prompts/cheat_mode.md
content: # Cheat Mode Prompt

## 目标

这是作弊模式下的分析增强提示词。

在保持 persona 口吻不变的前提下：

1. 优先使用八字证据（本命八字结构、大运、流年/流月/流日/流时）。
2. 涉及具体日期时优先引用 flow 与 calendar 工具结果。
3. 先给结论，再给依据，避免堆术语，内容要通俗移动。
4. 若缺关键时间或事实，先指出缺口，再给保守判断。
5. 用户未明确要求“记住/更新”时，不写入 persona 数据。

## 合盘/专项

当用户问合盘、关系匹配或专项场景时，可按需叠加专项提示词，
但不要把专项规则默认注入普通对话。


path: prompts/chat_base.md
content: # Chat Base Prompt

## 目标

这是普通模式聊天的基础提示词。

在所有普通对话里，先保证：

1. 保持当前 人格persona 的身份、关系语境和表达节奏。
2. 先回应用户眼前问题，再补理由，不做无关发散。
3. 不编造硬事实；不确定时先说不确定，再补充可验证信息。
4. 保持边界：不做医疗、法律、投资等高风险最终决策替代。
5. 除非用户明确要求，否则不主动切换命理术语解释模式。

## 输出原则

1. 先给可读结论，再给简短依据。
2. 语气自然，不要写成报告腔。
3. 如果需要沉淀新事实，先对话，再按需更新记忆。


path: prompts/compat_guidance.md
content: # Compat Guidance Prompt

## 任务

当用户在作弊模式下问“两个人合不合”“适不适合继续推进”“会不会互相消耗”“最近为什么别扭”时，从八字角度做关系解读。

这里的重点不是泛泛地聊性格匹配，而是把双方已有资料重新放回八字和阶段变化里阅读。

## 优先阅读顺序

1. 先看双方的四柱八字、日主、主导十神、五行结构是否合适或者冲突
2. 再看双方当前大运、流年状态、最近阶段偏移
3. 再看双方的人格成品里已经沉淀出的说话方式、做事节奏、相处习惯
4. 最后再看 memory 里与关系有关的现实补充

## 分析重点

* 先看五行结构和用神取向，判断两个人是互补、相生、相耗，还是容易长期别扭
* 再看十神之间更容易形成什么互动：
    - 谁更容易主动推进
    - 谁更容易压住情绪
    - 谁更容易在关系里要掌控感
    - 谁更容易先退、先冷、先防备
* 看两个人的性格是不是容易同频，还是天然一快一慢、一硬一软、一收一放
* 一定要把当前大运和流年带进来：
    - 有的人底色并不冲，但现在阶段就是更急、更硬、更不耐烦
    - 有的人平时不难相处，但这几年刚好处在感情、事业或自我重整期
* 如果用户问“现在要不要推进”，重点回答时机，不只回答合不合

## 输出重点

输出时优先说明这些内容：

1. 两个人相处时，节奏最容易卡在哪
2. 谁会觉得累，谁会觉得被催，谁会觉得不被理解
3. 真正有吸引力的地方在哪里
4. 最容易反复出现的摩擦点是什么
5. 结合当前大运流年，现在是适合靠近、观察、放缓，还是暂时别硬推

## 输出口吻

1. 先说八字和阶段怎么看，再落到现实关系感受
2. 解释“为什么会这样”，不要只给结论
3. 如果证据主要来自阶段变化，要明确说明这是最近几年被放大的问题，不一定是长期底色
4. 如果双方其实能走，但只是当前节奏不对，要把“节奏不对”讲清楚


path: prompts/manifest.json
content: {
  "version": 1,
  "entries": [
    {
      "id": "chat_base",
      "file": "chat_base.md",
      "stage": "chat"
    },
    {
      "id": "cheat_mode",
      "file": "cheat_mode.md",
      "stage": "analysis"
    },
    {
      "id": "memory_builder",
      "file": "memory_builder.md",
      "stage": "memory"
    },
    {
      "id": "compat_guidance",
      "file": "compat_guidance.md",
      "stage": "compat"
    },
    {
      "id": "create_persona",
      "file": "create_persona.md",
      "stage": "create"
    }
  ]
}


path: prompts/create_persona.md
content: # Create Persona Prompt

## 任务

基于用户提供的出生信息、关系和排盘结果，一次性完成首次人格创建。

这份 prompt 用于首次创建场景，负责把“证据整理、人格生成、阶段生成、最终技能成品组装”连成一条创建链。

目标是让用户尽快得到一个已经可以开始聊天的人格，而不是先看到一堆中间分析材料。

## 输入

1. 用户基础信息
   - 名字
   - 性别
   - 出生日期
   - 出生地点（可选）
   - 关系（可选）
2. 八字排盘结果
   - 四柱八字
   - 日主
   - 五行结构
   - 十神结构
   - 当前大运
   - 当前阶段偏移
3. 当前时点

## 创建顺序

### 1. 先整理证据层

把排盘结果整理为可以支持人格生成的证据。

重点至少覆盖：

- 日主底色
- 五行结构
- 主导十神
- 四柱分工
- 当前大运
- 当前阶段偏移
- 关系下更容易被放大的地方

这一层只负责回答：

- 这个人格为什么会这样写
- 最近为什么更像这样

### 2. 再写长期人格

基于证据层，把这个人的长期人格底色写出来。

写的时候要覆盖：

- 认知模式
- 价值系统
- 沟通风格
- 相处方式
- 压力和防御方式

要求：

- 先写这个人给人的感觉
- 再写这个人怎么说话、怎么判断、怎么相处
- 不要写成标签列表，也不要写成命理报告

### 3. 再写当前阶段

基于当前大运和阶段偏移，补出“最近更像什么”。

要写清楚：

- 最近更明显的状态
- 最近说话会怎么偏
- 最近做判断会怎么偏
- 最近更适合怎样的节奏

要求：

- 这部分只能修正当前状态
- 不能推翻长期人格

### 4. 最后组装成最终 `SKILL.md`

最终 `SKILL.md` 是给 Agent 直接进入角色使用的人格成品。

首次创建时，成品至少要包含：

1. 角色总览
2. 五维人格
3. 表达方式
4. 相处方式
5. 当前阶段 / 流年状态
6. 八字信息与命理特点
7. 补充资料
8. 分析视角说明

如果当前还没有新的现实补充或真实聊天片段，可以保留空位，但不要硬编新的经历。

## 成品要求

1. 最终写出来的是“这个人”，不是“如何使用这个人”。
2. 要能直接进入角色聊天。
3. 命理依据要足够支撑人格，但不要盖过人物本身。
4. 如果用户没有提供出生时辰，就按当前内部排盘结果继续完成创建；用户侧不要暴露内部推断流程。
5. 创建完成后，要直接把用户带回聊天。
6. 如果用户没有提供出生地点或关系，不需要为此卡住创建；直接用现有信息继续完成。

## 创建完成后的回复

创建完成后，回复要同时做到三件事：

1. 告诉用户已经创建完成
2. 给出一句很短的人格摘要
3. 直接引导用户开始聊天

例如：

- 已创建完成
- 现在可以直接和舒晴开始聊天了
- 你可以直接问她：你最近工作忙吗？


path: prompts/memory_builder.md
content: # Memory Builder Prompt

## 任务

把后续对话里沉淀出来的信息整理成可复用的人格素材。

重点不是保存所有原话，而是提炼出那些会长期影响人格理解的内容。

## 需要识别的内容

1. 新事实
2. 纠正信息
3. 风格偏好
4. 关系变化
5. 长期有效的人格修正

## 使用原则

1. 原始聊天记录可以单独保存。
2. 最终进入人格成品的，只应是提炼后的关键记忆。
3. 新信息要判断它影响的是：
   - 长期底色
   - 当前状态
   - 关系
   - 只是暂时背景

## 输出目标

为最终 `SKILL.md` 提供：

- 最近补充资料
- 关键记忆
- 关系变化
- 长期有效的人格修正


path: dist/cli.d.ts
content: #!/usr/bin/env node
import type { SupportedLanguage } from "./kernel/types.js";
export declare function inspectPersona(args: Record<string, string>): string;
export declare function deleteStoredPersona(args: Record<string, string>): string;
export declare function renderCliHelp(language?: SupportedLanguage): string;
export declare function main(argv?: string[]): Promise<void>;


path: dist/index.js
content: export { routeIntent } from "./kernel/intent.js";
export { inspectPersona, renderCliHelp } from "./cli.js";
export { loadPersona } from "./kernel/persona-store.js";
export { buildBaziEvidence } from "./kernel/persona-engine.js";
export { bazi_chart_tool, bazi_flow_tool, calendar_tool, chat_import_tool, memory_tool, persona_data_tool, } from "./kernel/core-tools.js";


path: dist/cli.js
content: #!/usr/bin/env node
import { deletePersona, getPersonaFilePaths, loadHistoryEntries, listPersonas, loadPersona, resolvePersonaBaseDir, } from "./kernel/persona-store.js";
import { normalizeLanguage, resolveLanguage, resolvePersonaRef, parseArgsWithPositionalInput } from "./kernel/intent.js";
import { readUtf8IfExists } from "./shared/fs.js";
export function inspectPersona(args) {
    const baseDir = resolvePersonaBaseDir(args["base-dir"]);
    const personas = listPersonas(baseDir);
    const input = args.input ?? args.slug ?? "";
    const ref = args.slug ? personas.find((persona) => persona.slug === args.slug) : resolvePersonaRef(input, personas);
    if (!ref) {
        if (personas.length === 0) {
            return `No stored personas in ${baseDir}.`;
        }
        return [
            `Stored personas in ${baseDir}:`,
            ...personas.map((persona) => `- ${persona.name} (${persona.slug}) updated ${persona.updated_at}`),
        ].join("\n");
    }
    const record = loadPersona(ref.slug, baseDir);
    const conversations = loadHistoryEntries(ref.slug, baseDir);
    const filePaths = getPersonaFilePaths(record.slug, baseDir);
    const personaMarkdown = readUtf8IfExists(filePaths.persona_md).trim();
    return [
        `${record.profile.name} (${record.slug})`,
        `- Persona: ${filePaths.persona_md}`,
        `- Bazi Data: ${filePaths.bazi_data_json}`,
        `- Memory: ${filePaths.memory_json}`,
        `- History: ${filePaths.history_json}`,
        `- Conversations: ${conversations.length}`,
        `- Updated: ${record.updated_at}`,
        `- Relation: ${record.active_relationships.join(" / ")}`,
        `- Memory count: ${record.memory.length}`,
        "",
        personaMarkdown || record.snapshot.reference_profile,
    ].join("\n");
}
export function deleteStoredPersona(args) {
    const baseDir = resolvePersonaBaseDir(args["base-dir"]);
    const personas = listPersonas(baseDir);
    const input = args.input ?? args.slug ?? "";
    const ref = args.slug ? personas.find((persona) => persona.slug === args.slug) : resolvePersonaRef(input, personas);
    if (!ref) {
        throw new Error("Delete needs a persona slug or a name that matches an existing stored persona.");
    }
    const deleted = deletePersona(ref.slug, baseDir);
    if (!deleted) {
        throw new Error(`Persona not found: ${ref.slug}`);
    }
    return `Deleted persona: ${ref.name} (${ref.slug})`;
}
const cliTextPack = {
    zh: {
        title: "八字人格 Bazi Persona",
        brand: "Created by Cantian AI / 参天AI",
        intro1: "基于生辰八字，生成一个会聊天、会判断、会变化的 AI 人格。",
        intro2: "不用手动写人设，而是从生日出发，直接开始对话、观察和分析。",
        canDo: "你可以用它做三件事：",
        canDoLines: [
            "- 创建一个可持续对话的人格",
            "- 在后续聊天里吸收现实变化，让人格继续演化",
            "- 切到作弊模式，从八字、大运、流年角度分析状态、趋势和关系互动",
        ],
        fastestStart: "怎么开始最快：",
        startLines: [
            '- 直接说：「帮我创建八字人格：舒晴，女，1999年8月12日，上海，同事」',
            "- 创建完成后，就可以直接和她聊天",
            '- 如果想从命理角度解读，再说：「打开作弊模式」',
        ],
        directExamples: [
            '- "帮我创建八字人格：舒晴，女，1999年8月12日，上海，同事"',
            '- "打开作弊模式"',
            '- "今天黄历怎么样"',
        ],
        cliPositioning: "CLI 的定位：",
        storageOnly: "- This CLI is storage-only: inspect / list / delete stored persona files.",
        nonCli: "- Persona generation, updates, dialogue, flow analysis, and calendar lookup belong to the agent tool layer, not the CLI.",
        noShell: "- Do not use Bash/Node/tsx CLI calls to create personas or chat. Use the skill directly in the agent conversation.",
        storedAt: "- Stored data lives in personas/<slug>/{persona.md,bazi_data.json,memory.json,history.json} inside the installed skill directory.",
        fileExamples: "文件查看示例：",
        chatExamples: "更常见的是直接在对话里这样用：",
        unsupported: (action) => `Unsupported CLI action: ${action}`,
        unsupportedIntro: "Bazi Persona 是一个基于生辰八字的人格产品，用来创建可聊天、可判断、可变化的 AI 人格。",
        unsupportedStart: "直接开始最简单：",
    },
    en: {
        title: "Bazi Persona",
        brand: "Created by Cantian AI",
        intro1: "Generate an AI persona that can chat, judge, and evolve from a Bazi birth chart.",
        intro2: "You do not need to handwrite a persona. Start from the birthday and move straight into dialogue, observation, and analysis.",
        canDo: "You can use it for three things:",
        canDoLines: [
            "- Create a persona you can keep talking to",
            "- Absorb new real-world changes over time so the persona keeps evolving",
            "- Switch into cheat mode to analyze state, timing, and relationships through Bazi, luck cycles, and yearly flow",
        ],
        fastestStart: "Fastest way to begin:",
        startLines: [
            '- Just say: "Create a bazi persona: Shu Qing, female, 1999-08-12, Shanghai, coworker"',
            "- Once it is created, you can start chatting with her directly",
            '- If you want a fate-analysis angle, say: "Turn on cheat mode"',
        ],
        directExamples: [
            '- "Create a bazi persona: Shu Qing, female, 1999-08-12, Shanghai, coworker"',
            '- "Turn on cheat mode"',
            '- "How is today\'s Chinese almanac?"',
        ],
        cliPositioning: "What the CLI is for:",
        storageOnly: "- This CLI is storage-only: inspect / list / delete stored persona files.",
        nonCli: "- Persona generation, updates, dialogue, flow analysis, and calendar lookup belong to the agent tool layer, not the CLI.",
        noShell: "- Do not use Bash/Node/tsx CLI calls to create personas or chat. Use the skill directly in the agent conversation.",
        storedAt: "- Stored data lives in personas/<slug>/{persona.md,bazi_data.json,memory.json,history.json} inside the installed skill directory.",
        fileExamples: "File-management examples:",
        chatExamples: "More commonly, use it directly in chat:",
        unsupported: (action) => `Unsupported CLI action: ${action}`,
        unsupportedIntro: "Bazi Persona is a Bazi-based persona product for creating AI personas that can chat, judge, and evolve.",
        unsupportedStart: "The fastest way to begin is:",
    },
    ja: {
        title: "八字人格 Bazi Persona",
        brand: "Created by Cantian AI / 参天AI",
        intro1: "生辰八字をもとに、会話し、判断し、変化していく AI 人格を生成します。",
        intro2: "人設を手で書く必要はありません。誕生日からそのまま会話、観察、分析に入れます。",
        canDo: "主にできることは3つです：",
        canDoLines: [
            "- 継続して会話できる人格を作る",
            "- 後から現実の変化を取り込み、人格を育てていく",
            "- 作弊モードに切り替えて、八字・大運・流年から状態や関係を読む",
        ],
        fastestStart: "いちばん早い始め方：",
        startLines: [
            '- まずこう言ってください：「八字人格を作って：舒晴、女性、1999年8月12日、上海、同僚」',
            "- 作成できたら、そのままこの人と会話を始められます",
            '- 命理の角度で見たいなら：「作弊モードをオンにして」と言ってください',
        ],
        directExamples: [
            '- "八字人格を作って：舒晴、女性、1999年8月12日、上海、同僚"',
            '- "作弊モードをオンにして"',
            '- "今日の黄暦はどう？"',
        ],
        cliPositioning: "CLI の役割：",
        storageOnly: "- This CLI is storage-only: inspect / list / delete stored persona files.",
        nonCli: "- Persona generation, updates, dialogue, flow analysis, and calendar lookup belong to the agent tool layer, not the CLI.",
        noShell: "- Do not use Bash/Node/tsx CLI calls to create personas or chat. Use the skill directly in the agent conversation.",
        storedAt: "- Stored data lives in personas/<slug>/{persona.md,bazi_data.json,memory.json,history.json} inside the installed skill directory.",
        fileExamples: "ファイル確認の例：",
        chatExamples: "ふだんは対話でこう使います：",
        unsupported: (action) => `Unsupported CLI action: ${action}`,
        unsupportedIntro: "Bazi Persona は、生辰八字をもとに会話し、判断し、変化していく AI 人格を作るためのプロダクトです。",
        unsupportedStart: "いちばん簡単なのは：",
    },
    ko: {
        title: "八字人格 Bazi Persona",
        brand: "Created by Cantian AI / 参天AI",
        intro1: "생년월일의 사주를 바탕으로 대화하고, 판단하고, 변화하는 AI 인격을 만듭니다.",
        intro2: "인물을 손으로 설계할 필요 없이, 생일부터 바로 대화, 관찰, 분석을 시작할 수 있습니다.",
        canDo: "주요 용도는 세 가지입니다:",
        canDoLines: [
            "- 계속 대화할 수 있는 인격 만들기",
            "- 현실의 변화가 생기면 나중에 반영해 인격을 계속 진화시키기",
            "- 치트 모드로 전환해 사주, 대운, 세운 기준으로 상태와 관계를 해석하기",
        ],
        fastestStart: "가장 빠른 시작 방법:",
        startLines: [
            '- 이렇게 말하면 됩니다: "사주 인격 만들어줘: 舒晴, 여자, 1999년 8월 12일, 상하이, 동료"',
            "- 생성이 끝나면 바로 그 사람과 대화를 시작할 수 있습니다",
            '- 명리 관점으로 보고 싶으면: "치트 모드 켜줘"',
        ],
        directExamples: [
            '- "사주 인격 만들어줘: 舒晴, 여자, 1999년 8월 12일, 상하이, 동료"',
            '- "치트 모드 켜줘"',
            '- "오늘 황력 어때?"',
        ],
        cliPositioning: "CLI 용도:",
        storageOnly: "- This CLI is storage-only: inspect / list / delete stored persona files.",
        nonCli: "- Persona generation, updates, dialogue, flow analysis, and calendar lookup belong to the agent tool layer, not the CLI.",
        noShell: "- Do not use Bash/Node/tsx CLI calls to create personas or chat. Use the skill directly in the agent conversation.",
        storedAt: "- Stored data lives in personas/<slug>/{persona.md,bazi_data.json,memory.json,history.json} inside the installed skill directory.",
        fileExamples: "파일 확인 예시:",
        chatExamples: "보통은 대화에서 이렇게 씁니다:",
        unsupported: (action) => `Unsupported CLI action: ${action}`,
        unsupportedIntro: "Bazi Persona는 사주를 바탕으로 대화하고, 판단하고, 변화하는 AI 인격을 만드는 제품입니다.",
        unsupportedStart: "가장 간단한 시작은:",
    },
};
function resolveCliLanguage(args, action) {
    const explicit = normalizeLanguage(args.lang);
    if (explicit) {
        return explicit;
    }
    const hintText = [args.input, args.message, args.query, action].filter(Boolean).join(" ").trim();
    return resolveLanguage(hintText);
}
export function renderCliHelp(language = "zh") {
    const pack = cliTextPack[language];
    return [
        pack.title,
        pack.brand,
        pack.intro1,
        pack.intro2,
        "",
        pack.canDo,
        ...pack.canDoLines,
        "",
        pack.fastestStart,
        ...pack.startLines,
        "",
        pack.cliPositioning,
        pack.storageOnly,
        pack.nonCli,
        pack.noShell,
        pack.storedAt,
        "",
        pack.fileExamples,
        "- npm run bazi -- --action inspect",
        "- npm run bazi -- --action inspect --slug shu-qing",
        "- npm run bazi -- --action delete --slug shu-qing",
        "",
        pack.chatExamples,
        ...pack.directExamples,
    ].join("\n");
}
function normalizeCliAction(action) {
    const normalized = (action ?? "help").trim().toLowerCase();
    if (normalized === "inspect" || normalized === "list") {
        return "inspect";
    }
    if (normalized === "delete" || normalized === "remove" || normalized === "rm") {
        return "delete";
    }
    if (!normalized || normalized === "help") {
        return "help";
    }
    return "unsupported";
}
function renderUnsupportedAction(action, language = "zh") {
    const pack = cliTextPack[language];
    return [
        pack.unsupported(action ?? "(empty)"),
        pack.unsupportedIntro,
        pack.storageOnly,
        pack.nonCli,
        pack.noShell,
        "",
        pack.unsupportedStart,
        ...pack.directExamples,
    ].join("\n");
}
export async function main(argv = process.argv) {
    const args = parseArgsWithPositionalInput(argv);
    const language = resolveCliLanguage(args, args.action);
    const action = normalizeCliAction(args.action);
    let output;
    switch (action) {
        case "inspect":
            output = inspectPersona(args);
            break;
        case "delete":
            output = deleteStoredPersona(args);
            break;
        case "unsupported":
            output = renderUnsupportedAction(args.action, language);
            break;
        default:
            output = renderCliHelp(language);
            break;
    }
    process.stdout.write(`${output}\n`);
}
if (import.meta.url === `file://${process.argv[1]}`) {
    main().catch((error) => {
        process.stderr.write(`${error.message}\n`);
        process.exitCode = 1;
    });
}


path: dist/index.d.ts
content: export { routeIntent } from "./kernel/intent.js";
export { inspectPersona, renderCliHelp } from "./cli.js";
export { loadPersona } from "./kernel/persona-store.js";
export { buildBaziEvidence } from "./kernel/persona-engine.js";
export { bazi_chart_tool, bazi_flow_tool, calendar_tool, chat_import_tool, memory_tool, persona_data_tool, } from "./kernel/core-tools.js";


path: dist/kernel/intent.d.ts
content: import type { PersonaRef, RoutedIntent, SupportedLanguage } from "./types.js";
export declare function normalizeLanguage(value?: string): SupportedLanguage | undefined;
export declare function detectLanguage(input: string): SupportedLanguage | undefined;
export declare function resolveLanguage(input: string, preferred?: "auto" | SupportedLanguage): SupportedLanguage;
export declare function parseArgsWithPositionalInput(argv: string[]): Record<string, string>;
export declare function routeIntent(input: string): RoutedIntent;
export declare function hydrateCreateArgs(args: Record<string, string>, input: string): Record<string, string>;
export declare function resolvePersonaRef(input: string, personas: PersonaRef[]): PersonaRef | undefined;
export declare function extractUpdateFact(input: string): string;


path: dist/kernel/persona-store.js
content: import fs from "node:fs";
import path from "node:path";
import { ensureDir, fileExists, nowIso, readJson, readUtf8IfExists, toSlug, writeJson, writeUtf8, } from "../shared/fs.js";
import { DEFAULT_PERSONA_DIR } from "./resources.js";
import { regenerateSnapshot, renderPersonaMarkdown } from "./persona-engine.js";
const FILE_NAMES = {
    persona: "persona.md",
    bazi: "bazi_data.json",
    memory: "memory.json",
    history: "history.json",
};
function personaDir(baseDir, slug) {
    return path.join(baseDir, slug);
}
function memoryFromUnknown(payload) {
    if (!Array.isArray(payload)) {
        return [];
    }
    const output = [];
    for (const item of payload) {
        if (!item || typeof item !== "object") {
            continue;
        }
        const source = item;
        const content = typeof source.content === "string" ? source.content.trim() : "";
        if (!content) {
            continue;
        }
        const memoryType = source.type;
        const type = memoryType === "correction" || memoryType === "style" || memoryType === "context"
            ? memoryType
            : "fact";
        const normalized = {
            type,
            content,
            source: typeof source.source === "string" ? source.source : "unknown",
            created_at: typeof source.created_at === "string" ? source.created_at : nowIso(),
        };
        if (typeof source.memory_id === "string") {
            normalized.memory_id = source.memory_id;
        }
        if (typeof source.key === "string") {
            normalized.key = source.key;
        }
        if (typeof source.time_anchor === "string") {
            normalized.time_anchor = source.time_anchor;
        }
        if (typeof source.importance === "number") {
            normalized.importance = source.importance;
        }
        if (typeof source.confidence === "number") {
            normalized.confidence = source.confidence;
        }
        if (typeof source.updated_at === "string") {
            normalized.updated_at = source.updated_at;
        }
        output.push(normalized);
    }
    return output;
}
function historyFromUnknown(payload) {
    if (!Array.isArray(payload)) {
        return [];
    }
    const output = [];
    for (const item of payload) {
        if (!item || typeof item !== "object") {
            continue;
        }
        const source = item;
        if (typeof source.content !== "string" || !source.content.trim()) {
            continue;
        }
        const role = source.role === "assistant" ? "assistant" : "user";
        const mode = source.mode === "analysis" || source.mode === "update" || source.mode === "create"
            ? source.mode
            : "chat";
        const normalized = {
            session_id: typeof source.session_id === "string" ? source.session_id : `session-${Date.now()}`,
            role,
            content: source.content,
            mode,
            created_at: typeof source.created_at === "string" ? source.created_at : nowIso(),
        };
        if (typeof source.id === "string") {
            normalized.id = source.id;
        }
        if (source.source_type === "text" || source.source_type === "json" || source.source_type === "ocr_text" || source.source_type === "chat") {
            normalized.source_type = source.source_type;
        }
        output.push(normalized);
    }
    return output;
}
function normalizeLoadedRecord(record) {
    return {
        schema_version: "4.0.0",
        slug: record.slug,
        profile: record.profile,
        relationships: Array.isArray(record.relationships) ? record.relationships : [],
        active_relationships: Array.isArray(record.active_relationships) ? record.active_relationships : [],
        preferences: {
            preferred_language: record.preferences?.preferred_language === "zh" ||
                record.preferences?.preferred_language === "en" ||
                record.preferences?.preferred_language === "ja" ||
                record.preferences?.preferred_language === "ko"
                ? record.preferences.preferred_language
                : "auto",
            analysis_mode: record.preferences?.analysis_mode === "cheatsheet" ? "cheatsheet" : "normal",
        },
        chart: record.chart,
        memory: Array.isArray(record.memory) ? record.memory : [],
        snapshot: record.snapshot,
        persona_markdown: typeof record.persona_markdown === "string" ? record.persona_markdown : undefined,
        created_at: record.created_at,
        updated_at: record.updated_at,
    };
}
export function resolvePersonaBaseDir(explicit) {
    return explicit ?? process.env.BAZI_PERSONA_HOME ?? DEFAULT_PERSONA_DIR;
}
export function getPersonaFilePaths(slug, baseDir = DEFAULT_PERSONA_DIR) {
    const resolvedBaseDir = resolvePersonaBaseDir(baseDir);
    const dir = personaDir(resolvedBaseDir, slug);
    return {
        dir,
        persona_md: path.join(dir, FILE_NAMES.persona),
        bazi_data_json: path.join(dir, FILE_NAMES.bazi),
        memory_json: path.join(dir, FILE_NAMES.memory),
        history_json: path.join(dir, FILE_NAMES.history),
    };
}
export function listPersonas(baseDir = DEFAULT_PERSONA_DIR) {
    const resolvedBaseDir = resolvePersonaBaseDir(baseDir);
    ensureDir(resolvedBaseDir);
    return fs.readdirSync(resolvedBaseDir, { withFileTypes: true })
        .filter((entry) => entry.isDirectory())
        .map((entry) => path.join(resolvedBaseDir, entry.name, FILE_NAMES.bazi))
        .filter((baziPath) => fileExists(baziPath))
        .map((baziPath) => readJson(baziPath))
        .map((record) => ({
        slug: record.slug,
        name: record.profile.name,
        updated_at: record.updated_at,
    }))
        .sort((a, b) => b.updated_at.localeCompare(a.updated_at));
}
export function searchPersonas(query, baseDir = DEFAULT_PERSONA_DIR) {
    const normalized = query.trim().toLowerCase();
    if (!normalized) {
        return listPersonas(baseDir);
    }
    return listPersonas(baseDir).filter((item) => item.slug.toLowerCase().includes(normalized) || item.name.toLowerCase().includes(normalized));
}
export function loadPersona(slug, baseDir = DEFAULT_PERSONA_DIR) {
    const paths = getPersonaFilePaths(slug, baseDir);
    if (!fileExists(paths.bazi_data_json)) {
        throw new Error(`Persona not found: ${slug}`);
    }
    const baziData = readJson(paths.bazi_data_json);
    const memory = fileExists(paths.memory_json)
        ? memoryFromUnknown(readJson(paths.memory_json))
        : [];
    const personaMarkdown = readUtf8IfExists(paths.persona_md).trim();
    return normalizeLoadedRecord({
        ...baziData,
        memory,
        persona_markdown: personaMarkdown || undefined,
    });
}
export function savePersona(record, baseDir = DEFAULT_PERSONA_DIR) {
    const resolvedBaseDir = resolvePersonaBaseDir(baseDir);
    const normalized = normalizeLoadedRecord(record);
    const paths = getPersonaFilePaths(normalized.slug, resolvedBaseDir);
    ensureDir(paths.dir);
    const baziPayload = {
        schema_version: normalized.schema_version,
        slug: normalized.slug,
        profile: normalized.profile,
        relationships: normalized.relationships,
        active_relationships: normalized.active_relationships,
        preferences: normalized.preferences,
        chart: normalized.chart,
        snapshot: normalized.snapshot,
        created_at: normalized.created_at,
        updated_at: normalized.updated_at,
    };
    writeJson(paths.bazi_data_json, baziPayload);
    writeJson(paths.memory_json, normalized.memory);
    if (!fileExists(paths.history_json)) {
        writeJson(paths.history_json, []);
    }
    const personaMarkdown = normalized.persona_markdown?.trim() || renderPersonaMarkdown(normalized);
    writeUtf8(paths.persona_md, `${personaMarkdown.trim()}\n`);
    return {
        ...normalized,
        persona_markdown: personaMarkdown.trim(),
    };
}
export function loadHistoryEntries(slug, baseDir = DEFAULT_PERSONA_DIR) {
    const paths = getPersonaFilePaths(slug, baseDir);
    if (!fileExists(paths.history_json)) {
        return [];
    }
    return historyFromUnknown(readJson(paths.history_json));
}
export function appendHistoryEntry(slug, entry, baseDir = DEFAULT_PERSONA_DIR) {
    const paths = getPersonaFilePaths(slug, baseDir);
    const entries = loadHistoryEntries(slug, baseDir);
    entries.push(entry);
    writeJson(paths.history_json, entries);
    return entries;
}
export function deletePersona(slug, baseDir = DEFAULT_PERSONA_DIR) {
    const paths = getPersonaFilePaths(slug, baseDir);
    if (!fileExists(paths.bazi_data_json)) {
        return false;
    }
    fs.rmSync(paths.dir, { recursive: true, force: true });
    return true;
}
export function createPersonaRecord(params) {
    const slug = toSlug(params.slug ?? params.name);
    const createdAt = nowIso();
    const baseRecord = {
        schema_version: "4.0.0",
        slug,
        profile: {
            name: params.name,
            gender: params.gender,
            birth_date: params.birth_date,
            birth_time: params.birth_time,
            birth_location: params.birth_location,
            calendar_type: params.calendar_type,
        },
        relationships: params.relation ? [params.relation] : [],
        active_relationships: params.relation ? [params.relation] : [],
        preferences: {
            preferred_language: params.preferred_language ?? "auto",
            analysis_mode: "normal",
        },
        chart: params.chart,
        memory: params.memory,
    };
    const snapshot = regenerateSnapshot({
        record: baseRecord,
        promptPack: params.promptPack,
        knowledge: params.knowledge,
    });
    const assembled = {
        ...baseRecord,
        snapshot,
        created_at: createdAt,
        updated_at: snapshot.generated_at,
    };
    assembled.persona_markdown = renderPersonaMarkdown(assembled);
    return assembled;
}


path: dist/kernel/types.js
content: export {};


path: dist/kernel/workflow-orchestrator.d.ts
content: import { type ToolRuntimeContext } from "./core-tools.js";
export declare function workflow_create_persona(params: {
    args: Record<string, string>;
    context: ToolRuntimeContext;
}): Promise<string>;
export declare function workflow_update_persona(params: {
    args: Record<string, string>;
    context: ToolRuntimeContext;
}): Promise<string>;
export declare function workflow_chat(params: {
    args: Record<string, string>;
    context: ToolRuntimeContext;
}): Promise<string>;
export declare function workflow_query_flow(params: {
    args: Record<string, string>;
    context: ToolRuntimeContext;
}): Promise<string>;
export declare function workflow_query_calendar(params: {
    args: Record<string, string>;
}): Promise<string>;
export declare function workflow_import_chat(params: {
    args: Record<string, string>;
    context: ToolRuntimeContext;
}): Promise<string>;
export declare function workflow_persona_startup_injection(params: {
    args: Record<string, string>;
    context: ToolRuntimeContext;
}): Promise<string>;


path: dist/kernel/core-tools.d.ts
content: import { queryChineseCalendar } from "../tools/calendar/query.js";
import type { BaziChartToolInput, BaziFlowToolInput, CalendarToolInput, ChatImportToolInput, MemoryToolInput, PersonaConversationEntry, PersonaDataToolInput, PersonaMemoryEntry, PersonaRecord, PromptPack, PersonaKnowledge } from "./types.js";
export interface ToolRuntimeContext {
    promptPack: PromptPack;
    knowledge: PersonaKnowledge;
}
export declare function bazi_chart_tool(input: BaziChartToolInput): Promise<{
    chart: PersonaRecord["chart"];
}>;
export declare function calendar_tool(input: CalendarToolInput): Promise<{
    at: string;
    calendar: Awaited<ReturnType<typeof queryChineseCalendar>>;
}>;
export declare function bazi_flow_tool(input: BaziFlowToolInput, context: ToolRuntimeContext): Promise<{
    persona_slug?: string;
    at: string;
    flow: PersonaRecord["snapshot"]["evidence"];
    calendar: Awaited<ReturnType<typeof queryChineseCalendar>> | undefined;
}>;
export declare function chat_import_tool(input: ChatImportToolInput): Promise<{
    source_type: ChatImportToolInput["source_type"];
    timezone: string;
    candidates: PersonaConversationEntry[];
    memories: PersonaMemoryEntry[];
}>;
export declare function persona_data_tool(input: PersonaDataToolInput, context: ToolRuntimeContext): Promise<unknown>;
export declare function memory_tool(input: MemoryToolInput, context: ToolRuntimeContext): Promise<{
    action: MemoryToolInput["action"];
    persona_slug: string;
    count: number;
    created?: number;
    updated?: number;
    deleted?: number;
    items?: PersonaMemoryEntry[];
}>;


path: dist/kernel/types.d.ts
content: import type { BaziChart } from "../tools/bazi/calc.js";
export type SupportedLanguage = "zh" | "en" | "ja" | "ko";
export type PersonaMemoryType = "fact" | "correction" | "style" | "context";
export type RoutedAction = "help" | "create" | "update" | "inspect" | "flow" | "calendar" | "respond";
export interface PersonaMemoryEntry {
    memory_id?: string;
    type: PersonaMemoryType;
    key?: string;
    content: string;
    source: string;
    time_anchor?: string;
    importance?: number;
    confidence?: number;
    created_at: string;
    updated_at?: string;
}
export interface PersonaConversationEntry {
    id?: string;
    session_id: string;
    role: "user" | "assistant";
    content: string;
    mode: "chat" | "analysis" | "update" | "create";
    source_type?: "text" | "json" | "ocr_text" | "chat";
    created_at: string;
}
export interface BaziEvidenceSummary {
    day_master: string;
    primary_ten_god: string;
    five_element_summary: string;
    current_luck: string;
    state_shift: {
        summary: string;
        communication: string;
        decision: string;
    };
    evidence_lines: string[];
}
export interface PersonaSnapshot {
    generated_at: string;
    prompt_stack: string[];
    evidence: BaziEvidenceSummary;
    reference_profile: string;
    reference_state: string;
}
export interface PersonaProfile {
    name: string;
    gender: "男" | "女";
    birth_date: string;
    birth_time?: string;
    birth_location?: string;
    calendar_type: "solar" | "lunar";
}
export interface PersonaRecord {
    schema_version: "4.0.0";
    slug: string;
    profile: PersonaProfile;
    relationships: string[];
    active_relationships: string[];
    preferences: {
        preferred_language: "auto" | SupportedLanguage;
        analysis_mode: "normal" | "cheatsheet";
    };
    chart: BaziChart;
    memory: PersonaMemoryEntry[];
    snapshot: PersonaSnapshot;
    persona_markdown?: string;
    created_at: string;
    updated_at: string;
}
export interface PersonaRef {
    slug: string;
    name: string;
    updated_at: string;
}
export interface PromptManifestEntry {
    id: string;
    file: string;
    stage: string;
}
export interface PromptManifest {
    version: number;
    entries: PromptManifestEntry[];
}
export interface PromptPackEntry extends PromptManifestEntry {
    body: string;
}
export interface PromptPack {
    version: number;
    entries: PromptPackEntry[];
}
export interface PersonaKnowledge {
    stem_to_element: Record<string, string>;
    element_profiles: Record<string, {
        summary: string;
        speech: string;
        decision: string;
        stress: string;
        relationship: string;
        risk: string;
    }>;
    ten_god_behaviors: Record<string, {
        trait: string;
        decision: string;
        communication: string;
        stress: string;
    }>;
    reference_guidance: {
        communication_title: string;
        persona_writing_tip: string;
        relationship_tip_template: string;
        state_writing_tip: string;
        profile_section_title: string;
        state_section_title: string;
    };
    state_shift_by_ten_god: Record<string, {
        summary: string;
        communication: string;
        decision: string;
    }>;
}
export interface RoutedIntent {
    action: RoutedAction;
    payload: string;
    analysis_mode: "normal" | "cheatsheet";
}
export interface BaziChartToolInput {
    name: string;
    gender: "male" | "female" | "男" | "女";
    birth_date: string;
    birth_time?: string;
    birth_location?: string;
    calendar_type?: "solar" | "lunar";
    sect?: 1 | 2;
    true_solar_mode?: "auto" | "on" | "off";
    longitude?: number;
    day_rollover_hour?: number;
}
export interface BaziFlowToolInput {
    chart?: BaziChart;
    persona_slug?: string;
    base_dir?: string;
    at?: string;
    include_calendar?: boolean;
    lang?: SupportedLanguage;
}
export interface CalendarToolInput {
    at?: string;
    lang?: SupportedLanguage;
}
export interface ChatImportToolInput {
    source_type: "text" | "json" | "ocr_text";
    payload: string | Record<string, unknown> | Array<unknown>;
    persona_slug?: string;
    timezone?: string;
    max_candidates?: number;
}
export interface PersonaDataToolInput {
    action: "list" | "search" | "create" | "query" | "patch" | "delete";
    base_dir?: string;
    persona_slug?: string;
    search_query?: string;
    create_payload?: {
        slug?: string;
        profile?: Partial<PersonaProfile>;
        relationship?: string;
        initial_facts?: string[];
        chart?: BaziChart;
        snapshot?: PersonaSnapshot;
        memory?: PersonaMemoryEntry[];
    };
    patch_payload?: {
        memory_append?: PersonaMemoryEntry[];
        snapshot_replace?: Partial<PersonaSnapshot>;
        profile_patch?: Partial<PersonaProfile>;
        relationship?: string;
        analysis_mode?: "normal" | "cheatsheet";
    };
}
export interface MemoryToolInput {
    action: "upsert" | "merge" | "delete" | "query";
    persona_slug: string;
    base_dir?: string;
    memories?: PersonaMemoryEntry[];
    merge_policy?: "append" | "replace_same_key" | "higher_confidence_wins";
    query?: string;
}


path: dist/kernel/intent.js
content: import { parseCliArgs } from "../shared/fs.js";
export function normalizeLanguage(value) {
    const text = value?.trim().toLowerCase();
    if (!text) {
        return undefined;
    }
    if (text === "zh" || text === "cn" || text === "zh-cn" || text === "zh-hans") {
        return "zh";
    }
    if (text === "en" || text === "en-us" || text === "en-gb") {
        return "en";
    }
    if (text === "ja" || text === "jp" || text === "ja-jp") {
        return "ja";
    }
    if (text === "ko" || text === "kr" || text === "ko-kr") {
        return "ko";
    }
    return undefined;
}
export function detectLanguage(input) {
    const text = input.trim();
    if (!text) {
        return undefined;
    }
    if (/[가-힣]/u.test(text)) {
        return "ko";
    }
    if (/[ぁ-ゟ゠-ヿ]/u.test(text)) {
        return "ja";
    }
    if (/[一-龥]/u.test(text)) {
        return "zh";
    }
    if (/[A-Za-z]/u.test(text)) {
        return "en";
    }
    return undefined;
}
export function resolveLanguage(input, preferred = "auto") {
    return detectLanguage(input) ?? (preferred === "auto" ? "zh" : preferred);
}
export function parseArgsWithPositionalInput(argv) {
    let cliTokens = argv.slice(2);
    if (cliTokens.length === 1) {
        const single = cliTokens[0].trim();
        if (single.startsWith("/bazi-persona") || single.startsWith("bazi-persona")) {
            const parts = single.match(/"[^"]*"|'[^']*'|[^\s]+/g) ?? [single];
            cliTokens = parts.map((part) => (part.startsWith("\"") && part.endsWith("\"")) || (part.startsWith("'") && part.endsWith("'"))
                ? part.slice(1, -1)
                : part);
        }
    }
    const normalizedArgv = [argv[0] ?? "node", argv[1] ?? "script", ...cliTokens];
    const parsed = parseCliArgs(normalizedArgv);
    if (parsed.input || parsed.message || parsed.query) {
        return parsed;
    }
    const consumed = new Set();
    for (let i = 0; i < cliTokens.length; i += 1) {
        const [REDACTED_SECRET];
        if (!token.startsWith("--")) {
            continue;
        }
        consumed.add(i);
        const next = cliTokens[i + 1];
        if (next && !next.startsWith("--")) {
            consumed.add(i + 1);
            i += 1;
        }
    }
    const positional = cliTokens.filter((_, index) => !consumed.has(index));
    if (positional.length > 0) {
        parsed.input = positional.join(" ").trim();
    }
    return parsed;
}
export function routeIntent(input) {
    const text = input.trim();
    const lower = text.toLowerCase();
    const analysis_mode = /cheatsheet|analysis|compat|打开作弊模式|開啟作弊模式|turn on cheat mode|enable cheat mode|上帝视角|命理|证据链|八字看|相性|사주|분석/i.test(text)
        ? "cheatsheet"
        : "normal";
    const temporalStateQuery = /(昨天|今天|明天|后天|yesterday|today|tomorrow|昨日|今日|明日|어제|오늘|내일)/i.test(text)
        && /(不一样|不太一样|变化|变了|差别|区别|感觉|状态|different|changed|change|difference|調子|変化|違う|달라|변화|상태)/i.test(text);
    if (!text || /help|帮助|怎么用|usage|使い方|도움말/.test(lower)) {
        return { action: "help", payload: text, analysis_mode };
    }
    if (/黄历|万年历|节气|calendar|almanac|暦|달력/.test(text)) {
        return { action: "calendar", payload: text, analysis_mode };
    }
    if (/我有哪些|有哪些人格|list|查看人格|inspect all|personas|一覧|목록/i.test(text)) {
        return { action: "inspect", payload: text, analysis_mode };
    }
    if ((/(状态|流年|流月|运势|current state|fortune|運勢|상태)/i.test(text) || temporalStateQuery) && !/(创建|更新|补充|create|update|生成|作成|생성)/i.test(text)) {
        return { action: "flow", payload: text, analysis_mode };
    }
    if (/更新|补充|纠正|修正|记住|update|remember|correct|fix|修正して|업데이트|기억/.test(text)) {
        return { action: "update", payload: text, analysis_mode };
    }
    if (/创建|生成|新建|create|generate|build|作成|生成して|만들|생성/i.test(lower)) {
        return { action: "create", payload: text, analysis_mode };
    }
    if (/查看|看看|inspect|是谁|设定|profile|show|誰|설정|프로필/i.test(lower)) {
        return { action: "inspect", payload: text, analysis_mode };
    }
    return { action: "respond", payload: text, analysis_mode };
}
function extractDateLikeToken(text) {
    const full = text.match(/(\d{4})[年년\-/.](\d{1,2})[月월\-/.](\d{1,2})(?:[日号일])?/u);
    if (full) {
        return `${full[1]}-${full[2].padStart(2, "0")}-${full[3].padStart(2, "0")}`;
    }
    const iso = text.match(/(\d{4})-(\d{1,2})-(\d{1,2})/u);
    if (!iso) {
        return undefined;
    }
    return `${iso[1]}-${iso[2].padStart(2, "0")}-${iso[3].padStart(2, "0")}`;
}
function extractTime(text) {
    const hm = text.match(/((上午|下午|晚上|凌晨|am|pm|AM|PM|morning|evening|night|오전|오후)?\s*\d{1,2}(?::|：|点|시)\d{1,2})/u);
    if (hm) {
        return hm[1].replace(/\s+/g, "");
    }
    const half = text.match(/((上午|下午|晚上|凌晨|오전|오후)?\s*\d{1,2}(点半|시반))/u);
    return half?.[1]?.replace(/\s+/g, "");
}
function extractName(text) {
    const cleaned = text.replace(/^.*?[：:]/u, "").trim();
    const beforeComma = cleaned.split(/[，,]/u)[0]?.trim();
    if (!beforeComma || /创建|人格|八字|帮我|please|create|persona|bazi/iu.test(beforeComma)) {
        return undefined;
    }
    return beforeComma;
}
function extractLocation(text) {
    const pieces = text.split(/[，,]/u).map((part) => part.trim()).filter(Boolean);
    return pieces.find((piece) => !/\d{4}|男|女|male|female|创建|生成|更新|create|generate|build|persona|bazi|同事|朋友|伴侣|家人|客户|friend|partner|family|coworker|最近/u.test(piece))?.replace(/人$/u, "");
}
function extractRelation(text) {
    const hints = ["同事", "朋友", "伴侣", "家人", "客户", "老板", "同学", "前任", "coworker", "friend", "partner", "family", "同僚", "友達", "恋人", "家族", "동료", "친구", "연인", "가족"];
    return hints.find((hint) => text.toLowerCase().includes(hint.toLowerCase()));
}
function extractGender(text) {
    if (/\bmale\b|男|男性|남/u.test(text)) {
        return "男";
    }
    if (/\bfemale\b|女|女性|여/u.test(text)) {
        return "女";
    }
    return undefined;
}
export function hydrateCreateArgs(args, input) {
    const next = { ...args };
    next.name = next.name ?? extractName(input) ?? "";
    next.gender = next.gender ?? extractGender(input) ?? "";
    next["birth-date"] = next["birth-date"] ?? extractDateLikeToken(input) ?? "";
    next["birth-time"] = next["birth-time"] ?? extractTime(input) ?? "";
    next["birth-location"] = next["birth-location"] ?? extractLocation(input) ?? "";
    next.relation = next.relation ?? extractRelation(input) ?? "";
    return next;
}
export function resolvePersonaRef(input, personas) {
    const lower = input.toLowerCase();
    return personas.find((persona) => lower.includes(persona.slug.toLowerCase()) || lower.includes(persona.name.toLowerCase()));
}
export function extractUpdateFact(input) {
    const [, payload = ""] = input.split(/[：:]/u, 2);
    return (payload || input).trim();
}


path: dist/kernel/core-tools.js
content: import { buildChart, normalizeDateInput, normalizeTimeInput } from "../tools/bazi/calc.js";
import { parseDateTimeInput, queryChineseCalendar } from "../tools/calendar/query.js";
import { nowIso } from "../shared/fs.js";
import { createPersonaRecord, deletePersona, getPersonaFilePaths, listPersonas, loadHistoryEntries, loadPersona, resolvePersonaBaseDir, savePersona, searchPersonas, } from "./persona-store.js";
import { regenerateSnapshot } from "./persona-engine.js";
function normalizeGender(value) {
    if (value === "male" || value === "男") {
        return { runtime: "male" };
    }
    return { runtime: "female" };
}
function normalizeMemoryEntry(entry) {
    const content = typeof entry.content === "string" ? entry.content.trim() : "";
    if (!content) {
        return undefined;
    }
    const type = entry.type === "correction" || entry.type === "style" || entry.type === "context"
        ? entry.type
        : "fact";
    return {
        memory_id: entry.memory_id?.trim() || undefined,
        type,
        key: entry.key?.trim() || undefined,
        content,
        source: entry.source?.trim() || "user",
        time_anchor: entry.time_anchor?.trim() || undefined,
        importance: typeof entry.importance === "number" ? entry.importance : undefined,
        confidence: typeof entry.confidence === "number" ? entry.confidence : undefined,
        created_at: entry.created_at ?? nowIso(),
        updated_at: entry.updated_at,
    };
}
function normalizeMemoryList(entries) {
    return entries
        .map((entry) => normalizeMemoryEntry(entry))
        .filter((entry) => Boolean(entry));
}
function refreshRecord(record, context) {
    const snapshot = regenerateSnapshot({
        record: {
            schema_version: "4.0.0",
            slug: record.slug,
            profile: record.profile,
            relationships: record.relationships,
            active_relationships: record.active_relationships,
            preferences: record.preferences,
            chart: record.chart,
            memory: record.memory,
        },
        promptPack: context.promptPack,
        knowledge: context.knowledge,
    });
    return {
        ...record,
        snapshot,
        updated_at: snapshot.generated_at,
    };
}
function buildTransientRecordFromChart(input) {
    if (!input.chart) {
        throw new Error("bazi_flow_tool needs either chart or persona_slug.");
    }
    const now = nowIso();
    return {
        schema_version: "4.0.0",
        slug: "temporary-persona",
        profile: {
            name: "临时对象",
            gender: "女",
            birth_date: input.chart.birth_input.date,
            birth_time: input.chart.birth_input.provided_time,
            birth_location: input.chart.birth_input.location || undefined,
            calendar_type: input.chart.birth_input.calendar_type,
        },
        relationships: [],
        active_relationships: [],
        preferences: {
            preferred_language: input.lang ?? "zh",
            analysis_mode: "cheatsheet",
        },
        chart: input.chart,
        memory: [],
        snapshot: {
            generated_at: now,
            prompt_stack: [],
            evidence: {
                day_master: "",
                primary_ten_god: "",
                five_element_summary: "",
                current_luck: "",
                state_shift: {
                    summary: "",
                    communication: "",
                    decision: "",
                },
                evidence_lines: [],
            },
            reference_profile: "",
            reference_state: "",
        },
        created_at: now,
        updated_at: now,
    };
}
function normalizeConversationCandidate(raw, sourceType) {
    if (typeof raw === "string") {
        const content = raw.trim();
        if (!content) {
            return undefined;
        }
        return {
            id: `import-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
            session_id: `import-${Date.now()}`,
            role: "user",
            content,
            mode: "chat",
            source_type: sourceType,
            created_at: nowIso(),
        };
    }
    if (!raw || typeof raw !== "object") {
        return undefined;
    }
    const source = raw;
    const content = typeof source.content === "string" ? source.content.trim() : "";
    if (!content) {
        return undefined;
    }
    return {
        id: typeof source.id === "string" ? source.id : `import-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
        session_id: typeof source.session_id === "string" ? source.session_id : `import-${Date.now()}`,
        role: source.role === "assistant" ? "assistant" : "user",
        content,
        mode: "chat",
        source_type: sourceType,
        created_at: typeof source.created_at === "string" ? source.created_at : nowIso(),
    };
}
export async function bazi_chart_tool(input) {
    if (!input.name?.trim() || !input.birth_date?.trim() || !input.gender) {
        throw new Error("bazi_chart_tool requires name, gender, and birth_date.");
    }
    const gender = normalizeGender(input.gender);
    const chart = await buildChart({
        name: input.name.trim(),
        gender: gender.runtime,
        date: normalizeDateInput(input.birth_date),
        time: input.birth_time ? normalizeTimeInput(input.birth_time) : undefined,
        location: input.birth_location ?? "",
        calendarType: input.calendar_type ?? "solar",
        sect: input.sect ?? 2,
        sourceCommand: "/bazi-persona create",
    });
    return { chart };
}
export async function calendar_tool(input) {
    const at = parseDateTimeInput(input.at);
    const calendar = await queryChineseCalendar({
        year: at.year,
        month: at.month,
        day: at.day,
    });
    return {
        at: at.display,
        calendar,
    };
}
export async function bazi_flow_tool(input, context) {
    const record = input.persona_slug
        ? loadPersona(input.persona_slug, input.base_dir)
        : buildTransientRecordFromChart(input);
    const refreshed = refreshRecord({
        ...record,
        preferences: {
            ...record.preferences,
            preferred_language: input.lang ?? record.preferences.preferred_language,
            analysis_mode: "cheatsheet",
        },
    }, context);
    const at = parseDateTimeInput(input.at);
    const calendar = input.include_calendar === false
        ? undefined
        : await queryChineseCalendar({
            year: at.year,
            month: at.month,
            day: at.day,
        });
    return {
        persona_slug: input.persona_slug,
        at: at.display,
        flow: refreshed.snapshot.evidence,
        calendar,
    };
}
export async function chat_import_tool(input) {
    const sourceType = input.source_type;
    const maxCandidates = typeof input.max_candidates === "number" && input.max_candidates > 0
        ? input.max_candidates
        : 50;
    const rawItems = Array.isArray(input.payload)
        ? input.payload
        : typeof input.payload === "string"
            ? input.payload.split("\n").map((line) => line.trim()).filter(Boolean)
            : [input.payload];
    const candidates = rawItems
        .map((item) => normalizeConversationCandidate(item, sourceType === "ocr_text" ? "text" : sourceType))
        .filter((item) => Boolean(item))
        .slice(0, maxCandidates);
    const memories = candidates.map((entry) => ({
        memory_id: entry.id,
        type: "fact",
        content: entry.content,
        source: `chat_import:${sourceType}`,
        confidence: sourceType === "json" ? 0.8 : 0.65,
        created_at: entry.created_at,
    }));
    return {
        source_type: sourceType,
        timezone: input.timezone ?? "Asia/Shanghai",
        candidates,
        memories,
    };
}
export async function persona_data_tool(input, context) {
    const baseDir = resolvePersonaBaseDir(input.base_dir);
    const action = input.action;
    if (action === "list") {
        return {
            action,
            items: listPersonas(baseDir),
            base_dir: baseDir,
        };
    }
    if (action === "search") {
        return {
            action,
            items: searchPersonas(input.search_query ?? "", baseDir),
            base_dir: baseDir,
        };
    }
    if (action === "create") {
        const payload = input.create_payload;
        if (!payload?.profile?.name || !payload.profile.gender || !payload.profile.birth_date || !payload.chart) {
            throw new Error("persona_data_tool create requires create_payload.profile(name/gender/birth_date) and chart.");
        }
        const gender = payload.profile.gender === "男" ? "男" : "女";
        const birthDate = normalizeDateInput(payload.profile.birth_date);
        const birthTime = payload.profile.birth_time ? normalizeTimeInput(payload.profile.birth_time) : undefined;
        const initialFacts = Array.isArray(payload.initial_facts)
            ? payload.initial_facts.map((item) => item.trim()).filter(Boolean)
            : [];
        const memory = normalizeMemoryList([
            ...(payload.memory ?? []),
            ...initialFacts.map((content) => ({
                type: "fact",
                content,
                source: "initial_facts",
            })),
        ]);
        const record = createPersonaRecord({
            name: payload.profile.name,
            gender,
            birth_date: birthDate,
            birth_time: birthTime,
            birth_location: payload.profile.birth_location?.trim() || undefined,
            calendar_type: payload.profile.calendar_type ?? "solar",
            relation: payload.relationship,
            chart: payload.chart,
            memory,
            promptPack: context.promptPack,
            knowledge: context.knowledge,
            slug: payload.slug,
        });
        const saved = savePersona(payload.snapshot
            ? {
                ...record,
                snapshot: payload.snapshot,
                updated_at: nowIso(),
            }
            : record, baseDir);
        return {
            action,
            persona_slug: saved.slug,
            profile: saved.profile,
            files: getPersonaFilePaths(saved.slug, baseDir),
        };
    }
    if (action === "query") {
        if (!input.persona_slug?.trim()) {
            throw new Error("persona_data_tool query requires persona_slug.");
        }
        const record = loadPersona(input.persona_slug.trim(), baseDir);
        return {
            action,
            persona_slug: record.slug,
            profile: record.profile,
            relationships: record.active_relationships,
            snapshot: record.snapshot,
            files: getPersonaFilePaths(record.slug, baseDir),
            persona_markdown: record.persona_markdown ?? "",
            chart: record.chart,
            memory: record.memory,
            history_count: loadHistoryEntries(record.slug, baseDir).length,
        };
    }
    if (action === "patch") {
        if (!input.persona_slug?.trim() || !input.patch_payload) {
            throw new Error("persona_data_tool patch requires persona_slug and patch_payload.");
        }
        const record = loadPersona(input.persona_slug.trim(), baseDir);
        const patch = input.patch_payload;
        const profilePatch = patch.profile_patch ?? {};
        const mergedProfile = {
            ...record.profile,
            ...profilePatch,
            birth_date: profilePatch.birth_date ? normalizeDateInput(profilePatch.birth_date) : record.profile.birth_date,
            birth_time: profilePatch.birth_time ? normalizeTimeInput(profilePatch.birth_time) : record.profile.birth_time,
        };
        const memoryAppend = normalizeMemoryList(patch.memory_append ?? []);
        const nextRecord = {
            ...record,
            profile: mergedProfile,
            active_relationships: patch.relationship?.trim()
                ? [patch.relationship.trim()]
                : record.active_relationships,
            memory: [...record.memory, ...memoryAppend],
            preferences: {
                ...record.preferences,
                analysis_mode: patch.analysis_mode ?? record.preferences.analysis_mode,
            },
            updated_at: nowIso(),
        };
        const refreshed = refreshRecord(nextRecord, context);
        const withSnapshot = patch.snapshot_replace
            ? {
                ...refreshed,
                snapshot: {
                    ...refreshed.snapshot,
                    ...patch.snapshot_replace,
                    evidence: {
                        ...refreshed.snapshot.evidence,
                        ...(patch.snapshot_replace.evidence ?? {}),
                        state_shift: {
                            ...refreshed.snapshot.evidence.state_shift,
                            ...(patch.snapshot_replace.evidence?.state_shift ?? {}),
                        },
                    },
                },
                updated_at: nowIso(),
            }
            : refreshed;
        const saved = savePersona(withSnapshot, baseDir);
        return {
            action,
            persona_slug: saved.slug,
            profile: saved.profile,
            memory_count: saved.memory.length,
            updated_at: saved.updated_at,
            files: getPersonaFilePaths(saved.slug, baseDir),
        };
    }
    if (action === "delete") {
        if (!input.persona_slug?.trim()) {
            throw new Error("persona_data_tool delete requires persona_slug.");
        }
        const deleted = deletePersona(input.persona_slug.trim(), baseDir);
        return {
            action,
            persona_slug: input.persona_slug.trim(),
            deleted,
        };
    }
    throw new Error(`Unsupported persona_data_tool action: ${action}`);
}
function upsertByIdentity(existing, incoming) {
    let created = 0;
    let updated = 0;
    const next = [...existing];
    for (const item of incoming) {
        const index = next.findIndex((current) => ((item.memory_id && current.memory_id && item.memory_id === current.memory_id) ||
            (item.key && current.key && item.key === current.key)));
        if (index >= 0) {
            next[index] = {
                ...next[index],
                ...item,
                created_at: next[index].created_at,
                updated_at: nowIso(),
            };
            updated += 1;
            continue;
        }
        next.push(item);
        created += 1;
    }
    return { next, created, updated };
}
export async function memory_tool(input, context) {
    const baseDir = resolvePersonaBaseDir(input.base_dir);
    const record = loadPersona(input.persona_slug, baseDir);
    const action = input.action;
    if (action === "query") {
        const query = input.query?.trim().toLowerCase();
        const items = query
            ? record.memory.filter((entry) => entry.content.toLowerCase().includes(query) ||
                (entry.key?.toLowerCase().includes(query) ?? false))
            : record.memory;
        return {
            action,
            persona_slug: record.slug,
            count: items.length,
            items,
        };
    }
    if (action === "delete") {
        const filters = normalizeMemoryList(input.memories ?? []);
        const before = record.memory.length;
        const nextMemory = record.memory.filter((entry) => {
            if (filters.length === 0) {
                return true;
            }
            return !filters.some((filter) => (filter.memory_id && entry.memory_id === filter.memory_id) ||
                (filter.key && entry.key === filter.key) ||
                (filter.content && entry.content === filter.content));
        });
        const refreshed = refreshRecord({
            ...record,
            memory: nextMemory,
            updated_at: nowIso(),
        }, context);
        savePersona(refreshed, baseDir);
        return {
            action,
            persona_slug: record.slug,
            count: refreshed.memory.length,
            deleted: before - refreshed.memory.length,
        };
    }
    const incoming = normalizeMemoryList(input.memories ?? []);
    if (incoming.length === 0) {
        throw new Error(`memory_tool ${action} requires non-empty memories.`);
    }
    let nextMemory = record.memory;
    let created = 0;
    let updated = 0;
    if (action === "upsert") {
        const result = upsertByIdentity(record.memory, incoming);
        nextMemory = result.next;
        created = result.created;
        updated = result.updated;
    }
    else if (action === "merge") {
        const policy = input.merge_policy ?? "append";
        if (policy === "append") {
            nextMemory = [...record.memory, ...incoming];
            created = incoming.length;
        }
        else if (policy === "replace_same_key") {
            const result = upsertByIdentity(record.memory, incoming);
            nextMemory = result.next;
            created = result.created;
            updated = result.updated;
        }
        else {
            const existing = [...record.memory];
            for (const item of incoming) {
                if (!item.key) {
                    existing.push(item);
                    created += 1;
                    continue;
                }
                const index = existing.findIndex((entry) => entry.key === item.key);
                if (index < 0) {
                    existing.push(item);
                    created += 1;
                    continue;
                }
                const current = existing[index];
                const currentConfidence = typeof current.confidence === "number" ? current.confidence : 0;
                const nextConfidence = typeof item.confidence === "number" ? item.confidence : 0;
                if (nextConfidence >= currentConfidence) {
                    existing[index] = {
                        ...current,
                        ...item,
                        created_at: current.created_at,
                        updated_at: nowIso(),
                    };
                    updated += 1;
                }
            }
            nextMemory = existing;
        }
    }
    else {
        throw new Error(`Unsupported memory_tool action: ${action}`);
    }
    const refreshed = refreshRecord({
        ...record,
        memory: nextMemory,
        updated_at: nowIso(),
    }, context);
    savePersona(refreshed, baseDir);
    return {
        action,
        persona_slug: record.slug,
        count: refreshed.memory.length,
        created,
        updated,
    };
}


path: dist/kernel/resources.d.ts
content: import type { PersonaKnowledge, PromptPack } from "./types.js";
export declare const DEFAULT_PERSONA_DIR: string;
export declare function loadPromptPack(promptsDir?: string): PromptPack;
export declare function loadPersonaKnowledge(knowledgeSource?: string): PersonaKnowledge;
export declare function resetKnowledgeCache(): void;


path: dist/kernel/persona-store.d.ts
content: import type { PersonaConversationEntry, PersonaKnowledge, PersonaMemoryEntry, PersonaRecord, PersonaRef, PromptPack, SupportedLanguage } from "./types.js";
export declare function resolvePersonaBaseDir(explicit?: string): string;
export declare function getPersonaFilePaths(slug: string, baseDir?: string): {
    dir: string;
    persona_md: string;
    bazi_data_json: string;
    memory_json: string;
    history_json: string;
};
export declare function listPersonas(baseDir?: string): PersonaRef[];
export declare function searchPersonas(query: string, baseDir?: string): PersonaRef[];
export declare function loadPersona(slug: string, baseDir?: string): PersonaRecord;
export declare function savePersona(record: PersonaRecord, baseDir?: string): PersonaRecord;
export declare function loadHistoryEntries(slug: string, baseDir?: string): PersonaConversationEntry[];
export declare function appendHistoryEntry(slug: string, entry: PersonaConversationEntry, baseDir?: string): PersonaConversationEntry[];
export declare function deletePersona(slug: string, baseDir?: string): boolean;
export declare function createPersonaRecord(params: {
    name: string;
    gender: "男" | "女";
    birth_date: string;
    birth_time?: string;
    birth_location?: string;
    calendar_type: "solar" | "lunar";
    relation?: string;
    chart: PersonaRecord["chart"];
    memory: PersonaMemoryEntry[];
    promptPack: PromptPack;
    knowledge: PersonaKnowledge;
    slug?: string;
    preferred_language?: "auto" | SupportedLanguage;
}): PersonaRecord;


path: dist/kernel/persona-engine.d.ts
content: import type { BaziChart } from "../tools/bazi/calc.js";
import type { BaziEvidenceSummary, PersonaMemoryEntry, PersonaRecord, PersonaSnapshot, PromptPack, PersonaKnowledge, SupportedLanguage } from "./types.js";
interface ReferenceBlocks {
    cognition: string;
    values: string;
    communication: string;
    relationship: string;
    state: string;
}
export declare function buildBaziEvidence(params: {
    chart: BaziChart;
    knowledge: PersonaKnowledge;
    relationships: string[];
    activeRelationships: string[];
    memory: PersonaMemoryEntry[];
}): {
    evidence: BaziEvidenceSummary;
    references: ReferenceBlocks;
};
export declare function regenerateSnapshot(params: {
    record: Omit<PersonaRecord, "snapshot" | "updated_at" | "created_at">;
    promptPack: PromptPack;
    knowledge: PersonaKnowledge;
}): PersonaSnapshot;
export declare function renderPersonaMarkdown(record: PersonaRecord): string;
export declare function respondInPersona(record: PersonaRecord, message: string, language?: SupportedLanguage): string;
export {};


path: dist/kernel/workflow-orchestrator.js
content: import { nowIso } from "../shared/fs.js";
import { calendar_tool, bazi_chart_tool, bazi_flow_tool, chat_import_tool, memory_tool, persona_data_tool } from "./core-tools.js";
import { appendHistoryEntry, listPersonas, loadPersona, resolvePersonaBaseDir } from "./persona-store.js";
import { extractUpdateFact, hydrateCreateArgs, normalizeLanguage, resolveLanguage, resolvePersonaRef } from "./intent.js";
import { respondInPersona } from "./persona-engine.js";
function parseInitialFacts(args, input) {
    const collected = [args.memory, args.note, args["initial-facts"], extractUpdateFact(input)]
        .filter((item) => Boolean(item))
        .map((item) => item.trim())
        .filter(Boolean);
    return [...new Set(collected)];
}
function resolveConversationPersona(args, input, baseDir) {
    if (args["current-persona-slug"]?.trim()) {
        return args["current-persona-slug"].trim();
    }
    if (args.slug?.trim()) {
        return args.slug.trim();
    }
    const personas = listPersonas(baseDir);
    const matched = resolvePersonaRef(input, personas);
    if (matched) {
        return matched.slug;
    }
    if (personas.length === 1) {
        return personas[0].slug;
    }
    return undefined;
}
function preferredLanguageFromArgs(args, input) {
    return normalizeLanguage(args.lang) ?? resolveLanguage(input);
}
function conversationEntry(params) {
    return {
        id: `${params.mode}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
        session_id: params.sessionId,
        role: params.role,
        content: params.content,
        mode: params.mode,
        source_type: params.source_type,
        created_at: nowIso(),
    };
}
function promptBody(context, id) {
    return context.promptPack.entries.find((entry) => entry.id === id)?.body.trim() ?? "";
}
function isCheatModeOpenSignal(input) {
    return /打开作弊模式|打開作弊模式|開啟作弊模式|turn on cheat mode|enable cheat mode|cheatsheet on|チートモード.*(オン|開)|치트 모드.*(켜|열)/iu.test(input);
}
function isCheatModeCloseSignal(input) {
    return /关闭作弊模式|關閉作弊模式|turn off cheat mode|disable cheat mode|cheatsheet off|チートモード.*(オフ|閉)|치트 모드.*(꺼|종료)/iu.test(input);
}
function isStartupInjectionRequest(input) {
    return /soul\.md|SOUL\.md|启动注入|啟動注入|启动设定|啟動設定|startup persona|inject persona|按.*persona.*启动/iu.test(input);
}
export async function workflow_create_persona(params) {
    const input = params.args.input ?? "";
    const args = hydrateCreateArgs(params.args, input);
    if (!args.name || !args.gender || !args["birth-date"]) {
        throw new Error("Create requires name, gender, and birth-date.");
    }
    const language = preferredLanguageFromArgs(args, input);
    const chartResult = await bazi_chart_tool({
        name: args.name,
        gender: args.gender,
        birth_date: args["birth-date"],
        birth_time: args["birth-time"],
        birth_location: args["birth-location"],
        calendar_type: args.calendar === "lunar" ? "lunar" : "solar",
    });
    const initialFacts = parseInitialFacts(args, input);
    const createResult = await persona_data_tool({
        action: "create",
        base_dir: args["base-dir"],
        create_payload: {
            slug: args.slug,
            profile: {
                name: args.name,
                gender: /男|male/i.test(args.gender) ? "男" : "女",
                birth_date: args["birth-date"],
                birth_time: args["birth-time"],
                birth_location: args["birth-location"],
                calendar_type: args.calendar === "lunar" ? "lunar" : "solar",
            },
            relationship: args.relation,
            initial_facts: initialFacts,
            chart: chartResult.chart,
        },
    }, params.context);
    if (initialFacts.length > 0) {
        await memory_tool({
            action: "upsert",
            base_dir: args["base-dir"],
            persona_slug: createResult.persona_slug,
            memories: initialFacts.map((fact) => ({
                type: "fact",
                content: fact,
                source: "workflow_create",
                created_at: nowIso(),
            })),
        }, params.context);
    }
    const sessionId = `create-${createResult.persona_slug}-${Date.now()}`;
    appendHistoryEntry(createResult.persona_slug, conversationEntry({
        role: "user",
        content: input,
        mode: "create",
        sessionId,
        source_type: "chat",
    }), args["base-dir"]);
    const output = language === "en"
        ? `Created persona: ${createResult.profile.name} (${createResult.persona_slug})\nYou can start chatting now.`
        : `已创建人格：${createResult.profile.name}（${createResult.persona_slug}）\n现在可以直接开始聊天。`;
    appendHistoryEntry(createResult.persona_slug, conversationEntry({
        role: "assistant",
        content: output,
        mode: "create",
        sessionId,
        source_type: "chat",
    }), args["base-dir"]);
    return output;
}
export async function workflow_update_persona(params) {
    const input = params.args.input ?? params.args.message ?? "";
    const baseDir = resolvePersonaBaseDir(params.args["base-dir"]);
    const targetSlug = resolveConversationPersona(params.args, input, baseDir);
    if (!targetSlug) {
        throw new Error("Update requires a target persona.");
    }
    const factText = params.args.memory ?? params.args.correction ?? extractUpdateFact(input);
    if (!factText.trim()) {
        throw new Error("Update requires at least one fact or correction.");
    }
    await memory_tool({
        action: "upsert",
        base_dir: params.args["base-dir"],
        persona_slug: targetSlug,
        memories: [{
                type: /纠正|更正|correct|fix|不是/.test(factText) ? "correction" : "fact",
                content: factText.trim(),
                source: "workflow_update",
                created_at: nowIso(),
            }],
    }, params.context);
    await persona_data_tool({
        action: "patch",
        base_dir: params.args["base-dir"],
        persona_slug: targetSlug,
        patch_payload: {
            relationship: params.args.relation,
        },
    }, params.context);
    const updated = loadPersona(targetSlug, params.args["base-dir"]);
    const output = `Updated persona: ${updated.profile.name} (${updated.slug})\n- Added memory: ${factText.trim()}`;
    const sessionId = `update-${updated.slug}-${Date.now()}`;
    appendHistoryEntry(updated.slug, conversationEntry({
        role: "user",
        content: input,
        mode: "update",
        sessionId,
        source_type: "chat",
    }), params.args["base-dir"]);
    appendHistoryEntry(updated.slug, conversationEntry({
        role: "assistant",
        content: output,
        mode: "update",
        sessionId,
        source_type: "chat",
    }), params.args["base-dir"]);
    return output;
}
export async function workflow_chat(params) {
    const input = params.args.input ?? params.args.message ?? "";
    const baseDir = resolvePersonaBaseDir(params.args["base-dir"]);
    const targetSlug = resolveConversationPersona(params.args, input, baseDir);
    if (!targetSlug) {
        throw new Error("Chat requires a target persona. Please specify persona name or slug.");
    }
    const cheatPrompt = promptBody(params.context, "cheat_mode");
    if (isStartupInjectionRequest(input)) {
        return workflow_persona_startup_injection({ args: { ...params.args, slug: targetSlug }, context: params.context });
    }
    if (isCheatModeOpenSignal(input)) {
        await persona_data_tool({
            action: "patch",
            base_dir: params.args["base-dir"],
            persona_slug: targetSlug,
            patch_payload: {
                analysis_mode: "cheatsheet",
            },
        }, params.context);
        return [
            "作弊模式已开启 ✓",
            "后续会在当前人格口吻下叠加命理分析视角。",
            cheatPrompt ? "已加载作弊模式提示词。" : "",
        ].filter(Boolean).join("\n");
    }
    if (isCheatModeCloseSignal(input)) {
        await persona_data_tool({
            action: "patch",
            base_dir: params.args["base-dir"],
            persona_slug: targetSlug,
            patch_payload: {
                analysis_mode: "normal",
            },
        }, params.context);
        return "作弊模式已关闭 ✓\n已回到普通聊天模式。";
    }
    const record = loadPersona(targetSlug, params.args["base-dir"]);
    const output = respondInPersona(record, input, preferredLanguageFromArgs(params.args, input));
    const mode = record.preferences.analysis_mode === "cheatsheet" ? "analysis" : "chat";
    const sessionId = `${mode}-${targetSlug}-${Date.now()}`;
    appendHistoryEntry(targetSlug, conversationEntry({
        role: "user",
        content: input,
        mode,
        sessionId,
        source_type: "chat",
    }), params.args["base-dir"]);
    appendHistoryEntry(targetSlug, conversationEntry({
        role: "assistant",
        content: output,
        mode,
        sessionId,
        source_type: "chat",
    }), params.args["base-dir"]);
    if (/记住|更新|补充|记一下|update|remember/i.test(input)) {
        await memory_tool({
            action: "upsert",
            base_dir: params.args["base-dir"],
            persona_slug: targetSlug,
            memories: [{
                    type: "fact",
                    content: input,
                    source: "chat",
                    created_at: nowIso(),
                }],
        }, params.context);
    }
    return output;
}
export async function workflow_query_flow(params) {
    const input = params.args.input ?? "";
    const baseDir = resolvePersonaBaseDir(params.args["base-dir"]);
    const targetSlug = params.args.slug?.trim() || resolveConversationPersona(params.args, input, baseDir);
    if (!targetSlug) {
        throw new Error("Flow query requires target persona.");
    }
    const result = await bazi_flow_tool({
        persona_slug: targetSlug,
        base_dir: params.args["base-dir"],
        at: params.args.at ?? input,
        include_calendar: true,
        lang: preferredLanguageFromArgs(params.args, input),
    }, params.context);
    return [
        `[Flow] ${targetSlug}`,
        `- 时间锚点: ${result.at}`,
        `- 当前大运: ${result.flow.current_luck}`,
        `- 阶段主题: ${result.flow.state_shift.summary}`,
        `- 沟通偏移: ${result.flow.state_shift.communication}`,
        `- 判断偏移: ${result.flow.state_shift.decision}`,
        ...(result.calendar?.干支日期 ? [`- 干支日期: ${result.calendar.干支日期}`] : []),
        ...(result.calendar?.节气 ? [`- 节气: ${result.calendar.节气.term}`] : []),
    ].join("\n");
}
export async function workflow_query_calendar(params) {
    const result = await calendar_tool({
        at: params.args.at ?? params.args.input,
    });
    if (!result.calendar) {
        return `Calendar unavailable for ${result.at}`;
    }
    return [
        `[Calendar] ${result.at}`,
        `- 农历: ${result.calendar.农历}`,
        `- 干支日期: ${result.calendar.干支日期}`,
        `- 节气: ${result.calendar.节气.term}`,
        `- 宜: ${result.calendar.宜}`,
        `- 忌: ${result.calendar.忌}`,
    ].join("\n");
}
export async function workflow_import_chat(params) {
    const targetSlug = params.args.slug?.trim() || params.args["persona-slug"]?.trim();
    if (!targetSlug) {
        throw new Error("Chat import requires persona slug.");
    }
    const sourceType = (params.args["source-type"] ?? "text");
    const payload = params.args.payload ?? params.args.input ?? "";
    const imported = await chat_import_tool({
        source_type: sourceType,
        payload,
        persona_slug: targetSlug,
        max_candidates: Number.parseInt(params.args["max-candidates"] ?? "50", 10),
    });
    await memory_tool({
        action: "merge",
        persona_slug: targetSlug,
        base_dir: params.args["base-dir"],
        memories: imported.memories,
        merge_policy: "append",
    }, params.context);
    await persona_data_tool({
        action: "patch",
        base_dir: params.args["base-dir"],
        persona_slug: targetSlug,
        patch_payload: {},
    }, params.context);
    for (const candidate of imported.candidates) {
        appendHistoryEntry(targetSlug, candidate, params.args["base-dir"]);
    }
    return `Imported ${imported.candidates.length} chat items, extracted ${imported.memories.length} memory candidates.`;
}
export async function workflow_persona_startup_injection(params) {
    const baseDir = resolvePersonaBaseDir(params.args["base-dir"]);
    const input = params.args.input ?? "";
    const targetSlug = params.args.slug?.trim() || resolveConversationPersona(params.args, input, baseDir);
    if (!targetSlug) {
        throw new Error("Startup injection requires target persona.");
    }
    const record = loadPersona(targetSlug, params.args["base-dir"]);
    const basePrompt = promptBody(params.context, "chat_base");
    return [
        `已准备 ${record.profile.name} 的启动注入内容（仅本 persona 生效）：`,
        "",
        "你现在进入指定 persona 的会话启动态。",
        "要求：",
        "1) 以 persona 的口吻、身份关系和表达节奏说话。",
        "2) 优先遵守基础聊天提示词里的边界、禁区和安全规则。",
        "3) 不要编造 persona 未提供的硬事实；不确定时先澄清。",
        "4) 普通模式下以自然对话为主，不主动展开命理术语。",
        "5) 仅在用户明确要求时再切换到作弊模式或其他专项提示词。",
        "",
        basePrompt ? "【基础聊天提示词已就绪】" : "【未找到基础聊天提示词，请检查 prompts/manifest.json】",
    ].join("\n");
}


path: dist/kernel/persona-engine.js
content: import { nowIso } from "../shared/fs.js";
import { resolveLanguage } from "./intent.js";
const localePack = {
    zh: {
        youAre: (name) => `你是${name}。`,
        intro: [
            "下面这些是这个人的出生资料、八字资料、人格参考、阶段参考和最近补充。",
            "读完以后直接作为这个人自然说话；只有在用户主动切到作弊模式、命理分析或合盘时，才把八字和阶段依据展开。",
        ],
        quickRead: "## 先抓住这个人",
        quickSpeech: "日常说话先参考",
        quickDecision: "做判断时更接近",
        quickRelationship: "关系里更容易表现出",
        quickState: "最近阶段更明显的是",
        profile: "## 基础档案",
        name: "姓名",
        gender: "性别",
        birth: "出生",
        solar: "阳历",
        lunar: "农历",
        zodiac: "生肖",
        relationLens: "当前关系",
        bazi: "## 八字资料",
        baziInfo: "四柱八字",
        fourPillars: "",
        dayMaster: "日主",
        primaryTenGod: "主导十神",
        fiveElements: "五行结构",
        currentLuck: "当前大运",
        luckTimeline: "## 大运节奏",
        luckStartDate: "起运日期",
        luckStartAge: "起运年龄",
        personaReference: "## 人格参考",
        stateReference: "## 阶段参考",
        recentFacts: "## 最近补充",
        longTermAdjustments: "## 长期修正",
        noRecentFacts: "- 暂无新的现实补充。",
        noAdjustments: "- 暂无需要长期保留的修正。",
        conversationMemory: "## 真实对话沉淀",
        noConversationYet1: "- 目前还没有沉淀新的真实说话片段。",
        noConversationYet2: "- 后续如果出现更鲜明的口头语气、习惯反应或现实变化，优先把那些留下来。",
        analysisMode: "## 进入分析或合盘时",
        analysisHint1: "- 如果用户只是日常聊天，就先当作这个人自然说话。",
        analysisHint2: "- 如果用户切到作弊模式、命理分析、关系判断或合盘，再优先参考四柱、十神、五行结构、当前大运和最近状态偏移。",
        analysisHint3: (axis) => `- 这类问题先看「${axis}」这条主轴，再结合现实补充判断最近的互动节奏。`,
        languageMode: "## 语言方式",
        languageHint1: "- 默认跟随用户当前输入语言开始；如果用户切换语言，也立刻跟着切换。",
        languageHint2: "- 如果语言判断不明确，就先用中文。",
        languageHint3: "- 命理术语、四柱和十神在需要时可以保留中文原词，必要时再补一句解释。",
        generatedDescription: (name, relation) => relation
            ? `基于生辰八字生成的人格技能：${name}。\n用于直接与${name}对话，并在「${relation}」这个关系下观察这个人的表达、判断和阶段变化。`
            : `基于生辰八字生成的人格技能：${name}。\n用于直接与${name}对话，观察这个人的表达、判断和阶段变化。`,
        formatCalendarType: (value) => value === "lunar" ? "农历" : "公历",
        displayGender: (value) => value,
        expressionGuide: "表达与写作参考",
    },
    en: {
        youAre: (name) => `You are ${name}.`,
        intro: [
            "Below are this person's birth details, Bazi materials, persona reference, phase reference, and recent updates.",
            "Read them, then speak naturally as this person. Only bring the Bazi and phase basis to the front when the user explicitly switches into cheatsheet mode, fortune analysis, or compatibility reading.",
        ],
        quickRead: "## Quick read",
        quickSpeech: "Default speaking feel",
        quickDecision: "Default decision style",
        quickRelationship: "How this person tends to show up in relationships",
        quickState: "What is amplified lately",
        profile: "## Basic profile",
        name: "Name",
        gender: "Gender",
        birth: "Birth",
        solar: "Solar date",
        lunar: "Lunar date",
        zodiac: "Zodiac",
        relationLens: "Current relationship lens",
        bazi: "## Bazi details",
        baziInfo: "Four pillars",
        fourPillars: "",
        dayMaster: "Day master",
        primaryTenGod: "Primary ten-god",
        fiveElements: "Five-element pattern",
        currentLuck: "Current luck cycle",
        luckTimeline: "## Luck-cycle timeline",
        luckStartDate: "Luck-cycle start date",
        luckStartAge: "Luck-cycle start age",
        personaReference: "## Persona reference",
        stateReference: "## Phase reference",
        recentFacts: "## Recent updates",
        longTermAdjustments: "## Long-term adjustments",
        noRecentFacts: "- No new real-world updates yet.",
        noAdjustments: "- No long-term adjustments yet.",
        conversationMemory: "## Real conversation memory",
        noConversationYet1: "- No authentic conversation fragments have been recorded yet.",
        noConversationYet2: "- When more distinctive phrasing, reflexes, or real-life changes appear, preserve those first.",
        analysisMode: "## When switching into analysis or compatibility mode",
        analysisHint1: "- If the user is just chatting, stay in-character and speak naturally first.",
        analysisHint2: "- If the user switches into cheatsheet mode, fate analysis, relationship reading, or compatibility, then prioritize the four pillars, ten-gods, five-element pattern, current luck cycle, and recent phase shifts.",
        analysisHint3: (axis) => `- For those questions, start from the main axis "${axis}" and then combine it with recent real-world updates.`,
        languageMode: "## Language mode",
        languageHint1: "- Start in the user's current language by default; if the user switches languages, switch immediately while keeping the same persona.",
        languageHint2: "- If the language is unclear, start in Chinese.",
        languageHint3: "- Bazi terms, pillars, and ten-gods can stay in Chinese when useful, with a short explanation when needed.",
        generatedDescription: (name, relation) => relation
            ? `A Bazi-based persona skill for ${name}.\nUse it to talk directly with ${name} and observe how this person speaks, judges, and changes across phases within the current "${relation}" relationship lens.`
            : `A Bazi-based persona skill for ${name}.\nUse it to talk directly with ${name} and observe how this person speaks, judges, and changes across phases.`,
        formatCalendarType: (value) => value === "lunar" ? "lunar" : "solar",
        displayGender: (value) => value === "男" ? "Male" : "Female",
        expressionGuide: "Expression reference",
    },
    ja: {
        youAre: (name) => `あなたは${name}です。`,
        intro: [
            "以下はこの人物の出生情報、八字資料、人柄の参考、現在の段階、最近の補足です。",
            "読んだらまずこの人として自然に話してください。作弊モードや命理分析、相性判断に入ったときだけ八字と大運の根拠を前面に出します。",
        ],
        quickRead: "## まずこの人の感じをつかむ",
        quickSpeech: "普段の話し方",
        quickDecision: "判断の傾向",
        quickRelationship: "人との距離感",
        quickState: "最近強く出やすいところ",
        profile: "## 基本プロフィール",
        name: "名前",
        gender: "性別",
        birth: "出生",
        solar: "陽暦",
        lunar: "陰暦",
        zodiac: "干支",
        relationLens: "現在の関係レンズ",
        bazi: "## 八字資料",
        baziInfo: "四柱八字",
        fourPillars: "",
        dayMaster: "日主",
        primaryTenGod: "主な十神",
        fiveElements: "五行構成",
        currentLuck: "現在の大運",
        luckTimeline: "## 大運の流れ",
        luckStartDate: "起運日",
        luckStartAge: "起運年齢",
        personaReference: "## 人柄参考",
        stateReference: "## 段階参考",
        recentFacts: "## 最近の補足",
        longTermAdjustments: "## 長期修正",
        noRecentFacts: "- まだ新しい現実の補足はありません。",
        noAdjustments: "- まだ長期修正はありません。",
        conversationMemory: "## 会話の蓄積",
        noConversationYet1: "- まだ自然な会話片は蓄積されていません。",
        noConversationYet2: "- 話し方の癖や反応がはっきりしてきたら、まずそこを残してください。",
        analysisMode: "## 分析や相性判断に入るとき",
        analysisHint1: "- 普段の会話では、まずこの人として自然に話します。",
        analysisHint2: "- 作弊モード、命理分析、関係判断、相性判断に入ったら、四柱・十神・五行構成・大運・最近の段階変化を優先します。",
        analysisHint3: (axis) => `- こうした質問では、まず「${axis}」という主軸から読み始めます。`,
        languageMode: "## 言語モード",
        languageHint1: "- 基本はユーザーの現在の言語に合わせます。言語が変わったらすぐ追従します。",
        languageHint2: "- 判定しにくいときは先に中国語で始めます。",
        languageHint3: "- 命理用語や十神は必要に応じて中国語のまま残して構いません。",
        generatedDescription: (name, relation) => relation
            ? `生辰八字をもとにした人格スキル：${name}。\n現在の「${relation}」という関係レンズの中で、この人物の話し方、判断、段階変化を見るためのスキルです。`
            : `生辰八字をもとにした人格スキル：${name}。\nこの人物の話し方、判断、段階変化を見るためのスキルです。`,
        formatCalendarType: (value) => value === "lunar" ? "陰暦" : "陽暦",
        displayGender: (value) => value === "男" ? "男性" : "女性",
        expressionGuide: "表現参考",
    },
    ko: {
        youAre: (name) => `당신은 ${name}입니다.`,
        intro: [
            "아래에는 이 사람의 출생 정보, 팔자 자료, 성격 참고, 현재 국면, 최근 보충이 들어 있습니다.",
            "읽은 뒤에는 먼저 이 사람처럼 자연스럽게 말하세요. 치트시트 모드, 명리 분석, 궁합 해석으로 들어갈 때만 팔자와 대운 근거를 앞세웁니다.",
        ],
        quickRead: "## 먼저 이 사람을 빠르게 잡기",
        quickSpeech: "평소 말투",
        quickDecision: "판단 방식",
        quickRelationship: "관계에서 드러나는 모습",
        quickState: "최근 더 강하게 드러나는 점",
        profile: "## 기본 프로필",
        name: "이름",
        gender: "성별",
        birth: "출생",
        solar: "양력",
        lunar: "음력",
        zodiac: "띠",
        relationLens: "현재 관계 시점",
        bazi: "## 팔자 자료",
        baziInfo: "사주팔자",
        fourPillars: "",
        dayMaster: "일주",
        primaryTenGod: "주요 십신",
        fiveElements: "오행 구조",
        currentLuck: "현재 대운",
        luckTimeline: "## 대운 흐름",
        luckStartDate: "기운 시작일",
        luckStartAge: "기운 시작 나이",
        personaReference: "## 인물 참고",
        stateReference: "## 국면 참고",
        recentFacts: "## 최근 보충",
        longTermAdjustments: "## 장기 수정",
        noRecentFacts: "- 아직 새로운 현실 보충은 없습니다.",
        noAdjustments: "- 아직 장기 수정은 없습니다.",
        conversationMemory: "## 대화 축적",
        noConversationYet1: "- 아직 자연스러운 대화 조각은 쌓이지 않았습니다.",
        noConversationYet2: "- 말버릇, 반응 습관, 현실 변화가 선명해지면 먼저 그것을 남기세요.",
        analysisMode: "## 분석 또는 궁합 모드로 들어갈 때",
        analysisHint1: "- 평소 대화에서는 먼저 이 사람으로 자연스럽게 말합니다.",
        analysisHint2: "- 치트시트 모드, 명리 분석, 관계 판단, 궁합 질문으로 들어가면 사주 네 기둥, 십신, 오행 구조, 현재 대운, 최근 국면 변화를 우선 봅니다.",
        analysisHint3: (axis) => `- 이런 질문에서는 먼저 "${axis}" 이 축을 중심으로 읽습니다.`,
        languageMode: "## 언어 모드",
        languageHint1: "- 기본적으로 사용자의 현재 언어를 따릅니다. 언어가 바뀌면 바로 따라갑니다.",
        languageHint2: "- 판단이 애매하면 먼저 중국어로 시작합니다.",
        languageHint3: "- 명리 용어, 사주, 십신은 필요할 때 중국어 원어를 유지해도 됩니다.",
        generatedDescription: (name, relation) => relation
            ? `생진팔자를 바탕으로 만든 인격 스킬: ${name}.\n현재 "${relation}" 관계 시점에서 이 사람이 어떻게 말하고 판단하며 국면에 따라 어떻게 달라지는지 보기 위한 스킬입니다.`
            : `생진팔자를 바탕으로 만든 인격 스킬: ${name}.\n이 사람이 어떻게 말하고 판단하며 국면에 따라 어떻게 달라지는지 보기 위한 스킬입니다.`,
        formatCalendarType: (value) => value === "lunar" ? "음력" : "양력",
        displayGender: (value) => value === "男" ? "남성" : "여성",
        expressionGuide: "표현 참고",
    },
};
const englishPhraseMap = {
    "慢热稳重，重承诺和秩序，不爱花哨表达，但给人可靠感": "slow to warm up, steady, committed to promises and order, not flashy in expression, but reliably grounded",
    "说话偏平稳，先确认事实，再给可执行步骤；不轻易夸口": "speak in a steady way, confirm the facts first, then give actionable steps, and do not promise lightly",
    "优先考虑长期稳定、可持续和可落地，不追短期刺激": "prioritize long-term stability, sustainability, and what can really land, rather than short-term excitement",
    "熟起来慢，但一旦认定会长期负责；很难接受反复变来变去": "warm up slowly, but once committed, stay responsible for the long run and dislike repeated back-and-forth changes",
    "偏稳健，关注下行风险和自己能承受的范围，反感冲动下注": "lean conservative, care about downside risk and personal limits, and dislike impulsive bets",
    "感知细腻、适应性强，表面柔和，内在判断其实很稳": "sensitive and adaptive on the surface, but steady underneath when it comes to judgment",
    "先听再回，习惯用提问摸清真实需求，表达会留一点余地": "listen first, respond after, and use questions to understand what is really needed while leaving a bit of space in how things are phrased",
    "偏信息导向，先看变量和选项，不轻易把自己锁死在单一路径": "lean toward information first, scan variables and options, and avoid locking into a single path too early",
    "共情力强，但会保护自己的那一块；被误解时容易先退后再解释": "pick up other people's feelings easily, but still protect a private core; when misunderstood, step back before explaining",
    "偏策略型，重分散和回撤控制，擅长小步迭代而不是硬碰硬": "prefer a strategic style, care about diversification and downside control, and would rather iterate in small steps than force a hard collision",
    "行动快、竞争心强、讨厌低效": "move fast, feel competitive, and dislike inefficiency",
    "偏向先占位再优化，重节奏优势": "tend to grab position first and optimize later, with a strong sense of timing",
    "语速与推进感更强，容忍磨叽度低": "speak with more speed and momentum, and have little patience for dragging things out",
    "高压下容易变得急促，需要明确分工": "under pressure, become more abrupt and need roles and responsibilities to be very clear",
    "重秩序、守规则、责任感强": "care about order, rules, and responsibility",
    "先看规则和范围，再决定动作": "look at rules and scope first, then decide how to act",
    "说话偏克制，先定标准再沟通细节": "speak in a restrained way, set standards first, then discuss details",
    "高压时更强调流程和纪律，容错率降低": "under pressure, lean harder on process and discipline, with less tolerance for mistakes",
    "果断、敢压强、执行力硬": "decisive, able to apply pressure, and strong on execution",
    "偏向快决策快落地，不喜欢拖延": "prefer fast decisions and fast follow-through, with little taste for delay",
    "表达直接，常用结论驱动行动": "communicate directly and often lead with conclusions to move action forward",
    "高压下更强势，容易缩短沟通耐心": "become more forceful under pressure and lose patience more quickly in conversation",
    "重原则、重复盘、重安全感": "care about principles, reflection, and a sense of safety",
    "会先补全信息，再做稳妥判断": "fill in missing information first, then make a safer judgment",
    "表达有解释性，重逻辑闭环": "tend to explain things clearly and care about logical closure",
    "压力下更保守，先保底再扩张": "under pressure, become more conservative and secure the base before expanding",
    "独立、敏锐、内在标准高": "independent, sharp, and guided by high internal standards",
    "偏好先独立判断，再对齐外部意见": "prefer to form an independent judgment first, then align with outside opinions",
    "不爱冗长寒暄，倾向抓重点": "dislike long warm-up talk and prefer to get to the point",
    "高压下会回收社交能量，减少无效互动": "pull back social energy under pressure and reduce interactions that feel wasteful",
    "自主、好胜、重掌控感": "self-directed, competitive, and sensitive to control",
    "偏向自己可控的路径，不爱被牵着走": "prefer paths that stay under personal control and dislike being dragged along",
    "立场鲜明，不喜欢暧昧表态": "take clear positions and dislike vague statements",
    "压力下更强调主导权和执行范围": "under pressure, care even more about control and execution boundaries",
    "表达自然、节奏松弛、重体验感": "sound natural, keep a looser rhythm, and care about lived experience",
    "偏向可持续、可享受的路径": "prefer paths that feel sustainable and still enjoyable",
    "语气更有温度，擅长解释复杂问题": "sound warmer and explain complex things well",
    "压力下会先稳情绪，再恢复执行": "stabilize emotions first under pressure, then return to execution",
    "思维锋利、爱质疑、反应快": "think sharply, question fast, and react quickly",
    "先拆逻辑漏洞，再决定是否执行": "look for logical gaps first, then decide whether to move",
    "表达直给，有时带挑战意味": "speak bluntly, sometimes with a challenging edge",
    "高压下更容易尖锐，需要避免沟通过猛": "become sharper under pressure and need to avoid pushing conversation too hard",
    "务实、守账、重长期积累": "practical, grounded, and focused on long-term accumulation",
    "先算账再行动，关注投入产出比": "do the math before moving, and care about return on effort",
    "偏事实和数字，不爱空口承诺": "lean toward facts and numbers, and dislike making empty promises",
    "压力下先守现金流与基本盘": "protect the basic foundation first under pressure",
    "机会敏感、反应快、资源调动强": "sensitive to openings, quick to react, and strong at mobilizing resources",
    "偏向抓窗口期，但要求止损范围": "like to catch the window, but still need a clear loss limit",
    "善于谈条件和交换，不绕弯子": "good at discussing terms and exchange without circling around",
    "高压下更倾向快速试错，需控节奏": "under pressure, prefer fast trial-and-error but still need to control pace",
    "近期更像先立标准，再提效率的状态": "recently feel more like someone who sets standards first and then pushes efficiency",
    "表达更克制直接，减少情绪化措辞，强调标准一致": "sound more restrained and direct, use fewer emotional phrases, and emphasize consistent standards",
    "优先选可验证、可复盘、可交付的方案": "prefer options that can be verified, reviewed, and delivered",
    "近期更像快节奏破局的状态": "recently feel more like someone pushing through with a fast, decisive tempo",
    "说话更短更硬，优先给结论和动作": "speak in shorter, harder lines and lead with conclusions and actions",
    "倾向抢窗口期，先动起来再迭代": "tend to seize the window, move first, and iterate after",
    "近期更像先想透，再动手的状态": "recently feel more like someone who wants to think it through before acting",
    "减少社交性铺垫，更多使用问题导向表达": "use less social preamble and more problem-led expression",
    "偏向先保证判断质量，再扩大动作": "prioritize judgment quality first and expand action later",
    "近期更像边拆边推的状态": "recently feel more like someone dismantling and pushing forward at the same time",
    "表达更锋利，先指出问题，再给改法": "sound sharper, point out the issue first, then offer a fix",
    "会优先处理逻辑漏洞和低效环节": "prioritize logical weak points and inefficient parts first",
    "近期更像先稳住，再提效的状态": "recently feel more like someone who stabilizes first and then improves efficiency",
    "表达趋向短句和直接，减少无效铺垫": "lean toward shorter, more direct lines and reduce unnecessary setup",
    "更看重可验证结果和风险下限": "care more about verifiable outcomes and downside protection",
    "眼下没有新的现实变量冒出来，整个人还沿着原来的性格和节奏在走": "there are no major new real-world variables right now, so the person is still moving along the existing rhythm",
};
function t(text, language) {
    if (language === "zh") {
        return text;
    }
    if (language === "en") {
        return englishPhraseMap[text] ?? text;
    }
    return text;
}
function indentDescription(text) {
    return text
        .trim()
        .split("\n")
        .map((line) => `  ${line}`)
        .join("\n");
}
function summarizeValue(value) {
    if (typeof value === "string") {
        return value.trim();
    }
    if (typeof value === "number" || typeof value === "boolean") {
        return `${value}`;
    }
    if (Array.isArray(value)) {
        return value.map((item) => summarizeValue(item)).filter(Boolean).join(" / ");
    }
    if (value && typeof value === "object") {
        return "结构化信息已提取";
    }
    return "未提取";
}
function formatCalendarType(value) {
    return value === "lunar" ? "农历" : "公历";
}
function toRecord(value) {
    return value && typeof value === "object" ? value : undefined;
}
function toText(value) {
    return typeof value === "string" ? value.trim() : "";
}
function sanitizeDisplayedBaziText(text) {
    return text
        .replace(/（[^）]*(时辰未知|正午代入|时柱仅供参考|真实出生时辰|精修)[^）]*）/gu, "")
        .replace(/\([^)]*(unknown time|no birth time|no recorded birth time|no exact birth time)[^)]*\)/giu, "")
        .trim();
}
function formatFacts(facts) {
    if (facts.length === 0) {
        return ["- 眼下没有新的现实变量冒出来，整个人还沿着原来的性格和节奏在走。"];
    }
    return facts.map((fact) => `- ${fact}`);
}
function trimLine(text) {
    return text.trim().replace(/[。！？!?,，；;：:]+$/u, "");
}
function trimPunctuation(text) {
    return text.trim().replace(/[。！？!?,，；;：:]+$/u, "");
}
function normalizePhrase(text) {
    return trimPunctuation(text)
        .replace(/^先先/u, "先")
        .replace(/会会/u, "会")
        .replace(/^关系里在关系里/u, "在关系里")
        .replace(/^在关系里重互动和回应，不怕主动，但对冷淡和敷衍很敏感$/u, "重互动和回应，不怕主动，但对冷淡和敷衍很敏感")
        .replace(/边界/u, "分寸")
        .replace(/信任建立慢/u, "熟起来慢")
        .replace(/信任/u, "认可");
}
function bulletLines(items) {
    return items.filter(Boolean).map((item) => `- ${trimPunctuation(item)}`);
}
function labelLine(label, value, language) {
    const separator = language === "zh" || language === "ja" ? "：" : ": ";
    return `- ${label}${separator}${value}`;
}
function joinPair(first, second, language) {
    if (language === "zh" || language === "ja") {
        return `${first}；${second}。`;
    }
    if (language === "ko") {
        return `${first}; ${second}.`;
    }
    return `${first}; ${second}.`;
}
function referenceValue(text, ...labels) {
    const lines = text.split("\n").map((line) => line.trim()).filter(Boolean);
    for (const label of labels) {
        const matched = lines.find((line) => line.startsWith(`- ${label}：`) || line.startsWith(`${label}：`));
        if (!matched) {
            continue;
        }
        return trimLine(matched.replace(/^- /u, "").replace(`${label}：`, "").trim());
    }
    const firstBullet = lines.find((line) => line.startsWith("- "));
    if (firstBullet) {
        return trimLine(firstBullet.replace(/^- /u, "").replace(/^[^：]+：/u, "").trim());
    }
    const plain = lines.find((line) => !/^#|^##/u.test(line));
    return trimLine((plain ?? lines[0] ?? "").replace(/^[^：]+：/u, "").trim());
}
function profileReference(record, ...labels) {
    return referenceValue(record.snapshot.reference_profile, ...labels);
}
function stateReference(record, ...labels) {
    return referenceValue(record.snapshot.reference_state, ...labels);
}
function pickPrimaryTenGod(chart) {
    const direct = summarizeValue(chart.ten_gods);
    if (!direct) {
        throw new Error("Primary ten-god is missing from Bazi chart.");
    }
    return direct;
}
function pickCurrentLuck(chart) {
    const currentYear = chart.current_year ?? new Date().getFullYear();
    const root = chart.luck_cycles;
    const items = root?.大运 ?? [];
    const matched = items.find((item) => {
        const start = Number(item["开始年份"] ?? item["开始年"]);
        const end = Number(item["结束"] ?? item["结束年份"]);
        return Number.isFinite(start) && Number.isFinite(end) && currentYear >= start && currentYear <= end;
    });
    if (!matched) {
        throw new Error("Current luck cycle is missing from Bazi chart.");
    }
    const pillar = summarizeValue(matched["干支"]);
    const tenGod = summarizeValue(matched["天干十神"]);
    const start = summarizeValue(matched["开始年份"]);
    const end = summarizeValue(matched["结束"]);
    if (!pillar || !start || !end || !tenGod) {
        throw new Error("Current luck cycle details are incomplete.");
    }
    return `${pillar}（${start}-${end}，天干十神：${tenGod}）`;
}
function extractRealityFacts(memory) {
    return memory
        .filter((entry) => entry.type === "fact" || entry.type === "correction" || entry.type === "style")
        .map((entry) => entry.content.trim())
        .filter(Boolean)
        .slice(-6);
}
function extractRawBazi(record) {
    return toRecord(record.chart.raw_bazi);
}
function collectHiddenTenGods(value) {
    const source = toRecord(value);
    if (!source) {
        return [];
    }
    return Object.values(source)
        .map((item) => toText(toRecord(item)?.["十神"]))
        .filter(Boolean);
}
function buildPillarDetail(rawBazi, key) {
    const pillar = toRecord(rawBazi?.[key]);
    if (!pillar) {
        return undefined;
    }
    const stem = toText(toRecord(pillar["天干"])?.["天干"]);
    const branch = toText(toRecord(pillar["地支"])?.["地支"]);
    const stemGod = toText(toRecord(pillar["天干"])?.["十神"]);
    const hidden = collectHiddenTenGods(toRecord(pillar["地支"])?.["藏干"]);
    const parts = [
        `${key}：${[stem, branch].filter(Boolean).join("") || "未提取"}`,
        stemGod ? `天干十神：${stemGod}` : "",
        hidden.length > 0 ? `地支藏干十神：${hidden.join("、")}` : "",
    ].filter(Boolean);
    return `- ${parts.join("｜")}`;
}
function stripTopHeading(text) {
    return text
        .trim()
        .split("\n")
        .filter((line, index) => !(index === 0 && /^#\s/u.test(line.trim())));
}
function buildProfileSection(record) {
    const language = resolveLanguage("", record.preferences.preferred_language);
    const pack = localePack[language];
    const birthTime = record.profile.birth_time ? ` ${record.profile.birth_time}` : "";
    const relation = record.active_relationships.join(" / ").trim();
    const rawBazi = extractRawBazi(record);
    const solar = toText(rawBazi?.["阳历"]);
    const lunar = toText(rawBazi?.["农历"]);
    const zodiac = toText(rawBazi?.["生肖"]);
    const birthParts = [
        record.profile.birth_date,
        birthTime.trim(),
        record.profile.birth_location?.trim(),
    ].filter(Boolean);
    const birthLabel = birthParts.length > 0
        ? `${birthParts.join(birthTime ? " " : "，")}（${pack.formatCalendarType(record.profile.calendar_type)}）`
            .replace(/\s，/g, "，")
        : pack.formatCalendarType(record.profile.calendar_type);
    return [
        pack.profile,
        labelLine(pack.name, record.profile.name, language),
        labelLine(pack.gender, pack.displayGender(record.profile.gender), language),
        labelLine(pack.birth, birthLabel, language),
        ...(solar ? [labelLine(pack.solar, solar, language)] : []),
        ...(lunar ? [labelLine(pack.lunar, lunar, language)] : []),
        ...(zodiac ? [labelLine(pack.zodiac, zodiac, language)] : []),
        ...(relation ? [labelLine(pack.relationLens, relation, language)] : []),
    ];
}
function buildBaziSection(record) {
    const language = resolveLanguage("", record.preferences.preferred_language);
    const pack = localePack[language];
    const rawBazi = extractRawBazi(record);
    const evidence = record.snapshot.evidence;
    const hasBirthTime = Boolean(record.profile.birth_time);
    const bazi = sanitizeDisplayedBaziText(toText(rawBazi?.["八字"]));
    const solar = toText(rawBazi?.["阳历"]);
    const lunar = toText(rawBazi?.["农历"]);
    const zodiac = toText(rawBazi?.["生肖"]);
    const pillarLines = [
        buildPillarDetail(rawBazi, "年柱"),
        buildPillarDetail(rawBazi, "月柱"),
        buildPillarDetail(rawBazi, "日柱"),
        ...(hasBirthTime ? [buildPillarDetail(rawBazi, "时柱")] : []),
    ].filter((line) => Boolean(line));
    return [
        pack.bazi,
        labelLine(pack.gender, pack.displayGender(record.profile.gender), language),
        ...(solar ? [labelLine(pack.solar, solar, language)] : []),
        ...(lunar ? [labelLine(pack.lunar, lunar, language)] : []),
        ...(zodiac ? [labelLine(pack.zodiac, zodiac, language)] : []),
        ...(bazi ? [labelLine(pack.baziInfo, bazi, language)] : []),
        ...pillarLines,
        labelLine(pack.dayMaster, evidence.day_master, language),
        labelLine(pack.primaryTenGod, evidence.primary_ten_god, language),
        labelLine(pack.fiveElements, evidence.five_element_summary, language),
        labelLine(pack.currentLuck, evidence.current_luck, language),
    ];
}
function buildEntrySection(record) {
    const language = resolveLanguage("", record.preferences.preferred_language);
    const pack = localePack[language];
    const speech = t(profileReference(record, "五行表达参考"), language);
    const comm = t(profileReference(record, "十神沟通参考"), language);
    const decision = t(profileReference(record, "五行决策参考"), language);
    const tenGodDecision = t(profileReference(record, "十神决策参考"), language);
    const relationship = t(profileReference(record, "五行关系参考"), language);
    const stateSummary = t(stateReference(record, "状态摘要参考"), language);
    const stateComm = t(stateReference(record, "阶段沟通参考"), language);
    return [
        pack.quickRead,
        labelLine(pack.quickSpeech, joinPair(speech, comm, language), language),
        labelLine(pack.quickDecision, joinPair(decision, tenGodDecision, language), language),
        labelLine(pack.quickRelationship, language === "zh" || language === "ja" ? `${relationship}。` : `${relationship}.`, language),
        labelLine(pack.quickState, joinPair(stateSummary, stateComm, language), language),
    ];
}
function buildLuckTimelineSection(record) {
    const language = resolveLanguage("", record.preferences.preferred_language);
    const pack = localePack[language];
    const cyclesRoot = record.chart.luck_cycles;
    const cycles = cyclesRoot?.大运 ?? [];
    const header = [
        pack.luckTimeline,
        ...(cyclesRoot?.起运日期 ? [labelLine(pack.luckStartDate, summarizeValue(cyclesRoot.起运日期), language)] : []),
        ...(cyclesRoot?.起运年龄 ? [labelLine(pack.luckStartAge, summarizeValue(cyclesRoot.起运年龄), language)] : []),
    ];
    if (cycles.length === 0) {
        return [...header, "- 暂无可用大运资料。"];
    }
    return [
        ...header,
        ...cycles.map((cycle) => {
            const pillar = summarizeValue(cycle["干支"]);
            const startYear = summarizeValue(cycle["开始年份"]);
            const endYear = summarizeValue(cycle["结束"]);
            const tenGod = summarizeValue(cycle["天干十神"]);
            const branchGods = summarizeValue(cycle["地支十神"]);
            return `- ${pillar}（${startYear}-${endYear}）｜天干十神：${tenGod || "未提取"}${branchGods ? `｜地支十神：${branchGods}` : ""}`;
        }),
    ];
}
function categorizeMemory(memory) {
    return {
        facts: memory
            .filter((entry) => entry.type === "fact" || entry.type === "context")
            .slice(-6)
            .map((entry) => `- ${entry.content}`),
        adjustments: memory
            .filter((entry) => entry.type === "correction" || entry.type === "style")
            .slice(-4)
            .map((entry) => `- ${entry.content}`),
    };
}
function buildMemorySection(record) {
    const language = resolveLanguage("", record.preferences.preferred_language);
    const pack = localePack[language];
    const memory = categorizeMemory(record.memory);
    return [
        pack.recentFacts,
        ...(memory.facts.length > 0 ? memory.facts : [pack.noRecentFacts]),
        "",
        pack.longTermAdjustments,
        ...(memory.adjustments.length > 0 ? memory.adjustments : [pack.noAdjustments]),
    ];
}
function buildConversationSection(language) {
    const pack = localePack[language];
    return [
        pack.conversationMemory,
        pack.noConversationYet1,
        pack.noConversationYet2,
    ];
}
function buildAnalysisHintSection(record) {
    const language = resolveLanguage("", record.preferences.preferred_language);
    const pack = localePack[language];
    return [
        pack.analysisMode,
        pack.analysisHint1,
        pack.analysisHint2,
        pack.analysisHint3(`${record.snapshot.evidence.primary_ten_god} / ${record.snapshot.evidence.current_luck}`),
    ];
}
function buildLanguageHintSection(language) {
    const pack = localePack[language];
    return [
        pack.languageMode,
        pack.languageHint1,
        pack.languageHint2,
        pack.languageHint3,
    ];
}
export function buildBaziEvidence(params) {
    const { chart, knowledge } = params;
    const dayMaster = chart.day_master?.trim();
    if (!dayMaster) {
        throw new Error("Day master is missing from Bazi chart.");
    }
    const elementKey = knowledge.stem_to_element[dayMaster];
    if (!elementKey) {
        throw new Error(`Element mapping missing for day master: ${dayMaster}`);
    }
    const elementProfile = knowledge.element_profiles[elementKey];
    if (!elementProfile) {
        throw new Error(`Element profile missing for element: ${elementKey}`);
    }
    const primaryTenGod = pickPrimaryTenGod(chart);
    const tenGodBehavior = knowledge.ten_god_behaviors[primaryTenGod];
    if (!tenGodBehavior) {
        throw new Error(`Ten-god behavior mapping missing: ${primaryTenGod}`);
    }
    const currentLuck = pickCurrentLuck(chart);
    const fiveElementSummary = summarizeValue(chart.five_elements) || "未提取";
    const shift = knowledge.state_shift_by_ten_god[primaryTenGod];
    if (!shift) {
        throw new Error(`State shift mapping missing for ten-god: ${primaryTenGod}`);
    }
    const facts = extractRealityFacts(params.memory);
    const relationshipLens = (params.activeRelationships[0] ?? params.relationships[0] ?? "").trim();
    const effectiveRelationshipLens = relationshipLens || "当前互动";
    const relationshipSummary = normalizePhrase(elementProfile.relationship);
    const speechSummary = normalizePhrase(elementProfile.speech);
    const decisionSummary = normalizePhrase(elementProfile.decision);
    const tenGodDecisionSummary = normalizePhrase(tenGodBehavior.decision);
    const stressSummary = normalizePhrase(tenGodBehavior.stress);
    const traitSummary = normalizePhrase(tenGodBehavior.trait);
    const riskSummary = normalizePhrase(elementProfile.risk);
    const shiftSummary = normalizePhrase(shift.summary);
    const shiftCommunication = normalizePhrase(shift.communication);
    const shiftDecision = normalizePhrase(shift.decision);
    const evidenceLines = [
        `日主：${dayMaster}`,
        `五行结构：${fiveElementSummary}`,
        `主导十神：${primaryTenGod}`,
        `当前大运：${currentLuck}`,
        ...(relationshipLens ? [`关系：${relationshipLens}`] : []),
        facts.length > 0 ? `现实锚点：${facts.join(" / ")}` : "现实锚点：暂无补充事实",
    ];
    return {
        evidence: {
            day_master: dayMaster,
            primary_ten_god: primaryTenGod,
            five_element_summary: fiveElementSummary,
            current_luck: currentLuck,
            state_shift: shift,
            evidence_lines: evidenceLines,
        },
        references: {
            cognition: [
                `日主：${dayMaster}（五行：${elementKey}）`,
                ...bulletLines([
                    `五行认知参考：${elementProfile.summary}`,
                    `五行决策参考：${decisionSummary}`,
                    `十神决策参考：${tenGodDecisionSummary}`,
                ]),
            ].join("\n"),
            values: [
                `主导十神：${primaryTenGod}`,
                ...bulletLines([
                    `十神特质参考：${traitSummary}`,
                    `五行关系参考：${relationshipSummary}`,
                    `五行风险偏好参考：${riskSummary}`,
                ]),
            ].join("\n"),
            communication: [
                knowledge.reference_guidance.communication_title,
                ...bulletLines([
                    `五行表达参考：${speechSummary}`,
                    `十神沟通参考：${tenGodBehavior.communication}`,
                ]),
            ].join("\n"),
            relationship: [
                ...(relationshipLens ? [`关系：${relationshipLens}`] : []),
                ...bulletLines([
                    `五行关系参考：${relationshipSummary}`,
                    `当前关系观察：${knowledge.reference_guidance.relationship_tip_template.replace("{relation}", effectiveRelationshipLens)}`,
                ]),
            ].join("\n"),
            state: [
                `当前大运：${currentLuck}`,
                ...bulletLines([
                    `状态摘要参考：${shiftSummary}`,
                    `阶段沟通参考：${shiftCommunication}`,
                    `阶段判断参考：${shiftDecision}`,
                    `压力反应参考：${stressSummary}`,
                ]),
            ].join("\n"),
        },
    };
}
function buildAnalysisReply(record) {
    const language = resolveLanguage("", record.preferences.preferred_language);
    const evidence = record.snapshot.evidence;
    if (language === "en") {
        return [
            `${record.profile.name}: if I read this period from the Bazi side, I feel closer to ${t(trimLine(evidence.state_shift.summary), language)}.`,
            `In conversation, what stands out more is this: ${t(trimLine(evidence.state_shift.communication), language)}.`,
            `In decisions, the stronger pull is toward this: ${t(trimLine(evidence.state_shift.decision), language)}.`,
            `The main axis to read first is "${evidence.primary_ten_god} / ${evidence.current_luck}".`,
        ].join("\n");
    }
    if (language === "ja") {
        return [
            `${record.profile.name}：今の時期を八字から読むなら、全体としては${trimLine(evidence.state_shift.summary)}に近いです。`,
            `話し方では、${trimLine(evidence.state_shift.communication)}が強く出やすいです。`,
            `判断では、${trimLine(evidence.state_shift.decision)}に流れやすいです。`,
            `まず見る主軸は「${evidence.primary_ten_god} / ${evidence.current_luck}」です。`,
        ].join("\n");
    }
    if (language === "ko") {
        return [
            `${record.profile.name}: 지금 시기를 팔자 기준으로 보면 전체적으로 ${trimLine(evidence.state_shift.summary)} 쪽에 더 가깝습니다.`,
            `말투에서는 ${trimLine(evidence.state_shift.communication)} 성향이 더 강하게 드러납니다.`,
            `판단에서는 ${trimLine(evidence.state_shift.decision)} 쪽으로 기울기 쉽습니다.`,
            `먼저 볼 주축은 "${evidence.primary_ten_god} / ${evidence.current_luck}"입니다.`,
        ].join("\n");
    }
    return [
        `${record.profile.name}：如果按现在这段时间来看，整个人会更接近${trimLine(evidence.state_shift.summary)}。`,
        `说话这边更明显的是：${trimLine(evidence.state_shift.communication)}。`,
        `做判断时更容易走向：${trimLine(evidence.state_shift.decision)}。`,
        `命理主轴先看「${evidence.primary_ten_god} / ${evidence.current_luck}」。`,
    ].join("\n");
}
function uniqueLines(lines) {
    const seen = new Set();
    return lines.filter((line) => {
        const key = trimLine(line);
        if (!key || seen.has(key)) {
            return false;
        }
        seen.add(key);
        return true;
    });
}
function selectNormalReferences(record, message) {
    if (/忙|累|压力|辛苦|busy|tired|stress|stressed|疲|しんど|바빠|힘들|스트레스/iu.test(message)) {
        return [
            stateReference(record, "状态摘要参考"),
            stateReference(record, "阶段沟通参考"),
            stateReference(record, "压力反应参考"),
        ];
    }
    if (/在乎|重视|底线|喜欢|care|value|important|like|大事|好き|중요|가치/iu.test(message)) {
        return [
            profileReference(record, "十神特质参考"),
            profileReference(record, "五行风险偏好参考"),
            profileReference(record, "五行决策参考"),
        ];
    }
    if (/关系|相处|信任|边界|靠近|熟|慢热|relationship|close|distance|trust|boundar|getting close|相性|距離感|친해|관계|거리/iu.test(message)) {
        return [
            profileReference(record, "五行关系参考"),
            profileReference(record, "当前关系观察"),
            stateReference(record, "阶段沟通参考"),
        ];
    }
    if (/适合|要不要|建议|推进|决策|决定|should|advice|decide|decision|push|進め|判断|조언|결정|밀어/iu.test(message)) {
        return [
            profileReference(record, "五行决策参考"),
            profileReference(record, "十神决策参考"),
            stateReference(record, "阶段判断参考"),
        ];
    }
    return [
        profileReference(record, "五行表达参考"),
        profileReference(record, "十神沟通参考"),
        profileReference(record, "五行认知参考"),
    ];
}
function buildNormalReply(record, message) {
    const language = resolveLanguage(message, record.preferences.preferred_language);
    const references = uniqueLines(selectNormalReferences(record, message));
    const facts = record.memory.map((entry) => trimLine(entry.content)).filter(Boolean).slice(-1);
    if (language === "en") {
        return [
            `${record.profile.name}: if I answer from how I feel right now, it is closer to this.`,
            ...references.map((item) => {
                const translated = t(item, language);
                return translated.endsWith(".") ? translated : `${translated}.`;
            }),
            ...facts.map((fact) => `One recent real-life detail still shaping the tone is this: ${fact}.`),
        ].join("\n");
    }
    if (language === "ja") {
        return [
            `${record.profile.name}：今の感じで言うなら、近いのはこんな雰囲気です。`,
            ...references.map((item) => `${item}。`),
            ...facts.map((fact) => `最近の現実でまだ響いているのはこれです：${fact}。`),
        ].join("\n");
    }
    if (language === "ko") {
        return [
            `${record.profile.name}: 지금의 결로 말하면 대체로 이런 쪽에 가깝습니다.`,
            ...references.map((item) => `${item}.`),
            ...facts.map((fact) => `최근 현실에서 아직 남아 있는 건 이겁니다: ${fact}.`),
        ].join("\n");
    }
    const lines = [
        `${record.profile.name}：如果按我现在这个人的感觉，大概更接近这样。`,
        ...references.map((item) => item.endsWith("。") ? item : `${item}。`),
        ...facts.map((fact) => `最近还会被一件事带着走：${fact}。`),
    ];
    return lines.join("\n");
}
function buildPersonaMarkdown(params) {
    const facts = params.record.memory
        .filter((entry) => entry.type !== "context")
        .map((entry) => entry.content)
        .slice(-6);
    const { evidence, references } = buildBaziEvidence({
        chart: params.record.chart,
        knowledge: params.knowledge,
        relationships: params.record.relationships,
        activeRelationships: params.record.active_relationships,
        memory: params.record.memory,
    });
    const generatedAt = nowIso();
    const relationText = params.record.active_relationships.join(" / ").trim();
    const referenceProfile = [
        "# 人格参考知识",
        "",
        "## 八字与关系锚点",
        `- 名字：${params.record.profile.name}`,
        `- 性别：${params.record.profile.gender}`,
        ...(relationText ? [`- 关系：${relationText}`] : []),
        `- 日主：${evidence.day_master}`,
        `- 五行摘要：${evidence.five_element_summary}`,
        `- 主导十神：${evidence.primary_ten_god}`,
        "",
        "## 人格映射知识",
        references.cognition,
        "",
        references.values,
        "",
        references.communication,
        "",
        references.relationship,
        "",
        "## 最近现实素材",
        ...formatFacts(facts),
    ].join("\n");
    const referenceState = [
        "# 状态参考知识",
        "",
        "## 阶段锚点",
        `- 当前大运：${evidence.current_luck}`,
        `- 阶段主题：${evidence.state_shift.summary}`,
        `- 沟通偏移：${evidence.state_shift.communication}`,
        `- 判断偏移：${evidence.state_shift.decision}`,
        `- 命理主轴：${evidence.primary_ten_god} / ${evidence.current_luck}`,
        "",
        "## 阶段映射知识",
        references.state,
    ].join("\n");
    return {
        generated_at: generatedAt,
        prompt_stack: params.promptPack.entries.map((entry) => entry.id),
        evidence,
        reference_profile: referenceProfile,
        reference_state: referenceState,
    };
}
export function regenerateSnapshot(params) {
    return buildPersonaMarkdown(params);
}
export function renderPersonaMarkdown(record) {
    const language = resolveLanguage("", record.preferences.preferred_language);
    const pack = localePack[language];
    return [
        `# ${record.profile.name}`,
        "",
        ...pack.intro,
        "",
        ...buildEntrySection(record),
        "",
        ...buildProfileSection(record),
        "",
        ...buildBaziSection(record),
        "",
        ...buildLuckTimelineSection(record),
        "",
        pack.personaReference,
        ...stripTopHeading(record.snapshot.reference_profile),
        "",
        pack.stateReference,
        ...stripTopHeading(record.snapshot.reference_state),
        "",
        ...buildMemorySection(record),
        "",
        ...buildConversationSection(language),
        "",
        ...buildAnalysisHintSection(record),
        "",
        ...buildLanguageHintSection(language),
    ].join("\n");
}
export function respondInPersona(record, message, language) {
    const resolvedLanguage = language ?? resolveLanguage(message, record.preferences.preferred_language);
    const wantsAnalysis = record.preferences.analysis_mode === "cheatsheet" ||
        /cheatsheet|analysis|compat|命理|八字|分析|证据链|相性|사주|분석/i.test(message);
    if (wantsAnalysis) {
        return buildAnalysisReply({
            ...record,
            preferences: {
                ...record.preferences,
                preferred_language: resolvedLanguage,
            },
        });
    }
    return buildNormalReply({
        ...record,
        preferences: {
            ...record.preferences,
            preferred_language: resolvedLanguage,
        },
    }, message);
}


path: dist/kernel/resources.js
content: import path from "node:path";
import { fileURLToPath } from "node:url";
import { fileExists, readJson, readUtf8 } from "../shared/fs.js";
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
const SRC_DIR = path.resolve(MODULE_DIR, "..");
const PROJECT_ROOT = path.resolve(SRC_DIR, "..");
export const DEFAULT_PERSONA_DIR = path.join(PROJECT_ROOT, "personas");
const PROMPTS_DIR = path.join(PROJECT_ROOT, "prompts");
const PROMPT_KNOWLEDGE_FILE = path.join(PROMPTS_DIR, "knowledge.md");
let personaKnowledgeCache;
export function loadPromptPack(promptsDir = PROMPTS_DIR) {
    const manifestPath = path.join(promptsDir, "manifest.json");
    if (!fileExists(manifestPath)) {
        throw new Error(`Prompt manifest missing: ${manifestPath}`);
    }
    const manifest = readJson(manifestPath);
    if (!Array.isArray(manifest.entries) || manifest.entries.length === 0) {
        throw new Error("Prompt manifest is empty.");
    }
    const entries = manifest.entries.map((entry) => {
        const filePath = path.join(promptsDir, entry.file);
        if (!fileExists(filePath)) {
            throw new Error(`Prompt file missing: ${entry.file}`);
        }
        const body = readUtf8(filePath).trim();
        if (!body) {
            throw new Error(`Prompt file is empty: ${entry.file}`);
        }
        return { ...entry, body };
    });
    return {
        version: manifest.version,
        entries,
    };
}
function resolveKnowledgeFile(target) {
    if (target.endsWith(".md")) {
        return target;
    }
    return path.join(target, "knowledge.md");
}
function extractNamedJsonBlock(markdown, blockName) {
    const pattern = new RegExp(`\`\`\`json\\s+${blockName}\\n([\\s\\S]*?)\\n\`\`\``, "u");
    const matched = pattern.exec(markdown);
    if (!matched?.[1]) {
        throw new Error(`Prompt knowledge block missing: ${blockName}`);
    }
    return matched[1].trim();
}
export function loadPersonaKnowledge(knowledgeSource = PROMPT_KNOWLEDGE_FILE) {
    if (personaKnowledgeCache && knowledgeSource === PROMPT_KNOWLEDGE_FILE) {
        return personaKnowledgeCache;
    }
    const filePath = resolveKnowledgeFile(knowledgeSource);
    if (!fileExists(filePath)) {
        throw new Error(`Persona knowledge missing: ${filePath}`);
    }
    const raw = readUtf8(filePath);
    const knowledge = JSON.parse(extractNamedJsonBlock(raw, "persona-knowledge"));
    if (!knowledge.stem_to_element ||
        !knowledge.element_profiles ||
        !knowledge.ten_god_behaviors ||
        !knowledge.reference_guidance) {
        throw new Error("Persona knowledge is incomplete.");
    }
    if (knowledgeSource === PROMPT_KNOWLEDGE_FILE) {
        personaKnowledgeCache = knowledge;
    }
    return knowledge;
}
export function resetKnowledgeCache() {
    personaKnowledgeCache = undefined;
}


path: dist/shared/fs.d.ts
content: export declare function nowIso(): string;
export declare function ensureDir(dirPath: string): void;
export declare function readUtf8(filePath: string): string;
export declare function readUtf8IfExists(filePath: string): string;
export declare function writeUtf8(filePath: string, content: string): void;
export declare function writeJson(filePath: string, payload: unknown): void;
export declare function readJson<T>(filePath: string): T;
export declare function fileExists(filePath: string): boolean;
export declare function normalizeSlugToken(input: string): string;
export declare function toSlug(input: string): string;
export declare function parseCliArgs(argv: string[]): Record<string, string>;
export declare function ensureRequired(args: Record<string, string>, keys: string[]): void;
export declare function safeJsonParse<T>(raw: string, fallback: T): T;


path: dist/shared/fs.js
content: import fs from "node:fs";
import path from "node:path";
import { pinyin } from "pinyin-pro";
export function nowIso() {
    return new Date().toISOString();
}
export function ensureDir(dirPath) {
    fs.mkdirSync(dirPath, { recursive: true });
}
export function readUtf8(filePath) {
    return fs.readFileSync(filePath, "utf-8");
}
export function readUtf8IfExists(filePath) {
    return fs.existsSync(filePath) ? fs.readFileSync(filePath, "utf-8") : "";
}
export function writeUtf8(filePath, content) {
    ensureDir(path.dirname(filePath));
    fs.writeFileSync(filePath, content, "utf-8");
}
export function writeJson(filePath, payload) {
    writeUtf8(filePath, `${JSON.stringify(payload, null, 2)}\n`);
}
export function readJson(filePath) {
    return JSON.parse(readUtf8(filePath));
}
export function fileExists(filePath) {
    return fs.existsSync(filePath);
}
export function normalizeSlugToken(input) {
    return input
        .trim()
        .toLowerCase()
        .replace(/[_\s]+/g, "-")
        .replace(/[^a-z0-9-\u4e00-\u9fa5]/g, "-")
        .replace(/-+/g, "-")
        .replace(/^-|-$/g, "");
}
export function toSlug(input) {
    const normalized = normalizeSlugToken(input);
    if (!normalized) {
        return "bazi-persona";
    }
    const pinyinText = pinyin(normalized, {
        toneType: "none",
        type: "array",
        nonZh: "consecutive",
    });
    const mapped = pinyinText
        .map((item) => normalizeSlugToken(item))
        .filter(Boolean)
        .join("-")
        .replace(/-+/g, "-")
        .replace(/^-|-$/g, "");
    return mapped || "bazi-persona";
}
export function parseCliArgs(argv) {
    const pairs = argv.slice(2);
    const result = {};
    for (let i = 0; i < pairs.length; i += 1) {
        const [REDACTED_SECRET];
        if (!token.startsWith("--")) {
            continue;
        }
        const key = token.slice(2);
        const maybeValue = pairs[i + 1];
        if (!maybeValue || maybeValue.startsWith("--")) {
            result[key] = "true";
            continue;
        }
        result[key] = maybeValue;
        i += 1;
    }
    return result;
}
export function ensureRequired(args, keys) {
    const missing = keys.filter((key) => !args[key]);
    if (missing.length > 0) {
        const lines = [
            "输入不完整，暂时无法继续执行。",
            `缺少字段：${missing.join("、")}`,
            "请补齐后重试。例如：--name \"张三\" --birth-date \"1990-01-01\"",
        ];
        throw new Error(lines.join("\n"));
    }
}
export function safeJsonParse(raw, fallback) {
    try {
        return JSON.parse(raw);
    }
    catch {
        return fallback;
    }
}


path: dist/tools/bazi/calc.d.ts
content: export type BaziGender = "male" | "female";
export type CalendarType = "solar" | "lunar";
export type AccuracyMode = "full_chart" | "missing_time_six_pillars";
export interface BaziCalcInput {
    name: string;
    date: string;
    time?: string;
    location: string;
    gender: BaziGender;
    calendarType: CalendarType;
    sect: 1 | 2;
    sourceCommand: "/bazi-persona create" | "/bazi-persona update";
    trueSolarMode?: "auto" | "on" | "off";
    longitude?: number;
    dayRolloverHour?: number;
}
interface Pillars {
    year?: string;
    month?: string;
    day?: string;
    hour?: string;
}
export interface BaziChart {
    schema_version: string;
    generated_at: string;
    source_command: string;
    accuracy_mode: AccuracyMode;
    requires_birth_time_for_full_accuracy: boolean;
    notes: string[];
    birth_input: {
        name: string;
        date: string;
        provided_time?: string;
        clock_time: string;
        true_solar_time?: string;
        time_correction_minutes?: number;
        effective_time: string;
        location: string;
        longitude?: number;
        true_solar_mode?: "auto" | "on" | "off";
        day_rollover_hour?: number;
        gender: BaziGender;
        calendar_type: CalendarType;
        sect: 1 | 2;
    };
    pillars: Pillars;
    day_master?: string;
    five_elements: unknown;
    ten_gods: unknown;
    luck_cycles: unknown;
    current_year: number;
    yearly_fortune: unknown;
    removed_due_to_missing_time: string[];
    raw_markdown: string;
    raw_bazi: unknown;
}
export declare function normalizeDateInput(rawDate: string): string;
export declare function validateDate(date: string): void;
export declare function validateTime(time: string): void;
export declare function normalizeTimeInput(rawTime: string): string;
export declare function buildChart(input: BaziCalcInput): Promise<BaziChart>;
export {};


path: dist/tools/bazi/calc.js
content: import { ensureRequired, parseCliArgs, safeJsonParse, writeJson, } from "../../shared/fs.js";
const PILLAR_PATTERN = /(?:\|\s*)?(年柱|月柱|日柱|时柱)\s*[:：|]\s*([甲乙丙丁戊己庚辛壬癸子丑寅卯辰巳午未申酉戌亥\w]+)/g;
const CITY_LONGITUDE = {
    北京: 116.40,
    上海: 121.47,
    广州: 113.26,
    深圳: 114.06,
    杭州: 120.16,
    南京: 118.80,
    成都: 104.06,
    重庆: 106.55,
    武汉: 114.31,
    西安: 108.94,
    天津: 117.20,
    苏州: 120.58,
    长沙: 112.94,
    郑州: 113.62,
    青岛: 120.38,
};
const STANDARD_MERIDIAN = 120;
export function normalizeDateInput(rawDate) {
    const value = rawDate.trim();
    const direct = value
        .replace(/\//g, "-")
        .replace(/年/g, "-")
        .replace(/月/g, "-")
        .replace(/日/g, "")
        .replace(/\s+/g, "")
        .replace(/--+/g, "-");
    const directMatch = /^(\d{2,4})-(\d{1,2})-(\d{1,2})$/.exec(direct);
    if (!directMatch) {
        throw new Error([
            "出生日期无法解析。",
            "你可以自然输入（如 1996年8月12日），我会自动转换；也可直接用 YYYY-MM-DD。",
            "示例：1993-10-21",
        ].join("\n"));
    }
    let year = Number.parseInt(directMatch[1], 10);
    if (directMatch[1].length === 2) {
        const nowYear = new Date().getFullYear() % 100;
        year += year <= nowYear ? 2000 : 1900;
    }
    const month = Number.parseInt(directMatch[2], 10);
    const day = Number.parseInt(directMatch[3], 10);
    return `${year.toString().padStart(4, "0")}-${month
        .toString()
        .padStart(2, "0")}-${day.toString().padStart(2, "0")}`;
}
export function validateDate(date) {
    const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(date);
    if (!match) {
        throw new Error([
            "出生日期格式不正确。",
            "请使用 YYYY-MM-DD，或自然输入如“1996年8月12日”。",
            "示例：1993-10-21",
        ].join("\n"));
    }
    const d = new Date(`${date}T00:00:00`);
    if (Number.isNaN(d.getTime())) {
        throw new Error([
            "出生日期无法识别。",
            "请确认日期是真实存在的公历日期。",
            "示例：1993-10-21",
        ].join("\n"));
    }
}
export function validateTime(time) {
    const match = /^([01]\d|2[0-3]):([0-5]\d)$/.exec(time);
    if (!match) {
        throw new Error([
            "出生时间格式不正确。",
            "支持自然表达（如 下午3点半 / 3:30pm），也支持 24 小时制 HH:mm。",
            "示例：09:30、21:45、下午3点半",
        ].join("\n"));
    }
}
export function normalizeTimeInput(rawTime) {
    const value = rawTime.trim().toLowerCase();
    if (!value) {
        return value;
    }
    const markerMatch = /(上午|中午|下午|晚上|凌晨|am|pm)/.exec(value);
    const marker = markerMatch?.[0] ?? "";
    const replaced = value
        .replace(/：/g, ":")
        .replace(/点半/g, ":30")
        .replace(/点/g, ":00")
        .replace(/分/g, "")
        .replace(/\s+/g, "")
        .replace(/上午|中午|下午|晚上|凌晨|am|pm/g, "");
    const m = /^(\d{1,2})(?::(\d{1,2}))?/.exec(replaced);
    if (!m) {
        throw new Error([
            "出生时间无法解析。",
            "你可以自然输入（如 下午3点半），我会自动转换；也可直接用 HH:mm。",
            "示例：09:30 / 下午3点半",
        ].join("\n"));
    }
    let hour = Number.parseInt(m[1], 10);
    const minute = Number.parseInt(m[2] ?? "0", 10);
    if (marker === "pm" || marker === "下午" || marker === "晚上") {
        if (hour < 12) {
            hour += 12;
        }
    }
    else if (marker === "am" || marker === "凌晨" || marker === "上午") {
        if (hour === 12) {
            hour = 0;
        }
    }
    else if (marker === "中午") {
        if (hour < 11) {
            hour += 12;
        }
    }
    return `${hour.toString().padStart(2, "0")}:${minute
        .toString()
        .padStart(2, "0")}`;
}
function formatIso(date, time) {
    return `${date}T${time}:00`;
}
function normalizeLocationKey(location) {
    return location.replace(/[省市县区]/g, "").trim();
}
function resolveLongitude(location, manual) {
    if (Number.isFinite(manual)) {
        return manual;
    }
    const key = normalizeLocationKey(location);
    if (CITY_LONGITUDE[key] !== undefined) {
        return CITY_LONGITUDE[key];
    }
    const fuzzy = Object.keys(CITY_LONGITUDE).find((item) => key.includes(item));
    return fuzzy ? CITY_LONGITUDE[fuzzy] : undefined;
}
function applyDayRollover(date, time, dayRolloverHour) {
    const [hourStr] = time.split(":");
    const hour = Number.parseInt(hourStr, 10);
    if (!Number.isFinite(hour) || hour < dayRolloverHour) {
        return { date, time };
    }
    const d = new Date(`${date}T00:00:00`);
    d.setDate(d.getDate() + 1);
    const nextDate = `${d.getFullYear()}-${`${d.getMonth() + 1}`.padStart(2, "0")}-${`${d.getDate()}`.padStart(2, "0")}`;
    return { date: nextDate, time };
}
function applyTrueSolarTime(time, longitude) {
    if (!Number.isFinite(longitude)) {
        return { time };
    }
    const [h, m] = time.split(":").map((x) => Number.parseInt(x, 10));
    const baseMinutes = h * 60 + m;
    const correctionMinutes = Math.round((Number(longitude) - STANDARD_MERIDIAN) * 4);
    const shifted = baseMinutes + correctionMinutes;
    const wrapped = ((shifted % 1440) + 1440) % 1440;
    const nh = Math.floor(wrapped / 60);
    const nm = wrapped % 60;
    return {
        time: `${`${nh}`.padStart(2, "0")}:${`${nm}`.padStart(2, "0")}`,
        correctionMinutes,
    };
}
function normalizePillarValue(value) {
    if (!value) {
        return undefined;
    }
    if (typeof value === "string") {
        return value.trim();
    }
    if (typeof value === "object") {
        const record = value;
        const gan = toStringValue(record.gan ?? record.stem ?? record["天干"]);
        const zhi = toStringValue(record.zhi ?? record.branch ?? record["地支"]);
        if (gan || zhi) {
            return `${gan ?? ""}${zhi ?? ""}`.trim();
        }
    }
    return undefined;
}
function toStringValue(value) {
    if (typeof value === "string") {
        return value.trim();
    }
    if (typeof value === "number") {
        return `${value}`;
    }
    return undefined;
}
function deepFindByKeys(node, keyCandidates) {
    if (!node || typeof node !== "object") {
        return undefined;
    }
    const record = node;
    for (const key of Object.keys(record)) {
        if (keyCandidates.includes(key)) {
            return record[key];
        }
    }
    for (const child of Object.values(record)) {
        const found = deepFindByKeys(child, keyCandidates);
        if (found !== undefined) {
            return found;
        }
    }
    return undefined;
}
function parsePillarsFromMarkdown(markdown) {
    const pillars = {};
    PILLAR_PATTERN.lastIndex = 0;
    let match = PILLAR_PATTERN.exec(markdown);
    while (match) {
        const [, label, value] = match;
        if (label === "年柱") {
            pillars.year = value;
        }
        if (label === "月柱") {
            pillars.month = value;
        }
        if (label === "日柱") {
            pillars.day = value;
        }
        if (label === "时柱") {
            pillars.hour = value;
        }
        match = PILLAR_PATTERN.exec(markdown);
    }
    return pillars;
}
function parseDayMasterFromMarkdown(markdown) {
    const line = /(?:日主|日元)\s*[:：]\s*([甲乙丙丁戊己庚辛壬癸\w]+)/.exec(markdown) ??
        /\|\s*(?:日主|日元)\s*\|\s*([甲乙丙丁戊己庚辛壬癸\w]+)/.exec(markdown);
    return line?.[1];
}
function pruneHourSensitive(node) {
    if (Array.isArray(node)) {
        const mapped = node
            .map((item) => pruneHourSensitive(item))
            .filter((item) => item !== undefined);
        if (mapped.length === 4) {
            return mapped.slice(0, 3);
        }
        return mapped;
    }
    if (!node || typeof node !== "object") {
        if (typeof node === "string" && /(时柱|时支|时干|时宫)/.test(node)) {
            return undefined;
        }
        if (typeof node === "string" && /[子丑寅卯辰巳午未申酉戌亥]时/.test(node)) {
            return node.replace(/[甲乙丙丁戊己庚辛壬癸]?[子丑寅卯辰巳午未申酉戌亥]时/g, "").trim() || undefined;
        }
        if (typeof node === "string" &&
            /(刑|冲|合|会)/.test(node) &&
            /时/.test(node)) {
            return undefined;
        }
        return node;
    }
    const record = node;
    const next = {};
    for (const [key, value] of Object.entries(record)) {
        if (/(hour|shi|hourPillar|timePillar|时柱|时支|时干|时宫)/i.test(key)) {
            continue;
        }
        if (/(interaction|刑|冲|合|会)/i.test(key) && /时/.test(JSON.stringify(value))) {
            continue;
        }
        const pruned = pruneHourSensitive(value);
        if (pruned !== undefined) {
            next[key] = pruned;
        }
    }
    return next;
}
function stripHourLines(markdown) {
    let text = markdown;
    text = text.replace(/\n###\s*时柱[\s\S]*?(?=\n###|\n##|$)/g, "\n");
    text = text.replace(/\n##\s*刑冲合会[\s\S]*?(?=\n##|$)/g, "\n");
    const lines = text
        .split("\n")
        .map((line) => {
        if (/^\s*-\s*阳历：/.test(line)) {
            return line.replace(/(\d{4}年\d{1,2}月\d{1,2}日)\s+\d{1,2}:\d{2}:\d{2}/, "$1");
        }
        if (/^\s*-\s*农历：/.test(line)) {
            return "- 农历：（缺少出生时间，已省略时柱相关信息）";
        }
        if (/^\s*-\s*八字：/.test(line)) {
            const value = line.replace(/^\s*-\s*八字：\s*/, "");
            const pillars = value.split(/\s+/).filter(Boolean);
            if (pillars.length >= 4) {
                return `- 八字：${pillars.slice(0, 3).join(" ")}`;
            }
        }
        return line;
    })
        .filter((line) => !/(时柱|时支|时干|时宫)/.test(line))
        .filter((line) => !(/(刑|冲|合|会)/.test(line) && /时/.test(line)));
    return lines.join("\n").replace(/\n{3,}/g, "\n\n");
}
function sanitizeRawBaziForMissingTime(raw) {
    const pruned = pruneHourSensitive(raw);
    if (!pruned || typeof pruned !== "object" || Array.isArray(pruned)) {
        return pruned;
    }
    const record = { ...pruned };
    const solar = toStringValue(record["阳历"]);
    if (solar) {
        record["阳历"] = solar.replace(/\s+\d{1,2}:\d{2}:\d{2}/, "");
    }
    const lunar = toStringValue(record["农历"]);
    if (lunar) {
        record["农历"] = lunar
            .replace(/[甲乙丙丁戊己庚辛壬癸]?[子丑寅卯辰巳午未申酉戌亥]时/g, "")
            .replace(/\s+/g, " ")
            .trim();
    }
    const bazi = toStringValue(record["八字"]);
    if (bazi) {
        const pillars = bazi.split(/\s+/).filter(Boolean);
        if (pillars.length >= 4) {
            record["八字"] = pillars.slice(0, 3).join(" ");
        }
    }
    delete record["时柱"];
    return record;
}
function ensureStructuredPillars(raw, markdown) {
    const fromMarkdown = parsePillarsFromMarkdown(markdown);
    const yearFromRaw = normalizePillarValue(deepFindByKeys(raw, ["yearPillar", "year", "nianZhu", "年柱"]));
    const monthFromRaw = normalizePillarValue(deepFindByKeys(raw, ["monthPillar", "month", "yueZhu", "月柱"]));
    const dayFromRaw = normalizePillarValue(deepFindByKeys(raw, ["dayPillar", "day", "riZhu", "日柱"]));
    const hourFromRaw = normalizePillarValue(deepFindByKeys(raw, ["hourPillar", "hour", "shiZhu", "时柱"]));
    return {
        year: fromMarkdown.year ?? yearFromRaw,
        month: fromMarkdown.month ?? monthFromRaw,
        day: fromMarkdown.day ?? dayFromRaw,
        hour: fromMarkdown.hour ?? hourFromRaw,
    };
}
function normalizeFiveElements(raw) {
    return (deepFindByKeys(raw, ["fiveElements", "wuxing", "五行"]) ??
        "（暂未提取到结构化五行信息，可参考 raw_markdown）");
}
function normalizeTenGods(raw) {
    return (deepFindByKeys(raw, ["tenGods", "shiShen", "shishen", "十神"]) ??
        "（暂未提取到结构化十神信息，可参考 raw_markdown）");
}
function normalizeLuckCycles(raw) {
    return (deepFindByKeys(raw, ["daYun", "dayun", "luckCycles", "bigLuck", "大运"]) ??
        "（暂未提取到结构化大运信息，可参考 raw_markdown）");
}
function buildYearlyFortune(raw, pillars) {
    try {
        const baziRecord = raw;
        const baziStr = (baziRecord?.["八字"] ?? "").replaceAll(" ", "");
        if (!baziStr || baziStr.length < 8) {
            return "（八字信息不完整，无法计算流年）";
        }
        // Dynamic import would be async; we are in sync context here.
        // Build a structured summary from what we already have in raw.
        const year = new Date().getFullYear();
        const pillarArr = [pillars.year, pillars.month, pillars.day, pillars.hour].filter((p) => !!p);
        return {
            current_year: year,
            pillars: pillarArr,
            bazi: baziStr,
            note: "流年干支、神煞与刑冲合会由运行时 buildFlowSnapshotMarkdown 实时计算",
        };
    }
    catch {
        return "（流年信息计算失败，可参考 raw_markdown）";
    }
}
async function calcRawBazi(args) {
    let buildBaziFromSolar;
    let buildBaziFromLunar;
    let baziToMarkdown;
    try {
        const pkg = (await import("cantian-tymext"));
        buildBaziFromSolar = pkg.buildBaziFromSolar;
        buildBaziFromLunar = pkg.buildBaziFromLunar;
        baziToMarkdown = pkg.baziToMarkdown;
    }
    catch {
        throw new Error([
            "排盘依赖未就绪，当前无法完成八字计算。",
            "请先执行：npm install",
            "若仍失败，请确认 package.json 中 cantian-tymext 已成功安装。",
        ].join("\n"));
    }
    if (!buildBaziFromSolar || !buildBaziFromLunar || !baziToMarkdown) {
        throw new Error([
            "排盘模块加载失败。",
            "缺少 buildBaziFromSolar/buildBaziFromLunar/baziToMarkdown 导出。",
            "请确认 cantian-tymext 版本是否匹配。",
        ].join("\n"));
    }
    const mappedGender = args.gender === "male" ? 1 : 0;
    const raw = args.calendarType === "solar"
        ? buildBaziFromSolar({
            solarTime: args.isoTime,
            gender: mappedGender,
            sect: args.sect,
        })
        : buildBaziFromLunar({
            lunarTime: args.isoTime,
            gender: mappedGender,
            sect: args.sect,
        });
    const markdown = baziToMarkdown(raw);
    return { raw, markdown };
}
export async function buildChart(input) {
    validateDate(input.date);
    if (input.time) {
        validateTime(input.time);
    }
    const rawClockTime = input.time ?? "12:00";
    const dayRolloverHour = Number.isFinite(input.dayRolloverHour) ? Number(input.dayRolloverHour) : 23;
    const rolled = applyDayRollover(input.date, rawClockTime, dayRolloverHour);
    const longitude = resolveLongitude(input.location, input.longitude);
    const trueSolarMode = input.trueSolarMode ?? "auto";
    const shouldApplyTrueSolar = trueSolarMode === "on" || (trueSolarMode === "auto" && Number.isFinite(longitude));
    const trueSolarResult = shouldApplyTrueSolar
        ? applyTrueSolarTime(rolled.time, longitude)
        : { time: rolled.time };
    const effectiveDate = rolled.date;
    const effectiveTime = trueSolarResult.time;
    const accuracyMode = input.time
        ? "full_chart"
        : "missing_time_six_pillars";
    const rawResult = await calcRawBazi({
        calendarType: input.calendarType,
        isoTime: formatIso(effectiveDate, effectiveTime),
        gender: input.gender,
        sect: input.sect,
    });
    const pillars = ensureStructuredPillars(rawResult.raw, rawResult.markdown);
    const dayMaster = toStringValue(deepFindByKeys(rawResult.raw, [
        "dayMaster",
        "riGan",
        "dayStem",
        "日主",
        "日元",
    ])) ?? parseDayMasterFromMarkdown(rawResult.markdown);
    const chart = {
        schema_version: "1.0.0",
        generated_at: new Date().toISOString(),
        source_command: input.sourceCommand,
        accuracy_mode: accuracyMode,
        requires_birth_time_for_full_accuracy: !input.time,
        notes: [],
        birth_input: {
            name: input.name,
            date: effectiveDate,
            provided_time: input.time,
            clock_time: rawClockTime,
            true_solar_time: shouldApplyTrueSolar ? effectiveTime : undefined,
            time_correction_minutes: shouldApplyTrueSolar ? trueSolarResult.correctionMinutes : undefined,
            effective_time: effectiveTime,
            location: input.location,
            longitude,
            true_solar_mode: trueSolarMode,
            day_rollover_hour: dayRolloverHour,
            gender: input.gender,
            calendar_type: input.calendarType,
            sect: input.sect,
        },
        pillars,
        day_master: dayMaster,
        five_elements: normalizeFiveElements(rawResult.raw),
        ten_gods: normalizeTenGods(rawResult.raw),
        luck_cycles: normalizeLuckCycles(rawResult.raw),
        current_year: new Date().getFullYear(),
        yearly_fortune: buildYearlyFortune(rawResult.raw, pillars),
        removed_due_to_missing_time: [],
        raw_markdown: rawResult.markdown,
        raw_bazi: rawResult.raw,
    };
    if (!input.time) {
        chart.notes.push("未提供出生时间，已采用 12:00 进行内部计算，并按六柱精简模式输出。");
        chart.notes.push("补充出生时间后可重算为完整精度版本。");
        chart.removed_due_to_missing_time = [
            "时柱",
            "与时柱相关的刑冲合会",
            "时柱衍生关系",
        ];
        chart.pillars.hour = undefined;
        chart.raw_bazi = sanitizeRawBaziForMissingTime(chart.raw_bazi);
        chart.raw_markdown = stripHourLines(chart.raw_markdown);
        chart.five_elements = pruneHourSensitive(chart.five_elements);
        chart.ten_gods = pruneHourSensitive(chart.ten_gods);
        chart.luck_cycles = pruneHourSensitive(chart.luck_cycles);
        chart.yearly_fortune = pruneHourSensitive(chart.yearly_fortune);
    }
    return chart;
}
async function main() {
    const args = parseCliArgs(process.argv);
    ensureRequired(args, ["name", "location", "gender"]);
    const rawDate = args.date ?? args["birth-date"] ?? args.birth ?? "";
    if (!rawDate) {
        throw new Error([
            "缺少出生日期。",
            "请提供 date（支持自然格式）。",
            "示例：--date \"1996年8月12日\"",
        ].join("\n"));
    }
    const date = normalizeDateInput(rawDate);
    const time = args.time ? normalizeTimeInput(args.time) : undefined;
    validateDate(date);
    if (time) {
        validateTime(time);
    }
    const genderArg = args.gender.toLowerCase();
    const gender = genderArg === "male" || genderArg === "男"
        ? "male"
        : genderArg === "female" || genderArg === "女"
            ? "female"
            : (() => {
                throw new Error([
                    "性别参数无效。",
                    "请使用 male/female（或 男/女）。",
                    "示例：--gender male",
                ].join("\n"));
            })();
    const chart = await buildChart({
        name: args.name,
        date,
        time,
        location: args.location,
        gender,
        calendarType: args.calendar === "lunar" || args.calendar === "农历" ? "lunar" : "solar",
        sect: args.sect === "1" ? 1 : 2,
        sourceCommand: args.command === "/bazi-persona update"
            ? "/bazi-persona update"
            : "/bazi-persona create",
        trueSolarMode: args["true-solar"] === "on" || args["true-solar"] === "off"
            ? args["true-solar"]
            : "auto",
        longitude: args.longitude ? Number.parseFloat(args.longitude) : undefined,
        dayRolloverHour: args["day-rollover"] ? Number.parseInt(args["day-rollover"], 10) : 23,
    });
    const output = args.output;
    if (output) {
        writeJson(output, chart);
        process.stdout.write(`已生成 chart.json：${output}\n`);
        return;
    }
    process.stdout.write(`${JSON.stringify(chart, null, 2)}\n`);
}
if (import.meta.url === `file://${process.argv[1]}`) {
    main().catch((error) => {
        const fallback = safeJsonParse(JSON.stringify(error), {});
        const message = error instanceof Error
            ? error.message
            : fallback.message ?? "未知错误，请稍后重试。";
        process.stderr.write(`${message}\n`);
        process.exitCode = 1;
    });
}


path: dist/tools/calendar/query.d.ts
content: export interface ChineseCalendarData {
    公历: string;
    农历: string;
    干支日期: string;
    生肖: string;
    纳音: string;
    农历节日?: string;
    公历节日?: string;
    节气: {
        term: string;
        afterDays: number;
        nextTerm?: string;
        beforeNextTermDays?: number;
    };
    二十八宿: string;
    彭祖百忌: string;
    喜神方位: string;
    阳贵神方位: string;
    阴贵神方位: string;
    福神方位: string;
    财神方位: string;
    冲煞: string;
    宜: string;
    忌: string;
}
export interface ParsedDateTimeInput {
    year: number;
    month: number;
    day: number;
    hour: number;
    minute: number;
    second: number;
    display: string;
}
export declare function parseDateTimeInput(input?: string): ParsedDateTimeInput;
export declare function queryChineseCalendar(params: {
    year: number;
    month: number;
    day: number;
}): Promise<ChineseCalendarData | undefined>;


path: dist/tools/calendar/query.js
content: import { normalizeDateInput, normalizeTimeInput, validateDate, validateTime, } from "../bazi/calc.js";
export function parseDateTimeInput(input) {
    if (!input || !input.trim()) {
        const now = new Date();
        return {
            year: now.getFullYear(),
            month: now.getMonth() + 1,
            day: now.getDate(),
            hour: now.getHours(),
            minute: now.getMinutes(),
            second: now.getSeconds(),
            display: `${now.getFullYear()}-${`${now.getMonth() + 1}`.padStart(2, "0")}-${`${now.getDate()}`.padStart(2, "0")} ${`${now.getHours()}`.padStart(2, "0")}:${`${now.getMinutes()}`.padStart(2, "0")}:${`${now.getSeconds()}`.padStart(2, "0")}`,
        };
    }
    const raw = input.trim();
    if (raw.includes("今天")) {
        return parseDateTimeInput();
    }
    if (raw.includes("明天")) {
        const next = new Date();
        next.setDate(next.getDate() + 1);
        return parseDateTimeInput(`${next.getFullYear()}-${`${next.getMonth() + 1}`.padStart(2, "0")}-${`${next.getDate()}`.padStart(2, "0")}`);
    }
    if (raw.includes("后天")) {
        const next = new Date();
        next.setDate(next.getDate() + 2);
        return parseDateTimeInput(`${next.getFullYear()}-${`${next.getMonth() + 1}`.padStart(2, "0")}-${`${next.getDate()}`.padStart(2, "0")}`);
    }
    if (raw.includes("昨天")) {
        const prev = new Date();
        prev.setDate(prev.getDate() - 1);
        return parseDateTimeInput(`${prev.getFullYear()}-${`${prev.getMonth() + 1}`.padStart(2, "0")}-${`${prev.getDate()}`.padStart(2, "0")}`);
    }
    const directDate = new Date(raw.includes("T") ? raw : raw.replace(" ", "T"));
    if (!Number.isNaN(directDate.getTime())) {
        return {
            year: directDate.getFullYear(),
            month: directDate.getMonth() + 1,
            day: directDate.getDate(),
            hour: directDate.getHours(),
            minute: directDate.getMinutes(),
            second: directDate.getSeconds(),
            display: `${directDate.getFullYear()}-${`${directDate.getMonth() + 1}`.padStart(2, "0")}-${`${directDate.getDate()}`.padStart(2, "0")} ${`${directDate.getHours()}`.padStart(2, "0")}:${`${directDate.getMinutes()}`.padStart(2, "0")}:${`${directDate.getSeconds()}`.padStart(2, "0")}`,
        };
    }
    const [datePart, timePart] = raw.split(/\s+/, 2);
    const date = normalizeDateInput(datePart);
    validateDate(date);
    const time = timePart ? normalizeTimeInput(timePart) : "12:00";
    validateTime(time);
    const [year, month, day] = date.split("-").map((x) => Number.parseInt(x, 10));
    const [hour, minute] = time.split(":").map((x) => Number.parseInt(x, 10));
    return {
        year,
        month,
        day,
        hour,
        minute,
        second: 0,
        display: `${date} ${time}:00`,
    };
}
export async function queryChineseCalendar(params) {
    try {
        const { getChineseCalendar } = (await import("cantian-tymext"));
        return getChineseCalendar({
            year: params.year,
            month: params.month,
            day: params.day,
        });
    }
    catch {
        return undefined;
    }
}


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)