explico 0.14.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +112 -1
- package/dist/compiler/cli-main.d.ts +61 -4
- package/dist/compiler/cli-main.js +77 -36
- package/dist/compiler/cli-reference.d.ts +50 -0
- package/dist/compiler/cli-reference.js +213 -0
- package/dist/compiler/cli-verbs.d.ts +45 -0
- package/dist/compiler/cli-verbs.js +224 -0
- package/dist/compiler/cli-verify.d.ts +51 -0
- package/dist/compiler/cli-verify.js +151 -0
- package/dist/compiler/cli.d.ts +19 -50
- package/dist/compiler/cli.js +45 -6
- package/dist/compiler/content-release.d.ts +49 -0
- package/dist/compiler/content-release.js +63 -0
- package/dist/compiler/content-schemas.d.ts +2 -2
- package/dist/compiler/manifest.js +29 -24
- package/dist/compiler/page-versions.d.ts +25 -1
- package/dist/compiler/page-versions.js +19 -0
- package/dist/compiler/validate-content.js +2 -2
- package/dist/reference/consistency.js +7 -1
- package/dist/reference/diagram-fit.d.ts +5 -1
- package/dist/reference/diagram-fit.js +5 -1
- package/dist/rules/catalog.js +16 -0
- package/dist/rules/in-effect.d.ts +1 -1
- package/dist/rules/in-effect.js +5 -4
- package/dist/rules/rules.schema.json +21 -0
- package/docs/authoring/README.md +2 -0
- package/docs/authoring/agents.md +18 -11
- package/docs/authoring/cli-reference.json +2172 -0
- package/docs/authoring/cli.md +887 -0
- package/docs/authoring/deck.md +24 -0
- package/docs/authoring/migrating.md +168 -18
- package/docs/authoring/versioning.md +2 -1
- package/docs/course-agent/AGENTS.md +10 -7
- package/docs/course-agent/agents/rules-reviewer.md +2 -2
- package/docs/course-agent/skills/draft-lesson/SKILL.md +7 -6
- package/docs/course-agent/skills/migrate-course/SKILL.md +5 -4
- package/docs/course-agent/skills/release-content/SKILL.md +5 -3
- package/docs/diagram-scaling.md +2 -2
- package/package.json +1 -1
- package/src/compiler/cli-main.ts +135 -47
- package/src/compiler/cli-reference.ts +285 -0
- package/src/compiler/cli-verbs.ts +270 -0
- package/src/compiler/cli-verify.ts +241 -0
- package/src/compiler/cli.ts +77 -17
- package/src/compiler/content-release.ts +91 -0
- package/src/compiler/manifest.ts +34 -24
- package/src/compiler/page-versions.ts +42 -0
- package/src/compiler/validate-content.ts +5 -2
- package/src/reference/consistency.ts +13 -2
- package/src/reference/diagram-fit.ts +18 -4
- package/src/rules/catalog.ts +17 -0
- package/src/rules/in-effect.ts +5 -4
package/CHANGELOG.md
CHANGED
|
@@ -14,6 +14,116 @@ under **Breaking** below; a PATCH never breaks.
|
|
|
14
14
|
|
|
15
15
|
## [Unreleased]
|
|
16
16
|
|
|
17
|
+
## [0.15.0] - 2026-10-12
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- **Rule D11: a deck is flagged when its chapter changed after it did.**
|
|
22
|
+
`deck-stale`, an info diagnostic, names a lesson's own deck whose chapter's
|
|
23
|
+
rule C5 version is newer than the deck's — two files mean the chapter and
|
|
24
|
+
the deck can drift apart with nothing else to notice it. Scoped to a
|
|
25
|
+
lesson's own deck; a part's deck covers more than one chapter, so it has
|
|
26
|
+
none to compare against and is never flagged. `page-versions.ts` gains
|
|
27
|
+
`deckStaleDiagnostics`; the diagnostic joins `diagnostics.math` beside
|
|
28
|
+
D1–D10's frame codes. No manifest shape change.
|
|
29
|
+
- **`content help <verb> [--json]`: each verb's contract, machine-readable.**
|
|
30
|
+
`content help symbol` prints the verb's usage, what it is for, its flags,
|
|
31
|
+
what it writes, when it exits 1, and the shape of its `--json` as a tree of
|
|
32
|
+
named types; `--json` prints the same with a self-contained JSON Schema
|
|
33
|
+
(draft 2020-12). `content help --json` prints the whole reference. The verb
|
|
34
|
+
table is one source, `src/compiler/cli-verbs.ts` (`CONTENT_VERBS`, from which
|
|
35
|
+
`CONTENT_USAGE` is now derived, every line ending in `[--json]`);
|
|
36
|
+
`scripts/agent/gen-cli.mjs` reads the TypeScript report types with the
|
|
37
|
+
compiler API and writes `docs/authoring/cli-reference.json` and
|
|
38
|
+
`docs/authoring/cli.md`, both shipped, `--check` in verify and CI. New
|
|
39
|
+
module `explico/compiler/cli-reference` (`loadCliReference`, `verbHelp`,
|
|
40
|
+
`schemaOf`, `formatVerbHelp`, `cliReferenceMarkdown`, and
|
|
41
|
+
`jsonShapeErrors`, which checks a value against one of the schemas).
|
|
42
|
+
`cli-main` exports the named `--json` types its verbs print beside the
|
|
43
|
+
reports: `RulesJson`, `EditPlanJson`, `NewJson`, `ErrorJson`.
|
|
44
|
+
- **`content verify [--e2e] [--force]`: a course's one gate before handing
|
|
45
|
+
work back.** `validate:content` → `content check --changed` → `astro check`
|
|
46
|
+
(skipped, by name, when `@astrojs/check` is not installed) → the course's
|
|
47
|
+
`test` script → `astro build`, and with `--e2e` its `test:e2e` script (or
|
|
48
|
+
`playwright test`). Each step is its own process with its whole output in
|
|
49
|
+
`node_modules/.cache/explico/verify/<step>.log`; the run prints one line per
|
|
50
|
+
step, the tail of each failed step's log, and exits 1 on a failure. A pass
|
|
51
|
+
records the tree it left (git HEAD, the diff against it, the untracked
|
|
52
|
+
files), and a rerun on the same tree that asks for no more answers from the
|
|
53
|
+
record without running anything. New module `explico/compiler/cli-verify`
|
|
54
|
+
(`runVerify`, `VerifyReport`).
|
|
55
|
+
- `runContentCli` takes an optional fifth argument, a manifest already
|
|
56
|
+
compiled for the course, so a test running many verbs compiles once.
|
|
57
|
+
- **`content release [major|minor|patch] [--write]`: a content release in
|
|
58
|
+
every course.** `## [Unreleased]` in `content/CHANGELOG.md` becomes a dated
|
|
59
|
+
`## [x.y.z]` heading and `about.version` becomes `x.y.z` (rule C3), shown as
|
|
60
|
+
a diff and written with `--write`, like `move`, `rename` and `fix`; the bump
|
|
61
|
+
defaults to what the section's headings imply (rule C2), and the date is the
|
|
62
|
+
local one. It was the playground's own `scripts/release-content.ts`, which
|
|
63
|
+
the shipped `release-content` skill called as `pnpm release:content` in
|
|
64
|
+
courses that never had it. The playground's `release:content` script is now
|
|
65
|
+
`content release --write`. New module `explico/compiler/content-release`
|
|
66
|
+
(`releaseEdit`, `planContentRelease`); new error code `no-changelog`.
|
|
67
|
+
|
|
68
|
+
### Changed
|
|
69
|
+
|
|
70
|
+
- `content init-agent` says `new` or `differs from the engine's copy` for each
|
|
71
|
+
file, where a dry run used to say `installed`; after `--write` every edit
|
|
72
|
+
verb prints what to run next.
|
|
73
|
+
- `content status` lists at most six of the pages with unreleased changes and
|
|
74
|
+
counts the rest; `content context` says how many of a lesson's diagnostics
|
|
75
|
+
sit in its own file and how many in the shared cards it shows (the first is
|
|
76
|
+
what `diagnostics --lesson` lists).
|
|
77
|
+
- The shipped procedures (`docs/course-agent/`) survive a course's prettier:
|
|
78
|
+
no inline code span crosses a line break, which prettier re-indented out of
|
|
79
|
+
its list item. `gen-map --check` fails on one.
|
|
80
|
+
- `migrating.md`, 0.13.1 → 0.14.0, corrected where upgrading
|
|
81
|
+
credit-and-derivatives found it wrong: the patch goes before `pnpm add`
|
|
82
|
+
(pnpm refuses with `ERR_PNPM_UNUSED_PATCH`) and its entry may sit in
|
|
83
|
+
`pnpm-workspace.yaml`; pnpm 11's `minimumReleaseAgeExclude`; a course that
|
|
84
|
+
commits `build/resolution/` fails its first run after the upgrade; stale
|
|
85
|
+
`lint-ignore.yml` lines and a course's own tier guidance (step 20); the 404
|
|
86
|
+
page without a raw `<div>` (step 24); tests comparing sidebar and page
|
|
87
|
+
colours (step 25). Upgrading time-series added two more: pnpm can instead
|
|
88
|
+
stop with `ERR_PNPM_NO_MATURE_MATCHING_VERSION` (step 1), and the first
|
|
89
|
+
`content/versions.json` goes in before the upgrade's own changelog bullet,
|
|
90
|
+
or every page reads "not yet released" (step 10).
|
|
91
|
+
- `content fix` takes several lessons, or `--all`, as one plan: the lessons
|
|
92
|
+
with nothing to fix are counted, not listed. A course upgrading to 0.14.0
|
|
93
|
+
can have a thousand redundant `\explain` wraps (data-science-I: 1193).
|
|
94
|
+
- `content check` lists a lesson's errors and warnings line by line and
|
|
95
|
+
counts its info by code, with the command that lists them; `--json` is
|
|
96
|
+
unchanged.
|
|
97
|
+
- `definition-repeated` on a local entry whose glyph a shared card already
|
|
98
|
+
owns no longer says "move one copy to `content/notation/`": promoting it
|
|
99
|
+
would put two shared cards on one glyph. It says the entry is a reused
|
|
100
|
+
letter (rule N4) and how to acknowledge the copies; `content move` prints a
|
|
101
|
+
caution when the glyph is taken (time-series: 9 of its 34).
|
|
102
|
+
- `dia-too-wide` on `<CurriculumMap>`'s competency graph names the map's own
|
|
103
|
+
props (`graph="per-part"`, `direction`, `warnAboveWidth`) instead of a node
|
|
104
|
+
`width` and a shorter label, which the author does not write there.
|
|
105
|
+
- The rules-in-effect page says the course's `rules` and `courseRules` live
|
|
106
|
+
in `course.settings.json` or `course.config.ts`, not only the first.
|
|
107
|
+
|
|
108
|
+
### Fixed
|
|
109
|
+
|
|
110
|
+
- `content move` wrote the new card a placeholder body ("Extend this body
|
|
111
|
+
with what a glossary reader needs."), which the glossary would publish, and
|
|
112
|
+
`domain: general` for a key without a prefix. The card now has no body, its
|
|
113
|
+
domain is the key's prefix when the course has that domain, else the one
|
|
114
|
+
most of the lesson's cards have; the plan says that the body is the
|
|
115
|
+
author's to write, and when the entry had no `label`.
|
|
116
|
+
- `validate:content`'s run log recorded each consistency warning
|
|
117
|
+
(`definition-repeated`, `glyph-unique-in-corpus`, …) at INFO and their
|
|
118
|
+
one-line digest as the only WARNING, so its `Run log: (… warning(s))` count
|
|
119
|
+
disagreed with `content status`. Each finding is recorded at WARNING now,
|
|
120
|
+
the digest at INFO.
|
|
121
|
+
- `ContextReport`'s `equations`, `tables`, `figures` and `diagrams` are typed
|
|
122
|
+
`ManifestLessonFloat[]`: `content context --json` always printed each
|
|
123
|
+
float's `short` and `caption`, which the type left out. The playground's
|
|
124
|
+
new `--json` contract test runs every read-only verb against the schemas and
|
|
125
|
+
caught it.
|
|
126
|
+
|
|
17
127
|
## [0.14.0] - 2026-10-11
|
|
18
128
|
|
|
19
129
|
### Breaking
|
|
@@ -4323,7 +4433,8 @@ n starts its row, and the panel hung from the button's end,
|
|
|
4323
4433
|
system with its completeness gate, the curriculum-graph validator, the
|
|
4324
4434
|
Astro/Starlight UI, the LMS / session / analytics seams, and the `content` CLI.
|
|
4325
4435
|
|
|
4326
|
-
[Unreleased]: https://github.com/danieltuzes/explico/compare/v0.
|
|
4436
|
+
[Unreleased]: https://github.com/danieltuzes/explico/compare/v0.15.0...HEAD
|
|
4437
|
+
[0.15.0]: https://github.com/danieltuzes/explico/compare/v0.14.0...v0.15.0
|
|
4327
4438
|
[0.14.0]: https://github.com/danieltuzes/explico/compare/v0.13.1...v0.14.0
|
|
4328
4439
|
[0.13.1]: https://github.com/danieltuzes/explico/compare/v0.13.0...v0.13.1
|
|
4329
4440
|
[0.13.0]: https://github.com/danieltuzes/explico/compare/v0.12.0...v0.13.0
|
|
@@ -1,9 +1,60 @@
|
|
|
1
|
+
import { EditPlan, ContentCliCode, RulesReport } from './cli.js';
|
|
2
|
+
export { CONTENT_USAGE } from './cli-verbs.js';
|
|
1
3
|
import { C as CourseConfig } from '../course-config-BLtIdkei.js';
|
|
4
|
+
import { Manifest } from './manifest.js';
|
|
5
|
+
import '../reference/types.js';
|
|
6
|
+
import '../reference/gate-math.js';
|
|
7
|
+
import './render-diagnostics.js';
|
|
2
8
|
import '../rules/catalog.js';
|
|
9
|
+
import '../rules/in-effect.js';
|
|
3
10
|
import '../rules/course-rules.js';
|
|
11
|
+
import '../curriculum/validation.js';
|
|
12
|
+
import '../reference/consistency.js';
|
|
13
|
+
import './collections.js';
|
|
14
|
+
import '../reference/gate-retired-keys.js';
|
|
15
|
+
import '../reference/lint-ignore.js';
|
|
16
|
+
import '../reference/equations-validate.js';
|
|
17
|
+
import '../reference/tables-validate.js';
|
|
18
|
+
import '../reference/keyed-float-validate.js';
|
|
19
|
+
import '../reference/figures-validate.js';
|
|
20
|
+
import '../reference/diagrams-validate.js';
|
|
21
|
+
import '../reference/examples-validate.js';
|
|
22
|
+
import '../reference/folds-validate.js';
|
|
23
|
+
import '../reference/knowledge-checks-validate.js';
|
|
24
|
+
import '../reference/raw-floats-validate.js';
|
|
25
|
+
import '../reference/lab-katex-validate.js';
|
|
26
|
+
import '../reference/mixed-content.js';
|
|
27
|
+
import '../reference/gloss.js';
|
|
28
|
+
import '../rules/waivers.js';
|
|
29
|
+
import './knowledge-version.js';
|
|
30
|
+
import './page-versions.js';
|
|
31
|
+
import './decks.js';
|
|
32
|
+
import '../reference/decks-validate.js';
|
|
33
|
+
import '../reference/deck-source.js';
|
|
34
|
+
import './lesson-derivation.js';
|
|
4
35
|
|
|
5
|
-
/**
|
|
6
|
-
|
|
36
|
+
/** `--json` of `rules`: the report without its Markdown page. */
|
|
37
|
+
type RulesJson = Pick<RulesReport, 'filter' | 'count' | 'rules'>;
|
|
38
|
+
/** `--json` of `move`, `rename`, `fix` and `init-agent`: the plan, and whether `--write` applied it. */
|
|
39
|
+
type EditPlanJson = EditPlan & {
|
|
40
|
+
/** True when `--write` applied the edits; false for a dry run. */
|
|
41
|
+
readonly written: boolean;
|
|
42
|
+
};
|
|
43
|
+
/** `--json` of `new lesson`, `new deck` and `new term`. */
|
|
44
|
+
interface NewJson {
|
|
45
|
+
readonly ok: true;
|
|
46
|
+
/** Course-relative paths of the files it created. */
|
|
47
|
+
readonly created: readonly string[];
|
|
48
|
+
}
|
|
49
|
+
/** `--json` of any verb whose run failed: a stable code and a message, never a stack. */
|
|
50
|
+
interface ErrorJson {
|
|
51
|
+
readonly ok: false;
|
|
52
|
+
readonly error: {
|
|
53
|
+
/** `internal` for an unexpected throw; every other code is stable. */
|
|
54
|
+
readonly code: ContentCliCode | 'internal';
|
|
55
|
+
readonly message: string;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
7
58
|
interface ContentArgs {
|
|
8
59
|
readonly command: string;
|
|
9
60
|
readonly positionals: readonly string[];
|
|
@@ -26,6 +77,10 @@ interface ContentArgs {
|
|
|
26
77
|
readonly to?: string;
|
|
27
78
|
/** `--write`: apply a planned edit instead of only showing its diff. */
|
|
28
79
|
readonly write: boolean;
|
|
80
|
+
/** `--e2e`: `verify` runs the browser tests too. */
|
|
81
|
+
readonly e2e: boolean;
|
|
82
|
+
/** `--force`: `verify` runs even on a tree its last pass covered. */
|
|
83
|
+
readonly force: boolean;
|
|
29
84
|
}
|
|
30
85
|
declare function parseContentArgs(argv: readonly string[]): ContentArgs;
|
|
31
86
|
/** Where a run prints; the process streams by default, a buffer in a test. */
|
|
@@ -38,6 +93,8 @@ interface ContentIo {
|
|
|
38
93
|
* success, 1 on a usage error, a `ContentCliError`, a check that failed, or
|
|
39
94
|
* an unexpected throw (reported by message, never a stack).
|
|
40
95
|
*/
|
|
41
|
-
declare function runContentCli(courseConfig: CourseConfig, argv?: readonly string[], io?: ContentIo
|
|
96
|
+
declare function runContentCli(courseConfig: CourseConfig, argv?: readonly string[], io?: ContentIo,
|
|
97
|
+
/** A manifest already compiled for this course, reused instead of compiling one: a test running many verbs compiles once. */
|
|
98
|
+
compiled?: Manifest): Promise<number>;
|
|
42
99
|
|
|
43
|
-
export {
|
|
100
|
+
export { type ContentArgs, type ContentIo, type EditPlanJson, type ErrorJson, type NewJson, type RulesJson, parseContentArgs, runContentCli };
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
formatSymbolReport,
|
|
27
27
|
ownerTag,
|
|
28
28
|
parseRulesFilter,
|
|
29
|
-
|
|
29
|
+
planFixMany,
|
|
30
30
|
planInitAgent,
|
|
31
31
|
planMove,
|
|
32
32
|
planRename,
|
|
@@ -36,26 +36,11 @@ import {
|
|
|
36
36
|
scaffoldTerm,
|
|
37
37
|
summarizeDiagnostics
|
|
38
38
|
} from "./cli.js";
|
|
39
|
+
import { formatVerbHelp, loadCliReference, verbHelp } from "./cli-reference.js";
|
|
40
|
+
import { CONTENT_USAGE } from "./cli-verbs.js";
|
|
41
|
+
import { planContentRelease } from "./content-release.js";
|
|
42
|
+
import { formatVerifyReport, formatVerifyStep, runVerify } from "./cli-verify.js";
|
|
39
43
|
import { compileManifest } from "./manifest.js";
|
|
40
|
-
const CONTENT_USAGE = `Usage:
|
|
41
|
-
pnpm content status
|
|
42
|
-
pnpm content diagnostics [info|warning|error] [--all | --code <c> | --lesson <id> | --changed] [--json]
|
|
43
|
-
pnpm content warnings [--json]
|
|
44
|
-
pnpm content context <lesson> [--json]
|
|
45
|
-
pnpm content equation "<latex>" [--lesson <id>] [--json]
|
|
46
|
-
pnpm content bindings <lesson> [--json]
|
|
47
|
-
pnpm content symbol <latex | key> [--json]
|
|
48
|
-
pnpm content check <lesson | deck>\u2026 [--changed] [--json]
|
|
49
|
-
pnpm content rule <ID> [--json]
|
|
50
|
-
pnpm content migrate [--from <version>] [--to <version>] [--json]
|
|
51
|
-
pnpm content rules [--element <e>[,<e>]] [--enforcement script|partial|guide|locked] [--edition chapter|deck] [--json]
|
|
52
|
-
pnpm content move <lesson>:<key> [--to <key>] [--write]
|
|
53
|
-
pnpm content rename <old> <new> [--write]
|
|
54
|
-
pnpm content fix <lesson> [--write]
|
|
55
|
-
pnpm content new lesson <id>
|
|
56
|
-
pnpm content new deck <lesson | part>
|
|
57
|
-
pnpm content new term <key>
|
|
58
|
-
pnpm content init-agent [--write]`;
|
|
59
44
|
const VALUED_FLAGS = ["element", "enforcement", "edition", "code", "lesson", "from", "to"];
|
|
60
45
|
function parseContentArgs(argv) {
|
|
61
46
|
const positionals = [];
|
|
@@ -64,6 +49,8 @@ function parseContentArgs(argv) {
|
|
|
64
49
|
let changed = false;
|
|
65
50
|
let all = false;
|
|
66
51
|
let write = false;
|
|
52
|
+
let e2e = false;
|
|
53
|
+
let force = false;
|
|
67
54
|
const valued = new RegExp(`^--(${VALUED_FLAGS.join("|")})(?:=(.*))?$`);
|
|
68
55
|
for (let i = 0; i < argv.length; i += 1) {
|
|
69
56
|
const arg = argv[i];
|
|
@@ -72,6 +59,8 @@ function parseContentArgs(argv) {
|
|
|
72
59
|
else if (arg === "--changed") changed = true;
|
|
73
60
|
else if (arg === "--all") all = true;
|
|
74
61
|
else if (arg === "--write") write = true;
|
|
62
|
+
else if (arg === "--e2e") e2e = true;
|
|
63
|
+
else if (arg === "--force") force = true;
|
|
75
64
|
else if (match) values[match[1]] = match[2] ?? argv[i += 1] ?? "";
|
|
76
65
|
else positionals.push(arg);
|
|
77
66
|
}
|
|
@@ -82,9 +71,15 @@ function parseContentArgs(argv) {
|
|
|
82
71
|
changed,
|
|
83
72
|
all,
|
|
84
73
|
write,
|
|
74
|
+
e2e,
|
|
75
|
+
force,
|
|
85
76
|
...values
|
|
86
77
|
};
|
|
87
78
|
}
|
|
79
|
+
function diagnosticsWhere(diagnostics, file) {
|
|
80
|
+
const own = diagnostics.filter((diagnostic) => diagnostic.file === file).length;
|
|
81
|
+
return own === diagnostics.length ? "" : ` (${own} in ${file}, ${diagnostics.length - own} in the shared cards it shows)`;
|
|
82
|
+
}
|
|
88
83
|
const PROCESS_IO = {
|
|
89
84
|
out: (text) => process.stdout.write(text),
|
|
90
85
|
err: (text) => process.stderr.write(text)
|
|
@@ -109,12 +104,12 @@ function writeScaffold(scaffold) {
|
|
|
109
104
|
}
|
|
110
105
|
return written;
|
|
111
106
|
}
|
|
112
|
-
async function runContentCli(courseConfig, argv = process.argv.slice(2), io = PROCESS_IO) {
|
|
107
|
+
async function runContentCli(courseConfig, argv = process.argv.slice(2), io = PROCESS_IO, compiled) {
|
|
113
108
|
const args = parseContentArgs(argv);
|
|
114
109
|
const json = (value) => io.out(`${JSON.stringify(value, null, 2)}
|
|
115
110
|
`);
|
|
116
111
|
try {
|
|
117
|
-
return await dispatch(courseConfig, args, io, json);
|
|
112
|
+
return await dispatch(courseConfig, args, io, json, compiled);
|
|
118
113
|
} catch (error) {
|
|
119
114
|
const code = error instanceof Usage || error instanceof ContentCliError ? error.code : "internal";
|
|
120
115
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -124,13 +119,40 @@ async function runContentCli(courseConfig, argv = process.argv.slice(2), io = PR
|
|
|
124
119
|
return 1;
|
|
125
120
|
}
|
|
126
121
|
}
|
|
127
|
-
async function dispatch(courseConfig, args, io, json) {
|
|
122
|
+
async function dispatch(courseConfig, args, io, json, compiled) {
|
|
128
123
|
const out = io.out;
|
|
129
|
-
if (args.command === "" || args.command === "help" || args.command === "
|
|
124
|
+
if (args.command === "" || args.command === "--help" || args.command === "help" && args.positionals.length === 0 && !args.json) {
|
|
130
125
|
out(`${CONTENT_USAGE}
|
|
131
126
|
`);
|
|
132
127
|
return 0;
|
|
133
128
|
}
|
|
129
|
+
if (args.command === "help") {
|
|
130
|
+
if (args.positionals.length > 1) usage("usage", `content help takes one verb; got ${args.positionals.join(" ")}.`);
|
|
131
|
+
const reference = loadCliReference();
|
|
132
|
+
const [word] = args.positionals;
|
|
133
|
+
if (word === void 0) {
|
|
134
|
+
json(reference);
|
|
135
|
+
return 0;
|
|
136
|
+
}
|
|
137
|
+
const help = verbHelp(reference, word);
|
|
138
|
+
if (args.json) json(help);
|
|
139
|
+
else out(formatVerbHelp(reference, help));
|
|
140
|
+
return 0;
|
|
141
|
+
}
|
|
142
|
+
if (args.command === "verify") {
|
|
143
|
+
if (args.positionals.length > 0) usage("usage", `content verify takes no argument; got ${args.positionals.join(" ")}.`);
|
|
144
|
+
if (!args.json) out(`verify (${args.e2e ? "with" : "without"} e2e; whole output under node_modules/.cache/explico/verify/)
|
|
145
|
+
`);
|
|
146
|
+
const report = runVerify({
|
|
147
|
+
e2e: args.e2e,
|
|
148
|
+
force: args.force,
|
|
149
|
+
...args.json ? {} : { onStep: (event) => event.phase === "end" && out(formatVerifyStep(event.step)) }
|
|
150
|
+
});
|
|
151
|
+
if (args.json) json(report);
|
|
152
|
+
else out(report.cached ? formatVerifyReport(report) : `
|
|
153
|
+
${formatVerifyReport(report)}`);
|
|
154
|
+
return report.ok ? 0 : 1;
|
|
155
|
+
}
|
|
134
156
|
if (args.command === "new") {
|
|
135
157
|
const [kind, id] = args.positionals;
|
|
136
158
|
if (kind !== "lesson" && kind !== "deck" && kind !== "term") {
|
|
@@ -151,18 +173,28 @@ ${written.map((p) => ` ${p}`).join("\n")}
|
|
|
151
173
|
if (["status", "rules", "warnings"].includes(args.command) && args.positionals.length > 0) {
|
|
152
174
|
usage("usage", `content ${args.command} takes no <lesson>; got ${args.positionals.join(" ")}.`);
|
|
153
175
|
}
|
|
154
|
-
if (args.command === "init-agent") {
|
|
155
|
-
|
|
156
|
-
|
|
176
|
+
if (args.command === "init-agent" || args.command === "release") {
|
|
177
|
+
let plan;
|
|
178
|
+
if (args.command === "init-agent") {
|
|
179
|
+
if (args.positionals.length > 0) usage("usage", `content init-agent takes no argument; got ${args.positionals.join(" ")}.`);
|
|
180
|
+
plan = planInitAgent();
|
|
181
|
+
} else {
|
|
182
|
+
if (args.positionals.length > 1) usage("usage", `content release takes one of major, minor, patch; got ${args.positionals.join(" ")}.`);
|
|
183
|
+
plan = planContentRelease(args.positionals[0]);
|
|
184
|
+
}
|
|
157
185
|
if (args.json) json({ ...plan, written: args.write });
|
|
158
186
|
else out(renderEditPlan(plan));
|
|
159
187
|
if (plan.edits.length === 0) return 0;
|
|
188
|
+
const next = plan.next.length > 0 ? plan.next.join(" \xB7 ") : "";
|
|
160
189
|
if (args.write) {
|
|
161
190
|
const touched = applyEditPlan(plan);
|
|
162
191
|
if (!args.json) out(`
|
|
163
|
-
written: ${touched.join(", ")}
|
|
192
|
+
written: ${touched.join(", ")}${next === "" ? "" : `
|
|
193
|
+
next: ${next}`}
|
|
194
|
+
`);
|
|
195
|
+
} else if (!args.json) out(`
|
|
196
|
+
dry run: nothing written. Add --write to apply${next === "" ? "" : `; then ${next}`}.
|
|
164
197
|
`);
|
|
165
|
-
} else if (!args.json) out("\ndry run: nothing written. Add --write to install.\n");
|
|
166
198
|
return 0;
|
|
167
199
|
}
|
|
168
200
|
if (args.command === "migrate") {
|
|
@@ -196,7 +228,7 @@ written: ${touched.join(", ")}
|
|
|
196
228
|
usage("unknown-command", `Unknown command "${args.command}".
|
|
197
229
|
${CONTENT_USAGE}`);
|
|
198
230
|
}
|
|
199
|
-
const manifest = await compileManifest(courseConfig);
|
|
231
|
+
const manifest = compiled ?? await compileManifest(courseConfig);
|
|
200
232
|
switch (args.command) {
|
|
201
233
|
case "status": {
|
|
202
234
|
const report = contentStatus(manifest);
|
|
@@ -206,9 +238,11 @@ ${CONTENT_USAGE}`);
|
|
|
206
238
|
}
|
|
207
239
|
const c = report.counts;
|
|
208
240
|
const unreleased = report.pageVersions.unreleased;
|
|
241
|
+
const UNRELEASED_SHOWN = 6;
|
|
242
|
+
const unreleasedList = unreleased.length <= UNRELEASED_SHOWN ? unreleased.join(", ") : `${unreleased.slice(0, UNRELEASED_SHOWN).join(", ")}, \u2026 ${unreleased.length - UNRELEASED_SHOWN} more in --json`;
|
|
209
243
|
out(
|
|
210
244
|
`Knowledge version ${report.knowledgeVersion}
|
|
211
|
-
Page versions: ${report.pageVersions.tracked} page(s) tracked, ${unreleased.length} with changes not yet released${unreleased.length > 0 ? ` (${
|
|
245
|
+
Page versions: ${report.pageVersions.tracked} page(s) tracked, ${unreleased.length} with changes not yet released${unreleased.length > 0 ? ` (${unreleasedList})` : ""}
|
|
212
246
|
Lessons ${c.lessons} \xB7 competencies ${c.competencies} \xB7 assessments ${c.assessments} \xB7 sources ${c.sources} \xB7 tracks ${c.tracks} \xB7 notation ${c.notationDefinitions} \xB7 keyed equations ${c.keyedEquations} \xB7 tables ${c.keyedTables} \xB7 figures ${c.keyedFigures} \xB7 diagrams ${c.keyedDiagrams} \xB7 example sets ${c.keyedExamples}
|
|
213
247
|
Diagnostics: ${report.diagnostics.errors} error(s), ${report.diagnostics.warnings} warning(s), ${report.diagnostics.infos} info
|
|
214
248
|
Orphans: ${report.orphans.length}
|
|
@@ -232,9 +266,8 @@ Agent guide: ${report.agentGuide.agentsFile === null ? "no AGENTS.md \u2014 `pnp
|
|
|
232
266
|
if (!oldKey || !newKey || extra.length > 0) usage("usage", "content rename needs <old> <new>.");
|
|
233
267
|
plan = planRename(manifest, oldKey, newKey);
|
|
234
268
|
} else {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
plan = planFix(manifest, lesson);
|
|
269
|
+
if (args.all === args.positionals.length > 0) usage("usage", "content fix needs <lesson> ids, or --all for every lesson.");
|
|
270
|
+
plan = planFixMany(manifest, args.all ? manifest.lessons.map((lesson) => lesson.id) : args.positionals);
|
|
238
271
|
}
|
|
239
272
|
if (args.json) json({ ...plan, written: args.write });
|
|
240
273
|
else out(renderEditPlan(plan));
|
|
@@ -329,7 +362,7 @@ teaches: ${report.lesson.teaches.join(", ") || "(none)"}
|
|
|
329
362
|
requires: ${report.lesson.requires.join(", ") || "(none)"}
|
|
330
363
|
notation: ${report.notation.map((n) => n.key).join(", ") || "(none)"}
|
|
331
364
|
sources: ${report.sources.map((s) => s.id).join(", ") || "(none)"}
|
|
332
|
-
diagnostics: ${report.diagnostics.length}
|
|
365
|
+
diagnostics: ${report.diagnostics.length}${diagnosticsWhere(report.diagnostics, report.lesson.file)}
|
|
333
366
|
tokens: context ${t.context} vs doc set ${t.docSet} + corpus ${t.corpus} (${(t.ratio * 100).toFixed(2)}% of the baseline)
|
|
334
367
|
`
|
|
335
368
|
);
|
|
@@ -371,8 +404,16 @@ tokens: context ${t.context} vs doc set ${t.docSet} + corpus ${t.corpus} (${(t.r
|
|
|
371
404
|
for (const entry of report.lessons) {
|
|
372
405
|
out(`${entry.ok ? "ok " : "FAIL"} ${entry.lesson}${entry.rendered ? "" : " (render failed)"}
|
|
373
406
|
`);
|
|
407
|
+
const infos = /* @__PURE__ */ new Map();
|
|
374
408
|
for (const diagnostic of entry.diagnostics) {
|
|
375
|
-
|
|
409
|
+
if (diagnostic.severity === "info") infos.set(diagnostic.code, (infos.get(diagnostic.code) ?? 0) + 1);
|
|
410
|
+
else out(` [${diagnostic.severity}] ${diagnostic.code}${ownerTag(diagnostic)}: ${diagnostic.message}
|
|
411
|
+
`);
|
|
412
|
+
}
|
|
413
|
+
if (infos.size > 0) {
|
|
414
|
+
const total = [...infos.values()].reduce((sum, n) => sum + n, 0);
|
|
415
|
+
const byCode = [...infos].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).map(([code, n]) => `${code} ${n}`).join(", ");
|
|
416
|
+
out(` + ${total} info (${byCode}); content diagnostics --lesson ${entry.lesson} lists them
|
|
376
417
|
`);
|
|
377
418
|
}
|
|
378
419
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { ContentVerb } from './cli-verbs.js';
|
|
2
|
+
|
|
3
|
+
/** A JSON Schema (draft 2020-12), the subset the generator writes. */
|
|
4
|
+
type JsonSchema = Readonly<Record<string, unknown>>;
|
|
5
|
+
/** `docs/authoring/cli-reference.json`, whole: what `content help --json` prints. */
|
|
6
|
+
interface CliReference {
|
|
7
|
+
/** Where it came from, and that it is generated. */
|
|
8
|
+
readonly generatedBy: string;
|
|
9
|
+
readonly verbs: readonly ContentVerb[];
|
|
10
|
+
/** The type every verb's `--json` prints when the run fails: a stable code and a message, never a stack. */
|
|
11
|
+
readonly error: string;
|
|
12
|
+
/** A JSON Schema of every type a verb's `--json` prints, and of every named type inside one, by name. */
|
|
13
|
+
readonly $defs: Readonly<Record<string, JsonSchema>>;
|
|
14
|
+
}
|
|
15
|
+
/** One verb's row, its usage line, and a self-contained JSON Schema of its `--json`. */
|
|
16
|
+
interface VerbHelp extends ContentVerb {
|
|
17
|
+
readonly usage: string;
|
|
18
|
+
readonly schema: JsonSchema;
|
|
19
|
+
}
|
|
20
|
+
/** What `content help <verb> --json` prints: every verb the word names (`new` names three). */
|
|
21
|
+
interface VerbHelpJson {
|
|
22
|
+
readonly verbs: readonly VerbHelp[];
|
|
23
|
+
}
|
|
24
|
+
/** `content help --json` prints the whole reference; `content help <verb> --json` that verb's rows. */
|
|
25
|
+
type HelpJson = CliReference | VerbHelpJson;
|
|
26
|
+
declare const CLI_REFERENCE_FILE = "cli-reference.json";
|
|
27
|
+
/** The reference beside the engine (`node_modules/explico/docs/authoring/`, or this repository's). */
|
|
28
|
+
declare function loadCliReference(): CliReference;
|
|
29
|
+
/** The verbs a word names: `symbol` one, `new` the three `new …` verbs. */
|
|
30
|
+
declare function verbsNamed(word: string): readonly ContentVerb[];
|
|
31
|
+
/** A self-contained schema of one named type: a `$ref` and the `$defs` it reaches. */
|
|
32
|
+
declare function schemaOf(reference: CliReference, name: string): JsonSchema;
|
|
33
|
+
declare function verbHelp(reference: CliReference, word: string): VerbHelpJson;
|
|
34
|
+
/** One line per property, `name?: type — description`, nested objects indented under their property. */
|
|
35
|
+
declare function shapeLines(schema: JsonSchema, indent?: string): string[];
|
|
36
|
+
/** A named type as a block: its name and description, then its shape. */
|
|
37
|
+
declare function defLines(name: string, schema: JsonSchema): string[];
|
|
38
|
+
/** `content help <verb>` as text: the usage, what it is for, its flags, and the shape of its `--json`. */
|
|
39
|
+
declare function formatVerbHelp(reference: CliReference, help: VerbHelpJson): string;
|
|
40
|
+
/** `docs/authoring/cli.md`, generated: one section per verb, then every `--json` type. */
|
|
41
|
+
declare function cliReferenceMarkdown(reference: CliReference): string;
|
|
42
|
+
/**
|
|
43
|
+
* Where `value` departs from `schema`, one message per place (`$.hits[0].tier:
|
|
44
|
+
* expected one of …`); empty when it conforms. The subset the generator
|
|
45
|
+
* writes: `$ref`, `type`, `const`, `enum`, `anyOf`, `properties`, `required`,
|
|
46
|
+
* `additionalProperties`, `items`.
|
|
47
|
+
*/
|
|
48
|
+
declare function jsonShapeErrors(schema: JsonSchema, value: unknown, defs?: CliReference["$defs"], path?: string): string[];
|
|
49
|
+
|
|
50
|
+
export { CLI_REFERENCE_FILE, type CliReference, type HelpJson, type JsonSchema, type VerbHelp, type VerbHelpJson, cliReferenceMarkdown, defLines, formatVerbHelp, jsonShapeErrors, loadCliReference, schemaOf, shapeLines, verbHelp, verbsNamed };
|