| @@ -10189,6 +10189,33 @@ | |||
| 10189 | "code" | 10189 | "code" |
| 10190 | ], | 10190 | ], |
| 10191 | "version": "0.1.0" | 10191 | "version": "0.1.0" |
| 10192 | + }, | ||
| 10193 | + { | ||
| 10194 | + "author": { | ||
| 10195 | + "email": "36114870@qq.com", | ||
| 10196 | + "name": "心栖智学" | ||
| 10197 | + }, | ||
| 10198 | + "category": "education", | ||
| 10199 | + "description": "Math worksheet generator for Chinese elementary school (grade 1-6). Auto-generates printable HTML worksheets with answer keys.", | ||
| 10200 | + "description_zh": "小学数学智能出题器 - 一键生成 1~6 年级数学练习题,支持加减乘除、分数、小数、百分数、比例等题型。输出带参考答案的 HTML 格式练习题,可打印可导出 PDF。教师家长必备。", | ||
| 10201 | + "gitcode_project_id": "9981130", | ||
| 10202 | + "homepage": "https://gitcode.com/atomgit_atomcode/atomcode-plugins-official/tree/main/plugins/math-worksheet", | ||
| 10203 | + "keywords": [ | ||
| 10204 | + "Education" | ||
| 10205 | + ], | ||
| 10206 | + "name": "math-worksheet", | ||
| 10207 | + "source": { | ||
| 10208 | + "path": "plugins/math-worksheet", | ||
| 10209 | + "ref": "main", | ||
| 10210 | + "source": "git-subdir", | ||
| 10211 | + "url": "https://gitcode.com/atomgit_atomcode/atomcode-plugins-official.git" | ||
| 10212 | + }, | ||
| 10213 | + "tier": "verified", | ||
| 10214 | + "strict": true, | ||
| 10215 | + "tags": [ | ||
| 10216 | + "Education" | ||
| 10217 | + ], | ||
| 10218 | + "version": "1.0.0" | ||
| 10192 | } | 10219 | } |
| 10193 | ], | 10220 | ], |
| 10194 | "version": "1.0.0" | 10221 | "version": "1.0.0" |
| @@ -0,0 +1,52 @@ | |||
| 1 | +# 小学数学智能出题器 (Math Worksheet Generator) | ||
| 2 | + | ||
| 3 | +一键生成高质量小学数学练习题,支持 1~6 年级全学段。 | ||
| 4 | +由 AI 自动生成不重复题目,带参考答案,可打印可导出 PDF。 | ||
| 5 | + | ||
| 6 | +## 使用方法 | ||
| 7 | + | ||
| 8 | +在 AtomCode 中运行: | ||
| 9 | + | ||
| 10 | +``` | ||
| 11 | +/math-worksheet:math-worksheet -g 3 -c 30 -o math.html | ||
| 12 | +``` | ||
| 13 | + | ||
| 14 | +或在终端直接运行: | ||
| 15 | + | ||
| 16 | +```bash | ||
| 17 | +python3 scripts/math_worksheet.py -g 3 -c 30 -o worksheet.html | ||
| 18 | +``` | ||
| 19 | + | ||
| 20 | +### 参数说明 | ||
| 21 | + | ||
| 22 | +| 参数 | 简写 | 说明 | 默认值 | | ||
| 23 | +|------|------|------|--------| | ||
| 24 | +| `--grade` | `-g` | 年级 (1-6) | 3 | | ||
| 25 | +| `--count` | `-c` | 题目数量 | 20 | | ||
| 26 | +| `--output` | `-o` | 输出 HTML 文件 | worksheet.html | | ||
| 27 | +| `--title` | `-t` | 自定义标题 | 自动生成 | | ||
| 28 | + | ||
| 29 | +### 各年级题型 | ||
| 30 | + | ||
| 31 | +| 年级 | 题型 | | ||
| 32 | +|------|------| | ||
| 33 | +| 一年级 | 20以内加减法 | | ||
| 34 | +| 二年级 | 乘法口诀 + 两位数加减 | | ||
| 35 | +| 三年级 | 多位数乘除 + 分数基础 | | ||
| 36 | +| 四年级 | 三位数乘两位数 + 四则混合 | | ||
| 37 | +| 五年级 | 小数运算 + 分数运算 | | ||
| 38 | +| 六年级 | 百分数 + 圆面积 + 比例 | | ||
| 39 | + | ||
| 40 | +## 适用场景 | ||
| 41 | + | ||
| 42 | +- 教师快速出题、打印练习题 | ||
| 43 | +- 家长辅导孩子课后练习 | ||
| 44 | +- 考前专项训练 | ||
| 45 | + | ||
| 46 | +## 许可 | ||
| 47 | + | ||
| 48 | +MIT License | ||
| 49 | + | ||
| 50 | +## 作者 | ||
| 51 | + | ||
| 52 | +- 邮箱:36114870@qq.com | ||
| @@ -0,0 +1,249 @@ | |||
| 1 | +#!/usr/bin/env python3 | ||
| 2 | +""" | ||
| 3 | +小学数学智能出题器 v1.0 | ||
| 4 | +Auto Math Worksheet Generator for Chinese Elementary School | ||
| 5 | +Usage: python3 math_worksheet.py --grade 3 --count 20 --output worksheet.html | ||
| 6 | +""" | ||
| 7 | +import argparse, random, os, datetime | ||
| 8 | +from html import escape as escape_html | ||
| 9 | +from fractions import Fraction | ||
| 10 | +from dataclasses import dataclass | ||
| 11 | +from typing import List | ||
| 12 | + | ||
| 13 | +VERSION = "1.0.0" | ||
| 14 | +AUTHOR = "心栖智学 AI 工具组" | ||
| 15 | + | ||
| 16 | + | ||
| 17 | +class Question: | ||
| 18 | + content: str | ||
| 19 | + answer: str | ||
| 20 | + difficulty: int # 1-6 | ||
| 21 | + | ||
| 22 | +class MathGenerator: | ||
| 23 | + """Generate elementary school math questions""" | ||
| 24 | + | ||
| 25 | + def __init__(self, grade: int): | ||
| 26 | + self.grade = grade | ||
| 27 | + random.seed() | ||
| 28 | + | ||
| 29 | + def generate(self, count: int) -> List[Question]: | ||
| 30 | + if self.grade == 1: | ||
| 31 | + return self._grade1(count) | ||
| 32 | + elif self.grade == 2: | ||
| 33 | + return self._grade2(count) | ||
| 34 | + elif self.grade == 3: | ||
| 35 | + return self._grade3(count) | ||
| 36 | + elif self.grade == 4: | ||
| 37 | + return self._grade4(count) | ||
| 38 | + elif self.grade == 5: | ||
| 39 | + return self._grade5(count) | ||
| 40 | + elif self.grade == 6: | ||
| 41 | + return self._grade6(count) | ||
| 42 | + else: | ||
| 43 | + raise ValueError(f"Unsupported grade: {self.grade}") | ||
| 44 | + | ||
| 45 | + def _grade1(self, n): | ||
| 46 | + qs = [] | ||
| 47 | + for _ in range(n): | ||
| 48 | + a, b = random.randint(1, 9), random.randint(1, 9) | ||
| 49 | + op = random.choice(['+', '-']) | ||
| 50 | + if op == '-' and a < b: | ||
| 51 | + a, b = b, a | ||
| 52 | + result = a + b if op == '+' else a - b | ||
| 53 | + qs.append(Question(f"{a} {op} {b} = ___", str(result), 1)) | ||
| 54 | + return qs | ||
| 55 | + | ||
| 56 | + def _grade2(self, n): | ||
| 57 | + qs = [] | ||
| 58 | + for _ in range(n // 2): | ||
| 59 | + a = random.randint(1, 9) | ||
| 60 | + b = random.randint(1, 9) | ||
| 61 | + qs.append(Question(f"{a} x {b} = ___", str(a * b), 2)) | ||
| 62 | + for _ in range(n - n // 2): | ||
| 63 | + a = random.randint(10, 99) | ||
| 64 | + b = random.randint(10, 99) | ||
| 65 | + op = random.choice(['+', '-']) | ||
| 66 | + if op == '-' and a < b: | ||
| 67 | + a, b = b, a | ||
| 68 | + r = a + b if op == '+' else a - b | ||
| 69 | + qs.append(Question(f"{a} {op} {b} = ___", str(r), 2)) | ||
| 70 | + return qs | ||
| 71 | + | ||
| 72 | + def _grade3(self, n): | ||
| 73 | + qs = [] | ||
| 74 | + for i in range(n): | ||
| 75 | + t = i % 3 | ||
| 76 | + if t == 0: | ||
| 77 | + a = random.randint(10, 99); b = random.randint(2, 9) | ||
| 78 | + qs.append(Question(f"{a} x {b} = ___", str(a * b), 3)) | ||
| 79 | + elif t == 1: | ||
| 80 | + a = random.randint(2, 9); b = random.randint(10, 99); r = a * b | ||
| 81 | + qs.append(Question(f"{r} / {a} = ___", str(b), 3)) | ||
| 82 | + else: | ||
| 83 | + d = random.randint(2, 9); n1 = random.randint(1, d-1); n2 = random.randint(1, d-1) | ||
| 84 | + op = random.choice(['+', '-']) | ||
| 85 | + if op == '-' and n1 < n2: n1, n2 = n2, n1 | ||
| 86 | + r = n1 + n2 if op == '+' else n1 - n2 | ||
| 87 | + qs.append(Question(f"{n1}/{d} {op} {n2}/{d} = ___/{d}", str(r), 3)) | ||
| 88 | + return qs | ||
| 89 | + | ||
| 90 | + def _grade4(self, n): | ||
| 91 | + qs = [] | ||
| 92 | + for i in range(n): | ||
| 93 | + t = i % 3 | ||
| 94 | + if t == 0: | ||
| 95 | + a = random.randint(100, 999); b = random.randint(10, 99) | ||
| 96 | + qs.append(Question(f"{a} x {b} = ___", str(a * b), 4)) | ||
| 97 | + elif t == 1: | ||
| 98 | + a = random.randint(10, 99); b = random.randint(2, 20); r = a * b | ||
| 99 | + qs.append(Question(f"{r} / {a} = ___", str(b), 4)) | ||
| 100 | + else: | ||
| 101 | + a, b, c = random.randint(1,9), random.randint(1,9), random.randint(1,9) | ||
| 102 | + qs.append(Question(f"{a} x {b} + {c} = ___", str(a*b+c), 4)) | ||
| 103 | + return qs | ||
| 104 | + | ||
| 105 | + def _grade5(self, n): | ||
| 106 | + qs = [] | ||
| 107 | + for i in range(n): | ||
| 108 | + t = i % 3 | ||
| 109 | + if t == 0: | ||
| 110 | + # 用整数运算确保乘积精确(1位小数 × 1位小数 = 2位小数) | ||
| 111 | + a_int = random.randint(10, 999) # 1位小数: 1.0 ~ 99.9 | ||
| 112 | + b_int = random.randint(1, 99) # 1位小数: 0.1 ~ 9.9 | ||
| 113 | + a_str = f"{a_int//10}.{a_int%10}" | ||
| 114 | + b_str = f"{b_int//10}.{b_int%10}" | ||
| 115 | + product = a_int * b_int | ||
| 116 | + pi, pf = divmod(product, 100) | ||
| 117 | + r = f"{pi}.{pf:02d}".rstrip('0').rstrip('.') | ||
| 118 | + qs.append(Question(f"{a_str} x {b_str} = ___", r, 5)) | ||
| 119 | + elif t == 1: | ||
| 120 | + d = random.randint(2, 12); n1, n2 = random.randint(1,d-1), random.randint(1,d-1) | ||
| 121 | + r = Fraction(n1, d) + Fraction(n2, d) | ||
| 122 | + qs.append(Question(f"{n1}/{d} + {n2}/{d} = ___", str(r), 5)) | ||
| 123 | + else: | ||
| 124 | + a, b = random.randint(10,99), random.randint(10,99) | ||
| 125 | + m = random.randint(2, 5) | ||
| 126 | + qs.append(Question(f"({a} + {b}) x {m} = ___", str((a+b)*m), 5)) | ||
| 127 | + return qs | ||
| 128 | + | ||
| 129 | + def _grade6(self, n): | ||
| 130 | + qs = [] | ||
| 131 | + for i in range(n): | ||
| 132 | + t = i % 4 | ||
| 133 | + if t == 0: | ||
| 134 | + # 用整数运算确保结果精确(1位小数运算) | ||
| 135 | + a_int = random.randint(1, 99) # 0.1 ~ 9.9 | ||
| 136 | + b_int = random.randint(1, 99) | ||
| 137 | + a_str = f"{a_int//10}.{a_int%10}" if a_int >= 10 else f"0.{a_int}" | ||
| 138 | + b_str = f"{b_int//10}.{b_int%10}" if b_int >= 10 else f"0.{b_int}" | ||
| 139 | + op = random.choice(['x', '/']) | ||
| 140 | + if op == 'x': | ||
| 141 | + product = a_int * b_int | ||
| 142 | + pi, pf = divmod(product, 100) | ||
| 143 | + r = f"{pi}.{pf:02d}".rstrip('0').rstrip('.') | ||
| 144 | + else: | ||
| 145 | + # 约束整除,结果精确 | ||
| 146 | + factor = random.randint(1, 20) | ||
| 147 | + a_int = b_int * factor | ||
| 148 | + a_str = f"{a_int//10}.{a_int%10}" if a_int >= 10 else f"0.{a_int}" | ||
| 149 | + b_str = f"{b_int//10}.{b_int%10}" if b_int >= 10 else f"0.{b_int}" | ||
| 150 | + r = str(factor) | ||
| 151 | + qs.append(Question(f"{a_str} {op} {b_str} = ___", r, 6)) | ||
| 152 | + elif t == 1: | ||
| 153 | + # 用 100 的约数确保答案精确为整数 | ||
| 154 | + p = random.choice([1, 2, 4, 5, 10, 20, 25, 50]) | ||
| 155 | + q = 100 // p | ||
| 156 | + k = random.randint(1, 50) * q # 结果(要求的数) | ||
| 157 | + value = p * k // 100 # 已知部分 | ||
| 158 | + qs.append(Question(f"一个数的 {p}% 是 {value},这个数是____", str(k), 6)) | ||
| 159 | + elif t == 2: | ||
| 160 | + r = random.randint(2, 10) | ||
| 161 | + qs.append(Question(f"半径为 {r}cm 的圆面积 = ____ cm2(pi取3.14)", str(round(3.14*r*r, 2)), 6)) | ||
| 162 | + else: | ||
| 163 | + a, b = random.randint(2, 8), random.randint(2, 8) | ||
| 164 | + qs.append(Question(f"{a}:{b} = ___:{b*3}", str(a*3), 6)) | ||
| 165 | + return qs | ||
| 166 | + | ||
| 167 | + | ||
| 168 | +def to_html(questions: List[Question], grade: int, title: str = "") -> str: | ||
| 169 | + if not title: | ||
| 170 | + title = f"小学{grade}年级数学练习题" | ||
| 171 | + title = escape_html(title) | ||
| 172 | + html = f"""<!DOCTYPE html> | ||
| 173 | +<html lang="zh-CN"> | ||
| 174 | +<head> | ||
| 175 | +<meta charset="UTF-8"><title>{title}</title> | ||
| 176 | +<style> | ||
| 177 | + | ||
| 178 | + body {{ font-family: 'SimSun', 'Noto Sans SC', sans-serif; font-size: 14pt; line-height: 2; }} | ||
| 179 | + h1 {{ text-align: center; font-size: 18pt; }} | ||
| 180 | + .sub {{ text-align: center; color: #666; font-size: 11pt; margin-bottom: 20px; }} | ||
| 181 | + .info {{ text-align: right; font-size: 10pt; color: #999; margin-bottom: 20px; }} | ||
| 182 | + table {{ width: 100%; border-collapse: collapse; }} | ||
| 183 | + td {{ padding: 8px 12px; border: 1px solid #ddd; font-size: 13pt; width: 50%; }} | ||
| 184 | + .answer-section {{ page-break-before: always; }} | ||
| 185 | + .answer-section td {{ color: #c00; font-weight: bold; }} | ||
| 186 | + .footer {{ text-align: center; color: #999; font-size: 9pt; margin-top: 30px; }} | ||
| 187 | +</style> | ||
| 188 | +</head> | ||
| 189 | +<body> | ||
| 190 | +<h1>{title}</h1> | ||
| 191 | +<p class="sub">姓名:________ 日期:________ 得分:____/_{len(questions)}</p> | ||
| 192 | +<p class="info">共 {len(questions)} 题 | 建议用时 {max(10, len(questions)//2)} 分钟</p> | ||
| 193 | +<table> | ||
| 194 | +""" | ||
| 195 | + for i in range(0, len(questions), 2): | ||
| 196 | + html += f"<tr><td>{i+1}. {questions[i].content}</td>" | ||
| 197 | + if i+1 < len(questions): | ||
| 198 | + html += f"<td>{i+2}. {questions[i+1].content}</td>" | ||
| 199 | + else: | ||
| 200 | + html += "<td></td>" | ||
| 201 | + html += "</tr>" | ||
| 202 | + | ||
| 203 | + html += '</table><div class="answer-section"><h2>参考答案</h2><table>' | ||
| 204 | + for i in range(0, len(questions), 2): | ||
| 205 | + html += f"<tr><td>{i+1}. {questions[i].answer}</td>" | ||
| 206 | + if i+1 < len(questions): | ||
| 207 | + html += f"<td>{i+2}. {questions[i+1].answer}</td>" | ||
| 208 | + else: | ||
| 209 | + html += "<td></td>" | ||
| 210 | + html += "</tr>" | ||
| 211 | + | ||
| 212 | + html += f"""</table></div> | ||
| 213 | +<p class="footer">由 {AUTHOR} 自动生成 | {datetime.datetime.now().strftime('%Y-%m-%d %H:%M')}</p> | ||
| 214 | +</body> | ||
| 215 | +</html>""" | ||
| 216 | + return html | ||
| 217 | + | ||
| 218 | + | ||
| 219 | +def main(): | ||
| 220 | + parser = argparse.ArgumentParser(description="小学数学智能出题器") | ||
| 221 | + parser.add_argument("--grade", "-g", type=int, default=3, choices=range(1, 7), | ||
| 222 | + help="年级 (1-6)") | ||
| 223 | + parser.add_argument("--count", "-c", type=int, default=20, choices=range(1, 201), help="题目数量 (1-200)") | ||
| 224 | + parser.add_argument("--output", "-o", default="worksheet.html", help="输出文件 (HTML)") | ||
| 225 | + parser.add_argument("--title", "-t", default="", help="自定义标题") | ||
| 226 | + parser.add_argument("--version", "-v", action="store_true", help="显示版本信息") | ||
| 227 | + args = parser.parse_args() | ||
| 228 | + args.output = os.path.abspath(os.path.normpath(args.output)) | ||
🟠 High Priority 第 209 行 触发条件:攻击者(或恶意输入)传入任意路径,例如:
第 218 行 放大因素:SKILL.md 第 5 行 失败后果:覆盖系统配置、用户脚本或其他关键文件,造成数据丢失或系统异常。 与上一轮审查的关系:上一轮报告了同样的问题(仅 建议:在 ![]() ![]() 不准确? 🟠 High Priority 第 209 行 触发条件:攻击者(或恶意输入)传入越界路径,例如:
失败模式:脚本以当前进程权限覆盖任意可写文件,可能导致系统后门植入或数据破坏。 建议:在 os.path.abspath 之后增加输出目录安全校验:用 os.path.commonpath([allowed_dir, resolved_path]) 检查,或限制输出必须在当前工作目录或其子目录内。如果路径越界,拒绝并报错退出。 ![]() ![]() 不准确? | |||
| 229 | + cwd = os.path.abspath(os.getcwd()) | ||
| 230 | + try: | ||
| 231 | + if os.path.commonpath([cwd]) != os.path.commonpath([cwd, args.output]): | ||
| 232 | + parser.error(f"输出路径 {args.output} 不在当前工作目录下,已拒绝") | ||
| 233 | + except ValueError: | ||
| 234 | + parser.error(f"输出路径 {args.output} 与当前工作目录不在同一盘符,已拒绝") | ||
| 235 | + | ||
| 236 | + if args.version: | ||
| 237 | + print(f"小学数学智能出题器 v{VERSION}") | ||
| 238 | + return | ||
| 239 | + | ||
| 240 | + gen = MathGenerator(args.grade) | ||
| 241 | + questions = gen.generate(args.count) | ||
| 242 | + html = to_html(questions, args.grade, args.title) | ||
| 243 | + with open(args.output, 'w', encoding='utf-8') as f: | ||
| 244 | + f.write(html) | ||
| 245 | + print(f"OK - {len(questions)} 道题已生成到 {os.path.abspath(args.output)}") | ||
| 246 | + | ||
| 247 | + | ||
| 248 | +if __name__ == "__main__": | ||
| 249 | + main() | ||
| @@ -0,0 +1,48 @@ | |||
| 1 | +--- | ||
| 2 | +name: math-worksheet | ||
| 3 | +description: 小学数学智能出题器 — 当用户需要生成小学数学练习题时使用。支持 1~6 年级,生成带答案的 HTML 练习题。Trigger when user says: 出题、练习题、数学试卷、math worksheet、数学练习。 | ||
| 4 | +argument-hint: "-g <年级> -c <题数> [-o <输出文件>] [-t <自定义标题>]" | ||
| 5 | +allowed-tools: "Bash(python3 math_worksheet.py:*)" | ||
| 6 | +--- | ||
| 7 | + | ||
| 8 | +# 小学数学智能出题器 | ||
| 9 | + | ||
| 10 | +你需要生成数学练习题时使用此工具。该工具位于 `${CLAUDE_SKILL_DIR}/../../scripts/math_worksheet.py`。 | ||
| 11 | + | ||
| 12 | +## 使用方法 | ||
| 13 | + | ||
| 14 | +1. 根据用户需求确定参数: | ||
| 15 | + - `-g` 或 `--grade`:年级 (1-6),默认 3 | ||
| 16 | + - `-c` 或 `--count`:题目数量,默认 20 | ||
| 17 | + - `-o` 或 `--output`:输出文件名,默认 worksheet.html | ||
| 18 | + - `-t` 或 `--title`:自定义试卷标题,可选 | ||
| 19 | + | ||
| 20 | +2. 运行脚本生成练习题: | ||
| 21 | + | ||
| 22 | +```bash | ||
| 23 | +cd "${CLAUDE_SKILL_DIR}/../../scripts" | ||
| 24 | +python3 math_worksheet.py -g 3 -c 30 -o 练习题.html | ||
| 25 | +``` | ||
| 26 | + | ||
| 27 | +3. 输出结果为 HTML 文件,可直接用浏览器打开、打印或导出 PDF。 | ||
| 28 | + | ||
| 29 | +## 各年级题型参考 | ||
| 30 | + | ||
| 31 | +| 年级 | 重点题型 | | ||
| 32 | +|------|---------| | ||
| 33 | +| 1 | 20以内加减法(难度★) | | ||
| 34 | +| 2 | 乘法口诀、两位数加减(难度★) | | ||
| 35 | +| 3 | 多位数乘除、分数基础(难度★★) | | ||
| 36 | +| 4 | 三位数乘两位数、四则混合(难度★★) | | ||
| 37 | +| 5 | 小数、分数运算(难度★★★) | | ||
| 38 | +| 6 | 百分数、圆面积、比例(难度★★★) | | ||
| 39 | + | ||
| 40 | +## 注意事项 | ||
| 41 | + | ||
| 42 | +- 每次运行题目随机生成,不重复 | ||
| 43 | +- 输出文件包含题目和参考答案两部分 | ||
| 44 | +- 建议题量:低年级 20-30 题,高年级 30-50 题 | ||
| 45 | + | ||
| 46 | +--- | ||
| 47 | + | ||
| 48 | +Author: 36114870@qq.com | MIT License | ||


🟡 Medium Priority
to_html()函数在第 153 行(<title>)和第 168 行(<h1>)直接将用户通过--title传入的字符串拼接到 HTML 模板中,未做任何 HTML 转义(未使用html.escape())。失败模式:若用户传入含 HTML 特殊字符的标题,例如:
虽然该工具为本地 CLI 出题器、HTML 仅供用户自己使用,实际安全风险较低,但会导致生成的 HTML 结构异常或渲染错误,属于正确性问题。
默认 title(
f"小学{grade}年级数学练习题")是安全的,问题仅影响--title自定义标题场景。建议:在
to_html()函数中对title做 HTML 转义:在文件头部import html,然后在第 148-149 行之后、拼接 HTML 之前添加title = html.escape(title)。