Claude Skill

encyclopedia-writing

Encyclopedic neutral-reference writing craft — NPOV (attribute facts not opinions, due weight, neutral faction labels, verdict restraint), summary style and Coatrack avoidance, wikilink conventions (link density, first-mention, slug alias, abbreviation glossing). Use when writing

LLM Mart · 0 points · 7 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download alfadur7-llm-wiki-newsroom-.claude_skills_encyclopedia-writing-ff4f181.zip · 16 KB
Part of alfadur7/llm-wiki-newsroom — 6 skills

Install

skills CLI npx skills add https://github.com/alfadur7/llm-wiki-newsroom/tree/main/.claude/skills/encyclopedia-writing
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install alfadur7-llm-wiki-newsroom@llmmart
Git git clone https://github.com/alfadur7/llm-wiki-newsroom.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole alfadur7/llm-wiki-newsroom collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

encyclopedia-writing

Neutral-reference writing craft drawn from the encyclopedic editing tradition (the Wikipedia policy family). It keeps this wiki's overview, issue, and hub pages a connected reference that is grounded in facts and sources and advocates no particular viewpoint. criteria.json is the SoT for each criterion's definition, comparator, and source; content-type thresholds and algorithm params are injected by .claude/layers/_manifest.json (the skill is content-type-agnostic). Examples are illustrative of the target English prose; the Korean-rendering rules below apply only when WIKI_LANG=ko and are inert on the default English corpus.

Wikilink connectivity (enc.link-density · enc.first-mention · enc.lead-body)

A reference's value comes from the link density that lets a reader move to adjacent concepts; at the same time, re-linking the same target within one section is visual noise.

  • enc.link-density — the EDITOR region (excluding AUTO blocks) holds at least the threshold number of [[wikilink]]s. "Articles on highly technical subjects might demand a higher density of links," so technical/finance domains are allowed slightly above the ceiling.
  • enc.first-mention — "link a term at most once per major section, at first occurrence." A second link of the same stem within a section becomes plain text. A section that structurally cites the same target twice (e.g. adjacent-field boundary prose) is exempted via the manifest's exempt_sections.
  • enc.lead-body — "the lead of an article usually has a greater density of links than later parts of the article"; this is expected, since the reader's first encounter is where navigation options should be densest. Because which span counts as the lead is content-type-bound, the orchestrator measures it.

All three are deterministic; checks.py (link-density·first-mention) or the orchestrator (lead-body) computes PASS/FAIL with manifest-injected thresholds/params.

Neutrality & notation (enc.verdict-restraint · enc.slug-alias · enc.abbr-gloss · enc.encyclopedic-tone)

A reference advocates no viewpoint (NPOV) and follows readable notation conventions (MoS). Deterministic; thresholds injected by manifest.

  • enc.verdict-restraint — "avoid stating opinions as facts" and "describe disputes, but do not engage in them." Write evaluative sentences in three strengths — observation ("the metric points the opposite way") and conditional recommendation ("appears to be"·"may be") are fine, but a verdict ("is reasonable"·"has the most explanatory power"·"is consistent with") over the threshold must be softened with hedging/attribution. Repeated verdicts under hedged wording stay neutral in form but biased in substance. (ko-localization renderings: observation ~ 지표가 정반대를 가리킨다; conditional ~로 보인다·~일 가능성이 있다; verdict ~이 합리적이다·가장 설명력이 높다·~에 부합한다.)
  • enc.slug-alias — do not expose a long raw kebab-case identifier; show a human-readable alias (MoS overlinking/readability). This wiki uses English aliases by default; native-script aliases only under WIKI_LANG=ko.
  • enc.abbr-gloss — "an acronym should be written out in full for the first time, followed by the abbreviation in parentheses" (commonly used abbreviations such as global brands are exempt). Applies to abbreviations appearing in body text.
  • enc.encyclopedic-tone — keep the encyclopedic register; do not self-reference the editorial artifact or editorial decisions (WP:SELFREF — an article "shouldn't refer to [the work] in a non-neutral fashion"). Editorializing such as "this document treats … as"·"here we keep only …" is deleted or absorbed into a factual statement. (ko-localization renderings: 본 문서는 ~로 둔다·여기서는 ~만 유지한다.)

Issue neutrality & balance (enc.npov-asf · enc.due-weight · enc.label-neutral · enc.back-reference)

A piece covering a dispute advocates no side and fairly juxtaposes both, grounded in facts and sources (WP:NPOV — ASF and DUE run through every section).

  • enc.npov-asf — attribute every value-judgment/interpretation/claim with a named subject + source + figure/quote (≥2 of the 3); "avoid stating opinions as facts," and use no weasel words ("some say"·"many people"·"as is known"; ko renderings 혹자는·많은 사람이·알려진 바로는). judge=M. e.g. ✅ "The IPCC warned of coastal-city risk in its 2021 report" (named source + date) / ❌ "Many experts believe coastal cities are at risk" (anonymous plurality — weasel)

  • enc.due-weight — coverage is proportionate to prominence in reliable sources, not skewed to one side (WP:DUE); a minority view does not get equal space and is explicitly framed as a minority. judge=M. e.g. ✅ "The Earth is spherical and the scientific consensus is overwhelming; a minority dissents but without credible grounds" (proportionate weight + minority noted) / ❌ "The Earth is round. But some believe it is flat." (false balance)

  • enc.label-neutral — both faction labels (or the aggregate's tension-axis titles) must be drawn from an equivalent vocabulary set. If only one side gets a value-laden word ("myth"·"empirical proof"·"skepticism"·"innovation," etc.), that side is privileged or demoted — unify both on a structural/standpoint basis ("replacement vs augmentation"·"industry-led vs regulation-led"). Deterministic check for one-sided skew = 0. ko-localization rendering rule (WIKI_LANG=ko only) — even when the English original is neutral, a literal Korean rendering can introduce connotation, so when localizing a title to Korean, check the following English→Korean mapping (applies to labels, titles, and body alike):

    English (neutral) Literal Korean (connotation introduced) Recommended Korean (equivalent)
    dual strategy 이중성 (hypocrisy) 이중 트랙·양 트랙 병행
    paradox 역설 (wrong) 반전·교차 구도
    tension 긴장 (instability) 대립·교차 압력
    trade-off 절충 (concession) 상충 균형·양립 조건
    hype 과장 (value judgment) 기대 담론·전망 강조
    myth 신화 (falsehood) 담론·통설·전망
  • enc.back-reference — a narrow issue piece keeps at least one entry-point link up to its parent/landscape overview (an upward link so the reader can move issue → field landscape; WP Summary style).

Aggregation linking (enc.summary-style · enc.coatrack)

Encyclopedic conventions for a higher roll-up that covers lower detail. Deterministic; thresholds injected by manifest.

  • enc.summary-style — "sections of long articles should be spun off into their own articles, leaving summaries in their place"; each subsection carries an entry-point link to its detail page (the {{Main}} hatnote). The roll-up summarizes and provides a drill-down path, not a copy of the detail.
  • enc.coatrack — keep the roll-up's nominal subject from being obscured; a coatrack article "gets away from its nominal subject, and instead gives more attention to ... tangential subjects." Block references that drift to another axis (WP Coatrack·Scope).

Navigational anchor (enc.nav-anchor-density · enc.connection-grouping)

A navigational-anchor page does not carry deep exposition; it serves as an entry point to adjacent pages. Deterministic; thresholds injected by manifest.

  • enc.nav-anchor-density — when an anchor page's body prose exceeds the advisory ceiling, spin the deep-dive off into a sub-page (Summary style spinoff). A central anchor (a heavily-cited core page) is legitimately content-rich and exempt.
  • enc.connection-grouping — when a connection list's flat links grow numerous, group them into sub-categories (### Category; ko ### 카테고리) (MoS Layout). A grouped list passes regardless of count.

Sources

Each URL points to the relevant page as of the last verification.

Files (llm-wiki-newsroom)
  • checks.py 23.2 KB
    """encyclopedia-writing craft skill — deterministic checks.
    
    The "full modularization" pattern that combines the probabilistic part
    (SKILL.md craft prose) and the deterministic part (this module) in a single skill
    folder. It reads criteria.json as the single SoT and provides craft-agnostic
    measurement algorithms. The threshold VALUE is injected from the manifest by the
    caller (the content-type orchestrator = tools/lint.py) — the skill does not know
    the content type.
    
    Owns the enc.* criteria bundles across four content types, each injected per
    _manifest.json bundles: wikilink craft (enc.link-density · enc.first-mention ·
    enc.slug-alias · enc.abbr-gloss), NPOV (enc.verdict-restraint), the
    contradiction-theme NPOV bundle (W1·N4·N5·N6·N7·L1·X2), the
    contradiction-aggregate (N5·N7·D2·D3·F1) and overview-aggregate (D2·F1)
    bundles, and the L2-2 hub body/connection grouping.
    """
    
    from __future__ import annotations
    
    import re
    
    
    # craft structural regexes — owned by the skill (content-type-agnostic).
    WIKILINK_RE = re.compile(r"\[\[([^\]|]+)(?:\|[^\]]*)?\]\]")
    AUTO_BLOCK_RE = re.compile(
        r"<!--\s*AUTO:\w+\s*BEGIN\s*-->.*?<!--\s*AUTO:\w+\s*END\s*-->",
        re.DOTALL,
    )
    
    
    # ── encyclopedic-notation craft (verdict restraint·slug alias·abbr gloss) — ported verbatim from overview.py ──
    # External craft sources: NPOV (verdict restraint)·MoS (slug alias·abbreviation
    # first-mention gloss). All act directly on the EDITOR region text
    # (content-type-agnostic·advisory).
    
    # B1 — verdict sentences. NPOV: an editor does not assert their own conclusion.
    # (dormant: matches Korean verb endings such as '합리적이다'/'부합한다'/'입증됐다';
    #  it will NOT fire on English prose. An English equivalent would need to detect
    #  verdict phrases like "is reasonable", "is consistent with", "has the most
    #  explanatory power". See FLAG in summary.)
    _VERDICT_RE = re.compile(
        r"(?:이|가)\s*(?:합리적이다|가장\s*설명력이\s*높다|부합한다|"
        r"확정됐다|실증됐다|확인됐다|정당화된다|입증됐다)"
        r"|에\s*부합한다"
    )
    
    
    def find_verdict_hits(content: str) -> list:
        """enc.verdict-restraint — list of verdict-phrase excerpts (each ≤60 chars). NPOV restraint."""
        hits = []
        for m in _VERDICT_RE.finditer(content):
            start = max(0, m.start() - 30)
            end = min(len(content), m.end() + 10)
            hits.append(re.sub(r"\s+", " ", content[start:end]).strip())
        return hits
    
    
    def find_unaliased_slugs(content: str, *, min_len: int = 10) -> list:
        """enc.slug-alias — list of stems where a kebab-case slug of ≥min_len chars is
        exposed as a bare `[[slug]]` (no alias). MoS: avoid raw slug exposure, prefer a
        human-readable alias. Threshold manifest-injected."""
        violations: list = []
        for m in re.finditer(r"\[\[([^\]]+)\]\]", content):
            raw = m.group(1).strip()
            if "|" in raw:
                continue
            stem = raw.split("/")[-1]
            if len(stem) >= min_len and "-" in stem and re.fullmatch(r"[a-z0-9\-]+", stem):
                violations.append(stem)
        return violations
    
    
    # L3 — first-mention parenthetical gloss for English abbreviations. MoS: gloss an
    # acronym on its first appearance. (Language-agnostic: uppercase acronyms occur in
    # English prose too.)
    _ABBR_RE = re.compile(r"(?<![A-Za-z])([A-Z][A-Z0-9]{1,4})(?![A-Za-z])")
    _ABBR_ALLOWLIST = {
        "AI", "API", "GPU", "CPU", "RAM", "SSD", "HDD", "LLM", "ML", "DC",
        "IT", "AX", "DX", "CX", "ROI", "KPI", "RTO", "RPO", "DR", "BCP",
        "VPN", "SSL", "TLS", "URL", "HTTP", "HTTPS", "JSON", "YAML", "XML",
        "CSV", "PDF", "HTML", "CSS",
        "ESG", "IPO", "CEO", "CTO", "CIO", "CFO", "COO",
        "RAG", "NPU", "DPU",
        "OS", "OT", "EU", "UK", "US", "KR", "JP", "CN", "UAE",
        "HBM", "DDR", "TSMC",
        "USD", "EUR", "JPY", "KRW", "CBDC", "USDC", "USDT",
        "SDK", "IDE", "CLI", "GUI", "UI", "UX", "IAM",
        "MVP", "POC", "QA",
        "APT", "MFA", "ZTNA", "SASE", "SIEM", "SOC", "SOAR",
        "EDR", "NDR", "XDR", "EPP", "CNAPP", "CSPM", "CWPP", "CIEM",
        "PPA", "SMR",
    }
    
    
    def find_abbr_violations(content: str) -> list:
        """enc.abbr-gloss — list of English abbreviations whose first appearance lacks a
        parenthetical gloss (allowlist excluded)."""
        # Strip YAML frontmatter first — English tokens in the tags:/sources: arrays
        # (NLP·OCR·RPA, etc.) are metadata, not body abbreviations (in the body they
        # appear as [[wikilink]]s). Exclude the frontmatter from glossing (same pattern
        # as _s6_long_sentences, content-type-agnostic).
        if content.startswith("---"):
            fm_end = content.find("\n---", 4)
            if fm_end > 0:
                content = content[fm_end + 4:]
        clean = re.sub(r"\[\[[^\]]+\]\]", " ", content)
        clean = re.sub(r"```.*?```", "", clean, flags=re.DOTALL)
        clean = re.sub(r"`[^`]*`", "", clean)
        seen: dict = {}
        for m in _ABBR_RE.finditer(clean):
            abbr = m.group(1)
            if abbr in _ABBR_ALLOWLIST:
                continue
            if abbr not in seen:
                after = clean[m.end():m.end() + 2]
                if after.lstrip().startswith("("):
                    seen[abbr] = 0
                else:
                    seen[abbr] = 1
        return [a for a, v in seen.items() if v > 0]
    
    
    def count_wikilinks_in_editor_region(content: str, *, strip_auto: bool = True) -> int:
        """enc.link-density algorithm — count of [[wikilink]] in the EDITOR region.
    
        Craft logic that unifies a measurement which previously lived in two places
        (the layers Rubric prose and an overview.py constant). strip_auto is a
        content-type param (L2-3=True excludes AUTO, L2-4=False raw) — manifest-injected.
        """
        editor = AUTO_BLOCK_RE.sub("", content) if strip_auto else content
        return len(WIKILINK_RE.findall(editor))
    
    
    def _split_h2_sections(editor: str) -> dict[str, str]:
        """Split the EDITOR into sections by `## ` heading (text before the first
        heading is `_preamble`).
    
        1:1 identical to the section-split logic in overview.py `_link_metrics` — the
        basis for the equivalence guarantee.
        """
        sections: dict[str, str] = {}
        current = "_preamble"
        buffer: list[str] = []
        for line in editor.splitlines():
            m = re.match(r"^##\s+(.+?)\s*$", line)
            if m and not line.startswith("###"):
                if buffer:
                    sections[current] = "\n".join(buffer)
                current = f"## {m.group(1).strip()}"
                buffer = []
            else:
                buffer.append(line)
        if buffer:
            sections[current] = "\n".join(buffer)
        return sections
    
    
    def count_duplicate_links(
        content: str,
        *,
        exempt_sections=(),
        strip_auto: bool = True,
        unit: str = "h2",
        include_preamble: bool = True,
        exclude_patterns=(),
    ):
        """enc.first-mention algorithm — same-stem wikilink duplicates within a unit
        (n>1 → n-1 accumulated).
    
        Wikipedia MoS "first-mention principle / overlinking avoidance" craft.
        Content-type variants are absorbed via manifest params — exempt_sections·
        strip_auto·unit (h2|h3 duplication unit)·include_preamble (whether to include
        the lead before the first heading)·exclude_patterns (regex strings that strip
        structural repetition such as drill-down links). Returns:
        (dup_total, duplicates[(heading::stem, n)]). The duplicates list is filled only
        for unit=h2 (used for the report's top-dup line).
        """
        editor = AUTO_BLOCK_RE.sub("", content) if strip_auto else content
        exempt = set(exempt_sections)
        compiled = [re.compile(p, re.MULTILINE) if isinstance(p, str) else p for p in exclude_patterns]
        duplicates: list[tuple[str, int]] = []
        dup_total = 0
        for heading, body in _split_h2_sections(editor).items():
            if heading in exempt:
                continue
            if not include_preamble and heading == "_preamble":
                continue
            cleaned = body
            for pat in compiled:
                cleaned = pat.sub("", cleaned)
            parts = re.split(r"^###\s", cleaned, flags=re.MULTILINE) if unit == "h3" else [cleaned]
            for part in parts:
                counts: dict[str, int] = {}
                for target in WIKILINK_RE.findall(part):
                    stem = target.strip().split("/")[-1]
                    counts[stem] = counts.get(stem, 0) + 1
                for stem, n in counts.items():
                    if n > 1:
                        if unit == "h2":
                            duplicates.append((f"{heading}::{stem}", n))
                        dup_total += n - 1
        return dup_total, duplicates
    
    
    # ── contradiction theme enc.* (N4·N5·N6·N7 NPOV·W1 links·L1 alias·X2 back-ref) ──
    # Ported verbatim from contradiction.py `_rubric_metrics`. Shared sections (conflict·
    # verdict·derived)·source_slugs·cluster_slugs are orchestrator-injected. WIKILINK_RE reused.
    
    # (dormant: every pattern below keys on Korean verdict verb endings; none will fire
    #  on English prose. An English equivalent would need verdict phrases like
    #  "is reasonable", "has the most explanatory power", "is consistent with", "leans
    #  toward", "is appropriate", "clearly", "the only interpretation/answer". See FLAG.)
    VERDICT_FAIL_PATTERNS = [
        re.compile(r"(이|가)\s*합리적이다"),
        re.compile(r"가장\s*설명력이\s*높다"),
        re.compile(r"에\s*부합한다"),
        re.compile(r"에\s*기운\s*상태"),
        re.compile(r"(이|가)\s*적절하다"),
        re.compile(r"명백히\s"),
        re.compile(r"(이|가)\s*[^.?!\n]{0,30}유일한\s*(해석|답|해결책)"),
    ]
    # English-native first: %|percent + magnitude words (billion|million|trillion) +
    # common counters (hours|people|users|cases|points|x|×) fire on the English corpus;
    # the Korean units (조|억|만|퍼센트|달러|시간|배|명…) fire under WIKI_LANG=ko. Single
    # SoT — `_lint/contradiction.py` consumes this exact regex (was a drifted second copy).
    NUMBER_TOKEN_RE = re.compile(
        r"\d+(?:,\d{3})*(?:\.\d+)?\s*"
        r"(?:%|percent|"
        r"billion|million|trillion|"
        r"hours?|hrs?|people|users|cases?|points?|x|×|"
        r"조|억|만|천억|백만|퍼센트|"
        r"달러|위안|유로|파운드|엔|"
        r"시간|배|명|건|개|대|기|점|"
        r"GW|MW|TB|PB|GB|"
        r"ppm|kg|km)"
        r"(?:\s*원)?"
    )
    N6_MIN_TOKEN_LEN = 3
    N6_NUMBER_REUSE_MAX = 2
    # The English keyword "Timeline" is live on English derived-tensions prose; the six
    # Korean transition keywords (before/after/generation/reversal/turning point/angle)
    # fire under WIKI_LANG=ko.
    N6_DERIVED_TRANSITION_KEYWORDS = ["이전", "이후", "세대", "반전", "전환점", "각도", "Timeline"]
    # A/B/C position label (N7). The letter is captured in `p` when it follows
    # `Position ` (English `**Position A**`, per contradiction.md) or in `b` when it
    # leads (`**C — Mediation**`, Korean `**A 입장**`). The strict alternation mirrors
    # contradiction.py's DIALECTIC_LABEL_RE so ordinary bold phrases starting with a
    # bare A/B/C (`**A key caveat**`) are not matched.
    DIALECTIC_LABEL_RE = re.compile(
        r"\*\*(?:Position\s+(?P<p>[ABC])\b|(?P<b>[ABC])\s*(?:[—-]|입장|중재|제3관점))[^*]*\*\*"
    )
    # Value-laden words used in a faction subtitle (N7 skew). English-native set first;
    # the Korean set fires under WIKI_LANG=ko. Matched case-insensitively in _value_hits.
    LABEL_VALUE_WORDS = {
        "myth", "empirical", "skepticism", "science", "heresy", "mainstream",
        "orthodoxy", "orthodox", "innovation", "optimism", "pessimism", "extreme",
        "fanaticism", "blind faith", "illusion", "bubble", "camp",
        "신화", "실증", "회의론", "과학", "이단", "주류", "정설",
        "혁신", "낙관", "비관", "극단", "광신", "맹신", "환상", "거품",
        "진영", "정통",
    }
    RAW_KEBAB_SLUG_RE = re.compile(r"\[\[([a-z][a-z0-9]*(?:-[a-z0-9]+){2,})(?:#[^|\]]+)?\]\]")
    L1_MIN_SLUG_LEN = 10
    
    
    def _slug_only(target: str) -> str:
        """Strip path and #anchor from a target. Verbatim from contradiction.py."""
        bare = target.strip().split("/")[-1]
        return bare.split("#", 1)[0]
    
    
    def _split_sentences(text: str) -> list:
        """Coarse sentence split of body text (N5). Verbatim from contradiction.py.
        (Splits on .!?。 + newlines — language-agnostic, but feeds the dormant Korean
        VERDICT_FAIL_PATTERNS.)"""
        parts = re.split(r"(?<=[.!?。])\s+|\n+", text)
        return [p.strip() for p in parts if p.strip()]
    
    
    def evaluate_contradiction_npov(
        body: str,
        *,
        conflict_section: str,
        verdict_section: str,
        derived_section: str,
        source_slugs: set,
        cluster_slugs: set,
    ) -> dict:
        """Measure contradiction theme enc.* (W1·N4·N5·N6·N7·L1·X2). Shared sections are
        orchestrator-injected. The returned dict is byte-identical to the corresponding
        _rubric_metrics keys."""
        # W1
        total_links = len(WIKILINK_RE.findall(body))
    
        # N4 — max re-appearance of a source-slug wikilink
        slug_counts: dict = {}
        for target in WIKILINK_RE.findall(body):
            stem = _slug_only(target)
            if source_slugs and stem not in source_slugs:
                continue
            slug_counts[stem] = slug_counts.get(stem, 0) + 1
        top_reused = sorted(slug_counts.items(), key=lambda x: -x[1])[:3]
        reuse_max = top_reused[0][1] if top_reused else 0
    
        # N5 — verdict sentences in `## Interpretive Direction` (Korean verdict matcher; ko-mode)
        verdict_fails = 0
        for sentence in _split_sentences(verdict_section):
            if any(p.search(sentence) for p in VERDICT_FAIL_PATTERNS):
                verdict_fails += 1
    
        # X2 — landscape back-reference
        landscape_refs = 0
        for target in WIKILINK_RE.findall(body):
            stem = _slug_only(target)
            if stem in cluster_slugs:
                landscape_refs += 1
    
        # L1 — raw kebab-case source slug (≥10 chars, no alias)
        raw_slug_matches = [m for m in RAW_KEBAB_SLUG_RE.findall(body) if len(m) >= L1_MIN_SLUG_LEN]
        raw_slugs = len(raw_slug_matches)
    
        # N6 — number-token re-appearance
        body_no_links = WIKILINK_RE.sub("", body)
        num_counts: dict = {}
        for tok in NUMBER_TOKEN_RE.findall(body_no_links):
            norm = tok.strip()
            if len(norm) < N6_MIN_TOKEN_LEN:
                continue
            norm = re.sub(r"\s+", " ", norm).strip()
            num_counts[norm] = num_counts.get(norm, 0) + 1
        top_num_reused = sorted(num_counts.items(), key=lambda x: -x[1])[:3]
        num_reuse_max = top_num_reused[0][1] if top_num_reused else 0
    
        derived_no_links = WIKILINK_RE.sub("", derived_section)
        derived_has_transition = any(kw in derived_no_links for kw in N6_DERIVED_TRANSITION_KEYWORDS)
        derived_reused_tokens: list = []
        for tok, n in top_num_reused:
            if n > N6_NUMBER_REUSE_MAX and tok in derived_no_links:
                derived_reused_tokens.append(tok)
    
        # N7 — value-word skew across the A·B labels (English-first LABEL_VALUE_WORDS; Korean set ko-mode)
        label_value_words: dict = {"A": [], "B": [], "C": []}
        for match in DIALECTIC_LABEL_RE.finditer(conflict_section):
            label = match.group("p") or match.group("b")
            full = match.group(0)
            paren = re.search(r"\(([^)]+)\)", full)
            subtitle = paren.group(1) if paren else ""
            hits = [w for w in LABEL_VALUE_WORDS if w in subtitle.lower()]
            if hits and not label_value_words[label]:
                label_value_words[label] = hits
        a_has = bool(label_value_words["A"])
        b_has = bool(label_value_words["B"])
        label_skew = 1 if (a_has != b_has) else 0
    
        return {
            "W1_total": total_links,
            "N4_reuse_max": reuse_max,
            "N4_top": top_reused,
            "N5_verdict_fails": verdict_fails,
            "N6_num_reuse_max": num_reuse_max,
            "N6_top": top_num_reused,
            "N6_derived_has_transition": derived_has_transition,
            "N6_derived_reused_tokens": derived_reused_tokens,
            "N7_label_skew": label_skew,
            "N7_label_words": label_value_words,
            "L1_raw_slugs": raw_slugs,
            "L1_samples": raw_slug_matches[:3],
            "X2_landscape_refs": landscape_refs,
        }
    
    
    # ── contradiction AGGREGATE enc.* (N5·N7 NPOV·D2 Summary drill·D3 DUE balance·F1 Coatrack) ──
    # Ported verbatim from contradiction.py `_check_contradictions_md`. Shared parsing
    # (insights_section·analysis_section·all_links·axes_named[from con D1]) is orchestrator-injected.
    
    L24_THEME_REF_RE = re.compile(r"\[\[([a-z][a-z0-9\-]+?)(?:\|([^\]]+))?\]\]")
    
    
    def evaluate_contradiction_aggregate(
        *,
        insights_section: str,
        analysis_section: str,
        all_links: list,
        axes_named: list,
        theme_slugs: set,
        cluster_slugs: set,
    ) -> dict:
        """Measure contradiction aggregate enc.* (N5·N7·D2·D3·F1). Shared parsing is
        orchestrator-injected (axes_named comes from con D1). The returned dict is
        byte-identical to the corresponding _check_contradictions_md values (verbatim
        port)."""
        # N5 — verdict sentences in `## Implications` (reuses Part 1 lexicon; dormant Korean)
        insights_verdict_fails = 0
        for sentence in _split_sentences(insights_section):
            if any(p.search(sentence) for p in VERDICT_FAIL_PATTERNS):
                insights_verdict_fails += 1
    
        # N7 — value-word skew in axis titles (axes_named comes from con D1; English-first lexicon, Korean set ko-mode)
        axis_skew_hits: list = []
        for axis_name in axes_named:
            # axis-title separators: 'vs'·'/'·'·' fire on English; '대' is the Korean
            # "vs" separator and is dormant on English titles.
            parts = re.split(r"\s+(?:vs|대|·|/)\s+", axis_name.strip(), maxsplit=1)
            if len(parts) != 2:
                continue
            left_hits = [w for w in LABEL_VALUE_WORDS if w in parts[0].lower()]
            right_hits = [w for w in LABEL_VALUE_WORDS if w in parts[1].lower()]
            if bool(left_hits) != bool(right_hits):
                axis_skew_hits.append(f"{axis_name.strip()} (L={left_hits}, R={right_hits})")
        n7_skew = len(axis_skew_hits)
    
        # D2 — theme-reference pipe aliases in the analysis section
        analysis_theme_refs: list = []
        for match in L24_THEME_REF_RE.finditer(analysis_section):
            slug = match.group(1).strip()
            alias = match.group(2)
            if slug in theme_slugs:
                analysis_theme_refs.append((slug, alias))
        d2_total = len(analysis_theme_refs)
        d2_aliased = sum(1 for _slug, alias in analysis_theme_refs if alias)
        d2_raw = [slug for slug, alias in analysis_theme_refs if not alias]
    
        # D3 — per-axis theme balance
        axis_theme_counts: dict = {}
        axis_parts = re.split(r"^###\s+", analysis_section, flags=re.MULTILINE)
        for part in axis_parts[1:]:
            lines = part.splitlines()
            if not lines:
                continue
            axis_name = lines[0].strip()
            # Skip the MECE residual axis: English `Other` or, under WIKI_LANG=ko, `기타`.
            if axis_name.lower() in ("other", "기타"):
                continue
            body = "\n".join(lines[1:])
            count = sum(
                1 for m in L24_THEME_REF_RE.finditer(body)
                if m.group(1).strip() in theme_slugs
            )
            if count > 0:
                axis_theme_counts[axis_name] = count
        if len(axis_theme_counts) >= 2:
            values = list(axis_theme_counts.values())
            d3_max = max(values)
            d3_min = min(values)
            d3_ratio = d3_max / d3_min if d3_min > 0 else float("inf")
        else:
            d3_max = sum(axis_theme_counts.values())
            d3_min = d3_max if axis_theme_counts else 0
            d3_ratio = 1.0 if axis_theme_counts else 0.0
    
        # F1 — block landscape-cluster references (Coatrack)
        f1_refs: list = []
        for target in all_links:
            stem = target.strip().split("/")[-1].split("#", 1)[0]
            if stem in cluster_slugs:
                f1_refs.append(stem)
        f1_count = len(f1_refs)
    
        return {
            "insights_verdict_fails": insights_verdict_fails,
            "axis_skew_hits": axis_skew_hits,
            "n7_skew": n7_skew,
            "analysis_theme_refs": analysis_theme_refs,
            "d2_total": d2_total,
            "d2_aliased": d2_aliased,
            "d2_raw": d2_raw,
            "d3_max": d3_max,
            "d3_min": d3_min,
            "d3_ratio": d3_ratio,
            "f1_refs": f1_refs,
            "f1_count": f1_count,
        }
    
    
    # ── overview L2-4 AGGREGATE enc.* (D2 Summary drill-down·F1 Coatrack) ──
    # Ported verbatim from overview.py `_check_overview_md`. section_spans (from con D1)·
    # all_links·theme_stems (wiki-global) are orchestrator-injected.
    
    def evaluate_overview_aggregate(content: str, *, section_spans: list, all_links: list, theme_stems: set) -> dict:
        """Measure overview L2-4 D2 (drill-down)·F1 (Coatrack). section_spans is the con
        D1 output [(slug, match_end)]. theme_stems is wiki-global (orchestrator-injected).
        Verbatim from overview.py."""
        # D2 — self-slug drill-down link inside the cluster section
        d2_missing: list = []
        for slug, start in section_spans:
            next_h2 = re.search(r"^##\s", content[start:], re.MULTILINE)
            end = start + next_h2.start() if next_h2 else len(content)
            sec_text = content[start:end]
            if not re.search(rf"\[\[{re.escape(slug)}(?:#[^\]|]*)?(?:\|[^\]]*)?\]\]", sec_text):
                d2_missing.append(slug)
        d2_found = len(section_spans)
        d2_count = d2_found - len(d2_missing)
    
        # F1 — block theme references (Coatrack)
        f1_count = 0
        for target in all_links:
            stem = target.strip().split("/")[-1].split("#", 1)[0]
            if stem in theme_stems:
                f1_count += 1
    
        return {
            "d2_count": d2_count,
            "d2_found": d2_found,
            "d2_missing": d2_missing,
            "f1_count": f1_count,
        }
    
    
    # ── L2-2 hub enc.* (body density·`## Connections` grouping — encyclopedic nav-anchor form) ──
    # Ported verbatim from hub_body.py `_check_body`. central_anchor (wiki-global inbound·
    # whether it's an entity)·thresholds are orchestrator/manifest-injected. Detects
    # bloat beyond the nav-anchor's responsibility.
    
    _HUB_FM_RE = re.compile(r"^---\n(.*?)\n---\n", re.DOTALL)
    _HUB_HTML_COMMENT_RE = re.compile(r"<!--.*?-->", re.DOTALL)
    # Matches the `## Connections` hub section (the live English header, per hub.md);
    # the hub body / link-grouping checks scope to this section.
    _HUB_YEONGYEOL_RE = re.compile(r"^##\s+Connections\s*$(.*?)(?=^##\s|\Z)", re.MULTILINE | re.DOTALL)
    
    
    def evaluate_hub_body(
        content: str,
        *,
        central_anchor: bool,
        body_len_advisory: int = 12000,
        yeongyeol_link_advisory: int = 50,
    ) -> dict:
        """Measure L2-2 hub body density (prose ≥ advisory · separates nav-anchor
        responsibility) and flat grouping of `## Connections` links. prose excludes the
        `## Connections` section (the nav link list is not prose). central_anchor (an
        entity or inbound≥threshold) is exempt from the body advisory — orchestrator-
        injected. Verbatim from hub_body.py. The orchestrator formats issues from the
        return value."""
        fm_match = _HUB_FM_RE.match(content)
        body = content[fm_match.end():] if fm_match else content
        body = _HUB_HTML_COMMENT_RE.sub("", body)
    
        prose = _HUB_YEONGYEOL_RE.sub("", body)
        body_len = len(prose.strip())
        body_fires = body_len >= body_len_advisory and not central_anchor
    
        section_match = _HUB_YEONGYEOL_RE.search(body)
        link_count = 0
        grouped = False
        link_fires = False
        if section_match:
            section_body = section_match.group(1)
            link_count = len(WIKILINK_RE.findall(section_body))
            grouped = bool(re.search(r"^###\s+", section_body, re.MULTILINE))
            link_fires = link_count >= yeongyeol_link_advisory and not grouped
    
        return {
            "body_len": body_len,
            "body_fires": body_fires,
            "yeongyeol_link_count": link_count,
            "grouped": grouped,
            "link_fires": link_fires,
        }
    
  • criteria.json 13.2 KB
    {
      "skill": "encyclopedia-writing",
      "_note": "Single SoT for neutral-reference craft criteria definition·comparator·source URL (applied to overview·contradiction·hub·source). IDs are skill-namespaced dots (enc.<slug>); old content-type-local IDs are back-referenced in `legacy`. judge=M (npov-asf·due-weight) keeps only definition + pass_condition and is reviewed qualitatively by desk. Content-type threshold VALUE and algorithm params (strip_auto·exempt_sections) are not owned by the craft skill (axis separation) — injected by manifest (layers/). The skill holds only craft-agnostic algorithm + default. Any Korean literals are matched tokens / the wiki's actual section headers.",
      "criteria": {
        "enc.link-density": {
          "name": "Wikilink total",
          "dimension": "Wikipedia MoS",
          "judge": "A",
          "algorithm": "count_wikilinks_in_editor_region",
          "comparator": ">=",
          "default_threshold": 150,
          "legacy": {"overview-cluster": "W1", "overview-aggregate": "W1", "contradiction-theme": "W1", "contradiction-aggregate": "W1", "source": "W1"},
          "source": "Wikipedia:Manual of Style/Linking",
          "source_url": "https://en.wikipedia.org/wiki/Wikipedia:Manual_of_Style/Linking",
          "note": "Count of [[wikilink]] in the EDITOR region. overview-cluster=150 strip_auto=true·overview-aggregate=200 strip_auto=false injected by manifest; contradiction-theme=30·aggregate=50·source=5 are orchestrator constants (contradiction.py W1_MIN_LINKS/L24_W1_MIN, source.py W1_MIN_LINKS)."
        },
        "enc.first-mention": {
          "name": "First-mention (minimize in-section duplicate links)",
          "dimension": "Wikipedia MoS",
          "judge": "A",
          "algorithm": "count_duplicate_links",
          "comparator": "<=",
          "default_threshold": 0,
          "legacy": {"overview-cluster": "W3", "overview-aggregate": "W3", "contradiction-theme": "W3"},
          "source": "Wikipedia:Manual of Style/Linking",
          "source_url": "https://en.wikipedia.org/wiki/Wikipedia:Manual_of_Style/Linking",
          "note": "Per-section same-stem duplicates (n>1 → n-1 accumulated); dup_total<=0 PASS. exempt_sections·strip_auto injected by manifest. L2-4 absorbs algorithm variants (### subsection unit·drill-down exclusion) via params."
        },
        "enc.lead-body": {
          "name": "Lead-body link density gradient",
          "dimension": "Wikipedia MoS",
          "judge": "A",
          "comparator": ">=",
          "default_threshold": 1.0,
          "legacy": {"overview-cluster": "W2", "overview-aggregate": "W2"},
          "source": "Wikipedia:Manual of Style/Linking",
          "source_url": "https://en.wikipedia.org/wiki/Wikipedia:Manual_of_Style/Linking",
          "note": "Lead-span per-paragraph link density ≥ body-section average (first contact is offered the choices). Measured by orchestrator (overview.py) since lead-section identification is content-type-bound — no skill checks.py."
        },
        "enc.broken-link": {
          "name": "Broken wikilink (existing filename)",
          "dimension": "Wikipedia MoS",
          "judge": "A",
          "comparator": "<=",
          "default_threshold": 0,
          "legacy": {"overview-cluster": "W4", "overview-aggregate": "W4", "contradiction-theme": "W4", "contradiction-aggregate": "W4"},
          "source": "Wikipedia:Manual of Style/Linking",
          "source_url": "https://en.wikipedia.org/wiki/Wikipedia:Manual_of_Style/Linking",
          "note": "Every [[wikilink]] must resolve to an existing page (broken links = 0). Measured by orchestrator (`python tools/lint.py graph structure`) since broken-link resolution needs the wiki-global page index, not single-file content — no skill checks.py (same pattern as enc.lead-body). roster-required, but verified via the graph-structure pass rather than the per-content-type group lint."
        },
        "enc.verdict-restraint": {
          "name": "Verdict restraint",
          "dimension": "Wikipedia NPOV",
          "judge": "A",
          "algorithm": "find_verdict_hits",
          "comparator": "<=",
          "default_threshold": 2,
          "legacy": {"overview-cluster": "B1", "overview-aggregate": "B1", "contradiction-theme": "N5", "contradiction-aggregate": "N5"},
          "source": "Wikipedia:Neutral point of view",
          "source_url": "https://en.wikipedia.org/wiki/Wikipedia:Neutral_point_of_view",
          "note": "Count of verdict sentences ('is reasonable'·'has the most explanatory power'·'is consistent with', etc.) ≤ threshold (loose). Over → hedging/attribution needed. Threshold injected by manifest. For contradiction, measured only in the Interpretive Direction (theme) / Implications (aggregate) section. NOTE the underlying matcher (find_verdict_hits) keys on Korean verb endings and is dormant on English prose — see checks.py FLAG."
        },
        "enc.npov-asf": {
          "name": "ASF attribution",
          "dimension": "Wikipedia NPOV",
          "judge": "M",
          "pass_condition": "A value-judgment/interpretation/claim sentence carries ≥2 of {named subject, source, figure/quote}; no weasel words ('some say'·'many people'·'as is known') (WP:ASF — Attribute Statements to Facts).",
          "legacy": {"contradiction-theme": "N1", "contradiction-aggregate": "N1"},
          "source": "Wikipedia:Manual of Style/Words to watch — WP:ASF",
          "source_url": "https://en.wikipedia.org/wiki/Wikipedia:Manual_of_Style/Words_to_watch#Attribution"
        },
        "enc.due-weight": {
          "name": "Due weight balance",
          "dimension": "Wikipedia NPOV",
          "judge": "M",
          "pass_condition": "Opposing sides' length/evidence is proportionate, not skewed to one side (theme = A:B evidence-count ratio within 1:2, aggregate = even per-theme summary length); a minority view carries an explicit balancing statement (WP:DUE).",
          "legacy": {"contradiction-theme": "N2", "contradiction-aggregate": "N2"},
          "source": "Wikipedia:Neutral point of view — Due and undue weight",
          "source_url": "https://en.wikipedia.org/wiki/Wikipedia:Neutral_point_of_view#Due_and_undue_weight"
        },
        "enc.label-neutral": {
          "name": "Faction-label neutrality",
          "dimension": "Wikipedia NPOV",
          "judge": "A",
          "algorithm": "evaluate_contradiction_npov",
          "comparator": "==",
          "default_threshold": 0,
          "legacy": {"contradiction-theme": "N7", "contradiction-aggregate": "N7"},
          "source": "Wikipedia:Manual of Style/Words to watch — WP:ASF",
          "source_url": "https://en.wikipedia.org/wiki/Wikipedia:Manual_of_Style/Words_to_watch#Attribution",
          "note": "If a value-laden word ('myth'·'empirical proof'·'skepticism'·'innovation', etc.) appears on only one side of the faction bold labels (theme) or tension-axis titles (aggregate), skew=1 FAIL. Both must be an equivalent vocabulary set. Measured by evaluate_contradiction_npov (theme)·evaluate_contradiction_aggregate (aggregate axis-skew). NOTE the value-word lexicon (LABEL_VALUE_WORDS) is English-native ('myth'·'empirical'·'skepticism'·'innovation', etc.); the Korean equivalents fire under WIKI_LANG=ko."
        },
        "enc.slug-alias": {
          "name": "Slug alias",
          "dimension": "Wikipedia MoS",
          "judge": "A",
          "algorithm": "find_unaliased_slugs",
          "comparator": "<=",
          "default_threshold": 0,
          "legacy": {"overview-cluster": "L1", "overview-aggregate": "L1", "contradiction-theme": "L1"},
          "source": "Wikipedia:Manual of Style/Linking",
          "source_url": "https://en.wikipedia.org/wiki/Wikipedia:Manual_of_Style/Linking",
          "note": "Count of kebab-case slugs ≥min_len exposed as bare [[slug]] (no alias) ≤ threshold. min_len·threshold injected by manifest. contradiction L1 uses a different detector (RAW_KEBAB_SLUG_RE in evaluate_contradiction_npov). Aliases are human-readable display text."
        },
        "enc.abbr-gloss": {
          "name": "Abbreviation glossing",
          "dimension": "Wikipedia MoS",
          "judge": "A",
          "algorithm": "find_abbr_violations",
          "comparator": "<=",
          "default_threshold": 3,
          "legacy": {"overview-cluster": "L3", "overview-aggregate": "L3"},
          "source": "Wikipedia:Manual of Style/Abbreviations",
          "source_url": "https://en.wikipedia.org/wiki/Wikipedia:Manual_of_Style/Abbreviations",
          "note": "Count of 2–5-char uppercase acronyms whose first occurrence lacks a parenthetical gloss ≤ threshold (global brands·wikilinked abbreviations allowlisted). Threshold injected by manifest."
        },
        "enc.summary-style": {
          "name": "Drill-down link (Summary style)",
          "dimension": "Wikipedia Summary style",
          "judge": "A",
          "algorithm": "evaluate_overview_aggregate",
          "comparator": "==",
          "default_threshold": "section_count",
          "legacy": {"overview-aggregate": "D2", "contradiction-aggregate": "D2"},
          "source": "Wikipedia:Summary style · Template:Main",
          "source_url": "https://en.wikipedia.org/wiki/Wikipedia:Summary_style",
          "note": "Each subsection of a roll-up carries an entry-point link (self slug alias) to its detail page (WP {{Main}} hatnote). overview = cluster sections, contradiction aggregate = theme drill-down [[slug|alias]] pipe alias. Measured by evaluate_overview_aggregate (overview)·evaluate_contradiction_aggregate (contradiction) bundle."
        },
        "enc.back-reference": {
          "name": "Landscape back-reference",
          "dimension": "Wikipedia Summary style",
          "judge": "A",
          "algorithm": "evaluate_contradiction_npov",
          "comparator": ">=",
          "default_threshold": 1,
          "legacy": {"contradiction-theme": "X2"},
          "source": "Wikipedia:Summary style · Template:Main",
          "source_url": "https://en.wikipedia.org/wiki/Wikipedia:Summary_style",
          "note": "A theme piece provides ≥1 entry point up to its related cluster overview as a [[<cluster-slug>|alias]] pipe alias (reader's issue→field landscape back-route). Measured by evaluate_contradiction_npov (cluster_slugs orchestrator-injected)."
        },
        "enc.coatrack": {
          "name": "Coatrack avoidance (block tangential)",
          "dimension": "Wikipedia Coatrack",
          "judge": "A",
          "algorithm": "evaluate_overview_aggregate",
          "comparator": "==",
          "default_threshold": 0,
          "legacy": {"overview-aggregate": "F1", "contradiction-aggregate": "F1"},
          "source": "Wikipedia:Coatrack articles · Wikipedia:Scope",
          "source_url": "https://en.wikipedia.org/wiki/Wikipedia:Coatrack_articles",
          "note": "0 references to another axis so the roll-up's nominal subject is not obscured by a tangential subject. Measured by evaluate_overview_aggregate (overview)·evaluate_contradiction_aggregate (contradiction blocks landscape cluster refs) bundle (all_links·other-axis stems orchestrator-injected)."
        },
        "enc.due-balance": {
          "name": "Axis balance (due impartiality)",
          "dimension": "Wikipedia NPOV",
          "judge": "A",
          "algorithm": "evaluate_contradiction_aggregate",
          "comparator": "<=",
          "default_threshold": 4.0,
          "legacy": {"contradiction-aggregate": "D3"},
          "source": "Wikipedia:Neutral point of view — Balancing aspects",
          "source_url": "https://en.wikipedia.org/wiki/Wikipedia:Neutral_point_of_view#Balancing_aspects_of_an_article",
          "note": "Max/min per-axis theme-count ratio ≤ threshold (prevent over-skew to one axis, WP balancing aspects). Natural skew possible → advisory (human review). Measured by evaluate_contradiction_aggregate bundle."
        },
        "enc.encyclopedic-tone": {
          "name": "Encyclopedic tone (no editorial self-reference)",
          "dimension": "Wikipedia Self-reference",
          "judge": "A",
          "comparator": "==",
          "default_threshold": 0,
          "legacy": {"hub": "voice"},
          "source": "Wikipedia:Manual of Style/Self-references to avoid",
          "source_url": "https://en.wikipedia.org/wiki/Wikipedia:Manual_of_Style/Self-references_to_avoid",
          "note": "The body does not self-reference editorial artifacts/decisions (e.g. 'this hub treats … as'·'is organized separately'·'here we keep only …'; violations = 0). General encyclopedic register, but measurement currently only via hub voice lint (orchestrator, hub_*.py regex)."
        },
        "enc.nav-anchor-density": {
          "name": "Navigational-anchor body density",
          "dimension": "Wikipedia Summary style",
          "judge": "A",
          "algorithm": "evaluate_hub_body",
          "comparator": "<=",
          "default_threshold": 12000,
          "legacy": {"hub": "body"},
          "source": "Wikipedia:Summary style",
          "source_url": "https://en.wikipedia.org/wiki/Wikipedia:Summary_style",
          "note": "When a navigational-anchor page's body prose exceeds the advisory ceiling, delegate the deep-dive to a sub-hub (spinoff). A central anchor (entity or inbound≥threshold) is legitimately content-rich and exempt — central_anchor·threshold injected by orchestrator (hub_body.py). advisory."
        },
        "enc.connection-grouping": {
          "name": "Connection-list grouping",
          "dimension": "Wikipedia MoS",
          "judge": "A",
          "algorithm": "evaluate_hub_body",
          "comparator": "<=",
          "default_threshold": 50,
          "legacy": {"hub": "yeongyeol"},
          "source": "Wikipedia:Manual of Style/Layout",
          "source_url": "https://en.wikipedia.org/wiki/Wikipedia:Manual_of_Style/Layout",
          "note": "When a connection list's flat links exceed the advisory ceiling, group into sub-categories (### Category headings). A grouped list passes regardless of count. Threshold orchestrator-injected. advisory."
        }
      }
    }
    
  • SKILL.md 10.1 KB
    ---
    name: encyclopedia-writing
    description: Encyclopedic neutral-reference writing craft — NPOV (attribute facts not opinions, due weight, neutral faction labels, verdict restraint), summary style and Coatrack avoidance, wikilink conventions (link density, first-mention, slug alias, abbreviation glossing). Use when writing or reviewing a neutral encyclopedic/wiki reference, landscape overview, hub, or source page, or when a source-based connected document that advocates no particular viewpoint is needed.
    ---
    
    # encyclopedia-writing
    
    Neutral-reference writing craft drawn from the encyclopedic editing tradition (the Wikipedia policy family). It keeps this wiki's overview, issue, and hub pages a connected reference that is grounded in facts and sources and advocates no particular viewpoint. `criteria.json` is the SoT for each criterion's definition, comparator, and source; content-type thresholds and algorithm params are injected by `.claude/layers/_manifest.json` (the skill is content-type-agnostic). Examples are illustrative of the target English prose; the Korean-rendering rules below apply only when WIKI_LANG=ko and are inert on the default English corpus.
    
    ## Wikilink connectivity (enc.link-density · enc.first-mention · enc.lead-body)
    
    A reference's value comes from the link density that lets a reader move to adjacent concepts; at the same time, re-linking the same target within one section is visual noise.
    
    - **enc.link-density** — the EDITOR region (excluding AUTO blocks) holds at least the threshold number of `[[wikilink]]`s. "Articles on highly technical subjects might demand a higher density of links," so technical/finance domains are allowed slightly above the ceiling.
    - **enc.first-mention** — "link a term at most once per major section, at first occurrence." A second link of the same stem within a section becomes plain text. A section that structurally cites the same target twice (e.g. adjacent-field boundary prose) is exempted via the manifest's `exempt_sections`.
    - **enc.lead-body** — "the lead of an article usually has a greater density of links than later parts of the article"; this is expected, since the reader's first encounter is where navigation options should be densest. Because which span counts as the lead is content-type-bound, the orchestrator measures it.
    
    All three are deterministic; `checks.py` (link-density·first-mention) or the orchestrator (lead-body) computes PASS/FAIL with manifest-injected thresholds/params.
    
    ## Neutrality & notation (enc.verdict-restraint · enc.slug-alias · enc.abbr-gloss · enc.encyclopedic-tone)
    
    A reference advocates no viewpoint (NPOV) and follows readable notation conventions (MoS). Deterministic; thresholds injected by manifest.
    
    - **enc.verdict-restraint** — "avoid stating opinions as facts" and "describe disputes, but do not engage in them." Write evaluative sentences in three strengths — observation ("the metric points the opposite way") and conditional recommendation ("appears to be"·"may be") are fine, but a verdict ("is reasonable"·"has the most explanatory power"·"is consistent with") over the threshold must be softened with hedging/attribution. Repeated verdicts under hedged wording stay neutral in form but biased in substance. (ko-localization renderings: observation `~ 지표가 정반대를 가리킨다`; conditional `~로 보인다`·`~일 가능성이 있다`; verdict `~이 합리적이다`·`가장 설명력이 높다`·`~에 부합한다`.)
    - **enc.slug-alias** — do not expose a long raw kebab-case identifier; show a human-readable alias (MoS overlinking/readability). This wiki uses English aliases by default; native-script aliases only under WIKI_LANG=ko.
    - **enc.abbr-gloss** — "an acronym should be written out in full for the first time, followed by the abbreviation in parentheses" (commonly used abbreviations such as global brands are exempt). Applies to abbreviations appearing in body text.
    - **enc.encyclopedic-tone** — keep the encyclopedic register; do not self-reference the editorial artifact or editorial decisions (WP:SELFREF — an article "shouldn't refer to [the work] in a non-neutral fashion"). Editorializing such as "this document treats … as"·"here we keep only …" is deleted or absorbed into a factual statement. (ko-localization renderings: `본 문서는 ~로 둔다`·`여기서는 ~만 유지한다`.)
    
    ## Issue neutrality & balance (enc.npov-asf · enc.due-weight · enc.label-neutral · enc.back-reference)
    
    A piece covering a dispute advocates no side and fairly juxtaposes both, grounded in facts and sources (WP:NPOV — ASF and DUE run through every section).
    
    - **enc.npov-asf** — attribute every value-judgment/interpretation/claim with a named subject + source + figure/quote (≥2 of the 3); "avoid stating opinions as facts," and use no weasel words ("some say"·"many people"·"as is known"; ko renderings `혹자는`·`많은 사람이`·`알려진 바로는`). judge=M. e.g. ✅ "The IPCC warned of coastal-city risk in its 2021 report" (named source + date) / ❌ "Many experts believe coastal cities are at risk" (anonymous plurality — weasel)
    - **enc.due-weight** — coverage is proportionate to prominence in reliable sources, not skewed to one side (WP:DUE); a minority view does not get equal space and is explicitly framed as a minority. judge=M. e.g. ✅ "The Earth is spherical and the scientific consensus is overwhelming; a minority dissents but without credible grounds" (proportionate weight + minority noted) / ❌ "The Earth is round. But some believe it is flat." (false balance)
    - **enc.label-neutral** — both faction labels (or the aggregate's tension-axis titles) must be drawn from an equivalent vocabulary set. If only one side gets a value-laden word ("myth"·"empirical proof"·"skepticism"·"innovation," etc.), that side is privileged or demoted — unify both on a structural/standpoint basis ("replacement vs augmentation"·"industry-led vs regulation-led"). Deterministic check for one-sided skew = 0. **ko-localization rendering rule (WIKI_LANG=ko only)** — even when the English original is neutral, a literal Korean rendering can introduce connotation, so when localizing a title to Korean, check the following English→Korean mapping (applies to labels, titles, and body alike):
    
      | English (neutral) | Literal Korean (connotation introduced) | Recommended Korean (equivalent) |
      |---|---|---|
      | dual strategy | 이중성 (hypocrisy) | 이중 트랙·양 트랙 병행 |
      | paradox | 역설 (wrong) | 반전·교차 구도 |
      | tension | 긴장 (instability) | 대립·교차 압력 |
      | trade-off | 절충 (concession) | 상충 균형·양립 조건 |
      | hype | 과장 (value judgment) | 기대 담론·전망 강조 |
      | myth | 신화 (falsehood) | 담론·통설·전망 |
    - **enc.back-reference** — a narrow issue piece keeps at least one entry-point link up to its parent/landscape overview (an upward link so the reader can move issue → field landscape; WP Summary style).
    
    ## Aggregation linking (enc.summary-style · enc.coatrack)
    
    Encyclopedic conventions for a higher roll-up that covers lower detail. Deterministic; thresholds injected by manifest.
    
    - **enc.summary-style** — "sections of long articles should be spun off into their own articles, leaving summaries in their place"; each subsection carries an entry-point link to its detail page (the `{{Main}}` hatnote). The roll-up summarizes and provides a drill-down path, not a copy of the detail.
    - **enc.coatrack** — keep the roll-up's nominal subject from being obscured; a coatrack article "gets away from its nominal subject, and instead gives more attention to ... tangential subjects." Block references that drift to another axis (WP Coatrack·Scope).
    
    ## Navigational anchor (enc.nav-anchor-density · enc.connection-grouping)
    
    A navigational-anchor page does not carry deep exposition; it serves as an entry point to adjacent pages. Deterministic; thresholds injected by manifest.
    
    - **enc.nav-anchor-density** — when an anchor page's body prose exceeds the advisory ceiling, spin the deep-dive off into a sub-page (Summary style spinoff). A central anchor (a heavily-cited core page) is legitimately content-rich and exempt.
    - **enc.connection-grouping** — when a connection list's flat links grow numerous, group them into sub-categories (`### Category`; ko `### 카테고리`) (MoS Layout). A grouped list passes regardless of count.
    
    ## Sources
    
    Each URL points to the relevant page as of the last verification.
    
    - [Wikipedia:Manual of Style/Linking](https://en.wikipedia.org/wiki/Wikipedia:Manual_of_Style/Linking) — wikilink density standard·lead-body gradient·first-mention principle·slug alias (overlinking avoidance)
    - [Wikipedia:Neutral point of view](https://en.wikipedia.org/wiki/Wikipedia:Neutral_point_of_view) — verdict restraint·[Due and undue weight](https://en.wikipedia.org/wiki/Wikipedia:Neutral_point_of_view#Due_and_undue_weight) (due weight)·[Balancing aspects](https://en.wikipedia.org/wiki/Wikipedia:Neutral_point_of_view#Balancing_aspects_of_an_article) (per-axis balance)
    - [Wikipedia:Manual of Style/Words to watch — WP:ASF](https://en.wikipedia.org/wiki/Wikipedia:Manual_of_Style/Words_to_watch#Attribution) — Attribute Statements to Facts·no weasel words (neutral faction labels·ASF attribution)
    - [Wikipedia:Manual of Style/Self-references to avoid](https://en.wikipedia.org/wiki/Wikipedia:Manual_of_Style/Self-references_to_avoid) — encyclopedic tone·no editorial self-reference
    - [Wikipedia:Manual of Style/Abbreviations](https://en.wikipedia.org/wiki/Wikipedia:Manual_of_Style/Abbreviations) — gloss abbreviation in parentheses on first appearance
    - [Wikipedia:Summary style](https://en.wikipedia.org/wiki/Wikipedia:Summary_style) · [Template:Main](https://en.wikipedia.org/wiki/Template:Main) — drill-down entry point for aggregating pieces ({{Main}})
    - [Wikipedia:Coatrack articles](https://en.wikipedia.org/wiki/Wikipedia:Coatrack_articles) · [Wikipedia:Scope](https://en.wikipedia.org/wiki/Wikipedia:Scope) — block tangential subjects·scope discipline
    - [Wikipedia:Manual of Style/Layout](https://en.wikipedia.org/wiki/Wikipedia:Manual_of_Style/Layout) — connection-list grouping (navigational anchor)
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related