learn-anything-cli 0.4.0 → 0.4.1
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 +3 -3
- package/README.zh-CN.md +3 -3
- package/dist/core/init.js +4 -0
- package/dist/core/templates/workflows/learn-explain.js +10 -6
- package/dist/core/templates/workflows/learn-topic.js +19 -3
- package/dist/scripts/init-sessions.d.mts +13 -0
- package/dist/scripts/init-sessions.mjs +89 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -43,7 +43,7 @@ After init, your AI assistant gains five learning commands:
|
|
|
43
43
|
|
|
44
44
|
### `/learn:topic <topic-name>` — Initialize a Topic
|
|
45
45
|
|
|
46
|
-
The AI
|
|
46
|
+
The AI creates a learning state file (`state.json`) and generates a knowledge map (`knowledge-map.md`) via `render.mjs`, then presents the landscape for you to choose your own path.
|
|
47
47
|
|
|
48
48
|
### `/learn:explain <concept-name>` — Recursive Deep Dive
|
|
49
49
|
|
|
@@ -81,8 +81,8 @@ Your Project/
|
|
|
81
81
|
├── .learn/ # Your learning data (knowledge maps, progress)
|
|
82
82
|
│ └── topics/
|
|
83
83
|
│ └── javascript/
|
|
84
|
-
│ ├──
|
|
85
|
-
│ ├── state.
|
|
84
|
+
│ ├── state.json # Learning data (single source of truth)
|
|
85
|
+
│ ├── knowledge-map.md # Auto-generated by render.mjs from state.json
|
|
86
86
|
│ └── sessions/
|
|
87
87
|
└── ...
|
|
88
88
|
```
|
package/README.zh-CN.md
CHANGED
|
@@ -41,7 +41,7 @@ learn-anything init
|
|
|
41
41
|
|
|
42
42
|
### `/learn:topic <topic-name>` — 初始化主题
|
|
43
43
|
|
|
44
|
-
AI
|
|
44
|
+
AI 创建学习状态文件(`state.json`),并通过 `render.mjs` 生成知识图谱(`knowledge-map.md`),展示知识全景让你自主选择学习路径。
|
|
45
45
|
|
|
46
46
|
### `/learn:explain <concept-name>` — 递归式深度学习
|
|
47
47
|
|
|
@@ -79,8 +79,8 @@ npx learn-anything-cli update
|
|
|
79
79
|
├── .learn/ # 你的学习数据(知识图谱、进度)
|
|
80
80
|
│ └── topics/
|
|
81
81
|
│ └── javascript/
|
|
82
|
-
│ ├──
|
|
83
|
-
│ ├── state.
|
|
82
|
+
│ ├── state.json # 学习数据(唯一数据源)
|
|
83
|
+
│ ├── knowledge-map.md # 由 render.mjs 从 state.json 自动生成
|
|
84
84
|
│ └── sessions/
|
|
85
85
|
└── ...
|
|
86
86
|
```
|
package/dist/core/init.js
CHANGED
|
@@ -162,6 +162,10 @@ export class InitCommand {
|
|
|
162
162
|
await FileSystemUtils.writeFile(path.join(scriptsDir, 'utils.mjs'), this.readCompiledScript('utils.mjs'));
|
|
163
163
|
await FileSystemUtils.writeFile(path.join(scriptsDir, 'render.mjs'), this.readCompiledScript('render.mjs'));
|
|
164
164
|
}
|
|
165
|
+
// topic -> init-sessions.mjs
|
|
166
|
+
if (entry.dirName === 'learn-anything-topic') {
|
|
167
|
+
await FileSystemUtils.writeFile(path.join(scriptsDir, 'init-sessions.mjs'), this.readCompiledScript('init-sessions.mjs'));
|
|
168
|
+
}
|
|
165
169
|
// status → utils.mjs + status.mjs
|
|
166
170
|
if (entry.dirName === 'learn-anything-status') {
|
|
167
171
|
await FileSystemUtils.writeFile(path.join(scriptsDir, 'utils.mjs'), this.readCompiledScript('utils.mjs'));
|
|
@@ -51,17 +51,21 @@ Structure your explanation:
|
|
|
51
51
|
|
|
52
52
|
**A) Determine the filename:**
|
|
53
53
|
|
|
54
|
-
Use the concept name exactly as it appears in state.json, in the same language. Convert to kebab-case and append the date:
|
|
54
|
+
Use the concept name exactly as it appears in state.json, in the same language. Convert to kebab-case and append the date. Place the file in the subdirectory matching the domain's \`slug\` field from state.json:
|
|
55
55
|
|
|
56
|
-
> \`./.learn/topics/<topic-name>/sessions/<concept-name-as-is>-YYYY-MM-DD.md\`
|
|
56
|
+
> \`./.learn/topics/<topic-name>/sessions/<domain-slug>/<concept-name-as-is>-YYYY-MM-DD.md\`
|
|
57
|
+
|
|
58
|
+
Where \`<domain-slug>\` is the \`slug\` field of the domain that contains this concept.
|
|
57
59
|
|
|
58
60
|
Examples:
|
|
59
|
-
-
|
|
60
|
-
-
|
|
61
|
-
-
|
|
61
|
+
- concept \`变量声明与数据类型\` in domain with slug \`语言基础\` → \`sessions/语言基础/变量声明与数据类型-2026-05-24.md\`
|
|
62
|
+
- concept \`Scope & Closures\` in domain with slug \`函数与作用域\` → \`sessions/函数与作用域/Scope-Closures-2026-05-24.md\`
|
|
63
|
+
- concept \`Event Loop\` in domain with slug \`async-programming\` → \`sessions/async-programming/Event-Loop-2026-05-24.md\`
|
|
62
64
|
|
|
63
65
|
Match the language the user is learning in — don't force-translate.
|
|
64
66
|
|
|
67
|
+
⚠️ If the domain subdirectory does not exist, create it first: \`mkdir -p ./.learn/topics/<topic-name>/sessions/<domain-slug>\`
|
|
68
|
+
|
|
65
69
|
**B) Write the session file** containing: positioning, analogy, core mechanism, code example with walkthrough, misconceptions, Socratic check, and quick summary. The file should be self-contained — re-readable without the chat.
|
|
66
70
|
|
|
67
71
|
Session file format:
|
|
@@ -153,7 +157,7 @@ Follow the workflow defined in the skill:
|
|
|
153
157
|
1. Load context: match topic → read state.json (single source of truth, do NOT read knowledge-map.md)
|
|
154
158
|
2. Assess user level (beginner/intermediate/advanced) and adjust teaching strategy
|
|
155
159
|
3. Compose the full explanation: positioning → analogy → core mechanism → code example → common misconceptions → Socratic check
|
|
156
|
-
4. CRITICAL — Write the session file FIRST (./.learn/topics/<topic>/sessions/<concept-name>-YYYY-MM-DD.md, matching the user's language), then echo the file content verbatim to the conversation. Also update state.json with Edit (last_explained, explain_count, status, confidence). Then run render.mjs to regenerate knowledge-map.md.
|
|
160
|
+
4. CRITICAL — Write the session file FIRST (./.learn/topics/<topic>/sessions/<domain-slug>/<concept-name>-YYYY-MM-DD.md, where <domain-slug> comes from state.json, matching the user's language), then echo the file content verbatim to the conversation. Also update state.json with Edit (last_explained, explain_count, status, confidence). Then run render.mjs to regenerate knowledge-map.md.
|
|
157
161
|
5. Identify sub-topics as recursive entry points (only AFTER saving the session and echoing to conversation)`;
|
|
158
162
|
export function getLearnExplainSkillTemplate() {
|
|
159
163
|
return {
|
|
@@ -77,7 +77,7 @@ Based on your expert understanding of "<topic-name>", generate a hierarchical kn
|
|
|
77
77
|
- **Slug format**: lowercase kebab-case ("Scope & Closures" → "scope-closures").
|
|
78
78
|
- All initial concepts: status "unexplored", confidence 0, counts 0, dates null.
|
|
79
79
|
|
|
80
|
-
### Step 4: Run render.mjs
|
|
80
|
+
### Step 4: Run render.mjs and init-sessions.mjs
|
|
81
81
|
|
|
82
82
|
\`\`\`bash
|
|
83
83
|
SCRIPT=$(find . -path '*/learn-anything-topic/scripts/render.mjs' -print -quit 2>/dev/null)
|
|
@@ -86,6 +86,13 @@ node "$SCRIPT" ./.learn/topics/<topic-name>
|
|
|
86
86
|
|
|
87
87
|
render.mjs validates state.json against the v1 schema and generates knowledge-map.md. If validation fails, fix state.json and re-run render.mjs. Do NOT manually write knowledge-map.md.
|
|
88
88
|
|
|
89
|
+
\`\`\`bash
|
|
90
|
+
SCRIPT=$(find . -path '*/learn-anything-topic/scripts/init-sessions.mjs' -print -quit 2>/dev/null)
|
|
91
|
+
node "$SCRIPT" ./.learn/topics/<topic-name>
|
|
92
|
+
\`\`\`
|
|
93
|
+
|
|
94
|
+
init-sessions.mjs reads state.json and creates domain subdirectories under \`sessions/\` (based on each domain's \`slug\`). This organizes future learning session files by domain. Safe to re-run — existing directories are skipped.
|
|
95
|
+
|
|
89
96
|
### Step 5: Present the knowledge map
|
|
90
97
|
|
|
91
98
|
Display the knowledge map as an ASCII tree:
|
|
@@ -124,6 +131,15 @@ Then guide the user:
|
|
|
124
131
|
|
|
125
132
|
Read \`./.learn/topics/<topic-name>/state.json\` — state.json is the single source of truth, do NOT read knowledge-map.md or state.yaml.
|
|
126
133
|
|
|
134
|
+
### Step 2.5: Run init-sessions.mjs to ensure domain directories exist
|
|
135
|
+
|
|
136
|
+
\`\`\`bash
|
|
137
|
+
SCRIPT=$(find . -path '*/learn-anything-topic/scripts/init-sessions.mjs' -print -quit 2>/dev/null)
|
|
138
|
+
node "$SCRIPT" ./.learn/topics/<topic-name>
|
|
139
|
+
\`\`\`
|
|
140
|
+
|
|
141
|
+
This ensures domain subdirectories under \`sessions/\` are created (in case they were not created before or new domains were added). Safe to re-run.
|
|
142
|
+
|
|
127
143
|
### Step 3: Calculate and display progress
|
|
128
144
|
|
|
129
145
|
From the domains/concepts structure, calculate: 🟢 mastered, 🔵 in progress, 🟠 needs practice, ⚪ unexplored.
|
|
@@ -158,8 +174,8 @@ const COMMAND_DESCRIPTION = 'Initialize or load a learning topic — view knowle
|
|
|
158
174
|
const COMMAND_CONTENT = `Use the learn-anything-topic skill to handle the user's /learn <topic-name> request.
|
|
159
175
|
Follow the workflow defined in the skill:
|
|
160
176
|
1. Determine if the topic exists
|
|
161
|
-
2. New topic: create directory structure → generate state.json (v1 with domains/concepts hierarchy) → run render.mjs
|
|
162
|
-
3. Existing topic: read state.json → calculate progress → give personalized recommendations`;
|
|
177
|
+
2. New topic: create directory structure → generate state.json (v1 with domains/concepts hierarchy) → run render.mjs → run init-sessions.mjs → present knowledge map and guide the user
|
|
178
|
+
3. Existing topic: read state.json → run init-sessions.mjs → calculate progress → give personalized recommendations`;
|
|
163
179
|
export function getLearnTopicSkillTemplate() {
|
|
164
180
|
return {
|
|
165
181
|
name: SKILL_NAME,
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* init-sessions.mjs — standalone script
|
|
4
|
+
* Reads state.json (v1) and creates domain subdirectories under sessions/.
|
|
5
|
+
*
|
|
6
|
+
* Usage: node init-sessions.mjs <topic-dir>
|
|
7
|
+
*
|
|
8
|
+
* This file is compiled from src/scripts/init-sessions.mts via tsc and
|
|
9
|
+
* copied into each skill's scripts/ directory by init/update.
|
|
10
|
+
*/
|
|
11
|
+
import type { StateV1 } from './utils.mjs';
|
|
12
|
+
export declare function initSessions(topicDir: string, state: StateV1): number;
|
|
13
|
+
//# sourceMappingURL=init-sessions.d.mts.map
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* init-sessions.mjs — standalone script
|
|
4
|
+
* Reads state.json (v1) and creates domain subdirectories under sessions/.
|
|
5
|
+
*
|
|
6
|
+
* Usage: node init-sessions.mjs <topic-dir>
|
|
7
|
+
*
|
|
8
|
+
* This file is compiled from src/scripts/init-sessions.mts via tsc and
|
|
9
|
+
* copied into each skill's scripts/ directory by init/update.
|
|
10
|
+
*/
|
|
11
|
+
import { existsSync, mkdirSync, readFileSync } from 'node:fs';
|
|
12
|
+
import { join, resolve } from 'node:path';
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
14
|
+
import { validateStateV1 } from './utils.mjs';
|
|
15
|
+
/* ------------------------------------------------------------------ */
|
|
16
|
+
/* Init Sessions */
|
|
17
|
+
/* ------------------------------------------------------------------ */
|
|
18
|
+
export function initSessions(topicDir, state) {
|
|
19
|
+
const sessionsDir = join(topicDir, 'sessions');
|
|
20
|
+
// Ensure the top-level sessions/ directory exists
|
|
21
|
+
if (!existsSync(sessionsDir)) {
|
|
22
|
+
mkdirSync(sessionsDir, { recursive: true });
|
|
23
|
+
}
|
|
24
|
+
let created = 0;
|
|
25
|
+
for (const domain of state.domains) {
|
|
26
|
+
const domainDir = join(sessionsDir, domain.slug);
|
|
27
|
+
if (!existsSync(domainDir)) {
|
|
28
|
+
mkdirSync(domainDir, { recursive: true });
|
|
29
|
+
created++;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return created;
|
|
33
|
+
}
|
|
34
|
+
/* ------------------------------------------------------------------ */
|
|
35
|
+
/* CLI */
|
|
36
|
+
/* ------------------------------------------------------------------ */
|
|
37
|
+
function usage() {
|
|
38
|
+
const script = process.argv[1]?.split('/').pop() || 'init-sessions.mjs';
|
|
39
|
+
console.error(`Usage: node ${script} <topic-dir>`);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
function main() {
|
|
43
|
+
const args = process.argv.slice(2);
|
|
44
|
+
if (args.length === 0) {
|
|
45
|
+
usage();
|
|
46
|
+
}
|
|
47
|
+
const topicDir = resolve(args[0]);
|
|
48
|
+
const statePath = join(topicDir, 'state.json');
|
|
49
|
+
// 1. Read state.json
|
|
50
|
+
let raw;
|
|
51
|
+
try {
|
|
52
|
+
raw = readFileSync(statePath, 'utf-8');
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
console.error(`Error: state.json not found at ${statePath}`, error);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
// 2. Parse JSON
|
|
59
|
+
let data;
|
|
60
|
+
try {
|
|
61
|
+
data = JSON.parse(raw);
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
65
|
+
console.error(`Error: Failed to parse state.json: ${msg}`);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
// 3. Validate v1 format
|
|
69
|
+
const errors = validateStateV1(data);
|
|
70
|
+
if (errors.length > 0) {
|
|
71
|
+
console.error('Error: state.json validation failed:');
|
|
72
|
+
for (const e of errors) {
|
|
73
|
+
console.error(` .${e.path}: ${e.message}`);
|
|
74
|
+
}
|
|
75
|
+
console.error('Fix the above issues in state.json and re-run init-sessions.mjs.');
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
const state = data;
|
|
79
|
+
// 4. Create domain subdirectories
|
|
80
|
+
const created = initSessions(topicDir, state);
|
|
81
|
+
// Summary to stdout
|
|
82
|
+
console.log(`Initialized ${state.domains.length} domain directories under sessions/ (${created} new) for "${state.topic}"`);
|
|
83
|
+
}
|
|
84
|
+
const isMain = process.argv[1] != null &&
|
|
85
|
+
fileURLToPath(import.meta.url) === resolve(process.argv[1]);
|
|
86
|
+
if (isMain) {
|
|
87
|
+
main();
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=init-sessions.mjs.map
|