@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
package/generator/init.ts
CHANGED
|
@@ -6,25 +6,28 @@
|
|
|
6
6
|
// plan has chosen one: `skills --archetype <name>` then adds that archetype's. `lernapps create` later takes over the
|
|
7
7
|
// package.json (the marker lernapps.init) and the installed packages.
|
|
8
8
|
import { spawnSync } from "node:child_process";
|
|
9
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
9
|
+
import { existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
10
10
|
import { createRequire } from "node:module";
|
|
11
|
+
import { tmpdir } from "node:os";
|
|
11
12
|
import { dirname, join, resolve } from "node:path";
|
|
12
13
|
import { parseArgs, type ParseArgsOptionsConfig } from "node:util";
|
|
13
14
|
import { UsageError } from "../src/usage.ts";
|
|
14
15
|
import { PLANS } from "../src/plan.ts";
|
|
15
|
-
import { CreateError, fromPackage, ownVersion, readJson, TOOLING, writeJson } from "./create.ts";
|
|
16
|
+
import { CreateError, fromPackage, ownVersion, readJson, TOOLING, WORK_BRANCH, writeJson } from "./create.ts";
|
|
16
17
|
|
|
17
18
|
/** The options of `lernapps init`, as node:util parseArgs takes them. */
|
|
18
19
|
export const INIT_OPTIONS = {
|
|
19
20
|
addon: { type: "string" },
|
|
21
|
+
tooling: { type: "string" },
|
|
20
22
|
help: { type: "boolean", short: "h", default: false },
|
|
21
23
|
} as const satisfies ParseArgsOptionsConfig;
|
|
22
24
|
|
|
23
|
-
export const INIT_USAGE = `Usage: lernapps init <short name> [--addon <package>]
|
|
25
|
+
export const INIT_USAGE = `Usage: lernapps init <short name> [--addon <package>] [--tooling <spec>]
|
|
24
26
|
|
|
25
27
|
Starts an app in the current folder, which must be empty except for .git. It needs git, Node and the npm registry; nothing
|
|
26
28
|
else. In order:
|
|
27
|
-
git repository on the branch work, which the plan names
|
|
29
|
+
git repository on the branch work, which the plan names; no commit. A repository without commits
|
|
30
|
+
switches to work; one with commits stays, and init warns: git switch -c work
|
|
28
31
|
package.json names the folder, depends on @lernapps/tooling (this version) and the add-on
|
|
29
32
|
npm install installs them
|
|
30
33
|
.vibe/plans/01-<short name>.md
|
|
@@ -36,6 +39,12 @@ Then follow the plan: it says what to do, and when to wait for the creator.
|
|
|
36
39
|
|
|
37
40
|
Options:
|
|
38
41
|
--addon <package> a package that extends the tooling, e.g. @lernapps/practice: its plan template and its skills
|
|
42
|
+
--tooling <spec> for maintainers: the npm spec of @lernapps/tooling to install instead of this version, e.g.
|
|
43
|
+
file:/path/to/lernapps-tooling-0.0.0.tgz or github:lernapps/tooling#<commit>; give the same
|
|
44
|
+
to lernapps create. Without it, a tooling of version 0.0.0 (a local build, not on the
|
|
45
|
+
registry) is packed with npm pack into a temporary folder and installed from that tarball
|
|
46
|
+
when its folder is a git checkout (file:<tarball>), with its dependencies, as from the
|
|
47
|
+
registry
|
|
39
48
|
-h, --help print this help
|
|
40
49
|
`;
|
|
41
50
|
|
|
@@ -62,22 +71,45 @@ const SKILLS_BIN = () => {
|
|
|
62
71
|
return join(dirname(manifest), bin?.["skills"] ?? "bin/cli.mjs");
|
|
63
72
|
};
|
|
64
73
|
|
|
65
|
-
|
|
66
|
-
|
|
74
|
+
export { WORK_BRANCH };
|
|
75
|
+
|
|
76
|
+
/** A git command in `dir`; a git that cannot run is an error of init. */
|
|
77
|
+
function git(dir: string, args: string[]) {
|
|
78
|
+
const result = spawnSync("git", args, { cwd: dir, encoding: "utf8" });
|
|
79
|
+
if (result.error) throw new CreateError(`git cannot run: ${result.error.message}. Install git, then run init again`);
|
|
80
|
+
return { ok: result.status === 0, out: `${result.stdout}${result.stderr}`.trim() };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** What `ensureRepository` did: a line for the output, and a warning when the plan's branch is not checked out. */
|
|
84
|
+
export interface Repository {
|
|
85
|
+
done?: string;
|
|
86
|
+
warning?: string;
|
|
87
|
+
}
|
|
67
88
|
|
|
68
89
|
/**
|
|
69
|
-
*
|
|
70
|
-
*
|
|
90
|
+
* Puts the folder on the branch `work`, which the plan names: `create` and the hooks need a repository, and the plan's
|
|
91
|
+
* first rule is to work there. No repository yet → `git init` on `work`. A repository without commits (a fresh
|
|
92
|
+
* `git init` on master or main, a clone of an empty GitHub repo) → its unborn branch becomes `work`: nothing is lost,
|
|
93
|
+
* since there is nothing on it. A repository with commits on another branch is left as it is, with a warning that
|
|
94
|
+
* names the command. No commit.
|
|
71
95
|
*/
|
|
72
|
-
export function ensureRepository(dir: string):
|
|
73
|
-
if (existsSync(join(dir, ".git")))
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (
|
|
80
|
-
|
|
96
|
+
export function ensureRepository(dir: string): Repository {
|
|
97
|
+
if (!existsSync(join(dir, ".git"))) {
|
|
98
|
+
const result = git(dir, ["init", "--quiet", `--initial-branch=${WORK_BRANCH}`]);
|
|
99
|
+
if (!result.ok) throw new CreateError(`git init failed: ${result.out}`);
|
|
100
|
+
return { done: `a git repository on the branch ${WORK_BRANCH}` };
|
|
101
|
+
}
|
|
102
|
+
const branch = git(dir, ["symbolic-ref", "--quiet", "--short", "HEAD"]);
|
|
103
|
+
if (branch.ok && branch.out === WORK_BRANCH) return {};
|
|
104
|
+
if (!git(dir, ["rev-parse", "--verify", "--quiet", "HEAD"]).ok) {
|
|
105
|
+
const result = git(dir, ["symbolic-ref", "HEAD", `refs/heads/${WORK_BRANCH}`]);
|
|
106
|
+
if (!result.ok) throw new CreateError(`git could not switch to the branch ${WORK_BRANCH}: ${result.out}`);
|
|
107
|
+
return { done: `the repository had no commit yet: now on the branch ${WORK_BRANCH}` };
|
|
108
|
+
}
|
|
109
|
+
const where = branch.ok ? `on the branch ${branch.out}` : "on no branch (detached HEAD)";
|
|
110
|
+
return {
|
|
111
|
+
warning: `the repository has commits and is ${where}, not on ${WORK_BRANCH}, which the plan works on. Run next: git switch -c ${WORK_BRANCH}`,
|
|
112
|
+
};
|
|
81
113
|
}
|
|
82
114
|
|
|
83
115
|
/** The names of the skills of the archetypes, with the archetype each belongs to (lernapps-<archetype>[-…]). */
|
|
@@ -121,6 +153,42 @@ function syncSkills(dir: string, exclude: string[]): void {
|
|
|
121
153
|
);
|
|
122
154
|
}
|
|
123
155
|
|
|
156
|
+
/** The version of a build that is not on the registry: package.json of the repository says 0.0.0. */
|
|
157
|
+
const LOCAL_VERSION = "0.0.0";
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* The tarball of the package in `root`, packed by npm pack (with its prepare script, so the build is current) into a
|
|
161
|
+
* new temporary folder.
|
|
162
|
+
*/
|
|
163
|
+
export function packBuild(root: string): string {
|
|
164
|
+
const out = mkdtempSync(join(tmpdir(), "lernapps-tooling-"));
|
|
165
|
+
run("npm", ["pack", "--silent", "--pack-destination", out], (message) => new CreateError(message), root);
|
|
166
|
+
const tarball = readdirSync(out).find((name) => name.endsWith(".tgz"));
|
|
167
|
+
if (tarball === undefined) throw new CreateError(`npm pack in ${root} wrote no tarball into ${out}`);
|
|
168
|
+
return join(out, tarball);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* The spec of @lernapps/tooling that init installs: the one given, else this version. A local build (version 0.0.0,
|
|
173
|
+
* which the registry does not have) is packed when its folder is a git checkout and installed from the tarball, else
|
|
174
|
+
* init says to name one with --tooling. Not the folder itself (file:<folder>): npm links it, so the app's
|
|
175
|
+
* node_modules lacks the tooling's dependencies (Playwright for the end-to-end tests, @lernapps/app-templates with
|
|
176
|
+
* the templates and their READMEs); from a tarball they are installed into the app, as from the registry.
|
|
177
|
+
*/
|
|
178
|
+
export function toolingSpec(
|
|
179
|
+
given: string | undefined,
|
|
180
|
+
version = ownVersion(),
|
|
181
|
+
root = fromPackage(),
|
|
182
|
+
pack: (root: string) => string = packBuild,
|
|
183
|
+
): string {
|
|
184
|
+
if (given !== undefined && given !== "") return given;
|
|
185
|
+
if (version !== LOCAL_VERSION) return version;
|
|
186
|
+
if (existsSync(join(root, ".git"))) return `file:${pack(resolve(root))}`;
|
|
187
|
+
throw new CreateError(
|
|
188
|
+
`this @lernapps/tooling is a local build (version ${LOCAL_VERSION}), which the npm registry does not have: name the tooling to install with --tooling <spec>, e.g. file:/path/to/lernapps-tooling-${LOCAL_VERSION}.tgz (npm pack) or github:lernapps/tooling#<commit>, and give the same to lernapps create`,
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
124
192
|
export function init(args: string[]): number {
|
|
125
193
|
const { values, positionals } = parseArgs({ args, options: INIT_OPTIONS, strict: true, allowPositionals: true });
|
|
126
194
|
if (values.help) {
|
|
@@ -139,6 +207,7 @@ export function init(args: string[]): number {
|
|
|
139
207
|
);
|
|
140
208
|
}
|
|
141
209
|
const addon = values.addon;
|
|
210
|
+
const tooling = toolingSpec(values.tooling);
|
|
142
211
|
const repository = ensureRepository(dir);
|
|
143
212
|
writeJson(join(dir, "package.json"), {
|
|
144
213
|
name,
|
|
@@ -149,7 +218,7 @@ export function init(args: string[]): number {
|
|
|
149
218
|
const fail = (message: string) => new CreateError(message);
|
|
150
219
|
run(
|
|
151
220
|
"npm",
|
|
152
|
-
["install", "--save-dev", "--no-audit", "--no-fund", `${TOOLING}@${
|
|
221
|
+
["install", "--save-dev", "--no-audit", "--no-fund", `${TOOLING}@${tooling}`, ...(addon ? [addon] : [])],
|
|
153
222
|
fail,
|
|
154
223
|
);
|
|
155
224
|
|
|
@@ -167,8 +236,9 @@ export function init(args: string[]): number {
|
|
|
167
236
|
dir,
|
|
168
237
|
archetypeSkills().map((skill) => skill.name),
|
|
169
238
|
);
|
|
239
|
+
if (repository.warning !== undefined) process.stderr.write(`lernapps init: warning: ${repository.warning}\n`);
|
|
170
240
|
process.stdout.write(
|
|
171
|
-
`lernapps init: ${relative(dir, target)} from ${relativeTemplate(template, dir)}; the skills of the packages are installed${repository ? `; ${repository}` : ""}\nNext: read ${relative(dir, target)} completely and follow it\n`,
|
|
241
|
+
`lernapps init: ${relative(dir, target)} from ${relativeTemplate(template, dir)}; the skills of the packages are installed${repository.done ? `; ${repository.done}` : ""}\nNext: ${repository.warning ? `git switch -c ${WORK_BRANCH}, then ` : ""}read ${relative(dir, target)} completely and follow it\n`,
|
|
172
242
|
);
|
|
173
243
|
return 0;
|
|
174
244
|
}
|
package/generator/update.ts
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
// them replaced with versions.
|
|
8
8
|
// It never moves the runtime back: a newer version (Renovate moves the runtime too) stays. An app made before the
|
|
9
9
|
// generator wrote the content guide gets its skeleton (generator/create.ts), and one made before the plans had a
|
|
10
|
-
// folder gets its plan .vibe/plan.md moved to .vibe/plans/01-first-version.md (src/plan.ts).
|
|
10
|
+
// folder gets its plan .vibe/plan.md moved to .vibe/plans/01-first-version.md (src/plan.ts). An explainer in format 1 gets `format: 2` and `strand: none` in src/app.yaml where the runtime knows
|
|
11
|
+
// format 2. The app's lernapps-*
|
|
11
12
|
// skills, which the app commits, are replaced with those of this tooling (its skills/), in the layout of
|
|
12
13
|
// `npx skills add`; the app's own skills (app-content, any other) stay as they are. An interactive app's architecture
|
|
13
14
|
// is replaced outside its app sections, which stay as they are (generator/architecture.ts).
|
|
@@ -40,6 +41,7 @@ import {
|
|
|
40
41
|
ownVersion,
|
|
41
42
|
readJson,
|
|
42
43
|
RUNTIME,
|
|
44
|
+
runtimeSpec,
|
|
43
45
|
SKILLS_LOCK,
|
|
44
46
|
templatesPackage,
|
|
45
47
|
TOOLING,
|
|
@@ -57,13 +59,16 @@ export const UPDATE_USAGE = `Usage: lernapps update
|
|
|
57
59
|
Moves the app in the current folder to the versions of this @lernapps/tooling, then runs npm install. npm update
|
|
58
60
|
moves the app within its version ranges; this moves the ranges, and what npm does not know:
|
|
59
61
|
@lernapps/app-templates to the version this tooling depends on: the runtime its tests ran with. A newer version
|
|
60
|
-
(e.g. from Renovate) is kept. A git commit, as apps once pinned, becomes a version
|
|
62
|
+
(e.g. from Renovate) is kept. A git commit, as apps once pinned, becomes a version; while
|
|
63
|
+
this tooling depends on a commit (an unreleased runtime), the app moves to that commit
|
|
61
64
|
@lernapps/tooling to the version of this tooling (unchanged when run from the app)
|
|
62
65
|
.github/workflows/ the actions of lernapps/tooling at the tag of that version
|
|
63
66
|
.agents/skills/app-content/SKILL.md
|
|
64
67
|
the content guide's skeleton, where the app has none yet (and its link for Claude Code)
|
|
65
68
|
.vibe/plan.md the plan of an app made before the plans had a folder: moved to
|
|
66
69
|
.vibe/plans/01-first-version.md, the first of its plans (one per piece of work)
|
|
70
|
+
src/app.yaml of an explainer in format 1, when the runtime knows format 2: adds format: 2 and
|
|
71
|
+
strand: none (the app is not on the learning map; its pages need no competencies)
|
|
67
72
|
.agents/skills/lernapps-*, .claude/skills/lernapps-*, skills-lock.json
|
|
68
73
|
replaced with the skills of this tooling, as npx skills add lays them out: those of
|
|
69
74
|
every phase, and of the archetypes only those of the current plan's archetype; every
|
|
@@ -98,9 +103,16 @@ function lowest(range: string): [number, number, number] | undefined {
|
|
|
98
103
|
const older = (a: [number, number, number], b: [number, number, number]) =>
|
|
99
104
|
a[0] !== b[0] ? a[0] < b[0] : a[1] !== b[1] ? a[1] < b[1] : a[2] < b[2];
|
|
100
105
|
|
|
101
|
-
/**
|
|
102
|
-
|
|
103
|
-
|
|
106
|
+
/**
|
|
107
|
+
* The next range of a dependency at `version` (target: ^ that version, or the spec given, e.g. a git commit of an
|
|
108
|
+
* unreleased runtime), and what the output says about it.
|
|
109
|
+
*/
|
|
110
|
+
export function nextRange(
|
|
111
|
+
name: string,
|
|
112
|
+
current: string | undefined,
|
|
113
|
+
version: string,
|
|
114
|
+
target = `^${version}`,
|
|
115
|
+
): { spec: string; line: string } {
|
|
104
116
|
// a local copy (a folder or a tarball of the package, as an app under development uses) is the creator's choice
|
|
105
117
|
if (current !== undefined && /^(file:|link:|\.{0,2}\/)/.test(current)) {
|
|
106
118
|
return { spec: current, line: `${name}: kept ${current}, a local copy` };
|
|
@@ -108,8 +120,9 @@ export function nextRange(name: string, current: string | undefined, version: st
|
|
|
108
120
|
const now = current === undefined ? undefined : lowest(current);
|
|
109
121
|
const next = lowest(version);
|
|
110
122
|
if (current === target) return { spec: current, line: `${name}: unchanged (${current})` };
|
|
111
|
-
// a newer version than this tooling's stays: it must never move back
|
|
112
|
-
|
|
123
|
+
// a newer version than this tooling's stays: it must never move back; a commit (no version to compare) is where the
|
|
124
|
+
// tooling's tests ran, so the app moves to it
|
|
125
|
+
if (lowest(target) !== undefined && now !== undefined && next !== undefined && !older(now, next)) {
|
|
113
126
|
return { spec: current ?? target, line: `${name}: kept ${current}, not moved to ${target}: it is not older` };
|
|
114
127
|
}
|
|
115
128
|
return { spec: target, line: `${name}: ${current ?? "none"} → ${target}` };
|
|
@@ -148,6 +161,35 @@ function migratePlan(): string[] {
|
|
|
148
161
|
];
|
|
149
162
|
}
|
|
150
163
|
|
|
164
|
+
/** The explainer's app file, and the schema file that tells the runtime knows format 2 (learning map). */
|
|
165
|
+
const EXPLAINER_APP = join("src", "app.yaml");
|
|
166
|
+
const EXPLAINER_V2_SCHEMA = join("runtime", "explainer", "explainer.v2.schema.json");
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Moves an explainer from format 1 to format 2 (the learning map): adds `format: 2` and `strand: none` to
|
|
170
|
+
* src/app.yaml when the app has neither, and points its schema comment to explainer.v2.schema.json. `strand: none`
|
|
171
|
+
* keeps the app off the map, so its pages need no competencies; the creator may name a strand later (skill
|
|
172
|
+
* lernapps-content-research). Only where the runtime of this tooling has the v2 schema: an older runtime does not
|
|
173
|
+
* know the fields. An app that is no explainer, or already has a `format` or a `strand`, stays as it is. What it did,
|
|
174
|
+
* as lines of the output.
|
|
175
|
+
*/
|
|
176
|
+
export function migrateExplainer(templatesRoot: string): string[] {
|
|
177
|
+
if (!existsSync(EXPLAINER_APP) || !existsSync(join(templatesRoot, EXPLAINER_V2_SCHEMA))) return [];
|
|
178
|
+
const text = readFileSync(EXPLAINER_APP, "utf8");
|
|
179
|
+
const top = (key: string) => new RegExp(`^${key}:`, "m").test(text);
|
|
180
|
+
if (!top("chapters") || top("format") || top("strand")) return [];
|
|
181
|
+
const schema = /^(# yaml-language-server: \$schema=.*)explainer\.v1\.schema\.json$/m;
|
|
182
|
+
const fields = "format: 2\nstrand: none\n";
|
|
183
|
+
let next: string;
|
|
184
|
+
if (schema.test(text))
|
|
185
|
+
next = text.replace(schema, (_all, head: string) => `${head}explainer.v2.schema.json\n${fields.trimEnd()}`);
|
|
186
|
+
else next = fields + text;
|
|
187
|
+
writeFileSync(EXPLAINER_APP, next);
|
|
188
|
+
return [
|
|
189
|
+
`explainer: ${EXPLAINER_APP} moved to format 2: added format: 2 and strand: none (not on the learning map; pages need no competencies). To join the map, pick a strand of https://lernapps.net/apps/map/ and give every page competencies`,
|
|
190
|
+
];
|
|
191
|
+
}
|
|
192
|
+
|
|
151
193
|
/**
|
|
152
194
|
* The skills of this tooling, as it ships them (skills/<group>/<name>/), and as the app keeps them (flat, by name).
|
|
153
195
|
* Every app has those of the groups process, tooling and content; of the group archetypes only those of its own
|
|
@@ -371,7 +413,7 @@ export function update(args: string[]): number {
|
|
|
371
413
|
const notMoved: string[] = [];
|
|
372
414
|
const runtime = devDependencies[RUNTIME];
|
|
373
415
|
if (runtime !== undefined) {
|
|
374
|
-
const next = nextRange(RUNTIME, runtime, templates.version);
|
|
416
|
+
const next = nextRange(RUNTIME, runtime, templates.version, runtimeSpec(templates.version));
|
|
375
417
|
if (next.spec !== runtime) specs.push(`${RUNTIME}@${next.spec}`);
|
|
376
418
|
lines.push(next.line);
|
|
377
419
|
}
|
|
@@ -404,6 +446,8 @@ export function update(args: string[]): number {
|
|
|
404
446
|
lines.push(...guide.map((line) => `content guide: ${line}`));
|
|
405
447
|
const plan = migratePlan();
|
|
406
448
|
lines.push(...plan);
|
|
449
|
+
const explainer = migrateExplainer(templates.root);
|
|
450
|
+
lines.push(...explainer);
|
|
407
451
|
const skills = [...replaceSkills(), ...unignoreSkills()];
|
|
408
452
|
lines.push(...skills);
|
|
409
453
|
const architecture = updateArchitecture();
|
|
@@ -421,6 +465,7 @@ export function update(args: string[]): number {
|
|
|
421
465
|
if (skillsMoved) files.push(SKILLS_LOCK);
|
|
422
466
|
if (skills.some((line) => line.includes(".gitignore"))) files.push(".gitignore");
|
|
423
467
|
if (architecture.lines.length > 0) files.push(`${ARCHITECTURE}/`);
|
|
468
|
+
if (explainer.length > 0) files.push(EXPLAINER_APP);
|
|
424
469
|
if (plan.some((line) => line.startsWith("plan: moved"))) files.push(".vibe/ (git add -A .vibe, to keep the move)");
|
|
425
470
|
process.stdout.write(
|
|
426
471
|
`Next: npx --no -- lernapps check, then commit ${files.slice(0, -1).join(", ")} and ${files.at(-1) ?? ""}\n`,
|
|
@@ -45,7 +45,7 @@ a correction.
|
|
|
45
45
|
|
|
46
46
|
## Notes
|
|
47
47
|
|
|
48
|
-
<!-- Curriculum orientation, new competencies by chapter, didactic approach, scope, sources and licences, open
|
|
48
|
+
<!-- Curriculum orientation, new competencies by chapter (ids of the strand), didactic approach, scope, sources and licences, open
|
|
49
49
|
questions. -->
|
|
50
50
|
|
|
51
51
|
## Explore
|
|
@@ -68,12 +68,19 @@ questions. -->
|
|
|
68
68
|
"(guess)". Topics: which competencies, chapters or rounds, for which school years; hurdles and examples;
|
|
69
69
|
amount; any change of look and feel or terms.
|
|
70
70
|
- [ ] Load skill `lernapps-content-research`. Research what the guide's curriculum and sources do not cover. Under
|
|
71
|
-
"Notes": curriculum orientation, new competencies by chapter as "Ich kann …", verified sources.
|
|
71
|
+
"Notes": curriculum orientation, new competencies by chapter as "Ich kann …", verified sources. The app keeps its
|
|
72
|
+
strand; with one, the new competencies are only those of that strand in the learning map (ids), as skill
|
|
73
|
+
`lernapps-content-research` says, an explainer as a rule one page each; competencies that build on each other (the
|
|
74
|
+
map's `requires`) share a chapter, in that order. None in the map → propose it to the
|
|
75
|
+
catalog. `strand: none` and a strand fits → recommend it.
|
|
72
76
|
- [ ] Under "Notes": the didactic approach per competency and the scope (in, out, data range).
|
|
73
77
|
- [ ] A kind of content the guide does not cover (a new page structure, screen, component or kind of exercise) →
|
|
74
78
|
load skills `lernapps-<archetype>-design` and `lernapps-exercise-patterns`. Else skip this task.
|
|
75
|
-
- [ ] **Checkpoint:** research. Show in plain words: curriculum orientation, new competencies by chapter
|
|
76
|
-
|
|
79
|
+
- [ ] **Checkpoint:** research. Show in plain words: curriculum orientation, new competencies by chapter (with their
|
|
80
|
+
ids), didactic
|
|
81
|
+
approach, scope, every "(guess)", sources and how you checked them, what is unchecked. With a strand, the map
|
|
82
|
+
decides the structure: new competencies that build on each other (its `requires`) share a chapter, in that
|
|
83
|
+
order; no curriculum search is needed for it. Stop and wait.
|
|
77
84
|
Changes → change, ask again.
|
|
78
85
|
- [ ] Set `phase: plan`: change the value line (`grep '^phase:'` prints `phase: plan`).
|
|
79
86
|
|
|
@@ -110,7 +117,8 @@ questions. -->
|
|
|
110
117
|
### Content method
|
|
111
118
|
|
|
112
119
|
<!-- One line each: the guide's patterns, figures and components used, and where; anything new, what it does and
|
|
113
|
-
cannot
|
|
120
|
+
cannot; an explainer on the map: per new page its prerequisites the app does not practise, and which get a recap
|
|
121
|
+
page, which a link. Something new goes into the guide in the slice that adds it. -->
|
|
114
122
|
|
|
115
123
|
### Completed
|
|
116
124
|
|
|
@@ -42,7 +42,7 @@ For you, the assistant.
|
|
|
42
42
|
|
|
43
43
|
## Notes
|
|
44
44
|
|
|
45
|
-
<!--
|
|
45
|
+
<!-- Strand (<subject>/<strand> or none), curriculum orientation, competencies by chapter (ids), didactic approach, scope, sources and licences, open
|
|
46
46
|
questions. -->
|
|
47
47
|
|
|
48
48
|
## Explore
|
|
@@ -59,7 +59,7 @@ questions. -->
|
|
|
59
59
|
|
|
60
60
|
- [ ] The skills of the phases are in `.agents/skills/` (`npx --yes @lernapps/tooling init <short name>` put them there
|
|
61
61
|
and wrote this plan; the app commits them). `.agents/skills/lernapps-app` missing → stop and tell the creator
|
|
62
|
-
the folder was not started with `init`.
|
|
62
|
+
the folder was not started with `init`. `git branch --show-current` prints `work`; else `git switch -c work`.
|
|
63
63
|
- [ ] Load skill `lernapps-app`. Tell the creator in two sentences what a lernapps app is and where it will live: at
|
|
64
64
|
their own address, free, public, and once listed as a copy on lernapps.net. The technical part (GitHub) comes at the end, and you do it.
|
|
65
65
|
- [ ] Interview: ask the topics "Goal" and "Learners" of "Answers".
|
|
@@ -74,7 +74,11 @@ questions. -->
|
|
|
74
74
|
which. No answer → ask once more; still none → write your assumption with "(guess)". Done when every topic
|
|
75
75
|
has an answer or a guess.
|
|
76
76
|
- [ ] Load skill `lernapps-content-research`. Research as it says. Under "Notes": curriculum orientation,
|
|
77
|
-
competencies by chapter as "Ich kann …", verified sources. No competencies → not done.
|
|
77
|
+
competencies by chapter as "Ich kann …", verified sources. No competencies → not done. First the learning map as
|
|
78
|
+
skill `lernapps-content-research` says: recommend a strand that fits (`<subject>/<strand>`), else `none`; with a
|
|
79
|
+
strand, only its competencies, by their ids from the map. Write both under "Notes", and how they map onto
|
|
80
|
+
chapters and pages (an explainer: as a rule one page per competency; competencies that build on each other, by
|
|
81
|
+
the map's `requires`, share a chapter, in that order).
|
|
78
82
|
- [ ] Under "Notes", one line per competency: how learners get there (derive, not only read off; e.g. a tree diagram
|
|
79
83
|
shows how a probability comes about), which misconception the feedback catches.
|
|
80
84
|
- [ ] Under "Notes", the scope: what is in, what is out and why, the data range (e.g. "all main-group elements",
|
|
@@ -82,8 +86,10 @@ questions. -->
|
|
|
82
86
|
- [ ] Fetch https://lernapps.net/apps/schemas/entry.v1.schema.json and read each property's description. Ask what
|
|
83
87
|
helps teachers and parents find the app. Fill "Entry draft". Search words: one or two words each, as people
|
|
84
88
|
type them.
|
|
85
|
-
- [ ] **Checkpoint:** research. Show in plain words: curriculum orientation, competencies by
|
|
86
|
-
approach, scope, every "(guess)", sources and how you
|
|
89
|
+
- [ ] **Checkpoint:** research. Show in plain words: the strand or why none, curriculum orientation, competencies by
|
|
90
|
+
chapter (with their ids) and their pages, didactic approach, scope, every "(guess)", sources and how you
|
|
91
|
+
checked them, what is unchecked, archetype and reason. With a strand, the map decides the structure: competencies
|
|
92
|
+
that build on each other (its `requires`) share a chapter, in that order; no curriculum search is needed for it.
|
|
87
93
|
Stop and wait. Changes → change, ask again.
|
|
88
94
|
- [ ] Set `phase: plan`: change the value line (`grep '^phase:'` prints `phase: plan`).
|
|
89
95
|
|
|
@@ -132,11 +138,12 @@ questions. -->
|
|
|
132
138
|
- [ ] Map the content as skill `lernapps-<archetype>-design` says; reread it if it is no longer in view.
|
|
133
139
|
- [ ] Choose a pattern per competency as skill `lernapps-exercise-patterns` says (loaded in Explore; reread it if it
|
|
134
140
|
is no longer in view). Write "Content method".
|
|
135
|
-
- [ ] Write the Code tasks under "Code": pages or screens, their content, what each shows. Each ends in a commit
|
|
141
|
+
- [ ] Write the Code tasks under "Code": pages or screens, their content, what each shows. Each ends in a commit:
|
|
142
|
+
one task, one commit. Steps too small for a commit of their own → one task.
|
|
136
143
|
After the first task that shows real content: the task "Show the creator a first look (`early-preview`)".
|
|
137
144
|
- [ ] Each screen, component or interactive exercise: a sketch (static SVG or image, or the steps with what
|
|
138
145
|
learners see after each). Words alone are not enough. Keep sketch files in `.vibe/sketches/`, which `create` leaves alone.
|
|
139
|
-
- [ ] Show the creator, short: goal, competencies by chapter, entry draft, tasks, sketches; the content method in
|
|
146
|
+
- [ ] Show the creator, short: goal, competencies by chapter (chapters along the map's `requires`), entry draft, tasks, sketches; the content method in
|
|
140
147
|
plain words (what learners see and do, what it cannot do). At most 2 questions.
|
|
141
148
|
- [ ] **Checkpoint:** confirm the plan and the content method, with the sketches shown. Stop and wait for an
|
|
142
149
|
explicit yes. Changes → change, ask again. No `create` before the yes.
|
|
@@ -145,7 +152,8 @@ questions. -->
|
|
|
145
152
|
### Content method
|
|
146
153
|
|
|
147
154
|
<!-- One line each: exercise patterns and where; figures and how they are made; data files; each component, what it
|
|
148
|
-
checks and cannot; how a later agent adds a chapter.
|
|
155
|
+
checks and cannot; how a later agent adds a chapter. An explainer on the map: per page its prerequisites (`requires`)
|
|
156
|
+
the app does not practise, and which get a recap page, which a link. Becomes the content guide in Code. -->
|
|
149
157
|
|
|
150
158
|
### Completed
|
|
151
159
|
|
|
@@ -153,7 +161,8 @@ checks and cannot; how a later agent adds a chapter. Becomes the content guide i
|
|
|
153
161
|
|
|
154
162
|
<!--
|
|
155
163
|
- Build the app from the archetype and the confirmed plan, locally on `work`. Work on your own.
|
|
156
|
-
- Commit after each task
|
|
164
|
+
- Commit after each task, literally: one task, one commit, its box ticked in it; the hooks check. Never one commit
|
|
165
|
+
for several tasks: join them into one task in the plan first. A check fails → skill `lernapps-cli`. Fix from the message; never
|
|
157
166
|
bypass a hook.
|
|
158
167
|
- Undecidable → choose, mark "(guess)" under "Key decisions", go on.
|
|
159
168
|
- Creator asks for a change → first say your reading in one sentence ("Ich nehme alle 44 Hauptgruppenelemente
|
|
@@ -168,10 +177,15 @@ checks and cannot; how a later agent adds a chapter. Becomes the content guide i
|
|
|
168
177
|
- [ ] Load skill `lernapps-cli`. Create the app in this plan's folder as it says. `create` keeps this plan. Never
|
|
169
178
|
write the app by hand.
|
|
170
179
|
- [ ] Fill the content guide `.agents/skills/app-content/SKILL.md` from "Notes" and "Content method"
|
|
171
|
-
(`app-content-guide`).
|
|
172
|
-
|
|
180
|
+
(`app-content-guide`). Keep its nine headings and fill each: Curriculum, Competencies, Content structure,
|
|
181
|
+
Terminology, Exercise patterns, Figures, Sources, Adding a chapter or round, Rules learned from mistakes
|
|
182
|
+
("None yet." until the first). `grep -c "FILL IN" .agents/skills/app-content/SKILL.md` prints 0. Commit it
|
|
183
|
+
before any content.
|
|
173
184
|
- [ ] Load skill `lernapps-<archetype>` and the content guide. Questions with options and feedback: also skill
|
|
174
185
|
`lernapps-content-questions`.
|
|
186
|
+
- [ ] An explainer: decide on `scope` as `references/structure.md` of skill `lernapps-explainer` says. Needed with an
|
|
187
|
+
`open-task` or the creator's material: then `scope` in `src/app.yaml` and on every page
|
|
188
|
+
(`explainer-scope-matches`). Write the choice under "Key decisions".
|
|
175
189
|
- [ ] <the tasks written in Plan; after each, commit on `work`; after the first with real content, show the
|
|
176
190
|
creator a first look (`early-preview`) and go on>
|
|
177
191
|
- [ ] The content guide matches what was built (`app-content-matches`).
|
|
@@ -87,7 +87,9 @@ scope: tooling
|
|
|
87
87
|
severity: error
|
|
88
88
|
```
|
|
89
89
|
|
|
90
|
-
- Keep `.agents/skills/app-content/SKILL.md` with its fixed headings
|
|
90
|
+
- Keep `.agents/skills/app-content/SKILL.md` with its fixed headings, in this order, each filled: Curriculum,
|
|
91
|
+
Competencies, Content structure, Terminology, Exercise patterns, Figures, Sources, Adding a chapter or round,
|
|
92
|
+
Rules learned from mistakes ("None yet." until the first rule).
|
|
91
93
|
- After the plan is confirmed and before content: replace every placeholder ("FILL IN") with concrete instructions
|
|
92
94
|
for writing new content: files, ids, data files, an example of this app. Not how the app works, no general advice.
|
|
93
95
|
- A content decision changes (pattern, figure type, source rule) → change the guide in the same commit.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lernapps/tooling",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Shared tooling of the lernapps.net apps and sites: the CLI lernapps, site actions, Renovate preset, guidance for building apps; documentation at https://lernapps.net/tooling/",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -107,7 +107,7 @@
|
|
|
107
107
|
"arc42": "arc42 --dir docs/arc42"
|
|
108
108
|
},
|
|
109
109
|
"dependencies": {
|
|
110
|
-
"@lernapps/app-templates": "
|
|
110
|
+
"@lernapps/app-templates": "github:lernapps/app-templates#cf943e1de177011fc54d5a80b255a192cb05b469",
|
|
111
111
|
"axe-core": "4.14.0",
|
|
112
112
|
"playwright": "1.64.1",
|
|
113
113
|
"skills": "1.7.4",
|
|
@@ -9,9 +9,10 @@ license: MIT
|
|
|
9
9
|
- You write YAML in `src/`; a Vue component only where the plan names one. Rendering, navigation, formulas, figures,
|
|
10
10
|
exercises, accessibility, languages and the no-JS page come from the runtime `@lernapps/app-templates`.
|
|
11
11
|
- `create` writes a placeholder: one chapter with one page and one exercise. Replace it with the planned content. The complete example, the chapter "Prozent" with a component, is in `node_modules/@lernapps/app-templates/explainer/`: look things up there, never copy it.
|
|
12
|
-
- Schema: `node_modules/@lernapps/app-templates/runtime/explainer/explainer.
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
- Schema: `node_modules/@lernapps/app-templates/runtime/explainer/explainer.v2.schema.json` (format 2: `format: 2`
|
|
13
|
+
in `src/app.yaml`, as `create` writes it). Start every YAML file with
|
|
14
|
+
`# yaml-language-server: $schema=../../node_modules/@lernapps/app-templates/runtime/explainer/explainer.v2.schema.json`
|
|
15
|
+
(one `../` less in `src/app.yaml`). Only an app that has not moved to format 2 keeps `explainer.v1.schema.json`.
|
|
15
16
|
- Questions of the quiz's types: also skill `lernapps-content-questions`.
|
|
16
17
|
|
|
17
18
|
## Read when
|
|
@@ -22,7 +23,8 @@ license: MIT
|
|
|
22
23
|
- [references/components.md](references/components.md): only when the plan names a component.
|
|
23
24
|
- [references/runtime.md](references/runtime.md): when the build or a runtime test fails, and before you set up the
|
|
24
25
|
tutor.
|
|
25
|
-
- [references/rules.md](references/rules.md): when a check names an `explainer
|
|
26
|
+
- [references/rules.md](references/rules.md): when a check names an `explainer-*`, `vue-component-*` or
|
|
27
|
+
`competencies-on-map` rule, and before you write a page with `competencies`.
|
|
26
28
|
|
|
27
29
|
## Gotchas
|
|
28
30
|
|
|
@@ -30,6 +32,10 @@ license: MIT
|
|
|
30
32
|
- Headings are `section` blocks, never Markdown headings.
|
|
31
33
|
- YAML: quote a text that has `: ` in it or looks like a number (`"8."`, `"01000001"`, `"0"`). Unquoted, YAML reads a map or a
|
|
32
34
|
number: the build stops, or the digits change. A table cell, a header cell too, is a text and never empty.
|
|
35
|
+
- A page with `competencies` teaches what their "Ich kann …" in the map says, not clearly more or other
|
|
36
|
+
(`content-matches-competencies`).
|
|
37
|
+
- A page with formulas written through the shell: a heredoc with an unquoted delimiter replaces `$…` and `\`
|
|
38
|
+
(`<<EOF`). Quote the delimiter (`<<'EOF'`), or write the file with the file tool.
|
|
33
39
|
- A new chapter: a folder with `chapter.yaml`, listed in `src/app.yaml`, each page listed in `chapter.yaml`.
|
|
34
40
|
- `selfAssessment` in a `chapter.yaml`: written in Commit, skill
|
|
35
41
|
`lernapps-explainer-self-assessment`. An exercise it names renamed or moved → change the reference too.
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
| `section` | `id`, `title`, `blocks` | heading and blocks |
|
|
11
11
|
| `text` | `text` | prose |
|
|
12
12
|
| `formula` | `formula` (LaTeX subset, no `$`) | formula on its own line, MathML |
|
|
13
|
-
| `figure` | `draw` (`hundred-grid` with `filled`, `bar` with `part`, `whole`) or `image` (`images/<name>.svg`, `source`, `licence`); `alt`, `caption` | inline SVG or bundled image |
|
|
13
|
+
| `figure` | `draw` (`hundred-grid` with `filled`, `bar` with `part`, `whole`) or `image` (`images/<name>.svg`, `source`, `licence`); `alt`, `caption` | inline SVG or bundled image; `bar` is one strip, the whole, with the part shaded and labelled with its percentage: a part of a whole only, never two quantities side by side or a value above the whole (an `image` or a `table` instead) |
|
|
14
14
|
| `labelled-figure` | `image`, `alt`, `caption`, `labels` (`x`, `y` in %, `text`, optional `marker` of 1 to 3 characters, e.g. the material's `A` to `E`, else the number), `legend` (default `true`; `false` shows only the markers, then `alt` names every marker), `source`, `licence` | image with markers (letters or numbers) and a legend of the texts |
|
|
15
15
|
| `table` | `caption`, `header`, `rowHeaders`, `rows` | table with caption |
|
|
16
16
|
| `timeline` | `events` (`date`, `title`, `label`, `text`) or `data` | dated events, earliest first |
|
|
@@ -62,6 +62,10 @@
|
|
|
62
62
|
- LaTeX subset → MathML; `formula` block or inline `$…$`.
|
|
63
63
|
- Commands: `\frac`, `\cdot`, `\times`, `\div`, `\%`, `\text`, `^`, `_`, `\sqrt`, `\approx`, `\pm`, `\le`, `\ge`,
|
|
64
64
|
`\neq`, `\,`, parentheses, braces (`\{`, `\}`).
|
|
65
|
+
- Trigonometry and more: `\sin`, `\cos`, `\tan`, `\arcsin` (or `\sin^{-1}`), `\arccos`, `\arctan`, Greek letters
|
|
66
|
+
(`\alpha`, `\varphi`, `\pi`, `\Delta`, …), degrees (`30^\circ`), spaces (`\ `, `\quad`, `\qquad`), `\Rightarrow`,
|
|
67
|
+
`\Leftrightarrow`, `\left(` … `\right)`:
|
|
68
|
+
`'\sin \alpha = \frac{a}{c} \quad \Rightarrow \quad a = c \cdot \sin \alpha'`.
|
|
65
69
|
- Chemistry (mhchem): `\ce{2H2 + O2 -> 2H2O}`, `\ce{CO2 + H2O <=> H2CO3}`, units `\pu{123 kJ/mol}`.
|
|
66
70
|
- Other command or parse error → build fails with the place.
|
|
67
71
|
- YAML formulas in single quotes. Same in every language unless `\text{…}` differs (then a map).
|
|
@@ -80,6 +84,6 @@
|
|
|
80
84
|
|
|
81
85
|
## Data files
|
|
82
86
|
|
|
83
|
-
- `src/data/<name>.yaml`: list of records, same
|
|
84
|
-
languages).
|
|
87
|
+
- `src/data/<name>.yaml`: list of records, same fields; a field name starts with a lower-case letter, then letters
|
|
88
|
+
and digits (`masse`, `hoeheM`; not `Masse`, `h_m`); values number, boolean, text (map if several languages).
|
|
85
89
|
- Used by `timeline` (`data: ereignisse`) and `generated` (`from: { data: elemente }`). Each fact once, there.
|
|
@@ -27,6 +27,9 @@
|
|
|
27
27
|
- New task per seed.
|
|
28
28
|
- `given`: `int: [min, max]` + `step`; `decimal: [min, max]` + `step`; `oneOf: [...]`; `pick: <n>` `from:` list or
|
|
29
29
|
`{ data: <name> }` (`distinct: true`; `pick: 1` = one record).
|
|
30
|
+
- An inline `from:` list holds numbers or texts only: a record in it (`{ de: …, en: … }`) is read as a text by
|
|
31
|
+
language and takes text values only. Records with numbers (`{ name: Stein, masse: 2 }`) → a data file
|
|
32
|
+
(`src/data/<name>.yaml`, `blocks.md`) and `from: { data: <name> }`.
|
|
30
33
|
- `where`: boolean expressions; failing draws are redrawn.
|
|
31
34
|
- `task`: `text`. A figure that changes with the task: an exercise component (`components.md`).
|
|
32
35
|
- `answer`: `value` (expression) and `compare`: `number` or
|
|
@@ -36,6 +39,9 @@
|
|
|
36
39
|
- Expressions (no eval): exact fractions, `+ - * /`, parentheses, `== != < <= > >=`, `and`, `or`, `not`, strings,
|
|
37
40
|
fields (`e.year`), index (`xs[0]`), lists, `round(x, n)`, `decimals`, `map(xs, field)`,
|
|
38
41
|
`sortBy(xs, field)`, `reverse`.
|
|
42
|
+
- `sqrt(x)`, `pow(x, n)`, `sin(x)`, `cos(x)`, `tan(x)`, `asin(x)`, `acos(x)`, `atan(x)`, angles in degrees
|
|
43
|
+
(`sin(30)` is `1/2`, `asin(0.5)` is `30`). A rational result is exact; any other (`sqrt(2)`, `sin(10)`) has 12
|
|
44
|
+
decimal places: give the answer with `round(…, n)` and `compare: { number: { decimals: n, tolerance } }`.
|
|
39
45
|
|
|
40
46
|
### Placeholders
|
|
41
47
|
|
|
@@ -283,3 +283,32 @@ severity: warning
|
|
|
283
283
|
belong to it.
|
|
284
284
|
- Each page's scope says what the page teaches, no more and no less; "Gehört nicht dazu" names what a learner or tutor
|
|
285
285
|
might expect but the material does not teach.
|
|
286
|
+
|
|
287
|
+
## `content-matches-competencies`: Each page teaches its competencies
|
|
288
|
+
|
|
289
|
+
```rule
|
|
290
|
+
id: content-matches-competencies
|
|
291
|
+
scope: tooling
|
|
292
|
+
severity: hint
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
- A page of a format-2 explainer with a strand teaches what the "Ich kann …" (`iCan`) of its competencies says in
|
|
296
|
+
`https://lernapps.net/apps/map/<subject>.json`: not clearly more, not something else.
|
|
297
|
+
- More to teach → a page of the competency that says it; none in the map → propose it to `lernapps/apps`
|
|
298
|
+
(`map/<subject>.yaml`), never invent it in the app.
|
|
299
|
+
- The review reads each page with the `iCan` of its competencies.
|
|
300
|
+
|
|
301
|
+
## `competencies-on-map`: The strand and the page competencies are on the map
|
|
302
|
+
|
|
303
|
+
```rule
|
|
304
|
+
id: competencies-on-map
|
|
305
|
+
scope: tooling
|
|
306
|
+
severity: warning
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
- `strand` in `src/app.yaml` is a strand of the subject's map; each page's `competencies` are ids of that strand.
|
|
310
|
+
- A recap page's `recap` (`<subject>/<competency>`) is on the map, of any strand, and the app does not practise it
|
|
311
|
+
on a page of its own.
|
|
312
|
+
- `lernapps check` loads `https://lernapps.net/apps/map/<subject>.json` and warns before the push. Not reachable or
|
|
313
|
+
no map yet → one hint that it could not check.
|
|
314
|
+
- At the listing an unknown id or one of another strand is an error of the catalog (`entry-learning-map`).
|