From d8ccaecade6982efd4bdb1c8af0df29eacfdd5ce Mon Sep 17 00:00:00 2001 From: hypercross Date: Sat, 12 Sep 2026 17:19:38 +0800 Subject: [PATCH] chore(skill): Own tttk-authoring in the repo Move the skill to .agents/skills/tttk-authoring and point gen:skill-reference at it. Activate it by symlinking ~/.agents/skills/tttk-authoring to the in-repo copy. Extend the reference drift guard to cover the skill copy now that it is versioned, and document activation in docs/development.md. --- .agents/skills/tttk-authoring/SKILL.md | 138 +++++++ .agents/skills/tttk-authoring/csv.md | 42 ++ .../tttk-authoring/examples/adventure.md | 68 ++++ .../tttk-authoring/examples/encounters.csv | 36 ++ .../tttk-authoring/examples/guardian.csv | 2 + .../tttk-authoring/examples/locations.csv | 47 +++ .agents/skills/tttk-authoring/layout.md | 20 + .agents/skills/tttk-authoring/markdown.md | 39 ++ .agents/skills/tttk-authoring/reference.md | 365 ++++++++++++++++++ .agents/skills/tttk-authoring/yaml-tag.md | 66 ++++ docs/check-plan.md | 4 +- docs/development.md | 26 ++ docs/skill-reference-plan.md | 21 +- package.json | 2 +- src/cli/validate/known.test.ts | 10 +- 15 files changed, 870 insertions(+), 16 deletions(-) create mode 100644 .agents/skills/tttk-authoring/SKILL.md create mode 100644 .agents/skills/tttk-authoring/csv.md create mode 100644 .agents/skills/tttk-authoring/examples/adventure.md create mode 100644 .agents/skills/tttk-authoring/examples/encounters.csv create mode 100644 .agents/skills/tttk-authoring/examples/guardian.csv create mode 100644 .agents/skills/tttk-authoring/examples/locations.csv create mode 100644 .agents/skills/tttk-authoring/layout.md create mode 100644 .agents/skills/tttk-authoring/markdown.md create mode 100644 .agents/skills/tttk-authoring/reference.md create mode 100644 .agents/skills/tttk-authoring/yaml-tag.md diff --git a/.agents/skills/tttk-authoring/SKILL.md b/.agents/skills/tttk-authoring/SKILL.md new file mode 100644 index 0000000..dbf0bd1 --- /dev/null +++ b/.agents/skills/tttk-authoring/SKILL.md @@ -0,0 +1,138 @@ +--- +name: tttk-authoring +description: Author and edit TTRPG game design documents for the tttk (Tabletop Toolkit) CLI. Use when writing or modifying markdown content that uses tttk custom elements (:md-dice, :md-table, :md-deck, :md-pins, :md-commander, etc.), custom fenced code blocks (role=spark-table / declare / file / tag), companion CSV data files, or when previewing content with `tttk serve` and validating it with `tttk check`. +--- + +# Authoring tttk game design documents + +`tttk` (Tabletop Toolkit) renders a folder of markdown into an interactive TTRPG +web app. A game design document is **ordinary GFM markdown** plus exactly two +extensions: + +1. **Custom elements** written with `marked-directive` syntax: `:name[content]{attrs}`. +2. **Custom fenced code blocks** whose info string carries a `role=` attribute. + +Everything else — headings, lists, tables, blockquotes, alerts, mermaid — is +standard markdown. Do not invent element names, props, or roles; check +`reference.md` when unsure. Deeper topics have their own files: `yaml-tag.md` +(deck config), `csv.md` (data files), `layout.md` (columns), and `markdown.md` +(alerts and mermaid). + +## Workflow + +- Content lives in a directory (commonly `content/`). Preview with hot reload: + `npx tttk serve ./content` (default port 3000, override with `-p 8080`). +- **Validate after every edit:** `npx tttk check ./content`. It exits `1` when + there are errors and prints `file:line` diagnostics. Use `--json` for + machine-readable output and `--quiet` for errors only. This is the primary + verification loop: write → check → fix. +- The scanner picks up `.md`, `.csv`, `.yarn`, and `.svg` files. +- All paths in directives are **relative to the current markdown file** + (`./data.csv`, `../shared/npcs.csv`, `./images/map.png`). +- A typical game/adventure folder: + ``` + my-adventure/ + ├── design.md # design notes (plain markdown) + ├── adventure.md # the document players/GM read + ├── locations.csv # data referenced by :md-table + ├── cast.csv + └── route.csv + ``` + +## Directive syntax + +``` +:name[content]{key="value" .class #id} +``` + +- `[content]` is the directive body — a path, dice formula, color, etc. +- `{key="value"}` sets attributes. `{.class}` and `{#id}` are shorthands. +- Container directives use `::: name` … `:::`; nest deeper with `::::`, `:::::`. + +Use the **registered element names**: `:md-dice`, not `:dice`. The full list is +in `reference.md`. + +## Code block roles + +Fenced blocks are dispatched by the `role=` attribute in the info string: + +| Info string | Behavior | +|---|---| +| ` ```csv role=spark-table ` | Body is CSV; replaced with `:md-table[./csv_]`. First column header must be a dice formula (`d6`, `2d6+1`, …). | +| ` ```markdown role=spark-table ` | Body is a GFM pipe table; converted to CSV, then same as above. | +| ` ```csv role=declare ` | Defines reactive variables / tag modifiers. Block is stripped. | +| ` ``` role=file ` | Body stored as text content, block stripped, referenced by its derived id. | +| ` ```yaml role=tag ` | Kept intact; rendered as a custom element from YAML (`tag:`, `body:`, …). | +| ` ``` ` (no role) | Rendered as a normal visible code block. | +| unknown role | Warned about and stripped. | + +## Rules that prevent broken output + +- Quote any attribute value containing spaces or `"`: + `{pins="A:30,40 B:10,30"}`. +- In CSV, wrap fields containing `,`, `"`, or a newline in double quotes and + escape `"` as `""`. Lines starting with `#` are comments. +- `label` and `body` are the conventional CSV columns. Put titles inside `body` + as markdown headings; use **bold labels** for sub-fields, not headings. +- A `group` column in a table CSV automatically produces group tabs — no + attribute needed. +- Reference each CSV once per document; don't repeat the same `:md-table`. +- For card decks, use the `yaml role=tag` form with `tag: md-deck` (see + `yaml-tag.md`). The `:md-deck[...]{...}` directive is legacy and can't express + template layers or card backs. +- Keep CSV files clean — header row plus data. Put deck config and templates in + the YAML block's `data-config`, not in CSV front matter (see `csv.md`). +- `{{prop}}` in a CSV `body` interpolates another column (or front-matter key) + from the same row; with `{remix}` it pulls from a random row instead. + +## Validation + +`npx tttk check ./content` validates against the same content registry the app +uses, so a clean check means the app resolves the content too. Errors exit `1`: + +| Code | Meaning | +|---|---| +| `TTTK-S002` | container directive `:::` never closed | +| `TTTK-D001` | unknown `:md-*` element name | +| `TTTK-D002` | malformed attribute block (e.g. an unquoted value with a space) | +| `TTTK-R001` | unknown code-block `role=` (the block is silently dropped) | +| `TTTK-R003` | `role=declare` failed to parse | +| `TTTK-R005` | `role=tag` YAML is invalid | +| `TTTK-R006` | `role=tag` missing or naming an unknown element | +| `TTTK-C001` | content reference does not resolve | +| `TTTK-C004` | `:md-embed` cycle | +| `TTTK-L001` | link target file does not exist | +| `TTTK-V001` | CSV failed to parse (e.g. an unclosed quote) | + +Warnings are usually real too: + +| Code | Meaning | +|---|---| +| `TTTK-R002` | spark-table first header is not a dice formula | +| `TTTK-R004` | `role=declare` row skipped (empty `key` or `expr`) | +| `TTTK-C002` | reference resolves to an unexpected file type | +| `TTTK-L002` | `#section` matches no heading | +| `TTTK-A001` | icon missing under `assets/` | +| `TTTK-A002` | image missing | +| `TTTK-V002` | CSV row column count differs from the header | +| `TTTK-V003` | duplicate `label` values | +| `TTTK-V004` | `{{prop}}` is not a CSV column or front-matter key | +| `TTTK-V005` | spark-table roll value is not a number or range | + +`TTTK-C003` (info) flags the same CSV referenced twice by `:md-table` in one +document. Icon checks assume no `assets/` directory exists above the checked +directory. The full code catalog lives in `reference.md`. + +## Reference + +- `reference.md` — **generated** catalog of every custom element, inline + directive, code block role, and dice notation. Do not edit by hand; + regenerate from the repo with `npm run gen:skill-reference`. +- `yaml-tag.md` — `role=tag` blocks, `data-config` layers, card backs, and the + legacy `:md-deck` layer syntax. +- `csv.md` — CSV authoring: columns, `{{prop}}` interpolation, quoting, and the + discouraged front-matter form. +- `layout.md` — column layout markers. +- `markdown.md` — GitHub alerts and mermaid diagrams. +- `examples/adventure.md` and `examples/encounters.csv` — a complete minimal + document and its data file. diff --git a/.agents/skills/tttk-authoring/csv.md b/.agents/skills/tttk-authoring/csv.md new file mode 100644 index 0000000..0c496b0 --- /dev/null +++ b/.agents/skills/tttk-authoring/csv.md @@ -0,0 +1,42 @@ +# CSV data files + +**Keep CSV files clean** — a header row and data rows, nothing else. Put deck +configuration and templates in the `yaml role=tag` block's `data-config`, not in +the CSV. This keeps data portable and presentation in one place. See +`reference.md` for the element and role catalog. + +```csv +label,body,name,type +1,"### 火球术卷轴 + +一张泛黄的羊皮纸。",火球术卷轴,消耗品 +``` + +- `label` — unique row id (number or text). +- `body` — main content, supports markdown. +- Any other columns are custom fields. +- `{{prop}}` in `body` interpolates another column from the same row (or a + random row when the table uses `{remix}`). +- Wrap values containing `,`, `"`, or newlines in double quotes; escape `"` as + `""`. Lines beginning with `#` are comments. + +## Front matter (avoid) + +The parser also accepts YAML front matter delimited by `---`, which can declare +`fields`, a `deck:` block, and arbitrary keys merged into every row. **Prefer +not to use it** — it mixes presentation into the data file. Use the YAML tag +block instead. + +```csv +--- +fields: + - name: name + description: 卡牌名称 +deck: + size: 63x88 + grid: 5x5 +game_system: D&D 5e +--- +label,body,name,type +1,"### 火球术卷轴",火球术卷轴,消耗品 +``` \ No newline at end of file diff --git a/.agents/skills/tttk-authoring/examples/adventure.md b/.agents/skills/tttk-authoring/examples/adventure.md new file mode 100644 index 0000000..674cd18 --- /dev/null +++ b/.agents/skills/tttk-authoring/examples/adventure.md @@ -0,0 +1,68 @@ +# The Hollow Mill + +## Adventure brief + +The mill on the village edge has stood silent for a year. Last week its wheel +began turning again — on its own. + +> [!WARNING] +> The mill's guardian is not hostile by default. Attacking it forfeits the +> peaceful resolution. + +## Locations + +:md-table[./locations.csv] + +## Random encounters + +:md-table[./encounters.csv]{roll=true} + +## The mill guardian + +:md-dice[1d20+3]{key="guardian-attack"} + +The guardian strikes with :[sword] for :md-dice[2d6+2]{key="guardian-damage"}. + +## Encounter table (inline) + +```csv role=spark-table roll=true +d6,Encounter +1,A crow watches from the rafters. +2,The floorboards groan underfoot. +3,A cold draft carries the smell of grain. +4,The wheel outside stops for one breath. +5,A sack of flour splits open. +6,The guardian's lantern flickers to life. +``` + +## Route + +```mermaid +graph TD + A[Village] --> B[Mill Road] + B --> C{Mill Door} + C -->|Force| D[Ground Floor] + C -->|Listen| E[Secret Cellar] + D --> F[Wheel Room] + E --> F +``` + +## Guardian card + +```yaml role=tag +tag: md-deck +body: ./guardian.csv +data-config: + size: 63x88 + grid: 5x5 + layers: + - prop: name + pos: 1,1-5,1 + font: 12 + - template: | + **{{type}}** — {{hp}} HP + {{description}} + pos: 1,3-5,8 + font: 3 + align: l +``` diff --git a/.agents/skills/tttk-authoring/examples/encounters.csv b/.agents/skills/tttk-authoring/examples/encounters.csv new file mode 100644 index 0000000..d136e64 --- /dev/null +++ b/.agents/skills/tttk-authoring/examples/encounters.csv @@ -0,0 +1,36 @@ +group,label,body +Mill Road,1,"### A crow on the fence + +A crow fixes you with one eye, then flies toward the mill. + +**Interaction**: Follow it (Perception 3/1) + +**Risk**: None" +Mill Road,2,"### Broken cart + +An overturned cart blocks half the road. + +**Interaction**: Clear it (Strength 3/2) + +**Risk**: Failure costs 1 Stamina" +Mill Road,3,"### Cold spot + +The air turns suddenly cold. + +**Interaction**: Press on (Constitution 3/1) + +**Risk**: Failure deals 1 Constitution damage" +Wheel Room,1,"### The turning wheel + +The wheel grinds without water. + +**Interaction**: Jam the gears (Strength 3/3) + +**Risk**: Failure deals 1 Agility damage" +Wheel Room,2,"### Flour dust + +Thick dust fills the air. + +**Interaction**: Cover your mouth (Constitution 3/1) + +**Risk**: Failure deals 1 Constitution damage" diff --git a/.agents/skills/tttk-authoring/examples/guardian.csv b/.agents/skills/tttk-authoring/examples/guardian.csv new file mode 100644 index 0000000..5822d90 --- /dev/null +++ b/.agents/skills/tttk-authoring/examples/guardian.csv @@ -0,0 +1,2 @@ +label,name,type,hp,description +1,The Hollow Miller,Spirit,10,"Bound to the wheel by an old promise. Speaks only in the sound of grinding stone." diff --git a/.agents/skills/tttk-authoring/examples/locations.csv b/.agents/skills/tttk-authoring/examples/locations.csv new file mode 100644 index 0000000..8d77cc2 --- /dev/null +++ b/.agents/skills/tttk-authoring/examples/locations.csv @@ -0,0 +1,47 @@ +label,body +Village,"### Village + +**Type**: Starting location + +A quiet village at the edge of the marsh. The miller's daughter asks after her +father. + +**Interaction**: Ask about the mill (Charisma 3/1) + +**Supplies**: Rope, torches, one healing draught" +Mill Road,"### Mill Road + +**Type**: Route + +A muddy track winding between hedgerows toward the mill. + +**Interaction**: Spot tracks (Perception 3/1) + +**Encounters**: 3 random encounters" +Ground Floor,"### Ground Floor + +**Type**: Hub + +Sacks of grain, a cold hearth, and a ladder to the wheel room. + +**Interaction**: +- Search the hearth (Perception 3/2) — find a brass key +- Climb the ladder (Agility 3/1)" +Secret Cellar,"### Secret Cellar + +**Type**: Archive + +A low cellar beneath the millstones. Ledgers line the walls. + +**Interaction**: Read the ledgers (Intelligence 3/2) — learn the guardian's name + +**Clue**: The last entry is dated one year ago, in the miller's hand" +Wheel Room,"### Wheel Room + +**Type**: Finale + +The great wheel turns in silence, though no water reaches it. + +**Interaction**: +- Speak the guardian's name (Charisma 3/3) — peaceful resolution +- Break the wheel (Strength 3/4) — hostile resolution" diff --git a/.agents/skills/tttk-authoring/layout.md b/.agents/skills/tttk-authoring/layout.md new file mode 100644 index 0000000..e195d1e --- /dev/null +++ b/.agents/skills/tttk-authoring/layout.md @@ -0,0 +1,20 @@ +# Column layout + +Wrap content in columns using line markers. This is a tttk extension on top of +standard markdown; see `reference.md` for the element and role catalog. + +```markdown +|--- +Left column +-|- +Right column +--| +``` + +| Marker | Meaning | +|---|---| +| `\|---` | Start the column container | +| `-\|-` | Divider between columns | +| `--\|` | End the container | + +Extra dashes add a `-N` class suffix (e.g. `-|--` → `col-2`) for custom widths. \ No newline at end of file diff --git a/.agents/skills/tttk-authoring/markdown.md b/.agents/skills/tttk-authoring/markdown.md new file mode 100644 index 0000000..b2f1bc6 --- /dev/null +++ b/.agents/skills/tttk-authoring/markdown.md @@ -0,0 +1,39 @@ +# Alerts and mermaid + +Standard markdown features that `tttk` renders. They are not tttk syntax — see +`reference.md` for the element and role catalog. + +## GitHub-style alerts + +```markdown +> [!NOTE] +> 这是一条备注 + +> [!TIP] +> 提示内容 + +> [!IMPORTANT] +> 重要信息 + +> [!WARNING] +> 警告信息 + +> [!CAUTION] +> 危险警告 +``` + +## Mermaid diagrams + +Fenced `mermaid` blocks render as diagrams: + +````markdown +```mermaid +graph TD + A[开始] --> B{选择} + B -->|选项 1| C[结果 1] + B -->|选项 2| D[结果 2] +``` +```` + +Supported: flowchart, sequenceDiagram, classDiagram, stateDiagram, erDiagram, +pie. \ No newline at end of file diff --git a/.agents/skills/tttk-authoring/reference.md b/.agents/skills/tttk-authoring/reference.md new file mode 100644 index 0000000..1a7a70c --- /dev/null +++ b/.agents/skills/tttk-authoring/reference.md @@ -0,0 +1,365 @@ +# tttk syntax reference + +Generated from `src/cli/validate/known.ts` by `npm run gen:reference`. +Do not edit by hand. + +Complete catalog of the custom elements, inline directives, and code block +roles that `tttk` understands. Everything not listed here is standard GFM +markdown. Element names are prefixed `md-` and must match exactly. + +## Contents + +- [Directive syntax](#directive-syntax) +- [Custom elements](#custom-elements) +- [Inline directives](#inline-directives) +- [Code block roles](#code-block-roles) +- [Dice notation](#dice-notation) +- [CSV data files](#csv-data-files) + +## Directive syntax + +```text +:name[content]{key="value" .class #id} +``` + +| Part | Meaning | Example | +|---|---|---| +| `:name` | Registered element name | `:md-dice` | +| `[content]` | Directive body | `[2d6+d8]` | +| `{attrs}` | Attributes | `{key="attack"}` | +| `{.class}` | Shorthand for `class` | `{.text-red-500}` | +| `{#id}` | Shorthand for `id` | `{#my-dice}` | + +Container directives: + +```markdown +::: container +Content here, including nested directives. +:dice[1d20] +::: +``` + +Nest with more colons (`::::`, `:::::`). + +## Custom elements + +| Directive | Body | Key attributes | +|---|---|---| +| `:md-dice` | dice formula | `key` | +| `:md-table` | CSV path or inline id | `roll`, `remix` | +| `:md-deck` | CSV path | `size`, `grid`, `layers` | +| `:md-link` | path, optional #section | — | +| `:md-embed` | path, optional #section | `headingBase` | +| `:md-bg` | image path or CSS color | `fit` | +| `:md-border` | color or border image | `width`, `slice` | +| `:md-font` | font family | `source`, `weight`, `color` | +| `:md-pins` | image path | `pins`, `fixed`, `labelStart` | +| `:md-commander` | command template CSV | `placeholder`, `class`, `height` | +| `:md-yarn-spinner` | Yarn Spinner file | `start` | + +### `:md-dice` + +Clickable dice roller; `key` stores the result in the URL. + +Clicking the text rolls; clicking the 🎲 resets to the formula. With `key`, the result is stored in the URL as `?dice-=`. + +- Body: dice formula +- Attributes: `key` + +```markdown +:md-dice[1d20+5]{key="attack"} +``` + +```markdown +:md-dice[2d6+3] +``` + +```markdown +:md-dice[3d6kh1] +``` + +### `:md-table` + +Tab-separated view of a CSV with optional group tabs. + +`roll` adds a 🎲 random-tab button; `remix` makes `{{prop}}` references pull from a random row. A `group` column in the CSV produces group tabs automatically. + +- Body: CSV path or inline id +- Attributes: `roll`, `remix` + +```markdown +:md-table[./npcs.csv] +``` + +```markdown +:md-table[./encounters.csv]{roll=true} +``` + +```markdown +:md-table[./quests.csv]{roll=true remix=true} +``` + +### `:md-deck` + +Card layout from a CSV (legacy; prefer `yaml role=tag`). + +Prefer the `yaml role=tag` form — it is the only form that supports template layers, per-layer alignment, and card backs. The directive form is legacy and only supports the compact `layers` string. + +- Body: CSV path +- Attributes: `size`, `grid`, `layers` + +```markdown +:md-deck[./cards.csv]{size="63x88" grid="5x5" layers="title:1,1-5,1f8 body:1,5-5,8f3"} +``` + +### `:md-link` + +Clickable link that expands the target article inline. + +- Body: path, optional #section + +```markdown +:md-link[./rules.md] +``` + +```markdown +:md-link[./rules.md#combat] +``` + +### `:md-embed` + +Renders the target article or section inline. + +`headingBase` shifts the heading levels of the embedded content. + +- Body: path, optional #section +- Attributes: `headingBase` + +```markdown +:md-embed[./rules.md#combat]{headingBase=1} +``` + +### `:md-bg` + +Sets the surrounding article card background. + +`fit` is one of `cover` (default), `contain`, `fill`, `none`, `scale-down`. + +- Body: image path or CSS color +- Attributes: `fit` +- Body may also be a CSS color (e.g. `#2a1a3a`, `crimson`). + +```markdown +:md-bg[./images/dungeon-bg.jpg]{fit="cover"} +``` + +```markdown +:md-bg[#2a1a3a] +``` + +### `:md-border` + +Applies a border to the article card. + +Body is a CSS color or a border-image path. Class flags select sides (`t b l r all`), style (`solid dashed dotted double groove ridge inset outset none`), and image repeat (`stretch repeat round space`). + +- Body: color or border image +- Attributes: `width`, `slice` +- Body may also be a CSS color (e.g. `#2a1a3a`, `crimson`). + +```markdown +:md-border[crimson]{.b} +``` + +```markdown +:md-border[./frame.png]{width="2mm" slice="10" .all.round} +``` + +### `:md-font` + +Sets the article font. + +`source` is `google`, `emfont`, or `local` (default). + +- Body: font family +- Attributes: `source`, `weight`, `color` + +```markdown +:md-font[Noto Sans SC]{source="google" weight="700" color="#1a1a2e"} +``` + +### `:md-pins` + +Numbered markers on an image. + +`pins` is a space-separated list of `label:x,y` (percent coordinates). `fixed` makes it read-only; otherwise click to add and click a pin to remove. `labelStart` sets the first label (letter or number). + +- Body: image path +- Attributes: `pins`, `fixed`, `labelStart` + +```markdown +:md-pins[./images/battle-map.png]{pins="A:25,50 B:75,30 C:50,80" fixed} +``` + +```markdown +:md-pins[./images/dungeon.png]{labelStart="1"} +``` + +### `:md-commander` + +Interactive command history / tracker panel. + +The body is a CSV of command templates (`command,parameter,label,description,insertedText`). + +- Body: command template CSV +- Attributes: `placeholder`, `class`, `height` + +```markdown +:md-commander[./commands.csv]{placeholder="输入命令…" height="400px"} +``` + +### `:md-yarn-spinner` + +Runs a Yarn Spinner dialogue file. + +`start` names the entry node (default `start`). + +- Body: Yarn Spinner file +- Attributes: `start` + +```markdown +:md-yarn-spinner[./story.yarn]{start="start"} +``` + +## Inline directives + +### `:[name]` + +Inserts an icon looked up under `assets/` (searching upward from the file). + +Default extension is `.png`; `.svg`, `.gif`, `.jpg`, `.jpeg`, and `.webp` are also supported. `label` overlays text, `repeat` duplicates N times, and `join` places a tag name between repeats. + +- Body: icon name +- Attributes: `label`, `repeat`, `join`, `class` + +```markdown +:[attack] :[shield.svg] :[potion] +``` + +```markdown +:[attack]{label=ATK repeat=3 join=br} +``` + +### `:cmd[command]` + +Renders a clickable span that dispatches a journal command. + +- Body: journal command +- Attributes: `label` + +```markdown +:cmd[roll 1d20+5]{label=Roll initiative} +``` + +```markdown +:cmd[spark dungeon room-type]{label=Random room} +``` + +## Code block roles + +The `role=` attribute on the info string determines how a fenced block is +handled. + +### `role=spark-table` + +CSV (or markdown pipe table) rendered as `:md-table`. + +The first column header must be a dice formula; each row's first cell is the roll value (a number, or a range like `1-3`). Extra fence attributes are forwarded to the generated `:md-table`, e.g. ` ```csv role=spark-table roll=true `. + +````markdown +```csv role=spark-table +d6,结果 +1,遭遇强盗 +2,平安无事 +``` +```` + +````markdown +```markdown role=spark-table +| d6 | 结果 | +|----|------| +| 1 | 遭遇强盗 | +| 2 | 平安无事 | +``` +```` + +### `role=declare` + +Reactive variable / tag-modifier definitions. + +Columns (any order): `tag`, `threshold`, `key`, `expr`. A row with an empty `tag` declares a variable; a row with a `#tag` applies a modifier when that tag is active. + +````markdown +```csv role=declare +tag,threshold,key,expr +,,$hp,$con*5+$mod_hp +#warrior,2,$mod_hp,20 +``` +```` + +### `role=file` + +Stored as inline text content and stripped. + +Referenced by the derived inline id, e.g. `:md-embed[./text_]`. + +````markdown +```text role=file +Stored as inline content. +``` +```` + +### `role=tag` + +YAML rendered as a custom element. + +`tag:` names the element, `body:` becomes its content, and any other key becomes an attribute. Fence-line attributes override the YAML body. See `yaml-tag.md`. + +````markdown +```yaml role=tag +tag: md-deck +body: ./cards.csv +size: 63x88 +grid: 5x5 +``` +```` + +### No role / unknown role + +No role → a normal visible code block. Unknown role → warned about and +stripped. + +## Dice notation + +Used by `:md-dice`, spark-table headers, and the journal `/roll`: + +| Notation | Meaning | +|---|---| +| `2d6` | two six-sided dice | +| `1d20+5` | d20 plus modifier | +| `3d6kh1` | keep highest 1 | +| `3d6dl2-1` | drop lowest 2, minus 1 | +| `{d4, d8}` | choose one of the listed dice | + +## CSV data files + +Keep CSV files clean — a header row and data rows. Put deck configuration +and templates in a `yaml role=tag` block's `data-config`. + +- `label` — unique row id (number or text). +- `body` — main content, supports markdown. +- Any other columns are custom fields. +- `{{prop}}` in `body` interpolates another column (or front-matter key) + from the same row; with `{remix}` it pulls from a random row. +- Wrap values containing `,`, `"`, or newlines in double quotes; escape + `"` as `""`. Lines beginning with `#` are comments. diff --git a/.agents/skills/tttk-authoring/yaml-tag.md b/.agents/skills/tttk-authoring/yaml-tag.md new file mode 100644 index 0000000..a9abfe8 --- /dev/null +++ b/.agents/skills/tttk-authoring/yaml-tag.md @@ -0,0 +1,66 @@ +# YAML tag blocks + +`` ```yaml role=tag `` renders a custom element from YAML. `tag` names the +element, `body` becomes its content, and any other key becomes an attribute. +Fence-line attributes override the YAML body. See `reference.md` for the +element and role catalog. + +````markdown +```yaml role=tag +tag: md-deck +body: ./cards.csv +data-config: + size: 63x88 + grid: 5x5 + layers: + - prop: title + pos: 1,1-5,1 + font: 12 + - template: | + **{{name}}** — {{type}} + {{description}} + pos: 1,3-5,8 + font: 3 + align: l +``` +```` + +Simple cases can stay on the fence line: + +````markdown +```yaml role=tag tag=md-dice body=3d6 +``` +```` + +## `data-config` layers + +Layers accept: + +- `prop` — read a CSV column, or `template` — markdown with `{{var}}` + substitution. +- `pos` — `x1,y1-x2,y2` grid span (1-based). +- `font` / `fontSize` — font size in mm. +- `orientation` — `n` / `s` / `e` / `w`. +- `align` — `l` / `c` / `r`, optionally prefixed `t` / `b`. +- `visible` — show or hide the layer. + +Use `back_layers` for card backs. + +## Legacy `:md-deck` directive + +`:md-deck[./cards.csv]{...}` still works but is **legacy** — prefer the YAML tag +block above. It only supports the compact `layers` string and cannot express +template layers or backs. + +```markdown +:md-deck[./cards.csv]{size="63x88" grid="5x5" layers="title:1,1-5,1f8 body:1,5-5,8f3"} +``` + +Compact layer syntax: `prop:x1,y1-x2,y2[f][nsew][[t|b]lcr]` + +- `prop` — CSV column to render. +- `x1,y1-x2,y2` — grid span (1-based). +- `f` — font size in mm, e.g. `f8`. +- `nsew` — text orientation (default `n`). +- `l` / `c` / `r` — horizontal align, optionally prefixed `t` / `b` for vertical + (`tl`, `bc`, …). \ No newline at end of file diff --git a/docs/check-plan.md b/docs/check-plan.md index 655cc8b..603cf85 100644 --- a/docs/check-plan.md +++ b/docs/check-plan.md @@ -337,7 +337,7 @@ Exit code is the contract that makes `check` usable in an agent loop. 11. **Reference generator** — `scripts/generate-reference.ts` renders the `known.ts` catalog to markdown. Canonical output `docs/reference.md` (versioned, testable); `--out` can target the skill copy at - `~/.agents/skills/tttk-authoring/reference.md`. Add `npm run + `.agents/skills/tttk-authoring/reference.md`. Add `npm run gen:reference`. 12. **Drift guard** — a test that greps `customElement("md-*")` call sites under `src/components` and asserts each appears in `known.ts` (and vice @@ -363,7 +363,7 @@ Exit code is the contract that makes `check` usable in an agent loop. - Update `docs/cli.md`: replace the `compile` section with `check`; document codes, `--json`, exit codes. - Update `README.md`. -- Update `~/.agents/skills/tttk-authoring/SKILL.md`: the verification step +- Update `.agents/skills/tttk-authoring/SKILL.md`: the verification step becomes **`npx tttk check ./content`**, and the workflow becomes write → check → fix. Remove the "compile is a no-op stub" note. Cross-link the code catalog (`reference.md`) with the diagnostic codes. diff --git a/docs/development.md b/docs/development.md index a857d81..1794702 100644 --- a/docs/development.md +++ b/docs/development.md @@ -60,6 +60,9 @@ tttk/ │ ├── styles.css # 样式 │ ├── index.html # HTML 模板 │ └── global.d.ts # 类型声明 +├── .agents/ +│ └── skills/ +│ └── tttk-authoring/ # 随项目维护的 agent skill(见下文) ├── bin/ │ └── cli/ # CLI 二进制文件 ├── content/ # 示例内容 @@ -71,6 +74,29 @@ tttk/ └── jest.config.js ``` +## Agent skills + +`tttk-authoring` skill 由本项目维护,源码位于 +`.agents/skills/tttk-authoring/`。要让 agent 加载它,把全局 skill 目录 +链接到仓库内的副本: + +```bash +# Windows(无需管理员权限的目录联接) +cmd //c mklink /J %USERPROFILE%\.agents\skills\tttk-authoring "%CD%\.agents\skills\tttk-authoring" + +# macOS / Linux +ln -s "$PWD/.agents/skills/tttk-authoring" ~/.agents/skills/tttk-authoring +``` + +`reference.md` 由 `known.ts` 生成,不要手改: + +```bash +npm run gen:skill-reference +``` + +其余文件(`SKILL.md`、`yaml-tag.md`、`csv.md`、`layout.md`、`markdown.md`) +为手写内容。 + ## 技术栈 | 类别 | 技术 | diff --git a/docs/skill-reference-plan.md b/docs/skill-reference-plan.md index 55cd761..2987062 100644 --- a/docs/skill-reference-plan.md +++ b/docs/skill-reference-plan.md @@ -10,6 +10,10 @@ skill's `reference.md` is now generated and byte-identical to `docs/reference.md`, with the extra content moved to `layout.md`, `yaml-tag.md`, `csv.md`, and `markdown.md`. +The skill is now **owned by the repo** at `.agents/skills/tttk-authoring/` and +activated by symlinking `~/.agents/skills/tttk-authoring` to it (see +`docs/development.md` § Agent skills). + ## 1. Goal Make the `tttk-authoring` skill's `reference.md` a **generated** file — the same @@ -19,10 +23,10 @@ hand-maintained element list, and no content lost in the swap. ## 2. Current state -The skill (`~/.agents/skills/tttk-authoring/`) has a hand-written -`reference.md` (~465 lines) and `SKILL.md`. The repo now generates -`docs/reference.md` from `src/cli/validate/known.ts` via `npm run -gen:reference`. +The skill (originally `~/.agents/skills/tttk-authoring/`, now +`.agents/skills/tttk-authoring/`) had a hand-written `reference.md` (~465 lines) +and `SKILL.md`. The repo generates `docs/reference.md` from +`src/cli/validate/known.ts` via `npm run gen:reference`. What the generated doc already covers: @@ -109,7 +113,7 @@ generator exists to prevent. `package.json`: - `gen:reference` — unchanged (writes `docs/reference.md`). -- `gen:skill-reference` — `gen:reference -- --out ~/.agents/skills/tttk-authoring/reference.md`. +- `gen:skill-reference` — `gen:reference -- --out .agents/skills/tttk-authoring/reference.md`. ## 6. New skill file outlines @@ -156,9 +160,10 @@ generator exists to prevent. checklist and a manual diff before deleting the old file. - **`known.ts` bloat.** Keep `details` to one paragraph per element; long-form material belongs in the companion files. -- **Skill path portability.** `~` expansion in the generator; the skill lives - outside the repo, so the drift guard cannot cover it — document that - `gen:skill-reference` is the refresh command. +- **Skill path portability.** The skill now lives in the repo, so + `gen:skill-reference` writes to a stable path; `~` expansion in `--out` + remains for ad-hoc targets. Activation is a symlink, which is environment + setup rather than repo state — documented in `docs/development.md`. - **Two references.** `docs/reference.md` and the skill copy are both generated from `known.ts`; they must be regenerated together. Note this in `docs/check-plan.md` §9. \ No newline at end of file diff --git a/package.json b/package.json index 58d30a5..029bcfd 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "ttrpg": "node --loader ts-node/esm ./src/cli/index.ts", "tttk": "node --loader ts-node/esm ./src/cli/index.ts", "gen:reference": "node --loader ts-node/esm ./scripts/generate-reference.ts", - "gen:skill-reference": "node --loader ts-node/esm ./scripts/generate-reference.ts --out ~/.agents/skills/tttk-authoring/reference.md", + "gen:skill-reference": "node --loader ts-node/esm ./scripts/generate-reference.ts --out .agents/skills/tttk-authoring/reference.md", "test": "jest", "prepare": "npx husky", "prepublishOnly": "npm run build" diff --git a/src/cli/validate/known.test.ts b/src/cli/validate/known.test.ts index 5e1ca89..a1e3925 100644 --- a/src/cli/validate/known.test.ts +++ b/src/cli/validate/known.test.ts @@ -41,11 +41,11 @@ describe("known element catalog", () => { expect(known).toEqual(registered); }); - test("docs/reference.md is up to date", () => { - const committed = readFileSync( - join(process.cwd(), "docs", "reference.md"), - "utf-8", - ); + test.each([ + ["docs/reference.md"], + [join(".agents", "skills", "tttk-authoring", "reference.md")], + ])("%s is up to date", (relativePath) => { + const committed = readFileSync(join(process.cwd(), relativePath), "utf-8"); expect(committed).toBe(renderReference()); });