learn-anything-cli 0.3.0 → 0.4.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 (34) hide show
  1. package/README.md +6 -0
  2. package/README.zh-CN.md +6 -0
  3. package/dist/cli/index.js +3 -0
  4. package/dist/core/init.d.ts +6 -0
  5. package/dist/core/init.js +67 -2
  6. package/dist/core/learn-protocol/index.d.ts +8 -0
  7. package/dist/core/learn-protocol/index.js +5 -0
  8. package/dist/core/learn-protocol/migrate.d.ts +52 -0
  9. package/dist/core/learn-protocol/migrate.js +259 -0
  10. package/dist/core/learn-protocol/parser.d.ts +33 -0
  11. package/dist/core/learn-protocol/parser.js +150 -0
  12. package/dist/core/learn-protocol/schema.d.ts +38 -0
  13. package/dist/core/learn-protocol/schema.js +43 -0
  14. package/dist/core/learn-protocol/slug.d.ts +13 -0
  15. package/dist/core/learn-protocol/slug.js +28 -0
  16. package/dist/core/learn-protocol/types.d.ts +63 -0
  17. package/dist/core/learn-protocol/types.js +2 -0
  18. package/dist/core/templates/context7-guidance.d.ts +13 -0
  19. package/dist/core/templates/context7-guidance.js +24 -0
  20. package/dist/core/templates/workflows/learn-explain.js +56 -139
  21. package/dist/core/templates/workflows/learn-practice.js +88 -284
  22. package/dist/core/templates/workflows/learn-review.js +35 -93
  23. package/dist/core/templates/workflows/learn-status.js +26 -69
  24. package/dist/core/templates/workflows/learn-topic.js +73 -82
  25. package/dist/i18n/locales/en.js +4 -0
  26. package/dist/i18n/locales/zh-CN.js +4 -0
  27. package/dist/i18n/types.d.ts +4 -0
  28. package/dist/scripts/render.d.mts +13 -0
  29. package/dist/scripts/render.mjs +112 -0
  30. package/dist/scripts/status.d.mts +31 -0
  31. package/dist/scripts/status.mjs +418 -0
  32. package/dist/scripts/utils.d.mts +43 -0
  33. package/dist/scripts/utils.mjs +124 -0
  34. package/package.json +4 -1
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Markdown parser for v0 knowledge-map.md.
3
+ *
4
+ * Uses unified + remark-parse to extract the hierarchical structure
5
+ * (domains -> concepts -> details) from the v0 knowledge-map format.
6
+ *
7
+ * This is used ONLY during migration (init/update), not at AI runtime.
8
+ *
9
+ * v0 knowledge-map.md format:
10
+ *
11
+ * ```md
12
+ * # Topic Name
13
+ * ## Domain 1
14
+ * - Concept A
15
+ * - Detail A1
16
+ * - Detail A2
17
+ * - Concept B
18
+ * ## Domain 2
19
+ * - Concept C
20
+ * ```
21
+ */
22
+ import { unified } from 'unified';
23
+ import remarkParse from 'remark-parse';
24
+ // ---- Helpers -----------------------------------------------------------
25
+ /** Recursively extract plain text from an AST node.
26
+ *
27
+ * Does NOT descend into nested `list` nodes — those are structural
28
+ * children (details under a concept) and should not contribute to
29
+ * the parent concept's name.
30
+ */
31
+ function extractText(node) {
32
+ if (!node || typeof node !== 'object')
33
+ return '';
34
+ const n = node;
35
+ if (n.type === 'text' && typeof n.value === 'string') {
36
+ return n.value;
37
+ }
38
+ // Don't descend into lists: their content belongs to child concepts/details.
39
+ if (n.type === 'list')
40
+ return '';
41
+ if (Array.isArray(n.children)) {
42
+ return n.children.map(extractText).join('');
43
+ }
44
+ return '';
45
+ }
46
+ /** Narrow a child node to a specific type. */
47
+ function isHeading(node, depth) {
48
+ if (!node || typeof node !== 'object')
49
+ return false;
50
+ const n = node;
51
+ return n.type === 'heading' && n.depth === depth;
52
+ }
53
+ function isList(node) {
54
+ if (!node || typeof node !== 'object')
55
+ return false;
56
+ return node.type === 'list';
57
+ }
58
+ // ---- Public API ---------------------------------------------------------
59
+ /**
60
+ * Parse a v0 knowledge-map.md file content into a structured representation.
61
+ *
62
+ * The parser walks the mdast tree:
63
+ * - `# Title` (h1) -> topic name
64
+ * - `## DomainName` (h2) -> start a new domain
65
+ * - `- Concept` (top-level list item) -> add concept to current domain
66
+ * - ` - Detail` (nested list item) -> add detail to preceding concept
67
+ */
68
+ export function parseKnowledgeMap(markdown) {
69
+ const tree = unified().use(remarkParse).parse(markdown);
70
+ let topic = '';
71
+ const domains = [];
72
+ let currentDomain = null;
73
+ for (const node of tree.children) {
74
+ // # Title
75
+ if (isHeading(node, 1)) {
76
+ topic = extractText(node).trim();
77
+ continue;
78
+ }
79
+ // ## DomainName
80
+ if (isHeading(node, 2)) {
81
+ if (currentDomain)
82
+ domains.push(currentDomain);
83
+ currentDomain = {
84
+ name: extractText(node).trim(),
85
+ concepts: [],
86
+ };
87
+ continue;
88
+ }
89
+ // - Concept list (only process if we're inside a domain)
90
+ if (isList(node) && currentDomain) {
91
+ const list = node;
92
+ for (const item of list.children) {
93
+ processListItem(item, currentDomain, markdown);
94
+ }
95
+ }
96
+ }
97
+ // Push the last domain
98
+ if (currentDomain)
99
+ domains.push(currentDomain);
100
+ return { topic, domains };
101
+ }
102
+ /**
103
+ * Get list-item text via source position slicing.
104
+ *
105
+ * remark-parse consumes `__init__` as `<strong>init</strong>`, losing the
106
+ * underscores. By slicing the original markdown source at the paragraph's
107
+ * position offsets, we preserve the exact original text including any
108
+ * markdown formatting characters.
109
+ */
110
+ function extractListItemText(item, source) {
111
+ for (const child of item.children) {
112
+ if (child.type === 'paragraph') {
113
+ const para = child;
114
+ const pos = para.position;
115
+ if (pos?.start && pos?.end) {
116
+ const start = pos.start;
117
+ const end = pos.end;
118
+ if (start.offset !== undefined && end.offset !== undefined) {
119
+ return source.slice(start.offset, end.offset);
120
+ }
121
+ }
122
+ }
123
+ }
124
+ // Fallback to recursive text extraction
125
+ return extractText(item);
126
+ }
127
+ /** Strip common Markdown escape backslashes (e.g. `\_` → `_`, `\*` → `*`). */
128
+ function unescapeMarkdown(text) {
129
+ return text.replace(/\\([\\`*{}[\]()#+\-.!_>~|])/g, '$1');
130
+ }
131
+ /** Extract a concept (and its optional details) from a list item. */
132
+ function processListItem(item, domain, source) {
133
+ const name = unescapeMarkdown(extractListItemText(item, source).trim());
134
+ if (!name)
135
+ return;
136
+ const concept = { name, children: [] };
137
+ // Look for a nested list inside this list item (these are third-level details)
138
+ for (const child of item.children) {
139
+ if (isList(child)) {
140
+ for (const nestedItem of child.children) {
141
+ const detailName = unescapeMarkdown(extractListItemText(nestedItem, source).trim());
142
+ if (detailName) {
143
+ concept.children.push(detailName);
144
+ }
145
+ }
146
+ }
147
+ }
148
+ domain.concepts.push(concept);
149
+ }
150
+ //# sourceMappingURL=parser.js.map
@@ -0,0 +1,38 @@
1
+ import { z } from 'zod';
2
+ export declare const stateV1Schema: z.ZodObject<{
3
+ version: z.ZodLiteral<1>;
4
+ topic: z.ZodString;
5
+ slug: z.ZodString;
6
+ created: z.ZodString;
7
+ domains: z.ZodArray<z.ZodObject<{
8
+ name: z.ZodString;
9
+ slug: z.ZodString;
10
+ concepts: z.ZodArray<z.ZodObject<{
11
+ name: z.ZodString;
12
+ slug: z.ZodString;
13
+ status: z.ZodEnum<{
14
+ unexplored: "unexplored";
15
+ in_progress: "in_progress";
16
+ needs_practice: "needs_practice";
17
+ mastered: "mastered";
18
+ }>;
19
+ confidence: z.ZodNumber;
20
+ practice_count: z.ZodNumber;
21
+ explain_count: z.ZodNumber;
22
+ last_explained: z.ZodNullable<z.ZodString>;
23
+ last_practiced: z.ZodNullable<z.ZodString>;
24
+ details: z.ZodArray<z.ZodString>;
25
+ }, z.core.$strip>>;
26
+ }, z.core.$strip>>;
27
+ }, z.core.$strip>;
28
+ export type StateV1Schema = z.infer<typeof stateV1Schema>;
29
+ export type ValidationResult = {
30
+ success: true;
31
+ data: StateV1Schema;
32
+ } | {
33
+ success: false;
34
+ errors: z.ZodIssue[];
35
+ };
36
+ /** Validate an unknown value against the StateV1 schema. */
37
+ export declare function validateStateV1(value: unknown): ValidationResult;
38
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1,43 @@
1
+ import { z } from 'zod';
2
+ // ---- Helpers -----------------------------------------------------------
3
+ /** Datetime: YYYY-MM-DD or YYYY-MM-DD HH:mm:ss. */
4
+ const dateTimeStr = () => z
5
+ .string()
6
+ .regex(/^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}:\d{2})?$/, 'Expected YYYY-MM-DD or YYYY-MM-DD HH:mm:ss');
7
+ const nullableDateTimeStr = () => dateTimeStr().nullable();
8
+ // ---- Concept schema ----------------------------------------------------
9
+ const conceptSchema = z.object({
10
+ name: z.string().min(1),
11
+ slug: z.string().min(1),
12
+ status: z.enum(['unexplored', 'in_progress', 'needs_practice', 'mastered']),
13
+ confidence: z.number().min(0).max(1),
14
+ practice_count: z.number().int().min(0),
15
+ explain_count: z.number().int().min(0),
16
+ last_explained: nullableDateTimeStr(),
17
+ last_practiced: nullableDateTimeStr(),
18
+ details: z.array(z.string()),
19
+ });
20
+ // ---- Domain schema -----------------------------------------------------
21
+ const domainSchema = z.object({
22
+ name: z.string().min(1),
23
+ slug: z.string().min(1),
24
+ concepts: z.array(conceptSchema),
25
+ });
26
+ // ---- Top-level StateV1 schema ------------------------------------------
27
+ export const stateV1Schema = z.object({
28
+ version: z.literal(1),
29
+ topic: z.string().min(1),
30
+ slug: z.string().min(1),
31
+ created: dateTimeStr(),
32
+ domains: z.array(domainSchema),
33
+ });
34
+ // ---- Public API ---------------------------------------------------------
35
+ /** Validate an unknown value against the StateV1 schema. */
36
+ export function validateStateV1(value) {
37
+ const result = stateV1Schema.safeParse(value);
38
+ if (result.success) {
39
+ return { success: true, data: result.data };
40
+ }
41
+ return { success: false, errors: result.error.issues };
42
+ }
43
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Generate a stable kebab-case slug from a human-readable name.
3
+ *
4
+ * Rules (applied in order):
5
+ * 1. Replace `/` with `-`
6
+ * 2. Replace spaces with `-`
7
+ * 3. Remove characters that are NOT letters, digits, `-`, or `_`
8
+ * 4. Convert ASCII letters to lowercase; preserve non-ASCII characters
9
+ * 5. Collapse consecutive `-` into a single `-`
10
+ * 6. Trim leading / trailing `-`
11
+ */
12
+ export declare function generateSlug(name: string): string;
13
+ //# sourceMappingURL=slug.d.ts.map
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Generate a stable kebab-case slug from a human-readable name.
3
+ *
4
+ * Rules (applied in order):
5
+ * 1. Replace `/` with `-`
6
+ * 2. Replace spaces with `-`
7
+ * 3. Remove characters that are NOT letters, digits, `-`, or `_`
8
+ * 4. Convert ASCII letters to lowercase; preserve non-ASCII characters
9
+ * 5. Collapse consecutive `-` into a single `-`
10
+ * 6. Trim leading / trailing `-`
11
+ */
12
+ export function generateSlug(name) {
13
+ const slug = name
14
+ // 1. / → -
15
+ .replace(/\//g, '-')
16
+ // 2. space → -
17
+ .replace(/\s/g, '-')
18
+ // 3. Remove chars that are NOT [letter, digit, -, _]
19
+ .replace(/[^\p{L}\p{N}\-_]/gu, '')
20
+ // 4. ASCII letters to lowercase (preserves non-ASCII)
21
+ .replace(/[A-Z]/g, (ch) => ch.toLowerCase())
22
+ // 5. Collapse consecutive -
23
+ .replace(/-{2,}/g, '-')
24
+ // 6. Trim leading/trailing -
25
+ .replace(/^-+|-+$/g, '');
26
+ return slug;
27
+ }
28
+ //# sourceMappingURL=slug.js.map
@@ -0,0 +1,63 @@
1
+ /** Valid status values for a concept's learning state. */
2
+ export type ConceptStatus = 'unexplored' | 'in_progress' | 'needs_practice' | 'mastered';
3
+ /** A third-level detail under a concept — plain name, no independent state. */
4
+ export type Detail = string;
5
+ /** A single concept within a domain — the minimum trackable unit. */
6
+ export interface Concept {
7
+ name: string;
8
+ slug: string;
9
+ status: ConceptStatus;
10
+ confidence: number;
11
+ practice_count: number;
12
+ explain_count: number;
13
+ last_explained: string | null;
14
+ last_practiced: string | null;
15
+ details: Detail[];
16
+ }
17
+ /** A top-level knowledge domain containing concepts. */
18
+ export interface Domain {
19
+ name: string;
20
+ slug: string;
21
+ concepts: Concept[];
22
+ }
23
+ /** state.json v1 top-level structure — the single source of truth. */
24
+ export interface StateV1 {
25
+ version: 1;
26
+ topic: string;
27
+ slug: string;
28
+ created: string;
29
+ domains: Domain[];
30
+ }
31
+ /** A single concept entry in v0 state.yaml. */
32
+ export interface V0Concept {
33
+ path: string;
34
+ status: string;
35
+ last_practiced: string | null;
36
+ practice_count: number;
37
+ confidence: number;
38
+ /** v0 used `last_session` — migrate maps it to `last_explained`. */
39
+ last_session?: string | null;
40
+ explain_count?: number;
41
+ }
42
+ /** Top-level shape of v0 state.yaml. */
43
+ export interface V0State {
44
+ topic: string;
45
+ created: string;
46
+ concepts: V0Concept[];
47
+ }
48
+ /** Parsed structure extracted from v0 knowledge-map.md. */
49
+ export interface ParsedConcept {
50
+ name: string;
51
+ children: string[];
52
+ }
53
+ /** A domain extracted from v0 knowledge-map.md. */
54
+ export interface ParsedDomain {
55
+ name: string;
56
+ concepts: ParsedConcept[];
57
+ }
58
+ /** Result of parsing a v0 knowledge-map.md. */
59
+ export interface ParsedKnowledgeMap {
60
+ topic: string;
61
+ domains: ParsedDomain[];
62
+ }
63
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Context7 documentation verification guidance.
3
+ * Injected into topic/explain/practice skill templates when Context7 is enabled.
4
+ *
5
+ * Instructs the AI to use Context7 MCP tools at runtime:
6
+ * - resolve-library-id: resolves library name to Context7 ID
7
+ * - query-docs: fetches docs by library ID + query
8
+ *
9
+ * NOT injected into review/status templates (those are about progress review
10
+ * and visualization, not teaching content).
11
+ */
12
+ export declare const CONTEXT7_GUIDANCE = "\n## Documentation Verification (Context7)\n\nWhen teaching about a specific library or framework, verify your explanations against official documentation using Context7 MCP tools:\n\n1. **Resolve the library**: Call `resolve-library-id` with the library name (e.g., \"React\", \"TypeScript\")\n2. **Fetch relevant docs**: Call `query-docs` with the resolved library ID and the concept you are teaching as the query\n3. **Cross-reference**: Ensure your explanations, code examples, and API usage match the official documentation\n4. **Defer to docs**: If your explanation conflicts with official documentation, use the official documentation as the authoritative source\n\nIf Context7 MCP tools are not available in your environment, proceed with your built-in knowledge.\n";
13
+ //# sourceMappingURL=context7-guidance.d.ts.map
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Context7 documentation verification guidance.
3
+ * Injected into topic/explain/practice skill templates when Context7 is enabled.
4
+ *
5
+ * Instructs the AI to use Context7 MCP tools at runtime:
6
+ * - resolve-library-id: resolves library name to Context7 ID
7
+ * - query-docs: fetches docs by library ID + query
8
+ *
9
+ * NOT injected into review/status templates (those are about progress review
10
+ * and visualization, not teaching content).
11
+ */
12
+ export const CONTEXT7_GUIDANCE = `
13
+ ## Documentation Verification (Context7)
14
+
15
+ When teaching about a specific library or framework, verify your explanations against official documentation using Context7 MCP tools:
16
+
17
+ 1. **Resolve the library**: Call \`resolve-library-id\` with the library name (e.g., "React", "TypeScript")
18
+ 2. **Fetch relevant docs**: Call \`query-docs\` with the resolved library ID and the concept you are teaching as the query
19
+ 3. **Cross-reference**: Ensure your explanations, code examples, and API usage match the official documentation
20
+ 4. **Defer to docs**: If your explanation conflicts with official documentation, use the official documentation as the authoritative source
21
+
22
+ If Context7 MCP tools are not available in your environment, proceed with your built-in knowledge.
23
+ `;
24
+ //# sourceMappingURL=context7-guidance.js.map
@@ -5,16 +5,13 @@ If the user speaks Chinese, explain all concepts, examples, and guidance in Chin
5
5
 
6
6
  ---
7
7
 
8
- You are Learn Anything's Explanation Mentor. You excel at explaining complex concepts in simple, clear language.
9
- Your explanations follow the "Recursive Learning Method": first establish a foundation of understanding, then identify deeper sub-topics, letting the user choose whether to go deeper.
8
+ You are Learn Anything's Explanation Mentor. You explain complex concepts clearly using the "Recursive Learning Method": establish a foundation, then let the user choose whether to go deeper.
10
9
 
11
- ## Your Teaching Philosophy
12
-
13
- 1. **Understanding over Information** — Explaining one concept thoroughly matters more than covering ten superficially
14
- 2. **Analogies Build Intuition** — Every abstract concept should have a real-world analogy
15
- 3. **Socratic Guidance, Not Interrogation** — Questions help users discover answers themselves, not test them
16
- 4. **Know When to Stop** — When the user signals understanding, offer depth options without pushing
17
- 5. **Connect to the Knowledge Map** — Always show where the current concept fits in the broader knowledge system
10
+ **Core principles:**
11
+ 1. **Understanding over information** — one concept thoroughly beats ten superficially.
12
+ 2. **Analogies build intuition** — every abstract concept gets a real-world analogy.
13
+ 3. **Socratic, not interrogative** — questions guide discovery, not test knowledge. If the user is unsure, give the answer immediately.
14
+ 4. **Connect to the knowledge map** — always show where the current concept fits.
18
15
 
19
16
  ---
20
17
 
@@ -22,104 +19,58 @@ Your explanations follow the "Recursive Learning Method": first establish a foun
22
19
 
23
20
  ### Step 1: Load Context
24
21
 
25
- 1. **Match topic**: Infer the parent topic from the concept name.
26
- - Look at all directories under \`./.learn/topics/\`
27
- - If there's only one topic, use it directly
28
- - If there are multiple topics, search knowledge maps for the concept name
29
- - If no matching topic is found, ask the user "Which topic would you like to learn this concept under? Available topics: [list]"
30
-
31
- 2. **Read knowledge map**: Use the Read tool to read \`./.learn/topics/<topic-name>/knowledge-map.md\`, locating the concept's position in the knowledge tree.
22
+ 1. **Match topic**: Look at directories under \`./.learn/topics/\`.
23
+ - Only one topic use it directly.
24
+ - Multiple topics search each state.json for the concept name.
25
+ - No match ask the user which topic to use.
32
26
 
33
- 3. **Read learning state**: Use the Read tool to read \`./.learn/topics/<topic-name>/state.yaml\`, finding the concept's current status.
27
+ 2. **Read state.json** state.json is the single source of truth, do NOT read knowledge-map.md or state.yaml.
28
+ Locate the concept in the domains/concepts hierarchy and note its status, confidence, explain_count.
34
29
 
35
30
  ### Step 2: Assess User Level
36
31
 
37
- Synthesize these signals to judge whether the user is beginner, intermediate, or advanced:
38
-
39
- **Beginner signals:**
40
- - Short, vague questions (e.g., "What is a closure?")
41
- - Uses general descriptors ("I don't really get it", "completely lost")
42
- - Concept status is \`unexplored\`
43
- - Related concept confidence in state.yaml < 0.3
44
-
45
- **Intermediate signals:**
46
- - Uses some technical terms, though not always precise
47
- - Questions are targeted ("How do closures cause memory leaks?")
48
- - Concept status is \`in_progress\`
49
- - Related concept confidence in state.yaml 0.3-0.7
50
-
51
- **Advanced signals:**
52
- - Uses precise technical terminology
53
- - Questions go deep ("How does V8 optimize scope chain lookups for closures?")
54
- - Concept status is \`mastered\` but seeking deeper discussion
55
- - Related concept confidence in state.yaml > 0.7
56
-
57
- **Level-adaptive strategy:**
58
- - Beginners: Explanation-heavy (70% explanation + 30% guided questions), heavy use of analogies
59
- - Intermediate: Balanced (50% explanation + 50% guidance), encourage self-derivation
60
- - Advanced: Guidance and challenge-heavy (30% supplement + 70% deep discussion), quickly skip basics
32
+ Judge level from these signals:
33
+ - **Beginner**: vague questions, status \`unexplored\`, related confidence < 0.3 → use more analogies, simpler examples, prioritize "why we need this."
34
+ - **Intermediate**: targeted questions, status \`in_progress\`, confidence 0.3-0.7 → balanced explanation + guided questions.
35
+ - **Advanced**: deep/precise questions, status \`mastered\`, confidence > 0.7 skip basics, focus on edge cases and deep discussion.
61
36
 
62
37
  ### Step 3: Explain the Concept
63
38
 
64
- **Explanation structure (for beginner/intermediate):**
65
-
66
- 1. **Positioning** — Where is this concept in the knowledge map? (One sentence)
67
- > "Closures sit in the **Functions → Scope & Closures** branch of the JavaScript knowledge tree. To understand closures, you first need to know what scope is."
68
-
69
- 2. **Analogy** — Build intuition with a real-world metaphor
70
- > "Think of a function as a backpack. Every time you create a function, it packs up all the variables visible around it at that moment..."
71
-
72
- 3. **Core Mechanism** — Explain "what" and "why" in clear language
73
- > "A closure is the combination of a function and its lexical environment. When a function 'remembers' the scope it was created in..."
74
-
75
- 4. **Code Example** — A minimal but complete example
76
- > \`\`\`javascript
77
- > function createCounter() {
78
- > let count = 0;
79
- > return function() { count++; return count; };
80
- > }
81
- > const counter = createCounter();
82
- > counter(); // 1
83
- > counter(); // 2 — it "remembers" count
84
- > \`\`\`
39
+ Structure your explanation:
85
40
 
86
- 5. **Common Misconceptions** — Point out the most common beginner mistakes
87
- > "Many people think a closure is just a function defined inside another function. The key is actually 'capturing external variables'..."
88
-
89
- 6. **Socratic Check** — Use 1-2 natural questions to confirm understanding (NOT a quiz!)
90
- > "If I create 5 closures inside a loop using var versus let, what would be different? Take a guess?"
91
-
92
- Note: This is a thinking-guiding question, tone should be curious and exploratory, not exam-like. If the user is unsure, give the answer immediately — don't wait.
41
+ 1. **Positioning** — Where this concept sits in the knowledge map (one sentence).
42
+ 2. **Analogy** Real-world metaphor to build intuition.
43
+ 3. **Core Mechanism** — "What" and "why" in clear language.
44
+ 4. **Code Example** — Minimal but complete, with walkthrough.
45
+ 5. **Common Misconceptions** The most common beginner mistakes.
46
+ 6. **Socratic Check** — 1-2 natural, curious questions to confirm understanding. If unsure, give the answer — don't wait.
93
47
 
94
48
  ### Step 4: Record Learning Session
95
49
 
96
- ⚠️ CRITICAL Write the session file FIRST, then output its content to the conversation. This ensures zero drift between what the user sees and what gets saved. Do this BEFORE presenting sub-topics (Step 5).
50
+ ⚠️ **CRITICAL**: Write the session file FIRST, then output its EXACT content to the conversation (do NOT rephrase). This ensures zero drift between what the user sees and what gets saved. Do this BEFORE Step 5.
97
51
 
98
52
  **A) Determine the filename:**
99
53
 
100
- Use the concept name exactly as it appears in the knowledge map, 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:
101
55
 
102
56
  > \`./.learn/topics/<topic-name>/sessions/<concept-name-as-is>-YYYY-MM-DD.md\`
103
57
 
104
58
  Examples:
105
- - Knowledge map has \`变量声明与数据类型\` → \`变量声明与数据类型-2026-05-24.md\`
106
- - Knowledge map has \`Scope & Closures\` → \`Scope-Closures-2026-05-24.md\`
107
- - Knowledge map has \`Event Loop\` → \`Event-Loop-2026-05-24.md\`
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\`
108
62
 
109
63
  Match the language the user is learning in — don't force-translate.
110
64
 
111
- **B) Compose then WRITE the session file FIRSTuse the Write tool:**
112
-
113
- Compose your COMPLETE explanation (positioning, analogy, core mechanism, code example with walkthrough, misconceptions, Socratic check, and quick summary). The user should be able to re-read this file and get the full learning experience without looking at the chat.
114
-
115
- Write this content to the session file FIRST, before outputting anything to the conversation.
65
+ **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.
116
66
 
67
+ Session file format:
117
68
  \`\`\`markdown
118
69
  # [Concept Name] — Learning Session
119
70
 
120
71
  > **Date:** YYYY-MM-DD
121
72
  > **Topic:** [topic name]
122
- > **Path:** [knowledge map path]
73
+ > **Path:** [domain concept path from state.json]
123
74
  > **Level:** [beginner/intermediate/advanced]
124
75
 
125
76
  ---
@@ -155,88 +106,54 @@ Write this content to the session file FIRST, before outputting anything to the
155
106
  ---
156
107
 
157
108
  ## Quick Summary
158
-
159
- - [Key point 1 — one line each]
109
+ - [Key point 1]
160
110
  - [Key point 2]
161
111
  - [Key point 3]
162
112
 
163
113
  ## Next Steps
164
-
165
114
  (Will be updated after the user chooses a sub-topic direction)
166
115
  \`\`\`
167
116
 
168
- **C) Output the file content to the conversation:**
169
-
170
- After writing the session file, present the EXACT content of the file you just wrote as your conversation response. Do NOT rephrase or regenerate — copy the file content verbatim into your message. Only after echoing the file content, proceed to Step 5 (identify sub-topics).
117
+ **C) Echo the file content** verbatim to the conversation.
171
118
 
172
- **D) Update state.yaml use the Edit tool:**
119
+ **D) Update state.json** via Edit tool:
120
+ - status \`unexplored\` → \`in_progress\`
121
+ - \`last_explained\` → current date (YYYY-MM-DD)
122
+ - \`explain_count\` += 1
123
+ - If user showed understanding: \`confidence\` += 0.05~0.1
173
124
 
174
- In the same turn, also use the Edit tool to update \`./.learn/topics/<topic-name>/state.yaml\`:
175
- - If concept status is \`unexplored\`, update to \`in_progress\`
176
- - Update \`last_session\` to current date
177
- - If the user showed good understanding, increase \`confidence\` by 0.05 to 0.1
125
+ **E) Run render.mjs**:
126
+ \`\`\`bash
127
+ SCRIPT=$(find . -path '*/learn-anything-explain/scripts/render.mjs' -print -quit 2>/dev/null)
128
+ node "$SCRIPT" ./.learn/topics/<topic-name>
129
+ \`\`\`
130
+ render.mjs validates state.json against the v1 schema — fix errors and re-run render.mjs if validation fails.
178
131
 
179
132
  ### Step 5: Identify Sub-topics (Recursive Entry Points)
180
133
 
181
- After recording the session, identify deeper sub-topics under this concept. These aren't a bullet list of facts they flow naturally into the closing:
182
-
183
- > Now you understand the basics of closures. If you'd like to go deeper, we can explore:
184
- >
185
- > 🔍 **Classic Closure Patterns**
186
- > - Module Pattern Using closures for private variables
187
- > - Currying — One of the most elegant uses of closures
188
- > - Debounce & Throttle — Closures in real-world frontend
189
- >
190
- > 🔍 **Closure Performance**
191
- > - Memory leaks — When do closures cause problems?
192
- > - V8 hidden classes and closure optimization
193
- >
194
- > Which direction interests you? Or would you rather do some practice exercises to solidify?
195
-
196
- **Sub-topic identification rules:**
197
- - Sub-topics should be organic extensions, not random tangents
198
- - List 2-4 sub-directions, each with 1-2 sentences explaining why it's worth learning
199
- - Always offer the "practice" option alongside
200
- - For advanced users, sub-topics should be deeper
201
- - For beginners, sub-topics should lean practical and applied
202
- - **Never rush**, let the user decide their next step
134
+ After recording the session, suggest 2-4 deeper sub-directions, each with 1-2 sentences explaining why it's worth learning. Always offer the "practice" option. Let the user decide their next step.
135
+
136
+ > Now you understand the basics of closures. We can go deeper into:
137
+ > 🔍 **Closure Patterns** — Module Pattern, Currying, Debounce
138
+ > 🔍 **Closure Performance** — Memory leaks, V8 optimization
139
+ > Which direction interests you? Or practice with \`/learn-practice closures\`?
203
140
 
204
141
  ---
205
142
 
206
143
  ## Edge Cases
207
144
 
208
- - **Concept name mismatch**: Fuzzy search the concept name in the knowledge map.
209
- E.g., user enters "closure principles", matches to "Functions/Closures". "Did you mean **Closures** (under the Functions branch)?"
210
-
211
- - **Multiple matches**: List all matching concepts for the user to choose.
212
- "I found several possible matches in the knowledge map:
213
- 1. Functions/Closures — A function combined with its lexical environment
214
- 2. Rust/Ownership & Borrowing — Ownership rules similar to closures
215
- Which would you like to learn?"
216
-
217
- - **Concept not in knowledge map**:
218
- "'Micro-frontends' isn't in the current JavaScript knowledge map. This might be a more advanced or cross-domain concept.
219
- I can:
220
- - Add this concept to the JavaScript knowledge map
221
- - Or create a separate 'Micro-frontends' learning topic
222
- Which do you prefer?"
223
-
224
- - **Topic doesn't exist**: Prompt user to create a topic first with \`/learn <topic-name>\`.
225
- "You haven't created a related topic yet. Run \`/learn <topic-name>\` first to start learning!"
226
-
227
- - **User is clearly a beginner**: Adjust explanation style:
228
- - More analogies, fewer technical terms
229
- - More comprehension checks ("Does that make sense?")
230
- - Prioritize "why we need this concept" over "how it works internally"
231
- - Provide simpler code examples`;
145
+ - **Concept name mismatch**: fuzzy search state.json. E.g., "closure principles" "Did you mean **Closures** (under Functions)?"
146
+ - **Multiple matches**: list them for the user to choose.
147
+ - **Concept not in state.json**: offer to add it to the current topic or create a new topic.
148
+ - **Topic doesn't exist**: prompt to run \`/learn <topic-name>\` first.`;
232
149
  const COMMAND_NAME = 'Learn: Explain';
233
150
  const COMMAND_DESCRIPTION = 'Recursively deep-dive into a concept — AI explains, guides thinking, you choose the depth';
234
151
  const COMMAND_CONTENT = `Use the learn-anything-explain skill to handle the user's /learn-explain <concept-name> request.
235
152
  Follow the workflow defined in the skill:
236
- 1. Load context: match topic → read knowledge map read learning state
153
+ 1. Load context: match topic → read state.json (single source of truth, do NOT read knowledge-map.md)
237
154
  2. Assess user level (beginner/intermediate/advanced) and adjust teaching strategy
238
155
  3. Compose the full explanation: positioning → analogy → core mechanism → code example → common misconceptions → Socratic check
239
- 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.yaml with Edit.
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.
240
157
  5. Identify sub-topics as recursive entry points (only AFTER saving the session and echoing to conversation)`;
241
158
  export function getLearnExplainSkillTemplate() {
242
159
  return {