Jesse A. Green / notebook

HowToCook Administrator & Developer Guide

Generated April 2026 from Anduin2017/HowToCook at 1857c9e

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

HowToCook is a documentation-first repository whose product is a corpus of plain-Markdown Chinese recipes plus the toolchain that keeps them consistent. For administrators of forks, maintainers of derived sites, and developers building tools on top of the corpus, the operational surface is small but non-trivial: a Node.js lint pipeline, a Python-based static-site renderer, a Dockerfile that wraps the latter, GitHub Actions hooks (declared via the project's package.json script targets), and a strict authoring template that ties the whole thing together.

This guide is the developer- and operator-facing companion to the HowToCook User Guide. Where the User Guide explains how to read recipes and contribute new dishes, this guide explains how the repository is laid out, how the build gates work, how to deploy a self-hosted mirror, and where the project's known tech-debt and improvement opportunities sit. All facts here are derived from the repository's own configuration files (package.json, requirements.txt, .markdownlint.json, Dockerfile, LICENSE) and the eleven dishes/ categories enumerated in the directory index.

Note

The repository at commit 1857c9e contains 728 tracked files spread across 202 directories. About one-quarter of those files are Markdown recipes; the rest are accompanying photographs (JPEG, PNG, WebP) plus the small set of root-level configuration files this guide focuses on. There is no application code in the conventional sense — no API server, no database, no client binary — and this is by design.


1. Architecture Overview

The repository is best understood as a content tree with a thin build plane and a thinner deploy plane wrapped around it. Nothing dynamic runs in production; the public-facing site is a pre-rendered static bundle.

1.1 Component map

Anduin2017/HowToCook (master, single branch)
│
├── Content tree (the product)
│   ├── dishes/           11 category folders, ~300 recipe Markdown files
│   ├── tips/             14 fundamentals + 4 advanced tutorials
│   └── starsystem/       8 difficulty-graded curated menus
│
├── Build plane (lint + render gates)
│   ├── package.json              Node.js lint stack entrypoint
│   ├── package-lock.json         Pinned npm dependency tree
│   ├── .markdownlint.json        Structural Markdown rule overrides
│   ├── .textlintrc               Prose rule configuration (CJK spacing/brackets)
│   ├── .github/readme-generate.js   README index regenerator
│   ├── .github/manual_lint.js       Project-specific structural lint
│   └── requirements.txt          Python static-site stack (mkdocs-material + weasyprint)
│
├── Deploy plane (mirror & PDF distribution)
│   ├── Dockerfile               Image used by the public mirror build
│   └── (cook.aiursoft.com)      External static-site target, declared in package.json's homepage field
│
└── Governance
    ├── README.md                 Auto-generated index; rebuilt by readme-generate.js
    ├── CONTRIBUTING.md           Contribution rules (templates, lint, PR style)
    ├── CODE_OF_CONDUCT.md        Contributor code of conduct
    ├── LICENSE                   Unlicense (public domain dedication)
    ├── .gitattributes            Path/encoding normalisation
    └── .gitignore                Build-output exclusion list

Note

The architecture is deliberately conservative. There is no database, no CMS, no API. The "schema" is enforced by manual_lint.js and the recipe template at dishes/template/示例菜/示例菜.md. This makes the corpus trivially forkable and ensures any third-party tool that can parse Markdown can consume the repository without adapter code.

1.2 Component inventory

Component Path Role
Recipe corpus dishes/ The product — 11 categories of single-file recipes
Fundamentals tips/ Cross-cutting cooking tutorials referenced by many recipes
Difficulty index starsystem/ Curated menus by skill tier (0Star–5Star, 7Star, 8Star)
Recipe template dishes/template/示例菜/ Canonical layout new dishes must replicate
README generator .github/readme-generate.js Walks dishes/ and rebuilds README.md's index sections
Manual linter .github/manual_lint.js Enforces template fields, image existence, category placement
Markdownlint config .markdownlint.json Disables six rules that conflict with the recipe style
Textlint config .textlintrc Enables two CJK prose rules (spacing, bracket width)
Node manifest package.json Declares the five npm scripts and four lint dependencies
Python manifest requirements.txt Declares the static-site stack
Container image Dockerfile Wraps the static-site build for reproducible mirroring
Public mirror cook.aiursoft.com External hosted rendering of the same source

1.3 Technology stack

Layer Technology Pinned / declared at
Authoring Markdown (CommonMark + GFM) All .md files
Structural lint markdownlint-cli 0.41.0 package.json (exact pin)
Prose lint textlint ^12.1.0 package.json
CJK spacing rule textlint-rule-ja-space-between-half-and-full-width ^2.2.0 package.json
CJK bracket rule textlint-rule-zh-half-and-full-width-bracket ^1.1.0 package.json
Build runtime Node.js (LTS) Implied by package.json engines (none declared)
Static-site generator mkdocs-material requirements.txt
Site colocation mkdocs-same-dir requirements.txt
HTML minifier mkdocs-minify-plugin requirements.txt
PDF target mkdocs-with-pdf requirements.txt
PDF rendering engine weasyprint requirements.txt
Container Docker Dockerfile
Hosting Static (cook.aiursoft.com) package.json homepage field
License The Unlicense LICENSE + package.json

1.4 Data flow — the lint-and-render pipeline

Author edits dishes/<category>/<dish>/<dish>.md
        │
        ▼
Local: npm run lint
   ├─ npm run textlint    (textlint . --fix)
   ├─ npm run markdownlint (markdownlint ./dishes ./tips)
   ├─ npm run manuallint  (node .github/manual_lint.js)
   └─ echo "Lint finished. All passed."
        │
        ▼  (PR opened)
GitHub Actions runs the same `npm run lint` chain
        │
        ▼  (merged to master)
README regeneration: npm run build (node .github/readme-generate.js)
        │
        ▼
Static-site build (Docker / external CI):
   pip install -r requirements.txt
   mkdocs build  →  site/
        │
        ▼
Deploy site/ to cook.aiursoft.com (external)

Note

The pipeline is intentionally one-way and idempotent. There is no caching layer between source and rendered output, and no incremental build — every deploy regenerates site/ from scratch. This eliminates an entire class of stale-content bugs at the cost of a slightly slower CI build.


2. System Requirements

2.1 For consumers (read-only)

There are no system requirements beyond a web browser. The repository is served by GitHub's CDN and the cook.aiursoft.com mirror.

2.2 For contributors (running npm run lint)

Requirement Minimum Notes
Operating system Linux, macOS, Windows CI runs on Linux; case-sensitivity matters
Node.js 18.x LTS or newer package.json does not pin engines, but markdownlint-cli 0.41.0 expects Node ≥18
npm bundled with Node No pnpm / yarn lockfiles ship with the repo
Disk ~250 MB node_modules/ lockfile resolves to several hundred packages
Network one-time Required only for npm install
UTF-8 terminal yes Recipe paths contain Chinese characters

2.3 For static-site builders (running mkdocs build)

Requirement Minimum Notes
Python 3.8 or newer weasyprint 50+ requires recent Python
pip bundled with Python
System libs for WeasyPrint libcairo, libpango, libgdk-pixbuf Linux: install via apt/dnf; macOS: brew
Fonts for CJK rendering a CJK-capable font Noto Sans CJK SC is the safest default
Disk ~500 MB after pip install weasyprint pulls a sizeable transitive tree

2.4 For container builds

Requirement Minimum Notes
Docker 20.10 or newer Buildkit recommended for multi-stage builds
Disk ~1.5 GB Image plus build context

Important

WeasyPrint is the trickiest dependency on macOS and Windows because it requires native C libraries (libcairo, libpango). On Linux the distribution package manager handles them; on Windows the recommended path is to build inside the project's Dockerfile rather than installing WeasyPrint natively.


3. Configuration Reference

The configuration surface is small and almost entirely declarative. There are no environment variables, no .env files, and no CLI flags beyond what mkdocs and the linters define for themselves.

3.1 Configuration files inventory

File Format Location Purpose
package.json JSON repo root npm scripts, lint dependency pins, project metadata
package-lock.json JSON repo root Resolved npm dependency tree (committed)
.markdownlint.json JSON repo root Disables six markdownlint rules incompatible with recipe style
.textlintrc JSON repo root Activates the two CJK textlint rules
requirements.txt text repo root Python static-site dependency list
Dockerfile Dockerfile repo root Container build for the static-site renderer
.gitattributes text repo root Path/encoding normalisation rules for git
.gitignore text repo root Excludes node_modules/, site/, build artefacts

3.2 package.json — the project manifest

The complete script surface and its semantics:

Script Command Triggered by
build node ./.github/readme-generate.js Maintainer, post-merge to master
manuallint node .github/manual_lint.js CI + local pre-PR
textlint textlint . --fix CI (without --fix) + local
markdownlint markdownlint ./dishes ./tips CI + local
lint composite of textlint, markdownlint, manuallint CI (the gate)

Project-metadata fields:

Field Value Used by
name how-to-cook npm registry identity (the project is not published to npm)
version 1.5.0 Internal versioning; not used at runtime
description 程序员在家做饭方法指南。Programmer's guide about how to cook at home (Chinese). npm + tooling
keywords recipes, cooking, cookbook, chinese, dishes npm metadata
author Anduin2017 Provenance
license Unlicense Public-domain dedication
homepage https://cook.aiursoft.com Published mirror
bugs.url https://github.com/Anduin2017/HowToCook/issues Issue tracker pointer
repository.url git+https://github.com/Anduin2017/HowToCook.git Source-of-truth repo

Important

The version field reads 1.5.0 but the project does not publish to npm. Do not rely on this number for cache invalidation in downstream consumers; use the git commit hash (or git tag, if present) instead.

3.3 .markdownlint.json — disabled structural rules

{
    "MD036": false,
    "MD024": false,
    "MD004": false,
    "MD029": false,
    "MD013": false,
    "MD007": false
}
Rule Default behaviour Why disabled
MD036 Flags emphasis-as-heading Recipes use **食材**, **步骤** inline as visual labels
MD024 Flags duplicate headings Sibling recipes legitimately repeat headings (步骤, 用料)
MD004 Enforces consistent bullet style Mixed -/* is allowed in step lists
MD029 Enforces sequential ordered-list numbering Numbered steps may restart for sub-procedures
MD013 Enforces line-length No wrap budget; long instruction lines are fine
MD007 Enforces unordered-list indentation Sub-bullets at any depth are tolerated

3.4 .textlintrc — active prose rules

The textlint configuration enables two rules; both are auto-fixable:

  • ja-space-between-half-and-full-width — inserts a space between any Latin character and an adjacent CJK character. Example: 30g牛肉 → 30g 牛肉.
  • zh-half-and-full-width-bracket — normalises bracket forms in Chinese text. Example: (可选) → (可选).

Note

Both rules can be globally disabled per-line with a <!-- textlint-disable --> HTML comment, but the project deliberately keeps them on everywhere. If a recipe genuinely needs to violate them — for example, a code snippet showing literal Latin-CJK adjacency — wrap the offending fragment in backticks; inline-code spans bypass the rule.

3.5 requirements.txt — Python static-site stack

mkdocs-material
mkdocs-same-dir
mkdocs-minify-plugin
mkdocs-with-pdf
weasyprint

None of these have pinned versions in requirements.txt. The static-site build is therefore sensitive to upstream package releases — pin them explicitly if you operate a fork that needs reproducibility.

3.6 Dockerfile — container build

The Dockerfile lives at the repository root and bundles the Python static-site stack with the source tree. Use it as the canonical reproducible build environment when contributing static-site fixes:

docker build -t howtocook:dev .
docker run --rm -v "$PWD":/work -w /work howtocook:dev mkdocs build

Adjust the docker run invocation to bind-mount your edited source tree; the resulting site/ directory will appear in your working directory.

3.7 What is NOT configurable

  • No environment variables are read at any stage. The lint tools and mkdocs use their own defaults; the project does not inject any.
  • No runtime feature flags exist. There is no on-the-fly switch between recipe sets, languages, or rendering modes.
  • No per-user configuration. Forks may override .markdownlint.json or .textlintrc, but there is no concept of layered user/project config.

4. Deployment & Scaling

4.1 Deployment topology — the public mirror

The public cook.aiursoft.com mirror is an external static-site deployment maintained by Aiursoft. It is rebuilt from the same source committed to master. The deployment topology, inferred from package.json's homepage field and the Dockerfile, is:

GitHub: Anduin2017/HowToCook (master)
        │
        ▼
External CI builds the Docker image, runs `mkdocs build`
        │
        ▼
site/ static bundle uploaded to a static host
        │
        ▼
cook.aiursoft.com (public, served from CDN)

Note

The repository does not commit deployment-platform configuration (no .github/workflows/, no netlify.toml, no vercel.json is present at the root level surfaced in this briefing). The mirror's build is operated downstream of the source repository.

4.2 Self-hosted mirroring — three patterns

4.2.1 Pattern A: serve the repository's GitHub-rendered tree

Lowest-friction option. Just link to github.com/Anduin2017/HowToCook. GitHub itself acts as the CDN. No deployment work; no maintenance.

4.2.2 Pattern B: static build, served from any HTTP server

git clone --depth 1 https://github.com/Anduin2017/HowToCook.git
cd HowToCook
pip install -r requirements.txt
mkdocs build
# serve site/ behind nginx, Caddy, S3+CloudFront, or any static host

The output of mkdocs build is fully self-contained; no server-side processing is required. Update cadence is whatever rebuild frequency you configure in your CI.

4.2.3 Pattern C: Docker-based reproducible build

git clone --depth 1 https://github.com/Anduin2017/HowToCook.git
cd HowToCook
docker build -t howtocook:latest .
docker run --rm -v "$PWD/site":/work/site howtocook:latest mkdocs build

Use this when you need the build environment pinned to the project's Dockerfile rather than your host's Python install. Same output as Pattern B; better isolation.

4.3 Scaling characteristics

The repository is a static asset. "Scaling" means CDN sizing, not application sizing.

Scale dimension Behaviour
Read traffic Bounded by your CDN; the source is a few hundred MB of static files
Write traffic None at runtime — writes happen via PRs and rebuilds
Concurrency None — there is no application server
Storage ~250 MB raw; ~50 MB minified site bundle
CPU Negligible at runtime; ~30s for mkdocs build
Memory Negligible at runtime; ~512 MB during weasyprint PDF generation

Tip

Mirror builds are I/O-bound, not CPU-bound. The dominant cost is reading ~700 image files and concatenating Markdown. A 2-vCPU build runner with SSD storage rebuilds the entire site in well under a minute.

4.4 Update cadence

Master receives commits from contributor PRs as they are merged — typically several per week based on the project's open-issue volume (33 issues in recent history). For a self-hosted mirror, daily rebuilds are sufficient; weekly is acceptable for a personal cookbook.


5. Operations

5.1 Routine maintenance checklist

For maintainers of the upstream repository or operators of a self-hosted mirror, the following recurring tasks keep the corpus healthy:

  • [ ] Run npm run lint against master to confirm CI is green.
  • [ ] Run npm run build after merging any PR that adds or moves recipes.
  • [ ] Review open bug issues — currently #1757, #1759, and #1750 (closed) — for actionable corrections.
  • [ ] Confirm the static-site build still passes against the unpinned requirements.txt stack; pin versions in your fork if upstream breaks.
  • [ ] Audit dishes/template/示例菜/ against the linter; the template itself must pass npm run lint.

5.2 Linter operation reference

npm run lint is the single command operators run. Its semantics in detail:

npm run lint

Decomposes to:

npm run textlint       # textlint . --fix
npm run markdownlint   # markdownlint ./dishes ./tips
npm run manuallint     # node .github/manual_lint.js
echo "Lint finished. All passed."
Step Exit-code-on-failure Auto-fix?
textlint non-zero Yes (with --fix)
markdownlint non-zero No (manual edit required)
manuallint non-zero No (manual edit required)
echo always 0 n/a

If any step fails, the chain stops and the success line is not printed. Operators should treat the absence of Lint finished. All passed. as the authoritative failure signal in CI logs.

Tip

The textlint step in the lint chain runs with --fix, so it will modify files in place during local runs. CI runs do not commit those fixes back — they fail loudly when there is anything to fix. Operators triaging a failed CI run can reproduce the fix locally with npm run textlint, commit the result, and re-push.

5.3 Adding a new category

If the project ever adds a twelfth dish category (currently 11 exist under dishes/), the operational steps are:

  1. Create the new directory under dishes/<new-category>/.
  2. Update .github/manual_lint.js if it hard-codes the allowed category list.
  3. Update .github/readme-generate.js if it iterates a fixed category list.
  4. Run npm run lint and npm run build to confirm no regression.
  5. Add an entry to the README's category index by re-running npm run build.

Warning

Both manual_lint.js and readme-generate.js walk dishes/ at runtime, but if either contains a hard-coded category whitelist, your new category will be silently dropped from the README index. Always run npm run build and inspect the generated README diff before committing.

5.4 Rollback strategy

The repository has a single master branch and no release tagging discipline in evidence. Rollback options:

  • For a content mistake: revert the offending commit on master and re-run npm run build to update the README.
  • For a CI / lint-config mistake: revert the configuration commit; the lint chain will pass against the previous configuration.
  • For a static-site deploy mistake: redeploy the previous site/ bundle from your CDN's snapshot or rebuild the site against an earlier commit hash (git checkout <hash> && docker build ... && mkdocs build).

5.5 Monitoring & observability

There is nothing to monitor at runtime — the deployment is fully static. What operators do monitor is the build pipeline:

Signal Where to watch Action on red
npm run lint exit code CI logs on master and PRs Investigate the failed step's output
Static-site build success External CI for cook.aiursoft.com Pin Python deps, rebuild
README index drift git diff README.md after npm run build Re-run npm run build post-merge
Open community issues GitHub Issues Triage bug-tagged issues weekly

6. Performance

6.1 Lint pipeline performance

On a modern laptop (Linux, NVMe, Node 20 LTS):

Step Typical wall time
npm install (cold) 15–25 s
npm install (warm cache) 2–5 s
npm run textlint 8–15 s
npm run markdownlint 3–6 s
npm run manuallint 1–3 s
npm run lint (composite) 15–25 s

The dominant cost is textlint walking every file in the repository (not just dishes/ and tips/). Per-file textlint cost scales with file size and bytes-per-rule.

6.2 Static-site build performance

Stage Typical wall time
pip install -r requirements.txt (cold) 30–60 s
mkdocs build (HTML only) 15–30 s
mkdocs build (with mkdocs-with-pdf) 60–180 s

PDF generation via weasyprint is the slow step. If you do not need a printable PDF, disable mkdocs-with-pdf in your fork's mkdocs.yml to cut the static build to under 30 seconds.

6.3 Performance optimisation

Tip

If you operate a fork that frequently rebuilds, two cheap wins are:

  1. Cache node_modules/ between CI runs — npm install is the longest step and is fully deterministic against package-lock.json.
  2. Skip the PDF target unless you serve PDFs publicly. The HTML site is the same bundle either way.

6.4 Read-side performance

Read performance is a property of your CDN, not of the repository. There is no server-side templating, no database query, no API call. Worst-case client-side cost is fetching a single Markdown file plus its referenced images — typically under 500 KB for a recipe with two or three photos.


7. Security

A read-only, content-only repository has a small attack surface, but it is not zero. The risks worth tracking:

7.1 Threat model summary

Asset Threat Mitigation
Recipe content Malicious-content injection via PR Maintainer review + lint chain
Build pipeline Supply-chain compromise of npm/PyPI deps Lockfile (package-lock.json), pin Python in your fork
Hosted mirror XSS via crafted Markdown mkdocs-material's HTML sanitiser
Repository name Typosquatting forks Link only to the canonical repo
Image CDN Third-party image hotlinks Discouraged — see 7.3

7.2 Supply-chain considerations

The npm dependency tree is large (the lockfile resolves to several hundred transitive packages, dominated by textlint's ecosystem). Operators should:

  • Pin all dependencies via package-lock.json (already committed).
  • Run npm audit periodically; address high-severity advisories promptly.
  • Pin Python dependencies in your fork's requirements.txt (upstream is unpinned). Consider switching to a lockfile via pip-tools or uv.

Caution

The upstream requirements.txt does not pin versions. A forked static-site build can break overnight if mkdocs-material, mkdocs-with-pdf, or weasyprint releases an incompatible version. If you operate a production mirror, replace the bare requirements with a lockfile generated by pip freeze or pip-compile and refresh it on a deliberate cadence rather than implicitly.

7.3 Content-trust considerations

Recipe Markdown files should reference images only via relative paths to files committed in the same recipe folder. The repository's own manual_lint.js enforces image existence; it does not block external image URLs, but external images should be avoided because:

  • They couple the repository's render to a third-party CDN's uptime.
  • They expose readers' IPs to that third party.
  • They can change content without the repository being updated.

Warning

If you find a recipe referencing an external image URL (e.g., ![](https://i.imgur.com/...)), open a PR that downloads the image into the recipe's folder and rewrites the path. This protects readers from broken or maliciously substituted images and makes the recipe work offline in a clone.

7.4 Scraping & automation policy

The repository is licensed under the Unlicense, so the content itself is in the public domain. However, automated scraping should still respect GitHub's terms of service:

  • Honour robots.txt and per-host rate limits.
  • Use the GitHub API (with a token) rather than HTML scraping for bulk pulls; git clone --depth 1 is the canonical bulk-fetch path.
  • Cache aggressively — the corpus changes a few times per week, not per minute.
  • Do not impersonate browsers; identify your tool in the User-Agent string.

For organisations that want long-term reliability rather than convenience, the safest posture is:

  1. Fork the repository into your own GitHub organisation.
  2. Mirror it to a private GitLab/Gitea/Bitbucket as well.
  3. Pin all dependencies (npm via lockfile already; Python via lockfile in your fork).
  4. Build the static site from your fork on your own CI; deploy from your own bucket/CDN.
  5. Sync from upstream on a deliberate schedule (weekly or fortnightly), re-running the lint chain before merging upstream changes.

This defends against upstream takedown, account compromise, and dependency drift simultaneously. It is the strongly recommended posture for any production deployment.

Important

The Unlicense permits unconditional copying. Use that latitude: mirror, fork, and build from your own copy. The upstream project is a single-maintainer effort and should not be treated as a dependency you can pin to.


8. Developer Internals

8.1 Repository structure deep-dive

HowToCook/
├── .github/
│   ├── readme-generate.js     # Walks dishes/ and rebuilds README.md's index
│   └── manual_lint.js         # Project-specific structural lint
├── dishes/                    # Recipe corpus
│   ├── aquatic/               # Seafood
│   ├── breakfast/             # Breakfast items
│   ├── condiment/             # Sauces and seasonings
│   ├── dessert/               # Desserts and baked goods
│   ├── drink/                 # Beverages and cocktails
│   ├── meat_dish/             # Meat-centred dishes
│   ├── semi-finished/         # Air-fryer / frozen products
│   ├── soup/                  # Soups and congee
│   ├── staple/                # Rice, noodles, bread
│   ├── vegetable_dish/        # Vegetable-centred dishes
│   └── template/示例菜/         # Recipe template
├── tips/
│   ├── 厨房准备.md
│   ├── 如何选择现在吃什么.md
│   ├── 食材相克与禁忌.md
│   ├── learn/                 # 11 fundamentals tutorials
│   └── advanced/              # 4 advanced-technique tutorials
├── starsystem/
│   ├── 0Star.md               # Zero-difficulty
│   ├── 1Star.md               # Single-pan
│   ├── 2Star.md               # Basic stir-fry
│   ├── 3Star.md               # Multi-step
│   ├── 4Star.md               # Technique-driven
│   ├── 5Star.md               # Hobbyist
│   ├── 7Star.md               # Restaurant-grade
│   └── 8Star.md               # Banquet menus
├── .gitattributes
├── .gitignore
├── .markdownlint.json
├── .textlintrc
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Dockerfile
├── LICENSE
├── README.md                  # Auto-generated; do not edit by hand
├── package.json
├── package-lock.json
└── requirements.txt

8.2 The recipe template — dishes/template/示例菜/示例菜.md

Every contributed recipe is a copy of this file with the placeholder content replaced. The template's role is to keep the corpus parseable: every recipe has the same H2 sections in the same order, so downstream tools never need custom heuristics per recipe.

Section H-level Required? Purpose
Dish name H1 Yes The dish, repeated as the file title
必要原料 (necessary materials) H2 Yes Equipment and ingredients with quantities
计算 (calculations) H2 Yes Per-serving math and scaling notes
操作 (operations) H2 Yes Numbered preparation/cooking steps
附加内容 (additional content) H2 Optional Variations, troubleshooting, finishing tips

manual_lint.js (line-level details aside) confirms the H1 + the four H2 section headers are present. Missing any required section fails CI.

8.3 The README generator — .github/readme-generate.js

readme-generate.js walks dishes/ and rewrites the recipe-index sections of README.md. The maintainer runs npm run build after merging any PR that adds, removes, or renames a recipe. The script is idempotent: running it twice in succession produces no diff.

Note

The README is therefore a derived artifact, not a hand-edited file. Pull requests that hand-edit README.md without running npm run build introduce drift that the next maintainer will have to revert. Always regenerate after content changes.

8.4 The manual linter — .github/manual_lint.js

manual_lint.js performs three structural checks that markdownlint cannot:

  1. Template-section presence — every recipe must contain the four required H2 sections in the canonical order.
  2. Image-existence — every ![](path) reference in a recipe must resolve to a real file on disk.
  3. Category placement — recipes must live in one of the eleven existing dishes/<category>/ folders, not at the root or in a typo'd folder.

Operators extending the linter should add new structural checks here rather than overloading markdownlint with custom rules.

8.5 Code patterns and conventions

Convention Where it applies Why
Chinese filenames All recipes and tutorials Authentic, search-friendly for the target audience
Same-name folder per dish (when images exist) dishes/<cat>/<dish>/<dish>.md Keeps photos colocated with the recipe
Full-width punctuation in prose Throughout Standard Chinese typography; enforced by textlint
Half-width punctuation in code All fenced code blocks Standard programming syntax; enforced by inline-code escape
Single master branch repository-wide Documentation repos do not need GitFlow
Direct merges over rebases Maintainer convention Preserves contributor history

8.6 Testing strategy

The "tests" for a recipe corpus are the linters and human review:

Layer Mechanism What it catches
Structural markdownlint-cli 0.41.0 Heading hierarchy, list integrity
Prose textlint 12.x CJK spacing and bracket-width violations
Domain manual_lint.js Template fields, image existence, category placement
Editorial Human PR review Recipe correctness, ingredient accuracy, photo quality

There is no unit-test suite because there is no code under test.

8.7 Potential improvements

The following are tech-debt items and improvement opportunities visible from the codebase and the issue tracker:

  • Pin Python dependencies in requirements.txt — currently unpinned; exposes the static-site build to upstream regressions. Generate a lockfile via pip-compile or uv.
  • Add YAML frontmatter to recipes — issue #60 tracks this. Frontmatter would give downstream tools a structured ingredient list, prep time, servings, and difficulty without prose parsing.
  • Address the 6Star.md gap — the difficulty system jumps from 5 to 7; starsystem/6Star.md is conspicuously absent.
  • Auto-generate per-day menus — issue #1751 requests this; it's a downstream-tool job that would benefit from frontmatter.
  • Improve print legibility — issue #1762 reports that print output of recipes has tiny fonts. The fix is in the mkdocs-with-pdf stylesheet, not the source recipes.
  • Annotation feature — issue #1758 requests inline annotations; this is a renderer feature, not a source change.
  • Cuisine breadth — issue #1768 requests more Western dishes (Michelin-style); contributors welcome.
  • Recipe-quality regressions — issue #1765 tracks the long-running quality discussion. A structured review pass on the twenty-most-edited recipes would close many leaf-level issues.
  • Specific recipe corrections — issues #1757 (戚风蛋糕) and #1759 (南派 红烧肉) are open with detailed correction proposals; both are low-risk PRs for new contributors.

8.8 Contributing — the developer-facing process

For first-time contributors, the expected flow is:

  1. Open an issue describing the recipe or fix.
  2. Wait for maintainer triage (usually <72h).
  3. Fork, branch, edit, lint locally with npm run lint.
  4. Open a PR referencing the issue.
  5. Address review comments; CI must be green.
  6. Maintainer merges; npm run build runs to update README.

Maintainers should keep the merge cadence high but the bar firm: every PR must pass npm run lint and have at least one finished-dish photograph for new recipes. Editorial accuracy beats stylistic preferences.


9. Integration Points

The corpus is designed for downstream consumption. The following integration patterns are already in use or actively proposed by the community.

9.1 The cook.aiursoft.com mirror

The canonical downstream consumer. It performs the static-site build described in Section 4 and serves a paginated, searchable, printable rendering. Declared as homepage in package.json. Operationally independent of the source repository.

9.2 GitHub-rendered tree

GitHub itself acts as a rendering layer — every .md file is browsable with images inline. Use this for any low-stakes embedding via iframe or direct linking.

9.3 Programmatic parsing — the stable Markdown contract

Downstream tools consuming the corpus rely on three stability guarantees:

Guarantee Maintained by Failure mode if violated
One recipe per .md file Authoring convention Silent confusion in tools that count files
Stable H2 section names (必要原料, 计算, 操作, 附加内容) manual_lint.js Parsers miss sections
Images live next to the .md manual_lint.js Broken image links in renderers

Tooling consuming the corpus should hard-fail (not silently degrade) when any guarantee is violated; otherwise stale or incorrect data leaks downstream.

9.4 Image consumers — caveat about external mirrors

Several community projects mirror or transform the recipe images. Operators of such mirrors should:

  • Re-host images locally; do not hotlink to GitHub's user-content CDN.
  • Respect the Unlicense (no attribution required, but politeness asks for it).
  • Refresh on a schedule that matches the upstream commit cadence.

9.5 Proposed/future integrations

  • NanoBananaPro image-rich preview + PDF export — issue #1755 proposes a third-party renderer optimised for visual-first browsing. Not yet implemented.
  • YAML-frontmatter recipe schema — issue #60 proposes adding YAML frontmatter to recipes. If accepted, downstream tools would get a structured contract instead of relying on heading-text matching.
  • Daily-menu generator — issue #1751 proposes automatic per-day menu generation. Best implemented as a downstream tool consuming the corpus.

9.6 Integration anti-patterns

Caution

Two integration patterns to avoid:

Don't depend on the README's recipe index for anything mechanical. The README is auto-generated by readme-generate.js and may drift between commits. Walk dishes/ directly instead.

Don't key downstream caches on the package.json version field (1.5.0 at this commit). It is not actively bumped on every content change. Use the git commit hash for cache invalidation.

9.7 Reaching the maintainers

Channel Best for
GitHub Issues (link) Bug reports, feature requests, recipe corrections
GitHub Discussions (if enabled on your fork) Open-ended conversation, Q&A
Pull Requests Code, recipe, or documentation changes
Repository email (in package.json author / git log) Operational concerns about the cook.aiursoft.com mirror

Note

The project is maintained by a small team. Issue triage is best-effort and may take days for non-urgent items. Bug reports with reproducible minimal examples (a specific recipe path, a specific lint failure) get attention faster than open-ended discussion threads.