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.
- package/README.md +6 -0
- package/README.zh-CN.md +6 -0
- package/dist/cli/index.js +3 -0
- package/dist/core/init.d.ts +6 -0
- package/dist/core/init.js +67 -2
- package/dist/core/learn-protocol/index.d.ts +8 -0
- package/dist/core/learn-protocol/index.js +5 -0
- package/dist/core/learn-protocol/migrate.d.ts +52 -0
- package/dist/core/learn-protocol/migrate.js +259 -0
- package/dist/core/learn-protocol/parser.d.ts +33 -0
- package/dist/core/learn-protocol/parser.js +150 -0
- package/dist/core/learn-protocol/schema.d.ts +38 -0
- package/dist/core/learn-protocol/schema.js +43 -0
- package/dist/core/learn-protocol/slug.d.ts +13 -0
- package/dist/core/learn-protocol/slug.js +28 -0
- package/dist/core/learn-protocol/types.d.ts +63 -0
- package/dist/core/learn-protocol/types.js +2 -0
- package/dist/core/templates/context7-guidance.d.ts +13 -0
- package/dist/core/templates/context7-guidance.js +24 -0
- package/dist/core/templates/workflows/learn-explain.js +56 -139
- package/dist/core/templates/workflows/learn-practice.js +88 -284
- package/dist/core/templates/workflows/learn-review.js +35 -93
- package/dist/core/templates/workflows/learn-status.js +26 -69
- package/dist/core/templates/workflows/learn-topic.js +73 -82
- package/dist/i18n/locales/en.js +4 -0
- package/dist/i18n/locales/zh-CN.js +4 -0
- package/dist/i18n/types.d.ts +4 -0
- package/dist/scripts/render.d.mts +13 -0
- package/dist/scripts/render.mjs +112 -0
- package/dist/scripts/status.d.mts +31 -0
- package/dist/scripts/status.mjs +418 -0
- package/dist/scripts/utils.d.mts +43 -0
- package/dist/scripts/utils.mjs +124 -0
- 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,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
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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**:
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
**
|
|
40
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
|
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
|
|
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
|
-
-
|
|
106
|
-
-
|
|
107
|
-
-
|
|
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)
|
|
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:** [
|
|
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)
|
|
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.
|
|
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
|
-
|
|
175
|
-
|
|
176
|
-
-
|
|
177
|
-
|
|
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,
|
|
182
|
-
|
|
183
|
-
> Now you understand the basics of closures.
|
|
184
|
-
>
|
|
185
|
-
> 🔍 **
|
|
186
|
-
>
|
|
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**:
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
- **
|
|
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
|
|
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.
|
|
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 {
|