# How AI Turns a Chaotic Knowledge Base into a Structured Wiki

> A four-stage pipeline: audit → categorize → restructure → migrate. Prompts, tools (Outline, Notion, Confluence), and a worked example of the transformation.
> Author: Roman Belov · Published: 2026-07-09 · Source: https://futurecraft.pro/blog/knowledge-base-wiki-ai/

In teams under 50 people, knowledge lives in Slack threads, ownerless Google Docs, Notion folders last touched in August 2023, and markdown files in repos nobody opens. Half of it is outdated. A good chunk is duplicated. The rest is unfindable.

The classic fix is to assign one person, hand them two months and full access. The outcome is predictable: enthusiasm fades after two weeks, the project stalls after a month. Effort wildly out of proportion to result, because 80% of the time goes to mechanical work — reading, classifying, reformatting.

LLMs cut the cost of that mechanical work by an order of magnitude. Not because "AI does it all automatically," but because a model chews through 200 pages in minutes and surfaces duplicates, stale sections, and coverage gaps. Humans decide; the LLM does the grinding.

What follows is a four-stage pipeline for turning a chaotic knowledge base into a structured wiki — with prompts, tools, and concrete metrics at each step. Same principle as [generating SOPs from chaos](/blog/sop-generator-ai-documentation/): reuse what already exists instead of writing from scratch.

## Stage 1: Audit — Inventory and Assessment

You can't fix what you haven't measured. The audit answers three questions: how many documents exist, what shape they're in, and where the gaps are.

### Extracting Content

Start by pulling everything into one format. Sources for a typical team:

```
Notion           → Export → Markdown (native export)
Google Docs      → Export → Markdown (via Google Takeout or Docs API)
Confluence       → Export → HTML/XML (Space Export → pandoc conversion)
Slack/Teams      → Export → JSON (Admin Export → channel filtering)
GitHub/GitLab    → Clone  → Markdown (wiki + docs/ + README)
Loom/video       → API    → Transcripts (Loom API or Whisper)
```

Converting Confluence exports via pandoc:

```bash
# Convert Confluence HTML export to markdown
find ./confluence-export -name "*.html" -exec sh -c '
  pandoc "$1" -f html -t gfm -o "${1%.html}.md"
' _ {} \;
```

Notion's export is simpler — it outputs markdown natively. Page relationships and database views don't survive the export, but that's fine at the audit stage.

### Document Audit Prompt

Once everything is in markdown, run each document through the LLM:

```
ROLE: Technical documentation auditor.

TASK: Analyze the document below and produce a structured assessment.

ASSESSMENT CRITERIA:
1. FRESHNESS: Last meaningful update date (from content clues, not metadata).
   Rate: current (< 6 months) | stale (6-12 months) | outdated (> 12 months)
2. COMPLETENESS: Does the document cover its topic fully?
   Rate: complete | partial | stub
3. ACCURACY: Are there references to deprecated tools, old URLs,
   removed features, or contradictory statements?
   Rate: accurate | has_issues | unreliable
4. DUPLICATES: Does this document overlap with others in the batch?
   List overlapping document IDs if yes.
5. AUDIENCE: Who is the intended reader?
   Options: engineering | product | operations | all | unclear

OUTPUT FORMAT (JSON):
{
  "doc_id": "<filename>",
  "title": "<extracted title>",
  "topic": "<one-line summary>",
  "freshness": "current|stale|outdated",
  "completeness": "complete|partial|stub",
  "accuracy": "accurate|has_issues|unreliable",
  "duplicates": ["<doc_id>", ...],
  "audience": "engineering|product|operations|all|unclear",
  "issues": ["<specific issue 1>", ...],
  "recommendation": "keep|update|merge|archive|delete"
}

DOCUMENT:
{document_content}
```

Run this in batch mode. Processing 300 documents through the Claude API (Sonnet) takes 15–20 minutes and costs $3–5. One important note: the `recommendation` field isn't a final call. It's input for a human who confirms or adjusts each one.

### Batch Processing Script

```python
import anthropic
import json
from pathlib import Path

client = anthropic.Anthropic()
AUDIT_PROMPT = Path("prompts/audit.txt").read_text()

def audit_document(file_path: Path) -> dict:
    content = file_path.read_text(encoding="utf-8")

    # Limit: documents > 50K characters are truncated
    if len(content) > 50_000:
        content = content[:50_000] + "\n\n[TRUNCATED — full document is longer]"

    response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=1024,
        messages=[{
            "role": "user",
            "content": AUDIT_PROMPT.replace("{document_content}", content)
        }]
    )
    return json.loads(response.content[0].text)

# Process all files
docs_dir = Path("./exported-docs")
results = []
for md_file in sorted(docs_dir.glob("**/*.md")):
    result = audit_document(md_file)
    results.append(result)
    print(f"  {result['doc_id']}: {result['recommendation']}")

# Save results
Path("audit-results.json").write_text(
    json.dumps(results, indent=2, ensure_ascii=False)
)
```

### Audit Metrics

The output gives you a health dashboard for your knowledge base:

```
Total documents:         312
├── keep:                 89  (29%)
├── update:              104  (33%)
├── merge:                47  (15%)
├── archive:              58  (19%)
└── delete:               14  (4%)

Duplicate pairs found:    63
Without owner:           187 (60%)
Outdated (> 12 mo):      142 (46%)
```

A typical split: a third stays, a third needs updating, the rest is dead weight. Seeing the scale in numbers makes it far easier to actually finish the project.

## Stage 2: Categorize — Taxonomy and Hierarchy

The audit told you *what* exists. Categorization figures out *where* things go. A flat list of 300 documents is useless — you need a hierarchy that reflects how people actually search for information.

### Two Approaches to Taxonomy

**Top-down:** define the structure first, then place documents. Works when the structure is obvious — an engineering wiki organized by service, for instance.

**Bottom-up:** the LLM analyzes all documents and proposes clusters. Works for chaotic bases where the "right" structure isn't clear up front.

In practice, the hybrid wins: the LLM generates clusters (bottom-up), a human adjusts them to real needs (top-down).

### Taxonomy Generation Prompt

```
ROLE: Information architect specializing in knowledge management.

TASK: Analyze the document inventory below and propose a hierarchical
taxonomy for a team wiki.

CONSTRAINTS:
- Maximum 3 levels of nesting (section → subsection → page)
- Maximum 8 top-level sections
- Every document must belong to exactly one section
- Section names should be action-oriented or domain-oriented,
  not generic ("How-to Guides" instead of "Documents")
- Consider the audience field from audit data

DOCUMENT INVENTORY:
{audit_results_json}

OUTPUT FORMAT:
For each proposed section:
1. Section name and one-line description
2. Subsections (if needed)
3. List of doc_ids assigned to this section
4. Gap analysis: topics that SHOULD exist but don't

Also provide:
- CROSS-REFERENCES: Documents that should link to each other
- LANDING PAGES: Sections that need an overview/index page (not present
  in current docs)
```

### Categorization Output

The LLM generates a structure that makes connections and gaps visible:

```
PROPOSED TAXONOMY:

1. Getting Started
   ├── Onboarding checklist
   ├── Development environment setup
   ├── Access & permissions
   └── [GAP] Architecture overview for new engineers

2. Engineering Practices
   ├── Code Review
   │   ├── review-guidelines.md
   │   ├── pr-template.md
   │   └── [MERGE] code-standards.md + style-guide.md
   ├── Testing
   │   ├── testing-strategy.md
   │   └── [GAP] E2E testing guide
   └── CI/CD
       ├── pipeline-overview.md
       └── deploy-process.md

3. Operations & Runbooks
   ├── Incident Response
   │   ├── on-call-rotation.md
   │   └── incident-playbook.md
   ├── Monitoring
   │   └── alerts-reference.md
   └── [GAP] Post-mortem template

...
```

The gap analysis is the most valuable output from this stage. The LLM surfaces missing documents that "everyone was going to write someday." That list becomes an immediate backlog for the next iteration.

### Cross-References

The LLM also earns its keep by finding connections between documents. A deploy process doc should link to the rollback runbook. A monitoring guide should link to the alerts reference. In a chaotic base those connections simply don't exist — documents were written at different times by different people who never cross-referenced anything.

Prompt for generating cross-references:

```
TASK: For each document in the taxonomy, identify 2-5 documents
that SHOULD be linked from it. Explain why each link is needed.

CRITERIA for a valid cross-reference:
- Document A explains a concept that Document B assumes as known
- Document A describes a process that includes steps from Document B
- Document A and B cover the same system from different angles
  (architecture vs operations vs troubleshooting)

OUTPUT: List of {source_doc, target_doc, link_type, reason}
link_type: prerequisite | related | see_also | continuation
```

## Stage 3: Restructure — Content Rework

Documents are sorted, structure is set. Now every document marked `update` or `merge` runs through LLM processing.

### Document Quality Standard

Before you restructure anything, you need a clear benchmark. What makes a document actually good:

```
DOCUMENT QUALITY STANDARD:

1. TITLE: Descriptive, searchable. Not "Notes" or "Untitled".
2. METADATA: Owner, last reviewed date, audience, status.
3. STRUCTURE:
   - TL;DR or summary at the top (2-3 sentences)
   - Logical heading hierarchy (H2 → H3, never skip levels)
   - Steps numbered, not bulleted
   - Code blocks with language tags
4. CONTENT:
   - No assumptions about reader's context
   - Links to prerequisites
   - Concrete examples, not abstract descriptions
   - Version-specific information marked explicitly
5. MAINTENANCE:
   - Review date set (quarterly for processes, monthly for configs)
   - Owner assigned
   - Change log at the bottom
```

### Restructuring Prompt

```
ROLE: Technical editor. You restructure documentation to match
a quality standard.

TASK: Rewrite the document below according to the QUALITY STANDARD.

RULES:
- Preserve ALL factual content. Do not invent information.
- If something is ambiguous, mark it with [NEEDS CLARIFICATION: ...].
- If a section references external knowledge, add
  [LINK NEEDED: <topic>].
- Convert implicit knowledge into explicit steps.
- Remove duplicate paragraphs and redundant phrasing.
- Add metadata header.

QUALITY STANDARD:
{quality_standard}

ORIGINAL DOCUMENT:
{document_content}

CONTEXT: This document belongs to section "{section_name}"
in the wiki. Target audience: {audience}.

OUTPUT: Restructured document in markdown. After the document,
provide a CHANGE LOG listing every significant change made
and why.
```

### Merging Duplicates

Documents flagged as `merge` require a separate prompt:

```
TASK: Merge these documents into one definitive document.

RULES:
- Take the most recent and complete information from each source.
- Where sources contradict each other, keep BOTH versions with
  a [CONFLICT: Source A says X, Source B says Y — needs resolution] marker.
- Do not silently drop information from any source.
- Structure the merged document according to the quality standard.

DOCUMENTS TO MERGE:
--- Document A ({doc_a_id}, last updated: {date_a}) ---
{doc_a_content}

--- Document B ({doc_b_id}, last updated: {date_b}) ---
{doc_b_content}
```

The `[NEEDS CLARIFICATION]` and `[CONFLICT]` markers aren't optional — they're load-bearing. The LLM shouldn't decide contested content. Those markers become concrete tasks assigned to specific people after migration.

### Generating Missing Documents

The gap analysis from Stage 2 identified topics that should be documented but weren't. The LLM generates skeleton documents:

```
TASK: Generate a skeleton document for the topic below.

TOPIC: {gap_topic}
SECTION: {parent_section}
AUDIENCE: {audience}

RULES:
- Create the document structure with headings and 1-2 sentence
  descriptions of what each section should contain.
- Mark every section body as [TO BE WRITTEN BY: <suggested owner>].
- Include a "Questions to Answer" section listing what information
  needs to be gathered.
- Do NOT generate fake content. Structure only.
```

Skeleton documents are easy wins. Ten headings with one-sentence descriptions take a minute — versus staring at a blank page for an hour. Authors are much more likely to fill in a skeleton than start from nothing.

## Stage 4: Migrate — Moving to the Target Platform

Content is restructured. Now it needs to land in a wiki system with proper navigation, permissions, and search.

### Choosing a Platform

Three options, each with its specifics:

| Criterion | Outline | Notion | Confluence |
|-----------|---------|--------|------------|
| Self-hosted | Yes (Docker) | No | Yes (Data Center) |
| Import API | REST + markdown native | REST, block model | REST, XHTML storage |
| Search | Full-text, fast | Full-text + AI | Full-text + CQL |
| Price (50 people) | $0 (self-hosted) | $500/mo (Plus, annual) | $520/mo (Cloud) |
| Markdown-native | Yes | No (conversion) | No (conversion) |
| Open-source | Yes (BSL) | No | No |

For teams already working in markdown who care about control, Outline is the right call. The API takes markdown without conversion, search is fast, and the self-hosted version has no feature gaps.

### Automating Import into Outline

```python
import requests
from pathlib import Path

OUTLINE_URL = "https://wiki.company.com"
API_TOKEN = "ol_api_..."

def get_or_create_collection(name: str) -> str:
    """Get or create a collection (top-level section)."""
    resp = requests.post(f"{OUTLINE_URL}/api/collections.list",
        headers={"Authorization": f"Bearer {API_TOKEN}"},
        json={}
    )
    for col in resp.json()["data"]:
        if col["name"] == name:
            return col["id"]

    resp = requests.post(f"{OUTLINE_URL}/api/collections.create",
        headers={"Authorization": f"Bearer {API_TOKEN}"},
        json={"name": name, "permission": "read_write"}
    )
    return resp.json()["data"]["id"]

def create_document(title: str, markdown: str,
                    collection_id: str, parent_id: str = None) -> str:
    """Create a document in a collection."""
    payload = {
        "title": title,
        "text": markdown,
        "collectionId": collection_id,
        "publish": True
    }
    if parent_id:
        payload["parentDocumentId"] = parent_id

    resp = requests.post(f"{OUTLINE_URL}/api/documents.create",
        headers={"Authorization": f"Bearer {API_TOKEN}"},
        json=payload
    )
    return resp.json()["data"]["id"]

def migrate_taxonomy(taxonomy: dict, docs_dir: Path):
    """Migrate the full taxonomy into Outline."""
    for section in taxonomy["sections"]:
        collection_id = get_or_create_collection(section["name"])

        for subsection in section.get("subsections", []):
            # Create parent document for subsection
            parent_id = create_document(
                title=subsection["name"],
                markdown=f"# {subsection['name']}\n\n{subsection.get('description', '')}",
                collection_id=collection_id
            )

            for doc_id in subsection["doc_ids"]:
                file_path = docs_dir / f"{doc_id}.md"
                if file_path.exists():
                    content = file_path.read_text()
                    title = content.split("\n")[0].lstrip("# ")
                    create_document(title, content, collection_id, parent_id)
                    print(f"  Migrated: {doc_id} → {section['name']}/{subsection['name']}")
```

### Importing into Notion

Notion uses a block model, not markdown. Conversion adds a step:

```python
from notion_client import Client

notion = Client(auth="secret_...")

def markdown_to_notion_blocks(markdown: str) -> list:
    """Convert markdown to Notion blocks."""
    blocks = []
    for line in markdown.split("\n"):
        if line.startswith("## "):
            blocks.append({
                "type": "heading_2",
                "heading_2": {"rich_text": [{"text": {"content": line[3:]}}]}
            })
        elif line.startswith("### "):
            blocks.append({
                "type": "heading_3",
                "heading_3": {"rich_text": [{"text": {"content": line[4:]}}]}
            })
        elif line.startswith("- "):
            blocks.append({
                "type": "bulleted_list_item",
                "bulleted_list_item": {"rich_text": [{"text": {"content": line[2:]}}]}
            })
        elif line.strip():
            blocks.append({
                "type": "paragraph",
                "paragraph": {"rich_text": [{"text": {"content": line}}]}
            })
    return blocks

def create_notion_page(title: str, markdown: str, parent_page_id: str):
    """Create a page in Notion."""
    blocks = markdown_to_notion_blocks(markdown)
    # Notion API limits 100 blocks per request
    notion.pages.create(
        parent={"page_id": parent_page_id},
        properties={"title": [{"text": {"content": title}}]},
        children=blocks[:100]
    )
```

Notion API caps at 100 blocks per request. Longer documents need pagination via `blocks.children.append`.

### Importing into Confluence

Confluence expects XHTML (Atlassian Storage Format). Convert markdown → XHTML via pandoc:

```bash
pandoc input.md -f gfm -t html -o output.html
```

Then via REST API:

```python
import requests

CONFLUENCE_URL = "https://company.atlassian.net/wiki"
AUTH = ("email@company.com", "api-token")

def create_confluence_page(title: str, html_body: str,
                           space_key: str, parent_id: int = None):
    payload = {
        "type": "page",
        "title": title,
        "space": {"key": space_key},
        "body": {
            "storage": {
                "value": html_body,
                "representation": "storage"
            }
        }
    }
    if parent_id:
        payload["ancestors"] = [{"id": parent_id}]

    requests.post(f"{CONFLUENCE_URL}/rest/api/content",
        auth=AUTH, json=payload)
```

### Post-Migration Validation

Migration without validation is where things quietly break. Checklist:

```
POST-MIGRATION VALIDATION:
□ Document count in wiki = planned count
□ All cross-references point to existing pages
□ Images render correctly (common migration issue)
□ Code blocks preserve formatting and syntax highlighting
□ Navigation hierarchy matches the taxonomy
□ Search finds documents by key terms
□ [NEEDS CLARIFICATION] markers collected into a task list
□ [CONFLICT] markers assigned to owners
□ Access permissions configured per section
```

Link validation script:

```python
import re
from pathlib import Path

def validate_internal_links(docs_dir: Path) -> list[str]:
    """Find broken internal links."""
    broken = []
    all_slugs = {f.stem for f in docs_dir.glob("**/*.md")}

    for md_file in docs_dir.glob("**/*.md"):
        content = md_file.read_text()
        links = re.findall(r'\[.*?\]\((?!http)(.*?)\)', content)
        for link in links:
            slug = link.strip("/").split("/")[-1]
            if slug not in all_slugs:
                broken.append(f"{md_file.name} → {link}")

    return broken
```

## Staying Current: Process After Migration

Migration isn't the finish line. Without a maintenance process, the knowledge base degrades right back to where it started. Three mechanisms that actually stick:

**Quarterly review.** Every document carries a next-review date in its metadata. An automated reminder goes to the owner a week before. The LLM assists the review by comparing the document against the current state of code, configs, and APIs.

**Freshness score.** Calculated automatically from: last edit date, view frequency, and inbound link count from other pages. Low-scoring documents enter the review queue.

**Docs-as-code.** Documentation lives in the same repo as the code. When code changes affect a documented process, a task to update the docs is created automatically. CI checks that PRs touching an API include updates to the corresponding documentation.

Prompt for automated review:

```
TASK: Compare this document against the current state
of the codebase/system and identify outdated sections.

DOCUMENT (from wiki):
{document_content}

CURRENT STATE (from code/config):
{relevant_code_or_config}

OUTPUT:
1. Sections that are still accurate (no changes needed)
2. Sections that need updating (with specific diffs)
3. Sections that reference removed/deprecated features
4. New features/changes not yet documented
```

## Transformation Metrics: A Worked Example

What follows isn't a report from a specific project — it's a worked example on a typical 300+ document base, showing what the pipeline's output can look like given the 312-document input and the Audit-stage breakdown above.

```
BEFORE:
- 312 documents, 46% outdated
- Average time to find information: 12 minutes
- 60% of documents without an owner
- 0 cross-references

AFTER:
- 218 documents (89 kept, 104 updated, 20 from merging 47, 5 created)
- 72 archived/deleted
- Average time to find information: 2 minutes
- 100% of documents with an owner
- 340+ cross-references

COST:
- LLM API (audit + restructure + merge): ~$15–25
- Human time: ~20 hours (vs ~120 for manual work)
- Calendar time: 2 weeks (vs 2–3 months)
```

The time ratio in this example is 5–6x. But the real win isn't the saved hours — it's that the project actually gets done. Manually migrating 300 documents is exactly the kind of task that gets abandoned at the halfway mark. An LLM-assisted pipeline is the kind that finishes in two weeks.

## Integration with AI Agent Context

A structured wiki isn't just for humans. It feeds context to AI agents. As the [context engineering guide](/blog/context-engineering-guide/) explains: an LLM's answer quality is determined by the quality of its context, not the prompts.

Concrete scenarios:

**RAG over the wiki.** Outline and Notion both expose full-text search APIs. An AI assistant in Slack searches the wiki and answers team questions with links to specific documents.

**MCP server for the wiki.** Outline has an MCP server (search, get, create, update). Claude Code or any other AI agent can treat the wiki as a tool. An engineer asks "how do I deploy service X" — the agent finds the runbook and returns the current steps.

**Automatic updates.** The CI/CD pipeline checks on each deploy whether documented processes were affected. If they were, the LLM generates a documentation diff and opens a PR or task.

## Checklist: From Chaos to Structure in 2 Weeks

```
Week 1: Audit + Categorize
□ Day 1–2: Export all sources to markdown
□ Day 3: Batch audit via LLM (automated)
□ Day 4: Review audit results (manual adjustments)
□ Day 5: Generate taxonomy + gap analysis
□ Day 5: Approve structure with the team

Week 2: Restructure + Migrate
□ Day 6–7: Batch restructure (LLM) + review markers
□ Day 8: Merge duplicates + generate skeleton documents
□ Day 9: Migrate to the target platform
□ Day 10: Validation + set up maintenance process
```

The pipeline handles knowledge bases from 50 to 1,000 documents. Below 50, LLM automation isn't worth it. Above 1,000, add an embeddings-based clustering step before categorization so the LLM doesn't try to process the entire corpus in one shot.

The core principle: LLMs do the mechanical work (reading, classifying, reformatting); humans make the decisions (what to keep, what structure to use, who owns what). Each side does what it's actually good at.

---

*Need help restructuring your team's knowledge base with AI? I help startups build AI products and automate processes — [belov.works](https://belov.works).*

## FAQ

**What happens to documents that reference confidential business information — can they be sent to the LLM for auditing?**
This is the most common blocker for teams considering LLM-assisted audits. Two options: run the audit on-premise using a self-hosted model (Llama 3, Mistral) via Ollama or a private API endpoint, which eliminates data-sharing concerns entirely. Alternatively, strip or pseudonymize sensitive fields (customer names, financial figures, internal project codenames) before sending to a cloud API — the structural and linguistic analysis the LLM performs doesn't require the specific values, only the document's content patterns.

**How do you handle documentation that exists only in video form — Loom recordings, meeting recordings, internal talks?**
Transcribe first, then treat the transcript as a document. Whisper (OpenAI, open-source) handles this well at $0.006/minute. A 30-minute Loom generates a ~5,000-word transcript that feeds directly into the audit prompt. The main challenge is segmentation: a recording of a 3-hour onboarding session should be split into topical chunks before auditing — otherwise the LLM struggles to produce useful `recommendation` and `audience` classifications.

**What's the minimum team size where this pipeline makes economic sense?**
The article's threshold of 50 documents is about pipeline complexity, not team size. A 5-person team with 60 documents accumulated over 2 years will benefit from the audit stage alone — the AI classification takes 20 minutes and $1, versus 4–6 hours of manual triage. The full four-stage pipeline (including migration scripting) is cost-effective from around 100 documents. Below that, a manual sort-and-move approach with the restructuring prompt applied selectively to the worst documents is more practical.
