feat(baoyu-translate): add three-mode translation skill with glossary support
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
ce259e4547
commit
5b4ba3ac3f
|
|
@ -45,7 +45,8 @@
|
|||
"./skills/baoyu-compress-image",
|
||||
"./skills/baoyu-url-to-markdown",
|
||||
"./skills/baoyu-format-markdown",
|
||||
"./skills/baoyu-markdown-to-html"
|
||||
"./skills/baoyu-markdown-to-html",
|
||||
"./skills/baoyu-translate"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -152,6 +152,7 @@ slide-deck/
|
|||
infographic/
|
||||
illustrations/
|
||||
comic/
|
||||
translate/
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
|
|
|
|||
26
README.md
26
README.md
|
|
@ -55,7 +55,7 @@ Simply tell Claude Code:
|
|||
|--------|-------------|--------|
|
||||
| **content-skills** | Content generation and publishing | [xhs-images](#baoyu-xhs-images), [infographic](#baoyu-infographic), [cover-image](#baoyu-cover-image), [slide-deck](#baoyu-slide-deck), [comic](#baoyu-comic), [article-illustrator](#baoyu-article-illustrator), [post-to-x](#baoyu-post-to-x), [post-to-wechat](#baoyu-post-to-wechat) |
|
||||
| **ai-generation-skills** | AI-powered generation backends | [image-gen](#baoyu-image-gen), [danger-gemini-web](#baoyu-danger-gemini-web) |
|
||||
| **utility-skills** | Utility tools for content processing | [url-to-markdown](#baoyu-url-to-markdown), [danger-x-to-markdown](#baoyu-danger-x-to-markdown), [compress-image](#baoyu-compress-image), [format-markdown](#baoyu-format-markdown) |
|
||||
| **utility-skills** | Utility tools for content processing | [url-to-markdown](#baoyu-url-to-markdown), [danger-x-to-markdown](#baoyu-danger-x-to-markdown), [compress-image](#baoyu-compress-image), [format-markdown](#baoyu-format-markdown), [translate](#baoyu-translate) |
|
||||
|
||||
## Update Skills
|
||||
|
||||
|
|
@ -739,6 +739,30 @@ Format plain text or markdown files with proper frontmatter, titles, summaries,
|
|||
| Code/commands | `` `inline` `` or ` ```block``` ` |
|
||||
| Quotes | `>` blockquote |
|
||||
|
||||
#### baoyu-translate
|
||||
|
||||
Translate articles and documents between languages with three modes: quick (direct), normal (analysis-informed), and refined (full publication-quality workflow).
|
||||
|
||||
```bash
|
||||
# Normal mode (default) - analyze then translate
|
||||
/translate article.md --to zh-CN
|
||||
|
||||
# Quick mode - direct translation
|
||||
/translate article.md --mode quick --to ja
|
||||
|
||||
# Refined mode - full workflow with review and polish
|
||||
/translate article.md --mode refined --to zh-CN
|
||||
```
|
||||
|
||||
**Modes**:
|
||||
| Mode | Steps | Use Case |
|
||||
|------|-------|----------|
|
||||
| Quick | Translate | Short texts, informal content |
|
||||
| Normal | Analyze → Translate | Articles, blog posts |
|
||||
| Refined | Analyze → Translate → Review → Polish | Publication-quality documents |
|
||||
|
||||
**Features**: Custom glossaries via EXTEND.md, audience-aware translation (general/technical/academic/business), automatic chunking for long documents with parallel subagent translation.
|
||||
|
||||
## Environment Configuration
|
||||
|
||||
Some skills require API keys or custom configuration. Environment variables can be set in `.env` files:
|
||||
|
|
|
|||
26
README.zh.md
26
README.zh.md
|
|
@ -55,7 +55,7 @@ npx skills add jimliu/baoyu-skills
|
|||
|------|------|----------|
|
||||
| **content-skills** | 内容生成和发布 | [xhs-images](#baoyu-xhs-images), [infographic](#baoyu-infographic), [cover-image](#baoyu-cover-image), [slide-deck](#baoyu-slide-deck), [comic](#baoyu-comic), [article-illustrator](#baoyu-article-illustrator), [post-to-x](#baoyu-post-to-x), [post-to-wechat](#baoyu-post-to-wechat) |
|
||||
| **ai-generation-skills** | AI 生成后端 | [image-gen](#baoyu-image-gen), [danger-gemini-web](#baoyu-danger-gemini-web) |
|
||||
| **utility-skills** | 内容处理工具 | [url-to-markdown](#baoyu-url-to-markdown), [danger-x-to-markdown](#baoyu-danger-x-to-markdown), [compress-image](#baoyu-compress-image), [format-markdown](#baoyu-format-markdown) |
|
||||
| **utility-skills** | 内容处理工具 | [url-to-markdown](#baoyu-url-to-markdown), [danger-x-to-markdown](#baoyu-danger-x-to-markdown), [compress-image](#baoyu-compress-image), [format-markdown](#baoyu-format-markdown), [translate](#baoyu-translate) |
|
||||
|
||||
## 更新技能
|
||||
|
||||
|
|
@ -739,6 +739,30 @@ AI 驱动的生成后端。
|
|||
| 代码/命令 | `` `行内` `` 或 ` ```代码块``` ` |
|
||||
| 引用 | `>` 引用块 |
|
||||
|
||||
#### baoyu-translate
|
||||
|
||||
三模式翻译技能:快速(直接翻译)、标准(分析后翻译)、精翻(完整出版级工作流,含审校与润色)。
|
||||
|
||||
```bash
|
||||
# 标准模式(默认)- 先分析再翻译
|
||||
/translate article.md --to zh-CN
|
||||
|
||||
# 快速模式 - 直接翻译
|
||||
/translate article.md --mode quick --to ja
|
||||
|
||||
# 精翻模式 - 完整工作流,含审校与润色
|
||||
/translate article.md --mode refined --to zh-CN
|
||||
```
|
||||
|
||||
**模式**:
|
||||
| 模式 | 步骤 | 适用场景 |
|
||||
|------|------|----------|
|
||||
| 快速 | 翻译 | 短文本、非正式内容 |
|
||||
| 标准 | 分析 → 翻译 | 文章、博客 |
|
||||
| 精翻 | 分析 → 翻译 → 审校 → 润色 | 出版级文档 |
|
||||
|
||||
**特性**:通过 EXTEND.md 自定义术语表、面向受众的翻译(通用/技术/学术/商务)、长文档自动分块并行翻译。
|
||||
|
||||
## 环境配置
|
||||
|
||||
部分技能需要 API 密钥或自定义配置。环境变量可以在 `.env` 文件中设置:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,256 @@
|
|||
---
|
||||
name: baoyu-translate
|
||||
description: Translates articles and documents between languages with three modes - quick (direct), normal (analyze then translate), and refined (analyze, translate, review, polish). Supports custom glossaries and terminology consistency via EXTEND.md. Use when user asks to "translate", "翻译", "精翻", "translate article", "translate to Chinese/English", or needs any document translation. Also triggers for "refined translation", "精细翻译", "proofread translation", or "快速翻译".
|
||||
---
|
||||
|
||||
# Translator
|
||||
|
||||
Three-mode translation skill: **quick** for direct translation, **normal** for analysis-informed translation, **refined** for full publication-quality workflow with review and polish.
|
||||
|
||||
## Script Directory
|
||||
|
||||
Scripts in `scripts/` subdirectory. `${SKILL_DIR}` = this SKILL.md's directory path. Resolve `${BUN_X}` runtime: if `bun` installed → `bun`; if `npx` available → `npx -y bun`; else suggest installing bun. Replace `${SKILL_DIR}` and `${BUN_X}` with actual values.
|
||||
|
||||
| Script | Purpose |
|
||||
|--------|---------|
|
||||
| `scripts/chunk.ts` | Split markdown into chunks by AST blocks (sections, headings, paragraphs), with line/word fallback for oversized blocks |
|
||||
|
||||
## Preferences (EXTEND.md)
|
||||
|
||||
Check EXTEND.md existence (priority order):
|
||||
|
||||
```bash
|
||||
# macOS, Linux, WSL, Git Bash
|
||||
test -f .baoyu-skills/baoyu-translate/EXTEND.md && echo "project"
|
||||
test -f "$HOME/.baoyu-skills/baoyu-translate/EXTEND.md" && echo "user"
|
||||
```
|
||||
|
||||
```powershell
|
||||
# PowerShell (Windows)
|
||||
if (Test-Path .baoyu-skills/baoyu-translate/EXTEND.md) { "project" }
|
||||
if (Test-Path "$HOME/.baoyu-skills/baoyu-translate/EXTEND.md") { "user" }
|
||||
```
|
||||
|
||||
| Path | Location |
|
||||
|------|----------|
|
||||
| `.baoyu-skills/baoyu-translate/EXTEND.md` | Project directory |
|
||||
| `$HOME/.baoyu-skills/baoyu-translate/EXTEND.md` | User home |
|
||||
|
||||
| Result | Action |
|
||||
|--------|--------|
|
||||
| Found | Read, parse, apply settings. On first use in session, briefly remind: "Using preferences from [path]. You can edit EXTEND.md to customize glossary, audience, etc." |
|
||||
| Not found | **MUST** run first-time setup (see below) — do NOT silently use defaults |
|
||||
|
||||
**EXTEND.md Supports**: Default target language | Default mode | Target audience | Custom glossaries | Translation style | Chunk settings
|
||||
|
||||
Schema: [references/config/extend-schema.md](references/config/extend-schema.md)
|
||||
|
||||
### First-Time Setup (BLOCKING)
|
||||
|
||||
**CRITICAL**: When EXTEND.md is not found, you **MUST** run the first-time setup before ANY translation. This is a **BLOCKING** operation.
|
||||
|
||||
Full reference: [references/config/first-time-setup.md](references/config/first-time-setup.md)
|
||||
|
||||
Use `AskUserQuestion` with all questions (target language, mode, audience, style, save location) in ONE call. After user answers, create EXTEND.md at the chosen location, confirm "Preferences saved to [path]", then continue.
|
||||
|
||||
## Defaults
|
||||
|
||||
All configurable values in one place. EXTEND.md overrides these; CLI flags override EXTEND.md.
|
||||
|
||||
| Setting | Default | EXTEND.md key | CLI flag | Description |
|
||||
|---------|---------|---------------|----------|-------------|
|
||||
| Target language | `zh-CN` | `target_language` | `--to` | Translation target language |
|
||||
| Mode | `normal` | `default_mode` | `--mode` | Translation mode |
|
||||
| Audience | `general` | `audience` | `--audience` | Target reader profile |
|
||||
| Style | `storytelling` | `style` | — | Translation style preference |
|
||||
| Chunk threshold | `4000` | `chunk_threshold` | — | Word count to trigger chunked translation |
|
||||
| Chunk max words | `5000` | `chunk_max_words` | — | Max words per chunk |
|
||||
|
||||
## Modes
|
||||
|
||||
| Mode | Flag | Steps | When to Use |
|
||||
|------|------|-------|-------------|
|
||||
| Quick | `--mode quick` | Translate | Short texts, informal content, quick tasks |
|
||||
| Normal | `--mode normal` (default) | Analyze → Translate | Articles, blog posts, general content |
|
||||
| Refined | `--mode refined` | Analyze → Translate → Review → Polish | Publication-quality, important documents |
|
||||
|
||||
**Default mode**: Normal (can be overridden in EXTEND.md `default_mode` setting).
|
||||
|
||||
**Auto-detection**:
|
||||
- "快翻", "quick", "直接翻译" → quick mode
|
||||
- "精翻", "refined", "publication quality", "proofread" → refined mode
|
||||
- Otherwise → default mode (normal)
|
||||
|
||||
**Upgrade prompt**: After normal mode completes, display:
|
||||
> Translation saved. To further review and polish, reply "继续润色" or "refine".
|
||||
|
||||
If user responds, continue with review → polish steps (same as refined mode Steps 3-4) on the existing output.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
/translate [--mode quick|normal|refined] [--from <lang>] [--to <lang>] [--audience <audience>] [--glossary <file>] <source>
|
||||
```
|
||||
|
||||
- `<source>`: File path, URL, or inline text
|
||||
- `--from`: Source language (auto-detect if omitted)
|
||||
- `--to`: Target language (from EXTEND.md or default `zh-CN`)
|
||||
- `--audience`: Target reader profile (from EXTEND.md or default `general`)
|
||||
- `--glossary`: Additional glossary file to merge with EXTEND.md glossary
|
||||
|
||||
**Audience presets**:
|
||||
|
||||
| Value | Description | Effect |
|
||||
|-------|-------------|--------|
|
||||
| `general` | General readers (default) | Plain language, more translator's notes for jargon |
|
||||
| `technical` | Developers / engineers | Less annotation on common tech terms |
|
||||
| `academic` | Researchers / scholars | Formal register, precise terminology |
|
||||
| `business` | Business professionals | Business-friendly tone, explain tech concepts |
|
||||
|
||||
Custom audience descriptions are also accepted, e.g., `--audience "AI感兴趣的普通读者"`.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Load Preferences
|
||||
|
||||
1.1 Check EXTEND.md (see Preferences section above)
|
||||
|
||||
1.2 Load built-in glossary for the language pair if available:
|
||||
- EN→ZH: [references/glossary-en-zh.md](references/glossary-en-zh.md)
|
||||
|
||||
1.3 Merge glossaries: EXTEND.md glossary + built-in glossary + `--glossary` file (CLI overrides all)
|
||||
|
||||
### Step 2: Materialize Source & Create Output Directory
|
||||
|
||||
If the input is not a file, save it as one first. Then create an output subdirectory next to the source file.
|
||||
|
||||
**2.1 Materialize source**
|
||||
|
||||
| Input Type | Action |
|
||||
|------------|--------|
|
||||
| File | Use as-is (no copy needed) |
|
||||
| Inline text | Save to `translate/{slug}.md` |
|
||||
| URL | Fetch content, save to `translate/{slug}.md` |
|
||||
|
||||
`{slug}`: 2-4 word kebab-case slug derived from content topic.
|
||||
|
||||
**2.2 Create output directory**
|
||||
|
||||
Create a subdirectory next to the source file: `{source-dir}/{source-basename}-{target-lang}/`
|
||||
|
||||
Examples:
|
||||
- `posts/article.md` → `posts/article-zh/`
|
||||
- `translate/ai-future.md` → `translate/ai-future-zh/`
|
||||
|
||||
**Conflict resolution**: If the output directory already exists, rename the existing one to `{name}.backup-YYYYMMDD-HHMMSS/` before creating the new one. Never overwrite existing results.
|
||||
|
||||
**2.3 Output directory contents**
|
||||
|
||||
All intermediate and final files go into this directory:
|
||||
|
||||
| File | Mode | Description |
|
||||
|------|------|-------------|
|
||||
| `translation.md` | All | Final translation (always this name) |
|
||||
| `01-analysis.md` | Normal, Refined | Content analysis (domain, tone, terminology) |
|
||||
| `02-prompt.md` | Normal, Refined | Assembled translation prompt (used by subagent or inline) |
|
||||
| `03-draft.md` | Refined | Initial draft before review |
|
||||
| `04-review.md` | Refined | Reviewed version with fixes |
|
||||
| `chunks/` | Chunked | Source chunks + translated chunks |
|
||||
| `chunks/chunk-01.md` | Chunked | Source chunk |
|
||||
| `chunks/chunk-01-draft.md` | Chunked | Translated chunk |
|
||||
|
||||
Detect source language if `--from` not specified.
|
||||
|
||||
### Step 3: Assess Content Length
|
||||
|
||||
Quick mode does not chunk — translate directly regardless of length. If content exceeds model context limits, suggest the user switch to normal or refined mode.
|
||||
|
||||
For normal and refined modes:
|
||||
|
||||
| Content | Action |
|
||||
|---------|--------|
|
||||
| < chunk threshold | Translate as single unit |
|
||||
| >= chunk threshold | Chunk translation (see Step 3.1) |
|
||||
|
||||
**3.1 Long Content Preparation** (normal/refined modes, >= chunk threshold only)
|
||||
|
||||
Before translating chunks:
|
||||
|
||||
1. **Extract terminology**: Scan entire document for proper nouns, technical terms, recurring phrases
|
||||
2. **Build session glossary**: Merge extracted terms with loaded glossaries, establish consistent translations
|
||||
3. **Split into chunks**: Use `${BUN_X} ${SKILL_DIR}/scripts/chunk.ts <file> [--max-words <chunk_max_words>]`
|
||||
- Parses markdown AST (headings, paragraphs, lists, code blocks, tables, etc.)
|
||||
- Splits at markdown block boundaries to preserve structure
|
||||
- If a single block exceeds the threshold, falls back to line splitting, then word splitting
|
||||
4. **Assemble translation prompt**:
|
||||
- Main agent reads `01-analysis.md` (if exists) and assembles shared context using Part 1 of [references/subagent-prompt-template.md](references/subagent-prompt-template.md) — inlining content background, merged glossary, and comprehension challenges
|
||||
- Save as `02-prompt.md` in the output directory (shared context only, no task instructions)
|
||||
5. **Draft translation via subagents** (if Agent tool available):
|
||||
- Spawn one subagent **per chunk**, all in parallel (Part 2 of the template)
|
||||
- Each subagent reads `02-prompt.md` for shared context, translates its chunk, saves to `chunks/chunk-NN-draft.md`
|
||||
- Terminology consistency is guaranteed by the shared `02-prompt.md` (glossary + comprehension challenges from analysis)
|
||||
- If no chunks (content under threshold): spawn one subagent for the entire source file
|
||||
- If Agent tool is unavailable, translate chunks sequentially inline using `02-prompt.md`
|
||||
6. **Merge**: Once all subagents complete, combine translated chunks in order, prepend frontmatter if present → save as `03-draft.md` (refined) or `translation.md` (normal)
|
||||
7. All intermediate files (source chunks + translated chunks) are preserved in `chunks/`
|
||||
|
||||
**After chunked draft is merged**, return control to main agent for review and polish (Step 4).
|
||||
|
||||
### Step 4: Translate & Refine
|
||||
|
||||
**Translation principles** (apply to all modes):
|
||||
|
||||
- **Accuracy first**: Facts, data, and logic must match the original exactly
|
||||
- **Natural flow**: Use idiomatic target language word order; break long sentences into shorter ones
|
||||
- **Terminology**: Use standard translations; annotate with original term in parentheses on first occurrence
|
||||
- **Preserve format**: Keep all markdown formatting (headings, bold, italic, images, links, code blocks)
|
||||
- **Respect original**: Maintain original structure and meaning; do not add, remove, or editorialize
|
||||
- **Translator's notes**: For terms, concepts, or cultural references that target readers may not understand — due to jargon, cultural gaps, or domain-specific knowledge — add a concise explanatory note in parentheses immediately after the term. The note should explain *what it means* in plain language, not just provide the English original. Format: `译文(English original,通俗解释)`. Calibrate annotation depth to the target audience: general readers need more notes than technical readers. Only add notes where genuinely needed; do not over-annotate obvious terms.
|
||||
|
||||
#### Quick Mode
|
||||
|
||||
Translate directly → save to `translation.md`.
|
||||
|
||||
#### Normal Mode
|
||||
|
||||
1. **Analyze** → `01-analysis.md` (domain, tone, audience, terminology, reader comprehension challenges)
|
||||
2. **Assemble prompt** → `02-prompt.md` (translation instructions with inlined context)
|
||||
3. **Translate** (following `02-prompt.md`) → `translation.md`
|
||||
|
||||
After completion, prompt user: "Translation saved. To further review and polish, reply **继续润色** or **refine**."
|
||||
|
||||
If user continues, proceed with review → polish (same as refined mode Steps 3-4 below), saving `03-draft.md` (rename current `translation.md`), `04-review.md`, and updated `translation.md`.
|
||||
|
||||
#### Refined Mode
|
||||
|
||||
Full workflow for publication quality. See [references/refined-workflow.md](references/refined-workflow.md) for detailed guidelines per step.
|
||||
|
||||
The subagent (if used in Step 3.1) only handles the initial draft. All subsequent steps are handled by the main agent, which may delegate to subagents at its discretion.
|
||||
|
||||
Steps and saved files (all in output directory):
|
||||
1. **Analyze** → `01-analysis.md` (domain, tone, terminology, reader comprehension challenges)
|
||||
2. **Assemble prompt** → `02-prompt.md` (translation instructions with inlined context)
|
||||
3. **Draft** → `03-draft.md` (initial translation with translator's notes; from subagent if chunked)
|
||||
4. **Review** → `04-review.md` (accuracy, naturalness, terminology fixes)
|
||||
5. **Polish** → `translation.md` (final publication-quality translation)
|
||||
|
||||
Each step reads the previous step's file and builds on it.
|
||||
|
||||
### Step 5: Output
|
||||
|
||||
Final translation is always at `translation.md` in the output directory.
|
||||
|
||||
Display summary:
|
||||
```
|
||||
**Translation complete** ({mode} mode)
|
||||
|
||||
Source: {source-path}
|
||||
Languages: {from} → {to}
|
||||
Output dir: {output-dir}/
|
||||
Final: {output-dir}/translation.md
|
||||
Glossary terms applied: {count}
|
||||
```
|
||||
|
||||
## Extension Support
|
||||
|
||||
Custom configurations via EXTEND.md. See **Preferences** section for paths and supported options.
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
# EXTEND.md Schema for baoyu-translate
|
||||
|
||||
## Format
|
||||
|
||||
EXTEND.md uses YAML format:
|
||||
|
||||
```yaml
|
||||
# Default target language (ISO code or common name)
|
||||
target_language: zh-CN
|
||||
|
||||
# Default translation mode
|
||||
default_mode: normal # quick | normal | refined
|
||||
|
||||
# Target audience (affects annotation depth and register)
|
||||
audience: general # general | technical | academic | business | or custom string
|
||||
|
||||
# Translation style preference
|
||||
style: storytelling # storytelling | formal | technical | literal
|
||||
|
||||
# Word count threshold to trigger chunked translation
|
||||
chunk_threshold: 4000
|
||||
|
||||
# Max words per chunk
|
||||
chunk_max_words: 5000
|
||||
|
||||
# Custom glossary (merged with built-in glossary)
|
||||
# CLI --glossary flag overrides these
|
||||
glossary:
|
||||
- from: "Reinforcement Learning"
|
||||
to: "强化学习"
|
||||
- from: "Transformer"
|
||||
to: "Transformer"
|
||||
note: "Keep English"
|
||||
|
||||
# Language-pair specific glossaries
|
||||
glossaries:
|
||||
en-zh:
|
||||
- from: "AI Agent"
|
||||
to: "AI 智能体"
|
||||
ja-zh:
|
||||
- from: "人工知能"
|
||||
to: "人工智能"
|
||||
```
|
||||
|
||||
## Fields
|
||||
|
||||
| Field | Type | Default | Description |
|
||||
|-------|------|---------|-------------|
|
||||
| `target_language` | string | `zh-CN` | Default target language code |
|
||||
| `default_mode` | string | `normal` | Default translation mode (`quick` / `normal` / `refined`) |
|
||||
| `audience` | string | `general` | Target reader profile (`general` / `technical` / `academic` / `business` / custom) |
|
||||
| `style` | string | `storytelling` | Translation style preference |
|
||||
| `chunk_threshold` | number | `4000` | Word count threshold to trigger chunked translation |
|
||||
| `chunk_max_words` | number | `5000` | Max words per chunk |
|
||||
| `glossary` | array | `[]` | Universal glossary entries |
|
||||
| `glossaries` | object | `{}` | Language-pair specific glossary entries |
|
||||
|
||||
## Glossary Entry
|
||||
|
||||
| Field | Required | Description |
|
||||
|-------|----------|-------------|
|
||||
| `from` | yes | Source term |
|
||||
| `to` | yes | Target translation |
|
||||
| `note` | no | Usage note (e.g., "Keep English", "Only in tech context") |
|
||||
|
||||
## Priority
|
||||
|
||||
1. CLI `--glossary` file entries
|
||||
2. EXTEND.md `glossaries[pair]` entries
|
||||
3. EXTEND.md `glossary` entries
|
||||
4. Built-in glossary (e.g., `references/glossary-en-zh.md`)
|
||||
|
||||
Later entries override earlier ones for the same source term.
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
---
|
||||
name: first-time-setup
|
||||
description: First-time setup flow for baoyu-translate preferences
|
||||
---
|
||||
|
||||
# First-Time Setup
|
||||
|
||||
## Overview
|
||||
|
||||
When no EXTEND.md is found, guide user through preference setup.
|
||||
|
||||
**BLOCKING OPERATION**: This setup MUST complete before ANY translation. Do NOT:
|
||||
- Start translating content
|
||||
- Ask about files or output paths
|
||||
- Proceed to any workflow steps
|
||||
|
||||
ONLY ask the questions in this setup flow, save EXTEND.md, then continue.
|
||||
|
||||
## Setup Flow
|
||||
|
||||
```
|
||||
No EXTEND.md found
|
||||
|
|
||||
v
|
||||
+---------------------+
|
||||
| AskUserQuestion |
|
||||
| (all questions) |
|
||||
+---------------------+
|
||||
|
|
||||
v
|
||||
+---------------------+
|
||||
| Create EXTEND.md |
|
||||
+---------------------+
|
||||
|
|
||||
v
|
||||
Continue translation
|
||||
```
|
||||
|
||||
## Questions
|
||||
|
||||
**Language**: Use user's input language or saved language preference.
|
||||
|
||||
Use AskUserQuestion with ALL questions in ONE call:
|
||||
|
||||
### Question 1: Target Language
|
||||
|
||||
```yaml
|
||||
header: "Target Language"
|
||||
question: "Default target language?"
|
||||
options:
|
||||
- label: "简体中文 zh-CN (Recommended)"
|
||||
description: "Translate to Simplified Chinese"
|
||||
- label: "繁體中文 zh-TW"
|
||||
description: "Translate to Traditional Chinese"
|
||||
- label: "English en"
|
||||
description: "Translate to English"
|
||||
- label: "日本語 ja"
|
||||
description: "Translate to Japanese"
|
||||
```
|
||||
|
||||
Note: User may type a custom language code.
|
||||
|
||||
### Question 2: Translation Mode
|
||||
|
||||
```yaml
|
||||
header: "Mode"
|
||||
question: "Default translation mode?"
|
||||
options:
|
||||
- label: "Normal (Recommended)"
|
||||
description: "Analyze content first, then translate"
|
||||
- label: "Quick"
|
||||
description: "Direct translation, no analysis"
|
||||
- label: "Refined"
|
||||
description: "Full workflow: analyze → translate → review → polish"
|
||||
```
|
||||
|
||||
### Question 3: Target Audience
|
||||
|
||||
```yaml
|
||||
header: "Audience"
|
||||
question: "Default target audience?"
|
||||
options:
|
||||
- label: "General readers (Recommended)"
|
||||
description: "Plain language, more translator's notes for jargon"
|
||||
- label: "Technical"
|
||||
description: "Developers/engineers, less annotation on tech terms"
|
||||
- label: "Academic"
|
||||
description: "Formal register, precise terminology"
|
||||
- label: "Business"
|
||||
description: "Business-friendly tone, explain tech concepts"
|
||||
```
|
||||
|
||||
Note: User may type a custom audience description.
|
||||
|
||||
### Question 4: Translation Style
|
||||
|
||||
```yaml
|
||||
header: "Style"
|
||||
question: "Translation style?"
|
||||
options:
|
||||
- label: "Storytelling (Recommended)"
|
||||
description: "Engaging, narrative-like flow"
|
||||
- label: "Formal"
|
||||
description: "Professional, structured"
|
||||
- label: "Technical"
|
||||
description: "Precise, documentation-style"
|
||||
- label: "Literal"
|
||||
description: "Close to original structure"
|
||||
```
|
||||
|
||||
### Question 5: Save Location
|
||||
|
||||
```yaml
|
||||
header: "Save"
|
||||
question: "Where to save preferences?"
|
||||
options:
|
||||
- label: "User (Recommended)"
|
||||
description: "$HOME/.baoyu-skills/ (all projects)"
|
||||
- label: "Project"
|
||||
description: ".baoyu-skills/ (this project only)"
|
||||
```
|
||||
|
||||
## Save Locations
|
||||
|
||||
| Choice | Path | Scope |
|
||||
|--------|------|-------|
|
||||
| User | `$HOME/.baoyu-skills/baoyu-translate/EXTEND.md` | All projects |
|
||||
| Project | `.baoyu-skills/baoyu-translate/EXTEND.md` | Current project |
|
||||
|
||||
## After Setup
|
||||
|
||||
1. Create directory if needed
|
||||
2. Write EXTEND.md with selected values
|
||||
3. Confirm: "Preferences saved to [path]"
|
||||
4. Mention: "You can add custom glossary terms to EXTEND.md anytime. See the `glossary` section in the file for the format."
|
||||
5. Continue with translation using saved preferences
|
||||
|
||||
## EXTEND.md Template
|
||||
|
||||
```yaml
|
||||
target_language: [zh-CN/zh-TW/en/ja/...]
|
||||
default_mode: [quick/normal/refined]
|
||||
audience: [general/technical/academic/business/custom]
|
||||
style: [storytelling/formal/technical/literal]
|
||||
|
||||
# Custom glossary (optional) — add your own term translations here
|
||||
# glossary:
|
||||
# - from: "Term"
|
||||
# to: "翻译"
|
||||
# - from: "Another Term"
|
||||
# to: "另一个翻译"
|
||||
# note: "Usage context"
|
||||
```
|
||||
|
||||
## Modifying Preferences Later
|
||||
|
||||
Users can edit EXTEND.md directly or delete it to trigger setup again.
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# English → Chinese Glossary
|
||||
|
||||
Terms where standard translation is non-obvious or easily mistranslated. Common terms with straightforward translations (e.g., Machine Learning → 机器学习) are omitted — the model already knows these.
|
||||
|
||||
| English | Chinese | Notes |
|
||||
|---------|---------|-------|
|
||||
| AI Agent | AI 智能体 | |
|
||||
| Vibe Coding | 凭感觉编程 | |
|
||||
| the Bitter Lesson | 苦涩的教训 | Rich Sutton's essay |
|
||||
| Context Engineering | 上下文工程 | |
|
||||
| AI Wrapper | AI 套壳 | |
|
||||
| RLHF | 基于人类反馈的强化学习 | |
|
||||
| Hallucination | 幻觉 | AI-specific meaning |
|
||||
| Alignment | 对齐 | AI safety context |
|
||||
| Guardrails | 护栏 | AI safety context |
|
||||
| Agentic | 智能体化的 | |
|
||||
| Grounding | 基础化/落地 | Context-dependent |
|
||||
| Embedding | 嵌入/向量化 | Context-dependent |
|
||||
| Moat | 护城河 | Business context |
|
||||
| Flywheel | 飞轮效应 | |
|
||||
| Boilerplate | 样板代码 | |
|
||||
|
|
@ -0,0 +1,186 @@
|
|||
# Translation Workflow Details
|
||||
|
||||
This file provides detailed guidelines for each workflow step. Steps are shared across modes:
|
||||
|
||||
- **Quick**: Translate only (no steps from this file)
|
||||
- **Normal**: Step 1 (Analysis) → Translate
|
||||
- **Refined**: Step 1 (Analysis) → Step 2 (Draft) → Step 3 (Review) → Step 4 (Polish)
|
||||
- **Normal → Upgrade**: After normal mode, user can continue with Step 3 → Step 4
|
||||
|
||||
All intermediate results are saved as files in the output directory.
|
||||
|
||||
## Step 1: Content Analysis
|
||||
|
||||
Before translating, deeply analyze the source material. Save analysis to `01-analysis.md` in the output directory. Focus on dimensions that directly inform translation quality.
|
||||
|
||||
### 1.1 Quick Summary
|
||||
|
||||
3-5 sentences capturing:
|
||||
- What is this content about?
|
||||
- What is the core argument?
|
||||
- What is the most valuable point?
|
||||
|
||||
### 1.2 Core Content
|
||||
|
||||
- **Core argument**: One sentence summary
|
||||
- **Key concepts**: What key concepts does the author use? How are they defined?
|
||||
- **Structure**: How is the argument developed? How do sections connect?
|
||||
- **Evidence**: What specific examples, data, or authoritative citations are used?
|
||||
|
||||
### 1.3 Background Context
|
||||
|
||||
- **Author**: Who is the author? What is their background and stance?
|
||||
- **Writing context**: What phenomenon, trend, or debate is this responding to?
|
||||
- **Purpose**: What problem is the author trying to solve? Who are they trying to influence?
|
||||
- **Implicit assumptions**: What unstated premises underlie the argument?
|
||||
|
||||
### 1.4 Terminology Extraction
|
||||
|
||||
- List all technical terms, proper nouns, brand names, acronyms
|
||||
- Cross-reference with loaded glossaries
|
||||
- For terms not in glossary, research standard translations
|
||||
- Record decisions in a working terminology table
|
||||
|
||||
### 1.5 Tone & Style
|
||||
|
||||
- Is the original formal or conversational?
|
||||
- Does it use humor, metaphor, or cultural references?
|
||||
- What register is appropriate for the translation given the target audience?
|
||||
|
||||
### 1.6 Reader Comprehension Challenges
|
||||
|
||||
Identify points where target readers may struggle, calibrated to the target audience:
|
||||
|
||||
- **Domain jargon**: Technical terms that lack widely-known translations or are meaningless when translated literally
|
||||
- **Cultural references**: Idioms, historical events, pop culture, social norms specific to the source culture
|
||||
- **Implicit knowledge**: Background context the original author assumes but target readers may lack
|
||||
- **Wordplay & metaphors**: Figurative language that doesn't carry over across languages
|
||||
- **Named concepts**: Theories, effects, or phenomena with coined names (e.g., "comb-over effect", "Dunning-Kruger effect")
|
||||
- **Cognitive gaps**: Counterintuitive claims or expectations vs. reality that need framing for target readers
|
||||
|
||||
For each identified challenge, note:
|
||||
1. The original term/passage
|
||||
2. Why it may confuse target readers
|
||||
3. A concise plain-language explanation to use as a translator's note
|
||||
|
||||
### 1.7 Translation Challenges
|
||||
|
||||
- Structural challenges (wordplay, ambiguity, puns that don't translate)
|
||||
- Passages where literal translation would lose meaning
|
||||
- Content where the author's voice or humor requires creative adaptation
|
||||
|
||||
**Save `01-analysis.md`** with:
|
||||
```
|
||||
## Quick Summary
|
||||
[3-5 sentences]
|
||||
|
||||
## Core Content
|
||||
Core argument: [one sentence]
|
||||
Key concepts: [list]
|
||||
Structure: [outline]
|
||||
|
||||
## Background Context
|
||||
Author: [who, background, stance]
|
||||
Writing context: [what this responds to]
|
||||
Purpose: [goal and target audience]
|
||||
Implicit assumptions: [unstated premises]
|
||||
|
||||
## Terminology
|
||||
[term → translation, ...]
|
||||
|
||||
## Tone & Style
|
||||
[assessment]
|
||||
|
||||
## Comprehension Challenges
|
||||
- [term/passage] → [why confusing] → [proposed note]
|
||||
- ...
|
||||
|
||||
## Translation Challenges
|
||||
[structural issues, creative adaptation needs]
|
||||
```
|
||||
|
||||
## Step 2: Assemble Translation Prompt
|
||||
|
||||
Main agent reads `01-analysis.md` and assembles a complete translation prompt using [references/subagent-prompt-template.md](subagent-prompt-template.md). Inline content background, merged glossary, and comprehension challenges into the prompt. Save to `02-prompt.md`.
|
||||
|
||||
This prompt is used by the subagent (chunked) or by the main agent itself (non-chunked).
|
||||
|
||||
## Step 3: Initial Draft
|
||||
|
||||
Save to `03-draft.md` in the output directory.
|
||||
|
||||
For chunked content, the subagent produces this draft (merged from chunk translations). For non-chunked content, the main agent produces it directly.
|
||||
|
||||
Translate the full content following `02-prompt.md`:
|
||||
|
||||
- Use the terminology decisions from Step 1 consistently
|
||||
- Match the identified tone and register
|
||||
- Break complex sentences into natural target-language patterns
|
||||
- Preserve all formatting and structure
|
||||
- First occurrence of technical terms: translated term (original in parentheses)
|
||||
- Add translator's notes for comprehension challenges identified in Step 1: use parentheses with a plain-language explanation, e.g., `译文(English original,通俗解释)`
|
||||
- Only annotate where genuinely needed — do not over-explain obvious terms
|
||||
|
||||
## Step 4: Review
|
||||
|
||||
The main agent reviews the draft. Save reviewed version to `04-review.md`.
|
||||
|
||||
Systematically review against these criteria:
|
||||
|
||||
### Accuracy Check
|
||||
- Compare each paragraph against the original
|
||||
- Verify all facts, numbers, dates, and proper nouns
|
||||
- Ensure no content was accidentally added, removed, or altered
|
||||
- Check that technical terms match glossary consistently throughout
|
||||
|
||||
### Naturalness Check
|
||||
- Read the translation as if it were original content (not a translation)
|
||||
- Flag sentences that sound like "translationese" — awkward word order, calques, unnatural phrasing
|
||||
- Check paragraph transitions and logical flow
|
||||
- Verify sentence length feels natural for target language
|
||||
|
||||
### Terminology Consistency
|
||||
- Verify each glossary term is translated the same way throughout
|
||||
- Check proper nouns are handled consistently (transliterated vs. kept in original)
|
||||
- Verify annotations appear on first occurrence only
|
||||
|
||||
### Cultural Adaptation & Translator's Notes
|
||||
- Are cultural references explained where needed?
|
||||
- Do metaphors and idioms work in the target language?
|
||||
- Are any references potentially confusing or offensive in the target culture?
|
||||
- Check translator's notes: are they accurate, concise, and genuinely helpful?
|
||||
- Identify any missed comprehension challenges that should have notes
|
||||
- Remove over-annotations on terms that are obvious to the target audience
|
||||
|
||||
## Step 5: Polish
|
||||
|
||||
Save final version to `translation.md`.
|
||||
|
||||
Final pass addressing all issues from the review:
|
||||
|
||||
- Fix all accuracy issues found in review
|
||||
- Rewrite unnatural sentences for fluency
|
||||
- Improve transitions between paragraphs
|
||||
- Ensure the translation reads as engaging, native-quality content
|
||||
- Verify formatting is preserved correctly
|
||||
- For storytelling-style content: ensure the narrative flow draws readers in
|
||||
- Final consistency check on terminology
|
||||
|
||||
## Subagent Responsibility
|
||||
|
||||
Each subagent (one per chunk) is responsible **only** for producing the initial draft of its chunk (Step 3). The main agent assembles the shared prompt (Step 2), spawns all subagents in parallel, then takes over for review (Step 4) and polish (Step 5). The main agent may delegate review or polish to subagents at its own discretion.
|
||||
|
||||
## Chunked Refined Translation
|
||||
|
||||
When content exceeds the chunk threshold (see Defaults in SKILL.md) and uses refined mode:
|
||||
|
||||
1. Main agent runs analysis (Step 1) on the **entire** document first → `01-analysis.md`
|
||||
2. Main agent assembles translation prompt → `02-prompt.md`
|
||||
3. Split into chunks → `chunks/`
|
||||
4. Spawn one subagent per chunk in parallel (each reads `02-prompt.md` for shared context) → merge all results into `03-draft.md`
|
||||
5. Main agent reviews the merged draft → `04-review.md`
|
||||
6. Main agent polishes → `translation.md`
|
||||
7. Final cross-chunk consistency check:
|
||||
- Check terminology consistency across chunk boundaries
|
||||
- Verify narrative flow between chunks
|
||||
- Fix any transition issues at chunk boundaries
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
# Subagent Translation Prompt Template
|
||||
|
||||
Two parts:
|
||||
1. **`02-prompt.md`** — Shared context (saved to output directory). Contains background, glossary, challenges, and principles. No task-specific instructions.
|
||||
2. **Subagent spawn prompt** — Task instructions passed when spawning each subagent. One subagent per chunk (or per source file if non-chunked).
|
||||
|
||||
The main agent reads `01-analysis.md` (if exists), inlines all relevant context into `02-prompt.md`, then spawns subagents in parallel with task instructions referencing that file.
|
||||
|
||||
Replace `{placeholders}` with actual values. Omit sections marked "if analysis exists" for quick mode.
|
||||
|
||||
---
|
||||
|
||||
## Part 1: `02-prompt.md` (shared context, saved as file)
|
||||
|
||||
```markdown
|
||||
You are a professional translator. Your task is to translate markdown content from {source_lang} to {target_lang}.
|
||||
|
||||
## Target Audience
|
||||
|
||||
{audience description}
|
||||
|
||||
## Content Background
|
||||
|
||||
{Inlined from 01-analysis.md if analysis exists: quick summary, core argument, author background, writing context, tone assessment.}
|
||||
|
||||
## Glossary
|
||||
|
||||
Apply these term translations consistently throughout. First occurrence of each term: include the original in parentheses after the translation.
|
||||
|
||||
{Merged glossary — combine built-in glossary + EXTEND.md glossary + terms extracted in analysis. One per line: English → Translation}
|
||||
|
||||
## Comprehension Challenges
|
||||
|
||||
The following terms or references may confuse target readers. Add translator's notes in parentheses where they appear: `译文(English original,通俗解释)`
|
||||
|
||||
{Inlined from 01-analysis.md comprehension challenges section if analysis exists. Each entry: term → explanation to use as note.}
|
||||
|
||||
## Translation Principles
|
||||
|
||||
- **Accuracy first**: Facts, data, and logic must match the original exactly
|
||||
- **Natural flow**: Use idiomatic {target_lang} word order; break long sentences into shorter ones
|
||||
- **Terminology**: Use glossary translations consistently; annotate with original term in parentheses on first occurrence
|
||||
- **Preserve format**: Keep all markdown formatting (headings, bold, italic, images, links, code blocks)
|
||||
- **Respect original**: Maintain original structure and meaning; do not add, remove, or editorialize
|
||||
- **Translator's notes**: For terms or cultural references listed in Comprehension Challenges above, add a concise explanatory note in parentheses. Only annotate where genuinely needed for the target audience.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Part 2: Subagent spawn prompt (passed as Agent tool prompt)
|
||||
|
||||
### Chunked mode (one subagent per chunk, all spawned in parallel)
|
||||
|
||||
```
|
||||
Read the translation instructions from: {output_dir}/02-prompt.md
|
||||
|
||||
Translate this chunk:
|
||||
1. Read `{output_dir}/chunks/chunk-{NN}.md`
|
||||
2. Translate following the instructions in 02-prompt.md
|
||||
3. Save translation to `{output_dir}/chunks/chunk-{NN}-draft.md`
|
||||
```
|
||||
|
||||
### Non-chunked mode
|
||||
|
||||
```
|
||||
Read the translation instructions from: {output_dir}/02-prompt.md
|
||||
|
||||
Translate the source file and save the result:
|
||||
1. Read `{source_file_path}`
|
||||
2. Save translation to `{output_path}`
|
||||
```
|
||||
|
|
@ -0,0 +1,161 @@
|
|||
{
|
||||
"lockfileVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"remark-frontmatter": "^5.0.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"remark-parse": "^11.0.0",
|
||||
"remark-stringify": "^11.0.0",
|
||||
"unified": "^11.0.5",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="],
|
||||
|
||||
"@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="],
|
||||
|
||||
"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
|
||||
|
||||
"@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
|
||||
|
||||
"bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="],
|
||||
|
||||
"ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
|
||||
|
||||
"character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="],
|
||||
|
||||
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="],
|
||||
|
||||
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
|
||||
|
||||
"devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="],
|
||||
|
||||
"escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
|
||||
|
||||
"extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
|
||||
|
||||
"fault": ["fault@2.0.1", "", { "dependencies": { "format": "^0.2.0" } }, "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ=="],
|
||||
|
||||
"format": ["format@0.2.2", "", {}, "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww=="],
|
||||
|
||||
"is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="],
|
||||
|
||||
"longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="],
|
||||
|
||||
"markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="],
|
||||
|
||||
"mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg=="],
|
||||
|
||||
"mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.3", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q=="],
|
||||
|
||||
"mdast-util-frontmatter": ["mdast-util-frontmatter@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "escape-string-regexp": "^5.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "micromark-extension-frontmatter": "^2.0.0" } }, "sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA=="],
|
||||
|
||||
"mdast-util-gfm": ["mdast-util-gfm@3.1.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ=="],
|
||||
|
||||
"mdast-util-gfm-autolink-literal": ["mdast-util-gfm-autolink-literal@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "ccount": "^2.0.0", "devlop": "^1.0.0", "mdast-util-find-and-replace": "^3.0.0", "micromark-util-character": "^2.0.0" } }, "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ=="],
|
||||
|
||||
"mdast-util-gfm-footnote": ["mdast-util-gfm-footnote@2.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0" } }, "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ=="],
|
||||
|
||||
"mdast-util-gfm-strikethrough": ["mdast-util-gfm-strikethrough@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg=="],
|
||||
|
||||
"mdast-util-gfm-table": ["mdast-util-gfm-table@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "markdown-table": "^3.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg=="],
|
||||
|
||||
"mdast-util-gfm-task-list-item": ["mdast-util-gfm-task-list-item@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ=="],
|
||||
|
||||
"mdast-util-phrasing": ["mdast-util-phrasing@4.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "unist-util-is": "^6.0.0" } }, "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w=="],
|
||||
|
||||
"mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA=="],
|
||||
|
||||
"mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="],
|
||||
|
||||
"micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="],
|
||||
|
||||
"micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="],
|
||||
|
||||
"micromark-extension-frontmatter": ["micromark-extension-frontmatter@2.0.0", "", { "dependencies": { "fault": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg=="],
|
||||
|
||||
"micromark-extension-gfm": ["micromark-extension-gfm@3.0.0", "", { "dependencies": { "micromark-extension-gfm-autolink-literal": "^2.0.0", "micromark-extension-gfm-footnote": "^2.0.0", "micromark-extension-gfm-strikethrough": "^2.0.0", "micromark-extension-gfm-table": "^2.0.0", "micromark-extension-gfm-tagfilter": "^2.0.0", "micromark-extension-gfm-task-list-item": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w=="],
|
||||
|
||||
"micromark-extension-gfm-autolink-literal": ["micromark-extension-gfm-autolink-literal@2.1.0", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw=="],
|
||||
|
||||
"micromark-extension-gfm-footnote": ["micromark-extension-gfm-footnote@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw=="],
|
||||
|
||||
"micromark-extension-gfm-strikethrough": ["micromark-extension-gfm-strikethrough@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw=="],
|
||||
|
||||
"micromark-extension-gfm-table": ["micromark-extension-gfm-table@2.1.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg=="],
|
||||
|
||||
"micromark-extension-gfm-tagfilter": ["micromark-extension-gfm-tagfilter@2.0.0", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg=="],
|
||||
|
||||
"micromark-extension-gfm-task-list-item": ["micromark-extension-gfm-task-list-item@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw=="],
|
||||
|
||||
"micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="],
|
||||
|
||||
"micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="],
|
||||
|
||||
"micromark-factory-space": ["micromark-factory-space@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg=="],
|
||||
|
||||
"micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="],
|
||||
|
||||
"micromark-factory-whitespace": ["micromark-factory-whitespace@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ=="],
|
||||
|
||||
"micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="],
|
||||
|
||||
"micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="],
|
||||
|
||||
"micromark-util-classify-character": ["micromark-util-classify-character@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q=="],
|
||||
|
||||
"micromark-util-combine-extensions": ["micromark-util-combine-extensions@2.0.1", "", { "dependencies": { "micromark-util-chunked": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg=="],
|
||||
|
||||
"micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="],
|
||||
|
||||
"micromark-util-decode-string": ["micromark-util-decode-string@2.0.1", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ=="],
|
||||
|
||||
"micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="],
|
||||
|
||||
"micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="],
|
||||
|
||||
"micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="],
|
||||
|
||||
"micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="],
|
||||
|
||||
"micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="],
|
||||
|
||||
"micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="],
|
||||
|
||||
"micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="],
|
||||
|
||||
"micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"remark-frontmatter": ["remark-frontmatter@5.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-frontmatter": "^2.0.0", "micromark-extension-frontmatter": "^2.0.0", "unified": "^11.0.0" } }, "sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ=="],
|
||||
|
||||
"remark-gfm": ["remark-gfm@4.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg=="],
|
||||
|
||||
"remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="],
|
||||
|
||||
"remark-stringify": ["remark-stringify@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-to-markdown": "^2.0.0", "unified": "^11.0.0" } }, "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw=="],
|
||||
|
||||
"trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="],
|
||||
|
||||
"unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="],
|
||||
|
||||
"unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="],
|
||||
|
||||
"unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="],
|
||||
|
||||
"unist-util-visit": ["unist-util-visit@5.1.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg=="],
|
||||
|
||||
"unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="],
|
||||
|
||||
"vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="],
|
||||
|
||||
"vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="],
|
||||
|
||||
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
import { readFileSync, writeFileSync, mkdirSync } from "fs"
|
||||
import { basename, dirname, join } from "path"
|
||||
import { unified } from "unified"
|
||||
import remarkParse from "remark-parse"
|
||||
import remarkGfm from "remark-gfm"
|
||||
import remarkFrontmatter from "remark-frontmatter"
|
||||
import remarkStringify from "remark-stringify"
|
||||
import type { Root, Content } from "mdast"
|
||||
|
||||
const args = process.argv.slice(2)
|
||||
const file = args.find(a => !a.startsWith("--"))
|
||||
const maxWords = parseInt(args[args.indexOf("--max-words") + 1] || "5000")
|
||||
|
||||
if (!file) {
|
||||
console.error("Usage: chunk.ts <file> [--max-words 5000]")
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const content = readFileSync(file, "utf-8")
|
||||
|
||||
const tree = unified()
|
||||
.use(remarkParse)
|
||||
.use(remarkGfm)
|
||||
.use(remarkFrontmatter, ["yaml"])
|
||||
.parse(content)
|
||||
|
||||
const stringify = unified()
|
||||
.use(remarkStringify, { bullet: "-", emphasis: "*", strong: "*" })
|
||||
.use(remarkGfm)
|
||||
.use(remarkFrontmatter, ["yaml"])
|
||||
|
||||
function nodeToMd(node: Content): string {
|
||||
const root: Root = { type: "root", children: [node] }
|
||||
return stringify.stringify(root).trim()
|
||||
}
|
||||
|
||||
function countWords(text: string): number {
|
||||
const cleaned = text.replace(/[#*`\[\]()>|_~-]/g, " ")
|
||||
const cjk = cleaned.match(/[\u4e00-\u9fff\u3400-\u4dbf\uf900-\ufaff]/g)
|
||||
const latin = cleaned.match(/[a-zA-Z0-9]+/g)
|
||||
return (cjk?.length || 0) + (latin?.length || 0)
|
||||
}
|
||||
|
||||
interface Block {
|
||||
md: string
|
||||
words: number
|
||||
}
|
||||
|
||||
function splitNodeToBlocks(node: Content): Block[] {
|
||||
const md = nodeToMd(node)
|
||||
const words = countWords(md)
|
||||
|
||||
if (words <= maxWords) return [{ md, words }]
|
||||
|
||||
if (node.type === "heading" || node.type === "thematicBreak" || node.type === "html") {
|
||||
return [{ md, words }]
|
||||
}
|
||||
|
||||
if ("children" in node && Array.isArray(node.children)) {
|
||||
const blocks: Block[] = []
|
||||
for (const child of node.children as Content[]) {
|
||||
blocks.push(...splitNodeToBlocks(child))
|
||||
}
|
||||
return blocks
|
||||
}
|
||||
|
||||
const lines = md.split("\n")
|
||||
if (lines.length > 1) {
|
||||
const blocks: Block[] = []
|
||||
let buf: string[] = []
|
||||
let bufWords = 0
|
||||
for (const line of lines) {
|
||||
const lw = countWords(line)
|
||||
if (bufWords + lw > maxWords && buf.length > 0) {
|
||||
blocks.push({ md: buf.join("\n"), words: bufWords })
|
||||
buf = [line]
|
||||
bufWords = lw
|
||||
} else {
|
||||
buf.push(line)
|
||||
bufWords += lw
|
||||
}
|
||||
}
|
||||
if (buf.length > 0) blocks.push({ md: buf.join("\n"), words: bufWords })
|
||||
return blocks
|
||||
}
|
||||
|
||||
return [{ md, words }]
|
||||
}
|
||||
|
||||
let frontmatter = ""
|
||||
const blocks: Block[] = []
|
||||
|
||||
for (const node of tree.children) {
|
||||
if (node.type === "yaml") {
|
||||
frontmatter = `---\n${node.value}\n---`
|
||||
continue
|
||||
}
|
||||
blocks.push(...splitNodeToBlocks(node as Content))
|
||||
}
|
||||
|
||||
const chunks: { blocks: Block[]; words: number }[] = []
|
||||
let cur: Block[] = []
|
||||
let curWords = 0
|
||||
|
||||
for (const b of blocks) {
|
||||
if (curWords + b.words > maxWords && cur.length > 0) {
|
||||
chunks.push({ blocks: cur, words: curWords })
|
||||
cur = [b]
|
||||
curWords = b.words
|
||||
} else {
|
||||
cur.push(b)
|
||||
curWords += b.words
|
||||
}
|
||||
}
|
||||
if (cur.length > 0) chunks.push({ blocks: cur, words: curWords })
|
||||
|
||||
const dir = join(dirname(file), "chunks")
|
||||
mkdirSync(dir, { recursive: true })
|
||||
|
||||
chunks.forEach((chunk, i) => {
|
||||
const num = String(i + 1).padStart(2, "0")
|
||||
const out = join(dir, `chunk-${num}.md`)
|
||||
writeFileSync(out, chunk.blocks.map(b => b.md).join("\n\n"))
|
||||
})
|
||||
|
||||
console.log(JSON.stringify({
|
||||
source: file,
|
||||
chunks: chunks.length,
|
||||
output_dir: dir,
|
||||
frontmatter: !!frontmatter,
|
||||
words_per_chunk: chunks.map(c => c.words)
|
||||
}))
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"remark-frontmatter": "^5.0.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"remark-parse": "^11.0.0",
|
||||
"remark-stringify": "^11.0.0",
|
||||
"unified": "^11.0.5"
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue