@chengzhao-dev/dsh-quarto-book 0.6.2 → 0.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.en.md +60 -31
  2. package/README.md +53 -24
  3. package/lib/index.js +49 -7
  4. package/lib/tools/pass-report.js +12 -12
  5. package/lib/tools/render-check.js +231 -62
  6. package/lib/workspace.js +1 -0
  7. package/locale/en.json +1 -1
  8. package/locale/zh.json +1 -1
  9. package/package.json +13 -7
  10. package/skills/designing-theme/assets/theme/assets/fonts/lxgw-bright-code-common.woff2 +0 -0
  11. package/skills/designing-theme/assets/theme/assets/fonts/lxgw-bright-medium-common.woff2 +0 -0
  12. package/skills/designing-theme/assets/theme/css/content.css +15 -0
  13. package/skills/designing-theme/assets/theme/css/mermaid.css +5 -6
  14. package/skills/designing-theme/knowledge/reading-surface.md +1 -1
  15. package/skills/designing-theme/references/theme-system.md +5 -5
  16. package/skills/designing-theme/scripts/checking-layout.py +60 -12
  17. package/skills/designing-theme/scripts/measuring-pages.mjs +2 -2
  18. package/skills/writing-quarto/SKILL.md +7 -0
  19. package/skills/writing-quarto/examples/02-structure-shape.md +26 -12
  20. package/skills/writing-quarto/examples/03-block-after.md +14 -0
  21. package/skills/writing-quarto/examples/03-qmd-elements.md +45 -2
  22. package/skills/writing-quarto/examples/04-paragraph-cases.md +14 -0
  23. package/skills/writing-quarto/examples/04-paragraph-opening.md +2 -2
  24. package/skills/writing-quarto/examples/05-sentence-frame.md +4 -4
  25. package/skills/writing-quarto/examples/06-wording-cases.md +23 -0
  26. package/skills/writing-quarto/examples/07-layout-list.md +3 -3
  27. package/skills/writing-quarto/examples/08-appendix.md +1 -1
  28. package/skills/writing-quarto/quarto/authoring.md +7 -6
  29. package/skills/writing-quarto/quarto/basics.md +17 -16
  30. package/skills/writing-quarto/quarto/coords.md +2 -1
  31. package/skills/writing-quarto/quarto/figures.md +49 -0
  32. package/skills/writing-quarto/quarto/mermaid.md +41 -5
  33. package/skills/writing-quarto/quarto/prose.md +4 -4
  34. package/skills/writing-quarto/quarto/refactor.md +1 -1
  35. package/skills/writing-quarto/quarto/terminal.md +1 -1
  36. package/skills/writing-quarto/references/book-journey.md +38 -0
  37. package/skills/writing-quarto/references/token-budget.md +26 -0
  38. package/skills/writing-quarto/scripts/checking-callouts.py +2 -2
  39. package/skills/writing-quarto/scripts/checking-dom.py +12 -6
  40. package/skills/writing-quarto/scripts/checking-links.py +29 -13
  41. package/skills/writing-quarto/scripts/checking-names.py +3 -1
  42. package/skills/writing-quarto/scripts/checking-output.py +3 -3
  43. package/skills/writing-quarto/scripts/checking-prose.py +11 -7
  44. package/skills/writing-quarto/scripts/testing-content.py +264 -1
  45. package/skills/writing-quarto/scripts/verifying-content.py +442 -36
  46. package/skills/writing-quarto/steps/03-blocks.md +20 -8
  47. package/skills/writing-quarto/steps/04-paragraphs.md +19 -2
  48. package/skills/writing-quarto/steps/06-wording.md +53 -0
  49. package/skills/writing-quarto/steps/07-layout.md +1 -1
  50. package/skills/writing-quarto/steps/08-reread.md +1 -0
  51. package/skills/writing-quarto/steps/09-check.md +3 -3
  52. package/templates/quarto-book-project/SKILL.md +1 -1
  53. package/templates/quarto-book-project/checks.json +5 -0
package/README.en.md CHANGED
@@ -1,14 +1,11 @@
1
1
  # dsh-quarto-book
2
2
 
3
3
  [![publish](https://github.com/chengzhao-dev/dsh-quarto-book/actions/workflows/publish.yml/badge.svg)](https://github.com/chengzhao-dev/dsh-quarto-book/actions/workflows/publish.yml)
4
+ [![npm](https://img.shields.io/npm/v/@chengzhao-dev/dsh-quarto-book)](https://www.npmjs.com/package/@chengzhao-dev/dsh-quarto-book)
4
5
 
5
6
  **English** | [简体中文](https://github.com/chengzhao-dev/dsh-quarto-book/blob/main/README.md)
6
7
 
7
- Polish Chinese technical chapters, check book structure and mixed Chinese-English prose, and maintain a consistent, readable theme; only inside `type: book` repositories.
8
-
9
- - Write `.qmd`: `writing-quarto` covers the chapter skeleton, structure and Chinese prose.
10
- - Change the theme: `designing-theme` covers CSS, fonts and palettes.
11
- - Check rendered HTML: `quarto_render_check` judges publishability once, and `quarto_pass_report` prints the closing receipt.
8
+ Take a Quarto Book from chapters to publishable output: `writing-quarto` owns the chapter skeleton, whole-book order and Chinese prose; `designing-theme` owns the theme; after a render, `quarto_render_check` decides whether the HTML is publishable and `quarto_pass_report` prints the closing receipt. Both skills appear only inside `type: book` repositories, and the workflow needs Quarto CLI and Python 3.
12
9
 
13
10
  ## What it provides
14
11
 
@@ -19,31 +16,51 @@ Polish Chinese technical chapters, check book structure and mixed Chinese-Englis
19
16
  | `quarto_render_check` | Check that `output-dir` is safe, render once when asked, and inspect HTML in `_book/` for publishing | Editing `.qmd`, fixing config automatically, or re-running a failed render |
20
17
  | `quarto_pass_report` | Compress the run into a fixed-line receipt card: skill, mode, files and steps changed, items held, check verdicts | Saving to disk, quoting chapter prose, or changing the text based on the receipt |
21
18
 
22
- For when the skills appear and which rule each step maps to, see [docs/plugin-flow.md](https://github.com/chengzhao-dev/dsh-quarto-book/blob/main/docs/plugin-flow.md). It is a human-facing walkthrough, not the rule source used while a skill runs.
19
+ Both skills are discovered from the package's `skills/` directory and loaded on demand. Relative paths inside a skill resolve against that skill's directory: `writing-quarto` uses `steps/`, `examples/`, `quarto/`, `references/`, `scripts/`; `designing-theme` uses `references/`, `knowledge/`, `assets/`, `scripts/`.
23
20
 
24
- Both skills are discovered from the package's `skills/` directory and loaded on demand. Relative paths inside a skill resolve against that skill's directory: `writing-quarto` uses `steps/`, `examples/`, `quarto/`, `scripts/`; `designing-theme` uses `references/`, `knowledge/`, `assets/`, `scripts/`.
21
+ ## Quick start
25
22
 
26
- ## Install
23
+ 1. Install the plugin:
27
24
 
28
- ```bash
29
- dsh plugin --profile web add npm:@chengzhao-dev/dsh-quarto-book@0.6.1
30
- ```
25
+ ```bash
26
+ dsh plugin --profile web add npm:@chengzhao-dev/dsh-quarto-book@latest
27
+ ```
31
28
 
32
- Replace `--profile` with the profile you actually run. The desktop app always runs the `desktop` profile, and that profile is owned by the app, so `dsh --profile desktop` is rejected; install or upgrade a plugin with `dsh plugin --profile desktop add <package>`, and use the app's Settings → Plugins → Add plugin with `npm:@chengzhao-dev/dsh-quarto-book@0.6.1` only if the app refuses that write. To follow a GitHub tag instead, use `github:chengzhao-dev/dsh-quarto-book#v0.6.1`.
29
+ 2. Open a session in the book repository. `_quarto.yml` needs a standalone `type: book` line, and the book needs at least one `.qmd` or `.md` chapter source; only then do both skill names appear.
30
+ 3. Call a skill: `writing-quarto` for prose, `designing-theme` for the theme.
31
+
32
+ Replace `--profile` with the profile you actually run. The desktop app always runs the `desktop` profile, and that profile is owned by the app, so `dsh --profile desktop` is rejected; install or upgrade with `dsh plugin --profile desktop add <package>`, and use the app's Settings → Plugins → Add plugin only if the app refuses that write. To pin a version, write `npm:@chengzhao-dev/dsh-quarto-book@0.6.4`; to follow a GitHub tag, write `github:chengzhao-dev/dsh-quarto-book#v0.6.4`.
33
33
 
34
34
  Upgrading from the old unscoped package `dsh-quarto-book` (removed from npm with 0.3.4, so this only affects profiles that installed it earlier): remove it from the same profile first with `dsh plugin --profile <profile> remove dsh-quarto-book`, then add the scoped package. Keeping both installed registers `writing-quarto` and `designing-theme` twice at the same rank 550.
35
35
 
36
- Requires DSH 0.2.x and Node.js 22 or newer. The source is TypeScript; the published package already contains compiled `lib/`, so installation needs no build step. The content, link and naming checks need Python 3; `quarto_render_check` needs the Quarto CLI for an actual render, and the post-render HTML checks also need Python 3. See [docs/plugin-layout.md](https://github.com/chengzhao-dev/dsh-quarto-book/blob/main/docs/plugin-layout.md) for the split.
36
+ Installation is scoped to a profile, not to a repository: it only affects the profile that ran `add`. The workspace check still decides whether the two skill names appear: it checks the session `cwd` for a book, and only then are both names selectable. See [docs/plugin-boundaries.md](https://github.com/chengzhao-dev/dsh-quarto-book/blob/main/docs/plugin-boundaries.md) for the six cases.
37
37
 
38
- Installation is scoped to a profile, not to a repository: it only affects the profile that ran `add`. The workspace gate still decides whether the two skill names appear: it checks the session `cwd` for a book, and only then are both names selectable. See [docs/plugin-boundaries.md](https://github.com/chengzhao-dev/dsh-quarto-book/blob/main/docs/plugin-boundaries.md) for the six cases.
38
+ ## Requirements
39
+
40
+ | Dependency | Purpose |
41
+ | --- | --- |
42
+ | DSH 0.2.x | Host; the plugin registers skills and both tools through `dsh.bundle.patch` |
43
+ | Node.js 22 or newer | Plugin runtime and check scripts |
44
+ | Python 3 | Content, link and naming checks, plus post-render HTML checks |
45
+ | Quarto CLI | Only when `quarto_render_check` actually renders |
46
+
47
+ The published package already contains compiled `lib/`, so installation needs no build step. See [docs/plugin-layout.md](https://github.com/chengzhao-dev/dsh-quarto-book/blob/main/docs/plugin-layout.md) for the split.
39
48
 
40
49
  ## Usage
41
50
 
42
- Load `writing-quarto` or `designing-theme` inside a `type: book` repository and follow the steps in `SKILL.md`. The plugin has no resident process and does not execute a skill when a session starts; seeing a name in the skill catalog only means the session `cwd` passed the gate, and a use happens only when `skill`, `quarto_render_check` or `quarto_pass_report` is invoked. The next invocation rereads the skill text; the tool never retries automatically, so the user decides whether to call it again after a failure.
51
+ ### Writing chapters
52
+
53
+ Load `writing-quarto` in a `type: book` repository and follow the nine steps in `SKILL.md`. Each step opens one same-numbered file in `steps/`; read the same-numbered case in `examples/` only when a signal is unclear; open `quarto/` only for figures, render checks or repository-wide refactors.
54
+
55
+ The repository's root `README.md` is a description page, not a chapter: it takes only steps 5–8, skips the task skeleton and structure of steps 1–4, has no "common errors" or "self-test questions", and never calls `quarto_render_check`. `README.en.md` is treated the same way.
43
56
 
44
- A project supplies exactly one skill of its own: `<book>/.agents/skills/quarto-book-project/`, starting from `templates/quarto-book-project/`. When it exists, both general skills read `SKILL.md` and then the `writing/` files it points to; when it does not, they skip it and do not guess project conventions. **Do not embed same-name skills into a book repository**: DSH picks one skill per name per layer by rank, lowest wins, and `.agents/skills` (rank 200) outranks the packaged provider (rank 550); a copy of `writing-quarto` or `designing-theme` in the book disables the plugin's text.
57
+ The plugin has no daemon and does not run skills when a session starts. A skill name in the catalog only means the check passed; only calling `skill`, `quarto_render_check` or `quarto_pass_report` counts as use. A second call in the same session re-reads the skill body, and failed tools are not retried automatically.
45
58
 
46
- Theme assets live in `skills/designing-theme/assets/theme/`. The package copy is authoritative; the book's `theme/` is the render copy. When a copy exists, `designing-theme` edits only that copy, and only when the user explicitly asks to install the plugin theme into the book and the target directory does not exist yet does the skill copy from the package and place `skills/writing-quarto/scripts/answering-disclosure.lua` at the book's `filters/answering-disclosure.lua`. `_quarto.yml` references only the book's copy, never an npm package or the plugin checkout.
59
+ ### Theming
60
+
61
+ Theme assets live in `skills/designing-theme/assets/theme/`. The package copy is authoritative; a book's `theme/` is the render copy. When a copy exists, `designing-theme` edits only that copy. It copies from the package only when the user explicitly asks to install the theme into the book and the target directory does not exist, and it also places `skills/writing-quarto/scripts/answering-disclosure.lua` at `filters/answering-disclosure.lua`. `_quarto.yml` always references the in-book copy, never an npm package or a checkout path.
62
+
63
+ ### Checks and rendering
47
64
 
48
65
  Run the content and link checks from the `writing-quarto` skill directory:
49
66
 
@@ -54,25 +71,31 @@ python scripts/checking-prose.py --root <book>
54
71
  python scripts/checking-names.py --root <book>
55
72
  ```
56
73
 
57
- `checking-prose.py` reports only high-confidence CJK/Latin spacing and punctuation issues. Add `checking-names.py` only for a whole-repository refactor; ordinary chapter checks run the first three.
74
+ `checking-prose.py` reports only mixed Chinese-English problems that can be judged directly; `checking-names.py` is for repository-wide refactors. For everyday chapter work, the first three are enough.
75
+
76
+ Post-render callouts, HTML output and DOM contracts are checked by `quarto_render_check`, which calls the bundled scripts; run `checking-callouts.py`, `checking-output.py` or `checking-dom.py` by hand only when debugging a script. `designing-theme`'s `scripts/checking-layout.py` checks overflow, contrast and page structure on rendered HTML. Neither tool runs those scripts at load time, and neither runs `quarto render`.
77
+
78
+ Call `quarto_pass_report` once at the end of a run; the card is the receipt, and the reply should not repeat it.
58
79
 
59
- Callouts, HTML output and DOM contracts after a render are checked by `quarto_render_check`, which invokes the packaged scripts; do not run `checking-callouts.py`, `checking-output.py` or `checking-dom.py` by hand. `designing-theme`'s `scripts/checking-layout.py` inspects rendered HTML for overflow, contrast and page structure. `apply()` runs none of these and never runs `quarto render`.
80
+ ### Project skill
60
81
 
61
- At the end of a run, call `quarto_pass_report` once to print the receipt card; the card is the receipt, so do not repeat it in the reply.
82
+ A project keeps its own conventions in one skill: `<book>/.agents/skills/quarto-book-project/`, with a template in `templates/quarto-book-project/`. When it exists, both generic skills read its `SKILL.md` first, then the `writing/` files it points to; when it does not exist, they skip it and do not guess.
83
+
84
+ **Do not vendor a same-named skill into the book repository.** DSH picks one skill per name at its rank: `.agents/skills` (rank 200) wins over the plugin (rank 550), so a copy of `writing-quarto` or `designing-theme` inside the book silently replaces the plugin's rules.
62
85
 
63
86
  ## Documentation
64
87
 
65
88
  | Document | Contents |
66
89
  | --- | --- |
67
90
  | [docs/plugin-flow.md](https://github.com/chengzhao-dev/dsh-quarto-book/blob/main/docs/plugin-flow.md) | The five layers from install to receipt, file routing and render order |
68
- | [docs/plugin-boundaries.md](https://github.com/chengzhao-dev/dsh-quarto-book/blob/main/docs/plugin-boundaries.md) | The six workspace-gate cases and their decision order |
69
- | [docs/plugin-layout.md](https://github.com/chengzhao-dev/dsh-quarto-book/blob/main/docs/plugin-layout.md) | Repository tree, published files and the Python/Quarto split |
91
+ | [docs/plugin-boundaries.md](https://github.com/chengzhao-dev/dsh-quarto-book/blob/main/docs/plugin-boundaries.md) | The six workspace-check cases and their order |
92
+ | [docs/plugin-layout.md](https://github.com/chengzhao-dev/dsh-quarto-book/blob/main/docs/plugin-layout.md) | Repository layout, package contents, and the Python/Quarto split |
70
93
  | [docs/writing-quarto-layout.md](https://github.com/chengzhao-dev/dsh-quarto-book/blob/main/docs/writing-quarto-layout.md) | The writing skill's directory layers |
71
94
  | [docs/versioning.md](https://github.com/chengzhao-dev/dsh-quarto-book/blob/main/docs/versioning.md) | Version numbers and release order |
72
95
 
73
- ## Local development
96
+ ## Development
74
97
 
75
- The source is TypeScript and the compiled `lib/` is committed. Published packages load `lib/` directly, so installation needs no build step. During development, link this checkout into the writing profile instead of copying skills into a book:
98
+ The source is TypeScript and the compiled `lib/` is committed; the published package loads `lib/` directly and needs no build on install. Link this checkout into the profile you write books with instead of copying skills into the book:
76
99
 
77
100
  ```bash
78
101
  npm ci
@@ -81,18 +104,24 @@ npm run check
81
104
  npm run pack
82
105
  ```
83
106
 
84
- `npm test` builds before running the tool tests; `npm run check` runs type checking and the skill path self-check; `npm run pack` writes the tarball only into `temp/`, which `.gitignore` already covers. Development install:
107
+ `npm test` compiles and runs the tool tests; `npm run check` type-checks and validates skill paths; `npm run pack` writes the tarball into `temp/`, which is already in `.gitignore`.
108
+
109
+ Development install:
85
110
 
86
111
  ```bash
87
- dsh plugin --profile web add D:\Github\dsh-quarto-book
112
+ dsh plugin --profile web add D:\\Github\\dsh-quarto-book
88
113
  ```
89
114
 
90
- `SKILL.md`, `steps/`, `examples/` and `quarto/` are re-read on the next `skill` tool call, so no npm release is needed after an edit. Theme edits inside the package do not reach the book's HTML automatically; to see a page, sync the changed files into the book's `theme/` and run `quarto render` at the book root. Never point `_quarto.yml` at the plugin checkout — that path stops existing on another machine.
115
+ `SKILL.md`, `steps/`, `examples/` and `quarto/` are re-read on the next `skill` call, so rule changes need no npm publish. Theme changes in the package do not reach a book's HTML by themselves: copy the changed files into the book's `theme/` and run `quarto render` in the book root. Do not point `_quarto.yml` at the checkout; that path does not exist on another machine.
116
+
117
+ The plugin ships as a bundle: `package.json` has `dsh.bundle.patch` pointing at `cordis.patch.yml`, and the patch inserts only the `id: quarto-book`, `name: '@chengzhao-dev/dsh-quarto-book'` line. The entry does not import host internals, so it also loads under pnpm's strict dependency layout.
118
+
119
+ The legacy `scripts/embedding-agents.ps1` copies both skill trees into a target repository's `.agents/`, which shadows the plugin at rank 200. Do not use it for new books; run it only to archive a standalone skill tree offline. The command is at the top of the script.
91
120
 
92
- The plugin ships as a bundle: `package.json` points `dsh.bundle.patch` at `cordis.patch.yml`, and the patch inserts only `id: quarto-book` / `name: '@chengzhao-dev/dsh-quarto-book'`. The entry imports nothing from the host, so it loads under pnpm's strict dependency layout.
121
+ ## Issues and contributing
93
122
 
94
- The legacy `scripts/embedding-agents.ps1` copies both skill trees verbatim into the target repository's `.agents/`, where they outrank the plugin (rank 200 vs 550). Do not use it for new books; run it only to archive a standalone skill tree offline, with the command from the script header.
123
+ Bugs and suggestions go to [GitHub Issues](https://github.com/chengzhao-dev/dsh-quarto-book/issues). Before sending a change, run `npm test` and `npm run check`; for skill prose, also run `node scripts/checking-skill-links.mjs`.
95
124
 
96
- ## Licence
125
+ ## License
97
126
 
98
- MIT. The bundled LXGW Bright and LXGW Bright Code fonts are used under the SIL Open Font License 1.1; upstream copyright notices and the full licence text are in `skills/designing-theme/assets/theme/assets/fonts/OFL.txt`.
127
+ MIT. LXGW Bright and LXGW Bright Code in the theme are used under the SIL Open Font License 1.1; the upstream copyright notice and full license are in `skills/designing-theme/assets/theme/assets/fonts/OFL.txt`.
package/README.md CHANGED
@@ -1,14 +1,11 @@
1
1
  # dsh-quarto-book
2
2
 
3
3
  [![publish](https://github.com/chengzhao-dev/dsh-quarto-book/actions/workflows/publish.yml/badge.svg)](https://github.com/chengzhao-dev/dsh-quarto-book/actions/workflows/publish.yml)
4
+ [![npm](https://img.shields.io/npm/v/@chengzhao-dev/dsh-quarto-book)](https://www.npmjs.com/package/@chengzhao-dev/dsh-quarto-book)
4
5
 
5
6
  [English](https://github.com/chengzhao-dev/dsh-quarto-book/blob/main/README.en.md) | **简体中文**
6
7
 
7
- 润色中文技术章节、检查 Book 结构与中英混排,并维护一套统一、可读的阅读主题;只在 `type: book` 的仓库里出现。
8
-
9
- - 写 `.qmd`:`writing-quarto` 管章节骨架、结构与中文行文。
10
- - 改主题:`designing-theme` 管 CSS、字体与 palette。
11
- - 渲染后检查 HTML:`quarto_render_check` 判一次可发布性,`quarto_pass_report` 出收尾回执。
8
+ 把一份 Quarto Book 从章节写到可发布:`writing-quarto` 负责章节骨架、整书次序与中文行文,`designing-theme` 负责主题;渲染后由 `quarto_render_check` 判断 HTML 是否可发布,`quarto_pass_report` 给出收尾回执。两个技能只在 `type: book` 的仓库里出现,运行需要 Quarto CLI 与 Python 3。
12
9
 
13
10
  ## 提供什么
14
11
 
@@ -17,33 +14,53 @@
17
14
  | `writing-quarto` | 新建或修改 `content/**/*.qmd`、`index.qmd`、`_quarto.yml`;章节骨架、中文行文、代码与图表、内容检查 | 不改 CSS、字体和颜色 |
18
15
  | `designing-theme` | 调整主题的 CSS、SCSS、字体、palette、导航与 Callout 样式 | 不改章节正文 |
19
16
  | `quarto_render_check` | 先判断 `output-dir` 是否安全,再按需渲染一次,检查 `_book/` 里的 HTML 是否可发布 | 不改 `.qmd`,不自动修配置,不重复渲染 |
20
- | `quarto_pass_report` | 收尾时把执行结论压成一张固定行数的回执卡片:技能、模式、改过的文件与步骤、未改项、检查结论 | 不保存到磁盘,不贴章节原文,不据回执改文章 |
17
+ | `quarto_pass_report` | 收尾时把执行结论压成固定行数的回执卡片:技能、模式、改过的文件与步骤、未改项、检查结论 | 不保存到磁盘,不贴章节原文,不据回执改文章 |
21
18
 
22
- 装上之后什么时候出现、每一步对应哪条规则,见 [docs/plugin-flow.md](https://github.com/chengzhao-dev/dsh-quarto-book/blob/main/docs/plugin-flow.md)。那是给人回看的流程说明,不是技能执行时的规则来源。
19
+ 两个技能从包内 `skills/` 目录发现,按需加载;技能里的相对路径以技能目录为基准:`writing-quarto` 用 `steps/`、`examples/`、`quarto/`、`references/`、`scripts/`,`designing-theme` 用 `references/`、`knowledge/`、`assets/`、`scripts/`。
23
20
 
24
- 两个技能从包内 `skills/` 目录发现,按需加载;技能里的相对路径以技能目录为基准解析:`writing-quarto` 用 `steps/`、`examples/`、`quarto/`、`scripts/`,`designing-theme` 用 `references/`、`knowledge/`、`assets/`、`scripts/`。
21
+ ## 快速开始
25
22
 
26
- ## 安装
23
+ 1. 安装插件:
27
24
 
28
- ```bash
29
- dsh plugin --profile web add npm:@chengzhao-dev/dsh-quarto-book@0.6.1
30
- ```
25
+ ```bash
26
+ dsh plugin --profile web add npm:@chengzhao-dev/dsh-quarto-book@latest
27
+ ```
31
28
 
32
- `--profile` 换成你实际运行的那个 profile。桌面应用固定跑 `desktop` profile,且该 profile 由应用独占管理,`dsh --profile desktop` 直接启动会被拒绝;安装或升级插件可用 `dsh plugin --profile desktop add <包>`,如果应用拒绝写入,再走「设置 → 插件 → 添加插件」,地址填 `npm:@chengzhao-dev/dsh-quarto-book@0.6.1`。想跟 GitHub 标签,也可以写 `github:chengzhao-dev/dsh-quarto-book#v0.6.1`。
29
+ 2. 在书仓库里打开会话。`_quarto.yml` 要有独立一行的 `type: book`,书根至少有一个 `.qmd` 或 `.md` 章节源文件;两个技能名这时才会出现。
30
+ 3. 调用技能:写正文用 `writing-quarto`,改主题用 `designing-theme`。
33
31
 
34
- 从旧的无作用域包 `dsh-quarto-book` 升级时(该包已随 0.3.4 从 npm 删除,只影响之前装过它的 profile),先在同一个 profile 里 `dsh plugin --profile <profile> remove dsh-quarto-book` 再装新包。两个包同时留着会以相同的 rank 550 各注册一遍 `writing-quarto` 和 `designing-theme`。
32
+ `--profile` 换成你实际运行的那个 profile。桌面应用固定跑 `desktop` profile,且该 profile 由应用独占管理,`dsh --profile desktop` 直接启动会被拒绝;安装或升级用 `dsh plugin --profile desktop add <包>`,应用拒绝写入时再走「设置 → 插件 → 添加插件」。需要固定版本时写 `npm:@chengzhao-dev/dsh-quarto-book@0.6.4`,跟 GitHub 标签时写 `github:chengzhao-dev/dsh-quarto-book#v0.6.4`。
35
33
 
36
- 要求 DSH 0.2.x、Node.js 22 及以上。源码是 TypeScript,发布包已带编译后的 `lib/`,安装后不需要构建。内容、链接与命名检查需要 Python 3;`quarto_render_check` 真正渲染时需要 Quarto CLI,渲染后的 HTML 检查同样用 Python 3。环境分工见 [docs/plugin-layout.md](https://github.com/chengzhao-dev/dsh-quarto-book/blob/main/docs/plugin-layout.md)。
34
+ 从旧的无作用域包 `dsh-quarto-book` 升级(该包已随 0.3.4 从 npm 删除,只影响之前装过它的 profile):先在同一个 profile 里 `dsh plugin --profile <profile> remove dsh-quarto-book`,再装新包。两个包同时留着会以相同的 rank 550 各注册一遍 `writing-quarto` 和 `designing-theme`。
37
35
 
38
- 安装单位是 profile,不是仓库:只影响执行 `add` 的那个 profile。装好之后还要过工作区门禁:按会话 `cwd` 判断当前目录是不是一本书,通过后两个技能名才会出现。门禁的六类情形见 [docs/plugin-boundaries.md](https://github.com/chengzhao-dev/dsh-quarto-book/blob/main/docs/plugin-boundaries.md)。
36
+ 安装单位是 profile,不是仓库:只影响执行 `add` 的那个 profile。装好之后还要过工作区检查:按会话 `cwd` 判断当前目录是不是一本书,通过后技能名才会出现。检查的六类情形见 [docs/plugin-boundaries.md](https://github.com/chengzhao-dev/dsh-quarto-book/blob/main/docs/plugin-boundaries.md)。
37
+
38
+ ## 环境要求
39
+
40
+ | 依赖 | 用途 |
41
+ | --- | --- |
42
+ | DSH 0.2.x | 宿主;插件按 `dsh.bundle.patch` 注册技能与两个工具 |
43
+ | Node.js 22 及以上 | 运行插件与检查脚本 |
44
+ | Python 3 | 内容、链接、命名检查,以及渲染后的 HTML 检查 |
45
+ | Quarto CLI | 仅 `quarto_render_check` 真正渲染时需要 |
46
+
47
+ 发布包已经带编译好的 `lib/`,安装后不需要构建。环境分工见 [docs/plugin-layout.md](https://github.com/chengzhao-dev/dsh-quarto-book/blob/main/docs/plugin-layout.md)。
39
48
 
40
49
  ## 使用
41
50
 
42
- 在 `type: book` 的仓库里加载 `writing-quarto` 或 `designing-theme`,按 `SKILL.md` 的步骤执行。插件没有常驻进程,也不会在会话开始时自动执行技能;技能目录里出现名字只说明当前会话过了门禁,只有调用 `skill`、`quarto_render_check` 或 `quarto_pass_report` 才算一次使用。同一会话再次调用会重新读技能正文;工具不会自动重试,失败后由用户决定是否再调一次。
51
+ ### 写章节
52
+
53
+ 在 `type: book` 的仓库里加载 `writing-quarto`,按 `SKILL.md` 的九步执行。每一步只打开同号的一个 `steps/` 文件;判不准才读 `examples/` 里同号的案例;要排图、查渲染或全仓重构才打开 `quarto/`。
54
+
55
+ 书仓库根目录的 `README.md` 是说明页,不是章节:只走第 5–8 步,不套第 1–4 步的任务与结构骨架,不写「常见错误」「自测问题」,不调 `quarto_render_check`。`README.en.md` 同样处理。
43
56
 
44
- 项目只放一份自己的约定技能:`<book>/.agents/skills/quarto-book-project/`,模板见 `templates/quarto-book-project/`。存在时两个通用技能先读它的 `SKILL.md`,再按它指向的 `writing/` 文件读章节表;不存在就跳过,不猜项目约定。**不要把同名技能嵌进书仓库**:DSH 在同一技能层按 rank 取一个,`.agents/skills`(rank 200)优先于插件(rank 550),书里再放 `writing-quarto` 或 `designing-theme` 会让插件正文失效。
57
+ 插件没有常驻进程,也不会在会话开始时自动执行技能。技能目录里出现名字只说明当前会话过了检查;只有调用 `skill`、`quarto_render_check` 或 `quarto_pass_report` 才算一次使用。同一会话再次调用会重读技能正文,工具失败后不会自动重试。
45
58
 
46
- 主题资产在 `skills/designing-theme/assets/theme/`,包内是权威来源,书内 `theme/` 是渲染副本;已有副本时 `designing-theme` 只改副本,只有用户明确要求「把插件主题装进这本书」且目标目录不存在时才从包复制,并把 `skills/writing-quarto/scripts/answering-disclosure.lua` 放到书内 `filters/answering-disclosure.lua`。`_quarto.yml` 只引用书内副本,不指向 npm 包或插件检出目录。
59
+ ### 改主题
60
+
61
+ 主题资产在 `skills/designing-theme/assets/theme/`,包内是权威来源,书内 `theme/` 是渲染副本。已有副本时 `designing-theme` 只改副本;只有用户明确要求「把插件主题装进这本书」且目标目录不存在,才从包复制,并把 `skills/writing-quarto/scripts/answering-disclosure.lua` 放到书内 `filters/answering-disclosure.lua`。`_quarto.yml` 只引用书内副本,不指向 npm 包或插件检出目录。
62
+
63
+ ### 检查与渲染
47
64
 
48
65
  内容与链接检查在 `writing-quarto` 技能目录下运行:
49
66
 
@@ -56,21 +73,27 @@ python scripts/checking-names.py --root <book>
56
73
 
57
74
  `checking-prose.py` 只报中文混排里能直接判断的问题;`checking-names.py` 只在全仓重构时加跑。日常章节检查跑前三条即可。
58
75
 
59
- 渲染后的 Callout、HTML 产物与 DOM 契约由 `quarto_render_check` 调包内脚本完成,不手跑 `checking-callouts.py`、`checking-output.py` 或 `checking-dom.py`;`designing-theme` 的 `scripts/checking-layout.py` 在渲染后的 HTML 上检查溢出、对比度和页面结构。`apply()` 不在加载时跑这些脚本,也不跑 `quarto render`。
76
+ 渲染后的 Callout、HTML 产物与 DOM 结构约定由 `quarto_render_check` 调包内脚本完成,不用手跑 `checking-callouts.py`、`checking-output.py` 或 `checking-dom.py`;`designing-theme` 的 `scripts/checking-layout.py` 在渲染后的 HTML 上检查溢出、对比度和页面结构。`apply()` 不在加载时跑这些脚本,也不跑 `quarto render`。
60
77
 
61
78
  一轮结束时调用一次 `quarto_pass_report`,把这次执行的结论压成卡片;卡片就是回执,回复里不再复述。
62
79
 
80
+ ### 项目技能
81
+
82
+ 项目只放一份自己的约定技能:`<book>/.agents/skills/quarto-book-project/`,模板见 `templates/quarto-book-project/`。存在时两个通用技能先读它的 `SKILL.md`,再按它指向的 `writing/` 文件读章节表;不存在就跳过,不猜项目约定。
83
+
84
+ **不要把同名技能嵌进书仓库**:DSH 在同一技能层按 rank 取一个,`.agents/skills`(rank 200)优先于插件(rank 550),书里再放 `writing-quarto` 或 `designing-theme` 会让插件正文失效。
85
+
63
86
  ## 文档
64
87
 
65
88
  | 文档 | 内容 |
66
89
  | --- | --- |
67
90
  | [docs/plugin-flow.md](https://github.com/chengzhao-dev/dsh-quarto-book/blob/main/docs/plugin-flow.md) | 从安装到回执的五层结构、文件分流与渲染顺序 |
68
- | [docs/plugin-boundaries.md](https://github.com/chengzhao-dev/dsh-quarto-book/blob/main/docs/plugin-boundaries.md) | 工作区门禁的六类情形与判定顺序 |
91
+ | [docs/plugin-boundaries.md](https://github.com/chengzhao-dev/dsh-quarto-book/blob/main/docs/plugin-boundaries.md) | 工作区检查的六类情形与判定顺序 |
69
92
  | [docs/plugin-layout.md](https://github.com/chengzhao-dev/dsh-quarto-book/blob/main/docs/plugin-layout.md) | 仓库目录、发布包内容与 Python/Quarto 分工 |
70
93
  | [docs/writing-quarto-layout.md](https://github.com/chengzhao-dev/dsh-quarto-book/blob/main/docs/writing-quarto-layout.md) | 写作技能的目录分层 |
71
94
  | [docs/versioning.md](https://github.com/chengzhao-dev/dsh-quarto-book/blob/main/docs/versioning.md) | 版本号与发布顺序 |
72
95
 
73
- ## 本地开发
96
+ ## 开发
74
97
 
75
98
  源码用 TypeScript,构建产物 `lib/` 提交进仓库;发布包直接加载 `lib/`,安装后不需要构建。开发时把本仓库链接进写书用的 profile,而不是把技能复制进书:
76
99
 
@@ -81,10 +104,12 @@ npm run check
81
104
  npm run pack
82
105
  ```
83
106
 
84
- `npm test` 会先编译再跑工具测试;`npm run check` 做类型检查与技能路径自检;`npm run pack` 把 tarball 只写进 `temp/`,那个目录已在 `.gitignore` 里。开发安装:
107
+ `npm test` 会先编译再跑工具测试;`npm run check` 做类型检查与技能路径自检;`npm run pack` 把 tarball 只写进 `temp/`,那个目录已在 `.gitignore` 里。
108
+
109
+ 开发安装:
85
110
 
86
111
  ```bash
87
- dsh plugin --profile web add D:\Github\dsh-quarto-book
112
+ dsh plugin --profile web add D:\\Github\\dsh-quarto-book
88
113
  ```
89
114
 
90
115
  `SKILL.md`、`steps/`、`examples/`、`quarto/` 在下一次 `skill` 工具调用时重读,改完不用发 npm。包内主题改动不会自动进书的 HTML;要看页面效果,把改过的文件同步到书的 `theme/`,再在书根运行 `quarto render`。不要把 `_quarto.yml` 指到插件检出目录,那条路径换机器就不存在。
@@ -93,6 +118,10 @@ dsh plugin --profile web add D:\Github\dsh-quarto-book
93
118
 
94
119
  旧装机脚本 `scripts/embedding-agents.ps1` 会把两棵技能原样复制进目标仓库的 `.agents/`,按 rank 200 盖住插件;新书不要用它,只有离线归档独立技能树时才执行,命令写在脚本头部。
95
120
 
121
+ ## 问题与贡献
122
+
123
+ 问题与建议走 [GitHub Issues](https://github.com/chengzhao-dev/dsh-quarto-book/issues)。提交改动前先运行 `npm test` 与 `npm run check`;涉及技能正文时,同时跑 `node scripts/checking-skill-links.mjs`。
124
+
96
125
  ## 许可
97
126
 
98
127
  MIT。主题内的 LXGW Bright 与 LXGW Bright Code 按 SIL Open Font License 1.1 使用;上游版权声明与许可全文在 `skills/designing-theme/assets/theme/assets/fonts/OFL.txt`。
package/lib/index.js CHANGED
@@ -24,15 +24,17 @@ const RANK = 550;
24
24
  const SKILL_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
25
25
  const PACKAGE_ROOT = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
26
26
  const SKILLS_ROOT = path.join(PACKAGE_ROOT, 'skills');
27
- /** 读取 SKILL.md 实际用到的几个键,只做最简单的 YAML frontmatter 解析。 */
27
+ /** 读取 SKILL.md 实际用到的几个键,只做最小的 YAML frontmatter 解析。 */
28
28
  function parseFrontmatter(source) {
29
- const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(source);
29
+ const normalized = source.replace(/^\uFEFF/, '');
30
+ const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(normalized);
30
31
  if (!match)
31
- return { data: {}, body: source };
32
+ return { data: {}, body: normalized };
32
33
  const data = {};
33
34
  const stack = [{ indent: -1, target: data }];
34
- for (const rawLine of match[1].split(/\r?\n/)) {
35
- const line = rawLine.replace(/\t/g, ' ');
35
+ const lines = match[1].split(/\r?\n/);
36
+ for (let index = 0; index < lines.length; index += 1) {
37
+ const line = lines[index].replace(/\t/g, ' ');
36
38
  if (!line.trim() || line.trimStart().startsWith('#'))
37
39
  continue;
38
40
  const indent = line.length - line.trimStart().length;
@@ -43,16 +45,56 @@ function parseFrontmatter(source) {
43
45
  stack.pop();
44
46
  const parent = stack[stack.length - 1].target;
45
47
  const key = pair[1];
46
- const value = (pair[2] ?? '').trim();
48
+ const value = stripInlineComment(pair[2] ?? '').trim();
47
49
  if (value === '') {
48
50
  const child = {};
49
51
  parent[key] = child;
50
52
  stack.push({ indent, target: child });
51
53
  continue;
52
54
  }
55
+ if (/^[>|][+-]?\d?$/.test(value)) {
56
+ const block = [];
57
+ let cursor = index + 1;
58
+ for (; cursor < lines.length; cursor += 1) {
59
+ const next = lines[cursor].replace(/\t/g, ' ');
60
+ if (!next.trim()) {
61
+ block.push('');
62
+ continue;
63
+ }
64
+ const nextIndent = next.length - next.trimStart().length;
65
+ if (nextIndent <= indent)
66
+ break;
67
+ block.push(next);
68
+ }
69
+ const nonBlank = block.filter((item) => item.trim());
70
+ const cut = nonBlank.length > 0 ? Math.min(...nonBlank.map((item) => item.length - item.trimStart().length)) : 0;
71
+ const text = block.map((item) => item.slice(cut)).join('\n');
72
+ parent[key] = value[0] === '|' ? text : text.replace(/([^\n])\n(?=[^\n])/g, '$1 ').replace(/\n{2,}/g, '\n');
73
+ index = cursor - 1;
74
+ continue;
75
+ }
53
76
  parent[key] = parseScalar(value);
54
77
  }
55
- return { data, body: source.slice(match[0].length) };
78
+ return { data, body: normalized.slice(match[0].length) };
79
+ }
80
+ /** 去掉 plain scalar 末尾的 ` # 注释`;引号内的 # 保留。 */
81
+ function stripInlineComment(value) {
82
+ let quote;
83
+ for (let index = 0; index < value.length; index += 1) {
84
+ const char = value[index];
85
+ if (quote) {
86
+ if (char === quote && value[index - 1] !== '\\')
87
+ quote = undefined;
88
+ continue;
89
+ }
90
+ if (char === '"' || char === "'") {
91
+ quote = char;
92
+ continue;
93
+ }
94
+ if (char === '#' && (index === 0 || /\s/.test(value[index - 1])))
95
+ return value.slice(0, index);
96
+ }
97
+ return value;
56
98
  }
57
99
  function parseScalar(value) {
58
100
  if ((value.startsWith('"') && value.endsWith('"')) ||
@@ -74,6 +74,7 @@ function normalizeFiles(value) {
74
74
  fail('files 必须是数组');
75
75
  if (value.length > MAX_FILES)
76
76
  fail(`files 最多 ${MAX_FILES} 个路径`);
77
+ const seen = new Set();
77
78
  return value.map((item) => {
78
79
  if (!isRecord(item))
79
80
  fail('files 的每一项必须是 { path, edits }');
@@ -85,7 +86,11 @@ function normalizeFiles(value) {
85
86
  fail('files 的 path 必须是非空字符串');
86
87
  if (!isPlainCount(item.edits))
87
88
  fail('files 的 edits 必须是非负整数');
88
- return { path: filePath.trim(), edits: item.edits };
89
+ const normalizedPath = filePath.trim();
90
+ if (seen.has(normalizedPath))
91
+ fail(`files 的 path 重复:${normalizedPath}`);
92
+ seen.add(normalizedPath);
93
+ return { path: normalizedPath, edits: item.edits };
89
94
  });
90
95
  }
91
96
  function normalizeSteps(value, skill) {
@@ -194,17 +199,12 @@ export function buildPassReport(input) {
194
199
  fail('未执行时必须给出 skip_reason');
195
200
  if ([...reason.trim()].length > MAX_SKIP_REASON)
196
201
  fail(`skip_reason 最多 ${MAX_SKIP_REASON} 个字`);
197
- if (input.files !== undefined && Array.isArray(input.files) && input.files.length > 0) {
198
- fail('未执行时 files 必须为空');
199
- }
200
- if (input.steps !== undefined && Array.isArray(input.steps) && input.steps.length > 0) {
201
- fail('未执行时 steps 必须为空');
202
- }
203
- if (input.held !== undefined && Array.isArray(input.held) && input.held.length > 0) {
204
- fail('未执行时 held 必须为空');
205
- }
206
- if (input.checks !== undefined && Array.isArray(input.checks) && input.checks.length > 0) {
207
- fail('未执行时 checks 必须为空');
202
+ for (const key of ['files', 'steps', 'held', 'checks']) {
203
+ const value = input[key];
204
+ if (value === undefined)
205
+ continue;
206
+ if (!Array.isArray(value) || value.length > 0)
207
+ fail(`未执行时 ${key} 必须为空数组`);
208
208
  }
209
209
  return { receipt: `未执行 ${typedSkill}:${reason.trim()}` };
210
210
  }