@graspful/cli 0.2.5 → 0.2.6
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/dist/commands/create-brand.d.ts +0 -5
- package/dist/commands/create-brand.d.ts.map +1 -1
- package/dist/commands/create-brand.js +3 -64
- package/dist/commands/create-brand.js.map +1 -1
- package/dist/commands/create-course.d.ts +0 -4
- package/dist/commands/create-course.d.ts.map +1 -1
- package/dist/commands/create-course.js +3 -32
- package/dist/commands/create-course.js.map +1 -1
- package/dist/commands/describe.d.ts.map +1 -1
- package/dist/commands/describe.js +9 -86
- package/dist/commands/describe.js.map +1 -1
- package/dist/commands/fill-concept.d.ts.map +1 -1
- package/dist/commands/fill-concept.js +11 -46
- package/dist/commands/fill-concept.js.map +1 -1
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +5 -92
- package/dist/commands/validate.js.map +1 -1
- package/package.json +2 -2
|
@@ -1,8 +1,3 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
export declare function scaffoldBrand(niche: string, options: {
|
|
3
|
-
name?: string;
|
|
4
|
-
domain?: string;
|
|
5
|
-
orgSlug?: string;
|
|
6
|
-
}): string;
|
|
7
2
|
export declare function registerCreateBrandCommand(createCmd: Command): void;
|
|
8
3
|
//# sourceMappingURL=create-brand.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-brand.d.ts","sourceRoot":"","sources":["../../src/commands/create-brand.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"create-brand.d.ts","sourceRoot":"","sources":["../../src/commands/create-brand.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,OAAO,QA4B5D"}
|
|
@@ -33,74 +33,12 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.scaffoldBrand = scaffoldBrand;
|
|
37
36
|
exports.registerCreateBrandCommand = registerCreateBrandCommand;
|
|
38
37
|
const fs = __importStar(require("fs"));
|
|
39
38
|
const yaml = __importStar(require("js-yaml"));
|
|
39
|
+
const shared_1 = require("@graspful/shared");
|
|
40
40
|
const output_1 = require("../lib/output");
|
|
41
41
|
const analytics_1 = require("../lib/analytics");
|
|
42
|
-
const NICHE_PRESETS = {
|
|
43
|
-
education: { preset: 'blue', tagline: 'Learn smarter, not harder', headline: 'Master any subject with adaptive learning' },
|
|
44
|
-
healthcare: { preset: 'emerald', tagline: 'Training that saves lives', headline: 'Adaptive healthcare education for professionals' },
|
|
45
|
-
finance: { preset: 'slate', tagline: 'Build financial expertise', headline: 'Master finance with adaptive learning' },
|
|
46
|
-
tech: { preset: 'indigo', tagline: 'Level up your skills', headline: 'Adaptive tech training that meets you where you are' },
|
|
47
|
-
legal: { preset: 'amber', tagline: 'Know the law, pass the exam', headline: 'Adaptive legal education for exam success' },
|
|
48
|
-
default: { preset: 'blue', tagline: 'Learn adaptively', headline: 'Personalized learning that works' },
|
|
49
|
-
};
|
|
50
|
-
function scaffoldBrand(niche, options) {
|
|
51
|
-
const config = NICHE_PRESETS[niche] || NICHE_PRESETS['default'];
|
|
52
|
-
const slug = (options.name || niche).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
53
|
-
const name = options.name || `${niche.charAt(0).toUpperCase() + niche.slice(1)} Academy`;
|
|
54
|
-
const domain = options.domain || `${slug}.graspful.ai`;
|
|
55
|
-
return yaml.dump({
|
|
56
|
-
brand: {
|
|
57
|
-
id: slug,
|
|
58
|
-
name,
|
|
59
|
-
domain,
|
|
60
|
-
tagline: config.tagline,
|
|
61
|
-
logoUrl: '/icon.svg',
|
|
62
|
-
orgSlug: options.orgSlug || 'TODO: your-org-slug',
|
|
63
|
-
},
|
|
64
|
-
theme: {
|
|
65
|
-
preset: config.preset,
|
|
66
|
-
radius: '0.5rem',
|
|
67
|
-
},
|
|
68
|
-
landing: {
|
|
69
|
-
hero: {
|
|
70
|
-
headline: config.headline,
|
|
71
|
-
subheadline: `${name} uses adaptive learning to help you master concepts faster.`,
|
|
72
|
-
ctaText: 'Start Learning',
|
|
73
|
-
},
|
|
74
|
-
features: {
|
|
75
|
-
heading: 'Why choose us?',
|
|
76
|
-
items: [
|
|
77
|
-
{ title: 'Adaptive Learning', description: 'Content adapts to your knowledge level', icon: 'brain' },
|
|
78
|
-
{ title: 'Spaced Repetition', description: 'Review at optimal intervals for lasting memory', icon: 'clock' },
|
|
79
|
-
{ title: 'Progress Tracking', description: 'See exactly where you stand', icon: 'chart' },
|
|
80
|
-
],
|
|
81
|
-
},
|
|
82
|
-
howItWorks: {
|
|
83
|
-
heading: 'How it works',
|
|
84
|
-
items: [
|
|
85
|
-
{ title: 'Take a diagnostic', description: 'We assess what you already know' },
|
|
86
|
-
{ title: 'Learn adaptively', description: 'Focus on gaps, skip what you know' },
|
|
87
|
-
{ title: 'Master the material', description: 'Prove mastery through progressive challenges' },
|
|
88
|
-
],
|
|
89
|
-
},
|
|
90
|
-
faq: [],
|
|
91
|
-
},
|
|
92
|
-
pricing: {
|
|
93
|
-
monthly: 0,
|
|
94
|
-
currency: 'usd',
|
|
95
|
-
trialDays: 0,
|
|
96
|
-
},
|
|
97
|
-
seo: {
|
|
98
|
-
title: `${name} — Adaptive Learning`,
|
|
99
|
-
description: config.tagline,
|
|
100
|
-
keywords: [niche, 'learning', 'adaptive', 'education'],
|
|
101
|
-
},
|
|
102
|
-
}, { lineWidth: 120, noRefs: true });
|
|
103
|
-
}
|
|
104
42
|
function registerCreateBrandCommand(createCmd) {
|
|
105
43
|
createCmd
|
|
106
44
|
.command('brand')
|
|
@@ -111,11 +49,12 @@ function registerCreateBrandCommand(createCmd) {
|
|
|
111
49
|
.option('--org <slug>', 'Organization slug')
|
|
112
50
|
.option('-o, --output <file>', 'Output file path (defaults to stdout)')
|
|
113
51
|
.action(async (opts) => {
|
|
114
|
-
const
|
|
52
|
+
const obj = (0, shared_1.scaffoldBrandObject)(opts.niche, {
|
|
115
53
|
name: opts.name,
|
|
116
54
|
domain: opts.domain,
|
|
117
55
|
orgSlug: opts.org,
|
|
118
56
|
});
|
|
57
|
+
const yamlContent = yaml.dump(obj, { lineWidth: 120, noRefs: true });
|
|
119
58
|
(0, analytics_1.cliCapture)('brand scaffolded', { niche: opts.niche });
|
|
120
59
|
if (opts.output) {
|
|
121
60
|
fs.writeFileSync(opts.output, yamlContent);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-brand.js","sourceRoot":"","sources":["../../src/commands/create-brand.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"create-brand.js","sourceRoot":"","sources":["../../src/commands/create-brand.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAOA,gEA4BC;AAlCD,uCAAyB;AACzB,8CAAgC;AAChC,6CAAuD;AACvD,0CAAuC;AACvC,gDAA8C;AAE9C,SAAgB,0BAA0B,CAAC,SAAkB;IAC3D,SAAS;SACN,OAAO,CAAC,OAAO,CAAC;SAChB,WAAW,CAAC,gCAAgC,CAAC;SAC7C,cAAc,CAAC,iBAAiB,EAAE,2DAA2D,CAAC;SAC9F,MAAM,CAAC,eAAe,EAAE,YAAY,CAAC;SACrC,MAAM,CAAC,mBAAmB,EAAE,eAAe,CAAC;SAC5C,MAAM,CAAC,cAAc,EAAE,mBAAmB,CAAC;SAC3C,MAAM,CAAC,qBAAqB,EAAE,uCAAuC,CAAC;SACtE,MAAM,CAAC,KAAK,EAAE,IAAsF,EAAE,EAAE;QACvG,MAAM,GAAG,GAAG,IAAA,4BAAmB,EAAC,IAAI,CAAC,KAAK,EAAE;YAC1C,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,OAAO,EAAE,IAAI,CAAC,GAAG;SAClB,CAAC,CAAC;QACH,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QAErE,IAAA,sBAAU,EAAC,kBAAkB,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;QACtD,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChB,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;YAC3C,IAAA,eAAM,EACJ,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,EACxC,6BAA6B,IAAI,CAAC,MAAM,EAAE,CAC3C,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC"}
|
|
@@ -1,7 +1,3 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
export declare function scaffoldCourse(topic: string, options: {
|
|
3
|
-
hours?: number;
|
|
4
|
-
source?: string;
|
|
5
|
-
}): string;
|
|
6
2
|
export declare function registerCreateCourseCommand(program: Command): Command;
|
|
7
3
|
//# sourceMappingURL=create-course.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-course.d.ts","sourceRoot":"","sources":["../../src/commands/create-course.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"create-course.d.ts","sourceRoot":"","sources":["../../src/commands/create-course.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,OAAO,WAkC3D"}
|
|
@@ -33,41 +33,12 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.scaffoldCourse = scaffoldCourse;
|
|
37
36
|
exports.registerCreateCourseCommand = registerCreateCourseCommand;
|
|
38
37
|
const fs = __importStar(require("fs"));
|
|
39
38
|
const yaml = __importStar(require("js-yaml"));
|
|
39
|
+
const shared_1 = require("@graspful/shared");
|
|
40
40
|
const output_1 = require("../lib/output");
|
|
41
41
|
const analytics_1 = require("../lib/analytics");
|
|
42
|
-
function scaffoldCourse(topic, options) {
|
|
43
|
-
const slug = topic.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
44
|
-
return yaml.dump({
|
|
45
|
-
course: {
|
|
46
|
-
id: slug,
|
|
47
|
-
name: topic,
|
|
48
|
-
description: `Adaptive course on ${topic}`,
|
|
49
|
-
estimatedHours: options.hours || 10,
|
|
50
|
-
version: '2026.1',
|
|
51
|
-
sourceDocument: options.source || 'TODO: Add source document',
|
|
52
|
-
},
|
|
53
|
-
sections: [
|
|
54
|
-
{ id: 'foundations', name: 'Foundations', description: 'Core concepts' },
|
|
55
|
-
{ id: 'application', name: 'Application', description: 'Applied concepts' },
|
|
56
|
-
],
|
|
57
|
-
concepts: [
|
|
58
|
-
{
|
|
59
|
-
id: `${slug}-intro`,
|
|
60
|
-
name: `Introduction to ${topic}`,
|
|
61
|
-
section: 'foundations',
|
|
62
|
-
difficulty: 2,
|
|
63
|
-
estimatedMinutes: 15,
|
|
64
|
-
tags: ['foundational'],
|
|
65
|
-
prerequisites: [],
|
|
66
|
-
knowledgePoints: [],
|
|
67
|
-
},
|
|
68
|
-
],
|
|
69
|
-
}, { lineWidth: 120, noRefs: true });
|
|
70
|
-
}
|
|
71
42
|
function registerCreateCourseCommand(program) {
|
|
72
43
|
const create = program
|
|
73
44
|
.command('create')
|
|
@@ -81,17 +52,17 @@ function registerCreateCourseCommand(program) {
|
|
|
81
52
|
.option('-o, --output <file>', 'Output file path (defaults to stdout)')
|
|
82
53
|
.option('--scaffold-only', 'Generate scaffold without AI enrichment', true)
|
|
83
54
|
.action(async (opts) => {
|
|
84
|
-
const
|
|
55
|
+
const obj = (0, shared_1.scaffoldCourseObject)(opts.topic, {
|
|
85
56
|
hours: parseInt(opts.hours, 10),
|
|
86
57
|
source: opts.source,
|
|
87
58
|
});
|
|
59
|
+
const yamlContent = yaml.dump(obj, { lineWidth: 120, noRefs: true });
|
|
88
60
|
(0, analytics_1.cliCapture)('course scaffolded', { topic: opts.topic, estimated_hours: parseInt(opts.hours, 10) });
|
|
89
61
|
if (opts.output) {
|
|
90
62
|
fs.writeFileSync(opts.output, yamlContent);
|
|
91
63
|
(0, output_1.output)({ file: opts.output, topic: opts.topic }, `Scaffold written to ${opts.output}`);
|
|
92
64
|
}
|
|
93
65
|
else {
|
|
94
|
-
// Output raw YAML to stdout (not wrapped in JSON even in json mode for piping)
|
|
95
66
|
console.log(yamlContent);
|
|
96
67
|
}
|
|
97
68
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-course.js","sourceRoot":"","sources":["../../src/commands/create-course.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"create-course.js","sourceRoot":"","sources":["../../src/commands/create-course.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAOA,kEAkCC;AAxCD,uCAAyB;AACzB,8CAAgC;AAChC,6CAAwD;AACxD,0CAAuC;AACvC,gDAA8C;AAE9C,SAAgB,2BAA2B,CAAC,OAAgB;IAC1D,MAAM,MAAM,GAAG,OAAO;SACnB,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CAAC,yBAAyB,CAAC,CAAC;IAE1C,MAAM;SACH,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CAAC,iCAAiC,CAAC;SAC9C,cAAc,CAAC,iBAAiB,EAAE,mBAAmB,CAAC;SACtD,MAAM,CAAC,iBAAiB,EAAE,wBAAwB,EAAE,IAAI,CAAC;SACzD,MAAM,CAAC,mBAAmB,EAAE,2BAA2B,CAAC;SACxD,MAAM,CAAC,qBAAqB,EAAE,uCAAuC,CAAC;SACtE,MAAM,CAAC,iBAAiB,EAAE,yCAAyC,EAAE,IAAI,CAAC;SAC1E,MAAM,CAAC,KAAK,EAAE,IAA+F,EAAE,EAAE;QAChH,MAAM,GAAG,GAAG,IAAA,6BAAoB,EAAC,IAAI,CAAC,KAAK,EAAE;YAC3C,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC;YAC/B,MAAM,EAAE,IAAI,CAAC,MAAM;SACpB,CAAC,CAAC;QACH,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QAErE,IAAA,sBAAU,EAAC,mBAAmB,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,eAAe,EAAE,QAAQ,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC;QAElG,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChB,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;YAC3C,IAAA,eAAM,EACJ,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,EACxC,uBAAuB,IAAI,CAAC,MAAM,EAAE,CACrC,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC,CAAC,CAAC;IAEL,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"describe.d.ts","sourceRoot":"","sources":["../../src/commands/describe.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"describe.d.ts","sourceRoot":"","sources":["../../src/commands/describe.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,QA8CvD"}
|
|
@@ -39,35 +39,6 @@ const yaml = __importStar(require("js-yaml"));
|
|
|
39
39
|
const shared_1 = require("@graspful/shared");
|
|
40
40
|
const output_1 = require("../lib/output");
|
|
41
41
|
const analytics_1 = require("../lib/analytics");
|
|
42
|
-
function computeGraphDepth(concepts) {
|
|
43
|
-
const graph = new Map();
|
|
44
|
-
for (const c of concepts) {
|
|
45
|
-
graph.set(c.id, c.prerequisites);
|
|
46
|
-
}
|
|
47
|
-
const memo = new Map();
|
|
48
|
-
function depth(id, visited) {
|
|
49
|
-
if (memo.has(id))
|
|
50
|
-
return memo.get(id);
|
|
51
|
-
if (visited.has(id))
|
|
52
|
-
return 0; // cycle, avoid infinite loop
|
|
53
|
-
visited.add(id);
|
|
54
|
-
const prereqs = graph.get(id) ?? [];
|
|
55
|
-
let maxPrereqDepth = 0;
|
|
56
|
-
for (const prereq of prereqs) {
|
|
57
|
-
if (graph.has(prereq)) {
|
|
58
|
-
maxPrereqDepth = Math.max(maxPrereqDepth, depth(prereq, visited));
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
const d = maxPrereqDepth + 1;
|
|
62
|
-
memo.set(id, d);
|
|
63
|
-
return d;
|
|
64
|
-
}
|
|
65
|
-
let maxDepth = 0;
|
|
66
|
-
for (const c of concepts) {
|
|
67
|
-
maxDepth = Math.max(maxDepth, depth(c.id, new Set()));
|
|
68
|
-
}
|
|
69
|
-
return maxDepth;
|
|
70
|
-
}
|
|
71
42
|
function registerDescribeCommand(program) {
|
|
72
43
|
program
|
|
73
44
|
.command('describe <file>')
|
|
@@ -91,70 +62,22 @@ function registerDescribeCommand(program) {
|
|
|
91
62
|
(0, output_1.outputError)(`Invalid course YAML: ${result.error.issues[0]?.message ?? 'unknown error'}`);
|
|
92
63
|
process.exit(1);
|
|
93
64
|
}
|
|
94
|
-
const
|
|
95
|
-
const concepts = data.concepts;
|
|
96
|
-
const sections = data.sections;
|
|
97
|
-
const authoredConcepts = concepts.filter((c) => c.knowledgePoints.length > 0);
|
|
98
|
-
const stubConcepts = concepts.filter((c) => c.knowledgePoints.length === 0);
|
|
99
|
-
const kpCount = concepts.reduce((sum, c) => sum + c.knowledgePoints.length, 0);
|
|
100
|
-
const problemCount = concepts.reduce((sum, c) => sum + c.knowledgePoints.reduce((s, kp) => s + kp.problems.length, 0), 0);
|
|
101
|
-
const graphDepth = computeGraphDepth(concepts);
|
|
102
|
-
const conceptsWithoutKps = stubConcepts.map((c) => c.id);
|
|
103
|
-
const kpsWithoutProblems = [];
|
|
104
|
-
for (const c of concepts) {
|
|
105
|
-
for (const kp of c.knowledgePoints) {
|
|
106
|
-
if (kp.problems.length === 0) {
|
|
107
|
-
kpsWithoutProblems.push(`${c.id}/${kp.id}`);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
// Section breakdown
|
|
112
|
-
const sectionBreakdown = [];
|
|
113
|
-
if (sections.length > 0) {
|
|
114
|
-
for (const section of sections) {
|
|
115
|
-
const sectionConcepts = concepts.filter((c) => c.section === section.id);
|
|
116
|
-
const sKps = sectionConcepts.reduce((sum, c) => sum + c.knowledgePoints.length, 0);
|
|
117
|
-
const sProblems = sectionConcepts.reduce((sum, c) => sum + c.knowledgePoints.reduce((s, kp) => s + kp.problems.length, 0), 0);
|
|
118
|
-
sectionBreakdown.push({ section: section.id, concepts: sectionConcepts.length, kps: sKps, problems: sProblems });
|
|
119
|
-
}
|
|
120
|
-
// Concepts without a section
|
|
121
|
-
const unsectioned = concepts.filter((c) => !c.section);
|
|
122
|
-
if (unsectioned.length > 0) {
|
|
123
|
-
const uKps = unsectioned.reduce((sum, c) => sum + c.knowledgePoints.length, 0);
|
|
124
|
-
const uProblems = unsectioned.reduce((sum, c) => sum + c.knowledgePoints.reduce((s, kp) => s + kp.problems.length, 0), 0);
|
|
125
|
-
sectionBreakdown.push({ section: '(unsectioned)', concepts: unsectioned.length, kps: uKps, problems: uProblems });
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
const stats = {
|
|
129
|
-
courseName: data.course.name,
|
|
130
|
-
courseId: data.course.id,
|
|
131
|
-
version: data.course.version,
|
|
132
|
-
estimatedHours: data.course.estimatedHours,
|
|
133
|
-
concepts: concepts.length,
|
|
134
|
-
authoredConcepts: authoredConcepts.length,
|
|
135
|
-
stubConcepts: stubConcepts.length,
|
|
136
|
-
knowledgePoints: kpCount,
|
|
137
|
-
problems: problemCount,
|
|
138
|
-
graphDepth,
|
|
139
|
-
conceptsWithoutKps: conceptsWithoutKps.length,
|
|
140
|
-
kpsWithoutProblems: kpsWithoutProblems.length,
|
|
141
|
-
sections: sectionBreakdown,
|
|
142
|
-
};
|
|
65
|
+
const stats = (0, shared_1.describeCourse)(result.data);
|
|
143
66
|
const humanLines = [
|
|
144
|
-
`Course: "${
|
|
145
|
-
`Concepts: ${concepts
|
|
146
|
-
`KPs: ${
|
|
147
|
-
`Graph depth: ${graphDepth}`,
|
|
148
|
-
`Missing: ${conceptsWithoutKps
|
|
67
|
+
`Course: "${stats.courseName}" (v${stats.version})`,
|
|
68
|
+
`Concepts: ${stats.concepts} (${stats.authoredConcepts} authored, ${stats.stubConcepts} stubs)`,
|
|
69
|
+
`KPs: ${stats.knowledgePoints}, Problems: ${stats.problems}`,
|
|
70
|
+
`Graph depth: ${stats.graphDepth}`,
|
|
71
|
+
`Missing: ${stats.conceptsWithoutKps} concepts need KPs, ${stats.kpsWithoutProblems} KPs need problems`,
|
|
149
72
|
];
|
|
150
|
-
if (
|
|
73
|
+
if (stats.sections.length > 0) {
|
|
151
74
|
humanLines.push('');
|
|
152
75
|
humanLines.push('Sections:');
|
|
153
|
-
for (const s of
|
|
76
|
+
for (const s of stats.sections) {
|
|
154
77
|
humanLines.push(` ${s.section}: ${s.concepts} concepts, ${s.kps} KPs, ${s.problems} problems`);
|
|
155
78
|
}
|
|
156
79
|
}
|
|
157
|
-
(0, analytics_1.cliCapture)('course described', { concept_count: concepts
|
|
80
|
+
(0, analytics_1.cliCapture)('course described', { concept_count: stats.concepts, kp_count: stats.knowledgePoints, problem_count: stats.problems });
|
|
158
81
|
(0, output_1.output)(stats, humanLines.join('\n'));
|
|
159
82
|
});
|
|
160
83
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"describe.js","sourceRoot":"","sources":["../../src/commands/describe.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"describe.js","sourceRoot":"","sources":["../../src/commands/describe.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAOA,0DA8CC;AApDD,uCAAyB;AACzB,8CAAgC;AAChC,6CAAoE;AACpE,0CAAoD;AACpD,gDAA8C;AAE9C,SAAgB,uBAAuB,CAAC,OAAgB;IACtD,OAAO;SACJ,OAAO,CAAC,iBAAiB,CAAC;SAC1B,WAAW,CAAC,8CAA8C,CAAC;SAC3D,MAAM,CAAC,KAAK,EAAE,IAAY,EAAE,EAAE;QAC7B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACzB,IAAA,oBAAW,EAAC,mBAAmB,IAAI,EAAE,CAAC,CAAC;YACvC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,IAAI,GAAY,CAAC;QACjB,IAAI,CAAC;YACH,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;QAClD,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,GAAG,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;YACvD,IAAA,oBAAW,EAAC,qBAAqB,GAAG,EAAE,CAAC,CAAC;YACxC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,MAAM,MAAM,GAAG,yBAAgB,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QAC/C,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,IAAA,oBAAW,EAAC,wBAAwB,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,OAAO,IAAI,eAAe,EAAE,CAAC,CAAC;YAC1F,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,MAAM,KAAK,GAAG,IAAA,uBAAc,EAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAE1C,MAAM,UAAU,GAAG;YACjB,YAAY,KAAK,CAAC,UAAU,OAAO,KAAK,CAAC,OAAO,GAAG;YACnD,aAAa,KAAK,CAAC,QAAQ,KAAK,KAAK,CAAC,gBAAgB,cAAc,KAAK,CAAC,YAAY,SAAS;YAC/F,QAAQ,KAAK,CAAC,eAAe,eAAe,KAAK,CAAC,QAAQ,EAAE;YAC5D,gBAAgB,KAAK,CAAC,UAAU,EAAE;YAClC,YAAY,KAAK,CAAC,kBAAkB,uBAAuB,KAAK,CAAC,kBAAkB,oBAAoB;SACxG,CAAC;QAEF,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC9B,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACpB,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YAC7B,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;gBAC/B,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,QAAQ,cAAc,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,QAAQ,WAAW,CAAC,CAAC;YAClG,CAAC;QACH,CAAC;QAED,IAAA,sBAAU,EAAC,kBAAkB,EAAE,EAAE,aAAa,EAAE,KAAK,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,CAAC,eAAe,EAAE,aAAa,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;QAClI,IAAA,eAAM,EAAC,KAAK,EAAE,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;AACP,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fill-concept.d.ts","sourceRoot":"","sources":["../../src/commands/fill-concept.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,OAAO,
|
|
1
|
+
{"version":3,"file":"fill-concept.d.ts","sourceRoot":"","sources":["../../src/commands/fill-concept.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,OAAO,WA+C1D"}
|
|
@@ -63,55 +63,20 @@ function registerFillConceptCommand(program) {
|
|
|
63
63
|
(0, output_1.outputError)(`YAML parse error: ${msg}`);
|
|
64
64
|
process.exit(1);
|
|
65
65
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
const data = parsed.data;
|
|
72
|
-
const concept = data.concepts.find((c) => c.id === conceptId);
|
|
73
|
-
if (!concept) {
|
|
74
|
-
(0, output_1.outputError)(`Concept "${conceptId}" not found. Available: ${data.concepts.map((c) => c.id).join(', ')}`);
|
|
75
|
-
process.exit(1);
|
|
76
|
-
}
|
|
77
|
-
if (concept.knowledgePoints.length > 0) {
|
|
78
|
-
(0, output_1.output)({ conceptId, existingKps: concept.knowledgePoints.length, action: 'skipped' }, `Concept "${conceptId}" already has ${concept.knowledgePoints.length} KP(s). Use --force to overwrite (not yet implemented).`);
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
const kpCount = parseInt(opts.kps, 10);
|
|
82
|
-
const problemsPerKp = parseInt(opts.problems, 10);
|
|
83
|
-
const newKps = [];
|
|
84
|
-
for (let i = 1; i <= kpCount; i++) {
|
|
85
|
-
const problems = [];
|
|
86
|
-
for (let j = 1; j <= problemsPerKp; j++) {
|
|
87
|
-
problems.push({
|
|
88
|
-
id: `${conceptId}-kp${i}-p${j}`,
|
|
89
|
-
type: 'multiple_choice',
|
|
90
|
-
question: `TODO: Write question ${j} for ${conceptId} KP${i}`,
|
|
91
|
-
options: ['Option A', 'Option B', 'Option C', 'Option D'],
|
|
92
|
-
correct: 0,
|
|
93
|
-
explanation: 'TODO: Explain the correct answer',
|
|
94
|
-
difficulty: Math.min(j + 1, 5),
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
newKps.push({
|
|
98
|
-
id: `${conceptId}-kp${i}`,
|
|
99
|
-
instruction: `TODO: Write instruction for ${concept.name} — knowledge point ${i}`,
|
|
100
|
-
workedExample: `TODO: Write a worked example for ${concept.name} — knowledge point ${i}`,
|
|
101
|
-
problems,
|
|
66
|
+
try {
|
|
67
|
+
const updated = (0, shared_1.fillConceptInRaw)(raw, conceptId, {
|
|
68
|
+
kps: parseInt(opts.kps, 10),
|
|
69
|
+
problemsPerKp: parseInt(opts.problems, 10),
|
|
102
70
|
});
|
|
71
|
+
const updatedYaml = yaml.dump(updated, { lineWidth: 120, noRefs: true, schema: yaml.JSON_SCHEMA });
|
|
72
|
+
fs.writeFileSync(file, updatedYaml);
|
|
73
|
+
(0, analytics_1.cliCapture)('concept filled', { concept_id: conceptId });
|
|
74
|
+
(0, output_1.output)({ conceptId, kpsAdded: parseInt(opts.kps, 10), problemsPerKp: parseInt(opts.problems, 10), file }, `Added ${opts.kps} KP stub(s) with ${opts.problems} problem(s) each to "${conceptId}" in ${file}`);
|
|
103
75
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const targetConcept = concepts.find((c) => c['id'] === conceptId);
|
|
108
|
-
if (targetConcept) {
|
|
109
|
-
targetConcept['knowledgePoints'] = newKps;
|
|
76
|
+
catch (e) {
|
|
77
|
+
(0, output_1.outputError)(e instanceof Error ? e.message : String(e));
|
|
78
|
+
process.exit(1);
|
|
110
79
|
}
|
|
111
|
-
const updatedYaml = yaml.dump(rawObj, { lineWidth: 120, noRefs: true, schema: yaml.JSON_SCHEMA });
|
|
112
|
-
fs.writeFileSync(file, updatedYaml);
|
|
113
|
-
(0, analytics_1.cliCapture)('concept filled', { concept_id: conceptId });
|
|
114
|
-
(0, output_1.output)({ conceptId, kpsAdded: kpCount, problemsPerKp, file }, `Added ${kpCount} KP stub(s) with ${problemsPerKp} problem(s) each to "${conceptId}" in ${file}`);
|
|
115
80
|
});
|
|
116
81
|
return fill;
|
|
117
82
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fill-concept.js","sourceRoot":"","sources":["../../src/commands/fill-concept.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAOA,
|
|
1
|
+
{"version":3,"file":"fill-concept.js","sourceRoot":"","sources":["../../src/commands/fill-concept.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAOA,gEA+CC;AArDD,uCAAyB;AACzB,8CAAgC;AAChC,6CAAoD;AACpD,0CAAoD;AACpD,gDAA8C;AAE9C,SAAgB,0BAA0B,CAAC,OAAgB;IACzD,MAAM,IAAI,GAAG,OAAO;SACjB,OAAO,CAAC,MAAM,CAAC;SACf,WAAW,CAAC,uBAAuB,CAAC,CAAC;IAExC,IAAI;SACD,OAAO,CAAC,4BAA4B,CAAC;SACrC,WAAW,CAAC,oCAAoC,CAAC;SACjD,MAAM,CAAC,eAAe,EAAE,2BAA2B,EAAE,GAAG,CAAC;SACzD,MAAM,CAAC,oBAAoB,EAAE,gCAAgC,EAAE,GAAG,CAAC;SACnE,MAAM,CAAC,KAAK,EAAE,IAAY,EAAE,SAAiB,EAAE,IAAuC,EAAE,EAAE;QACzF,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACzB,IAAA,oBAAW,EAAC,mBAAmB,IAAI,EAAE,CAAC,CAAC;YACvC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC/C,IAAI,GAAY,CAAC;QACjB,IAAI,CAAC;YACH,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC3B,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,GAAG,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;YACvD,IAAA,oBAAW,EAAC,qBAAqB,GAAG,EAAE,CAAC,CAAC;YACxC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,IAAA,yBAAgB,EAAC,GAAG,EAAE,SAAS,EAAE;gBAC/C,GAAG,EAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;gBAC3B,aAAa,EAAE,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,CAAC;aAC3C,CAAC,CAAC;YAEH,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;YACnG,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;YAEpC,IAAA,sBAAU,EAAC,gBAAgB,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC,CAAC;YACxD,IAAA,eAAM,EACJ,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,aAAa,EAAE,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,EACjG,SAAS,IAAI,CAAC,GAAG,oBAAoB,IAAI,CAAC,QAAQ,wBAAwB,SAAS,QAAQ,IAAI,EAAE,CAClG,CAAC;QACJ,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,IAAA,oBAAW,EAAC,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;YACxD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,CAAC,CAAC;IAEL,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/commands/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/commands/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,QAsCvD"}
|
|
@@ -39,52 +39,6 @@ const yaml = __importStar(require("js-yaml"));
|
|
|
39
39
|
const shared_1 = require("@graspful/shared");
|
|
40
40
|
const output_1 = require("../lib/output");
|
|
41
41
|
const analytics_1 = require("../lib/analytics");
|
|
42
|
-
function detectFileType(data) {
|
|
43
|
-
if (typeof data !== 'object' || data === null)
|
|
44
|
-
return null;
|
|
45
|
-
const obj = data;
|
|
46
|
-
if ('course' in obj)
|
|
47
|
-
return 'course';
|
|
48
|
-
if ('brand' in obj)
|
|
49
|
-
return 'brand';
|
|
50
|
-
if ('academy' in obj)
|
|
51
|
-
return 'academy';
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
function detectCycles(concepts) {
|
|
55
|
-
const graph = new Map();
|
|
56
|
-
for (const c of concepts) {
|
|
57
|
-
graph.set(c.id, c.prerequisites);
|
|
58
|
-
}
|
|
59
|
-
const visited = new Set();
|
|
60
|
-
const inStack = new Set();
|
|
61
|
-
const cycles = [];
|
|
62
|
-
function dfs(node, path) {
|
|
63
|
-
if (inStack.has(node)) {
|
|
64
|
-
const cycleStart = path.indexOf(node);
|
|
65
|
-
const cycle = path.slice(cycleStart).concat(node);
|
|
66
|
-
cycles.push(`Cycle: ${cycle.join(' -> ')}`);
|
|
67
|
-
return true;
|
|
68
|
-
}
|
|
69
|
-
if (visited.has(node))
|
|
70
|
-
return false;
|
|
71
|
-
visited.add(node);
|
|
72
|
-
inStack.add(node);
|
|
73
|
-
path.push(node);
|
|
74
|
-
for (const dep of graph.get(node) ?? []) {
|
|
75
|
-
dfs(dep, path);
|
|
76
|
-
}
|
|
77
|
-
path.pop();
|
|
78
|
-
inStack.delete(node);
|
|
79
|
-
return false;
|
|
80
|
-
}
|
|
81
|
-
for (const id of graph.keys()) {
|
|
82
|
-
if (!visited.has(id)) {
|
|
83
|
-
dfs(id, []);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
return cycles;
|
|
87
|
-
}
|
|
88
42
|
function registerValidateCommand(program) {
|
|
89
43
|
program
|
|
90
44
|
.command('validate <file>')
|
|
@@ -103,54 +57,13 @@ function registerValidateCommand(program) {
|
|
|
103
57
|
(0, output_1.output)({ valid: false, errors: [`YAML parse error: ${msg}`], stats: {} }, `FAIL YAML parse error: ${msg}`);
|
|
104
58
|
process.exit(1);
|
|
105
59
|
}
|
|
106
|
-
const
|
|
107
|
-
if (!
|
|
108
|
-
(0, output_1.output)({
|
|
109
|
-
process.exit(1);
|
|
110
|
-
}
|
|
111
|
-
const schemaMap = {
|
|
112
|
-
course: shared_1.CourseYamlSchema,
|
|
113
|
-
brand: shared_1.BrandYamlSchema,
|
|
114
|
-
academy: shared_1.AcademyManifestSchema,
|
|
115
|
-
};
|
|
116
|
-
const result = schemaMap[fileType].safeParse(raw);
|
|
117
|
-
if (!result.success) {
|
|
118
|
-
const errors = result.error.issues.map((i) => `${i.path.join('.')}: ${i.message}`);
|
|
119
|
-
(0, output_1.output)({ valid: false, fileType, errors, stats: {} }, `FAIL ${fileType} validation (${errors.length} error${errors.length === 1 ? '' : 's'}):\n${errors.map((e) => ` - ${e}`).join('\n')}`);
|
|
120
|
-
process.exit(1);
|
|
121
|
-
}
|
|
122
|
-
// For courses, also check DAG
|
|
123
|
-
const dagErrors = [];
|
|
124
|
-
let stats = { fileType };
|
|
125
|
-
if (fileType === 'course') {
|
|
126
|
-
const data = result.data;
|
|
127
|
-
const conceptIds = new Set(data.concepts.map((c) => c.id));
|
|
128
|
-
// Check prerequisite references
|
|
129
|
-
for (const concept of data.concepts) {
|
|
130
|
-
for (const prereq of concept.prerequisites) {
|
|
131
|
-
if (!conceptIds.has(prereq)) {
|
|
132
|
-
dagErrors.push(`Concept "${concept.id}" has unknown prerequisite "${prereq}"`);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
// Check for cycles
|
|
137
|
-
const cycles = detectCycles(data.concepts.map((c) => ({ id: c.id, prerequisites: c.prerequisites })));
|
|
138
|
-
dagErrors.push(...cycles);
|
|
139
|
-
const kpCount = data.concepts.reduce((sum, c) => sum + c.knowledgePoints.length, 0);
|
|
140
|
-
const problemCount = data.concepts.reduce((sum, c) => sum + c.knowledgePoints.reduce((s, kp) => s + kp.problems.length, 0), 0);
|
|
141
|
-
stats = {
|
|
142
|
-
fileType,
|
|
143
|
-
concepts: data.concepts.length,
|
|
144
|
-
knowledgePoints: kpCount,
|
|
145
|
-
problems: problemCount,
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
if (dagErrors.length > 0) {
|
|
149
|
-
(0, output_1.output)({ valid: false, fileType, errors: dagErrors, stats }, `FAIL DAG validation (${dagErrors.length} error${dagErrors.length === 1 ? '' : 's'}):\n${dagErrors.map((e) => ` - ${e}`).join('\n')}`);
|
|
60
|
+
const result = (0, shared_1.validateParsedYaml)(raw);
|
|
61
|
+
if (!result.valid) {
|
|
62
|
+
(0, output_1.output)(result, `FAIL ${result.fileType ?? 'unknown'} validation (${result.errors.length} error${result.errors.length === 1 ? '' : 's'}):\n${result.errors.map((e) => ` - ${e}`).join('\n')}`);
|
|
150
63
|
process.exit(1);
|
|
151
64
|
}
|
|
152
|
-
(0, analytics_1.cliCapture)('course validated', { valid: true, error_count: 0, file_type: fileType });
|
|
153
|
-
(0, output_1.output)(
|
|
65
|
+
(0, analytics_1.cliCapture)('course validated', { valid: true, error_count: 0, file_type: result.fileType });
|
|
66
|
+
(0, output_1.output)(result, `PASS ${result.fileType} validation\n${Object.entries(result.stats).map(([k, v]) => ` ${k}: ${v}`).join('\n')}`);
|
|
154
67
|
});
|
|
155
68
|
}
|
|
156
69
|
//# sourceMappingURL=validate.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/commands/validate.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/commands/validate.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAOA,0DAsCC;AA5CD,uCAAyB;AACzB,8CAAgC;AAChC,6CAAsD;AACtD,0CAAoD;AACpD,gDAA8C;AAE9C,SAAgB,uBAAuB,CAAC,OAAgB;IACtD,OAAO;SACJ,OAAO,CAAC,iBAAiB,CAAC;SAC1B,WAAW,CAAC,mEAAmE,CAAC;SAChF,MAAM,CAAC,KAAK,EAAE,IAAY,EAAE,EAAE;QAC7B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACzB,IAAA,oBAAW,EAAC,mBAAmB,IAAI,EAAE,CAAC,CAAC;YACvC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,IAAI,GAAY,CAAC;QACjB,IAAI,CAAC;YACH,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;QAClD,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,GAAG,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;YACvD,IAAA,eAAM,EACJ,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,qBAAqB,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EACjE,2BAA2B,GAAG,EAAE,CACjC,CAAC;YACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,MAAM,MAAM,GAAG,IAAA,2BAAkB,EAAC,GAAG,CAAC,CAAC;QAEvC,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YAClB,IAAA,eAAM,EACJ,MAAM,EACN,SAAS,MAAM,CAAC,QAAQ,IAAI,SAAS,gBAAgB,MAAM,CAAC,MAAM,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,OAAO,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAChL,CAAC;YACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,IAAA,sBAAU,EAAC,kBAAkB,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;QAC5F,IAAA,eAAM,EACJ,MAAM,EACN,SAAS,MAAM,CAAC,QAAQ,gBAAgB,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAClH,CAAC;IACJ,CAAC,CAAC,CAAC;AACP,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@graspful/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"description": "Create adaptive learning courses from YAML. CLI and MCP server for AI agents.",
|
|
5
5
|
"keywords": ["course", "learning", "adaptive", "mcp", "mcp-server", "ai", "ai-agent", "agent", "cli", "education", "edtech", "knowledge-graph", "spaced-repetition", "course-creation", "yaml", "adaptive-learning", "lms"],
|
|
6
6
|
"repository": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"lint": "echo 'no lint configured yet'"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@graspful/shared": "^0.2.
|
|
24
|
+
"@graspful/shared": "^0.2.3",
|
|
25
25
|
"chalk": "^5.4.0",
|
|
26
26
|
"commander": "^13.0.0",
|
|
27
27
|
"js-yaml": "^4.1.0",
|