@lernapps/tooling 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/check/rules/competencies-on-map.mjs +85 -0
- package/dist/check/rules/one-competency-per-page.mjs +18 -0
- package/dist/check/rules/prerequisites-linked.mjs +38 -0
- package/dist/cli.mjs +191 -52
- package/dist/explainer-map-DW4XCcY0.mjs +124 -0
- package/dist/skills/lernapps-app/SKILL.md +2 -1
- package/dist/skills/lernapps-cli/SKILL.md +13 -4
- package/dist/skills/lernapps-content-questions/SKILL.md +2 -0
- package/dist/skills/lernapps-content-questions/references/question-types.md +2 -1
- package/dist/skills/lernapps-content-research/SKILL.md +41 -5
- package/dist/skills/lernapps-exercise-patterns/references/mathematics.md +25 -1
- package/dist/skills/lernapps-exercise-patterns/references/physics.md +1 -0
- package/dist/skills/lernapps-explainer/SKILL.md +10 -4
- package/dist/skills/lernapps-explainer/references/blocks.md +7 -3
- package/dist/skills/lernapps-explainer/references/exercises.md +6 -0
- package/dist/skills/lernapps-explainer/references/rules.md +29 -0
- package/dist/skills/lernapps-explainer/references/structure.md +62 -0
- package/dist/skills/lernapps-explainer-design/SKILL.md +46 -2
- package/dist/skills/lernapps-explainer-design/references/rules.md +1 -0
- package/dist/skills/lernapps-explainer-self-assessment/SKILL.md +1 -0
- package/dist/skills/lernapps-listing/SKILL.md +9 -0
- package/generator/create.ts +39 -10
- package/generator/init.ts +89 -19
- package/generator/update.ts +53 -8
- package/guidance/increment-template.md +13 -5
- package/guidance/plan-template.md +25 -11
- package/guidance/workflow-rules.md +3 -1
- package/package.json +2 -2
- package/skills/archetypes/lernapps-explainer/SKILL.md +10 -4
- package/skills/archetypes/lernapps-explainer/references/blocks.md +7 -3
- package/skills/archetypes/lernapps-explainer/references/exercises.md +6 -0
- package/skills/archetypes/lernapps-explainer/references/rules.md +29 -0
- package/skills/archetypes/lernapps-explainer/references/structure.md +62 -0
- package/skills/archetypes/lernapps-explainer-design/SKILL.md +46 -2
- package/skills/archetypes/lernapps-explainer-design/references/rules.md +1 -0
- package/skills/archetypes/lernapps-explainer-self-assessment/SKILL.md +1 -0
- package/skills/content/lernapps-content-questions/SKILL.md +2 -0
- package/skills/content/lernapps-content-questions/references/question-types.md +2 -1
- package/skills/content/lernapps-content-research/SKILL.md +41 -5
- package/skills/content/lernapps-exercise-patterns/references/mathematics.md +25 -1
- package/skills/content/lernapps-exercise-patterns/references/physics.md +1 -0
- package/skills/process/lernapps-app/SKILL.md +2 -1
- package/skills/process/lernapps-listing/SKILL.md +9 -0
- package/skills/tooling/lernapps-cli/SKILL.md +13 -4
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { a as strandsOf, i as readExplainer, n as loadMap, r as mapUrl } from "../../explainer-map-DW4XCcY0.mjs";
|
|
2
|
+
// check/rules/competencies-on-map.ts
|
|
3
|
+
const RECAP = /^([a-z0-9]+(?:-[a-z0-9]+)*)\/([a-z0-9]+(?:-[a-z0-9]+)*)$/;
|
|
4
|
+
var competencies_on_map_default = {
|
|
5
|
+
id: "competencies-on-map",
|
|
6
|
+
url: "https://lernapps.net/tooling/rules/#competencies-on-map",
|
|
7
|
+
description: "The strand of an explainer in format 2 is a strand of its subject's learning map, every page's competencies belong to it, and every recap page repeats a competency of the map that the app does not practise.",
|
|
8
|
+
severity: "warning",
|
|
9
|
+
run: async ({ project }) => {
|
|
10
|
+
const explainer = project === void 0 ? void 0 : readExplainer(project);
|
|
11
|
+
if (explainer === void 0 || explainer.strand === "none") return void 0;
|
|
12
|
+
const [subject = "", strandId = ""] = explainer.strand.split("/");
|
|
13
|
+
const url = mapUrl(subject);
|
|
14
|
+
const loaded = await loadMap(url);
|
|
15
|
+
if ("failed" in loaded) return [{
|
|
16
|
+
where: explainer.strandAt,
|
|
17
|
+
found: `the strand and the competencies could not be checked: ${loaded.failed}`,
|
|
18
|
+
fix: "Nothing to fix for this here: check again later (lernapps check); the listing checks the ids against the map",
|
|
19
|
+
severity: "hint"
|
|
20
|
+
}];
|
|
21
|
+
const strands = loaded.map.strands;
|
|
22
|
+
const strandOf = strandsOf(loaded.map);
|
|
23
|
+
const problems = [];
|
|
24
|
+
const practised = /* @__PURE__ */ new Map();
|
|
25
|
+
for (const page of explainer.pages) for (const id of page.competencies) if (!practised.has(id)) practised.set(id, page.where);
|
|
26
|
+
const strand = strands.find((candidate) => candidate.id === strandId);
|
|
27
|
+
if (strand === void 0) problems.push({
|
|
28
|
+
where: explainer.strandAt,
|
|
29
|
+
found: `the strand ${explainer.strand} is not on the map of ${subject} (its strands: ${strands.map((s) => s.id).join(", ")})`,
|
|
30
|
+
fix: `Name a strand of ${url} as <subject>/<strand>, or strand: none; a strand the map lacks is proposed to lernapps/apps (map/${subject}.yaml)`
|
|
31
|
+
});
|
|
32
|
+
for (const page of explainer.pages) {
|
|
33
|
+
for (const id of page.competencies) {
|
|
34
|
+
const of = strandOf.get(id);
|
|
35
|
+
if (of === void 0) problems.push({
|
|
36
|
+
where: page.where,
|
|
37
|
+
found: `the competency ${id} is not on the map of ${subject}`,
|
|
38
|
+
fix: `Use an id of the strand ${explainer.strand} from ${url}; a competency the map lacks is proposed to lernapps/apps (map/${subject}.yaml), never invented in the app`
|
|
39
|
+
});
|
|
40
|
+
else if (strand !== void 0 && of !== strandId) problems.push({
|
|
41
|
+
where: page.where,
|
|
42
|
+
found: `the competency ${id} belongs to the strand ${subject}/${of}, not to the app's strand ${explainer.strand}`,
|
|
43
|
+
fix: `Practise only competencies of ${explainer.strand}; a page of ${subject}/${of} belongs in an app of that strand`
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
if (page.recap !== void 0) problems.push(...await recapProblems(page.where, page.recap, {
|
|
47
|
+
subject,
|
|
48
|
+
map: loaded.map,
|
|
49
|
+
url,
|
|
50
|
+
practised
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
return problems;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
/** A recap page's `recap`: a competency of a map, of any strand, that the app does not practise on a page of its own. */
|
|
57
|
+
async function recapProblems(where, recap, app) {
|
|
58
|
+
const { subject, url, practised } = app;
|
|
59
|
+
const [, recapSubject = "", id = ""] = RECAP.exec(recap) ?? [];
|
|
60
|
+
if (recapSubject === "") return [{
|
|
61
|
+
where,
|
|
62
|
+
found: `the recap ${recap} is not <subject>/<competency>`,
|
|
63
|
+
fix: `Name the competency the page repeats as <subject>/<competency>, e.g. ${subject}/<id> from ${url}`
|
|
64
|
+
}];
|
|
65
|
+
const other = recapSubject === subject ? { map: app.map } : await loadMap(mapUrl(recapSubject));
|
|
66
|
+
if ("failed" in other) return [{
|
|
67
|
+
where,
|
|
68
|
+
found: `the recap ${recap} could not be checked: ${other.failed}`,
|
|
69
|
+
fix: "Nothing to fix for this here: check again later (lernapps check); the listing checks the recap against the map",
|
|
70
|
+
severity: "hint"
|
|
71
|
+
}];
|
|
72
|
+
if (strandsOf(other.map).get(id) === void 0) return [{
|
|
73
|
+
where,
|
|
74
|
+
found: `the recap ${recap} is not on the map of ${recapSubject}`,
|
|
75
|
+
fix: `Name a competency of ${mapUrl(recapSubject)} that a page of this app requires; a competency the map lacks is proposed to lernapps/apps (map/${recapSubject}.yaml)`
|
|
76
|
+
}];
|
|
77
|
+
const page = recapSubject === subject ? practised.get(id) : void 0;
|
|
78
|
+
if (page !== void 0) return [{
|
|
79
|
+
where,
|
|
80
|
+
found: `the recap ${recap} is a competency the app also practises (${page})`,
|
|
81
|
+
fix: `A competency the app practises needs no recap: remove this recap page, or the competency ${id} from the page that practises it`
|
|
82
|
+
}];
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
export { competencies_on_map_default as default };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { i as readExplainer } from "../../explainer-map-DW4XCcY0.mjs";
|
|
2
|
+
// check/rules/one-competency-per-page.ts
|
|
3
|
+
var one_competency_per_page_default = {
|
|
4
|
+
id: "one-competency-per-page",
|
|
5
|
+
url: "https://lernapps.net/tooling/rules/#one-competency-per-page",
|
|
6
|
+
description: "Each page of an explainer in format 2 names at most one competency of the learning map.",
|
|
7
|
+
severity: "hint",
|
|
8
|
+
run: ({ project }) => {
|
|
9
|
+
const explainer = project === void 0 ? void 0 : readExplainer(project);
|
|
10
|
+
if (explainer === void 0) return void 0;
|
|
11
|
+
return explainer.pages.filter((page) => page.competencies.length > 1).map((page) => ({
|
|
12
|
+
where: page.where,
|
|
13
|
+
found: `the page names ${page.competencies.length} competencies (${page.competencies.join(", ")})`,
|
|
14
|
+
fix: "Give each competency a page of its own, and keep one id in competencies on each; several pages for one large competency are fine"
|
|
15
|
+
}));
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
export { one_competency_per_page_default as default };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { a as strandsOf, i as readExplainer, n as loadMap, r as mapUrl, t as competencyPage } from "../../explainer-map-DW4XCcY0.mjs";
|
|
2
|
+
// check/rules/prerequisites-linked.ts
|
|
3
|
+
const escape = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4
|
+
var prerequisites_linked_default = {
|
|
5
|
+
id: "prerequisites-linked",
|
|
6
|
+
url: "https://lernapps.net/tooling/rules/#prerequisites-linked",
|
|
7
|
+
description: "Every prerequisite that a page's competencies require and the app does not practise has a recap page in the app or a link to its page on the learning map.",
|
|
8
|
+
severity: "hint",
|
|
9
|
+
run: async ({ project }) => {
|
|
10
|
+
const explainer = project === void 0 ? void 0 : readExplainer(project);
|
|
11
|
+
if (explainer === void 0 || explainer.strand === "none") return void 0;
|
|
12
|
+
const [subject = ""] = explainer.strand.split("/");
|
|
13
|
+
const loaded = await loadMap(mapUrl(subject));
|
|
14
|
+
if ("failed" in loaded) return [];
|
|
15
|
+
const strandOf = strandsOf(loaded.map);
|
|
16
|
+
const requires = new Map(loaded.map.strands.flatMap((strand) => strand.competencies.map((competency) => [competency.id, competency.requires ?? []])));
|
|
17
|
+
const practised = new Set(explainer.pages.flatMap((page) => page.competencies));
|
|
18
|
+
const recapped = new Set(explainer.pages.flatMap((page) => page.recap === void 0 ? [] : [page.recap]));
|
|
19
|
+
const problems = [];
|
|
20
|
+
for (const page of explainer.pages) {
|
|
21
|
+
const named = /* @__PURE__ */ new Set();
|
|
22
|
+
for (const id of page.competencies) for (const prerequisite of requires.get(id) ?? []) {
|
|
23
|
+
const of = strandOf.get(prerequisite);
|
|
24
|
+
if (of === void 0 || practised.has(prerequisite) || named.has(prerequisite)) continue;
|
|
25
|
+
named.add(prerequisite);
|
|
26
|
+
if (recapped.has(`${subject}/${prerequisite}`)) continue;
|
|
27
|
+
if (new RegExp(`/apps/map/${escape(subject)}/c/${escape(prerequisite)}(?:/|\\b)`).test(page.text)) continue;
|
|
28
|
+
problems.push({
|
|
29
|
+
where: page.where,
|
|
30
|
+
found: `${id} requires ${subject}/${prerequisite} (strand ${subject}/${of}), which the app does not practise; no recap page repeats it and the page does not link it`,
|
|
31
|
+
fix: `Name it in the page's text with a link to ${competencyPage(subject, prerequisite)}, or add a recap page (recap: ${subject}/${prerequisite}, descriptive blocks only) before this page`
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return problems;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
export { prerequisites_linked_default as default };
|