Commit Graph

32 Commits

Author SHA1 Message Date
sjfhsjfh e3370cad15 chore: release 0.0.1
workspace.package version 0.1.0 → 0.0.1(6 个 crate 经 version.workspace=true
继承,单点改动)。render typst 包维持 0.1.0(独立生态,不锁步)。

- cph --version 报 cph 0.0.1
- 嵌入 render cache 目录按 CLI 版本建键:~/.cache(或 ~/Library/Caches)/cph/render-0.0.1/
- README 补"安装 cph 命令行"一节:cargo install --path crates/cph-cli --locked

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v0.0.1
2026-06-23 15:14:34 +08:00
sjfhsjfh b3148175fe chore: Merge spec-driven into main
main 与 spec-driven 无共同祖先(orphan)。本 merge commit 的 tree 取 spec-driven
的全部内容(完全以 spec-driven 为准),main 原有的 seed 文件不再保留。此后在 main
上准备发版本。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 14:56:37 +08:00
sjfhsjfh 144e2d8c80 feat(spec+checker): slides/逐字稿 markdown 内容面 + assembleMarkdown step(ADR-0015)
给课程加两个 markdown 内容面:slides(大纲:h2 part → h3 小节 → ![]() 图 +
:::widget 教具)与逐字稿(口播)。直接 markdown+KaTeX 撰写,绕开 typst→md
公式转换(等于在 slides 面选 ADR-0014 的 R2)。

spec:
- RichContent.lean: content leaf 带 format(typst|markdown),新增 ContentFormat
  归纳,RichContentRef 加 format 字段
- Export/Render.lean: Step 加 assembleMarkdown (field),钉执行语义
  (同 shell 三边界 + 注入课程 h1 + 自包含收集引用图)
- Courseware.lean: ADR 区间 →0015

实现:
- cph-schema: x-cph-content 支持 true→.typ / 字符串→该扩展名(.md);
  ContentField 带 ext;4 个 kind schema 各加可选 slides/transcript
- cph-model: Step::AssembleMarkdown{field} + manifest "assemble-markdown" 解析
- cph-check: run_markdown_assemble_target / target_is_markdown_assemble
  (照 run_shell_target 形状:check 门控、注入 h1、按 [[parts]] 序拼、写盘后
  收集 ![](rel) 引用图进 build 根使产物自包含、引用图缺失属 build-过程错误
  不入 6 类诊断);compile_targets 已 filter TypstCompile,纯 assemble 自然排除
- cph-cli: run_markdown_assemble_build 路由
- cph-typst: template_step 补 AssembleMarkdown => None 分支

测试:全 workspace 49 passed(新增 6 个 markdown 装配测试含图收集);
lake build 25 jobs 绿。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 14:55:55 +08:00
sjfhsjfh 9590e15236 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>
2026-06-23 12:09:49 +08:00
sjfhsjfh c6b4d439ac docs(spec): 修正误译 "授权 surface" → "撰写态"(authoring surface)
Info.lean 里把用户填写态误译成了"授权"(authorization),与 Permission 模块的
grant 概念撞车且语义错位。正确是 authoring surface = 撰写态:用户在 manifest 里
实际填写的形态(RawInfo/RawAuthor),归一化后即消失。仅措辞,无语义/类型改动。
lake build 绿。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 00:15:48 +08:00
sjfhsjfh 5dc29fe558 feat(model): author 是有序列表;Info(canonical) vs RawInfo(授权 surface) 入契约
一节课可多人署名(教研组),故 canonical author 是**有序列表**而非单值。on-disk
形式接受裸字符串(单作者)或数组(多作者),但此"字符串或数组"二态**只活在加载边界**:
RawInfo 经归一化折叠成 canonical Info,其后系统只见 List。

Lean 母本(ADR-0008 范围):新增 Model/Info.lean,把 Info(canonical,authors: List
String)与 RawInfo/RawAuthor(授权便利 surface)+ 归一化 RawInfo.toInfo 钉死——
canonical 接收端恒为列表,raw 形式不泄漏。ADR-0008 [info] 块补 author-as-list 决策。

impl 对齐:cph-model Info.author: Option<String> → authors: Vec<String>;RawAuthor
untagged enum(One|Many)+ into_vec 归一化;augmented manifest 发射 author 数组
(typst document(author:) 与模板已接受 string/array)。doc 两端互引 spec。

测试:load.rs 单作者→单元素列表;target-configs fixture 加数组作者断言多作者。
验证:lake build 绿(25 jobs);cargo test 全绿;clippy 静默;TH-141(单字符串作者)
check+build PDF 无回归。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 00:11:03 +08:00
sjfhsjfh c684d25d50 chore(spec+checker): cleanup pass — trim docs, reorg Courseware, covers-as-data, fold DanglingReference (ADR-0012)
Spec母本噪音清理 + 一处 spec↔impl 对齐 + 一处契约自洽修正。

Part A — spec doc 瘦身:每条 doc 收到"语义点 + 标签 + ADR ref + 承载性 why",
把跨文件复读的方法论(element-kind 开放性对比、likec4 画不出、分歧点测试)上移到
module header。OPEN 框架保持清晰(RunState/Capability 完整性仍明示须 surface)。

Part B — Courseware/ 由 10 文件平铺重组为 Model/ Export/ Check/ Open/ 四子命名空间
(namespace Spec.Courseware 不变,零引用改动)+ 四个子 aggregator。lake build 绿(24 jobs)。

Part C — 渲染覆盖落为数据(ADR-0011 对齐):去掉 cph-check 里硬编码的
COVERED_TARGETS,改读 TargetConfig.covers。cph-model 新增 covers: Option<Vec<String>>
(None=未声明,由 cph-check 默认为全部 known kinds;显式 [] 表示不覆盖任何 kind)。
新增 3 个覆盖行为测试。

ADR-0012 — DanglingReference 退役(诊断 7→6 类):其两种情形(未解析 @ref、越界/缺失
相对 import)都是 typst 编译期失败,归 typstCompile。同步移除 Oracle.refsResolve
(被 compiles 蕴含),Legal 少一个合取项。impl 删去从未被发射的 DiagCode::DanglingReference,
闭合 named-but-unemitted 的 spec↔impl 缝。ADR-0010 加修订指针。

OPEN 点(RunState/Capability 完整性、QuestionBank、Course)按既定纪律保持 OPEN,本次
只改善其框架措辞,不决策。

验证:spec lake build 绿;cargo test 全绿(含新增覆盖测试);clippy 静默;
TH-141 check 0 errors/0 warnings 无回归。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 23:59:32 +08:00
sjfhsjfh cadf8ad0d3 test(examples): TH-141 to template model — exports/*.typ + manifest v2 (WU-E')
Acceptance fixture for ADR-0011's template build model. TH-141 gains
exports/student.typ + exports/teacher.typ (the framework default templates,
copied from render/templates/ — these should be auto-seeded at project creation,
out of scope this round). manifest.toml [targets.*] converted to v2: artifact =
{type="single-file", filepath} + a typst-compile step naming the template;
numbering removed (it now lives in the template).

Verified end-to-end through the new path (template compiled as main + augmented
manifest, no generated driver): `cph check` → 0 errors/0 warnings; `cph build`
student → 9pp PDF, teacher → 12pp PDF; numbering correct (一、 / 2.1 / 2.2, zero
"二、一"); real content rendered.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 10:06:31 +08:00
sjfhsjfh d76f9f9a54 feat(checker): compile template as main, augmented manifest closes optional-content (WU-D', ADR-0011)
Replace generated-driver compilation with the template model.

cph-typst:
- driver.rs DELETED (generate_driver, static-include driver, numbering-threading).
- New manifest.rs: build_augmented_manifest(&Lesson) emits a TOML doc with [info]
  + ordered [[parts]] each carrying a `fields` array = the kind's content fields
  whose <root>/<path>/<field>.typ exists on disk. Reuses cph-schema's
  content_field_names (new dep; no cycle). This closes the OPEN point WU-C'
  surfaced: typst has no file-exists primitive, so the engine (which has disk
  access) tells the template which optional content (lemma proof) is present.
- World main = the real engineering-file template (<root>/<template>); the
  augmented manifest is served as an in-memory virtual file at /.cph/manifest.toml
  (never written to the tree), injected via sys.inputs.manifest. render_dir kept
  only for @local/cph-render + vendored @preview/numbly resolution.
- Artifact handling: SingleFile+TypstCompile compiles; FileTree, shell-only, and
  undeclared-target are blocking "deferred/not-declared" diagnostics (ADR-0011).
- Engine public signatures unchanged; LessonWorld::new takes template+manifest_src.

cph-check: no source change needed (only uses lesson.targets/target.name +
unchanged Engine methods); pipeline + Legal alignment intact.

Fixtures: mini gets exports/{student,teacher}.typ (from render/templates/) + v2
manifest + a second proof-less lemma to exercise optional content. render-stub
retired (tests use the real render/). Through-template PDFs offline: student 44KB,
teacher 56KB; proof-less lemma compiles clean (optional-content confirmed).
Workspace: fmt + clippy -D warnings + all tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 10:05:03 +08:00
sjfhsjfh 7e15cf34f6 feat(render): export templates + render-lesson entry; template owns include loop (WU-C', ADR-0011)
Replace the display(parts) entry (which a generated driver called) with the
template model: real editable templates (render/templates/{student,teacher}.typ,
the framework defaults copied into an engineering file's exports/) read the
manifest via toml(sys.inputs.manifest) and assemble parts themselves.

Key finding (drives the engine): a dynamic `include` inside a package resolves
against the PACKAGE root, never --root — so the include loop must live in the
TEMPLATE (which sits under --root): `include "/" + part.path + "/" + field +
".typ"` (root-relative absolute, runtime-computed — legal per ADR-0011's verified
fact). cph-render exposes render-lesson(info, target, parts, heading-numbering)
(content-in, never includes) + part-fields (kind→fields) + default-heading-numbering.
Numbering (numbly per-level) is set in the template, editable per engineering file.

Surfaced OPEN point closed by the engine (WU-D'): typst has no file-exists
primitive, so optional content (lemma proof) presence comes from a per-part
`fields` array the engine computes from disk. Manifest injected as root-relative
absolute (--input manifest=/...). numbly stays vendored/offline. display removed.

local-packages symlink (test-only @local resolution) gitignored — regenerable,
self-referential; the embedded engine mounts cph-render directly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 09:57:51 +08:00
sjfhsjfh d5bce1bede feat(checker): cph-model manifest v2 — Artifact-with-fields + typed steps (WU-B', ADR-0011)
[targets.*] reworked: artifact is now an inline table with a type discriminator
({type="single-file",filepath} / {type="file-tree",root,outputs}) and steps is an
ordered array-of-tables ({type="typst-compile",template} / {type="shell",run}).
numbering removed entirely (presentation lives in the template per ADR-0011).

TargetConfig { name, artifact: Artifact, steps: Vec<Step> }; Artifact and Step
mirror Spec.Courseware.Artifact / Spec.Courseware.Step (doc-cited). Sensible
defaults so a bare [targets.student] still works: single-file build/<name>.pdf +
one typst-compile exports/<name>.typ. Malformed artifact/step type → non-fatal
SchemaViolation with fallback. 8 tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 09:57:50 +08:00
sjfhsjfh de87cddaf1 feat(spec): Artifact gets fields, target = artifact + typed steps, drop RenderRule (ADR-0011)
Fix the abstraction level review flagged. lake build green (20 jobs), no sorry.

- Artifact.lean: bare enum → ADT with fields — singleFile(filepath) /
  fileTree(root, outputs:glob). The product semantics (one file where, vs a tree
  of what) are pinned in fields + doc, not erased behind empty constructors.
  Paths/globs carried as String (semantics in doc; no filesystem algebra).
- Render.lean: Step inductive (typstCompile(template) | shell(run)) replaces the
  loose BuildStepForm. TargetSpec = artifact + steps:List Step + covers:Kind→Prop
  (the old renders:Kind→Option RenderRule is gone — coverage is now a plain
  declaration; the render "how" lives in the typstCompile template). covers /
  renderIgnored semantics continuous with ADR-0005.
- Primitives.lean: RenderRule field removed (the per-target payload is retired per
  ADR-0011); a note records where the "how" now lives.
- Diagnostic.lean unchanged in meaning (covers signature stable; Legal still
  quantifies over declared targets).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 09:46:34 +08:00
sjfhsjfh 9b37914bfb docs(adr): add ADR-0011 (export builds = typed steps over templates), correct ADR-0008
Refines ADR-0009 by fixing the shape of an artifact and a build; corrects a
false premise that propagated into the implementation.

ADR-0011:
- Artifact is an ADT WITH fields: SingleFile{filepath} / FileTree{root,
  outputs:glob} — the product semantics (one file where, vs a tree of what) are
  pinned, not erased behind a bare tag.
- A target = artifact + ordered typed steps: typstCompile{template} (typed
  because the framework wires the manifest in — not a raw shell line) | shell{run}
  (the hatch where ADR-0005 category-b medium-only builds land).
- Presentation (numbly numbering, styling) lives in the template file
  (exports/<target>.typ), NOT the manifest; manifest [targets.*.numbering] removed.
- Correction (verified against typst source: typst-eval/src/import.rs:22,174;
  tests/suite/scripting/include.typ): include/import paths MAY be runtime-computed
  (only a bare `import expr` without `as` is restricted). So the framework does
  NOT generate a static-include driver — a template toml()-reads the manifest and
  includes each part via a computed path. Removes a whole generated layer.
- RenderRule-as-payload retired; coverage downgraded to a declaration (which
  kinds a target renders) feeding the unchanged renderIgnored warning.

ADR-0008: corrected the two clauses (the "generated driver / static #import"
wording) that were residue of the false premise, pointing them at the
template-injection model and citing ADR-0011.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 09:44:32 +08:00
sjfhsjfh c93503de50 test(examples): TH-141 declares structured target build config (WU-E)
Acceptance fixture for the export-as-build model. TH-141's [targets.student] /
[targets.teacher] now carry artifact = "single-file" + an explicit per-level
heading numbering override (一、 / 1.1 / 1.1.1), exercising ADR-0009's
file-resident override path end-to-end.

Verified: `cph check` → 0 errors / 0 warnings; `cph build` both targets → PDFs
(student 9pp, teacher 12pp) with correct numbering — zero "二、一" occurrences
(the bug the override layer was missing to fix). numbly resolves offline through
the embedded engine via the vendored package.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 08:49:21 +08:00
sjfhsjfh bd1699a4c0 feat(checker): align cph-typst + cph-check to export-as-build model (WU-D)
Integrate the WU-B (structured targets) and WU-C (numbly, config) changes.

cph-typst:
- World resolves any @preview/* package from the in-repo vendored dir
  (<render_dir>/vendor/typst-packages/preview/<name>/<version>/), fully offline —
  no typst-kit download. Proven: build_pdf_with_real_render compiles the real
  render package (which imports @preview/numbly:0.1.0) through the embedded
  engine, PDF out.
- Driver threads each target's presentation config: looks up the TargetConfig by
  name, emits `config: (numbering: (heading: (...)))` (escaped) when an override
  is present, else `config: (:)` (render default).
- target_precheck guards artifact/target: FileTree → blocking diagnostic (MVP is
  single-file, deferred per ADR-0009); --target not declared → blocking
  diagnostic; no-targets lesson still defaults through.

cph-check:
- Migrated to Lesson.targets: Vec<TargetConfig> (target_names() / target.name at
  the 6 sites). Pipeline unchanged (already matches ADR-0010 phases). Documented
  that !has_errors() implements Spec.Courseware.Legal (no error-level diagnostic).

Workspace: fmt + clippy -D warnings + all tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 08:47:13 +08:00
sjfhsjfh 6f46aa708a fix(render): per-level heading numbering via numbly; config-driven, offline (WU-C)
Fixes the numbering bug (single pattern "一、" reused across levels → level-2
rendered "二、一、…"). display now takes a `config` dict; heading numbering uses
numbly per level — framework default ("{1:一}、","{1:1}.{2:1}","{1:1}.{2:1}.{3:1}")
→ 一、 / 1.1 / 1.1.1 — overridable via config.numbering.heading from the
engineering file (ADR-0009's file-resident override path, the layer whose absence
caused the bug). Verified: grep "二、一" = 0 across all smoke PDFs.

Adds @preview/numbly:0.1.0 (pure typst, zero transitive deps), VENDORED in-repo
at render/vendor/typst-packages/preview/numbly/0.1.0/ for network-free CI;
resolve via --package-cache-path render/vendor/typst-packages. parts contract +
student/teacher field visibility unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 08:42:15 +08:00
sjfhsjfh b5bf9d60d1 feat(checker): cph-model — structured export-target build config (WU-B, ADR-0009)
manifest.toml [targets.*] upgraded from bare names to structured build configs.
Lesson.targets: Vec<String> → Vec<TargetConfig> { name, artifact, numbering }.
ArtifactKind { SingleFile, FileTree } mirrors Spec.Courseware.Artifact.
NumberingConfig.heading: Option<Vec<String>> — None means "use framework
default" (render package owns it), so absence is distinguishable from a value.
Empty [targets.x] body = all defaults, no diagnostics. Malformed config (bad
artifact value, non-array numbering.heading) → non-fatal SchemaViolation, target
kept with defaults. Enabled toml `preserve_order` so target declaration order is
real (the old Vec<String> never actually preserved order). target_names() helper
for call sites that only want names. 8 tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 08:42:15 +08:00
sjfhsjfh 09b026f598 feat(spec): backfill Export terminology + diagnostics/legality/pipeline (ADR-0009/0010)
Lift the business decisions the MVP made into the Lean master, per constitution
rule 5. lake build green (20 jobs), no sorry.

- Artifact.lean: `inductive Artifact | singleFile | fileTree` — product shape is
  part of a target's value (ADR-0009); it determines the assemble/reduce step.
- Render.lean reworked target-centric (ADR-0009, amends ADR-0005's matrix):
  `TargetSpec` carries `artifact` + a per-kind `renders` map (field visibility
  lives in this map); `RenderConfig.spec : TargetId → Option TargetSpec`; `covers`
  rewritten (target must exist AND its map covers the kind) — semantics continuous
  with ADR-0005 so renderIgnored still typechecks. `BuildStepForm` (declarative |
  shell) is a structural anchor for the build form (shell hatch = ADR-0005's
  medium-only category b) without replicating the schema.
- Diagnostic.lean extended: `DiagKind` (7 classes) + `DiagKind.severity` (six
  error, renderIgnored warning); `Oracle` makes external-facility verdicts
  (compiles/refsResolve/dataConforms/contentFilesPresent) an explicit
  implementation boundary; `Legal` = no error-level diagnostic (backfills
  ADR-0005's deferred full-legality), with renderIgnored deliberately excluded
  (it's a warning).
- Pipeline.lean: the 5 phases (load→structural→schema→compile→coverage) as
  structure — order + the compile gate (compile runs only on zero prior errors) +
  load halts the pipeline. Algorithms stay out of Lean (depth ceiling).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 08:36:29 +08:00
sjfhsjfh 81cf762a0c docs(adr): add ADR-0009 (export target = build) + ADR-0010 (diagnostics/legality/pipeline)
Lift business decisions made during the MVP build into the contract, per
constitution rule 5 (divergence points belong in the spec).

ADR-0009 — amends ADR-0005's render-matrix framing: an export target is a *build*
producing a typed Artifact (SingleFile | FileTree). build = map (per-kind, the
field-visibility lives here) + reduce (assemble, determined by artifact type:
SingleFile concatenates in lesson order then compiles — which is why @ref and
counters work). Build form = declarative schema + a run-shell hatch (where
ADR-0005's medium-only category (b) and npm/HTML builds live), not a Makefile.
typst compile is hidden backend mechanism, not part of a target. Defaults seeded
at project creation, overridable in the file — the heading-numbering bug
(一、 reused → 二、一、) is the worked example of that missing override layer.

ADR-0010 — backfills ADR-0005's deferred "legal lesson": the 7-class diagnostic
taxonomy with per-class meaning + severity; legal lesson = no error-level
diagnostic; the 5-phase pipeline (load→structural→schema→compile→coverage) with
compile gated on zero prior errors. External-facility diagnostics (TypstCompile,
DanglingReference, schema conformance) modeled as abstract predicates whose
verdict comes from an implementation oracle, not from Lean — keeps the contract
honest about what it does and doesn't decide.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 08:33:11 +08:00
sjfhsjfh 9309a175c0 docs(readme): reflect the checker workspace, render package, and examples
The repo-layout block said implementation parts were "尚未创建"; they now exist.
Document the repo-root cargo workspace (so future parts reuse base crates), the
crates/ map (cph-diag/model/schema/typst reusable; cph-check/cli the checker),
the render/ typst package, and examples/.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 01:58:11 +08:00
sjfhsjfh b86ee24a26 feat(checker): cph-check orchestrator + cph-cli cph binary (WU-5)
Closes the core pipeline: the `cph` CLI checks an engineering file and compiles
a PDF.

cph-check — phases (a) load → (b) structural (known-kind set) → (c) schema →
(d) typst compile → (e) render-coverage. Compile is gated: skipped if (a)-(c)
produced any Error (broken input would only add noise); `check` compiles every
declared target (dedup identical diags), `build` compiles the one requested.
Render-coverage emits a non-blocking RenderIgnored *warning* when a (kind,
target) has no render rule — severity pinned via RENDER_IGNORED_SEVERITY const
citing spec Diagnostic.renderIgnoredSeverity + ADR-0005. No double-emit:
unknown/missing parts are skipped downstream. API: check(root, engine) ->
CheckReport, build(root, engine, target) -> (Option<pdf>, CheckReport).

cph-cli — clap: `cph check <path>` (exit 1 on any Error, warnings → 0) and
`cph build <path> --target <name> [-o out]` (default target student, default
out <path>/build/<target>.pdf). Diagnostics → stderr via Display, results →
stdout. --render-dir override.

End-to-end on examples/TH-141 (39 parts): `check` → 0 errors/0 warnings;
`build` → real PDF student 9pp/395KB, teacher 12pp/474KB. Broken-element check
(unclosed delimiter in a stmt.typ) → span-accurate
`error[E-TYPST-COMPILE]: unclosed delimiter --> lemmas/…/stmt.typ:7:8`, exit 1.
Workspace: fmt + clippy -D warnings + test all green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 01:57:34 +08:00
sjfhsjfh 7e76482a31 feat(checker): cph-typst — embed typst 0.15, driver-gen, compile, PDF (WU-4)
The heart of the pipeline. Embeds the typst 0.15 compiler as a library to
compile-check a lesson and export a PDF, with diagnostics mapped back to source
spans (the product's value).

- LessonWorld: a typst World over the engineering-file directory tree. vpath =
  real relative path (ADR-0007); the per-target driver is mounted in-memory as
  `main` (.cph/driver-<target>.typ, never written to disk); @local/cph-render is
  resolved from render_dir (CPH_RENDER_DIR / with_render_dir), no package install
  or network. Fonts via typst-kit FontStore (system CJK).
- Driver generation: from the manifest, per target — static root-relative
  `include` of each content file (⇒ module body content, ADR-0006), assembled
  into the frozen `display(info, target, parts)` call; optional lemma proof
  emitted only if proof.typ exists; example `source` scalar threaded through.
- Diagnostics: typst SourceDiagnostic (errors + warnings) → cph_diag::Diagnostic.
  0.15 DiagSpan handled via span.id() + WorldExt::range → file-relative
  SourceSpan with 1-based line/col; driver-internal spans re-pointed with a
  "generated driver" hint, never dropped. typst hints/trace carried through.
- Engine API: new / with_render_dir / compile_check / build_pdf.
- 6 tests incl. build_pdf_with_real_render — full World→driver→cph-render→PDF
  producing real PDF-1.7 output (student 42KB / teacher 54KB). clippy/fmt clean.

Also fixes render/typst.toml `compiler = ">=0.15.0"` → `"0.15.0"`: typst 0.15's
manifest parser rejects a `>=` comparator on the compiler field (the 0.14 CLU
WU-2 tested against was lenient). This unblocks loading the real render package.

Pinned: typst/typst-pdf/typst-syntax/typst-library/typst-layout/typst-kit =0.15.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 01:49:15 +08:00
sjfhsjfh c73a2c903f feat(examples): migrate TH-141 sample to declarative layout (WU-6)
Real-content fixture for the end-to-end pipeline. TH-141 (39 parts: 22 segment,
15 lemma, 2 example) migrated from the prototype's typst `#let parts` manifest
to ADR-0008: declarative manifest.toml (project+info+ordered parts+targets) +
per-element element.toml (kind + scalars; examples carry `source`). Content .typ
files copied byte-identical (no math corruption); per-element main.typ + meta.toml
dropped (wiring is now generated). Part order matches source exactly; 5 lemmas
have no proof.typ (optional); no cross-file imports / paralearn refs / figs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 01:33:40 +08:00
sjfhsjfh 752a5c661d feat(checker): cph-schema — 4 kind JSON Schemas + validation (WU-3)
Declarative JSON Schema per kind (segment/example/lemma/sop), embedded as real
.json artifacts (ADR-0006) and validated by a small hand-rolled interpreter
(properties/required/type/enum/additionalProperties + the `x-cph-content`
marker) — no jsonschema-crate churn for a one-optional-string scalar surface.
`x-cph-content: true` marks content fields (not in element.toml; require sibling
<field>.typ); everything else is an element.toml scalar. `validate(desc)` is
self-contained: UnknownKind for unknown kinds, MissingContentFile (path named in
message) for absent required .typ, SchemaViolation for bad scalars; lemma proof
optional ⇒ no diagnostic when absent. 8 tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 01:33:40 +08:00
sjfhsjfh c17af60bb0 feat(render): rebuild typst render package cph-render (WU-2)
Single entry `display(info, target, parts)` over an ordered parts array, the
frozen contract the generated typst driver calls. Per-kind dispatch for
segment/example/lemma/sop; student/teacher matrix derived internally from
`target` (student hides example solution + lemma proof; teacher shows all).
Content-field values are consumed as already-evaluated content (driver
`include`s them ⇒ module body, ADR-0006), never imported/stringified. Optional
fields (lemma proof, example source) handled gracefully; unknown kind/target
degrade without crashing. Zero @preview deps (CI-robust); CJK + math styled.
Smoke compiles to non-empty student/teacher PDFs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 01:33:40 +08:00
sjfhsjfh c151963967 ci: add Rust checker workflow (fmt + clippy -D warnings + test)
Mirrors spec-check's framing: an internal health gate on the implementation
(build/test/lint), not a spec→impl conformance gate (alignment is by review).
Installs fonts-noto-cjk ahead of the typst-compiling crates (WU-4+), since the
engineering files are CJK-heavy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 01:28:12 +08:00
sjfhsjfh c810ea6137 feat(checker): scaffold repo-root cargo workspace + cph-diag + cph-model (WU-1)
Stand up the Rust implementation of the rule-based checker (the thing that
"stands in Lean's position" at product runtime). Single repo-wide cargo
workspace at the repo root so future components (e.g. an exporter) can reuse
shared crates.

- workspace: 6 member crates under crates/; cph-schema/cph-typst/cph-check/
  cph-cli are stubs for later WUs that compile clean today.
- cph-diag: shared diagnostic vocabulary. Severity { Warning, Error } mirrors
  Spec.Courseware.Diagnostic.Severity exactly (two-valued, no info/note — a
  contract decision documented in-code since there's no CI gate for alignment).
  Closed DiagCode set with stable string forms; Diagnostic with optional span +
  fix hint; Display renders `error[CODE]: msg / --> file:line:col / hint: …`.
- cph-model: the ADR-0008 loader. Reads manifest.toml ([project]/[info]/ordered
  [[parts]]/[targets.*]) + each part's element.toml into an ordered Lesson
  (mirrors Lean `Lesson = List (Element P)`). Structure-only: cross-checks
  part.kind == element.toml kind, rejects `..` traversal; does NOT do schema
  validation (WU-3), content-file existence (WU-3), or typst (WU-4).
  `load(root) -> (Option<Lesson>, Vec<Diagnostic>)`: Some even on collectible
  part errors, None only on hard manifest failure.
- 13 tests (7 cph-diag, 6 cph-model incl. static fixtures doubling as format
  docs). build + test + clippy -D warnings + fmt --check all green.

Judgment call flagged for review: cph-diag has no ManifestMalformed code, so
manifest-structure errors map to SchemaViolation (TODO noted in-code).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 01:27:32 +08:00
sjfhsjfh 8599f472c0 docs(adr): add ADR-0008 — declarative manifest + on-disk layout conventions
Discharge the layout question ADR-0007 deferred. Fix the engineering-file
on-disk arrangement so the checker recovers lesson order/membership without
evaluating typst:

- manifest.toml at root: [project] + [info] + ordered [[parts]] (kind+path) +
  [targets.*]. Array order IS lesson order (ADR-0005). Subsumes the samples'
  project.toml/info.toml and replaces main.typ's `#let parts`.
- element.toml per element folder: explicit kind + scalar fields (self-describing,
  parent-dir grouping not load-bearing).
- content fields are convention-named <field>.typ siblings; the kind's JSON Schema
  (ADR-0006) is the source of truth for which .typ files must exist. Drops the
  samples' per-element main.typ wiring (now generated, not hand-authored).

Surfaced gaps (deferred): per-file render override (ADR-0005 says the matrix is
overridable in-file; MVP keeps rules in the render layer, no override), 题库
on-disk layout, manifest richness (per-part metadata, sectioning).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 01:18:48 +08:00
sjfhsjfh f1dce07789 feat(spec): build out System layer and element schema model
Picture the whole framework, then pin the element-kind language layer.

System platform layer — rebuild the semantics likec4 can't draw (ADR-0001..0004):
- Prelude: opaque Identifiers carrier (ProjectId/RunId/SessionId/Principal)
- System/Run: RunState + Terminal subset (set completeness left OPEN, no invented pending)
- System/Lock: lock owner=run (typed), LockTable exclusivity, WellFormed invariant
  (a lock holder must be a non-terminal run — couples Lock and Run)
- System/Permission: read<edit<manage role lattice, capability derivation,
  can_mono monotonicity theorem; force-release sits outside the lattice (admin-only)
- System/Audit: intentionally thin (mostly plumbing, OPEN)

Courseware product layer — split the single Lesson file into focused modules and
fix doc tautology: Primitives / Element / Lesson / Render / Diagnostic, plus
QuestionBank and Course skeletons (core relations OPEN, not invented).
Diagnostic upgrades WarnsIgnored into a severity-tagged checker rule.

Element schema + rich-content model (ADR-0006, ADR-0007):
- docs/adr/0006: kind schema is declarative JSON Schema; field types are built-in
  scalars plus a `content` extension; a `content` value is typst source taken as
  module body; rich content must carry a VirtualPath (else click-to-jump and
  relative import break — verified against typst source); import boundary =
  within the engineering file + @package
- docs/adr/0007: on-disk form is a real directory tree (agent/grep friendly);
  VirtualPath = real relative path; layout conventions deferred
- spec/Courseware/RichContent: prose anchor for rich content (opaque VPath,
  RichContentRef); ElementData kept abstract — JSON/typst internals are
  implementation detail, not contract

lake build green (18 jobs), no sorry, toolchain v4.31.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 00:31:14 +08:00
sjfhsjfh 4a828e07b9 feat(spec): add lesson model contract (ADR-0005)
First product-core concept in the semantic master spec: a curriculum
engineering file is one lesson, modeled as an ordered sequence of typed
element instances over an open kind universe.

- docs/adr/0005: lesson model decision narrative + deferred OPEN items
- spec/Spec/Courseware/Lesson.lean: Primitives carrier (KindId/ElementData/
  TargetId/RenderRule kept abstract), Element, Lesson, RenderConfig, and the
  first checker seed (WarnsIgnored: used-kind-with-no-render-rule => warning)
- wire Spec.lean -> Spec.Courseware; drop placeholder Basic.lean

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 23:11:47 +08:00
sjfhsjfh 3a22669f0a chore: scaffold monorepo with spec/ Lean project and governance docs
- spec/: freshly initialized Lean library project (lake init, v4.31.0), no deps
- .gitea/workflows/spec-check.yml: CI running lake build on spec/ (well-formedness gate, not a spec-to-impl gate)
- README.md: monorepo overview + the 5 "constitution" rules positioning spec/ as upstream semantic master
- CLAUDE.md: global agent manual
- spec/README.md: contract writing conventions (prose+type, PINNED/OPEN/ADR tags, no sorry)
- docs/adr/0001-0004: system-level decision records (retrieved from main)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 22:00:18 +08:00
hongjr03 ca9f1242f8 chore: add c4 spec 2026-06-06 19:22:58 +08:00