dsh-mastery-loop 0.1.0 → 0.2.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.
Files changed (28) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/package.json +1 -1
  3. package/skills/mastery-loop/SKILL.md +22 -21
  4. package/skills/mastery-loop/exam-templates.md +102 -0
  5. package/skills/mastery-loop/scripts/_lib.mjs +408 -0
  6. package/skills/mastery-loop/scripts/aggregate.mjs +155 -0
  7. package/skills/mastery-loop/scripts/graph.mjs +448 -0
  8. package/skills/mastery-loop/scripts/ledger.mjs +223 -0
  9. package/skills/mastery-loop/scripts/rewrite-links.mjs +81 -0
  10. package/skills/mastery-loop/scripts/scan.mjs +276 -0
  11. package/skills/mastery-loop/scripts/validate.mjs +115 -59
  12. package/skills/mastery-loop/templates.md +210 -62
  13. package/skills/mastery-loop-setup/SKILL.md +105 -76
  14. package/skills/mastery-loop-setup/seeds/config.json +39 -0
  15. package/skills/mastery-loop-setup/seeds/error-taxonomy.json +4 -0
  16. package/skills/mastery-loop-setup/seeds/errors.jsonl +1 -0
  17. package/skills/mastery-loop-setup/seeds/graph.json +13 -0
  18. package/skills/mastery-loop-setup/seeds/ledger.jsonl +2 -0
  19. package/skills/mastery-loop-setup/seeds/moc.md +15 -0
  20. package/skills/mastery-loop-setup/seeds/note.md +33 -0
  21. package/skills/mastery-loop-setup/seeds/paper-format.json +15 -0
  22. package/skills/mastery-loop-setup/seeds/state.jsonl +2 -0
  23. package/skills/mastery-loop-setup/seeds/config.md +0 -34
  24. package/skills/mastery-loop-setup/seeds/error-taxonomy.md +0 -17
  25. package/skills/mastery-loop-setup/seeds/errors.md +0 -48
  26. package/skills/mastery-loop-setup/seeds/index.md +0 -25
  27. package/skills/mastery-loop-setup/seeds/ledger.md +0 -26
  28. package/skills/mastery-loop-setup/seeds/paper-format.md +0 -30
@@ -1,21 +1,29 @@
1
1
  #!/usr/bin/env node
2
- // mastery-loop 初始化校验
2
+ // mastery-loop 结构校验
3
3
  //
4
- // 用法:node validate.mjs <知识库根目录>
4
+ // 用途:本脚本是结构校验器 —— 查 kb/ 与 sys/ 是否按契约建齐。
5
+ // 契约正文:../templates.md (第一节「库的布局」)
5
6
  //
6
- // 判据(票 18 M4):知识库根目录下 6 个固定文件名都在 = 初始化完成。
7
- // 硬要求:**永远不要崩溃** —— 干净目录上应当报「6 个全缺」并以退出码 1 结束。
7
+ // 用法:node validate.mjs <工作区根>
8
+ //
9
+ // 硬要求:**永远不要崩溃** —— 干净目录上应报「全缺」并以退出码 1 结束。
8
10
 
9
- import { readFileSync, existsSync, statSync } from 'node:fs';
11
+ import { readFileSync, existsSync, statSync, readdirSync } from 'node:fs';
10
12
  import { join, resolve } from 'node:path';
11
13
 
12
- const FILES = [
13
- { name: 'ledger.md', label: '复习账本', required: true },
14
- { name: 'index.md', label: '反向索引', required: true },
15
- { name: 'errors.md', label: '错误原因表', required: true },
16
- { name: 'paper-format.md', label: '卷子格式', required: true },
17
- { name: 'error-taxonomy.md', label: '失分原因分类', required: true },
18
- { name: 'config.md', label: '配置', required: true },
14
+ const KB_FILES = [
15
+ { rel: 'kb/knowledge', kind: 'dir', label: '知识点笔记目录' },
16
+ { rel: 'kb/MOC', kind: 'dir', label: '聚合产物目录' },
17
+ ];
18
+
19
+ const SYS_FILES = [
20
+ { rel: 'sys/config.json', kind: 'json', label: '设定(路径 / 分类 / 语言)' },
21
+ { rel: 'sys/graph.json', kind: 'json', label: '关系图' },
22
+ { rel: 'sys/ledger.jsonl', kind: 'jsonl', label: '账本(真值)' },
23
+ { rel: 'sys/errors.jsonl', kind: 'jsonl', label: '错误原因表' },
24
+ { rel: 'sys/error-taxonomy.json', kind: 'json', label: '失分原因分类' },
25
+ { rel: 'sys/paper-format.json', kind: 'json', label: '卷面参数' },
26
+ { rel: 'sys/state.jsonl', kind: 'jsonl', label: '状态(哈希)' },
19
27
  ];
20
28
 
21
29
  function readIfPossible(path) {
@@ -26,49 +34,93 @@ function readIfPossible(path) {
26
34
  }
27
35
  }
28
36
 
29
- /** 每个文件一点结构检查;返回问题列表(空 = 通过) */
30
- function inspect(name, text) {
31
- const problems = [];
32
- if (text === null) return problems;
33
-
34
- if (name === 'ledger.md') {
35
- if (!/首次对齐/.test(text)) problems.push('缺少「首次对齐」标记行 —— 初始化未完成');
36
- if (!/\|\s*知识点\s*\|/.test(text)) problems.push('缺少账本表头(`知识点 / 是否学习过 / …`)');
37
- }
38
-
39
- if (name === 'index.md') {
40
- if (!/^#\s/m.test(text)) problems.push('缺少 H1 标题');
41
- }
42
-
43
- if (name === 'errors.md') {
44
- if (!/^#\s/m.test(text)) problems.push('缺少 H1 标题');
45
- }
37
+ function countNotes(dir) {
38
+ let n = 0;
39
+ const walk = (d, depth) => {
40
+ if (depth > 3) return;
41
+ let entries = [];
42
+ try {
43
+ entries = readdirSync(d, { withFileTypes: true });
44
+ } catch {
45
+ return;
46
+ }
47
+ for (const e of entries) {
48
+ if (e.name.startsWith('.')) continue;
49
+ if (e.isDirectory()) walk(join(d, e.name), depth + 1);
50
+ else if (/\.md$/i.test(e.name)) n++;
51
+ }
52
+ };
53
+ walk(dir, 0);
54
+ return n;
55
+ }
46
56
 
47
- if (name === 'paper-format.md') {
48
- const courses = (text.match(/^##\s+(?!没有往年卷)/gm) || []).length;
49
- if (courses === 0) problems.push('还没有任何课程条目(每个课程的每个考试各一条)');
57
+ /** 返回问题列表(空 = 通过) */
58
+ function inspect(rel, kind) {
59
+ const problems = [];
60
+ const path = rel;
61
+ const text = readIfPossible(path);
62
+
63
+ if (kind === 'json') {
64
+ let data;
65
+ try {
66
+ data = JSON.parse(text);
67
+ } catch (err) {
68
+ problems.push(`不是合法 JSON:${err.message}`);
69
+ return problems;
70
+ }
71
+ if (typeof data !== 'object' || data === null || Array.isArray(data)) {
72
+ problems.push('顶层不是对象');
73
+ return problems;
74
+ }
75
+ if (!data['用途']) problems.push('缺「用途」字段(每个文件开头要有一句「这个文件是干什么的」)');
76
+ if (rel.endsWith('config.json')) {
77
+ if (!data['分类'] || !Array.isArray(data['分类']?.['层级']) || data['分类']['层级'].length === 0) {
78
+ problems.push('缺「分类.层级」—— 分类是全库级定义,必填');
79
+ }
80
+ if (!data['路径'] || !data['路径']['知识库']) problems.push('缺「路径.知识库」');
81
+ }
82
+ if (rel.endsWith('graph.json')) {
83
+ if (!Array.isArray(data['edges'])) problems.push('缺「edges」数组');
84
+ if (!data['规则']) problems.push('缺「规则」字段(前置判据)');
85
+ }
50
86
  }
51
87
 
52
- if (name === 'config.md') {
53
- if (!/材料根目录/.test(text)) problems.push('缺少「材料根目录」');
54
- if (!/知识库根目录/.test(text)) problems.push('缺少「知识库根目录」');
55
- if (/<绝对路径>/.test(text)) problems.push('路径还是占位符,没填');
88
+ if (kind === 'jsonl') {
89
+ const lines = String(text).split(/\r?\n/).filter((l) => l.trim());
90
+ if (!lines.length) {
91
+ problems.push('空文件 —— 至少要有一行 header');
92
+ return problems;
93
+ }
94
+ let head = null;
95
+ try {
96
+ head = JSON.parse(lines[0]);
97
+ } catch (err) {
98
+ problems.push(`第一行不是合法 JSON:${err.message}`);
99
+ }
100
+ if (head && head.kind !== 'header') problems.push('第一行不是 `{"kind":"header","用途":"…"}` 头部记录');
101
+ if (head && !head['用途']) problems.push('头部记录缺「用途」');
102
+ for (let i = 1; i < lines.length; i++) {
103
+ try {
104
+ JSON.parse(lines[i]);
105
+ } catch (err) {
106
+ problems.push(`第 ${i + 1} 行不是合法 JSON:${err.message}`);
107
+ break;
108
+ }
109
+ }
56
110
  }
57
111
 
58
- // error-taxonomy.md 初始为空是对的 —— 不检查内容
59
-
60
112
  return problems;
61
113
  }
62
114
 
63
115
  function main() {
64
116
  const arg = process.argv[2];
65
117
  if (!arg) {
66
- console.log('用法:node validate.mjs <知识库根目录>');
118
+ console.log('用法:node validate.mjs <工作区根>');
67
119
  process.exit(2);
68
120
  }
69
121
 
70
122
  const root = resolve(arg);
71
- console.log('校验知识库根目录:' + root);
123
+ console.log('校验工作区根:' + root);
72
124
 
73
125
  if (!existsSync(root) || !statSync(root).isDirectory()) {
74
126
  console.log('❌ 这个目录不存在,或不是目录。');
@@ -78,39 +130,44 @@ function main() {
78
130
  let missing = 0;
79
131
  let problematic = 0;
80
132
 
81
- for (const f of FILES) {
82
- const path = join(root, f.name);
83
- let text = null;
133
+ const dirs = [...KB_FILES, ...SYS_FILES].filter((f) => f.kind === 'dir');
134
+ const files = [...KB_FILES, ...SYS_FILES].filter((f) => f.kind !== 'dir');
84
135
 
85
- if (!existsSync(path)) {
86
- console.log(`❌ ${f.name} —— 缺失(${f.label})`);
136
+ for (const f of dirs) {
137
+ const p = join(root, f.rel);
138
+ if (!existsSync(p) || !statSync(p).isDirectory()) {
139
+ console.log(`❌ ${f.rel}/ —— 缺失(${f.label})`);
87
140
  missing++;
88
- continue;
141
+ } else if (f.rel === 'kb/knowledge') {
142
+ const n = countNotes(p);
143
+ console.log(`✅ ${f.rel}/ —— ${f.label}(${n} 篇知识点)`);
144
+ } else {
145
+ console.log(`✅ ${f.rel}/ —— ${f.label}`);
89
146
  }
147
+ }
90
148
 
91
- text = readIfPossible(path);
92
- if (text === null) {
93
- console.log(`❌ ${f.name} —— 存在但读不了`);
94
- problematic++;
149
+ for (const f of files) {
150
+ const p = join(root, f.rel);
151
+ if (!existsSync(p)) {
152
+ console.log(`❌ ${f.rel} —— 缺失(${f.label})`);
153
+ missing++;
95
154
  continue;
96
155
  }
97
-
98
- const problems = inspect(f.name, text);
99
- if (problems.length === 0) {
100
- console.log(`✅ ${f.name} —— ${f.label}`);
156
+ const problems = inspect(p, f.kind);
157
+ if (!problems.length) {
158
+ console.log(`✅ ${f.rel} —— ${f.label}`);
101
159
  } else {
102
- console.log(`⚠️ ${f.name} —— ${f.label}`);
103
- for (const p of problems) console.log(` · ${p}`);
160
+ console.log(`⚠️ ${f.rel} —— ${f.label}`);
161
+ for (const pr of problems) console.log(` · ${pr}`);
104
162
  problematic++;
105
163
  }
106
164
  }
107
165
 
108
166
  console.log('');
109
167
  if (missing === 0 && problematic === 0) {
110
- console.log('✅ 初始化已完成:6 个文件都在。');
168
+ console.log('✅ 结构齐:kb/ 与 sys/ 都按契约建好了。');
111
169
  process.exit(0);
112
170
  }
113
-
114
171
  console.log(`未完成:缺 ${missing} 个、有问题的 ${problematic} 个。`);
115
172
  console.log('跑一遍 `mastery-loop-setup` 补齐。');
116
173
  process.exit(1);
@@ -119,7 +176,6 @@ function main() {
119
176
  try {
120
177
  main();
121
178
  } catch (err) {
122
- // 无论如何都不要崩
123
179
  console.log('❌ 校验脚本自身出错:' + (err && err.message ? err.message : String(err)));
124
180
  process.exit(1);
125
181
  }
@@ -1,107 +1,255 @@
1
- # 固定参考(Fixed Reference)
1
+ # 契约正文(Contract)
2
2
 
3
- [`SKILL.md`](SKILL.md) 的模板与口径。**按需查,别通读。**
3
+ > **本文件是这套 skill 的契约正文**:库长什么样、每个文件干什么、什么能脚本化。
4
+ > `SKILL.md`(*复习主流程*)与 `mastery-loop-setup`(*一次性初始化*)都**只链接到这里**,不各写一份。
5
+ > 改契约**只改这个文件**。出题 / 判分 / 考前 list 的模板在 [exam-templates.md](exam-templates.md)。
4
6
 
5
7
  ---
6
8
 
7
- ## 试卷模板
9
+ ## 一、库的布局
8
10
 
9
- > ⚠️ **参数从 `paper-format.md` 查**对应考试的那一条 —— **不要从往年卷现抄**。
10
- > 查不到 → 用**默认出卷法:判断题 + 每题说明理由**。
11
+ 工作区根下**两个并列文件夹**:`kb/`(内容区,给人看)与 `sys/`(机器区,给脚本读)。
12
+
13
+ ```
14
+ <工作区根>/
15
+ ├── kb/
16
+ │ ├── knowledge/ # 知识点笔记,一篇一个知识点
17
+ │ └── MOC/ # 聚合产物:镜像分类层级
18
+ │ ├── 00 总览.md # 全库视图(可选)
19
+ │ └── <类1>/<类2>/… # 每层一份「该层的索引」,叶子层是讲次级 MOC
20
+ └── sys/
21
+ ├── config.json # 设定:路径 / 分类 / 语言 / 确认标记
22
+ ├── graph.json # 关系图:知识点的前置关系(真值)
23
+ ├── ledger.jsonl # 账本:逐知识点的复习统计(真值)
24
+ ├── errors.jsonl # 错误原因表
25
+ ├── error-taxonomy.json # 失分原因分类(初始为空,agent 维护)
26
+ ├── paper-format.json # 卷面参数(每个课程的每场考试一条)
27
+ ├── state.jsonl # 状态:内容哈希 / schema 版本 / 检查时间(用于跳过重扫)
28
+ └── changes.jsonl # 变更流水 —— **只在没有 git 且用户不装 git 时启用**
29
+ ```
30
+
31
+ **文件夹命名**:固定结构文件夹一律**英文、无特殊符号**(`kb` `knowledge` `MOC` `sys`)。
32
+ **类值目录**(`MOC/<类1>/…`)用**用户自己的分类值**(可以是中文);`config.json` 里给每个类值记一个 **ASCII slug** 供外部工具用。
33
+
34
+ **每个文件开头一句「本文件是什么」**:
35
+
36
+ - Markdown:第一行就是这句话;
37
+ - JSON:第一个字段是 `"用途"`;
38
+ - JSONL:第一行是一条 `{"kind":"header","用途":"…"}` 记录(脚本跳过它)。
39
+
40
+ ---
41
+
42
+ ## 二、知识点契约
43
+
44
+ ### frontmatter
45
+
46
+ ```yaml
47
+ ---
48
+ 类型: 知识点
49
+ 归属: ["[[Deep Learning/doc/Lecture/W1L1 Introduction to the Unit.pdf|深度学习 L1]]"]
50
+ aliases: ["Backpropagation"]
51
+ 标签: [深度学习, 神经网络, 核心]
52
+ ---
53
+ ```
54
+
55
+ | 字段 | 值 | 说明 |
56
+ | --- | --- | --- |
57
+ | `类型` | `知识点` | 结构化字段,不是标签 |
58
+ | `分类` | **类路径,多值** | **生成这篇知识点时就附上**(哪个类、哪个子类)。一条一个类路径,层级用 `分类.分隔符`(默认 `/`)分隔。脚本**只读这个字段**做聚合,不解析路径 |
59
+ | `归属` | **来源文件,多值,必填** | 这个知识点**出自哪份资料**(PDF / DOCX / 网页快照都算)。元素是带路径的 wikilink + 人类可读别名 |
60
+ | `aliases` | 英文名列表 | Obsidian 原生别名 |
61
+ | `标签` | 既有标签 | 既有标签(用于搜索/标签面板) |
62
+
63
+ **铁律**
64
+
65
+ - **没有 `课程:`、没有 `主题:`、没有 `难度:`** —— 课程与讲次是**从 `归属` 解析出来的**(见第四节),不是存的字段。
66
+ - **`归属` 是「来源」,不是「关系」** —— 前置/后置在 `sys/graph.json` 里。
67
+ - **列表字段不止 `归属`** —— `标签`、`aliases` 同样是列表。
68
+ - frontmatter 里的 **wikilink 必须加引号**(`[` 会被 YAML 当数组开头)。
69
+ - **契约之外的字段原样保留**,不清理。
70
+
71
+ ### 正文(固定顺序)
11
72
 
12
73
  ```markdown
13
- # PRACTICE EXAM PAPER — ROUND N
74
+ # <中文名> <英文名>
75
+
76
+ ## 一句话
77
+ ## 核心内容
78
+ ## 前置知识
79
+ ## 后置知识
80
+ ## 易错点 / 考点
81
+ ## 讲义出处
82
+ ```
14
83
 
15
- **Based on**: <paper-format.md 里的考试名>
16
- **Time Allowed**: <抄自 paper-format.md>
17
- **Total Marks**: <抄自 paper-format.md>
84
+ - `## 前置知识` = **逻辑判定**,唯一判据:**删掉它,本篇的核心内容就解释不通**(只收**直接**前置)。
85
+ - `## 后置知识` = **由图反算,绝不手写**(脚本渲染)。
86
+ - 每条前置后面带 `—— <理由>`,与 `graph.json` 里该边的 `reason` **逐字一致**。
87
+ - **没有 `## 平行关联`**。
88
+
89
+ ### 链接写法
90
+
91
+ - **一律带路径**,**相对工作区根**(= Obsidian vault 根),**带后缀**,**带人类可读别名**:
92
+ `[[kb/knowledge/梯度.md|梯度]]` · `[[Deep Learning/doc/Lecture/W1L1 ….pdf|深度学习 L1]]`
93
+ - 代价:改名 / 搬目录会断链 —— 由 `scan.mjs`(*扫库脚本*)发现、`rewrite-links.mjs`(*重写链接脚本*)修。
18
94
 
19
95
  ---
20
96
 
21
- ## Question 1 (<marks> marks)
97
+ ## 三、关系图 `sys/graph.json`
98
+
99
+ ```json
100
+ {
101
+ "用途": "本文件是知识点关系图:只存边(from 是 to 的前置),节点就是 knowledge/ 里的笔记本身。",
102
+ "规则": "判据:删掉 from,to 的核心内容就解释不通。只收直接前置;同一篇的前置集合内两两不可比。",
103
+ "导出": "后置 = 本表的反向(脚本算);没有「平行」段。",
104
+ "version": 1,
105
+ "updatedAt": "2026-10-14 20:30",
106
+ "coverage": { "深度学习": { "done": ["L1"], "todo": ["L2"] } },
107
+ "edges": [
108
+ { "from": "梯度", "to": "最速下降与参数空间", "reason": "没有梯度就没有下降方向" }
109
+ ]
110
+ }
111
+ ```
22
112
 
23
- <题干>
113
+ - **边不带课程** —— 课程归属由知识点的 `归属`/标签解析,边只是纯逻辑。
114
+ - `reason` **≤30 字**,指出 `to` 依赖 `from` 的**那一步**。
115
+ - `coverage`:**按分类层级**记「哪些做完了、哪些没做」。
116
+ - 图**允许分批**:`coverage` 里没做的部分,校验不报错。
24
117
 
25
- (a) <小问> (<marks> marks)
26
- (b) ...
118
+ ---
27
119
 
28
- 答案:
29
- 思维链:
120
+ ## 四、分类与范围
30
121
 
31
- ## Question 2 (<marks> marks)
122
+ **分类是全库级定义**(层级与类值记在 `sys/config.json`),但**每篇知识点属于哪个类,是在生成这篇知识点的时候就附在它身上的** —— 存在 frontmatter 的 `分类` 字段里。**脚本只读这个字段做聚合**,不解析路径、不猜、不写正则。
32
123
 
33
- ...
124
+ ```yaml
125
+ 分类: ["深度学习/L3", "机器学习/L5"] # 一条一个类路径;层级用 `分类.分隔符`(默认 /)分隔
126
+ ```
34
127
 
35
- 答案:
36
- 思维链:
128
+ `config.json` 里记:
129
+
130
+ ```json
131
+ "分类": {
132
+ "层级": ["课程", "讲次"],
133
+ "字段": "分类",
134
+ "分隔符": "/",
135
+ "类值": {
136
+ "课程": [ { "值": "深度学习", "slug": "dl" } ],
137
+ "讲次": [ { "值": "L3", "slug": "l3" } ]
138
+ },
139
+ "确认": "(用户于 <YYYY-MM-DD HH:MM> 确认)"
140
+ }
141
+ ```
37
142
 
38
- **END OF PAPER**
143
+ - **一篇可以属于多个类**(`归属` 是多值的,类也跟着多值);
144
+ - **归类在生成知识点时定**,不由后面的脚本推;规则怎么算、按什么分,由 agent 在 setup 时**看这个库**再和用户确认(**不预设形状**:课程 / 章 / 模块 / 主题都行);
145
+ - **范围 = 分类里的一个或多个类路径**(例:`深度学习/L3`),脚本按它筛。
146
+
147
+ ---
148
+
149
+ ## 五、聚合与两区
150
+
151
+ `kb/MOC/` 下的一切都是**从 `归属` + 图聚合出来的**,不是手写枚举。每份 MOC / 索引里分两区:
152
+
153
+ ```markdown
154
+ <!-- AUTO:BEGIN -->
155
+ - [[kb/knowledge/梯度.md|梯度]] —— ∇J 是代价函数对全部参数的一阶偏导向量
156
+ <!-- AUTO:END -->
157
+
158
+ ## 主线(手写区)
39
159
  ```
40
160
 
41
- 硬性要求:
161
+ - **机器区**:`aggregate.mjs`(*聚合脚本*)生成与覆盖(按分类层级列 `- [[链接]] —— 一句话`、覆盖进度)。
162
+ - **手写区**:agent / 人写,**脚本永不碰**(主线、脉络、指定阅读、考点提示)。
163
+ - 聚合脚本**只做聚合**:不写待办、不打印摘要 —— **要说话的是 `scan.mjs`**。
164
+ - 「分组」不是独立概念:**分类有几层就分几层**;只有两层时 MOC 内部不再分组。
165
+
166
+ ---
167
+
168
+ ## 六、脚本边界
169
+
170
+ **脚本只做重复性、机械性、不需要变通的事。** 要理解内容、要随机应变、要交互的**留给 agent**。
171
+
172
+ | 脚本 | 干什么 | 谁触发 |
173
+ | --- | --- | --- |
174
+ | `graph.mjs` | 读写 `sys/graph.json`;六项校验;由图渲染两段 | agent |
175
+ | `aggregate.mjs` | 从 `归属` + 图聚合 MOC / 索引的**机器区** | agent |
176
+ | `scan.mjs` | 扫库五项(见第七节);读 `state.jsonl` **按哈希跳过没变的**;发现坏链接就提示跑 `rewrite-links.mjs` | agent |
177
+ | `rewrite-links.mjs` | 改名 / 搬目录后重写所有带路径链接 | agent(由 scan 触发) |
178
+ | `ledger.mjs` | 按 60/30/10 筛清单、算达标、**批改写回** | agent |
179
+ | `validate.mjs` | 查 `kb/` 与 `sys/` 的结构是否齐 | agent / setup |
42
180
 
43
- - **Round N** 每轮递增;**跨轮保持同一套卷子参数**,这样正确率才可比。
44
- - **每道题后面必须挂 `答案:` / `思维链:` 两行** —— 选择题也要。
45
- - 判断正误类小题,题干里就写明「**先判对错,再给理由**」。
46
- - 分值分配**照 `paper-format.md`**,不要自己发明比例。
47
- - 卷尾之后**不得**出现任何答案、提示、「解析见下」的字样。
181
+ **不能脚本化**(明确留 agent):判断谁是谁的前置、写 `reason`、出题、批改、**错误原因去重**(脚本会把「把 A1 和 B 弄混」和「把 A2 和 B 弄混」误判成重复)、frontmatter 规范化、分类协商。
182
+
183
+ **脚本纪律**:Node(零依赖,`node xxx.mjs`);**不拼 shell**(路径含中文 / 空格);只读写自己那类文件。
48
184
 
49
185
  ---
50
186
 
51
- ## 判分口径
187
+ ## 七、校验
188
+
189
+ ### 图(`graph.mjs check`,六项)
52
190
 
53
- | 题型 | 给分 | 扣分 / 零分 |
191
+ | # | 名称 | 判据 |
54
192
  | --- | --- | --- |
55
- | 判断正误 | 分类正确 + 解释命中关键词 | 只写 T/F 没解释 → 0;解释不引材料逻辑 → 减半 |
56
- | 计算 | 结果正确 + 中间步骤齐全 | 结果对但无 working → 扣步骤分;结果错但步骤对 → 给步骤分 |
57
- | 定义 | 关键词出现 + 给出具体示例 | 只背课本句式、无示例 → 不满分 |
58
- | 画图 / 证明 | 图元齐全(轴标签、方向、边界)/ 每步有依据 | 图对但无标注 → 扣分 |
193
+ | 1 | 双向自洽 | 边 `from→to` ⟺ `to` 的前置里有 `from` 且 `from` 的后置里有 `to`;正文里出现的关系必须在图里有边 |
194
+ | 2 | 无自链 | 没有 `from == to` |
195
+ | 3 | 无悬空 | `from` / `to` 都能找到对应笔记 |
196
+ | 4 | 同段无重复 | 同一小节里同一目标只出现一次 |
197
+ | 5 | 前后置互斥 | 同一篇的前置集合 ∩ 后置集合 = ∅ |
198
+ | 6 | 前置两两不可比 | 同一篇的前置里,任两条不可互为前置/后置(只收直接前置) |
199
+
200
+ ### 扫库(`scan.mjs`,五项)
59
201
 
60
- **总分与正确率按卷面原始分算**,不折算、不向上取整。
202
+ ① 悬空链接 ② 带路径链接与当前目录结构不符 ③ `config.json` 的分类 / 版本与现状不符 ④ 图与笔记两段不一致(= 图校验)⑤ 账本 / 错误原因里引用的知识点是否还存在。
61
203
 
62
- ### ⚠️ 与「做对」的区别
204
+ ---
63
205
 
64
- - **判分**用的是上面这张表(看答案)。
65
- - **账本里的「做对」是另一回事**:必须 **思维链对 且 答案对**。
66
- **蒙对**在评分表里可能得分,但在账本里**不算做对** —— 而且**要提炼一条错误原因**。
206
+ ## 八、排序(可选,不是契约)
67
207
 
68
- 批改纪律:
208
+ 本机**若装了 Miyo**,可以在**已按知识深度排好之后**,对「深度上并列、拿不准先后」的条目问一次相关度作参考:
209
+ `POST http://127.0.0.1:8742/v0/search/related`,body `{"file_path":"<已注册 folder 名>/<库内相对路径>","limit":N}`。
69
210
 
70
- - **给分严格,只认关键词。** 别给「思路对了」这种虚分。
71
- - 判错要**指到用户具体的句子**(在思维链里找),不要笼统说「理解不到位」。
72
- - 也要指出**答得好**的部分 —— 避免用户误以为整块都薄弱。
211
+ - **知识深度是定序者**,Miyo 只作参考;分数**不写进** frontmatter / 图;
212
+ - **没装就跳过**:不报错、不检查、不阻塞 —— 发布版**不依赖** Miyo;
213
+ - 实测提醒:`file_path` 三种分隔符都能用;真正 404 的是「缺已注册 folder 前缀 / 绝对路径 / 不带 `.md` / 路径不在索引里」。
73
214
 
74
215
  ---
75
216
 
76
- ## 考前 list
217
+ ## 九、git 与历史
77
218
 
78
- **独立成文**,与知识库根目录那 6 个文件并列。**只收出过错的点**,按知识点一条。
219
+ **能用 git 就用 git** —— 这条管两件事,都优先走 git:
79
220
 
80
- **从第一轮批改就开始攒** —— 每轮出错就把三行补进去,别等最后生成。
221
+ | 要回答的问题 | 有 git | 没有 git(且用户不装) |
222
+ | --- | --- | --- |
223
+ | **哪些文件变过**(决定要不要重看) | `git status` + 上次检查的提交号之后 `git diff` | `sys/state.jsonl` 里逐文件的内容哈希 |
224
+ | **发生过什么**(决定怎么追溯) | git 历史(skill **不自动 `commit`、绝不 `push`**,只提示) | `sys/changes.jsonl`(*变更流水*):一行一条,只追加不改 |
81
225
 
82
- ```markdown
83
- # 考前 list
226
+ ```json
227
+ {"at":"2026-10-14 20:30","by":"user","what":"config:分类","from":"","to":"课程 × 讲次","why":"用户确认"}
228
+ ```
84
229
 
85
- ## Section A: <主题>
230
+ - `sys/state.jsonl` 在 git 模式下**只有一行**(`{"mode":"git","last":"<commit>",…}`);没有 git 时才逐文件记哈希;
231
+ - 管「要不要重看」的是它,管「发生过什么」的是 git(或 `changes.jsonl`)—— **两者不是一回事**;
232
+ - 沙箱里抓子进程输出会被拒(EPERM),所以脚本调 git 时把输出**重定向到文件描述符**再读回来,而不是用管道。
86
233
 
87
- ### <知识点>
88
- > **规则**:<正确说法>
89
- > ⚠️ **常见错误**:<我实际犯的错 —— 与 errors.md 里的错误原因一致>
90
- > 🔑 **锚**:<一句话记忆钩子 / 反例>
234
+ ---
91
235
 
92
- ### <知识点>
93
- ...
236
+ ## 十、用户确认标记
94
237
 
95
- ## Section B: <主题>
96
- ...
238
+ **问出来的信息**(分类、路径、语言、范围、融合决定)后面跟一个括号:
97
239
 
98
- ## ⏱️ 考前 15 分钟速查
99
- - <最致命的 N 条,一行一条>
100
240
  ```
241
+ (用户于 2026-10-14 20:30 确认)
242
+ ```
243
+
244
+ - **精确到分钟**;**只标问出来的**(agent 自己推的不标);
245
+ - **就地只留最新一条**;历史翻 git(无 git 时翻 `changes.jsonl`);
246
+ - 适用范围:**发布出去的库同样适用**(不是开发者专用)。
247
+
248
+ ---
101
249
 
102
- 要求:
250
+ ## 十一、复习循环里用到的量
103
251
 
104
- - 一个知识点**只占三行**,超过说明还没提炼干净。
105
- - **`⚠️ 常见错误` 必须与 `errors.md` 里的错误原因逐字一致** —— 那是同一份事实,不要换措辞。
106
- - 锚点优先用**反例**或**极端情形**,不要口诀式废话。
107
- - 涵盖**全部轮次**,不重复同一意思的条目。
252
+ - **配比**(按本次范围题池统算):60% 熟练度最低 · 30% `是否学习过 = 否` · 10% `最后复习日期` 最久远。
253
+ - **达标**(逐点):`总共出过 ≥ 5` **且** `熟练程度 ≥ 本次目标正确率`;`总共出过 < 5` 标「样本不足」。
254
+ - **账本字段**:`知识点 / 是否学习过 / 做对 / 总共出过 / 熟练程度 / 最后复习日期`。
255
+ - **做对** = 思维链对 **且** 答案对(蒙对不算)。