muninn-cli 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- muninn_cli-0.1.1/.agent/api.md +193 -0
- muninn_cli-0.1.1/.agent/plan.md +124 -0
- muninn_cli-0.1.1/.github/ISSUE_TEMPLATE/bug_report.md +41 -0
- muninn_cli-0.1.1/.github/ISSUE_TEMPLATE/feature_request.md +26 -0
- muninn_cli-0.1.1/.github/workflows/ci.yml +57 -0
- muninn_cli-0.1.1/.github/workflows/publish.yml +31 -0
- muninn_cli-0.1.1/.gitignore +218 -0
- muninn_cli-0.1.1/.python-version +1 -0
- muninn_cli-0.1.1/.vscode/settings.json +5 -0
- muninn_cli-0.1.1/LICENSE +21 -0
- muninn_cli-0.1.1/PKG-INFO +196 -0
- muninn_cli-0.1.1/README.md +176 -0
- muninn_cli-0.1.1/README_zh.md +174 -0
- muninn_cli-0.1.1/example/chemistry/elements.json +38 -0
- muninn_cli-0.1.1/example/chemistry/manifest.json +7 -0
- muninn_cli-0.1.1/example/chemistry/plugin.py +53 -0
- muninn_cli-0.1.1/example/flashcard/manifest.json +7 -0
- muninn_cli-0.1.1/example/flashcard/plugin.py +5 -0
- muninn_cli-0.1.1/example/flashcard/words.csv +11 -0
- muninn_cli-0.1.1/main.py +6 -0
- muninn_cli-0.1.1/pyproject.toml +45 -0
- muninn_cli-0.1.1/scripts/bump_version.py +109 -0
- muninn_cli-0.1.1/src/__init__.py +0 -0
- muninn_cli-0.1.1/src/cli/__init__.py +0 -0
- muninn_cli-0.1.1/src/cli/manager.py +161 -0
- muninn_cli-0.1.1/src/cli/runner.py +121 -0
- muninn_cli-0.1.1/src/core/__init__.py +0 -0
- muninn_cli-0.1.1/src/core/base_plugin.py +32 -0
- muninn_cli-0.1.1/src/core/helpers.py +205 -0
- muninn_cli-0.1.1/src/core/scheduler.py +44 -0
- muninn_cli-0.1.1/src/core/state.py +62 -0
- muninn_cli-0.1.1/src/main.py +91 -0
- muninn_cli-0.1.1/src/ui.py +47 -0
- muninn_cli-0.1.1/tests/__init__.py +0 -0
- muninn_cli-0.1.1/tests/cli/__init__.py +0 -0
- muninn_cli-0.1.1/tests/cli/test_manager.py +182 -0
- muninn_cli-0.1.1/tests/conftest.py +92 -0
- muninn_cli-0.1.1/tests/core/__init__.py +0 -0
- muninn_cli-0.1.1/tests/core/test_helpers.py +280 -0
- muninn_cli-0.1.1/tests/core/test_scheduler.py +60 -0
- muninn_cli-0.1.1/tests/core/test_state.py +74 -0
- muninn_cli-0.1.1/uv.lock +108 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# Muninn API 扩展规划 - 面向插件开发者的辅助层
|
|
2
|
+
|
|
3
|
+
## 背景:当前问题
|
|
4
|
+
|
|
5
|
+
以 `example/chemistry/plugin.py` 为例,开发者目前需要:
|
|
6
|
+
|
|
7
|
+
1. 手动维护一个 `self.problems` 字典,每个 "方向" 都需要完整填写 `type`, `element`, `statement`, `expected` 字段
|
|
8
|
+
2. 在 `check_answer` 中用 `if q_type == ...` 分支处理不同题型的判题逻辑
|
|
9
|
+
3. 自己实现所有容错匹配(忽略大小写、空格、输入顺序等)
|
|
10
|
+
4. 即使是最简单的正反面闪卡,也要实现全部 5 个抽象方法
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 目标:降低插件开发者的心智负担
|
|
15
|
+
|
|
16
|
+
Muninn 的 `src/core/` 层新增一组 **辅助工具 (Helpers)**,让开发者专注于"数据"和"语义",而不是"胶水代码"。
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 1. `QuestionType` —— 可复用的题型单元
|
|
21
|
+
|
|
22
|
+
### 设计思路
|
|
23
|
+
|
|
24
|
+
把"一种问答方向"抽象成一个独立的 `QuestionType` 对象。它封装了三件事:
|
|
25
|
+
|
|
26
|
+
- 如何**渲染**题目(给定数据项,生成题目文本)
|
|
27
|
+
- 如何**渲染**预期答案(给定数据项,生成标准答案文本)
|
|
28
|
+
- 如何**判断**用户输入是否正确(使用内置的 Matcher,详见第 2 节)
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
# 插件开发者的代码
|
|
32
|
+
from core.helpers import QuestionType, Matchers
|
|
33
|
+
|
|
34
|
+
# 把一种"问答方向"声明为一行
|
|
35
|
+
q_num_to_element = QuestionType(
|
|
36
|
+
label="看序号背元素",
|
|
37
|
+
statement=lambda el: f"原子序数: {el['num']}",
|
|
38
|
+
answer=lambda el: f"{el['name']} {el['sym']}",
|
|
39
|
+
matcher=Matchers.chinese_symbol_pair("name", "sym"), # 内置容错匹配
|
|
40
|
+
)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 注册到插件
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
class Plugin(BaseRecitePlugin):
|
|
47
|
+
QUESTION_TYPES = [
|
|
48
|
+
QuestionType(
|
|
49
|
+
label="看序号背元素",
|
|
50
|
+
statement=lambda el: f"原子序数: {el['num']}",
|
|
51
|
+
answer=lambda el: f"{el['name']} {el['sym']}",
|
|
52
|
+
matcher=Matchers.chinese_symbol_pair("name", "sym"),
|
|
53
|
+
),
|
|
54
|
+
QuestionType(
|
|
55
|
+
label="看元素背序号",
|
|
56
|
+
statement=lambda el: f"元素: {el['name']} ({el['sym']})",
|
|
57
|
+
answer=lambda el: str(el["num"]),
|
|
58
|
+
matcher=Matchers.exact_integer("num"),
|
|
59
|
+
),
|
|
60
|
+
]
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## 2. `Matchers` —— 内置容错匹配器
|
|
66
|
+
|
|
67
|
+
常见的输入容错逻辑不应该由每个插件重复实现。Muninn 提供一组开箱即用的 Matcher 工厂函数:
|
|
68
|
+
|
|
69
|
+
| Matcher | 说明 | 示例 |
|
|
70
|
+
| ------------------------------------------ | ------------------------------------------------ | ------------------------- |
|
|
71
|
+
| `Matchers.exact(key)` | 精确匹配某字段(去除首尾空格) | 完全相同的字符串 |
|
|
72
|
+
| `Matchers.exact_integer(key)` | 只提取数字后精确匹配 | `" 17 "` / `"#17"` → `17` |
|
|
73
|
+
| `Matchers.case_insensitive(key)` | 忽略大小写匹配 | 英文单词背诵 |
|
|
74
|
+
| `Matchers.chinese_symbol_pair(key1, key2)` | 匹配"中文+符号"或"符号+中文"任意顺序,忽略空格 | `氢H` / `H氢` / `氢 H` |
|
|
75
|
+
| `Matchers.any_order(*keys)` | 多个字段任意顺序输入,忽略分隔符 | 周期+族 `4 IVB` / `IVB4` |
|
|
76
|
+
| `Matchers.custom(fn)` | 传入自定义函数 `(data_item, user_input) -> bool` | 完全自由扩展 |
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## 3. `DataPlugin` —— 面向"实体+多题型"场景的高阶基类
|
|
81
|
+
|
|
82
|
+
### 设计思路
|
|
83
|
+
|
|
84
|
+
当一组 `QuestionType` 需要应用到一批数据上("每条数据 × 每种题型 = 一道题"),这个模式极其常见。`DataPlugin` 是 `BaseRecitePlugin` 的子类,它自动完成 ID 生成、问题注册和方法路由,开发者只需提供:
|
|
85
|
+
|
|
86
|
+
1. 数据加载逻辑 `load_records()` → 返回记录列表
|
|
87
|
+
2. 题型列表 `QUESTION_TYPES`
|
|
88
|
+
3. 可选的过滤器 `filter(record, q_type)` → 某条数据是否参与某种题型
|
|
89
|
+
|
|
90
|
+
### 改写后的 chemistry plugin(从 87 行缩减到 ~40 行)
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
import os
|
|
94
|
+
import json
|
|
95
|
+
from core.helpers import DataPlugin, QuestionType, Matchers
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class Plugin(DataPlugin):
|
|
99
|
+
QUESTION_TYPES = [
|
|
100
|
+
QuestionType(
|
|
101
|
+
label="看序号背元素",
|
|
102
|
+
statement=lambda el: f"原子序数: {el['num']}",
|
|
103
|
+
answer=lambda el: f"{el['name']} {el['sym']}",
|
|
104
|
+
matcher=Matchers.chinese_symbol_pair("name", "sym"),
|
|
105
|
+
),
|
|
106
|
+
QuestionType(
|
|
107
|
+
label="看元素背序号",
|
|
108
|
+
statement=lambda el: f"元素: {el['name']} ({el['sym']})",
|
|
109
|
+
answer=lambda el: str(el["num"]),
|
|
110
|
+
matcher=Matchers.exact_integer("num"),
|
|
111
|
+
),
|
|
112
|
+
QuestionType(
|
|
113
|
+
label="看位置背元素",
|
|
114
|
+
statement=lambda el: f"位置: 第{el['period']}周期 {el['group']}族",
|
|
115
|
+
answer=lambda el: f"{el['name']} {el['sym']}",
|
|
116
|
+
matcher=Matchers.chinese_symbol_pair("name", "sym"),
|
|
117
|
+
),
|
|
118
|
+
QuestionType(
|
|
119
|
+
label="看元素背位置",
|
|
120
|
+
statement=lambda el: f"元素: {el['name']} ({el['sym']})",
|
|
121
|
+
answer=lambda el: f"{el['period']} {el['group']}",
|
|
122
|
+
matcher=Matchers.any_order("period", "group"),
|
|
123
|
+
),
|
|
124
|
+
]
|
|
125
|
+
|
|
126
|
+
def load_records(self) -> list:
|
|
127
|
+
with open(
|
|
128
|
+
os.path.join(self.workspace_dir, "elements.json"), encoding="utf-8"
|
|
129
|
+
) as f:
|
|
130
|
+
return json.load(f)
|
|
131
|
+
|
|
132
|
+
def filter(self, record: dict, q_type: QuestionType) -> bool:
|
|
133
|
+
"""过滤掉 0 族元素的位置类题目"""
|
|
134
|
+
if q_type.label in ("看位置背元素", "看元素背位置"):
|
|
135
|
+
return record["group"] != "0"
|
|
136
|
+
return True
|
|
137
|
+
|
|
138
|
+
def get_expand_info(self, problem_id: str) -> str:
|
|
139
|
+
el, _ = self._resolve(problem_id)
|
|
140
|
+
return f"{el['eng']} ({el['sym']}, {el['name']}) - 第{el['period']}周期 {el['group']}族"
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## 4. `FlashcardPlugin` —— 最简单的闪卡场景
|
|
146
|
+
|
|
147
|
+
对于纯粹的"正面→背面"记忆(如 GRE 单词、古诗词),提供一个更高阶的 `FlashcardPlugin`,实现 0 样板代码的目标:
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
# flashcard 格式 CSV / JSON:每条数据有 front, back 两个字段即可
|
|
151
|
+
class Plugin(FlashcardPlugin):
|
|
152
|
+
DATA_FILE = "words.csv"
|
|
153
|
+
|
|
154
|
+
# FlashcardPlugin 自动实现 load_records, get_all_problem_ids,
|
|
155
|
+
# render_statement, check_answer, get_expected_display
|
|
156
|
+
# 开发者完全不需要写任何一行额外代码(除非需要定制)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## 5. 实现计划
|
|
162
|
+
|
|
163
|
+
### Phase 1:`Matchers` + `QuestionType`
|
|
164
|
+
|
|
165
|
+
- 在 `src/core/helpers.py` 中实现 `Matchers` 工具类和 `QuestionType` 数据类
|
|
166
|
+
- 无需修改 `BaseRecitePlugin`,完全向后兼容
|
|
167
|
+
|
|
168
|
+
### Phase 2:`DataPlugin`
|
|
169
|
+
|
|
170
|
+
- 在 `src/core/helpers.py` 中实现 `DataPlugin(BaseRecitePlugin)` 子类
|
|
171
|
+
- 将 `example/chemistry/plugin.py` 改写为 `DataPlugin` 版本作为验证
|
|
172
|
+
|
|
173
|
+
### Phase 3:`FlashcardPlugin`
|
|
174
|
+
|
|
175
|
+
- 支持从 CSV/JSON 自动加载 `front/back` 格式数据
|
|
176
|
+
- 提供一个示例闪卡包(如 GRE 单词)验证
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## 附:改动涉及文件
|
|
181
|
+
|
|
182
|
+
```text
|
|
183
|
+
src/
|
|
184
|
+
└── core/
|
|
185
|
+
├── base_plugin.py # 不变
|
|
186
|
+
├── helpers.py # ✅ 新增:QuestionType, Matchers, DataPlugin, FlashcardPlugin
|
|
187
|
+
├── scheduler.py # 不变
|
|
188
|
+
└── state.py # 不变
|
|
189
|
+
|
|
190
|
+
example/
|
|
191
|
+
├── chemistry/plugin.py # ✅ 改写为 DataPlugin 版本(用于验证)
|
|
192
|
+
└── flashcard/ # ✅ 新增:FlashcardPlugin 示例包
|
|
193
|
+
```
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Muninn - 架构设计与项目规划
|
|
2
|
+
|
|
3
|
+
## 1. 核心设计理念:宿主与插件架构
|
|
4
|
+
|
|
5
|
+
本项目旨在将“背诵软件”解耦为一个 **“宿主容器 (Host)”** 和若干 **“背诵包插件 (Plugins)”**。
|
|
6
|
+
|
|
7
|
+
- **宿主 (CLI Framework)**:负责题目的调度算法(如按权重出题)、界面渲染、用户输入捕获、题库包的管理(导入/导出)以及进度的持久化存储。
|
|
8
|
+
- **插件 (Recite Pack)**:负责定义具体的题目逻辑,包含静态数据(如元素周期表、古诗文内容)和出题/判题的 Python 脚本。
|
|
9
|
+
|
|
10
|
+
这种架构能让用户自由开发、导入和导出第三方题库包,极大地提高了可扩展性。
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 2. “背诵包”的规范定义 (Package Specification)
|
|
15
|
+
|
|
16
|
+
一个标准的背诵包在物理上是一个独立的文件夹,分发时可打包为 `.zip`,结构如下:
|
|
17
|
+
|
|
18
|
+
```text
|
|
19
|
+
chemistry/
|
|
20
|
+
├── manifest.json # 包的元数据 (包名、包ID、作者、版本、描述、当前版本哈希)
|
|
21
|
+
├── data.csv # 静态数据 (也可以是 json, yaml, sqlite 或媒体资源)
|
|
22
|
+
└── plugin.py # 核心逻辑脚本 (必须实现 CLI 规定的标准接口)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 3. 标准化 API 契约 (Plugin API Contract)
|
|
28
|
+
|
|
29
|
+
所有导入的 `plugin.py` 必须提供一个继承自 `BaseRecitePlugin` 的主类。CLI 会在初始化时将专用的工作区路径 (`workspace_dir`) 传给该插件,插件需从该路径加载自己的静态数据。
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
class BaseRecitePlugin:
|
|
33
|
+
def __init__(self, workspace_dir: str):
|
|
34
|
+
"""
|
|
35
|
+
初始化插件
|
|
36
|
+
:param workspace_dir: CLI 分配给该包的私有静态数据目录
|
|
37
|
+
"""
|
|
38
|
+
self.workspace_dir = workspace_dir
|
|
39
|
+
self.load_data()
|
|
40
|
+
|
|
41
|
+
def load_data(self):
|
|
42
|
+
"""加载 workspace_dir 中的静态数据,由子类实现"""
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
def get_all_problem_ids(self) -> list[str]:
|
|
46
|
+
"""返回题库中所有题目的唯一 ID,供 CLI 调度器建立索引和存档进度"""
|
|
47
|
+
raise NotImplementedError
|
|
48
|
+
|
|
49
|
+
def render_statement(self, problem_id: str) -> str:
|
|
50
|
+
"""根据题目 ID 返回要在终端显示的问题文本"""
|
|
51
|
+
raise NotImplementedError
|
|
52
|
+
|
|
53
|
+
def check_answer(self, problem_id: str, user_input: str) -> bool:
|
|
54
|
+
"""回调函数:判断用户输入是否正确"""
|
|
55
|
+
raise NotImplementedError
|
|
56
|
+
|
|
57
|
+
def get_expected_display(self, problem_id: str) -> str:
|
|
58
|
+
"""回答错误时,展示给用户的标准答案"""
|
|
59
|
+
raise NotImplementedError
|
|
60
|
+
|
|
61
|
+
def get_expand_info(self, problem_id: str) -> str:
|
|
62
|
+
"""回答正确时,展示给用户的拓展/提示信息 (可选)"""
|
|
63
|
+
return ""
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 4. CLI 宿主的四大职责 (CLI Core Responsibilities)
|
|
69
|
+
|
|
70
|
+
1. **包管理器 (Package Manager)**
|
|
71
|
+
- `new`: 在当前工作目录生成一个标准的背诵包插件模板(包含 `manifest.json`, 示例 `plugin.py` 和数据文件),方便用户快速开始自己的开发。
|
|
72
|
+
- `import`: 接收一个本地的文件夹路径或 `.zip` 压缩包,将其拷贝/解压至 `~/.muninn/packs/<pack_id>/`,并校验完整性。如果本地已经有该包的旧版本,应该覆盖为新版本。
|
|
73
|
+
- `loader`: 使用 `importlib` 动态加载包内的 `plugin.py`。
|
|
74
|
+
2. **状态与存储管理器 (State Manager)**
|
|
75
|
+
- **进度隔离**:进度的存储(答对次数、耗时)绝对不能放在包的 `workspace_dir` 中,以防止导出时泄露私人数据。
|
|
76
|
+
- 所有进度集中存放在 `~/.muninn/states/<pack_id>.db`。
|
|
77
|
+
3. **全局调度算法 (Global Scheduler)**
|
|
78
|
+
- 提取自当前的智能权重逻辑。接管出题顺序,在内存中维护优先队列。
|
|
79
|
+
4. **统一终端 UI (Terminal UI)**
|
|
80
|
+
- 负责 ANSI 颜色输出、清屏操作、数据统计的 banner 显示及捕捉用户输入。
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## 5. 工作流示例 (Workflow)
|
|
85
|
+
|
|
86
|
+
当用户运行命令 `muninn run my_chemistry_pack` 时:
|
|
87
|
+
|
|
88
|
+
1. **CLI 启动**:定位到 `~/.muninn/packs/my_chemistry_pack/`。
|
|
89
|
+
2. **实例化插件**:初始化插件并传入 `workspace_dir`,插件完成数据加载。
|
|
90
|
+
3. **初始化调度**:CLI 调用 `get_all_problem_ids()`,并从 State Manager 读取每个题目的权重,压入堆中。
|
|
91
|
+
4. **游戏循环**:
|
|
92
|
+
- 调度器弹出一道题的 ID。
|
|
93
|
+
- CLI 调用 `render_statement(ID)` 打印题目。
|
|
94
|
+
- CLI 截获用户输入 `user_input`。
|
|
95
|
+
- CLI 将其传给插件:`check_answer(ID, user_input)`。
|
|
96
|
+
- 得到 True/False 后,CLI 更新权重并保存状态,打印拓展信息或标准答案。
|
|
97
|
+
- 循环往复。
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## 6. 项目系统目录架构
|
|
102
|
+
|
|
103
|
+
```text
|
|
104
|
+
muninn/ # 源码库目录
|
|
105
|
+
├── src/ # 核心源码
|
|
106
|
+
│ ├── core/
|
|
107
|
+
│ │ ├── base_plugin.py # 插件基类 API 契约
|
|
108
|
+
│ │ ├── scheduler.py # 权重与出题队列调度算法
|
|
109
|
+
│ │ └── state.py # 学习进度持久化管理
|
|
110
|
+
│ ├── cli/
|
|
111
|
+
│ │ ├── manager.py # New/Import 包管理模块
|
|
112
|
+
│ │ └── runner.py # 核心游戏循环 (整合 UI, Plugin, Scheduler)
|
|
113
|
+
│ └── ui.py # 终端渲染与颜色控制
|
|
114
|
+
└── pyproject.toml # 使用 uv 管理依赖,打包配置,注册全局命令 'muninn'
|
|
115
|
+
|
|
116
|
+
~/.muninn/ # App Data 目录 (程序运行时在用户系统生成)
|
|
117
|
+
├── packs/ # 统一存放通过 import 导入的第三方题库包
|
|
118
|
+
│ └── chemistry/ # 具体的包目录 (被当作 workspace_dir)
|
|
119
|
+
│ ├── manifest.json
|
|
120
|
+
│ ├── data.csv
|
|
121
|
+
│ └── plugin.py
|
|
122
|
+
└── states/ # 进度隔离存储
|
|
123
|
+
└── chemistry.db # 记录该题库中所有题目的熟练度和学习数据
|
|
124
|
+
```
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: "\U0001F41E Bug Report"
|
|
3
|
+
about: "报告一个问题 / Report a bug"
|
|
4
|
+
labels: ["bug"]
|
|
5
|
+
assignees: ["a1fredbao"]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 🐛 问题描述 / Bug Description
|
|
9
|
+
|
|
10
|
+
<!-- 简要描述你遇到的问题 / Briefly describe the bug -->
|
|
11
|
+
|
|
12
|
+
## 📋 复现步骤 / Steps to Reproduce
|
|
13
|
+
|
|
14
|
+
1.
|
|
15
|
+
2.
|
|
16
|
+
3.
|
|
17
|
+
|
|
18
|
+
## 🤔 期望行为 / Expected Behavior
|
|
19
|
+
|
|
20
|
+
<!-- 描述你期望看到的正确行为 / What did you expect to happen? -->
|
|
21
|
+
|
|
22
|
+
## 😱 实际行为 / Actual Behavior
|
|
23
|
+
|
|
24
|
+
<!-- 描述实际发生了什么 / What actually happened? -->
|
|
25
|
+
|
|
26
|
+
## 🖥️ 环境信息 / Environment
|
|
27
|
+
|
|
28
|
+
- **OS**:
|
|
29
|
+
- **Python version**: <!-- `python --version` -->
|
|
30
|
+
- **Muninn version**: <!-- `muninn --version` or `pip show muninn` -->
|
|
31
|
+
- **Pack ID** (if applicable):
|
|
32
|
+
|
|
33
|
+
## 📎 错误日志 / Error Log
|
|
34
|
+
|
|
35
|
+
```text
|
|
36
|
+
粘贴错误信息 / Paste error output here
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## 📝 补充信息 / Additional Context
|
|
40
|
+
|
|
41
|
+
<!-- 其他你认为相关的信息 / Any other context about the problem -->
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: "\U0001F680 Feature Request"
|
|
3
|
+
about: "建议一个新功能 / Suggest a new feature or improvement"
|
|
4
|
+
labels: ["enhancement"]
|
|
5
|
+
assignees: ["a1fredbao"]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 🚀 功能描述 / Feature Description
|
|
9
|
+
|
|
10
|
+
<!-- 简要描述你希望增加的功能 / Briefly describe the feature you'd like -->
|
|
11
|
+
|
|
12
|
+
## 💡 动机与背景 / Motivation & Context
|
|
13
|
+
|
|
14
|
+
<!-- 为什么需要这个功能?它解决了什么问题? / Why is this feature needed? What problem does it solve? -->
|
|
15
|
+
|
|
16
|
+
## 🎯 建议方案 / Proposed Solution
|
|
17
|
+
|
|
18
|
+
<!-- 如果你有实现思路,请描述 / If you have ideas on how to implement this, describe them -->
|
|
19
|
+
|
|
20
|
+
## 🔄 替代方案 / Alternatives Considered
|
|
21
|
+
|
|
22
|
+
<!-- 你是否考虑过其他解决方案? / Have you considered any alternative solutions? -->
|
|
23
|
+
|
|
24
|
+
## 📝 补充信息 / Additional Context
|
|
25
|
+
|
|
26
|
+
<!-- 截图、草图、参考链接等 / Screenshots, mockups, reference links, etc. -->
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: ["**"]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: ["**"]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
lint:
|
|
11
|
+
name: Lint
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- name: Checkout code
|
|
15
|
+
uses: actions/checkout@v7
|
|
16
|
+
|
|
17
|
+
- name: Install uv
|
|
18
|
+
uses: astral-sh/setup-uv@v9.0.0
|
|
19
|
+
with:
|
|
20
|
+
version: "latest"
|
|
21
|
+
|
|
22
|
+
- name: Set up Python
|
|
23
|
+
run: uv python install
|
|
24
|
+
|
|
25
|
+
- name: Install dependencies
|
|
26
|
+
run: uv sync
|
|
27
|
+
|
|
28
|
+
- name: Run Ruff (linter)
|
|
29
|
+
run: uv run ruff check src/
|
|
30
|
+
|
|
31
|
+
- name: Run Ruff (formatter check)
|
|
32
|
+
run: uv run ruff format --check src/
|
|
33
|
+
|
|
34
|
+
test:
|
|
35
|
+
name: Test (${{ matrix.os }})
|
|
36
|
+
runs-on: ${{ matrix.os }}
|
|
37
|
+
needs: lint
|
|
38
|
+
strategy:
|
|
39
|
+
matrix:
|
|
40
|
+
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
41
|
+
steps:
|
|
42
|
+
- name: Checkout code
|
|
43
|
+
uses: actions/checkout@v7
|
|
44
|
+
|
|
45
|
+
- name: Install uv
|
|
46
|
+
uses: astral-sh/setup-uv@v9.0.0
|
|
47
|
+
with:
|
|
48
|
+
version: "latest"
|
|
49
|
+
|
|
50
|
+
- name: Set up Python
|
|
51
|
+
run: uv python install
|
|
52
|
+
|
|
53
|
+
- name: Install dependencies
|
|
54
|
+
run: uv sync
|
|
55
|
+
|
|
56
|
+
- name: Run tests
|
|
57
|
+
run: uv run pytest tests/ -v
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
name: Build and Publish
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
environment: pypi
|
|
12
|
+
permissions:
|
|
13
|
+
id-token: write
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
- name: Checkout code
|
|
17
|
+
uses: actions/checkout@v7
|
|
18
|
+
|
|
19
|
+
- name: Install uv
|
|
20
|
+
uses: astral-sh/setup-uv@v9.0.0
|
|
21
|
+
with:
|
|
22
|
+
version: "latest"
|
|
23
|
+
|
|
24
|
+
- name: Set up Python
|
|
25
|
+
run: uv python install
|
|
26
|
+
|
|
27
|
+
- name: Build package
|
|
28
|
+
run: uv build
|
|
29
|
+
|
|
30
|
+
- name: Publish to PyPI
|
|
31
|
+
run: uv publish
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[codz]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py.cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
# Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# UV
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
# uv.lock
|
|
102
|
+
|
|
103
|
+
# poetry
|
|
104
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
105
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
106
|
+
# commonly ignored for libraries.
|
|
107
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
108
|
+
# poetry.lock
|
|
109
|
+
# poetry.toml
|
|
110
|
+
|
|
111
|
+
# pdm
|
|
112
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
113
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
114
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
115
|
+
# pdm.lock
|
|
116
|
+
# pdm.toml
|
|
117
|
+
.pdm-python
|
|
118
|
+
.pdm-build/
|
|
119
|
+
|
|
120
|
+
# pixi
|
|
121
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
122
|
+
# pixi.lock
|
|
123
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
124
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
125
|
+
.pixi
|
|
126
|
+
|
|
127
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
128
|
+
__pypackages__/
|
|
129
|
+
|
|
130
|
+
# Celery stuff
|
|
131
|
+
celerybeat-schedule
|
|
132
|
+
celerybeat.pid
|
|
133
|
+
|
|
134
|
+
# Redis
|
|
135
|
+
*.rdb
|
|
136
|
+
*.aof
|
|
137
|
+
*.pid
|
|
138
|
+
|
|
139
|
+
# RabbitMQ
|
|
140
|
+
mnesia/
|
|
141
|
+
rabbitmq/
|
|
142
|
+
rabbitmq-data/
|
|
143
|
+
|
|
144
|
+
# ActiveMQ
|
|
145
|
+
activemq-data/
|
|
146
|
+
|
|
147
|
+
# SageMath parsed files
|
|
148
|
+
*.sage.py
|
|
149
|
+
|
|
150
|
+
# Environments
|
|
151
|
+
.env
|
|
152
|
+
.envrc
|
|
153
|
+
.venv
|
|
154
|
+
env/
|
|
155
|
+
venv/
|
|
156
|
+
ENV/
|
|
157
|
+
env.bak/
|
|
158
|
+
venv.bak/
|
|
159
|
+
|
|
160
|
+
# Spyder project settings
|
|
161
|
+
.spyderproject
|
|
162
|
+
.spyproject
|
|
163
|
+
|
|
164
|
+
# Rope project settings
|
|
165
|
+
.ropeproject
|
|
166
|
+
|
|
167
|
+
# mkdocs documentation
|
|
168
|
+
/site
|
|
169
|
+
|
|
170
|
+
# mypy
|
|
171
|
+
.mypy_cache/
|
|
172
|
+
.dmypy.json
|
|
173
|
+
dmypy.json
|
|
174
|
+
|
|
175
|
+
# Pyre type checker
|
|
176
|
+
.pyre/
|
|
177
|
+
|
|
178
|
+
# pytype static type analyzer
|
|
179
|
+
.pytype/
|
|
180
|
+
|
|
181
|
+
# Cython debug symbols
|
|
182
|
+
cython_debug/
|
|
183
|
+
|
|
184
|
+
# PyCharm
|
|
185
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
186
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
187
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
188
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
189
|
+
# .idea/
|
|
190
|
+
|
|
191
|
+
# Abstra
|
|
192
|
+
# Abstra is an AI-powered process automation framework.
|
|
193
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
194
|
+
# Learn more at https://abstra.io/docs
|
|
195
|
+
.abstra/
|
|
196
|
+
|
|
197
|
+
# Visual Studio Code
|
|
198
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
199
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
200
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
201
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
202
|
+
# .vscode/
|
|
203
|
+
# Temporary file for partial code execution
|
|
204
|
+
tempCodeRunnerFile.py
|
|
205
|
+
|
|
206
|
+
# Ruff stuff:
|
|
207
|
+
.ruff_cache/
|
|
208
|
+
|
|
209
|
+
# PyPI configuration file
|
|
210
|
+
.pypirc
|
|
211
|
+
|
|
212
|
+
# Marimo
|
|
213
|
+
marimo/_static/
|
|
214
|
+
marimo/_lsp/
|
|
215
|
+
__marimo__/
|
|
216
|
+
|
|
217
|
+
# Streamlit
|
|
218
|
+
.streamlit/secrets.toml
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|