@arcaneorion/dsh-teaching-board 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -3
- package/cordis.patch.yml +6 -2
- package/package.json +5 -3
- package/skills/stage-panel/SKILL.md +107 -0
- package/skills/stage-panel/references/diagram-selection.md +80 -0
- package/skills/stage-panel/references/html-patterns.md +93 -0
- package/skills/stage-panel/references/visual-style.md +43 -0
- package/skills/stage-panel/submode/code/SKILL.md +17 -0
- package/skills/stage-panel/submode/code/examples/go-hello-channel.json +36 -0
- package/skills/stage-panel/submode/code/examples/go-http-api-basics.json +58 -0
- package/skills/stage-panel/submode/code/examples/py-agent-loop.json +79 -0
- package/skills/stage-panel/submode/code/examples/py-ai-parameters-intro.json +65 -0
- package/skills/stage-panel/submode/code/examples/py-fizzbuzz-intro.json +74 -0
- package/skills/stage-panel/submode/code/examples/py-training-loop.json +114 -0
- package/skills/stage-panel/submode/code/examples/rs-move-basics.json +118 -0
- package/skills/stage-panel/submode/code/references/host-toolchains.md +61 -0
- package/skills/stage-panel/submode/code/schemas/lesson.schema.json +225 -0
- package/skills/stage-panel/submode/code/templates/lab.html +561 -0
- package/skills/stage-panel/submode/game-study/SKILL.md +16 -0
- package/skills/stage-panel/submode/game-study/examples/hft-microstructure.json +60 -0
- package/skills/stage-panel/submode/game-study/examples/securities-law.json +57 -0
- package/skills/stage-panel/submode/game-study/schemas/quest.schema.json +95 -0
- package/skills/stage-panel/submode/game-study/templates/quiz.html +516 -0
- package/skills/stage-panel/submode/learn/SKILL.md +16 -0
- package/skills/stage-panel/submode/learn/examples/epsilon-delta.json +140 -0
- package/skills/stage-panel/submode/learn/examples/template-showcase.json +147 -0
- package/skills/stage-panel/submode/learn/schemas/lesson.schema.json +184 -0
- package/skills/stage-panel/submode/learn/templates/lesson.html +782 -0
- package/skills/stage-panel/submode/ml/SKILL.md +9 -0
- package/skills/stage-panel/submode/ml/examples/gd.json +7 -0
- package/skills/stage-panel/submode/ml/templates/gd.html +217 -0
- package/skills/stage-panel/submode/quant/SKILL.md +9 -0
- package/skills/stage-panel/submode/quant/references/export_backtest_data.py +192 -0
- package/skills/stage-panel/submode/quant/templates/dashboard.html +381 -0
- package/src/index.js +11 -0
- package/src/skills.js +88 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": 1,
|
|
3
|
+
"id": "py-ai-parameters-intro",
|
|
4
|
+
"title": "AI 的参数:模型如何学习",
|
|
5
|
+
"subtitle": "从最简单的例子理解 AI 参数调整",
|
|
6
|
+
"summary": "用一个简单的温度预测例子,理解 AI 模型的参数(权重和偏置)如何影响预测结果。不用复杂语法,只用基础 Python。",
|
|
7
|
+
"domain": "machine-learning",
|
|
8
|
+
"lang": "python",
|
|
9
|
+
"level": "intro",
|
|
10
|
+
"objectives": [
|
|
11
|
+
"理解什么是模型参数(权重 w 和偏置 b)",
|
|
12
|
+
"看到参数改变如何影响预测结果",
|
|
13
|
+
"理解预测值和真实值的误差"
|
|
14
|
+
],
|
|
15
|
+
"starter": {
|
|
16
|
+
"filename": "ai_params.py",
|
|
17
|
+
"code": "# AI 模型的核心:参数\n# 假设我们要预测温度:输入小时数,输出温度\n\n# 真实数据:(小时, 温度)\ndata = [\n (6, 15),\n (12, 25),\n (18, 20),\n]\n\n# 模型参数(这就是 AI 要学习的东西)\nw = 1.0\nb = 10.0\n\nprint(\"当前参数\")\nprint(\"权重 w =\", w)\nprint(\"偏置 b =\", b)\nprint()\n\n# 用当前参数做预测\nprint(\"预测结果\")\nfor hour, real_temp in data:\n predicted_temp = w * hour + b\n error = predicted_temp - real_temp\n print(\"小时\", hour, \": 真实温度\", real_temp, \"预测温度\", predicted_temp, \"误差\", error)\n\nprint()\nprint(\"参数不好,预测就不准!\")\n"
|
|
18
|
+
},
|
|
19
|
+
"run": {},
|
|
20
|
+
"activities": [
|
|
21
|
+
{
|
|
22
|
+
"id": "predict-output",
|
|
23
|
+
"type": "predict",
|
|
24
|
+
"prompt": "运行前先预测:当 w=1.0, b=10.0 时,中午12点的预测温度是多少度?",
|
|
25
|
+
"required": true,
|
|
26
|
+
"before_run": true
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"id": "run-and-observe",
|
|
30
|
+
"type": "trace",
|
|
31
|
+
"prompt": "点击运行,观察输出。预测值和真实值的误差有多大?哪个时间点的误差最大?",
|
|
32
|
+
"required": false
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"id": "adjust-params",
|
|
36
|
+
"type": "implement",
|
|
37
|
+
"prompt": "试着修改参数 w 和 b,再运行一次。看看误差有没有变小?",
|
|
38
|
+
"required": true
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"id": "explain-params",
|
|
42
|
+
"type": "explain",
|
|
43
|
+
"prompt": "用自己的话解释:什么是模型参数?为什么调整参数能改变预测结果?",
|
|
44
|
+
"required": true
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
"checks": [
|
|
48
|
+
{
|
|
49
|
+
"id": "check-runs",
|
|
50
|
+
"label": "代码能运行",
|
|
51
|
+
"type": "exit_code",
|
|
52
|
+
"expected": 0
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"id": "check-output",
|
|
56
|
+
"label": "输出包含预测结果",
|
|
57
|
+
"type": "stdout_contains",
|
|
58
|
+
"expected": "预测温度"
|
|
59
|
+
}
|
|
60
|
+
],
|
|
61
|
+
"submission": {
|
|
62
|
+
"require_run": true,
|
|
63
|
+
"require_reflection": true
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": 1,
|
|
3
|
+
"id": "py-fizzbuzz-intro",
|
|
4
|
+
"title": "Python:先跑起来再改 FizzBuzz",
|
|
5
|
+
"subtitle": "用一次成功运行建立 Code Lab 手感",
|
|
6
|
+
"lang": "python",
|
|
7
|
+
"langs": [
|
|
8
|
+
"python"
|
|
9
|
+
],
|
|
10
|
+
"level": "intro",
|
|
11
|
+
"stage": "编辑 → 运行 → 改输出",
|
|
12
|
+
"summary": "先让脚本打印 1..15 的 FizzBuzz,再按练习改规则。运行在本机 python3 上完成。",
|
|
13
|
+
"objectives": [
|
|
14
|
+
"成功运行 starter 并看到输出",
|
|
15
|
+
"把 15 的倍数改成同时打印 FizzBuzz",
|
|
16
|
+
"向导师说明 if 顺序为什么重要"
|
|
17
|
+
],
|
|
18
|
+
"focus": {
|
|
19
|
+
"concept": "control-flow",
|
|
20
|
+
"trap": "wrong-if-order"
|
|
21
|
+
},
|
|
22
|
+
"starter": {
|
|
23
|
+
"filename": "main.py",
|
|
24
|
+
"code": "def fizzbuzz(n: int) -> str:\n if n % 3 == 0:\n return \"Fizz\"\n if n % 5 == 0:\n return \"Buzz\"\n return str(n)\n\n\nif __name__ == \"__main__\":\n for i in range(1, 16):\n print(fizzbuzz(i))\n"
|
|
25
|
+
},
|
|
26
|
+
"snippets": [
|
|
27
|
+
{
|
|
28
|
+
"id": "main",
|
|
29
|
+
"lang": "python",
|
|
30
|
+
"title": "FizzBuzz starter",
|
|
31
|
+
"filename": "main.py",
|
|
32
|
+
"role": "primary",
|
|
33
|
+
"editable": true,
|
|
34
|
+
"code": "def fizzbuzz(n: int) -> str:\n if n % 3 == 0:\n return \"Fizz\"\n if n % 5 == 0:\n return \"Buzz\"\n return str(n)\n\n\nif __name__ == \"__main__\":\n for i in range(1, 16):\n print(fizzbuzz(i))\n",
|
|
35
|
+
"annotations": [
|
|
36
|
+
{
|
|
37
|
+
"line": 2,
|
|
38
|
+
"kind": "warn",
|
|
39
|
+
"text": "只判断 %3 时,15 会过早返回 Fizz"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"line": 10,
|
|
43
|
+
"kind": "info",
|
|
44
|
+
"text": "入口:打印 1..15"
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
],
|
|
49
|
+
"run": {
|
|
50
|
+
"snippet_id": "main",
|
|
51
|
+
"filename": "main.py"
|
|
52
|
+
},
|
|
53
|
+
"exercises": [
|
|
54
|
+
{
|
|
55
|
+
"id": "e1",
|
|
56
|
+
"type": "predict_output",
|
|
57
|
+
"prompt": "当前实现对 15 会打印什么?",
|
|
58
|
+
"snippet_id": "main"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"id": "e2",
|
|
62
|
+
"type": "fix_code",
|
|
63
|
+
"prompt": "改到 15 打印 FizzBuzz,且 3/5 仍正确。",
|
|
64
|
+
"snippet_id": "main"
|
|
65
|
+
}
|
|
66
|
+
],
|
|
67
|
+
"hints": [
|
|
68
|
+
"先处理 n % 15 == 0(或同时整除 3 与 5)。",
|
|
69
|
+
"改完点「运行」,看 1..15 整表。"
|
|
70
|
+
],
|
|
71
|
+
"next": [
|
|
72
|
+
"py-list-comprehension"
|
|
73
|
+
]
|
|
74
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": 1,
|
|
3
|
+
"id": "py-training-loop",
|
|
4
|
+
"title": "模型训练:亲手跑一次梯度下降",
|
|
5
|
+
"subtitle": "不用框架,直接观察参数、损失与预测如何更新",
|
|
6
|
+
"summary": "用纯 Python 拟合 y = 2x + 1。代码展示训练数据、前向计算、损失、梯度和参数更新之间的完整关系。",
|
|
7
|
+
"domain": "machine-learning",
|
|
8
|
+
"lang": "python",
|
|
9
|
+
"level": "intro",
|
|
10
|
+
"stage": "预测 → 训练 → 观察 → 迁移",
|
|
11
|
+
"objectives": [
|
|
12
|
+
"指出训练循环的五个组成部分",
|
|
13
|
+
"解释 loss 下降意味着什么",
|
|
14
|
+
"修改学习率并比较收敛"
|
|
15
|
+
],
|
|
16
|
+
"prerequisites": [
|
|
17
|
+
"Python 循环和函数",
|
|
18
|
+
"导数的直觉"
|
|
19
|
+
],
|
|
20
|
+
"starter": {
|
|
21
|
+
"filename": "train.py",
|
|
22
|
+
"code": "data = [(0.0, 1.0), (1.0, 3.0), (2.0, 5.0), (3.0, 7.0)]\n\nw = 0.0\nb = 0.0\nlr = 0.05\n\nfor step in range(201):\n predictions = [w * x + b for x, _ in data]\n errors = [pred - y for pred, (_, y) in zip(predictions, data)]\n loss = sum(error ** 2 for error in errors) / len(data)\n\n dw = 2 * sum(error * x for error, (x, _) in zip(errors, data)) / len(data)\n db = 2 * sum(errors) / len(data)\n w -= lr * dw\n b -= lr * db\n\n if step in {0, 20, 100, 200}:\n print(f\"step={step:3d} loss={loss:.6f} w={w:.4f} b={b:.4f}\")\n\nprint(f\"prediction={w * 5 + b:.2f}\")\n"
|
|
23
|
+
},
|
|
24
|
+
"snippets": [
|
|
25
|
+
{
|
|
26
|
+
"id": "main",
|
|
27
|
+
"lang": "python",
|
|
28
|
+
"title": "最小训练循环",
|
|
29
|
+
"filename": "train.py",
|
|
30
|
+
"role": "primary",
|
|
31
|
+
"editable": true,
|
|
32
|
+
"code": "data = [(0.0, 1.0), (1.0, 3.0), (2.0, 5.0), (3.0, 7.0)]\n\nw = 0.0\nb = 0.0\nlr = 0.05\n\nfor step in range(201):\n predictions = [w * x + b for x, _ in data]\n errors = [pred - y for pred, (_, y) in zip(predictions, data)]\n loss = sum(error ** 2 for error in errors) / len(data)\n\n dw = 2 * sum(error * x for error, (x, _) in zip(errors, data)) / len(data)\n db = 2 * sum(errors) / len(data)\n w -= lr * dw\n b -= lr * db\n\n if step in {0, 20, 100, 200}:\n print(f\"step={step:3d} loss={loss:.6f} w={w:.4f} b={b:.4f}\")\n\nprint(f\"prediction={w * 5 + b:.2f}\")\n",
|
|
33
|
+
"annotations": [
|
|
34
|
+
{
|
|
35
|
+
"line": 7,
|
|
36
|
+
"kind": "info",
|
|
37
|
+
"text": "前向计算:当前参数产生预测"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"line": 9,
|
|
41
|
+
"kind": "info",
|
|
42
|
+
"text": "目标函数:均方误差"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"line": 11,
|
|
46
|
+
"kind": "warn",
|
|
47
|
+
"text": "梯度决定参数应向哪个方向变化"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"line": 13,
|
|
51
|
+
"kind": "info",
|
|
52
|
+
"text": "优化器最小形式:参数减去学习率乘梯度"
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
],
|
|
57
|
+
"run": {
|
|
58
|
+
"snippet_id": "main",
|
|
59
|
+
"filename": "train.py"
|
|
60
|
+
},
|
|
61
|
+
"activities": [
|
|
62
|
+
{
|
|
63
|
+
"id": "predict-loss",
|
|
64
|
+
"type": "predict",
|
|
65
|
+
"prompt": "训练过程中 loss 应整体上升还是下降?w 和 b 最终应接近什么值?",
|
|
66
|
+
"required": true,
|
|
67
|
+
"before_run": true
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"id": "trace-loop",
|
|
71
|
+
"type": "trace",
|
|
72
|
+
"prompt": "用自己的话串起:数据 → 预测 → 误差 → loss → 梯度 → 参数更新。",
|
|
73
|
+
"required": true
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"id": "transfer-lr",
|
|
77
|
+
"type": "transfer",
|
|
78
|
+
"prompt": "把 lr 改为 0.5 和 0.005 分别运行。比较收敛速度或不稳定现象。",
|
|
79
|
+
"required": true
|
|
80
|
+
}
|
|
81
|
+
],
|
|
82
|
+
"checks": [
|
|
83
|
+
{
|
|
84
|
+
"id": "exit-ok",
|
|
85
|
+
"label": "训练脚本正常退出",
|
|
86
|
+
"type": "exit_code",
|
|
87
|
+
"expected": 0
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"id": "has-loss",
|
|
91
|
+
"label": "输出包含训练损失",
|
|
92
|
+
"type": "stdout_contains",
|
|
93
|
+
"expected": "loss="
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"id": "predict-eleven",
|
|
97
|
+
"label": "模型对 x=5 的预测接近 11",
|
|
98
|
+
"type": "stdout_contains",
|
|
99
|
+
"expected": "prediction=11.00"
|
|
100
|
+
}
|
|
101
|
+
],
|
|
102
|
+
"hints": [
|
|
103
|
+
"线性模型只有两个参数:斜率 w 和截距 b。",
|
|
104
|
+
"学习率过大可能越过最低点,过小则需要更多步。"
|
|
105
|
+
],
|
|
106
|
+
"submission": {
|
|
107
|
+
"require_run": true,
|
|
108
|
+
"require_reflection": true
|
|
109
|
+
},
|
|
110
|
+
"next": [
|
|
111
|
+
"py-autograd",
|
|
112
|
+
"py-train-validation-split"
|
|
113
|
+
]
|
|
114
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": 1,
|
|
3
|
+
"id": "rs-move-basics",
|
|
4
|
+
"title": "Rust move:为什么 s 不能再读",
|
|
5
|
+
"subtitle": "用一次编译失败学会所有权转移",
|
|
6
|
+
"lang": "rust",
|
|
7
|
+
"level": "intro",
|
|
8
|
+
"stage": "编辑 → 运行 → 读编译错误",
|
|
9
|
+
"summary": "Rust 默认 move。String 被赋值给另一个变量后,旧名字不能再使用;编译器在运行前拦住 use-after-move。Python 同一写法只是多了一个引用名。",
|
|
10
|
+
"objectives": [
|
|
11
|
+
"指出哪一行发生 move",
|
|
12
|
+
"根据 E0382 说明为什么第 3 行非法",
|
|
13
|
+
"用一句话对比 Python 的引用语义"
|
|
14
|
+
],
|
|
15
|
+
"focus": {
|
|
16
|
+
"concept": "ownership-move",
|
|
17
|
+
"trap": "use-after-move"
|
|
18
|
+
},
|
|
19
|
+
"starter": {
|
|
20
|
+
"filename": "main.rs",
|
|
21
|
+
"code": "fn main() {\n let s = String::from(\"hi\");\n let t = s;\n println!(\"{s}\");\n}\n"
|
|
22
|
+
},
|
|
23
|
+
"snippets": [
|
|
24
|
+
{
|
|
25
|
+
"id": "main",
|
|
26
|
+
"lang": "rust",
|
|
27
|
+
"title": "会编译失败的例子",
|
|
28
|
+
"filename": "main.rs",
|
|
29
|
+
"role": "primary",
|
|
30
|
+
"editable": true,
|
|
31
|
+
"code": "fn main() {\n let s = String::from(\"hi\");\n let t = s;\n println!(\"{s}\");\n}\n",
|
|
32
|
+
"annotations": [
|
|
33
|
+
{
|
|
34
|
+
"line": 2,
|
|
35
|
+
"kind": "info",
|
|
36
|
+
"text": "在堆上创建 String,s 拥有它"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"line": 3,
|
|
40
|
+
"kind": "move",
|
|
41
|
+
"text": "所有权从 s 移到 t,s 失效"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"line": 4,
|
|
45
|
+
"kind": "error",
|
|
46
|
+
"text": "use after move → 编译期 E0382"
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
],
|
|
51
|
+
"run": {
|
|
52
|
+
"snippet_id": "main",
|
|
53
|
+
"filename": "main.rs"
|
|
54
|
+
},
|
|
55
|
+
"trace": {
|
|
56
|
+
"steps": [
|
|
57
|
+
{
|
|
58
|
+
"id": "s1",
|
|
59
|
+
"title": "创建 s",
|
|
60
|
+
"focus_snippet": "main",
|
|
61
|
+
"lines": [
|
|
62
|
+
2
|
|
63
|
+
],
|
|
64
|
+
"note": "s 拥有堆上的 String。"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"id": "s2",
|
|
68
|
+
"title": "move 到 t",
|
|
69
|
+
"focus_snippet": "main",
|
|
70
|
+
"lines": [
|
|
71
|
+
3
|
|
72
|
+
],
|
|
73
|
+
"note": "赋值转移所有权,不是共享。"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"id": "s3",
|
|
77
|
+
"title": "再读 s",
|
|
78
|
+
"focus_snippet": "main",
|
|
79
|
+
"lines": [
|
|
80
|
+
4
|
|
81
|
+
],
|
|
82
|
+
"note": "编译器拒绝;可改为用 t,或 s.clone(),或只传 &s。"
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
},
|
|
86
|
+
"exercises": [
|
|
87
|
+
{
|
|
88
|
+
"id": "e1",
|
|
89
|
+
"type": "spot_bug",
|
|
90
|
+
"prompt": "标出会触发 use-after-move 的那一行(相对 main 片段)。",
|
|
91
|
+
"snippet_id": "main",
|
|
92
|
+
"hint": "move 发生在赋值之后的下一次使用。"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"id": "e2",
|
|
96
|
+
"type": "fix_code",
|
|
97
|
+
"prompt": "改到能编译并打印 hi。至少一种:打印 t、clone、或借用。",
|
|
98
|
+
"snippet_id": "main"
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"id": "e3",
|
|
102
|
+
"type": "short",
|
|
103
|
+
"prompt": "为什么 Python 对照片段能直接 print(s)?"
|
|
104
|
+
}
|
|
105
|
+
],
|
|
106
|
+
"hints": [
|
|
107
|
+
"先让 rustc 报错,再读 error code,比背规则快。",
|
|
108
|
+
"Copy 类型(如 i32)赋值是复制,不会 move;String 不是 Copy。"
|
|
109
|
+
],
|
|
110
|
+
"solution": {
|
|
111
|
+
"code": "fn main() {\n let s = String::from(\"hi\");\n let t = s.clone();\n println!(\"{s}\");\n println!(\"{t}\");\n}\n",
|
|
112
|
+
"explanation": "clone 显式复制堆数据;或直接 println!(\"{t}\") 使用新主人;需要共享只读时用 &s。"
|
|
113
|
+
},
|
|
114
|
+
"next": [
|
|
115
|
+
"rs-borrow-intro",
|
|
116
|
+
"rs-copy-vs-clone"
|
|
117
|
+
]
|
|
118
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# 宿主工具链检测与安装提示
|
|
2
|
+
|
|
3
|
+
Code Lab **不**捆绑编译器。运行前检测本机工具;缺失则 `blocked` 并提示安装。
|
|
4
|
+
|
|
5
|
+
## 检测命令(doctor 可用)
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
python3 --version
|
|
9
|
+
uv --version
|
|
10
|
+
rustc --version
|
|
11
|
+
cargo --version
|
|
12
|
+
go version
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
项目信号文件:
|
|
16
|
+
|
|
17
|
+
| 语言 | 信号 |
|
|
18
|
+
|------|------|
|
|
19
|
+
| Python | `pyproject.toml`, `uv.lock`, `requirements.txt` |
|
|
20
|
+
| Rust | `Cargo.toml`, `Cargo.lock` |
|
|
21
|
+
| Go | `go.mod`, `go.sum` |
|
|
22
|
+
|
|
23
|
+
## MVP runner 最小依赖
|
|
24
|
+
|
|
25
|
+
| profile | 需要 |
|
|
26
|
+
|---------|------|
|
|
27
|
+
| `python-script` | `python3` |
|
|
28
|
+
| `python-uv-script` | `uv`(推荐 NixOS/本环境) |
|
|
29
|
+
| `rust-rustc-single` | `rustc` |
|
|
30
|
+
| `go-run-single` | `go` |
|
|
31
|
+
|
|
32
|
+
`cargo` / `go test` 为 Phase 2。
|
|
33
|
+
|
|
34
|
+
## NixOS 提示(示例,按用户 flake 调整)
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# 临时 shell 示例
|
|
38
|
+
nix-shell -p python3 uv rustc cargo go
|
|
39
|
+
|
|
40
|
+
# 或进入项目 devShell(若有 flake)
|
|
41
|
+
nix develop
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
不要在 skill 内 `nix-build` 出一套私有工具链当默认路径;应复用用户环境。
|
|
45
|
+
|
|
46
|
+
## 库从哪里来
|
|
47
|
+
|
|
48
|
+
- **单文件课**:尽量 std only,降低依赖摩擦。
|
|
49
|
+
- **需要第三方库的课**:在**用户项目**里用 uv/cargo/go.mod 声明;lesson 文档写明前置。
|
|
50
|
+
- skill `examples/` 只放 lesson JSON,不 vendoring site-packages / target。
|
|
51
|
+
|
|
52
|
+
## 失败语义
|
|
53
|
+
|
|
54
|
+
| 情况 | run status | 对用户 |
|
|
55
|
+
|------|------------|--------|
|
|
56
|
+
| 无 rustc | `blocked` | 显示缺失工具 + 安装提示 |
|
|
57
|
+
| 编译/运行失败 | `failed` | 展示 stderr,供教学 |
|
|
58
|
+
| 超时 | `timeout` | 提示减小输入或查死循环 |
|
|
59
|
+
| 成功 | `ok` | 展示 stdout |
|
|
60
|
+
|
|
61
|
+
禁止在工具缺失时回退到「浏览器假执行」而不告知。
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://localweb.dev/schemas/code.lesson.schema.json",
|
|
4
|
+
"title": "LocalWeb Code Lab Lesson",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": true,
|
|
7
|
+
"required": ["title", "lang"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"schema_version": {
|
|
10
|
+
"type": "integer",
|
|
11
|
+
"const": 1
|
|
12
|
+
},
|
|
13
|
+
"id": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"pattern": "^[A-Za-z0-9_-]+$"
|
|
16
|
+
},
|
|
17
|
+
"title": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"minLength": 1
|
|
20
|
+
},
|
|
21
|
+
"subtitle": {
|
|
22
|
+
"type": "string"
|
|
23
|
+
},
|
|
24
|
+
"summary": {
|
|
25
|
+
"type": "string"
|
|
26
|
+
},
|
|
27
|
+
"lang": {
|
|
28
|
+
"type": "string",
|
|
29
|
+
"enum": ["python", "rust", "go"]
|
|
30
|
+
},
|
|
31
|
+
"langs": {
|
|
32
|
+
"type": "array",
|
|
33
|
+
"items": {
|
|
34
|
+
"type": "string",
|
|
35
|
+
"enum": ["python", "rust", "go"]
|
|
36
|
+
},
|
|
37
|
+
"description": "本课涉及的语言;跨语言对照时使用。"
|
|
38
|
+
},
|
|
39
|
+
"level": {
|
|
40
|
+
"type": "string",
|
|
41
|
+
"enum": ["intro", "intermediate", "advanced"]
|
|
42
|
+
},
|
|
43
|
+
"stage": {
|
|
44
|
+
"type": "string",
|
|
45
|
+
"description": "教学阶段标签,如 编辑练习 / 读错信息 / 对照语义"
|
|
46
|
+
},
|
|
47
|
+
"objectives": {
|
|
48
|
+
"type": "array",
|
|
49
|
+
"items": { "type": "string" }
|
|
50
|
+
},
|
|
51
|
+
"focus": {
|
|
52
|
+
"type": "object",
|
|
53
|
+
"additionalProperties": true,
|
|
54
|
+
"properties": {
|
|
55
|
+
"concept": { "type": "string" },
|
|
56
|
+
"trap": { "type": "string" }
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"starter": {
|
|
60
|
+
"type": "object",
|
|
61
|
+
"description": "单文件课的快速写法;与 snippets 二选一或同时提供。",
|
|
62
|
+
"required": ["code"],
|
|
63
|
+
"additionalProperties": true,
|
|
64
|
+
"properties": {
|
|
65
|
+
"filename": { "type": "string" },
|
|
66
|
+
"code": { "type": "string" }
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
"snippets": {
|
|
70
|
+
"type": "array",
|
|
71
|
+
"items": {
|
|
72
|
+
"type": "object",
|
|
73
|
+
"required": ["id", "lang", "code"],
|
|
74
|
+
"additionalProperties": true,
|
|
75
|
+
"properties": {
|
|
76
|
+
"id": { "type": "string" },
|
|
77
|
+
"lang": {
|
|
78
|
+
"type": "string",
|
|
79
|
+
"enum": ["python", "rust", "go"]
|
|
80
|
+
},
|
|
81
|
+
"title": { "type": "string" },
|
|
82
|
+
"filename": { "type": "string" },
|
|
83
|
+
"code": { "type": "string" },
|
|
84
|
+
"role": {
|
|
85
|
+
"type": "string",
|
|
86
|
+
"enum": ["primary", "contrast", "solution", "tests"],
|
|
87
|
+
"default": "primary"
|
|
88
|
+
},
|
|
89
|
+
"editable": {
|
|
90
|
+
"type": "boolean",
|
|
91
|
+
"default": false
|
|
92
|
+
},
|
|
93
|
+
"annotations": {
|
|
94
|
+
"type": "array",
|
|
95
|
+
"items": {
|
|
96
|
+
"type": "object",
|
|
97
|
+
"required": ["line", "text"],
|
|
98
|
+
"additionalProperties": true,
|
|
99
|
+
"properties": {
|
|
100
|
+
"line": { "type": "integer", "minimum": 1 },
|
|
101
|
+
"kind": {
|
|
102
|
+
"type": "string",
|
|
103
|
+
"enum": ["info", "move", "error", "alias", "hint", "warn"]
|
|
104
|
+
},
|
|
105
|
+
"text": { "type": "string" }
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
"run": {
|
|
113
|
+
"type": "object",
|
|
114
|
+
"additionalProperties": true,
|
|
115
|
+
"properties": {
|
|
116
|
+
"snippet_id": {
|
|
117
|
+
"type": "string",
|
|
118
|
+
"description": "默认展示哪个 snippet;缺省取 role=primary 或 starter。"
|
|
119
|
+
},
|
|
120
|
+
"filename": { "type": "string" }
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
"compare": {
|
|
124
|
+
"type": "object",
|
|
125
|
+
"additionalProperties": true,
|
|
126
|
+
"properties": {
|
|
127
|
+
"axes": {
|
|
128
|
+
"type": "array",
|
|
129
|
+
"items": { "type": "string" }
|
|
130
|
+
},
|
|
131
|
+
"rows": {
|
|
132
|
+
"type": "array",
|
|
133
|
+
"items": {
|
|
134
|
+
"type": "object",
|
|
135
|
+
"required": ["lang", "cells"],
|
|
136
|
+
"additionalProperties": true,
|
|
137
|
+
"properties": {
|
|
138
|
+
"lang": { "type": "string" },
|
|
139
|
+
"cells": {
|
|
140
|
+
"type": "array",
|
|
141
|
+
"items": { "type": "string" }
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
"trace": {
|
|
149
|
+
"type": "object",
|
|
150
|
+
"additionalProperties": true,
|
|
151
|
+
"properties": {
|
|
152
|
+
"steps": {
|
|
153
|
+
"type": "array",
|
|
154
|
+
"items": {
|
|
155
|
+
"type": "object",
|
|
156
|
+
"required": ["id", "title"],
|
|
157
|
+
"additionalProperties": true,
|
|
158
|
+
"properties": {
|
|
159
|
+
"id": { "type": "string" },
|
|
160
|
+
"title": { "type": "string" },
|
|
161
|
+
"focus_snippet": { "type": "string" },
|
|
162
|
+
"lines": {
|
|
163
|
+
"type": "array",
|
|
164
|
+
"items": { "type": "integer", "minimum": 1 }
|
|
165
|
+
},
|
|
166
|
+
"note": { "type": "string" }
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
"exercises": {
|
|
173
|
+
"type": "array",
|
|
174
|
+
"items": {
|
|
175
|
+
"type": "object",
|
|
176
|
+
"required": ["id", "type", "prompt"],
|
|
177
|
+
"additionalProperties": true,
|
|
178
|
+
"properties": {
|
|
179
|
+
"id": { "type": "string" },
|
|
180
|
+
"type": {
|
|
181
|
+
"type": "string",
|
|
182
|
+
"enum": [
|
|
183
|
+
"predict_output",
|
|
184
|
+
"spot_bug",
|
|
185
|
+
"fix_code",
|
|
186
|
+
"short",
|
|
187
|
+
"choice"
|
|
188
|
+
]
|
|
189
|
+
},
|
|
190
|
+
"prompt": { "type": "string" },
|
|
191
|
+
"hint": { "type": "string" },
|
|
192
|
+
"snippet_id": { "type": "string" },
|
|
193
|
+
"choices": {
|
|
194
|
+
"type": "array",
|
|
195
|
+
"items": {
|
|
196
|
+
"type": "object",
|
|
197
|
+
"required": ["id", "label"],
|
|
198
|
+
"properties": {
|
|
199
|
+
"id": { "type": "string" },
|
|
200
|
+
"label": { "type": "string" }
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
"hints": {
|
|
208
|
+
"type": "array",
|
|
209
|
+
"items": { "type": "string" }
|
|
210
|
+
},
|
|
211
|
+
"solution": {
|
|
212
|
+
"type": "object",
|
|
213
|
+
"additionalProperties": true,
|
|
214
|
+
"description": "默认不渲染;由 agent 决定是否展示。",
|
|
215
|
+
"properties": {
|
|
216
|
+
"code": { "type": "string" },
|
|
217
|
+
"explanation": { "type": "string" }
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
"next": {
|
|
221
|
+
"type": "array",
|
|
222
|
+
"items": { "type": "string" }
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|