Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
213 changes: 213 additions & 0 deletions .claude/skills/collect_knowledge/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
---
name: collect_knowledge
description: Collect and import knowledge from local files or directories into the knowledge tree, translating any non-English content to English first and verifying the translation before anything is merged. Analyzes content to determine domain placement, evaluates whether the knowledge already exists in the tree, decides optimal organization (new file, merge, or skip), and generates frontmatter. Use this skill whenever importing local markdown, documentation, notes, or text files from an external directory into the tree — especially when the source may be in another language, when you need deduplication against existing content, or when a corpus needs organizing by domain.
allowed-tools: Read, Write, Glob, Grep
---

# Collect Knowledge

Import local files and directories into the knowledge tree under `tree/`, in English, without breaking their structure.

The hard part is not reading files — it is that a corpus in another language must come out the other side fully translated, with its headings, code, tables, links and frontmatter intact. That does not happen by intending it. Translation is expensive and every other step in this skill is cheap, so under load the expensive step is the one that silently gets skipped. This skill therefore treats translation as a **gated** step: staged, machine-checked, and blocked from merging until it passes.

## When to use

- You have a local directory of notes or documentation to integrate into the tree
- A corpus is in Chinese, Japanese, Korean, Russian, or any non-English language and needs to land in the tree in English
- You're consolidating multiple local sources and need to avoid duplicates
- You want to assess whether new knowledge is truly new or supplements existing tree content
- You need to audit an already-imported part of the tree for untranslated content

## The tools

Three scripts under `scripts/`, all path-agnostic — they take arguments, hold no embedded paths, and work on any markdown corpus:

| Script | Role |
|---|---|
| `lang_scan.py` | Detect what still needs translating, and where. Plans chunks for large files. |
| `verify_translation.py` | The gate. Residual-language, structure-parity and anchor checks. |
| `mdlang.py` | Shared library — not run directly. |

They resolve their own imports, so run them from any working directory. Python 3.9+, no third-party dependencies.

## Workflow

### 1. Triage the source

```bash
python scripts/lang_scan.py <source-path> --summary
```

This buckets every file by how much non-English text it holds, and names the dominant script. Use it to size the job before starting, then branch per file:

| Bucket | Meaning | What to do |
|---|---|---|
| `CLEAN` | no non-Latin text found | skip to step 5 — nothing to translate |
| `TRACE` | under 5% of lines | translate whole-file; often just a stray line or a proper noun |
| `PARTIAL` | 5–25% of lines | translate whole-file, or chunk if long |
| `HEAVY` | over 25% of lines | chunk if over ~400 lines, then translate |

Add `--show 20` on any single file to see exactly which lines and which category.

### 2. Stage, don't edit in place

Translate into a staging directory, never over the source. Keeping the original untouched is what makes the structure comparison in step 4 possible, and what makes a bad translation recoverable.

Mirror the path each file has *relative to the corpus root* — step 4 pairs originals to translations by that relative path, and files that don't line up get no structure checking at all:

```bash
SRC=<source-path> # e.g. ~/corpus/domain-05
STAGE=/tmp/collect-staging

# For a file at $SRC/01-identity/03-tokens.md, stage it at
# $STAGE/01-identity/03-tokens.md
mkdir -p "$STAGE/01-identity"
```

### 3. Translate

For each file that isn't `CLEAN`.

**Read the translation rules below before starting**, and skim `references/translation-rules.md` for the cases the summary can't settle — mixed-language files, text that should legitimately stay in the source language, and ambiguous inline code.

**Small files (under ~400 lines):** read, translate, write the whole file to staging.

**Large files:** ask for a chunk plan first —

```bash
python scripts/lang_scan.py <file> --plan
```

It prints one line per chunk:

```
chunk 0 lines 1-56 ( 56) (frontmatter -- translate title/description/summary, keep slugs and dates)
chunk 1 lines 57-498 (442)
chunk 2 lines 499-999 (501) 2.6 Grype in continuous integration
chunk 3 lines 1000-1565 (566) 4.1 CVSS v3.1 base scoring
```

Ranges are contiguous and cover the file from line 1. Translate them **in order**, appending each result to the staged file (`>>`), so the reassembled document has every line accounted for.

Chunk 0 is always the frontmatter when the file has any. It gets its own chunk precisely because it sits above the first heading — a plan built from heading boundaries alone would skip it, and untranslated `title`/`description`/`summary` is the costliest thing to miss. Body boundaries land on `##`/`#` headings and never inside a code fence, so each piece is self-contained. A chunk may run somewhat over the target size when a single section is long; that's expected.

Chunking is not an optimization, it's a correctness measure. Asked to "translate this 2,700-line document," a model starts condensing somewhere in the middle; asked to translate a bounded section, it translates it. Carry forward the terminology you chose in earlier chunks so a term doesn't change spelling halfway through the file.

Whatever you do, **translate — do not summarize, improve, or reorganize.** Every heading, row, bullet and code block in the source must appear in the output. Step 4 checks this, and a file that lost content will be sent back.

### 4. Verify — the gate

```bash
python scripts/verify_translation.py \
--original-dir <source-path> \
--translated-dir /tmp/collect-staging
```

Exit code 0 means clean; 1 means at least one file failed. Failures are specific — line numbers for residual text, counts for dropped structure, named targets for broken anchors. **Fix and re-run until it exits 0.** Nothing moves into `tree/` before that.

Warnings (`!`) don't block, but read them. The two that matter most:

- **Line-count delta beyond ±15%** (the default `line_delta_tolerance`). A large *negative* delta usually means a section was summarized away rather than translated — check the chunk seams first.
- **Non-Latin text inside code.** Usually a comment that was missed. Occasionally a legitimate string literal, which is why it warns rather than blocks.

### 5. Analyze, deduplicate, and place

Only once the content is in English:

- Determine the domain (`networking`, `infrastructure`, `security`, `ai`, etc.) and place under `tree/<domain>/`, using subdirectories where a topic warrants it
- Query the tree for near-duplicates using [[query_knowledge]] — semantic similarity plus metadata overlap
- Decide per file: **new** → write it; **duplicate** → skip and note it; **supplement** → merge or cross-reference

This ordering is deliberate. Similarity search against an English tree using a source-language query returns noise, so deduplication before translation produces a bad answer.

### 6. Move files into tree

Move staged files into the knowledge tree `tree/` folder. Use the folder or subfolders best suited and create the folders needed if they do not exist.

---

## Translation rules

The single question that governs everything: **will a reader or an embedding index see this text?** If yes it must be English. If it's a machine identifier — a path, a URL, a variable name — it must be left exactly as it is.

### Always translate

| What | Why it matters |
|---|---|
| Body prose, headings, list items, table cells | The obvious content |
| Frontmatter `title`, `description`, `summary`, `audience`, `intent_queries` | **These feed the embedding index.** Leaving them in the source language poisons search across the whole tree — the highest-cost mistake available here |
| Comments inside code blocks | They're explanation, not code |
| Link text and wikilink aliases — the part *after* the `\|` | Reader-visible |
| Blockquotes, callouts, admonitions | Reader-visible |

### Never translate

| What | What breaks if you do |
|---|---|
| URLs, and link targets inside `](...)` | Dead links |
| Wikilink targets — the part *before* the `\|` in `[[target\|alias]]` | The graph loses the edge |
| Code: identifiers, function names, string literals, CLI flags | The code stops working |
| Code fence info strings (` ```yaml `) | Syntax highlighting breaks; the verifier flags it |
| Frontmatter *keys*, and slug-valued fields (`tags`, `category`, `tier`, dates) | Schema drift |

### Regenerate, don't translate

**Heading anchors.** A table of contents entry like `[概述](#1-概述)` has two halves that behave differently: the link text is prose and gets translated, while the `#1-概述` target is *derived* from the heading. Translating it as text produces `#1-overview` only by luck; the reliable move is to rebuild every anchor from the final English heading using GitHub slug rules — lowercase, punctuation dropped, spaces to hyphens. `verify_translation.py` checks that every anchor resolves to a heading that actually exists.

**Relative paths to other documents.** If a source file links to `../other-domain/doc.md` and that file lands somewhere else in `tree/`, the link needs remapping to the new location, not translating. Cross-corpus links that have no counterpart in the tree should be flagged in the report rather than silently left broken.

For edge cases — mixed-language files, quoted source-language material that should stay, ambiguous inline code — see `references/translation-rules.md`.

---

## Frontmatter convention

Keep every key the source already has and keep its slug-valued fields untouched; translate the human-readable values; then add the two provenance fields below **if they aren't already present**:

```yaml
---
title: Human-readable title, in English
description: Brief summary, in English — this is indexed, so it must be English
tags: [domain, topic] # slugs, left as-is
created: '2026-08-29'
last_updated: 2026-08-29
source_path: /original/path/to/file.md
original_language: Chinese # or English when no translation was needed
---
```

`source_path` and `original_language` are what make a bad translation recoverable later — without them there's no way to find the original and redo it. The verifier warns when they're missing.

---

## Auditing content that's already in the tree

The scanner works on the tree itself, which is how you find content that was imported before this gate existed:

```bash
python scripts/lang_scan.py tree/ --summary
python scripts/lang_scan.py tree/ --bucket HEAVY --show 5 # worst offenders
python scripts/lang_scan.py tree/ --gate # exit 1 if any remain
```

The `--gate` form is suitable for a pre-commit hook or CI step, so untranslated content can't re-enter.

---

## Next steps

After collecting:

1. **Review the report** — placements, translations, duplicates, merge candidates
2. **Spot-check a translated file** against its original for terminology accuracy — the verifier checks structure and completeness, not whether "承载网" was rendered well
3. **Handle merge candidates** — integrate and update cross-references
4. **Verify organization** — confirm domain placement; add subdirectories where a topic has grown
5. **Index** — run `make ingest && make reindex`
6. **Query** — use [[query_knowledge]] to retrieve the new material

## Related

- Index collected knowledge: [[store_knowledge]]
- Search the knowledge tree: [[query_knowledge]]
- Run analysis on collected material: [[run_research_report]]
- Config (embedding model, chunk sizes): `kb/config.py`
182 changes: 182 additions & 0 deletions .claude/skills/collect_knowledge/references/translation-rules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
# Translation rules — edge cases

Read this when a file doesn't fit the straightforward cases in SKILL.md. The
governing question is always the same: **will a reader or the embedding index
see this text?** English if yes; untouched if it's a machine identifier.

## Mixed-language files

Common in technical corpora: English headings with source-language body, or
English body with source-language comments in code. Don't decide per file —
`lang_scan.py` reports per line and per category, so work from its output
rather than from an impression of the file.

A file scanning as `TRACE` (under 5% of lines) is usually one of:

- a stray untranslated bullet left over from an earlier partial pass
- a proper noun that has no English form — legitimate, see below
- a quoted error message or log line — see below

All three need a decision, not a blanket skip.

## Text that should stay in the source language

These are real, and forcing them to English makes the document worse:

- **Quoted terminal output, log lines, and error messages** produced by a
localized tool. The reader needs to match what their screen shows. Translate
the surrounding explanation; leave the quoted string, and add a bracketed
English gloss after it if the meaning matters.
- **Proper nouns with no established English form** — a company, a product, a
domestic standard. Use the official English name if one exists; otherwise
transliterate and put the original in parentheses on first use.
- **Deliberate examples of source-language input** — a document about CJK text
handling needs its CJK sample data intact.

Put these inside inline code or a fenced block where the format allows. That
moves them into a `WARN` category rather than `BLOCK`, so the gate surfaces
them for review instead of failing the file. If a genuine case can't be marked
that way, note it in the collection report so a human can confirm.

## Inline code containing source-language text

Ambiguous, and worth slowing down for. Two cases:

```
`kubectl get pods` <- a command. Never translate.
`安全组` <- a term someone wrapped in backticks for emphasis.
Translate it, and consider whether it should be
bold rather than code.
```

Rule of thumb: if it would be typed into a terminal or appear in source code,
it's code. If it's a concept being named, it's prose that was formatted wrong.

## Code blocks

Translate comments. Leave everything else:

```yaml
# 传统方式:长期 Token 存储在 Secret 中 <- translate this line
apiVersion: v1
kind: Secret
metadata:
name: default-token-xxx <- never touch
```

Two traps:

- **String literals that are user-facing.** A `message: "操作失败"` in an example
config is a judgement call — translating it makes the example clearer,
leaving it keeps the example faithful. Prefer translating, since these are
illustrative rather than executable, and note it if the example is copied
from a real system.
- **The info string.** ` ```yaml ` is a language identifier. `verify_translation.py`
compares the full sequence of info strings before and after and fails on any
change, because a translated info string breaks highlighting everywhere it
appears.

## Heading anchors and tables of contents

The most common silent breakage. A TOC entry has two halves that behave
differently:

```
[概述](#1-概述)
^^^^ ^^^^^^^^
prose derived identifier
```

Translate the first, **regenerate** the second from the final English heading
using GitHub slug rules:

1. Strip markdown formatting (`**bold**`, `` `code` ``, links → their text)
2. Lowercase
3. Remove everything that isn't a word character, space, or hyphen
4. Spaces → hyphens
5. Duplicate slugs get `-1`, `-2`, … appended in document order

`mdlang.slugify()` implements exactly this, and `verify_translation.py` fails
the file if any anchor doesn't resolve to a heading that exists. Never
hand-guess an anchor; derive it.

## Wikilinks

```
[[domain-05-security/README.md|返回目录]]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^
target — a path alias — reader-visible
```

Translate the alias (`返回目录` → `Back to index`). The target is a path:

- If the referenced document is also being collected, **remap** the target to
its new location under `tree/`
- If it isn't, the link will dangle. Flag it in the report rather than leaving
a broken edge or deleting the link silently — a dangling wikilink is a valid
marker of something worth collecting later, but only if someone knows it's there.

A bare `[[Kubernetes|Kubernetes]]` with no path refers to a tree note by title;
leave those alone unless the tree uses a different title.

## Frontmatter

Any human-readable value must be English, because `title`, `description` and
`summary` are what get embedded — untranslated values there degrade retrieval
for every future query, not just for the file they're in.

| Field kind | Examples | Action |
|---|---|---|
| Human-readable | `title`, `description`, `summary`, `abstract`, `audience`, `intent_queries` | Translate |
| Slug / enum | `tags`, `category`, `tier`, `difficulty`, `reading_level` | Leave |
| Date / version | `created`, `last_updated`, `k8s_versions` | Leave |
| Provenance | `source_path`, `original_language`, `authors` | Leave; add if missing |

Unknown keys default to *must be translated*. A corpus you haven't seen before
is far more likely to hide untranslated prose behind an unfamiliar key than to
hold deliberate source-language text there. To exempt a key, add it to
`preserve_frontmatter` in a config file and pass `--config`.

`trigger_keywords` and `keywords` are a mixed case: usually English technical
terms already, but any source-language entries in them should be translated,
since they exist to be matched against English queries.

## Tuning the checks for a different corpus

Everything is config-driven. Write a JSON file with only the keys you're
changing and pass `--config` to either script:

```json
{
"preserve_frontmatter": ["tags", "category", "author_native_name"],
"warn_categories": ["inline_code", "code", "code_comment"],
"max_chunk_lines": 250,
"line_delta_tolerance": 0.25
}
```

Useful adjustments:

- **`preserve_frontmatter`** — add keys that legitimately hold source-language
values, such as a native-script author name.
- **Move `code_comment` from blocking to warning** for a corpus where code
comments are quoted from external sources and shouldn't be rewritten.
- **Lower `max_chunk_lines`** if translations are drifting or losing content;
smaller chunks are more reliable and cost more passes.
- **Raise `line_delta_tolerance`** for language pairs with large length
differences. Chinese → English typically *grows* line count slightly when
prose reflows; a large negative delta is still a red flag in any pair.

## When the gate keeps failing

Re-run the failing file through `lang_scan.py --show 30` to see exactly which
lines remain. In order of likelihood:

1. **Frontmatter was skipped.** The most common miss by a wide margin — the
body reads as English and the file looks done.
2. **The tail of a long file.** Attention drifted near the end. Re-translate
the last chunks specifically rather than the whole file.
3. **Code comments.** Easy to read past when scanning a fenced block.
4. **Anchors.** Headings became English and the TOC didn't follow.
5. **Structure counts off by a few.** Usually a table row or list item dropped
at a chunk boundary — check the seams between chunks first.
Loading