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.
- package/CHANGELOG.md +30 -0
- package/package.json +1 -1
- package/skills/mastery-loop/SKILL.md +22 -21
- package/skills/mastery-loop/exam-templates.md +102 -0
- package/skills/mastery-loop/scripts/_lib.mjs +408 -0
- package/skills/mastery-loop/scripts/aggregate.mjs +155 -0
- package/skills/mastery-loop/scripts/graph.mjs +448 -0
- package/skills/mastery-loop/scripts/ledger.mjs +223 -0
- package/skills/mastery-loop/scripts/rewrite-links.mjs +81 -0
- package/skills/mastery-loop/scripts/scan.mjs +276 -0
- package/skills/mastery-loop/scripts/validate.mjs +115 -59
- package/skills/mastery-loop/templates.md +210 -62
- package/skills/mastery-loop-setup/SKILL.md +105 -76
- package/skills/mastery-loop-setup/seeds/config.json +39 -0
- package/skills/mastery-loop-setup/seeds/error-taxonomy.json +4 -0
- package/skills/mastery-loop-setup/seeds/errors.jsonl +1 -0
- package/skills/mastery-loop-setup/seeds/graph.json +13 -0
- package/skills/mastery-loop-setup/seeds/ledger.jsonl +2 -0
- package/skills/mastery-loop-setup/seeds/moc.md +15 -0
- package/skills/mastery-loop-setup/seeds/note.md +33 -0
- package/skills/mastery-loop-setup/seeds/paper-format.json +15 -0
- package/skills/mastery-loop-setup/seeds/state.jsonl +2 -0
- package/skills/mastery-loop-setup/seeds/config.md +0 -34
- package/skills/mastery-loop-setup/seeds/error-taxonomy.md +0 -17
- package/skills/mastery-loop-setup/seeds/errors.md +0 -48
- package/skills/mastery-loop-setup/seeds/index.md +0 -25
- package/skills/mastery-loop-setup/seeds/ledger.md +0 -26
- 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
|
-
//
|
|
4
|
+
// 用途:本脚本是结构校验器 —— 查 kb/ 与 sys/ 是否按契约建齐。
|
|
5
|
+
// 契约正文:../templates.md (第一节「库的布局」)
|
|
5
6
|
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
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
|
|
13
|
-
{
|
|
14
|
-
{
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
{
|
|
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
|
-
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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 (
|
|
53
|
-
|
|
54
|
-
if (
|
|
55
|
-
|
|
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('
|
|
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
|
-
|
|
82
|
-
|
|
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
|
-
|
|
86
|
-
|
|
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
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
99
|
-
|
|
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.
|
|
103
|
-
for (const
|
|
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('✅
|
|
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
|
-
#
|
|
1
|
+
# 契约正文(Contract)
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
74
|
+
# <中文名> <英文名>
|
|
75
|
+
|
|
76
|
+
## 一句话
|
|
77
|
+
## 核心内容
|
|
78
|
+
## 前置知识
|
|
79
|
+
## 后置知识
|
|
80
|
+
## 易错点 / 考点
|
|
81
|
+
## 讲义出处
|
|
82
|
+
```
|
|
14
83
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
26
|
-
(b) ...
|
|
118
|
+
---
|
|
27
119
|
|
|
28
|
-
|
|
29
|
-
思维链:
|
|
120
|
+
## 四、分类与范围
|
|
30
121
|
|
|
31
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
|
56
|
-
|
|
|
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
|
-
##
|
|
217
|
+
## 九、git 与历史
|
|
77
218
|
|
|
78
|
-
|
|
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
|
-
```
|
|
83
|
-
|
|
226
|
+
```json
|
|
227
|
+
{"at":"2026-10-14 20:30","by":"user","what":"config:分类","from":"","to":"课程 × 讲次","why":"用户确认"}
|
|
228
|
+
```
|
|
84
229
|
|
|
85
|
-
|
|
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
|
-
|
|
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
|
-
-
|
|
106
|
-
-
|
|
107
|
-
-
|
|
252
|
+
- **配比**(按本次范围题池统算):60% 熟练度最低 · 30% `是否学习过 = 否` · 10% `最后复习日期` 最久远。
|
|
253
|
+
- **达标**(逐点):`总共出过 ≥ 5` **且** `熟练程度 ≥ 本次目标正确率`;`总共出过 < 5` 标「样本不足」。
|
|
254
|
+
- **账本字段**:`知识点 / 是否学习过 / 做对 / 总共出过 / 熟练程度 / 最后复习日期`。
|
|
255
|
+
- **做对** = 思维链对 **且** 答案对(蒙对不算)。
|