learn-anything-cli 0.4.0 → 0.4.2

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 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 generates a hierarchical knowledge map (`.learn/topics/<topic>/knowledge-map.md`), creates a learning state tracker (`state.yaml`), and presents the landscape for you to choose your own path.
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
- │ ├── knowledge-map.md
85
- │ ├── state.yaml
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 生成层次化知识图谱(`.learn/topics/<topic>/knowledge-map.md`),创建学习状态跟踪文件(`state.yaml`),展示知识全景让你自主选择学习路径。
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
- │ ├── knowledge-map.md
83
- │ ├── state.yaml
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
- - state.json has \`变量声明与数据类型\` → \`变量声明与数据类型-2026-05-24.md\`
60
- - state.json has \`Scope & Closures\` → \`Scope-Closures-2026-05-24.md\`
61
- - state.json has \`Event Loop\` → \`Event-Loop-2026-05-24.md\`
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 {
@@ -1,5 +1,5 @@
1
1
  const SKILL_NAME = 'learn-anything-practice';
2
- const SKILL_DESCRIPTION = 'Master concepts through hands-on practice. Coding topics get real project files to edit in your IDE; conceptual topics get chat-based discussion. Dual-mode: Project Mode + Chat Mode.';
2
+ const SKILL_DESCRIPTION = 'Master concepts through hands-on practice. Coding topics get real project files to edit in your IDE; conceptual topics get chat-based discussion. Dual-mode (Project Mode + Chat Mode).';
3
3
  const INSTRUCTIONS = `Always respond in the same language the user uses.
4
4
  If the user speaks Chinese, explain all concepts, examples, and guidance in Chinese.
5
5
 
@@ -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 to generate knowledge-map.md
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 to generate knowledge-map.md → present knowledge map and guide the user
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "learn-anything-cli",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "AI-powered recursive learning system with Socratic method and TDD practice",
5
5
  "keywords": [
6
6
  "learn-anything-cli",