From 345f6009f2c17de694d3eac6e601d467d5a4f4a2 Mon Sep 17 00:00:00 2001 From: Myriad-Dreamin Date: Sun, 15 Mar 2026 05:20:00 +0800 Subject: [PATCH 1/7] dev: add create-english-typst-grammar-skill proposal --- .../.openspec.yaml | 2 + .../design.md | 108 ++++++++++++++++++ .../proposal.md | 27 +++++ .../typst-document-authoring-skill/spec.md | 36 ++++++ .../tasks.md | 22 ++++ 5 files changed, 195 insertions(+) create mode 100644 openspec/changes/create-english-typst-grammar-skill/.openspec.yaml create mode 100644 openspec/changes/create-english-typst-grammar-skill/design.md create mode 100644 openspec/changes/create-english-typst-grammar-skill/proposal.md create mode 100644 openspec/changes/create-english-typst-grammar-skill/specs/typst-document-authoring-skill/spec.md create mode 100644 openspec/changes/create-english-typst-grammar-skill/tasks.md diff --git a/openspec/changes/create-english-typst-grammar-skill/.openspec.yaml b/openspec/changes/create-english-typst-grammar-skill/.openspec.yaml new file mode 100644 index 0000000..49ccc67 --- /dev/null +++ b/openspec/changes/create-english-typst-grammar-skill/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-03-14 diff --git a/openspec/changes/create-english-typst-grammar-skill/design.md b/openspec/changes/create-english-typst-grammar-skill/design.md new file mode 100644 index 0000000..80cb58e --- /dev/null +++ b/openspec/changes/create-english-typst-grammar-skill/design.md @@ -0,0 +1,108 @@ +## Context + +This repository contains a detailed Typst grammar catalog in `src/tutorial/reference-grammar.typ`. The proposed change turns that tutorial asset into a repo-local Codex skill that helps author user Typst documents by selecting valid grammar patterns from canonical examples instead of inventing syntax ad hoc. + +The skill needs to be English-only from Codex's perspective, even though the source tutorial and some example literals are Chinese. The same change also needs a validation workflow that does not depend on IDE-specific language services. The user explicitly wants grammar validation to come from Typst compilation, then separate validation passes for text output through HTML and visual output through SVG plus Playwright MCP inspection. + +## Goals / Non-Goals + +**Goals:** +- Create a repo-local Codex skill for user Typst document authoring. +- Ground the skill in canonical grammar examples extracted from `src/tutorial/reference-grammar.typ`. +- Keep all skill-authored prose, metadata, navigation, and workflow documentation in English. +- Make `typst compile` the authoritative validation path for grammar and compile-time failures. +- Define a repeatable text-validation workflow using Typst HTML output. +- Define a repeatable visual-validation workflow using Typst SVG output and Playwright MCP. + +**Non-Goals:** +- Translate the full Chinese tutorial into English. +- Replace Typst's own compiler diagnostics with a custom parser or validator. +- Depend on Tinymist, VS Code, or any specific editor integration for the required validation path. +- Build a general-purpose Typst skill for all repositories outside this repo's structure and assets. + +## Decisions + +### Decision: Build the skill around extracted grammar references + +The skill will not point Codex at the full tutorial file every time. Instead, it will maintain generated reference material derived from `src/tutorial/reference-grammar.typ` so Codex can search a compact catalog first and only open the source file when deeper context is needed. + +Why this approach: +- It keeps the skill's active context smaller than loading the full tutorial source. +- It preserves direct traceability back to the source grammar table. +- It allows English-facing headings and navigation even when the source material is not in English. + +Alternative considered: +- Read `src/tutorial/reference-grammar.typ` directly on every use. Rejected because it is larger, noisier, and less friendly to repeated low-context lookups. + +### Decision: Treat English-only as a requirement for skill-authored materials, not for verbatim source code blocks + +The skill's `SKILL.md`, generated headings, navigation labels, validation guidance, and agent metadata will be written in English. Verbatim code examples may still contain non-English literals when they come directly from canonical source examples. + +Why this approach: +- It satisfies the requirement that the skill itself is written in English. +- It avoids mutating canonical Typst examples in ways that could accidentally change grammar or semantics. +- It keeps the extracted references trustworthy as grammar sources. + +Alternative considered: +- Rewrite all source examples into English text. Rejected because it introduces translation work and risks changing the exact shape of canonical grammar examples. + +### Decision: Use `typst compile` as the mandatory validation source of truth + +The skill will validate authored Typst documents with `typst compile`, not with editor-specific LSP diagnostics. Compiler exit status and compiler diagnostics will be treated as the required pass/fail signal for grammar and compile-time correctness. + +Why this approach: +- It matches the requested workflow. +- It works consistently outside any specific IDE. +- It keeps the validation path aligned with the actual renderer and compiler used for outputs. + +Alternative considered: +- Use Tinymist or another LSP as the main validator. Rejected because the requested change explicitly prefers Typst compilation over IDE tooling. + +### Decision: Split validation into compile, text, and visual passes + +The skill will define three validation layers: +- compile validation with `typst compile` +- text validation with `typst compile --features html` +- visual validation with `typst compile ... a.svg` plus Playwright MCP inspection + +Why this approach: +- Grammar correctness does not guarantee correct text output. +- Correct text output does not guarantee correct layout or rendering. +- The separation maps directly to the user's requested workflow and keeps the skill operationally clear. + +Alternative considered: +- Treat a successful PDF or SVG compile as sufficient validation. Rejected because it misses easy-to-inspect text structure issues that HTML surfaces well. + +### Decision: Make Playwright an inspection layer over generated SVG, not the rendering source + +The skill will rely on Typst to generate SVG and on Playwright MCP to inspect that output in a browser-compatible context. The browser step is observational only; it will not replace Typst rendering. + +Why this approach: +- Typst remains the authoritative renderer. +- Playwright is strong at visual verification and DOM/screenshot workflows. +- It keeps the boundary between generation and inspection simple. + +Alternative considered: +- Use Playwright to render HTML instead of inspecting SVG. Rejected because the requested visual path is explicitly based on Typst SVG output. + +## Risks / Trade-offs + +- Strict English-only expectations may conflict with verbatim source examples that include Chinese literals. → Mitigation: keep all authored guidance in English and document that canonical code blocks are source data, not translated prose. +- HTML export is still an in-development Typst feature and may emit warnings or change behavior over time. → Mitigation: treat HTML as a validation aid for text structure, not as a production output contract. +- SVG inspection can depend on the execution environment and how Playwright accesses generated files. → Mitigation: document a browser-compatible inspection path and keep SVG generation itself independent from Playwright. +- Grammar extraction logic can drift if `src/tutorial/reference-grammar.typ` changes structure. → Mitigation: keep extraction logic small, source-specific, and covered by representative regeneration checks. + +## Migration Plan + +1. Add or replace the repo-local skill under `.codex/skills/`. +2. Generate English-facing reference material from `src/tutorial/reference-grammar.typ`. +3. Switch validation guidance from any editor-specific path to `typst compile`. +4. Add HTML and SVG validation guidance and any supporting scripts needed for repeatable execution. +5. If a previous prototype skill exists, retire or overwrite it so only the new English-only workflow remains. + +Rollback strategy: +- Remove the new skill folder and generated references, then restore the previous repo-local skill state if needed. + +## Open Questions + +- None at proposal time. The current request is specific enough to proceed with implementation planning. diff --git a/openspec/changes/create-english-typst-grammar-skill/proposal.md b/openspec/changes/create-english-typst-grammar-skill/proposal.md new file mode 100644 index 0000000..a897845 --- /dev/null +++ b/openspec/changes/create-english-typst-grammar-skill/proposal.md @@ -0,0 +1,27 @@ +## Why + +Codex can already edit Typst files, but it still benefits from a repo-specific skill that teaches it to prefer canonical grammar patterns over improvised syntax. This repository already contains a rich grammar example table in `src/tutorial/reference-grammar.typ`, so now is a good time to turn that material into an English-only skill that reliably guides authoring and validation for user Typst documents. + +## What Changes + +- Create a new Codex skill that teaches Typst document authoring by adapting examples from `src/tutorial/reference-grammar.typ` instead of inventing grammar from scratch. +- Require the skill itself to be written in English, including `SKILL.md`, generated references, validation guidance, and UI metadata. +- Add reusable resources that extract and organize grammar examples from `src/tutorial/reference-grammar.typ` for low-context lookup during authoring. +- Standardize grammar validation around `typst compile` so syntax and semantic failures are detected through Typst’s own compiler output instead of editor-specific tooling. +- Define a text-validation workflow based on Typst HTML output, for example `typst compile --features html a.typ a.html`. +- Define a visual-validation workflow based on Typst SVG output plus Playwright MCP inspection, for example `typst compile a.typ a.svg`. + +## Capabilities + +### New Capabilities +- `typst-document-authoring-skill`: Provide an English-only Codex skill that teaches Typst authoring from canonical grammar examples and validates authored documents with Typst compilation, HTML output, and SVG plus Playwright review. + +### Modified Capabilities +- None. + +## Impact + +- Affects repo-local skill content under `.codex/skills/`. +- Adds or updates scripts and references used to extract grammar examples from `src/tutorial/reference-grammar.typ`. +- Defines compile-based validation workflows for `.typ`, `.html`, and `.svg` outputs. +- Shapes how Codex authoring guidance is presented for user Typst documents in this repository. diff --git a/openspec/changes/create-english-typst-grammar-skill/specs/typst-document-authoring-skill/spec.md b/openspec/changes/create-english-typst-grammar-skill/specs/typst-document-authoring-skill/spec.md new file mode 100644 index 0000000..c5f0096 --- /dev/null +++ b/openspec/changes/create-english-typst-grammar-skill/specs/typst-document-authoring-skill/spec.md @@ -0,0 +1,36 @@ +## ADDED Requirements + +### Requirement: Skill materials are English-facing +The skill SHALL present its metadata, instructions, navigation text, validation guidance, and generated reference prose in English so Codex can use it without relying on Chinese tutorial prose. + +#### Scenario: English-only authored materials +- **WHEN** the skill folder is created or updated +- **THEN** `SKILL.md`, agent metadata, and generated guidance files MUST use English for authored prose and labels + +### Requirement: Skill references canonical grammar examples +The skill SHALL provide reference material derived from `src/tutorial/reference-grammar.typ` so Codex can locate and adapt canonical Typst grammar examples while authoring user documents. + +#### Scenario: Grammar lookup from extracted references +- **WHEN** Codex needs a Typst syntax pattern for a user document +- **THEN** the skill MUST provide an extracted reference path that maps back to canonical examples from `src/tutorial/reference-grammar.typ` + +### Requirement: Grammar validation uses Typst compilation +The skill SHALL define `typst compile` as the required validation workflow for grammar and compile-time errors in authored Typst documents. + +#### Scenario: Compile failure is treated as invalid grammar or document state +- **WHEN** Codex validates a `.typ` file with the skill's required workflow +- **THEN** a non-zero `typst compile` result MUST be treated as a blocking validation failure + +### Requirement: Text output can be validated through HTML export +The skill SHALL define a text-validation workflow based on Typst HTML output so Codex can inspect textual structure and rendered text separately from compile success. + +#### Scenario: HTML validation of authored text +- **WHEN** Codex needs to verify rendered text or document structure after a successful compile +- **THEN** the skill MUST direct Codex to use `typst compile --features html` to produce HTML output for inspection + +### Requirement: Visual output can be validated through SVG and Playwright +The skill SHALL define a visual-validation workflow based on Typst SVG output and Playwright MCP inspection so Codex can review rendered layout after compilation. + +#### Scenario: SVG and Playwright visual inspection +- **WHEN** Codex needs to inspect rendered layout or visual regressions in a user Typst document +- **THEN** the skill MUST direct Codex to compile the document to SVG and use Playwright MCP as the visual inspection step diff --git a/openspec/changes/create-english-typst-grammar-skill/tasks.md b/openspec/changes/create-english-typst-grammar-skill/tasks.md new file mode 100644 index 0000000..92c90e0 --- /dev/null +++ b/openspec/changes/create-english-typst-grammar-skill/tasks.md @@ -0,0 +1,22 @@ +## 1. Skill setup + +- [ ] 1.1 Audit any existing Typst skill or prototype files and choose the final repo-local skill target under `.codex/skills/` +- [ ] 1.2 Create or update the repo-local skill scaffold and English-facing agent metadata for the Typst document authoring skill + +## 2. Grammar reference resources + +- [ ] 2.1 Implement or update the extraction flow that reads `src/tutorial/reference-grammar.typ` and produces skill reference material +- [ ] 2.2 Generate an English-facing grammar reference catalog that preserves traceability back to the canonical source examples +- [ ] 2.3 Write the final English-only `SKILL.md` and reference guidance for grammar-first authoring + +## 3. Compile-based validation workflow + +- [ ] 3.1 Replace any editor-specific validation requirement with a `typst compile`-based grammar validation workflow +- [ ] 3.2 Add documented HTML validation commands and guidance for checking rendered text output +- [ ] 3.3 Add documented SVG generation and Playwright MCP guidance for visual inspection of rendered output + +## 4. Verification and cleanup + +- [ ] 4.1 Smoke-test successful and failing `typst compile` runs on representative `.typ` files +- [ ] 4.2 Smoke-test HTML and SVG output generation plus a Playwright-based visual inspection path +- [ ] 4.3 Validate the final skill contents and retire or overwrite any superseded Typst skill prototype From 971815a6b4898c0339b620e08c62824ec03bba7f Mon Sep 17 00:00:00 2001 From: Myriad-Dreamin Date: Sun, 15 Mar 2026 16:21:27 +0800 Subject: [PATCH 2/7] dev: implement create-english-typst-grammar-skill proposal --- .../skills/typst-grammar-authoring/SKILL.md | 499 ++++ .../agents/openai.yaml | 4 + .../references/grammar-catalog.json | 2220 +++++++++++++++++ .../scripts/generate_grammar_catalog.py | 299 +++ .../tasks.md | 20 +- 5 files changed, 3032 insertions(+), 10 deletions(-) create mode 100644 .codex/skills/typst-grammar-authoring/SKILL.md create mode 100644 .codex/skills/typst-grammar-authoring/agents/openai.yaml create mode 100644 .codex/skills/typst-grammar-authoring/references/grammar-catalog.json create mode 100644 .codex/skills/typst-grammar-authoring/scripts/generate_grammar_catalog.py diff --git a/.codex/skills/typst-grammar-authoring/SKILL.md b/.codex/skills/typst-grammar-authoring/SKILL.md new file mode 100644 index 0000000..7ba5599 --- /dev/null +++ b/.codex/skills/typst-grammar-authoring/SKILL.md @@ -0,0 +1,499 @@ +--- +name: typst-grammar-authoring +description: Use when authoring or validating Typst documents in this repo from canonical grammar examples in src/tutorial/reference-grammar.typ, especially when you need compile, HTML, or SVG-based validation workflows. +metadata: + short-description: Author Typst docs from canonical grammar examples +--- + +# Typst Grammar Authoring + +Use this skill when the user wants help drafting, fixing, or validating Typst +documents in this repository. + +## Workflow + +1. Start with the grammar lookup section in this file and pick the closest + existing pattern before inventing new syntax. +2. Copy the smallest matching example, then adapt it incrementally. +3. Run `typst compile` after each meaningful edit. Any non-zero exit code is a + blocking failure. +4. After compile succeeds, use HTML output to inspect rendered text and SVG + output plus Playwright MCP for visual checks when available. +5. If `src/tutorial/reference-grammar.typ` changes, regenerate this embedded + lookup section and the traceability JSON with: + `python .codex/skills/typst-grammar-authoring/scripts/generate_grammar_catalog.py` + +## Validation + +Compile validation: + +```powershell +typst compile --root . path\to\document.typ target\typst-grammar-authoring-check\document.pdf +``` + +Text validation through HTML: + +```powershell +typst compile --root . --features html path\to\document.typ target\typst-grammar-authoring-check\document.html +rg "expected text" target\typst-grammar-authoring-check\document.html +``` + +Visual validation through SVG: + +```powershell +typst compile --root . path\to\document.typ target\typst-grammar-authoring-check\document.svg +typst compile --root . path\to\document.typ target\typst-grammar-authoring-check\document-{0p}.svg +``` + +Playwright inspection: + +- Use Playwright only after SVG generation succeeds. +- Open the SVG directly if the MCP server supports local files. +- Otherwise use a tiny local HTML wrapper that embeds the SVG, then capture a + screenshot and inspect layout, spacing, numbering, line breaks, and emphasis. + +## Guardrails + +- Keep all skill-authored prose and instructions in English. +- Canonical code examples may retain non-English literals from the source + tutorial. +- Do not treat Tinymist or editor diagnostics as the source of truth. +- Use `references/grammar-catalog.json` only when you need exact source + mapping; keep it out of active context by default. +- Use `{p}` or `{0p}` in multi-page SVG output paths. +- Treat HTML export as a validation aid, not a production contract. + +## Grammar Lookup + +This section is generated from `src/tutorial/reference-grammar.typ` and is kept +compact on purpose. Verbose source metadata stays in +`references/grammar-catalog.json` instead of the active skill body. + + +### Base Elements + +- `paragraph`: `writing-markup` +- `heading`: `= Heading`; `== Heading` +- `strong`: `*Strong*` +- `emph`: `_emphasis_`; `*_emphasis_*` +- `list`: + +~~~typ ++ List 1 ++ List 2 +~~~ + +- `continue-list`: + +~~~typ +4. List 1 ++ List 2 +~~~ + +- `emum`: + +~~~typ +- Enum 1 +- Enum 2 +~~~ + +- `mix-list-emum`: + +~~~typ +- Enum 1 + + Item 1 +- Enum 2 +~~~ + +- `raw`: + +~~~typ +`code` +~~~ + +- `long-raw`: + +~~~typ +``` code``` +~~~ + +- `lang-raw`: + +~~~typ +```rs trait World``` +~~~ + +- `blocky-raw`: + +~~~typ +```typ += Heading +``` +~~~ + +- `image`: `#image("/assets/files/香風とうふ店.jpg", width: 50pt)` +- `image-stretch`: `#image("/assets/files/香風とうふ店.jpg", width: 50pt, height: 50pt, fit: "stretch")` +- `image-inline`: `在一段话中插入一个#box(baseline: 0.15em, image("/assets/files/info-icon.svg", width: 1em))图片。` +- `figure`: + +~~~typ +#figure(```typ +#image("/assets/files/香風とうふ店.jpg") +```, caption: [用于加载香風とうふ店送外卖的宝贵影像的代码]) +~~~ + +- `link`: `#link("https://zh.wikipedia.org")[维基百科]` +- `http-link`: `https://zh.wikipedia.org` +- `internal-link`: + +~~~typ +== 某个标题 +#link()[链接到某个标题] +~~~ + +- `table`: `#table(columns: 2, [111], [2], [3])` +- `table-align`: `#table(columns: 2, align: center, [111], [2], [3])` +- `inline-math`: `$sum_x$` +- `display-math`: `$ sum_x $` +- `escape-sequences`: `>\_<` +- `unicode-escape-sequences`: `\u{9999}` +- `newline-by-space`: `A \ B` +- `newline`: + +~~~typ +A \ +B +~~~ + +- `shorthand`: `北京--上海` +- `shorthand-space`: `A~B` +- `inline-comment`: `// 行内注释` +- `cross-line-comment`: + +~~~typ +/* 行间注释 + */ +~~~ + +- `box`: `在一段话中插入一个#box(baseline: 0.15em, image("/assets/files/info-icon.svg", width: 1em))图片。` +### Text Styling + +- `highlight`: `#highlight[高亮一段内容]` +- `underline`: `#underline[Language]` +- `underline-evade`: + +~~~typ +#underline( + evade: false)[ጿኈቼዽ] +~~~ + +- `overline`: `#overline[ጿኈቼዽ]` +- `strike`: `#strike[ጿኈቼዽ]` +- `subscript`: `威严满满#sub[抱头蹲防]` +- `superscript`: `香風とうふ店#super[TM]` +- `text-size`: `#text(size: 24pt)[一斤鸭梨]` +- `text-fill`: `#text(fill: blue)[蓝色鸭梨]` +- `text-font`: `#text(font: "Microsoft YaHei")[板正鸭梨]` +### Script Declarations + +- `enter-script`: `#1` +- `code-block`: `#{"a"; "b"}` +- `content-block`: `#[内容块]` +- `none-literal`: `#none` +- `false-literal`: `#false` +- `true-literal`: `#true` +- `integer-literal`: `#(-1), #(0), #(1)` +- `n-adecimal-literal`: `#(-0xdeadbeef), #(-0o644), #(-0b1001)` +- `float-literal`: `#(0.001), #(.1), #(2.)` +- `exp-repr-float`: `#(1e2), #(1.926e3), #(-1e-3)` +- `string-literal`: `#"Hello world!!"` +- `str-escape-sequences`: `#"\""` +- `str-unicode-escape-sequences`: `#"\u{9999}"` +- `array-literal`: `#(1, "OvO", [一段内容])` +- `dict-literal`: `#(neko-mimi: 2, "utterance": "喵喵喵")` +- `empty-array`: `#()` +- `empty-dict`: `#(:)` +- `paren-empty-array`: `#(())` +- `single-member-array`: `#(1,)` +- `var-decl`: `#let x = 1` +- `func-decl`: `#let f(x) = x * 2` +- `closure`: `#let f = (x, y) => x + y` +- `named-param`: `#let g(named: none) = named` +- `variadic-param`: `#let g(..args) = args.pos().join([、])` +- `destruct-array`: `#let (one, hello-world) = (1, "Hello, World")` +- `destruct-array-eliminate`: `#let (_, second, ..) = (1, "Hello, World", []); #second` +- `destruct-dict`: `#let (neko-mimi: mimi) = (neko-mimi: 2); #mimi` +- `array-remapping`: + +~~~typ +#let (a, b, c) = (1, 2, 3) +#let (b, c, a) = (a, b, c) +#a, #b, #c +~~~ + +- `array-swap`: + +~~~typ +#let (a, b) = (1, 2) +#((a, b) = (b, a)) +#a, #b +~~~ + +- `placeholder`: + +~~~typ +#let last-two(t) = { + let _ = t.pop() + t.pop() +} +#last-two((1, 2, 3, 4)) +~~~ + +### Script Statements + +- `if`: + +~~~typ +#if true { 1 }, +#if false { 1 } else { 0 } +~~~ + +- `if-if`: + +~~~typ +#if false { 0 } else if true { 1 }, +#if false { 2 } else if false { 1 } else { 0 } +~~~ + +- `while`: + +~~~typ +#{ + let i = 0; + while i < 10 { + (i * 2, ) + i += 1; + } +} +~~~ + +- `for`: + +~~~typ +#for i in range(10) { + (i * 2, ) +} +~~~ + +- `for-destruct`: `#for (特色, 这个) in (neko-mimi: 2) [猫猫的 #特色 是 #这个\ ]` +- `break`: `#for i in range(10) { (i, ); (i + 1926, ); break }` +- `continue`: + +~~~typ +#for i in range(10) { + if calc.even(i) { continue } + (i, ) +} +~~~ + +- `return`: + +~~~typ +#let never(..args) = return +#type(never(1, 2)) +~~~ + +- `include`: `#include "other-file.typ"` +### Script Styling + +- `set`: + +~~~typ +#set text(size: 24pt) +四斤鸭梨 +~~~ + +- `scope`: + +~~~typ +两只#[兔#set text(fill: rgb("#ffd1dc").darken(15%)) + #[兔白#set text(fill: orange) + 又白],真可爱 +] +~~~ + +- `set-if`: + +~~~typ +#let is-dark-theme = true +#set rect(fill: black) if is-dark-theme +#set text(fill: white) if is-dark-theme +#rect([wink!]) +~~~ + +- `show-set`: + +~~~typ +#show: set text(fill: blue) +wink! +~~~ + +- `show`: + +~~~typ +#show raw: it => it.lines.at(1) +获取代码片段第二行内容:```typ +#{ +set text(fill: true) +} +``` +~~~ + +- `text-selector`: + +~~~typ +#show "cpp": strong(emph(box("C++"))) +在古代,cpp是一门常用语言。 +~~~ + +- `regex-selector`: + +~~~typ +#show regex("[”。]+"): it => { + set text(font: "KaiTi") + highlight(it, fill: yellow) +} +“无名,万物之始也;有名,万物之母也。” +~~~ + +- `label-selector`: + +~~~typ +#show <一整段话>: set text(fill: blue) +#[$lambda$语言是世界上最好的语言。] <一整段话> + +另一段话。 +~~~ + +- `selector-exp`: + +~~~typ +#show heading.where(level: 2): set text(fill: blue) += 一级标题 +== 二级标题 +~~~ + +- `here`: `#context here().position()` +- `here-calc`: `#context [ 页码是偶数:#calc.even(here().page()) ]` +- `query`: `#context query().at(0).body` +- `state`: `#state("my-state", 1)` +### Script Expressions + +- `func-call`: `#calc.pow(4, 3)` +- `content-param`: `#emph[emphasis]` +- `member-exp`: + +~~~typ +#`OvO`.text +~~~ + +- `method-exp`: `#"Hello World".split(" ")` +- `dict-member-exp`: + +~~~typ +#let cat = (neko-mimi: 2) +#cat.neko-mimi +~~~ + +- `content-member-exp`: + +~~~typ +#`OvO`.text +~~~ + +- `repr`: `#repr[ 一段文本 ]` +- `type`: `#type[一段文本]` +- `eval`: `#type(eval("1"))` +- `eval-markup-mode`: `#eval("== 一个标题", mode: "markup")` +- `array-in`: + +~~~typ +#let pol = (1, "OvO", []) +#(1 in pol) +~~~ + +- `array-not-in`: + +~~~typ +#let pol = (1, "OvO", []) +#([另一段内容] not in pol) +~~~ + +- `dict-in`: + +~~~typ +#let cat = (neko-mimi: 2) +#("neko-mimi" in cat) +~~~ + +- `logical-cmp-exp`: + +~~~typ +#(1 < 0), #(1 >= 2), +#(1 == 2), #(1 != 2) +~~~ + +- `logical-calc-exp`: `#(not false), #(false or true), #(true and false)` +- `plus-exp`: `#(+1), #(+0), #(1), #(++1)` +- `minus-exp`: + +~~~typ +#(-1), #(-0), #(--1), +#(-+-1) +~~~ + +- `arith-exp`: + +~~~typ +#(1 + 1), #(1 + -1), +#(1 - 1), #(1 - -1) +~~~ + +- `assign-exp`: `#let a = 1; #repr(a = 10), #a, #repr(a += 2), #a` +- `string-concat-exp`: `#("a" + "b")` +- `string-mul-exp`: `#("a" * 4), #(4 * "ab")` +- `string-cmp-exp`: `#("a" == "b"), #("a" != "b"), #("a" < "ab"), #("a" >= "a")` +- `int-to-float`: `#float(1), #(type(float(1)))` +- `bool-to-int`: `#int(true), #(type(int(true)))` +- `float-to-int`: `#int(1), #(type(int(1)))` +- `dec-str-to-int`: `#int("1"), #(type(int("1")))` +- `nadec-str-to-int`: + +~~~typ +#let safe-to-int(x) = { + let res = eval(x) + assert(type(res) == int, message: "should be integer") + res +} +#safe-to-int("0xf"), #(type(safe-to-int("0xf"))) \ +#safe-to-int("0o755"), #(type(safe-to-int("0o755"))) \ +#safe-to-int("0b1011"), #(type(safe-to-int("0b1011"))) \ +~~~ + +- `num-to-str`: + +~~~typ +#repr(str(1)), +#repr(str(.5)) +~~~ + +- `int-to-nadec-str`: `#str(501, base:16), #str(0xdeadbeef, base:36)` +- `bool-to-str`: `#repr(false)` +- `int-to-bool`: + +~~~typ +#let to-bool(x) = x != 0 +#repr(to-bool(0)), +#repr(to-bool(1)) +~~~ + diff --git a/.codex/skills/typst-grammar-authoring/agents/openai.yaml b/.codex/skills/typst-grammar-authoring/agents/openai.yaml new file mode 100644 index 0000000..d756060 --- /dev/null +++ b/.codex/skills/typst-grammar-authoring/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Typst Grammar Authoring" + short_description: "Author Typst docs from canonical grammar examples" + default_prompt: "Use $typst-grammar-authoring to draft or validate a Typst document from the tutorial's canonical grammar examples." diff --git a/.codex/skills/typst-grammar-authoring/references/grammar-catalog.json b/.codex/skills/typst-grammar-authoring/references/grammar-catalog.json new file mode 100644 index 0000000..ccb6bf9 --- /dev/null +++ b/.codex/skills/typst-grammar-authoring/references/grammar-catalog.json @@ -0,0 +1,2220 @@ +{ + "source_path": "src/tutorial/reference-grammar.typ", + "category_count": 6, + "entry_count": 127, + "categories": [ + { + "id": "base-elements", + "english_heading": "Base Elements", + "source_heading": "基本元素", + "source_anchor": "grammar-table:base-elements", + "heading_line": 30, + "entries": [ + { + "lookup_key": "paragraph", + "source_label": "段落", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-paragraph" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 33, + "entry_line_end": 39, + "code_line_start": 37, + "code_line_end": 37 + }, + "code": "writing-markup" + }, + { + "lookup_key": "heading", + "source_label": "标题", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-heading" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 40, + "entry_line_end": 46, + "code_line_start": 44, + "code_line_end": 44 + }, + "code": "= Heading" + }, + { + "lookup_key": "heading", + "source_label": "二级标题", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-heading" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 47, + "entry_line_end": 53, + "code_line_start": 51, + "code_line_end": 51 + }, + "code": "== Heading" + }, + { + "lookup_key": "strong", + "source_label": "着重标记", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-strong" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 54, + "entry_line_end": 60, + "code_line_start": 58, + "code_line_end": 58 + }, + "code": "*Strong*" + }, + { + "lookup_key": "emph", + "source_label": "强调标记", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-emph" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 61, + "entry_line_end": 67, + "code_line_start": 65, + "code_line_end": 65 + }, + "code": "_emphasis_" + }, + { + "lookup_key": "emph", + "source_label": "着重且强调标记", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-emph" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 68, + "entry_line_end": 74, + "code_line_start": 72, + "code_line_end": 72 + }, + "code": "*_emphasis_*" + }, + { + "lookup_key": "list", + "source_label": "有序列表", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-list" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 75, + "entry_line_end": 82, + "code_line_start": 79, + "code_line_end": 80 + }, + "code": "+ List 1\n+ List 2" + }, + { + "lookup_key": "continue-list", + "source_label": "有序列表(重新开始标号)", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-continue-list" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 83, + "entry_line_end": 90, + "code_line_start": 87, + "code_line_end": 88 + }, + "code": "4. List 1\n+ List 2" + }, + { + "lookup_key": "emum", + "source_label": "无序列表", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-emum" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 91, + "entry_line_end": 98, + "code_line_start": 95, + "code_line_end": 96 + }, + "code": "- Enum 1\n- Enum 2" + }, + { + "lookup_key": "mix-list-emum", + "source_label": "交替有序与无序列表", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-mix-list-emum" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 99, + "entry_line_end": 107, + "code_line_start": 103, + "code_line_end": 105 + }, + "code": "- Enum 1\n + Item 1\n- Enum 2" + }, + { + "lookup_key": "raw", + "source_label": "短代码片段", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-raw" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 108, + "entry_line_end": 114, + "code_line_start": 112, + "code_line_end": 112 + }, + "code": "`code`" + }, + { + "lookup_key": "long-raw", + "source_label": "长代码片段", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-long-raw" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 115, + "entry_line_end": 121, + "code_line_start": 119, + "code_line_end": 119 + }, + "code": "``` code```" + }, + { + "lookup_key": "lang-raw", + "source_label": "语法高亮", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-lang-raw" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 122, + "entry_line_end": 128, + "code_line_start": 126, + "code_line_end": 126 + }, + "code": "```rs trait World```" + }, + { + "lookup_key": "blocky-raw", + "source_label": "块代码片段", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-blocky-raw" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 129, + "entry_line_end": 137, + "code_line_start": 133, + "code_line_end": 135 + }, + "code": "```typ\n= Heading\n```" + }, + { + "lookup_key": "image", + "source_label": "图像", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-image" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 138, + "entry_line_end": 144, + "code_line_start": 142, + "code_line_end": 142 + }, + "code": "#image(\"/assets/files/香風とうふ店.jpg\", width: 50pt)" + }, + { + "lookup_key": "image-stretch", + "source_label": "拉伸图像", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-image-stretch" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 145, + "entry_line_end": 151, + "code_line_start": 149, + "code_line_end": 149 + }, + "code": "#image(\"/assets/files/香風とうふ店.jpg\", width: 50pt, height: 50pt, fit: \"stretch\")" + }, + { + "lookup_key": "image-inline", + "source_label": "内联图像(盒子法)", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-image-inline" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 152, + "entry_line_end": 158, + "code_line_start": 156, + "code_line_end": 156 + }, + "code": "在一段话中插入一个#box(baseline: 0.15em, image(\"/assets/files/info-icon.svg\", width: 1em))图片。" + }, + { + "lookup_key": "figure", + "source_label": "图像标题", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-figure" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 159, + "entry_line_end": 167, + "code_line_start": 163, + "code_line_end": 165 + }, + "code": "#figure(```typ\n#image(\"/assets/files/香風とうふ店.jpg\")\n```, caption: [用于加载香風とうふ店送外卖的宝贵影像的代码])" + }, + { + "lookup_key": "link", + "source_label": "链接", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-link" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 168, + "entry_line_end": 174, + "code_line_start": 172, + "code_line_end": 172 + }, + "code": "#link(\"https://zh.wikipedia.org\")[维基百科]" + }, + { + "lookup_key": "http-link", + "source_label": "HTTP(S)链接", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-http-link" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 175, + "entry_line_end": 181, + "code_line_start": 179, + "code_line_end": 179 + }, + "code": "https://zh.wikipedia.org" + }, + { + "lookup_key": "internal-link", + "source_label": "内部链接", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-internal-link" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 182, + "entry_line_end": 189, + "code_line_start": 186, + "code_line_end": 187 + }, + "code": "== 某个标题 \n#link()[链接到某个标题]" + }, + { + "lookup_key": "table", + "source_label": "表格", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-table" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 190, + "entry_line_end": 196, + "code_line_start": 194, + "code_line_end": 194 + }, + "code": "#table(columns: 2, [111], [2], [3])" + }, + { + "lookup_key": "table-align", + "source_label": "对齐表格", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-table-align" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 197, + "entry_line_end": 203, + "code_line_start": 201, + "code_line_end": 201 + }, + "code": "#table(columns: 2, align: center, [111], [2], [3])" + }, + { + "lookup_key": "inline-math", + "source_label": "行内数学公式", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-inline-math" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 204, + "entry_line_end": 210, + "code_line_start": 208, + "code_line_end": 208 + }, + "code": "$sum_x$" + }, + { + "lookup_key": "display-math", + "source_label": "行间数学公式", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-display-math" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 211, + "entry_line_end": 217, + "code_line_start": 215, + "code_line_end": 215 + }, + "code": "$ sum_x $" + }, + { + "lookup_key": "escape-sequences", + "source_label": "标记转义序列", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-escape-sequences" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 218, + "entry_line_end": 224, + "code_line_start": 222, + "code_line_end": 222 + }, + "code": ">\\_<" + }, + { + "lookup_key": "unicode-escape-sequences", + "source_label": "标记的Unicode转义序列", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-unicode-escape-sequences" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 225, + "entry_line_end": 231, + "code_line_start": 229, + "code_line_end": 229 + }, + "code": "\\u{9999}" + }, + { + "lookup_key": "newline-by-space", + "source_label": "换行符(转义序列)", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-newline-by-space" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 232, + "entry_line_end": 238, + "code_line_start": 236, + "code_line_end": 236 + }, + "code": "A \\ B" + }, + { + "lookup_key": "newline", + "source_label": "换行符情形二", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-newline" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 239, + "entry_line_end": 246, + "code_line_start": 243, + "code_line_end": 244 + }, + "code": "A \\\nB" + }, + { + "lookup_key": "shorthand", + "source_label": "速记符", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-shorthand" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 247, + "entry_line_end": 253, + "code_line_start": 251, + "code_line_end": 251 + }, + "code": "北京--上海" + }, + { + "lookup_key": "shorthand-space", + "source_label": "空格(速记符)", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-shorthand-space" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 254, + "entry_line_end": 260, + "code_line_start": 258, + "code_line_end": 258 + }, + "code": "A~B" + }, + { + "lookup_key": "inline-comment", + "source_label": "行内注释", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-inline-comment" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 261, + "entry_line_end": 267, + "code_line_start": 265, + "code_line_end": 265 + }, + "code": "// 行内注释" + }, + { + "lookup_key": "cross-line-comment", + "source_label": "行间注释", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-cross-line-comment" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 268, + "entry_line_end": 275, + "code_line_start": 272, + "code_line_end": 273 + }, + "code": "/* 行间注释\n */" + }, + { + "lookup_key": "box", + "source_label": "行内盒子", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-box" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 276, + "entry_line_end": 282, + "code_line_start": 280, + "code_line_end": 280 + }, + "code": "在一段话中插入一个#box(baseline: 0.15em, image(\"/assets/files/info-icon.svg\", width: 1em))图片。" + } + ] + }, + { + "id": "text-styling", + "english_heading": "Text Styling", + "source_heading": "修饰文本", + "source_anchor": "grammar-table:text", + "heading_line": 285, + "entries": [ + { + "lookup_key": "highlight", + "source_label": "背景高亮", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-highlight" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 288, + "entry_line_end": 294, + "code_line_start": 292, + "code_line_end": 292 + }, + "code": "#highlight[高亮一段内容]" + }, + { + "lookup_key": "underline", + "source_label": "下划线", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-underline" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 295, + "entry_line_end": 301, + "code_line_start": 299, + "code_line_end": 299 + }, + "code": "#underline[Language]" + }, + { + "lookup_key": "underline-evade", + "source_label": "无驱逐效果的下划线", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-underline-evade" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 302, + "entry_line_end": 309, + "code_line_start": 306, + "code_line_end": 307 + }, + "code": "#underline(\n evade: false)[ጿኈቼዽ]" + }, + { + "lookup_key": "overline", + "source_label": "上划线", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-overline" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 310, + "entry_line_end": 316, + "code_line_start": 314, + "code_line_end": 314 + }, + "code": "#overline[ጿኈቼዽ]" + }, + { + "lookup_key": "strike", + "source_label": "中划线(删除线)", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-strike" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 317, + "entry_line_end": 323, + "code_line_start": 321, + "code_line_end": 321 + }, + "code": "#strike[ጿኈቼዽ]" + }, + { + "lookup_key": "subscript", + "source_label": "下标", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-subscript" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 324, + "entry_line_end": 330, + "code_line_start": 328, + "code_line_end": 328 + }, + "code": "威严满满#sub[抱头蹲防]" + }, + { + "lookup_key": "superscript", + "source_label": "上标", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-superscript" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 331, + "entry_line_end": 337, + "code_line_start": 335, + "code_line_end": 335 + }, + "code": "香風とうふ店#super[TM]" + }, + { + "lookup_key": "text-size", + "source_label": "设置文本大小", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-text-size" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 338, + "entry_line_end": 344, + "code_line_start": 342, + "code_line_end": 342 + }, + "code": "#text(size: 24pt)[一斤鸭梨]" + }, + { + "lookup_key": "text-fill", + "source_label": "设置文本颜色", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-text-fill" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 345, + "entry_line_end": 351, + "code_line_start": 349, + "code_line_end": 349 + }, + "code": "#text(fill: blue)[蓝色鸭梨]" + }, + { + "lookup_key": "text-font", + "source_label": "设置字体", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-text-font" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 352, + "entry_line_end": 358, + "code_line_start": 356, + "code_line_end": 356 + }, + "code": "#text(font: \"Microsoft YaHei\")[板正鸭梨]" + } + ] + }, + { + "id": "script-declarations", + "english_heading": "Script Declarations", + "source_heading": "脚本声明", + "source_anchor": "grammar-table:decl", + "heading_line": 361, + "entries": [ + { + "lookup_key": "enter-script", + "source_label": "进入脚本模式", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-enter-script" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 364, + "entry_line_end": 370, + "code_line_start": 368, + "code_line_end": 368 + }, + "code": "#1" + }, + { + "lookup_key": "code-block", + "source_label": "代码块", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-code-block" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 371, + "entry_line_end": 377, + "code_line_start": 375, + "code_line_end": 375 + }, + "code": "#{\"a\"; \"b\"}" + }, + { + "lookup_key": "content-block", + "source_label": "内容块", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-content-block" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 378, + "entry_line_end": 384, + "code_line_start": 382, + "code_line_end": 382 + }, + "code": "#[内容块]" + }, + { + "lookup_key": "none-literal", + "source_label": "空字面量", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-none-literal" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 385, + "entry_line_end": 391, + "code_line_start": 389, + "code_line_end": 389 + }, + "code": "#none" + }, + { + "lookup_key": "false-literal", + "source_label": "假(布尔值)", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-false-literal" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 392, + "entry_line_end": 398, + "code_line_start": 396, + "code_line_end": 396 + }, + "code": "#false" + }, + { + "lookup_key": "true-literal", + "source_label": "真(布尔值)", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-true-literal" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 399, + "entry_line_end": 405, + "code_line_start": 403, + "code_line_end": 403 + }, + "code": "#true" + }, + { + "lookup_key": "integer-literal", + "source_label": "整数字面量", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-integer-literal" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 406, + "entry_line_end": 412, + "code_line_start": 410, + "code_line_end": 410 + }, + "code": "#(-1), #(0), #(1)" + }, + { + "lookup_key": "n-adecimal-literal", + "source_label": "进制数字面量", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-n-adecimal-literal" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 413, + "entry_line_end": 419, + "code_line_start": 417, + "code_line_end": 417 + }, + "code": "#(-0xdeadbeef), #(-0o644), #(-0b1001)" + }, + { + "lookup_key": "float-literal", + "source_label": "浮点数字面量", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-float-literal" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 420, + "entry_line_end": 426, + "code_line_start": 424, + "code_line_end": 424 + }, + "code": "#(0.001), #(.1), #(2.)" + }, + { + "lookup_key": "exp-repr-float", + "source_label": "指数表示法", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-exp-repr-float" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 427, + "entry_line_end": 433, + "code_line_start": 431, + "code_line_end": 431 + }, + "code": "#(1e2), #(1.926e3), #(-1e-3)" + }, + { + "lookup_key": "string-literal", + "source_label": "字符串字面量", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-string-literal" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 434, + "entry_line_end": 440, + "code_line_start": 438, + "code_line_end": 438 + }, + "code": "#\"Hello world!!\"" + }, + { + "lookup_key": "str-escape-sequences", + "source_label": "字符串转义序列", + "reference": { + "expression": "refs.scripting-base.with(reference: )", + "module": "scripting-base", + "anchor": "grammar-str-escape-sequences" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 441, + "entry_line_end": 447, + "code_line_start": 445, + "code_line_end": 445 + }, + "code": "#\"\\\"\"" + }, + { + "lookup_key": "str-unicode-escape-sequences", + "source_label": "字符串的Unicode转义序列", + "reference": { + "expression": "refs.scripting-base.with(reference: )", + "module": "scripting-base", + "anchor": "grammar-str-unicode-escape-sequences" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 448, + "entry_line_end": 454, + "code_line_start": 452, + "code_line_end": 452 + }, + "code": "#\"\\u{9999}\"" + }, + { + "lookup_key": "array-literal", + "source_label": "数组字面量", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-array-literal" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 455, + "entry_line_end": 461, + "code_line_start": 459, + "code_line_end": 459 + }, + "code": "#(1, \"OvO\", [一段内容])" + }, + { + "lookup_key": "dict-literal", + "source_label": "字典字面量", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-dict-literal" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 462, + "entry_line_end": 468, + "code_line_start": 466, + "code_line_end": 466 + }, + "code": "#(neko-mimi: 2, \"utterance\": \"喵喵喵\")" + }, + { + "lookup_key": "empty-array", + "source_label": "空数组", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-empty-array" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 469, + "entry_line_end": 475, + "code_line_start": 473, + "code_line_end": 473 + }, + "code": "#()" + }, + { + "lookup_key": "empty-dict", + "source_label": "空字典", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-empty-dict" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 476, + "entry_line_end": 482, + "code_line_start": 480, + "code_line_end": 480 + }, + "code": "#(:)" + }, + { + "lookup_key": "paren-empty-array", + "source_label": "被括号包裹的空数组", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-paren-empty-array" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 483, + "entry_line_end": 489, + "code_line_start": 487, + "code_line_end": 487 + }, + "code": "#(())" + }, + { + "lookup_key": "single-member-array", + "source_label": "含有一个元素的数组", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-single-member-array" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 490, + "entry_line_end": 496, + "code_line_start": 494, + "code_line_end": 494 + }, + "code": "#(1,)" + }, + { + "lookup_key": "var-decl", + "source_label": "变量声明", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-var-decl" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 497, + "entry_line_end": 503, + "code_line_start": 501, + "code_line_end": 501 + }, + "code": "#let x = 1" + }, + { + "lookup_key": "func-decl", + "source_label": "函数声明", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-func-decl" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 504, + "entry_line_end": 510, + "code_line_start": 508, + "code_line_end": 508 + }, + "code": "#let f(x) = x * 2" + }, + { + "lookup_key": "closure", + "source_label": "函数闭包", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-closure" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 511, + "entry_line_end": 517, + "code_line_start": 515, + "code_line_end": 515 + }, + "code": "#let f = (x, y) => x + y" + }, + { + "lookup_key": "named-param", + "source_label": "具名参数声明", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-named-param" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 518, + "entry_line_end": 524, + "code_line_start": 522, + "code_line_end": 522 + }, + "code": "#let g(named: none) = named" + }, + { + "lookup_key": "variadic-param", + "source_label": "含变参函数", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-variadic-param" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 525, + "entry_line_end": 531, + "code_line_start": 529, + "code_line_end": 529 + }, + "code": "#let g(..args) = args.pos().join([、])" + }, + { + "lookup_key": "destruct-array", + "source_label": "数组解构赋值", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-destruct-array" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 532, + "entry_line_end": 538, + "code_line_start": 536, + "code_line_end": 536 + }, + "code": "#let (one, hello-world) = (1, \"Hello, World\")" + }, + { + "lookup_key": "destruct-array-eliminate", + "source_label": "数组解构赋值情形二", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-destruct-array-eliminate" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 539, + "entry_line_end": 545, + "code_line_start": 543, + "code_line_end": 543 + }, + "code": "#let (_, second, ..) = (1, \"Hello, World\", []); #second" + }, + { + "lookup_key": "destruct-dict", + "source_label": "字典解构赋值", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-destruct-dict" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 546, + "entry_line_end": 552, + "code_line_start": 550, + "code_line_end": 550 + }, + "code": "#let (neko-mimi: mimi) = (neko-mimi: 2); #mimi" + }, + { + "lookup_key": "array-remapping", + "source_label": "数组内容重映射", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-array-remapping" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 553, + "entry_line_end": 561, + "code_line_start": 557, + "code_line_end": 559 + }, + "code": "#let (a, b, c) = (1, 2, 3)\n#let (b, c, a) = (a, b, c)\n#a, #b, #c" + }, + { + "lookup_key": "array-swap", + "source_label": "数组内容交换", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-array-swap" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 562, + "entry_line_end": 570, + "code_line_start": 566, + "code_line_end": 568 + }, + "code": "#let (a, b) = (1, 2)\n#((a, b) = (b, a))\n#a, #b" + }, + { + "lookup_key": "placeholder", + "source_label": "占位符(`let _ = ..`)", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-placeholder" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 571, + "entry_line_end": 581, + "code_line_start": 575, + "code_line_end": 579 + }, + "code": "#let last-two(t) = {\n let _ = t.pop()\n t.pop()\n}\n#last-two((1, 2, 3, 4))" + } + ] + }, + { + "id": "script-statements", + "english_heading": "Script Statements", + "source_heading": "脚本语句", + "source_anchor": null, + "heading_line": 584, + "entries": [ + { + "lookup_key": "if", + "source_label": "`if`语句", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-if" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 587, + "entry_line_end": 594, + "code_line_start": 591, + "code_line_end": 592 + }, + "code": "#if true { 1 },\n#if false { 1 } else { 0 }" + }, + { + "lookup_key": "if-if", + "source_label": "串联`if`语句", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-if-if" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 595, + "entry_line_end": 602, + "code_line_start": 599, + "code_line_end": 600 + }, + "code": "#if false { 0 } else if true { 1 },\n#if false { 2 } else if false { 1 } else { 0 }" + }, + { + "lookup_key": "while", + "source_label": "`while`语句", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-while" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 603, + "entry_line_end": 615, + "code_line_start": 607, + "code_line_end": 613 + }, + "code": "#{\n let i = 0;\n while i < 10 {\n (i * 2, )\n i += 1;\n }\n}" + }, + { + "lookup_key": "for", + "source_label": "`for`语句", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-for" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 616, + "entry_line_end": 624, + "code_line_start": 620, + "code_line_end": 622 + }, + "code": "#for i in range(10) {\n (i * 2, )\n}" + }, + { + "lookup_key": "for-destruct", + "source_label": "`for`语句解构赋值", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-for-destruct" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 625, + "entry_line_end": 631, + "code_line_start": 629, + "code_line_end": 629 + }, + "code": "#for (特色, 这个) in (neko-mimi: 2) [猫猫的 #特色 是 #这个\\ ]" + }, + { + "lookup_key": "break", + "source_label": "`break`语句", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-break" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 632, + "entry_line_end": 638, + "code_line_start": 636, + "code_line_end": 636 + }, + "code": "#for i in range(10) { (i, ); (i + 1926, ); break }" + }, + { + "lookup_key": "continue", + "source_label": "`continue`语句", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-continue" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 639, + "entry_line_end": 648, + "code_line_start": 643, + "code_line_end": 646 + }, + "code": "#for i in range(10) {\n if calc.even(i) { continue }\n (i, )\n}" + }, + { + "lookup_key": "return", + "source_label": "`return`语句", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-return" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 649, + "entry_line_end": 656, + "code_line_start": 653, + "code_line_end": 654 + }, + "code": "#let never(..args) = return\n#type(never(1, 2))" + }, + { + "lookup_key": "include", + "source_label": "`include`语句", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-include" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 657, + "entry_line_end": 663, + "code_line_start": 661, + "code_line_end": 661 + }, + "code": "#include \"other-file.typ\"" + } + ] + }, + { + "id": "script-styling", + "english_heading": "Script Styling", + "source_heading": "脚本样式", + "source_anchor": null, + "heading_line": 666, + "entries": [ + { + "lookup_key": "set", + "source_label": "`set`语句", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-set" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 669, + "entry_line_end": 676, + "code_line_start": 673, + "code_line_end": 674 + }, + "code": "#set text(size: 24pt)\n四斤鸭梨" + }, + { + "lookup_key": "scope", + "source_label": "作用域", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-scope" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 677, + "entry_line_end": 686, + "code_line_start": 681, + "code_line_end": 684 + }, + "code": "两只#[兔#set text(fill: rgb(\"#ffd1dc\").darken(15%))\n #[兔白#set text(fill: orange)\n 又白],真可爱\n]" + }, + { + "lookup_key": "set-if", + "source_label": "`set if`语句", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-set-if" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 687, + "entry_line_end": 696, + "code_line_start": 691, + "code_line_end": 694 + }, + "code": "#let is-dark-theme = true\n#set rect(fill: black) if is-dark-theme\n#set text(fill: white) if is-dark-theme\n#rect([wink!])" + }, + { + "lookup_key": "show-set", + "source_label": "`show set`语句", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-show-set" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 697, + "entry_line_end": 704, + "code_line_start": 701, + "code_line_end": 702 + }, + "code": "#show: set text(fill: blue)\nwink!" + }, + { + "lookup_key": "show", + "source_label": "`show`语句", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-show" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 705, + "entry_line_end": 716, + "code_line_start": 709, + "code_line_end": 714 + }, + "code": "#show raw: it => it.lines.at(1)\n获取代码片段第二行内容:```typ\n#{\nset text(fill: true)\n}\n```" + }, + { + "lookup_key": "text-selector", + "source_label": "文本选择器", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-text-selector" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 717, + "entry_line_end": 724, + "code_line_start": 721, + "code_line_end": 722 + }, + "code": "#show \"cpp\": strong(emph(box(\"C++\")))\n在古代,cpp是一门常用语言。" + }, + { + "lookup_key": "regex-selector", + "source_label": "正则文本选择器", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-regex-selector" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 725, + "entry_line_end": 735, + "code_line_start": 729, + "code_line_end": 733 + }, + "code": "#show regex(\"[”。]+\"): it => {\n set text(font: \"KaiTi\")\n highlight(it, fill: yellow)\n}\n“无名,万物之始也;有名,万物之母也。”" + }, + { + "lookup_key": "label-selector", + "source_label": "标签选择器", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-label-selector" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 736, + "entry_line_end": 745, + "code_line_start": 740, + "code_line_end": 743 + }, + "code": "#show <一整段话>: set text(fill: blue)\n#[$lambda$语言是世界上最好的语言。] <一整段话>\n\n另一段话。" + }, + { + "lookup_key": "selector-exp", + "source_label": "选择器表达式", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-selector-exp" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 746, + "entry_line_end": 754, + "code_line_start": 750, + "code_line_end": 752 + }, + "code": "#show heading.where(level: 2): set text(fill: blue)\n= 一级标题\n== 二级标题" + }, + { + "lookup_key": "here", + "source_label": "获取位置", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-here" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 755, + "entry_line_end": 761, + "code_line_start": 759, + "code_line_end": 759 + }, + "code": "#context here().position()" + }, + { + "lookup_key": "here-calc", + "source_label": "检测当前页面是否为偶数页(位置相关计算)", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-here-calc" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 762, + "entry_line_end": 768, + "code_line_start": 766, + "code_line_end": 766 + }, + "code": "#context [ 页码是偶数:#calc.even(here().page()) ]" + }, + { + "lookup_key": "query", + "source_label": "查询文档内容", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-query" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 769, + "entry_line_end": 775, + "code_line_start": 773, + "code_line_end": 773 + }, + "code": "#context query().at(0).body" + }, + { + "lookup_key": "state", + "source_label": "声明全局状态", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-state" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 776, + "entry_line_end": 782, + "code_line_start": 780, + "code_line_end": 780 + }, + "code": "#state(\"my-state\", 1)" + } + ] + }, + { + "id": "script-expressions", + "english_heading": "Script Expressions", + "source_heading": "脚本表达式", + "source_anchor": null, + "heading_line": 785, + "entries": [ + { + "lookup_key": "func-call", + "source_label": "函数调用", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-func-call" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 788, + "entry_line_end": 794, + "code_line_start": 792, + "code_line_end": 792 + }, + "code": "#calc.pow(4, 3)" + }, + { + "lookup_key": "content-param", + "source_label": "函数调用传递内容参数", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-content-param" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 795, + "entry_line_end": 801, + "code_line_start": 799, + "code_line_end": 799 + }, + "code": "#emph[emphasis]" + }, + { + "lookup_key": "member-exp", + "source_label": "成员访问", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-member-exp" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 802, + "entry_line_end": 808, + "code_line_start": 806, + "code_line_end": 806 + }, + "code": "#`OvO`.text" + }, + { + "lookup_key": "method-exp", + "source_label": "方法调用", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-method-exp" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 809, + "entry_line_end": 815, + "code_line_start": 813, + "code_line_end": 813 + }, + "code": "#\"Hello World\".split(\" \")" + }, + { + "lookup_key": "dict-member-exp", + "source_label": "字典成员访问", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-dict-member-exp" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 816, + "entry_line_end": 823, + "code_line_start": 820, + "code_line_end": 821 + }, + "code": "#let cat = (neko-mimi: 2)\n#cat.neko-mimi" + }, + { + "lookup_key": "content-member-exp", + "source_label": "内容成员访问", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-content-member-exp" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 824, + "entry_line_end": 830, + "code_line_start": 828, + "code_line_end": 828 + }, + "code": "#`OvO`.text" + }, + { + "lookup_key": "repr", + "source_label": "代码表示的自省函数", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-repr" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 831, + "entry_line_end": 837, + "code_line_start": 835, + "code_line_end": 835 + }, + "code": "#repr[ 一段文本 ]" + }, + { + "lookup_key": "type", + "source_label": "类型的自省函数", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-type" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 838, + "entry_line_end": 844, + "code_line_start": 842, + "code_line_end": 842 + }, + "code": "#type[一段文本]" + }, + { + "lookup_key": "eval", + "source_label": "求值函数", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-eval" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 845, + "entry_line_end": 851, + "code_line_start": 849, + "code_line_end": 849 + }, + "code": "#type(eval(\"1\"))" + }, + { + "lookup_key": "eval-markup-mode", + "source_label": "求值函数(标记模式)", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-eval-markup-mode" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 852, + "entry_line_end": 858, + "code_line_start": 856, + "code_line_end": 856 + }, + "code": "#eval(\"== 一个标题\", mode: \"markup\")" + }, + { + "lookup_key": "array-in", + "source_label": "判断数组内容", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-array-in" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 859, + "entry_line_end": 866, + "code_line_start": 863, + "code_line_end": 864 + }, + "code": "#let pol = (1, \"OvO\", [])\n#(1 in pol)" + }, + { + "lookup_key": "array-not-in", + "source_label": "判断数组内容不在", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-array-not-in" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 867, + "entry_line_end": 874, + "code_line_start": 871, + "code_line_end": 872 + }, + "code": "#let pol = (1, \"OvO\", [])\n#([另一段内容] not in pol)" + }, + { + "lookup_key": "dict-in", + "source_label": "判断字典内容", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-dict-in" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 875, + "entry_line_end": 882, + "code_line_start": 879, + "code_line_end": 880 + }, + "code": "#let cat = (neko-mimi: 2)\n#(\"neko-mimi\" in cat)" + }, + { + "lookup_key": "logical-cmp-exp", + "source_label": "逻辑比较表达式", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-logical-cmp-exp" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 883, + "entry_line_end": 890, + "code_line_start": 887, + "code_line_end": 888 + }, + "code": "#(1 < 0), #(1 >= 2),\n#(1 == 2), #(1 != 2)" + }, + { + "lookup_key": "logical-calc-exp", + "source_label": "逻辑运算表达式", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-logical-calc-exp" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 891, + "entry_line_end": 897, + "code_line_start": 895, + "code_line_end": 895 + }, + "code": "#(not false), #(false or true), #(true and false)" + }, + { + "lookup_key": "plus-exp", + "source_label": "取正运算", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-plus-exp" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 898, + "entry_line_end": 904, + "code_line_start": 902, + "code_line_end": 902 + }, + "code": "#(+1), #(+0), #(1), #(++1)" + }, + { + "lookup_key": "minus-exp", + "source_label": "取负运算", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-minus-exp" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 905, + "entry_line_end": 912, + "code_line_start": 909, + "code_line_end": 910 + }, + "code": "#(-1), #(-0), #(--1),\n#(-+-1)" + }, + { + "lookup_key": "arith-exp", + "source_label": "算术运算表达式", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-arith-exp" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 913, + "entry_line_end": 920, + "code_line_start": 917, + "code_line_end": 918 + }, + "code": "#(1 + 1), #(1 + -1),\n#(1 - 1), #(1 - -1)" + }, + { + "lookup_key": "assign-exp", + "source_label": "赋值表达式", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-assign-exp" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 921, + "entry_line_end": 927, + "code_line_start": 925, + "code_line_end": 925 + }, + "code": "#let a = 1; #repr(a = 10), #a, #repr(a += 2), #a" + }, + { + "lookup_key": "string-concat-exp", + "source_label": "字符串连接", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-string-concat-exp" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 928, + "entry_line_end": 934, + "code_line_start": 932, + "code_line_end": 932 + }, + "code": "#(\"a\" + \"b\")" + }, + { + "lookup_key": "string-mul-exp", + "source_label": "重复连接字符串", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-string-mul-exp" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 935, + "entry_line_end": 941, + "code_line_start": 939, + "code_line_end": 939 + }, + "code": "#(\"a\" * 4), #(4 * \"ab\")" + }, + { + "lookup_key": "string-cmp-exp", + "source_label": "字符串比较", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-string-cmp-exp" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 942, + "entry_line_end": 948, + "code_line_start": 946, + "code_line_end": 946 + }, + "code": "#(\"a\" == \"b\"), #(\"a\" != \"b\"), #(\"a\" < \"ab\"), #(\"a\" >= \"a\")" + }, + { + "lookup_key": "int-to-float", + "source_label": "整数转浮点数", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-int-to-float" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 949, + "entry_line_end": 955, + "code_line_start": 953, + "code_line_end": 953 + }, + "code": "#float(1), #(type(float(1)))" + }, + { + "lookup_key": "bool-to-int", + "source_label": "布尔值转整数", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-bool-to-int" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 956, + "entry_line_end": 962, + "code_line_start": 960, + "code_line_end": 960 + }, + "code": "#int(true), #(type(int(true)))" + }, + { + "lookup_key": "float-to-int", + "source_label": "浮点数转整数", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-float-to-int" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 963, + "entry_line_end": 969, + "code_line_start": 967, + "code_line_end": 967 + }, + "code": "#int(1), #(type(int(1)))" + }, + { + "lookup_key": "dec-str-to-int", + "source_label": "十进制字符串转整数", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-dec-str-to-int" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 970, + "entry_line_end": 976, + "code_line_start": 974, + "code_line_end": 974 + }, + "code": "#int(\"1\"), #(type(int(\"1\")))" + }, + { + "lookup_key": "nadec-str-to-int", + "source_label": "十六进制/八进制/二进制字符串转整数", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-nadec-str-to-int" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 977, + "entry_line_end": 990, + "code_line_start": 981, + "code_line_end": 988 + }, + "code": "#let safe-to-int(x) = {\n let res = eval(x)\n assert(type(res) == int, message: \"should be integer\")\n res\n}\n#safe-to-int(\"0xf\"), #(type(safe-to-int(\"0xf\"))) \\\n#safe-to-int(\"0o755\"), #(type(safe-to-int(\"0o755\"))) \\\n#safe-to-int(\"0b1011\"), #(type(safe-to-int(\"0b1011\"))) \\" + }, + { + "lookup_key": "num-to-str", + "source_label": "数字转字符串", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-num-to-str" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 991, + "entry_line_end": 998, + "code_line_start": 995, + "code_line_end": 996 + }, + "code": "#repr(str(1)),\n#repr(str(.5))" + }, + { + "lookup_key": "int-to-nadec-str", + "source_label": "整数转十六进制字符串", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-int-to-nadec-str" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 999, + "entry_line_end": 1005, + "code_line_start": 1003, + "code_line_end": 1003 + }, + "code": "#str(501, base:16), #str(0xdeadbeef, base:36)" + }, + { + "lookup_key": "bool-to-str", + "source_label": "布尔值转字符串", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-bool-to-str" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 1006, + "entry_line_end": 1012, + "code_line_start": 1010, + "code_line_end": 1010 + }, + "code": "#repr(false)" + }, + { + "lookup_key": "int-to-bool", + "source_label": "数字转布尔值", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-int-to-bool" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 1013, + "entry_line_end": 1021, + "code_line_start": 1017, + "code_line_end": 1019 + }, + "code": "#let to-bool(x) = x != 0\n#repr(to-bool(0)),\n#repr(to-bool(1))" + } + ] + } + ] +} diff --git a/.codex/skills/typst-grammar-authoring/scripts/generate_grammar_catalog.py b/.codex/skills/typst-grammar-authoring/scripts/generate_grammar_catalog.py new file mode 100644 index 0000000..9a7f100 --- /dev/null +++ b/.codex/skills/typst-grammar-authoring/scripts/generate_grammar_catalog.py @@ -0,0 +1,299 @@ +#!/usr/bin/env python3 +"""Generate Typst grammar data and sync the compact lookup into SKILL.md.""" + +from __future__ import annotations + +import argparse +import json +import os +import re +import sys +import textwrap +from collections import OrderedDict +from pathlib import Path + + +CATEGORY_MAP = { + "基本元素": { + "id": "base-elements", + "title": "Base Elements", + }, + "修饰文本": { + "id": "text-styling", + "title": "Text Styling", + }, + "脚本声明": { + "id": "script-declarations", + "title": "Script Declarations", + }, + "脚本语句": { + "id": "script-statements", + "title": "Script Statements", + }, + "脚本样式": { + "id": "script-styling", + "title": "Script Styling", + }, + "脚本表达式": { + "id": "script-expressions", + "title": "Script Expressions", + }, +} + +CATEGORY_PATTERN = re.compile( + r"^==\s+分类:(?P.+?)(?:\s+<(?P[^>]+)>)?\s*$", + re.MULTILINE, +) + +ENTRY_PATTERN = re.compile( + r""" + \(\s*\n + \s*\[(?P.*?)\],\s*\n + \s*(?Prefs\.(?P[\w-]+)\.with\(reference:\s*<(?P[^>]+)>\)),\s*\n + \s*`{3,4}typ\s*\n + (?P.*?) + \n\s*`{3,4},\s*\n + \s*\), + """, + re.DOTALL | re.VERBOSE, +) + +GENERATED_BEGIN = "" +GENERATED_END = "" +INLINE_CODE_LIMIT = 88 + + +def line_number(text: str, offset: int) -> int: + return text.count("\n", 0, offset) + 1 + + +def normalize_code(raw_code: str) -> str: + return textwrap.dedent(raw_code).strip("\n") + + +def lookup_key(reference_anchor: str) -> str: + prefix = "grammar-" + if reference_anchor.startswith(prefix): + return reference_anchor[len(prefix) :] + return reference_anchor + + +def repo_relative(path: Path, repo_root: Path) -> str: + try: + return Path(os.path.relpath(path.resolve(), repo_root.resolve())).as_posix() + except ValueError: + return path.resolve().as_posix() + + +def parse_categories(source_text: str, source_path: Path, repo_root: Path) -> list[dict]: + categories = [] + category_matches = list(CATEGORY_PATTERN.finditer(source_text)) + if not category_matches: + raise ValueError(f"No categories found in {source_path}") + + for index, match in enumerate(category_matches): + section_start = match.end() + section_end = ( + category_matches[index + 1].start() + if index + 1 < len(category_matches) + else len(source_text) + ) + section_text = source_text[section_start:section_end] + source_heading = match.group("source_heading") + english_heading = CATEGORY_MAP.get(source_heading, {}).get("title", source_heading) + category_id = CATEGORY_MAP.get(source_heading, {}).get( + "id", + f"category-{index + 1}", + ) + category = { + "id": category_id, + "english_heading": english_heading, + "source_heading": source_heading, + "source_anchor": match.group("source_anchor"), + "heading_line": line_number(source_text, match.start()), + "entries": [], + } + + for entry_match in ENTRY_PATTERN.finditer(section_text): + absolute_start = section_start + entry_match.start() + absolute_end = section_start + entry_match.end() + code_start = section_start + entry_match.start("code") + code_end = section_start + entry_match.end("code") + entry = { + "lookup_key": lookup_key(entry_match.group("reference_anchor")), + "source_label": entry_match.group("source_label").strip(), + "reference": { + "expression": entry_match.group("reference_expr").strip(), + "module": entry_match.group("reference_module"), + "anchor": entry_match.group("reference_anchor"), + }, + "source": { + "path": repo_relative(source_path, repo_root), + "entry_line_start": line_number(source_text, absolute_start), + "entry_line_end": line_number(source_text, absolute_end), + "code_line_start": line_number(source_text, code_start), + "code_line_end": line_number(source_text, code_end), + }, + "code": normalize_code(entry_match.group("code")), + } + category["entries"].append(entry) + + if not category["entries"]: + raise ValueError( + f"No entries parsed for category '{source_heading}' in {source_path}" + ) + + categories.append(category) + + return categories + + +def build_catalog(source_path: Path, repo_root: Path) -> dict: + source_text = source_path.read_text(encoding="utf-8") + categories = parse_categories(source_text, source_path, repo_root) + return { + "source_path": repo_relative(source_path, repo_root), + "category_count": len(categories), + "entry_count": sum(len(category["entries"]) for category in categories), + "categories": categories, + } + + +def is_inline_safe(code: str) -> bool: + return "\n" not in code and "`" not in code and len(code) <= INLINE_CODE_LIMIT + + +def grouped_entries(entries: list[dict]) -> list[dict]: + groups: OrderedDict[str, list[str]] = OrderedDict() + for entry in entries: + groups.setdefault(entry["lookup_key"], []) + if entry["code"] not in groups[entry["lookup_key"]]: + groups[entry["lookup_key"]].append(entry["code"]) + return [ + { + "lookup_key": lookup_key, + "examples": examples, + } + for lookup_key, examples in groups.items() + ] + + +def render_example_block(code: str) -> list[str]: + return [ + "~~~typ", + code, + "~~~", + ] + + +def render_skill_lookup(catalog: dict) -> str: + lines: list[str] = [] + for category in catalog["categories"]: + lines.extend( + [ + f"### {category['english_heading']}", + "", + ] + ) + for group in grouped_entries(category["entries"]): + inline_examples = [code for code in group["examples"] if is_inline_safe(code)] + if len(inline_examples) == len(group["examples"]): + joined = "; ".join(f"`{code}`" for code in inline_examples) + lines.append(f"- `{group['lookup_key']}`: {joined}") + continue + + lines.append(f"- `{group['lookup_key']}`:") + lines.append("") + for index, code in enumerate(group["examples"]): + lines.extend(render_example_block(code)) + if index != len(group["examples"]) - 1: + lines.append("") + lines.append("") + + while lines and not lines[-1]: + lines.pop() + return "\n".join(lines) + "\n" + + +def update_skill(skill_path: Path, generated_lookup: str) -> None: + skill_text = skill_path.read_text(encoding="utf-8") + pattern = re.compile( + rf"{re.escape(GENERATED_BEGIN)}\n.*?{re.escape(GENERATED_END)}", + re.DOTALL, + ) + replacement = f"{GENERATED_BEGIN}\n{generated_lookup}{GENERATED_END}" + updated_text, count = pattern.subn(lambda _: replacement, skill_text, count=1) + if count != 1: + raise ValueError( + f"Could not find generated section markers in {skill_path}" + ) + skill_path.write_text(updated_text, encoding="utf-8") + + +def write_outputs( + catalog: dict, + output_dir: Path, + skill_path: Path, +) -> None: + output_dir.mkdir(parents=True, exist_ok=True) + json_path = output_dir / "grammar-catalog.json" + json_path.write_text( + json.dumps(catalog, indent=2, ensure_ascii=False) + "\n", + encoding="utf-8", + ) + update_skill(skill_path, render_skill_lookup(catalog)) + print(f"[OK] Wrote {json_path}") + print(f"[OK] Updated {skill_path}") + + +def default_source(script_path: Path) -> Path: + return script_path.parents[4] / "src" / "tutorial" / "reference-grammar.typ" + + +def default_skill_path(script_path: Path) -> Path: + return script_path.parent.parent / "SKILL.md" + + +def main() -> int: + script_path = Path(__file__).resolve() + parser = argparse.ArgumentParser( + description="Generate Typst grammar data from reference-grammar.typ.", + ) + parser.add_argument( + "--source", + type=Path, + default=default_source(script_path), + help="Path to src/tutorial/reference-grammar.typ", + ) + parser.add_argument( + "--output-dir", + type=Path, + default=script_path.parent.parent / "references", + help="Directory for generated reference files", + ) + parser.add_argument( + "--skill-path", + type=Path, + default=default_skill_path(script_path), + help="Path to the SKILL.md file to sync", + ) + args = parser.parse_args() + + try: + repo_root = script_path.parents[4] + source_path = args.source.resolve() + catalog = build_catalog(source_path, repo_root) + write_outputs( + catalog, + args.output_dir.resolve(), + args.skill_path.resolve(), + ) + except Exception as exc: # pragma: no cover - CLI error surface + print(f"[ERROR] {exc}", file=sys.stderr) + return 1 + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/openspec/changes/create-english-typst-grammar-skill/tasks.md b/openspec/changes/create-english-typst-grammar-skill/tasks.md index 92c90e0..7510be8 100644 --- a/openspec/changes/create-english-typst-grammar-skill/tasks.md +++ b/openspec/changes/create-english-typst-grammar-skill/tasks.md @@ -1,22 +1,22 @@ ## 1. Skill setup -- [ ] 1.1 Audit any existing Typst skill or prototype files and choose the final repo-local skill target under `.codex/skills/` -- [ ] 1.2 Create or update the repo-local skill scaffold and English-facing agent metadata for the Typst document authoring skill +- [x] 1.1 Audit any existing Typst skill or prototype files and choose the final repo-local skill target under `.codex/skills/` +- [x] 1.2 Create or update the repo-local skill scaffold and English-facing agent metadata for the Typst document authoring skill ## 2. Grammar reference resources -- [ ] 2.1 Implement or update the extraction flow that reads `src/tutorial/reference-grammar.typ` and produces skill reference material -- [ ] 2.2 Generate an English-facing grammar reference catalog that preserves traceability back to the canonical source examples -- [ ] 2.3 Write the final English-only `SKILL.md` and reference guidance for grammar-first authoring +- [x] 2.1 Implement or update the extraction flow that reads `src/tutorial/reference-grammar.typ` and produces skill reference material +- [x] 2.2 Generate an English-facing grammar reference catalog that preserves traceability back to the canonical source examples +- [x] 2.3 Write the final English-only `SKILL.md` and reference guidance for grammar-first authoring ## 3. Compile-based validation workflow -- [ ] 3.1 Replace any editor-specific validation requirement with a `typst compile`-based grammar validation workflow -- [ ] 3.2 Add documented HTML validation commands and guidance for checking rendered text output -- [ ] 3.3 Add documented SVG generation and Playwright MCP guidance for visual inspection of rendered output +- [x] 3.1 Replace any editor-specific validation requirement with a `typst compile`-based grammar validation workflow +- [x] 3.2 Add documented HTML validation commands and guidance for checking rendered text output +- [x] 3.3 Add documented SVG generation and Playwright MCP guidance for visual inspection of rendered output ## 4. Verification and cleanup -- [ ] 4.1 Smoke-test successful and failing `typst compile` runs on representative `.typ` files +- [x] 4.1 Smoke-test successful and failing `typst compile` runs on representative `.typ` files - [ ] 4.2 Smoke-test HTML and SVG output generation plus a Playwright-based visual inspection path -- [ ] 4.3 Validate the final skill contents and retire or overwrite any superseded Typst skill prototype +- [x] 4.3 Validate the final skill contents and retire or overwrite any superseded Typst skill prototype From e4f66329e14334dabeabc8e4e358d07f6ae379a5 Mon Sep 17 00:00:00 2001 From: Myriad-Dreamin Date: Sun, 15 Mar 2026 17:41:39 +0800 Subject: [PATCH 3/7] feat: finish create-english-typst-grammar-skill proposal --- .../skills/typst-grammar-authoring/SKILL.md | 225 +++++++++--------- .../agents/openai.yaml | 4 - .../update-typst-grammar-authoring/SKILL.md | 62 +++++ .../agents/openai.yaml | 4 + .../references/grammar-catalog.json | 0 .../scripts/generate_grammar_catalog.py | 10 +- .../design.md | 155 +++++++----- .../proposal.md | 48 +++- .../typst-document-authoring-skill/spec.md | 92 +++++-- .../tasks.md | 31 +-- 10 files changed, 415 insertions(+), 216 deletions(-) delete mode 100644 .codex/skills/typst-grammar-authoring/agents/openai.yaml create mode 100644 .codex/skills/update-typst-grammar-authoring/SKILL.md create mode 100644 .codex/skills/update-typst-grammar-authoring/agents/openai.yaml rename .codex/skills/{typst-grammar-authoring => update-typst-grammar-authoring}/references/grammar-catalog.json (100%) rename .codex/skills/{typst-grammar-authoring => update-typst-grammar-authoring}/scripts/generate_grammar_catalog.py (96%) diff --git a/.codex/skills/typst-grammar-authoring/SKILL.md b/.codex/skills/typst-grammar-authoring/SKILL.md index 7ba5599..bd7752e 100644 --- a/.codex/skills/typst-grammar-authoring/SKILL.md +++ b/.codex/skills/typst-grammar-authoring/SKILL.md @@ -1,14 +1,16 @@ --- name: typst-grammar-authoring -description: Use when authoring or validating Typst documents in this repo from canonical grammar examples in src/tutorial/reference-grammar.typ, especially when you need compile, HTML, or SVG-based validation workflows. +description: Use when authoring or validating Typst documents from canonical grammar examples, especially when you need compile, HTML, or SVG-based validation workflows. metadata: short-description: Author Typst docs from canonical grammar examples + from-tutorial-rev: https://github.com/typst-doc-cn/tutorial/tree/971815a6b4898c0339b620e08c62824ec03bba7f --- # Typst Grammar Authoring Use this skill when the user wants help drafting, fixing, or validating Typst -documents in this repository. +documents. Everything needed for grammar lookup lives in this file so it can be +copied into another repository without sibling scripts or reference files. ## Workflow @@ -17,32 +19,31 @@ documents in this repository. 2. Copy the smallest matching example, then adapt it incrementally. 3. Run `typst compile` after each meaningful edit. Any non-zero exit code is a blocking failure. -4. After compile succeeds, use HTML output to inspect rendered text and SVG - output plus Playwright MCP for visual checks when available. -5. If `src/tutorial/reference-grammar.typ` changes, regenerate this embedded - lookup section and the traceability JSON with: - `python .codex/skills/typst-grammar-authoring/scripts/generate_grammar_catalog.py` +4. After compile succeeds, use HTML output to inspect rendered text and + document structure when wording or content ordering matters. +5. Use SVG output plus Playwright MCP when you need to inspect visual layout, + spacing, numbering, line breaks, or emphasis. ## Validation Compile validation: -```powershell -typst compile --root . path\to\document.typ target\typst-grammar-authoring-check\document.pdf +```sh +typst compile --root . path/to/document.typ target/typst-grammar-authoring-check/document.pdf ``` Text validation through HTML: -```powershell -typst compile --root . --features html path\to\document.typ target\typst-grammar-authoring-check\document.html -rg "expected text" target\typst-grammar-authoring-check\document.html +```sh +typst compile --root . --features html path/to/document.typ target/typst-grammar-authoring-check/document.html +rg "expected text" target/typst-grammar-authoring-check/document.html ``` Visual validation through SVG: -```powershell -typst compile --root . path\to\document.typ target\typst-grammar-authoring-check\document.svg -typst compile --root . path\to\document.typ target\typst-grammar-authoring-check\document-{0p}.svg +```sh +typst compile --root . path/to/document.typ target/typst-grammar-authoring-check/document.svg +typst compile --root . path/to/document.typ target/typst-grammar-authoring-check/document-{0p}.svg ``` Playwright inspection: @@ -55,21 +56,24 @@ Playwright inspection: ## Guardrails - Keep all skill-authored prose and instructions in English. -- Canonical code examples may retain non-English literals from the source - tutorial. +- Canonical syntax examples may retain non-English literals from their source + examples. - Do not treat Tinymist or editor diagnostics as the source of truth. -- Use `references/grammar-catalog.json` only when you need exact source - mapping; keep it out of active context by default. +- Do not assume sibling updater scripts, reference files, or repo-local + metadata exist when using this skill elsewhere. - Use `{p}` or `{0p}` in multi-page SVG output paths. - Treat HTML export as a validation aid, not a production contract. +- Keep command examples platform-neutral by using forward-slash or placeholder + paths. ## Grammar Lookup -This section is generated from `src/tutorial/reference-grammar.typ` and is kept -compact on purpose. Verbose source metadata stays in -`references/grammar-catalog.json` instead of the active skill body. +This section is embedded on purpose so the skill stays self-contained. Examples +are derived from the unofficial tutorial grammar samples and kept compact so the +lookup remains usable in a single file. + ### Base Elements - `paragraph`: `writing-markup` @@ -78,78 +82,78 @@ compact on purpose. Verbose source metadata stays in - `emph`: `_emphasis_`; `*_emphasis_*` - `list`: -~~~typ +```typ + List 1 + List 2 -~~~ +``` - `continue-list`: -~~~typ +```typ 4. List 1 + List 2 -~~~ +``` - `emum`: -~~~typ +```typ - Enum 1 - Enum 2 -~~~ +``` - `mix-list-emum`: -~~~typ +```typ - Enum 1 + Item 1 - Enum 2 -~~~ +``` - `raw`: -~~~typ +```typ `code` -~~~ +``` - `long-raw`: -~~~typ +````typ ``` code``` -~~~ +```` - `lang-raw`: -~~~typ +````typ ```rs trait World``` -~~~ +```` - `blocky-raw`: -~~~typ +````typ ```typ = Heading ``` -~~~ +```` - `image`: `#image("/assets/files/香風とうふ店.jpg", width: 50pt)` - `image-stretch`: `#image("/assets/files/香風とうふ店.jpg", width: 50pt, height: 50pt, fit: "stretch")` - `image-inline`: `在一段话中插入一个#box(baseline: 0.15em, image("/assets/files/info-icon.svg", width: 1em))图片。` - `figure`: -~~~typ +````typ #figure(```typ #image("/assets/files/香風とうふ店.jpg") ```, caption: [用于加载香風とうふ店送外卖的宝贵影像的代码]) -~~~ +```` - `link`: `#link("https://zh.wikipedia.org")[维基百科]` - `http-link`: `https://zh.wikipedia.org` - `internal-link`: -~~~typ +```typ == 某个标题 #link()[链接到某个标题] -~~~ +``` - `table`: `#table(columns: 2, [111], [2], [3])` - `table-align`: `#table(columns: 2, align: center, [111], [2], [3])` @@ -160,32 +164,33 @@ compact on purpose. Verbose source metadata stays in - `newline-by-space`: `A \ B` - `newline`: -~~~typ +```typ A \ B -~~~ +``` - `shorthand`: `北京--上海` - `shorthand-space`: `A~B` - `inline-comment`: `// 行内注释` - `cross-line-comment`: -~~~typ +```typ /* 行间注释 */ -~~~ +``` - `box`: `在一段话中插入一个#box(baseline: 0.15em, image("/assets/files/info-icon.svg", width: 1em))图片。` + ### Text Styling - `highlight`: `#highlight[高亮一段内容]` - `underline`: `#underline[Language]` - `underline-evade`: -~~~typ +```typ #underline( evade: false)[ጿኈቼዽ] -~~~ +``` - `overline`: `#overline[ጿኈቼዽ]` - `strike`: `#strike[ጿኈቼዽ]` @@ -194,6 +199,7 @@ B - `text-size`: `#text(size: 24pt)[一斤鸭梨]` - `text-fill`: `#text(fill: blue)[蓝色鸭梨]` - `text-font`: `#text(font: "Microsoft YaHei")[板正鸭梨]` + ### Script Declarations - `enter-script`: `#1` @@ -225,49 +231,49 @@ B - `destruct-dict`: `#let (neko-mimi: mimi) = (neko-mimi: 2); #mimi` - `array-remapping`: -~~~typ +```typ #let (a, b, c) = (1, 2, 3) #let (b, c, a) = (a, b, c) #a, #b, #c -~~~ +``` - `array-swap`: -~~~typ +```typ #let (a, b) = (1, 2) #((a, b) = (b, a)) #a, #b -~~~ +``` - `placeholder`: -~~~typ +```typ #let last-two(t) = { let _ = t.pop() t.pop() } #last-two((1, 2, 3, 4)) -~~~ +``` ### Script Statements - `if`: -~~~typ +```typ #if true { 1 }, #if false { 1 } else { 0 } -~~~ +``` - `if-if`: -~~~typ +```typ #if false { 0 } else if true { 1 }, #if false { 2 } else if false { 1 } else { 0 } -~~~ +``` - `while`: -~~~typ +```typ #{ let i = 0; while i < 10 { @@ -275,141 +281,143 @@ B i += 1; } } -~~~ +``` - `for`: -~~~typ +```typ #for i in range(10) { (i * 2, ) } -~~~ +``` - `for-destruct`: `#for (特色, 这个) in (neko-mimi: 2) [猫猫的 #特色 是 #这个\ ]` - `break`: `#for i in range(10) { (i, ); (i + 1926, ); break }` - `continue`: -~~~typ +```typ #for i in range(10) { if calc.even(i) { continue } (i, ) } -~~~ +``` - `return`: -~~~typ +```typ #let never(..args) = return #type(never(1, 2)) -~~~ +``` - `include`: `#include "other-file.typ"` + ### Script Styling - `set`: -~~~typ +```typ #set text(size: 24pt) 四斤鸭梨 -~~~ +``` - `scope`: -~~~typ +```typ 两只#[兔#set text(fill: rgb("#ffd1dc").darken(15%)) #[兔白#set text(fill: orange) 又白],真可爱 ] -~~~ +``` - `set-if`: -~~~typ +```typ #let is-dark-theme = true #set rect(fill: black) if is-dark-theme #set text(fill: white) if is-dark-theme #rect([wink!]) -~~~ +``` - `show-set`: -~~~typ +```typ #show: set text(fill: blue) wink! -~~~ +``` - `show`: -~~~typ +````typ #show raw: it => it.lines.at(1) 获取代码片段第二行内容:```typ #{ set text(fill: true) } ``` -~~~ +```` - `text-selector`: -~~~typ +```typ #show "cpp": strong(emph(box("C++"))) 在古代,cpp是一门常用语言。 -~~~ +``` - `regex-selector`: -~~~typ +```typ #show regex("[”。]+"): it => { set text(font: "KaiTi") highlight(it, fill: yellow) } “无名,万物之始也;有名,万物之母也。” -~~~ +``` - `label-selector`: -~~~typ +```typ #show <一整段话>: set text(fill: blue) #[$lambda$语言是世界上最好的语言。] <一整段话> 另一段话。 -~~~ +``` - `selector-exp`: -~~~typ +```typ #show heading.where(level: 2): set text(fill: blue) = 一级标题 == 二级标题 -~~~ +``` - `here`: `#context here().position()` - `here-calc`: `#context [ 页码是偶数:#calc.even(here().page()) ]` - `query`: `#context query().at(0).body` - `state`: `#state("my-state", 1)` + ### Script Expressions - `func-call`: `#calc.pow(4, 3)` - `content-param`: `#emph[emphasis]` - `member-exp`: -~~~typ +```typ #`OvO`.text -~~~ +``` - `method-exp`: `#"Hello World".split(" ")` - `dict-member-exp`: -~~~typ +```typ #let cat = (neko-mimi: 2) #cat.neko-mimi -~~~ +``` - `content-member-exp`: -~~~typ +```typ #`OvO`.text -~~~ +``` - `repr`: `#repr[ 一段文本 ]` - `type`: `#type[一段文本]` @@ -417,47 +425,47 @@ set text(fill: true) - `eval-markup-mode`: `#eval("== 一个标题", mode: "markup")` - `array-in`: -~~~typ +```typ #let pol = (1, "OvO", []) #(1 in pol) -~~~ +``` - `array-not-in`: -~~~typ +```typ #let pol = (1, "OvO", []) #([另一段内容] not in pol) -~~~ +``` - `dict-in`: -~~~typ +```typ #let cat = (neko-mimi: 2) #("neko-mimi" in cat) -~~~ +``` - `logical-cmp-exp`: -~~~typ +```typ #(1 < 0), #(1 >= 2), #(1 == 2), #(1 != 2) -~~~ +``` - `logical-calc-exp`: `#(not false), #(false or true), #(true and false)` - `plus-exp`: `#(+1), #(+0), #(1), #(++1)` - `minus-exp`: -~~~typ +```typ #(-1), #(-0), #(--1), #(-+-1) -~~~ +``` - `arith-exp`: -~~~typ +```typ #(1 + 1), #(1 + -1), #(1 - 1), #(1 - -1) -~~~ +``` - `assign-exp`: `#let a = 1; #repr(a = 10), #a, #repr(a += 2), #a` - `string-concat-exp`: `#("a" + "b")` @@ -469,7 +477,7 @@ set text(fill: true) - `dec-str-to-int`: `#int("1"), #(type(int("1")))` - `nadec-str-to-int`: -~~~typ +```typ #let safe-to-int(x) = { let res = eval(x) assert(type(res) == int, message: "should be integer") @@ -478,22 +486,23 @@ set text(fill: true) #safe-to-int("0xf"), #(type(safe-to-int("0xf"))) \ #safe-to-int("0o755"), #(type(safe-to-int("0o755"))) \ #safe-to-int("0b1011"), #(type(safe-to-int("0b1011"))) \ -~~~ +``` - `num-to-str`: -~~~typ +```typ #repr(str(1)), #repr(str(.5)) -~~~ +``` - `int-to-nadec-str`: `#str(501, base:16), #str(0xdeadbeef, base:36)` - `bool-to-str`: `#repr(false)` - `int-to-bool`: -~~~typ +```typ #let to-bool(x) = x != 0 #repr(to-bool(0)), #repr(to-bool(1)) -~~~ +``` + diff --git a/.codex/skills/typst-grammar-authoring/agents/openai.yaml b/.codex/skills/typst-grammar-authoring/agents/openai.yaml deleted file mode 100644 index d756060..0000000 --- a/.codex/skills/typst-grammar-authoring/agents/openai.yaml +++ /dev/null @@ -1,4 +0,0 @@ -interface: - display_name: "Typst Grammar Authoring" - short_description: "Author Typst docs from canonical grammar examples" - default_prompt: "Use $typst-grammar-authoring to draft or validate a Typst document from the tutorial's canonical grammar examples." diff --git a/.codex/skills/update-typst-grammar-authoring/SKILL.md b/.codex/skills/update-typst-grammar-authoring/SKILL.md new file mode 100644 index 0000000..6bc2ac2 --- /dev/null +++ b/.codex/skills/update-typst-grammar-authoring/SKILL.md @@ -0,0 +1,62 @@ +--- +name: update-typst-grammar-authoring +description: Use when maintaining the portable typst-grammar-authoring skill from this repository's canonical Typst grammar source. +metadata: + short-description: Regenerate the portable Typst grammar skill +--- + +# Update Typst Grammar Authoring + +Use this skill when a maintainer needs to refresh the portable +`typst-grammar-authoring` skill after changing the canonical grammar examples in +`src/tutorial/reference-grammar.typ`. + +## Inputs And Outputs + +- Canonical source: `src/tutorial/reference-grammar.typ` +- Portable skill: `.codex/skills/typst-grammar-authoring/SKILL.md` +- Traceability artifact: + `.codex/skills/update-typst-grammar-authoring/references/grammar-catalog.json` + +## Workflow + +1. Review the source edits in `src/tutorial/reference-grammar.typ` and confirm + they are ready to publish into the portable skill. +2. Regenerate the portable skill body and traceability artifact: + + ```sh + python .codex/skills/update-typst-grammar-authoring/scripts/generate_grammar_catalog.py + ``` + +3. Review the generated diff for the portable skill and the traceability JSON: + + ```sh + git diff -- .codex/skills/typst-grammar-authoring/SKILL.md .codex/skills/update-typst-grammar-authoring/references/grammar-catalog.json + ``` + +4. Verify the portable skill stayed single-file: + + ```powershell + Get-ChildItem -Force .codex/skills/typst-grammar-authoring + ``` + +5. Verify the portable skill's command examples stayed platform-neutral and did + not reintroduce Windows absolute paths: + + ```sh + rg -n "[A-Za-z]:\\\\|path\\\\to|target\\\\typst-grammar-authoring-check|\\.codex\\\\skills|src\\\\tutorial" .codex/skills/typst-grammar-authoring/SKILL.md + ``` + +6. If the regenerated lookup looks correct, keep the portable skill focused on + author guidance only. Any exact source mapping belongs in the updater-owned + traceability JSON, not in the portable `SKILL.md`. + +## Guardrails + +- Keep the distributed `typst-grammar-authoring` folder limited to `SKILL.md`. +- Treat `.codex/skills/update-typst-grammar-authoring/references/grammar-catalog.json` + as the source-mapping artifact for maintainers. +- Keep maintenance-only commands, source-path references, and review steps in + this updater skill rather than the portable one. +- If the generator output reveals a design issue in the portable skill, update + the portable template first and rerun the generator before final review. diff --git a/.codex/skills/update-typst-grammar-authoring/agents/openai.yaml b/.codex/skills/update-typst-grammar-authoring/agents/openai.yaml new file mode 100644 index 0000000..811a648 --- /dev/null +++ b/.codex/skills/update-typst-grammar-authoring/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Update Typst Grammar Authoring" + short_description: "Regenerate the portable Typst grammar skill" + default_prompt: "Use $update-typst-grammar-authoring to refresh the portable Typst grammar skill and its traceability artifacts from src/tutorial/reference-grammar.typ." diff --git a/.codex/skills/typst-grammar-authoring/references/grammar-catalog.json b/.codex/skills/update-typst-grammar-authoring/references/grammar-catalog.json similarity index 100% rename from .codex/skills/typst-grammar-authoring/references/grammar-catalog.json rename to .codex/skills/update-typst-grammar-authoring/references/grammar-catalog.json diff --git a/.codex/skills/typst-grammar-authoring/scripts/generate_grammar_catalog.py b/.codex/skills/update-typst-grammar-authoring/scripts/generate_grammar_catalog.py similarity index 96% rename from .codex/skills/typst-grammar-authoring/scripts/generate_grammar_catalog.py rename to .codex/skills/update-typst-grammar-authoring/scripts/generate_grammar_catalog.py index 9a7f100..86f32e7 100644 --- a/.codex/skills/typst-grammar-authoring/scripts/generate_grammar_catalog.py +++ b/.codex/skills/update-typst-grammar-authoring/scripts/generate_grammar_catalog.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Generate Typst grammar data and sync the compact lookup into SKILL.md.""" +"""Generate Typst grammar data and sync it into the portable skill.""" from __future__ import annotations @@ -224,9 +224,7 @@ def update_skill(skill_path: Path, generated_lookup: str) -> None: replacement = f"{GENERATED_BEGIN}\n{generated_lookup}{GENERATED_END}" updated_text, count = pattern.subn(lambda _: replacement, skill_text, count=1) if count != 1: - raise ValueError( - f"Could not find generated section markers in {skill_path}" - ) + raise ValueError(f"Could not find generated section markers in {skill_path}") skill_path.write_text(updated_text, encoding="utf-8") @@ -251,7 +249,7 @@ def default_source(script_path: Path) -> Path: def default_skill_path(script_path: Path) -> Path: - return script_path.parent.parent / "SKILL.md" + return script_path.parents[2] / "typst-grammar-authoring" / "SKILL.md" def main() -> int: @@ -275,7 +273,7 @@ def main() -> int: "--skill-path", type=Path, default=default_skill_path(script_path), - help="Path to the SKILL.md file to sync", + help="Path to the portable SKILL.md file to sync", ) args = parser.parse_args() diff --git a/openspec/changes/create-english-typst-grammar-skill/design.md b/openspec/changes/create-english-typst-grammar-skill/design.md index 80cb58e..1cf9b29 100644 --- a/openspec/changes/create-english-typst-grammar-skill/design.md +++ b/openspec/changes/create-english-typst-grammar-skill/design.md @@ -1,108 +1,155 @@ ## Context -This repository contains a detailed Typst grammar catalog in `src/tutorial/reference-grammar.typ`. The proposed change turns that tutorial asset into a repo-local Codex skill that helps author user Typst documents by selecting valid grammar patterns from canonical examples instead of inventing syntax ad hoc. +This repository contains a detailed Typst grammar catalog in +`src/tutorial/reference-grammar.typ`. The original change turned that tutorial +asset into a Codex skill, but the first implementation blended portable author +guidance with repo-local maintenance machinery. As a result, +`typst-grammar-authoring` now includes updater scripts, traceability artifacts, +and path examples that are not ideal for copying into another repository. -The skill needs to be English-only from Codex's perspective, even though the source tutorial and some example literals are Chinese. The same change also needs a validation workflow that does not depend on IDE-specific language services. The user explicitly wants grammar validation to come from Typst compilation, then separate validation passes for text output through HTML and visual output through SVG plus Playwright MCP inspection. +The revised direction is to separate those responsibilities: + +- `typst-grammar-authoring` is the portable skill that authors use. +- `update-typst-grammar-authoring` is the repo-local maintenance skill that + updates the portable one from canonical tutorial examples. ## Goals / Non-Goals **Goals:** -- Create a repo-local Codex skill for user Typst document authoring. -- Ground the skill in canonical grammar examples extracted from `src/tutorial/reference-grammar.typ`. -- Keep all skill-authored prose, metadata, navigation, and workflow documentation in English. -- Make `typst compile` the authoritative validation path for grammar and compile-time failures. -- Define a repeatable text-validation workflow using Typst HTML output. -- Define a repeatable visual-validation workflow using Typst SVG output and Playwright MCP. +- Keep `typst-grammar-authoring` as a self-contained, English-only, + distributable skill with only `SKILL.md`. +- Keep grammar lookup and validation workflows available inside the portable + `SKILL.md`. +- Move generator scripts, traceability data, and repo-specific maintenance + instructions into `update-typst-grammar-authoring`. +- Eliminate Windows-specific command paths and other platform-specific path + examples from the distributed `SKILL.md`. +- Preserve the ability to regenerate the portable skill from + `src/tutorial/reference-grammar.typ`. **Non-Goals:** - Translate the full Chinese tutorial into English. - Replace Typst's own compiler diagnostics with a custom parser or validator. -- Depend on Tinymist, VS Code, or any specific editor integration for the required validation path. -- Build a general-purpose Typst skill for all repositories outside this repo's structure and assets. +- Depend on Tinymist, VS Code, or any specific editor integration for required + validation. +- Create a package manager or external publishing workflow for distributing the + portable skill. ## Decisions -### Decision: Build the skill around extracted grammar references +### Decision: Split authoring and maintenance into two skills + +The repository will contain two skills with different responsibilities: -The skill will not point Codex at the full tutorial file every time. Instead, it will maintain generated reference material derived from `src/tutorial/reference-grammar.typ` so Codex can search a compact catalog first and only open the source file when deeper context is needed. +- `typst-grammar-authoring` for portable author guidance +- `update-typst-grammar-authoring` for repo-local regeneration and maintenance Why this approach: -- It keeps the skill's active context smaller than loading the full tutorial source. -- It preserves direct traceability back to the source grammar table. -- It allows English-facing headings and navigation even when the source material is not in English. +- It keeps the distributed skill easy to copy into other repositories. +- It lets the updater skill retain repo-specific scripts and source references + without polluting the authoring skill. +- It makes it clearer which instructions are for end users versus maintainers. Alternative considered: -- Read `src/tutorial/reference-grammar.typ` directly on every use. Rejected because it is larger, noisier, and less friendly to repeated low-context lookups. +- Keep one repo-local skill with embedded updater logic. Rejected because it + mixes portability and maintenance concerns in the same artifact. -### Decision: Treat English-only as a requirement for skill-authored materials, not for verbatim source code blocks +### Decision: Make the portable skill a single-file skill -The skill's `SKILL.md`, generated headings, navigation labels, validation guidance, and agent metadata will be written in English. Verbatim code examples may still contain non-English literals when they come directly from canonical source examples. +`typst-grammar-authoring` will consist only of `SKILL.md`. It must not depend +on sibling scripts, references, or optional UI metadata to function after being +copied to another repository. Why this approach: -- It satisfies the requirement that the skill itself is written in English. -- It avoids mutating canonical Typst examples in ways that could accidentally change grammar or semantics. -- It keeps the extracted references trustworthy as grammar sources. +- A single file is the easiest form to distribute and review. +- It minimizes accidental coupling to repo-local layout and helper artifacts. +- It keeps the authoring experience simple for downstream repositories. Alternative considered: -- Rewrite all source examples into English text. Rejected because it introduces translation work and risks changing the exact shape of canonical grammar examples. +- Keep `agents/`, `references/`, or `scripts/` alongside the distributed skill. + Rejected because the user explicitly wants the authoring skill to remain only + a Markdown file. + +### Decision: Put all repo-specific updater assets under `update-typst-grammar-authoring` -### Decision: Use `typst compile` as the mandatory validation source of truth +The updater skill will own: -The skill will validate authored Typst documents with `typst compile`, not with editor-specific LSP diagnostics. Compiler exit status and compiler diagnostics will be treated as the required pass/fail signal for grammar and compile-time correctness. +- regeneration scripts +- traceability artifacts +- repo-specific instructions that mention `src/tutorial/reference-grammar.typ` +- any maintenance-only metadata Why this approach: -- It matches the requested workflow. -- It works consistently outside any specific IDE. -- It keeps the validation path aligned with the actual renderer and compiler used for outputs. +- It centralizes maintenance behavior in one place. +- It keeps downstream consumers from carrying unnecessary repo-specific files. +- It preserves exact source traceability without expanding the portable skill + context. Alternative considered: -- Use Tinymist or another LSP as the main validator. Rejected because the requested change explicitly prefers Typst compilation over IDE tooling. +- Leave traceability JSON and generator scripts under the portable skill folder. + Rejected because those files are maintenance artifacts, not distributed + authoring guidance. -### Decision: Split validation into compile, text, and visual passes +### Decision: Use platform-neutral path examples in the portable skill -The skill will define three validation layers: -- compile validation with `typst compile` -- text validation with `typst compile --features html` -- visual validation with `typst compile ... a.svg` plus Playwright MCP inspection +The distributed `SKILL.md` will use forward-slash, repo-relative, or placeholder +paths such as `path/to/document.typ` and `target/typst-grammar-authoring-check/document.html`. +It must not use Windows absolute paths or Windows-style backslash-only command +examples. Why this approach: -- Grammar correctness does not guarantee correct text output. -- Correct text output does not guarantee correct layout or rendering. -- The separation maps directly to the user's requested workflow and keeps the skill operationally clear. +- It keeps the skill readable across operating systems. +- It avoids downstream users mistaking repo-local Windows commands for required + syntax. +- It makes the portable skill genuinely portable instead of just copyable. Alternative considered: -- Treat a successful PDF or SVG compile as sufficient validation. Rejected because it misses easy-to-inspect text structure issues that HTML surfaces well. +- Keep PowerShell-oriented backslash paths because the repo is currently being + edited on Windows. Rejected because the distributed skill should not inherit a + single machine's path style. -### Decision: Make Playwright an inspection layer over generated SVG, not the rendering source +### Decision: Keep the portable skill self-contained, but keep verbose traceability out of it -The skill will rely on Typst to generate SVG and on Playwright MCP to inspect that output in a browser-compatible context. The browser step is observational only; it will not replace Typst rendering. +The portable skill will still embed a compact grammar lookup and validation +workflow inside `SKILL.md`, but verbose canonical references and source-line +traceability will live in updater-owned artifacts instead. Why this approach: -- Typst remains the authoritative renderer. -- Playwright is strong at visual verification and DOM/screenshot workflows. -- It keeps the boundary between generation and inspection simple. +- It preserves low-context usability for authors. +- It keeps the portable skill smaller than a fully traceable catalog. +- It still gives maintainers a way to audit generated entries precisely. Alternative considered: -- Use Playwright to render HTML instead of inspecting SVG. Rejected because the requested visual path is explicitly based on Typst SVG output. +- Remove all generated grammar data from the portable skill and require a sidecar + reference file. Rejected because the portable skill must remain single-file. ## Risks / Trade-offs -- Strict English-only expectations may conflict with verbatim source examples that include Chinese literals. → Mitigation: keep all authored guidance in English and document that canonical code blocks are source data, not translated prose. -- HTML export is still an in-development Typst feature and may emit warnings or change behavior over time. → Mitigation: treat HTML as a validation aid for text structure, not as a production output contract. -- SVG inspection can depend on the execution environment and how Playwright accesses generated files. → Mitigation: document a browser-compatible inspection path and keep SVG generation itself independent from Playwright. -- Grammar extraction logic can drift if `src/tutorial/reference-grammar.typ` changes structure. → Mitigation: keep extraction logic small, source-specific, and covered by representative regeneration checks. +- A single-file portable skill is still larger than a minimal skill because it + embeds grammar lookup content. → Mitigation: keep examples compact and keep + verbose traceability in the updater skill only. +- Splitting the skills adds another artifact to maintain. → Mitigation: make the + updater skill the clear owner of regeneration and validation steps. +- Portable path examples may drift back toward platform-specific syntax during + updates. → Mitigation: add explicit tasks and verification for path style. ## Migration Plan -1. Add or replace the repo-local skill under `.codex/skills/`. -2. Generate English-facing reference material from `src/tutorial/reference-grammar.typ`. -3. Switch validation guidance from any editor-specific path to `typst compile`. -4. Add HTML and SVG validation guidance and any supporting scripts needed for repeatable execution. -5. If a previous prototype skill exists, retire or overwrite it so only the new English-only workflow remains. +1. Create a repo-local `update-typst-grammar-authoring` skill under + `.codex/skills/`. +2. Move generator scripts, traceability artifacts, and repo-specific update + instructions into the updater skill. +3. Reduce `.codex/skills/typst-grammar-authoring/` to a single distributable + `SKILL.md`. +4. Rewrite distributed validation commands and examples to use platform-neutral + paths. +5. Verify the updater still regenerates the distributed skill correctly. Rollback strategy: -- Remove the new skill folder and generated references, then restore the previous repo-local skill state if needed. +- Restore the previous single-skill folder layout if the split causes an + unacceptable maintenance burden. ## Open Questions -- None at proposal time. The current request is specific enough to proceed with implementation planning. +- None at proposal update time. The split between portable and maintenance + skills is the intended direction. diff --git a/openspec/changes/create-english-typst-grammar-skill/proposal.md b/openspec/changes/create-english-typst-grammar-skill/proposal.md index a897845..77cd420 100644 --- a/openspec/changes/create-english-typst-grammar-skill/proposal.md +++ b/openspec/changes/create-english-typst-grammar-skill/proposal.md @@ -1,20 +1,43 @@ ## Why -Codex can already edit Typst files, but it still benefits from a repo-specific skill that teaches it to prefer canonical grammar patterns over improvised syntax. This repository already contains a rich grammar example table in `src/tutorial/reference-grammar.typ`, so now is a good time to turn that material into an English-only skill that reliably guides authoring and validation for user Typst documents. +The current `typst-grammar-authoring` implementation mixes two different jobs: +portable Typst authoring guidance for end users and repo-local maintenance +logic for regenerating that guidance from `src/tutorial/reference-grammar.typ`. +That coupling makes the distributed skill harder to copy into another +repository, and it has already allowed Windows-style command paths and +repo-specific maintenance details to leak into `SKILL.md`. + +We want to split those concerns cleanly: + +- `typst-grammar-authoring` should become the portable skill that users can copy + to another repository as a single `SKILL.md` file. +- `update-typst-grammar-authoring` should become the repo-local maintenance + skill that regenerates and validates the portable skill from this tutorial's + canonical grammar source. ## What Changes -- Create a new Codex skill that teaches Typst document authoring by adapting examples from `src/tutorial/reference-grammar.typ` instead of inventing grammar from scratch. -- Require the skill itself to be written in English, including `SKILL.md`, generated references, validation guidance, and UI metadata. -- Add reusable resources that extract and organize grammar examples from `src/tutorial/reference-grammar.typ` for low-context lookup during authoring. -- Standardize grammar validation around `typst compile` so syntax and semantic failures are detected through Typst’s own compiler output instead of editor-specific tooling. -- Define a text-validation workflow based on Typst HTML output, for example `typst compile --features html a.typ a.html`. -- Define a visual-validation workflow based on Typst SVG output plus Playwright MCP inspection, for example `typst compile a.typ a.svg`. +- Split the current single skill implementation into two separate skills: + `typst-grammar-authoring` and `update-typst-grammar-authoring`. +- Make `typst-grammar-authoring` a self-contained, English-only, distributable + skill consisting only of `SKILL.md`. +- Move generator scripts, traceability artifacts, repo-specific update + instructions, and any maintenance-only metadata into + `update-typst-grammar-authoring`. +- Require the distributed `typst-grammar-authoring/SKILL.md` to use + platform-neutral path examples instead of Windows-specific command paths. +- Keep compile, HTML, and SVG validation guidance in the portable skill while + keeping verbose traceability data outside its default context. ## Capabilities ### New Capabilities -- `typst-document-authoring-skill`: Provide an English-only Codex skill that teaches Typst authoring from canonical grammar examples and validates authored documents with Typst compilation, HTML output, and SVG plus Playwright review. +- `typst-document-authoring-skill`: Provide a portable, single-file Typst + authoring skill with embedded grammar lookup and compile-based validation + guidance. +- `typst-document-authoring-skill-maintenance`: Provide a repo-local updater + skill that regenerates the portable Typst authoring skill from + `src/tutorial/reference-grammar.typ`. ### Modified Capabilities - None. @@ -22,6 +45,9 @@ Codex can already edit Typst files, but it still benefits from a repo-specific s ## Impact - Affects repo-local skill content under `.codex/skills/`. -- Adds or updates scripts and references used to extract grammar examples from `src/tutorial/reference-grammar.typ`. -- Defines compile-based validation workflows for `.typ`, `.html`, and `.svg` outputs. -- Shapes how Codex authoring guidance is presented for user Typst documents in this repository. +- Changes `typst-grammar-authoring` from a repo-local folder with support files + into a portable single-file skill. +- Adds or updates a separate `update-typst-grammar-authoring` skill for + generation, validation, and traceability workflows. +- Removes Windows-specific command path examples from the distributed authoring + skill. diff --git a/openspec/changes/create-english-typst-grammar-skill/specs/typst-document-authoring-skill/spec.md b/openspec/changes/create-english-typst-grammar-skill/specs/typst-document-authoring-skill/spec.md index c5f0096..18c5197 100644 --- a/openspec/changes/create-english-typst-grammar-skill/specs/typst-document-authoring-skill/spec.md +++ b/openspec/changes/create-english-typst-grammar-skill/specs/typst-document-authoring-skill/spec.md @@ -1,36 +1,92 @@ ## ADDED Requirements -### Requirement: Skill materials are English-facing -The skill SHALL present its metadata, instructions, navigation text, validation guidance, and generated reference prose in English so Codex can use it without relying on Chinese tutorial prose. +### Requirement: Portable authoring skill is English-facing and self-contained +The distributed `typst-grammar-authoring` skill SHALL be complete as a +single-file, English-facing `SKILL.md` so it can be copied into another +repository without requiring sibling support files. -#### Scenario: English-only authored materials -- **WHEN** the skill folder is created or updated -- **THEN** `SKILL.md`, agent metadata, and generated guidance files MUST use English for authored prose and labels +#### Scenario: Single-file portable distribution +- **WHEN** `typst-grammar-authoring` is prepared for downstream use +- **THEN** the skill folder MUST be complete with only `SKILL.md` +- **AND** that `SKILL.md` MUST contain the authoring guidance, grammar lookup, + and validation workflow needed by the consuming repository -### Requirement: Skill references canonical grammar examples -The skill SHALL provide reference material derived from `src/tutorial/reference-grammar.typ` so Codex can locate and adapt canonical Typst grammar examples while authoring user documents. +### Requirement: Portable authoring skill avoids Windows-specific path examples +The distributed `typst-grammar-authoring/SKILL.md` SHALL use platform-neutral, +forward-slash, or placeholder filesystem paths in commands and examples. -#### Scenario: Grammar lookup from extracted references -- **WHEN** Codex needs a Typst syntax pattern for a user document -- **THEN** the skill MUST provide an extracted reference path that maps back to canonical examples from `src/tutorial/reference-grammar.typ` +#### Scenario: Portable command examples +- **WHEN** `typst-grammar-authoring/SKILL.md` is authored or regenerated +- **THEN** command examples MUST use repo-relative or placeholder paths such as + `path/to/document.typ` +- **AND** the file MUST NOT include Windows absolute paths or Windows-style + backslash-only command paths + +### Requirement: Portable authoring skill embeds compact grammar lookup +The distributed `typst-grammar-authoring/SKILL.md` SHALL embed a compact Typst +grammar lookup derived from `src/tutorial/reference-grammar.typ` so it remains +single-file while still teaching canonical syntax patterns. + +#### Scenario: Grammar lookup without sidecar references +- **WHEN** Codex needs a Typst syntax pattern while using + `typst-grammar-authoring` +- **THEN** the skill MUST provide that lookup inside `SKILL.md` +- **AND** the lookup MUST be derived from canonical examples in + `src/tutorial/reference-grammar.typ` + +### Requirement: Updater skill owns regeneration of the portable skill +The repo-local `update-typst-grammar-authoring` skill SHALL own the workflow +that regenerates the distributed `typst-grammar-authoring/SKILL.md` from +`src/tutorial/reference-grammar.typ`. + +#### Scenario: Repo-local regeneration +- **WHEN** a maintainer updates canonical grammar examples from + `src/tutorial/reference-grammar.typ` +- **THEN** `update-typst-grammar-authoring` MUST provide the update workflow +- **AND** that workflow MUST produce the distributed + `typst-grammar-authoring/SKILL.md` + +### Requirement: Updater skill preserves verbose traceability outside portable context +The updater workflow SHALL preserve exact source traceability in separate +maintenance artifacts without injecting verbose canonical reference and +traceability sections into the distributed `typst-grammar-authoring/SKILL.md`. + +#### Scenario: Maintainer needs exact source mapping +- **WHEN** a maintainer needs exact source mapping for a generated grammar entry +- **THEN** `update-typst-grammar-authoring` MUST provide a separate + traceability artifact +- **AND** the distributed `typst-grammar-authoring/SKILL.md` MUST omit verbose + canonical reference and traceability sections from its active context ### Requirement: Grammar validation uses Typst compilation -The skill SHALL define `typst compile` as the required validation workflow for grammar and compile-time errors in authored Typst documents. +The portable authoring skill SHALL define `typst compile` as the required +validation workflow for grammar and compile-time errors in authored Typst +documents. #### Scenario: Compile failure is treated as invalid grammar or document state - **WHEN** Codex validates a `.typ` file with the skill's required workflow -- **THEN** a non-zero `typst compile` result MUST be treated as a blocking validation failure +- **THEN** a non-zero `typst compile` result MUST be treated as a blocking + validation failure ### Requirement: Text output can be validated through HTML export -The skill SHALL define a text-validation workflow based on Typst HTML output so Codex can inspect textual structure and rendered text separately from compile success. +The portable authoring skill SHALL define a text-validation workflow based on +Typst HTML output so Codex can inspect textual structure and rendered text +separately from compile success. #### Scenario: HTML validation of authored text -- **WHEN** Codex needs to verify rendered text or document structure after a successful compile -- **THEN** the skill MUST direct Codex to use `typst compile --features html` to produce HTML output for inspection +- **WHEN** Codex needs to verify rendered text or document structure after a + successful compile +- **THEN** the skill MUST direct Codex to use `typst compile --features html` + to produce HTML output for inspection ### Requirement: Visual output can be validated through SVG and Playwright -The skill SHALL define a visual-validation workflow based on Typst SVG output and Playwright MCP inspection so Codex can review rendered layout after compilation. +The portable authoring skill SHALL define a visual-validation workflow based on +Typst SVG output and Playwright MCP inspection so Codex can review rendered +layout after compilation. #### Scenario: SVG and Playwright visual inspection -- **WHEN** Codex needs to inspect rendered layout or visual regressions in a user Typst document -- **THEN** the skill MUST direct Codex to compile the document to SVG and use Playwright MCP as the visual inspection step +- **WHEN** Codex needs to inspect rendered layout or visual regressions in a + user Typst document +- **THEN** the skill MUST direct Codex to compile the document to SVG +- **AND** the skill MUST direct Codex to use Playwright MCP as the visual + inspection step diff --git a/openspec/changes/create-english-typst-grammar-skill/tasks.md b/openspec/changes/create-english-typst-grammar-skill/tasks.md index 7510be8..a31100d 100644 --- a/openspec/changes/create-english-typst-grammar-skill/tasks.md +++ b/openspec/changes/create-english-typst-grammar-skill/tasks.md @@ -1,22 +1,23 @@ -## 1. Skill setup +## 1. Split the skill topology -- [x] 1.1 Audit any existing Typst skill or prototype files and choose the final repo-local skill target under `.codex/skills/` -- [x] 1.2 Create or update the repo-local skill scaffold and English-facing agent metadata for the Typst document authoring skill +- [x] 1.1 Create a repo-local `update-typst-grammar-authoring` skill target under `.codex/skills/` +- [x] 1.2 Move generator scripts, traceability artifacts, and updater-only metadata out of `typst-grammar-authoring` and into `update-typst-grammar-authoring` +- [x] 1.3 Reduce `.codex/skills/typst-grammar-authoring/` to a distributable single-file skill that contains only `SKILL.md` -## 2. Grammar reference resources +## 2. Rewrite the portable authoring skill -- [x] 2.1 Implement or update the extraction flow that reads `src/tutorial/reference-grammar.typ` and produces skill reference material -- [x] 2.2 Generate an English-facing grammar reference catalog that preserves traceability back to the canonical source examples -- [x] 2.3 Write the final English-only `SKILL.md` and reference guidance for grammar-first authoring +- [x] 2.1 Rewrite `typst-grammar-authoring/SKILL.md` to remove Windows-specific command paths and use platform-neutral path examples +- [x] 2.2 Keep grammar lookup and validation guidance self-contained inside the distributed `typst-grammar-authoring/SKILL.md` +- [x] 2.3 Remove repo-local maintenance instructions, source-path dependencies, and sidecar-file assumptions from the distributed `typst-grammar-authoring/SKILL.md` -## 3. Compile-based validation workflow +## 3. Build the updater skill workflow -- [x] 3.1 Replace any editor-specific validation requirement with a `typst compile`-based grammar validation workflow -- [x] 3.2 Add documented HTML validation commands and guidance for checking rendered text output -- [x] 3.3 Add documented SVG generation and Playwright MCP guidance for visual inspection of rendered output +- [x] 3.1 Add updater instructions for regenerating `typst-grammar-authoring/SKILL.md` from `src/tutorial/reference-grammar.typ` +- [x] 3.2 Keep exact source traceability available as updater-owned artifacts without expanding the portable skill context +- [x] 3.3 Add updater validation guidance for refreshing, reviewing, and diffing the distributed skill output -## 4. Verification and cleanup +## 4. Verify the split -- [x] 4.1 Smoke-test successful and failing `typst compile` runs on representative `.typ` files -- [ ] 4.2 Smoke-test HTML and SVG output generation plus a Playwright-based visual inspection path -- [x] 4.3 Validate the final skill contents and retire or overwrite any superseded Typst skill prototype +- [x] 4.1 Verify `.codex/skills/typst-grammar-authoring/` contains only `SKILL.md` +- [x] 4.2 Verify the distributed `SKILL.md` contains no Windows-specific or absolute repo-local paths in command examples +- [x] 4.3 Smoke-test the updater workflow and the portable skill after the split From 19185a6e991bee825baf4570c6f596ffb35a510e Mon Sep 17 00:00:00 2001 From: Myriad-Dreamin Date: Sun, 15 Mar 2026 17:51:07 +0800 Subject: [PATCH 4/7] feat: archive create-english-typst-grammar-skill proposal --- .../skills/openspec-archive-change/SKILL.md | 13 ++- .../.openspec.yaml | 0 .../design.md | 0 .../proposal.md | 0 .../typst-document-authoring-skill/spec.md | 0 .../tasks.md | 0 openspec/config.yaml | 28 +++--- .../typst-document-authoring-skill/spec.md | 95 +++++++++++++++++++ 8 files changed, 118 insertions(+), 18 deletions(-) rename openspec/changes/{create-english-typst-grammar-skill => archive/2026-03-15-create-english-typst-grammar-skill}/.openspec.yaml (100%) rename openspec/changes/{create-english-typst-grammar-skill => archive/2026-03-15-create-english-typst-grammar-skill}/design.md (100%) rename openspec/changes/{create-english-typst-grammar-skill => archive/2026-03-15-create-english-typst-grammar-skill}/proposal.md (100%) rename openspec/changes/{create-english-typst-grammar-skill => archive/2026-03-15-create-english-typst-grammar-skill}/specs/typst-document-authoring-skill/spec.md (100%) rename openspec/changes/{create-english-typst-grammar-skill => archive/2026-03-15-create-english-typst-grammar-skill}/tasks.md (100%) create mode 100644 openspec/specs/typst-document-authoring-skill/spec.md diff --git a/.codex/skills/openspec-archive-change/SKILL.md b/.codex/skills/openspec-archive-change/SKILL.md index 9b1f851..51e529c 100644 --- a/.codex/skills/openspec-archive-change/SKILL.md +++ b/.codex/skills/openspec-archive-change/SKILL.md @@ -82,7 +82,17 @@ Archive a completed change in the experimental workflow. mv openspec/changes/ openspec/changes/archive/YYYY-MM-DD- ``` -6. **Display summary** +6. **Finalize synced spec purpose** + + If specs were synced or created under `openspec/specs/`: + - Open each affected main spec at `openspec/specs//spec.md` + - Check the `## Purpose` section + - If it contains the autogenerated archive placeholder (for example, + `TBD - created by archiving change ...`) or is still too generic, replace + it with a concise, capability-specific purpose statement before reporting + completion + +7. **Display summary** Show archive completion summary including: - Change name @@ -112,3 +122,4 @@ All artifacts complete. All tasks complete. - Show clear summary of what happened - If sync is requested, use openspec-sync-specs approach (agent-driven) - If delta specs exist, always run the sync assessment and show the combined summary before prompting +- If archiving creates or updates canonical specs, do not leave autogenerated placeholder `## Purpose` text behind diff --git a/openspec/changes/create-english-typst-grammar-skill/.openspec.yaml b/openspec/changes/archive/2026-03-15-create-english-typst-grammar-skill/.openspec.yaml similarity index 100% rename from openspec/changes/create-english-typst-grammar-skill/.openspec.yaml rename to openspec/changes/archive/2026-03-15-create-english-typst-grammar-skill/.openspec.yaml diff --git a/openspec/changes/create-english-typst-grammar-skill/design.md b/openspec/changes/archive/2026-03-15-create-english-typst-grammar-skill/design.md similarity index 100% rename from openspec/changes/create-english-typst-grammar-skill/design.md rename to openspec/changes/archive/2026-03-15-create-english-typst-grammar-skill/design.md diff --git a/openspec/changes/create-english-typst-grammar-skill/proposal.md b/openspec/changes/archive/2026-03-15-create-english-typst-grammar-skill/proposal.md similarity index 100% rename from openspec/changes/create-english-typst-grammar-skill/proposal.md rename to openspec/changes/archive/2026-03-15-create-english-typst-grammar-skill/proposal.md diff --git a/openspec/changes/create-english-typst-grammar-skill/specs/typst-document-authoring-skill/spec.md b/openspec/changes/archive/2026-03-15-create-english-typst-grammar-skill/specs/typst-document-authoring-skill/spec.md similarity index 100% rename from openspec/changes/create-english-typst-grammar-skill/specs/typst-document-authoring-skill/spec.md rename to openspec/changes/archive/2026-03-15-create-english-typst-grammar-skill/specs/typst-document-authoring-skill/spec.md diff --git a/openspec/changes/create-english-typst-grammar-skill/tasks.md b/openspec/changes/archive/2026-03-15-create-english-typst-grammar-skill/tasks.md similarity index 100% rename from openspec/changes/create-english-typst-grammar-skill/tasks.md rename to openspec/changes/archive/2026-03-15-create-english-typst-grammar-skill/tasks.md diff --git a/openspec/config.yaml b/openspec/config.yaml index 392946c..1bb2226 100644 --- a/openspec/config.yaml +++ b/openspec/config.yaml @@ -1,20 +1,14 @@ schema: spec-driven -# Project context (optional) -# This is shown to AI when creating artifacts. -# Add your tech stack, conventions, style guides, domain knowledge, etc. -# Example: -# context: | -# Tech stack: TypeScript, React, Node.js -# We use conventional commits -# Domain: e-commerce platform +context: | + When a change is archived and syncs or creates canonical specs under + `openspec/specs/`, review each resulting `## Purpose` section before calling + the archive complete. + Replace any autogenerated placeholder like + `TBD - created by archiving change ...` with a concise, domain-specific + purpose statement that describes the capability. -# Per-artifact rules (optional) -# Add custom rules for specific artifacts. -# Example: -# rules: -# proposal: -# - Keep proposals under 500 words -# - Always include a "Non-goals" section -# tasks: -# - Break tasks into chunks of max 2 hours +rules: + specs: + - Every spec in `openspec/specs/*/spec.md` must include a concrete `## Purpose` section. + - Never leave the autogenerated archive placeholder in a canonical spec purpose; replace it before reporting archive success. diff --git a/openspec/specs/typst-document-authoring-skill/spec.md b/openspec/specs/typst-document-authoring-skill/spec.md new file mode 100644 index 0000000..d5074da --- /dev/null +++ b/openspec/specs/typst-document-authoring-skill/spec.md @@ -0,0 +1,95 @@ +# typst-document-authoring-skill Specification + +## Purpose +Define the canonical requirements for a portable, English-facing Typst authoring skill and the updater and validation workflows that maintain it. +## Requirements +### Requirement: Portable authoring skill is English-facing and self-contained +The distributed `typst-grammar-authoring` skill SHALL be complete as a +single-file, English-facing `SKILL.md` so it can be copied into another +repository without requiring sibling support files. + +#### Scenario: Single-file portable distribution +- **WHEN** `typst-grammar-authoring` is prepared for downstream use +- **THEN** the skill folder MUST be complete with only `SKILL.md` +- **AND** that `SKILL.md` MUST contain the authoring guidance, grammar lookup, + and validation workflow needed by the consuming repository + +### Requirement: Portable authoring skill avoids Windows-specific path examples +The distributed `typst-grammar-authoring/SKILL.md` SHALL use platform-neutral, +forward-slash, or placeholder filesystem paths in commands and examples. + +#### Scenario: Portable command examples +- **WHEN** `typst-grammar-authoring/SKILL.md` is authored or regenerated +- **THEN** command examples MUST use repo-relative or placeholder paths such as + `path/to/document.typ` +- **AND** the file MUST NOT include Windows absolute paths or Windows-style + backslash-only command paths + +### Requirement: Portable authoring skill embeds compact grammar lookup +The distributed `typst-grammar-authoring/SKILL.md` SHALL embed a compact Typst +grammar lookup derived from `src/tutorial/reference-grammar.typ` so it remains +single-file while still teaching canonical syntax patterns. + +#### Scenario: Grammar lookup without sidecar references +- **WHEN** Codex needs a Typst syntax pattern while using + `typst-grammar-authoring` +- **THEN** the skill MUST provide that lookup inside `SKILL.md` +- **AND** the lookup MUST be derived from canonical examples in + `src/tutorial/reference-grammar.typ` + +### Requirement: Updater skill owns regeneration of the portable skill +The repo-local `update-typst-grammar-authoring` skill SHALL own the workflow +that regenerates the distributed `typst-grammar-authoring/SKILL.md` from +`src/tutorial/reference-grammar.typ`. + +#### Scenario: Repo-local regeneration +- **WHEN** a maintainer updates canonical grammar examples from + `src/tutorial/reference-grammar.typ` +- **THEN** `update-typst-grammar-authoring` MUST provide the update workflow +- **AND** that workflow MUST produce the distributed + `typst-grammar-authoring/SKILL.md` + +### Requirement: Updater skill preserves verbose traceability outside portable context +The updater workflow SHALL preserve exact source traceability in separate +maintenance artifacts without injecting verbose canonical reference and +traceability sections into the distributed `typst-grammar-authoring/SKILL.md`. + +#### Scenario: Maintainer needs exact source mapping +- **WHEN** a maintainer needs exact source mapping for a generated grammar entry +- **THEN** `update-typst-grammar-authoring` MUST provide a separate + traceability artifact +- **AND** the distributed `typst-grammar-authoring/SKILL.md` MUST omit verbose + canonical reference and traceability sections from its active context + +### Requirement: Grammar validation uses Typst compilation +The portable authoring skill SHALL define `typst compile` as the required +validation workflow for grammar and compile-time errors in authored Typst +documents. + +#### Scenario: Compile failure is treated as invalid grammar or document state +- **WHEN** Codex validates a `.typ` file with the skill's required workflow +- **THEN** a non-zero `typst compile` result MUST be treated as a blocking + validation failure + +### Requirement: Text output can be validated through HTML export +The portable authoring skill SHALL define a text-validation workflow based on +Typst HTML output so Codex can inspect textual structure and rendered text +separately from compile success. + +#### Scenario: HTML validation of authored text +- **WHEN** Codex needs to verify rendered text or document structure after a + successful compile +- **THEN** the skill MUST direct Codex to use `typst compile --features html` + to produce HTML output for inspection + +### Requirement: Visual output can be validated through SVG and Playwright +The portable authoring skill SHALL define a visual-validation workflow based on +Typst SVG output and Playwright MCP inspection so Codex can review rendered +layout after compilation. + +#### Scenario: SVG and Playwright visual inspection +- **WHEN** Codex needs to inspect rendered layout or visual regressions in a + user Typst document +- **THEN** the skill MUST direct Codex to compile the document to SVG +- **AND** the skill MUST direct Codex to use Playwright MCP as the visual + inspection step From 3ad2b8cd500b873e0047d5e68c6088ec20536781 Mon Sep 17 00:00:00 2001 From: Myriad-Dreamin <35292584+Myriad-Dreamin@users.noreply.github.com> Date: Sun, 15 Mar 2026 18:40:11 +0800 Subject: [PATCH 5/7] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- openspec/specs/typst-document-authoring-skill/spec.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openspec/specs/typst-document-authoring-skill/spec.md b/openspec/specs/typst-document-authoring-skill/spec.md index d5074da..8cfce06 100644 --- a/openspec/specs/typst-document-authoring-skill/spec.md +++ b/openspec/specs/typst-document-authoring-skill/spec.md @@ -2,6 +2,9 @@ ## Purpose Define the canonical requirements for a portable, English-facing Typst authoring skill and the updater and validation workflows that maintain it. + +This specification defines the `typst-document-authoring-skill` capability, which is concretely implemented by the portable `typst-grammar-authoring` skill (distributed as `typst-grammar-authoring/SKILL.md`) and its repo-local updater workflow `update-typst-grammar-authoring`. + ## Requirements ### Requirement: Portable authoring skill is English-facing and self-contained The distributed `typst-grammar-authoring` skill SHALL be complete as a From 6141aed83c5d6d8c6e79f6cdb11d5e6d2fbb3900 Mon Sep 17 00:00:00 2001 From: Myriad-Dreamin <35292584+Myriad-Dreamin@users.noreply.github.com> Date: Sun, 15 Mar 2026 18:40:33 +0800 Subject: [PATCH 6/7] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../scripts/generate_grammar_catalog.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.codex/skills/update-typst-grammar-authoring/scripts/generate_grammar_catalog.py b/.codex/skills/update-typst-grammar-authoring/scripts/generate_grammar_catalog.py index 86f32e7..28092b9 100644 --- a/.codex/skills/update-typst-grammar-authoring/scripts/generate_grammar_catalog.py +++ b/.codex/skills/update-typst-grammar-authoring/scripts/generate_grammar_catalog.py @@ -218,11 +218,15 @@ def render_skill_lookup(catalog: dict) -> str: def update_skill(skill_path: Path, generated_lookup: str) -> None: skill_text = skill_path.read_text(encoding="utf-8") pattern = re.compile( - rf"{re.escape(GENERATED_BEGIN)}\n.*?{re.escape(GENERATED_END)}", + rf"{re.escape(GENERATED_BEGIN)}(?P\r?\n).*?{re.escape(GENERATED_END)}", re.DOTALL, ) - replacement = f"{GENERATED_BEGIN}\n{generated_lookup}{GENERATED_END}" - updated_text, count = pattern.subn(lambda _: replacement, skill_text, count=1) + + def _replacement(match: re.Match[str]) -> str: + newline = match.group("newline") + return f"{GENERATED_BEGIN}{newline}{generated_lookup}{GENERATED_END}" + + updated_text, count = pattern.subn(_replacement, skill_text, count=1) if count != 1: raise ValueError(f"Could not find generated section markers in {skill_path}") skill_path.write_text(updated_text, encoding="utf-8") From f8943b40b2cbb191032d0edb1956f291571774f3 Mon Sep 17 00:00:00 2001 From: Myriad-Dreamin Date: Sun, 15 Mar 2026 18:41:39 +0800 Subject: [PATCH 7/7] fix: render example block --- .../scripts/generate_grammar_catalog.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/.codex/skills/update-typst-grammar-authoring/scripts/generate_grammar_catalog.py b/.codex/skills/update-typst-grammar-authoring/scripts/generate_grammar_catalog.py index 28092b9..fdcfc1f 100644 --- a/.codex/skills/update-typst-grammar-authoring/scripts/generate_grammar_catalog.py +++ b/.codex/skills/update-typst-grammar-authoring/scripts/generate_grammar_catalog.py @@ -85,7 +85,9 @@ def repo_relative(path: Path, repo_root: Path) -> str: return path.resolve().as_posix() -def parse_categories(source_text: str, source_path: Path, repo_root: Path) -> list[dict]: +def parse_categories( + source_text: str, source_path: Path, repo_root: Path +) -> list[dict]: categories = [] category_matches = list(CATEGORY_PATTERN.finditer(source_text)) if not category_matches: @@ -100,7 +102,9 @@ def parse_categories(source_text: str, source_path: Path, repo_root: Path) -> li ) section_text = source_text[section_start:section_end] source_heading = match.group("source_heading") - english_heading = CATEGORY_MAP.get(source_heading, {}).get("title", source_heading) + english_heading = CATEGORY_MAP.get(source_heading, {}).get( + "title", source_heading + ) category_id = CATEGORY_MAP.get(source_heading, {}).get( "id", f"category-{index + 1}", @@ -180,9 +184,9 @@ def grouped_entries(entries: list[dict]) -> list[dict]: def render_example_block(code: str) -> list[str]: return [ - "~~~typ", + "```typ", code, - "~~~", + "```", ] @@ -196,7 +200,9 @@ def render_skill_lookup(catalog: dict) -> str: ] ) for group in grouped_entries(category["entries"]): - inline_examples = [code for code in group["examples"] if is_inline_safe(code)] + inline_examples = [ + code for code in group["examples"] if is_inline_safe(code) + ] if len(inline_examples) == len(group["examples"]): joined = "; ".join(f"`{code}`" for code in inline_examples) lines.append(f"- `{group['lookup_key']}`: {joined}")