forked from bai/curriculum-project-hub
feat(checker+typst): 嵌入 render 使 cph 可 cargo install;实现 shell step 执行器(ADR-0013)
三件事,服务于"把恒一小奥 KenKen 课作者化为合法 native 工程文件"这条主线
(工程文件本身在 repo 外的 sibling playground,不入此提交):
1. cph-typst: 把 render/ 编译期嵌入二进制,`cargo install` 后开箱即用
- build.rs 把 render/ 拷进 OUT_DIR,剔除 dev 自指符号链接 vendor/local-packages
(否则 include_dir! 无限递归)
- embedded.rs 用 include_dir! 嵌入,运行时按版本解压到 per-user cache dir
(CPH_RENDER_DIR 仍优先作 dev override)
- default_render_dir 不再用编译期 CARGO_MANIFEST_DIR 写死路径
2. cph-check + cph-cli: 实现 Step::Shell 执行器(此前仅占位)
- run_shell_target: 跑 check 门控 → 以工程根为 cwd 执行 shell step,首非零退出即停
- target_is_shell + CLI run_shell_build 路由;`check` 不跑 shell(结构校验只看 lesson)
- compile_targets 只保留含 TypstCompile 的 target,纯 shell target 不走 typst 引擎
- 语义:失败属 build-过程错误,不入 6 类诊断(taxonomy 保持 6,显式拒绝加 ShellStep 码)
3. spec + ADR: 在 Export/Render.lean 钉 shell step 执行语义 prose;
ADR-0013(已实现)、ADR-0014(md/HTML 导出后端方向,Proposed/设计先行,未实现)
测试:全 workspace 42 passed(新增 4 个 shell 执行器测试);lake build 25 jobs 绿。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
# ADR 0013: Shell-Step Execution For Tool-Generated Assets
|
||||
|
||||
## Status
|
||||
|
||||
Accepted — and implemented. The `Step::Shell` escape hatch (introduced
|
||||
structurally in ADR-0011 but left unimplemented) now has an executor in
|
||||
`cph-check`, wired into `cph build --target <shell-target>`. First real use: the
|
||||
KenKen interactive boards of the 恒一小奥 lesson, generated by the external
|
||||
`kendoku` CLI.
|
||||
|
||||
Builds on **ADR-0005** (category (b): medium-only information — HTML interactive
|
||||
behavior, build scripts, npm deps — belongs to the target's build, not the
|
||||
element) and **ADR-0009/0011** (a target is an `Artifact` produced by an ordered
|
||||
list of typed `Step`s; `Shell` is the escape hatch for steps that resist
|
||||
declaration). It does not amend them; it fixes the **execution semantics** of
|
||||
`Shell`, which those ADRs left open ("MVP 不实现,先建结构").
|
||||
|
||||
## Context
|
||||
|
||||
Authoring the 恒一小奥 KenKen lesson as a native engineering file surfaced a
|
||||
concrete category-(b) need. The lesson's interactive teaching apparatus — a
|
||||
clickable 4×4 KenKen board per problem — is produced by a **separate tool**
|
||||
(`kendoku`, a TypeScript CLI living in the playground: `kendoku export
|
||||
<puzzle.json> -o <out.html>`). The structured source of truth is the puzzle JSON
|
||||
(cages / solution / cell-to-cage); the self-contained interactive HTML is a
|
||||
**generated build artifact**, exactly ADR-0005's category (b) and ADR-0009's
|
||||
"third-party archive that runs an external build".
|
||||
|
||||
The contract already named this (`Step::Shell`, `Artifact::FileTree`), and
|
||||
`cph-model` already parsed both, but the engine refused them: there was **no step
|
||||
executor**, and `cph build` only knew how to compile a typst template to a single
|
||||
PDF. So the lesson's apparatus could be *described* but not *produced* through the
|
||||
same framework.
|
||||
|
||||
Three questions about *how* a shell step runs were genuinely undecided, and
|
||||
divergent answers would matter (safety, what `check` means, where failures go).
|
||||
|
||||
## Decision
|
||||
|
||||
### A shell step executes the command, in the engineering root, on explicit build
|
||||
|
||||
`cph build --target <name>` for a target whose steps are `Shell` runs each `run`
|
||||
string through the platform shell (`sh -c` / `cmd /C`) with the **engineering
|
||||
root as the working directory**, so manifests use root-relative paths. cph
|
||||
**executes** the declared command and reports the outcome; it does **not**
|
||||
assemble or post-process the tool's output — the external tool writes the files
|
||||
(this is the difference from a `TypstCompile` step, where the framework owns the
|
||||
compile).
|
||||
|
||||
### Arbitrary command execution is opt-in by construction
|
||||
|
||||
A shell step is arbitrary code execution, so it must never be a surprise:
|
||||
|
||||
- It runs **only** on an explicit `cph build --target <shell-target>`.
|
||||
- `cph check` **never** runs shell steps — `check` validates lesson *structure*
|
||||
(is the lesson legal?), not the outcome of running an external tool. A
|
||||
shell/file-tree target is also skipped by the check pipeline's typst-compile
|
||||
phase (it is not a typst build).
|
||||
- The CLI prints each command before running it, and streams the tool's output.
|
||||
|
||||
### A shell step failure is a build-process error, not a lesson diagnostic
|
||||
|
||||
A non-zero exit is a failure of the *build process*, categorically distinct from
|
||||
the six lesson-legality diagnostics (`PartPathMissing`, `UnknownKind`,
|
||||
`MissingContentFile`, `SchemaViolation`, `TypstCompile`, `RenderIgnored`). Those
|
||||
six describe defects *in the lesson*; a tool that fails to run says nothing about
|
||||
the lesson's legality.
|
||||
|
||||
**We deliberately do NOT add a `ShellStep` diagnostic code.** The closed,
|
||||
spec-pinned taxonomy (`Check/Diagnostic.lean`, ADR-0010/0012) stays at six. Shell
|
||||
outcomes are reported by the build-execution layer (exit status + captured
|
||||
stdout/stderr surfaced by the CLI), keeping the diagnostic vocabulary about
|
||||
lesson legality only. This is the lighter, constitution-respecting choice
|
||||
(taxonomy is a pinned divergence point; not every error category belongs in it).
|
||||
|
||||
### `kendoku` reachability is left open
|
||||
|
||||
How the external tool is invoked (a relative `node …/dist/cli.js` path, `npx
|
||||
kendoku` after `npm link`, or an installed binary on PATH) is **not** fixed by
|
||||
the contract — it is a property of a given engineering file's `run` string and
|
||||
its host. The 恒一小奥 manifest uses a relative `node` path as an MVP and its
|
||||
README documents the prerequisite; a portable convention is left open.
|
||||
|
||||
## Consequences
|
||||
|
||||
- The same framework now *produces* the apparatus it could previously only
|
||||
describe: `cph build --target interactives` runs `kendoku` and writes the 10
|
||||
interactive boards under the target's `FileTree` root. The lesson's PDFs
|
||||
(`student`/`teacher`) and its interactives all come from one engineering file
|
||||
through one CLI.
|
||||
- `Step::Shell` is no longer a placeholder; its execution semantics are pinned in
|
||||
`Export/Render.lean`.
|
||||
- The diagnostic taxonomy is unchanged (still six). Build-process failures live
|
||||
outside it.
|
||||
- `TypstCompile` targets are unaffected: the PDF path is exactly as before.
|
||||
|
||||
## Open Questions / Deferred
|
||||
|
||||
- **Portable tool reachability.** A cross-machine convention for locating an
|
||||
external generator (`npx`/PATH/vendored) rather than a host-specific path.
|
||||
- **`FileTree` output validation.** Whether the checker should verify a shell
|
||||
target actually produced the files its `Artifact::FileTree` `outputs` glob
|
||||
declares (currently the tool's exit status is the only signal).
|
||||
- **Step animation (GIF) pipeline.** `kendoku steps` → headless-Chrome
|
||||
screenshots → ffmpeg is a multi-tool chain; this lesson uses pre-generated
|
||||
static step PNGs instead. Wiring that chain as shell steps is deferred.
|
||||
@@ -0,0 +1,107 @@
|
||||
# ADR 0014: Direction For A Markdown / HTML Export Backend
|
||||
|
||||
## Status
|
||||
|
||||
Proposed — **design-only**. This ADR fixes the *direction* and the key open
|
||||
decision for a future markdown/HTML export target; it does **not** implement a
|
||||
backend. No code or schema changes accompany it. (The 恒一小奥 lesson ships with
|
||||
`student`/`teacher` PDF targets and a `kendoku`-generated `interactives` target;
|
||||
a markdown/HTML analog is future work gated on the decision below.)
|
||||
|
||||
Builds on **ADR-0009** (export targets are builds producing typed artifacts;
|
||||
"which markdown dialect … stays open") and **ADR-0011** (presentation lives in
|
||||
the template; the manifest is injected). Relates to **ADR-0013** (a non-typst
|
||||
backend could ride a shell step or earn a typed step — still open).
|
||||
|
||||
## Context
|
||||
|
||||
The third-party 恒一小奥 deliverable was a slide-deck markdown bundle
|
||||
(`lesson_slides.md`). A recurring goal is to export an analog of such
|
||||
markdown/HTML from the same engineering file. The hard part is **math**: a
|
||||
curriculum is formula-heavy, and a markdown/HTML export that turns formulas into
|
||||
images (losing selectability, accessibility, and editability) is a regression.
|
||||
Three facts were established (web research + reading the pinned deps), and a
|
||||
fourth is the user's framing:
|
||||
|
||||
1. **typlite (tinymist's markdown export)** renders math **as images** and loses
|
||||
cross-references and styles — it goes through HTML as an intermediary. This is
|
||||
exactly the failure mode to avoid for a math-rich curriculum.
|
||||
2. **typst 0.15 native HTML export** (the version this repo pins; `typst-html
|
||||
0.15` is already in `Cargo.lock`) emits equations as **MathML**. MathML is
|
||||
*accessibility-oriented*: selectable and screen-readable, but **not** trivially
|
||||
convertible to KaTeX/LaTeX, and rendering fidelity varies by browser.
|
||||
3. **`mitex`** converts **LaTeX → typst** (an authoring-time import direction),
|
||||
**not** typst → LaTeX. So it is not, by itself, the typst-source → markdown
|
||||
lever; the user's instinct that "keep the formula source span and convert it"
|
||||
is right about *what* we want, but `mitex`'s direction is the reverse of what a
|
||||
typst→markdown path needs.
|
||||
4. **The user's framing**: the content a markdown/HTML target needs and the
|
||||
content the PDF 讲义/教案 need **do not fully overlap** — so whether a
|
||||
markdown/HTML export must reuse the *same* `typst-content` source, or instead
|
||||
draw on target-specific content, is **genuinely open** and is the pivotal
|
||||
design decision here.
|
||||
|
||||
## Decision (direction, not implementation)
|
||||
|
||||
### The pivotal open decision: shared source vs. target-specific content
|
||||
|
||||
Before any backend is built, decide: **does the markdown/HTML target render the
|
||||
same per-element `typst-content` the PDF targets render, or does it consume
|
||||
target-specific content?** This ADR does **not** pre-pick it; it names it as the
|
||||
gating decision and frames the two routes so the next round can choose
|
||||
deliberately (constitution rule 2 — surface, do not assume):
|
||||
|
||||
- **(R1) Shared typst-content.** One source of truth; markdown/HTML is another
|
||||
projection of the same `.typ`. Requires a real typst-source → markdown/HTML
|
||||
converter that preserves math as editable markup. Highest reuse, hardest math
|
||||
story.
|
||||
- **(R2) Target-specific content.** The element carries (or the target supplies)
|
||||
content authored for the web medium. Decouples the hard typst→markdown math
|
||||
problem from the PDF path, at the cost of a second content surface to maintain.
|
||||
|
||||
### Preferred math direction (whichever route is chosen)
|
||||
|
||||
Math must survive as **markup, not images**. The favored approach is to obtain
|
||||
each equation's **source span** from the typst AST and convert that span's source
|
||||
to LaTeX/KaTeX, rather than accept typlite's image rendering or 0.15's
|
||||
accessibility-MathML as the final form. Concretely, the next round should
|
||||
evaluate:
|
||||
|
||||
- harvesting equation source spans via `typst-syntax` and emitting
|
||||
`$…$`/`$$…$$` (KaTeX-compatible) from the typst math source, and
|
||||
- studying tinymist's own conversion crate (typlite / `cmarker`) for reusable
|
||||
pieces — adopting what helps, but **not** its math-as-image fallback.
|
||||
|
||||
A small **proof-of-concept** scoped to *this* lesson (whose math is trivial —
|
||||
`8×`, `2-`, `12×`) is the right first experiment: it exercises the pipeline
|
||||
end-to-end while sidestepping the hardest formula cases, and shows whether
|
||||
equations come out as text/KaTeX rather than images.
|
||||
|
||||
### Backend mechanics, once the route is chosen
|
||||
|
||||
- typst 0.15 HTML export is reachable today (`typst-html 0.15` is a transitive
|
||||
dep; promotable to a direct dep) — usable for R1's HTML side.
|
||||
- A markdown (rather than HTML) artifact may post-process that HTML, or emit
|
||||
markdown directly from the AST — open, decided with the route.
|
||||
- Per ADR-0013, the backend may run as a typed step or a shell step; whether the
|
||||
non-typst backend earns its own typed `Step` constructor is still open.
|
||||
|
||||
## Consequences
|
||||
|
||||
- The export-backend work has an explicit gating decision (R1 vs. R2) and a
|
||||
pinned math principle (markup, not images), so the next round starts from a
|
||||
decision rather than a blank page.
|
||||
- No implementation, schema, or Lean change lands now — the contract is
|
||||
unchanged; this is a recorded design direction.
|
||||
- The PDF and interactives paths are unaffected and remain the lesson's shipping
|
||||
targets.
|
||||
|
||||
## Open Questions / Deferred
|
||||
|
||||
- **R1 vs. R2** — the pivotal decision above; unresolved by design.
|
||||
- **Concrete math conversion** — span-harvest → KaTeX vs. reusing a tinymist
|
||||
crate; to be settled by the PoC.
|
||||
- **Markdown dialect** — which dialect/fenced-div conventions the markdown
|
||||
artifact targets (ADR-0009 left this open; still open).
|
||||
- **Typed non-typst step** — whether the backend rides `Shell` or earns a typed
|
||||
`Step` (ADR-0013 open question).
|
||||
Reference in New Issue
Block a user