---
name: kodadocs-docs-from-tickets
description: "Generate documentation pages from support tickets, GitHub Issues, Discussions, and Discord exports by clustering common questions and converting them into organized doc site content."
---

# Docs from Support Tickets

## Overview

This skill converts support tickets and community questions into structured documentation pages. It ingests data from multiple sources (GitHub Issues, GitHub Discussions, Discord exports, and plain text/CSV logs), clusters similar questions by topic, identifies the most frequently asked questions, and generates new doc pages that integrate into your KodaDocs site. It deduplicates against existing documentation, supports incremental updates (only new tickets since last run), and tracks which tickets have been converted to docs.

Use this skill when:
- Users repeatedly ask the same questions across support channels and you want those answers documented
- You have a backlog of GitHub Issues labeled "question" or "support" that should become help articles
- You export Discord support channels and want to mine them for FAQ content
- You want to create a "Community Questions" or "FAQ" section driven by real user questions
- You want to run periodic sweeps to keep docs in sync with actual user questions

## Prerequisites

Before running this skill, ensure the following:

1. **At least one ticket source:** GitHub Issues/Discussions (`gh` CLI authenticated), a Discord export (JSON), or a plain text/CSV support log file.
2. **KodaDocs manifest (recommended):** A `.kodadocs/run_manifest.json` from a previous pipeline run for doc site integration and deduplication.
3. **Project codebase (recommended):** Source code provides context for generating accurate answers.
4. **KodaDocs MCP tools available:** The `mcp__kodadocs__*` tools must be accessible.

## Workflow

### Step 1: Detect and Configure Ticket Sources

Check which sources are available:
```bash
gh auth status 2>&1
git remote -v 2>&1
```

| Source | Detection | Default |
|--------|----------|---------|
| GitHub Issues | `gh auth status` + GitHub remote | Enabled if available |
| GitHub Discussions | Same; check if Discussions enabled on repo | Enabled if available |
| Discord export | User provides JSON file path | Disabled unless provided |
| Plain text/CSV | User provides file path | Disabled unless provided |

**Configuration parameters:** `github_labels` (default: `["question", "support", "help wanted", "faq"]`), `github_issue_limit` (default: 200), `discussion_categories` (default: `["Q&A", "General", "Help"]`), `min_cluster_size` (default: 2), `top_n` (default: 20), `output_section_name` (default: "Community Questions"), `since_date` (default: last run date or all time).

### Step 2: Ingest Tickets from All Sources

Normalize all tickets into a common internal format:

```json
{
  "id": "<source>-<unique-id>",
  "source": "github-issue | github-discussion | discord | text-log",
  "title": "<question summary>",
  "body": "<full question text>",
  "answer": "<resolved answer or null>",
  "answer_author": "<who answered or null>",
  "labels": [],
  "url": "<link to original or null>",
  "created_at": "<ISO timestamp>",
  "resolved": true | false,
  "ticket_ref": "<original ID>"
}
```

#### Source A: GitHub Issues

```bash
gh issue list --state open --label "question" --limit 200 --json number,title,body,labels,comments,createdAt,url,author
gh issue list --state closed --label "question" --limit 200 --json number,title,body,labels,comments,createdAt,closedAt,url,author
```

Repeat for each custom label. Also fetch unlabeled Issues:
```bash
gh issue list --state all --limit 200 --json number,title,body,labels,comments,createdAt,closedAt,url,author
```

**Include unlabeled Issues that look like questions:**
- Title contains a question mark (`?`)
- Title starts with question words: How, Why, Can, Does, Is, What, Where, When, Should
- Body contains help-seeking phrases: "how do I", "is it possible", "does this support", "I can't", "not working", "getting an error", "help me", "stuck on"

**Exclude Issues that match:**
- Labels: `bug` (unless also labeled `question`), `feature request`, `enhancement`, `wontfix`, `invalid`
- Label `duplicate` or "duplicate of" in comments
- Bot-generated Issues (author login contains `[bot]` or `dependabot`)
- Issues with zero comments and no resolution (unanswerable)

**Extract answer from each qualifying Issue:**
1. Scan comments in chronological order
2. Prefer the first comment from a repo collaborator or maintainer (check `authorAssociation`)
3. If no collaborator comment, use the closing comment
4. If the Issue was closed with a linked PR, note the PR number for context
5. If no clear answer exists, set `answer` to `null` and `resolved` to `false`

Normalize: `id` = `github-issue-<number>`, `ticket_ref` = `#<number>`.

#### Source B: GitHub Discussions

```bash
gh repo view --json owner,name -q '.owner.login + "/" + .name'
gh api graphql -f query='{ repository(owner:"<owner>",name:"<repo>") { discussions(first:100,orderBy:{field:CREATED_AT,direction:DESC}) { nodes { number title body createdAt url answer { body author { login } } labels(first:10) { nodes { name } } category { name } } } } }'
```

Filter to configured categories. Prioritize answered Discussions. Use accepted answer or most-upvoted reply. Normalize as `github-discussion-<number>`.

#### Source C: Discord Export (JSON)

Expected JSON structure (from DiscordChatExporter or equivalent):
```json
{
  "messages": [
    {
      "id": "123456789",
      "content": "How do I configure the API key?",
      "author": { "name": "user123", "isBot": false },
      "timestamp": "2026-02-15T10:30:00Z",
      "type": "Default",
      "reference": null
    }
  ]
}
```

**Identify question messages:**
- Contains a question mark
- Starts with question words (How, Why, What, Can, Does, Is, Where, When)
- Is a thread starter or first message in a help channel

**Find answer messages for each question:**
- If the question is in a thread, use the thread replies
- If the question has a `reference` (reply-to), follow the reply chain
- If a reply has a checkmark or thumbs-up reaction, prefer that reply
- Fallback: take the next 1-3 messages from a non-bot user

**Filter noise:**
- Skip bot messages (unless they are useful bot responses like FAQ bots)
- Skip messages shorter than 15 characters
- Skip messages that are only links, emojis, or greetings ("hi", "thanks", "ok")

**Handle self-resolutions:**
- If the questioner says "figured it out", "solved it", "nvm, I found it", look for their follow-up explanation
- If they explain the solution, capture that as the answer
- If they do not explain, mark `answer` as `null`

Normalize: `id` = `discord-<message-id>`, `title` = first sentence up to 100 chars.

#### Source D: Plain Text / CSV

**CSV:** Map columns case-insensitively (`question`/`title`/`subject` for question, `answer`/`response`/`resolution` for answer, plus optional `date`, `category`, `id`, `status`). Handle TSV with tab delimiter.

**Plain text:** Parse `Q:` / `A:` pairs, numbered entries, or dash-prefixed entries. Normalize as `textlog-<line-number>` or `textlog-<ticket_id>`.

### Step 3: Apply Incremental Filtering

```
Call mcp__kodadocs__load_manifest with:
  project_path: "<project-root>"
```

Extract `tickets_from_docs.tickets_processed` from the manifest. Remove tickets whose `id` is already processed. If `since_date` is set, remove tickets created before that date. If zero new tickets remain, inform the user and exit early.

### Step 4: Cluster Similar Questions

**Keyword extraction:** Lowercase text, remove stop words and punctuation, extract tokens and meaningful bigrams ("api key", "environment variable", "rate limit", "custom domain").

**Grouping algorithm:**
1. Build keyword frequency map across all tickets
2. Identify dominant keywords (appearing in 3+ tickets)
3. Create initial clusters by dominant keyword (tickets can belong to multiple)
4. Merge clusters sharing >60% of tickets
5. Place unclustered tickets in "General"; subdivide if General exceeds 30%

**Map keywords to topic labels:** "install/setup" -> "Installation & Setup", "api/key/token/auth" -> "Authentication & API Keys", "deploy/build/hosting" -> "Deployment", "error/fail/crash" -> "Troubleshooting", "config/env/settings" -> "Configuration", "performance/slow" -> "Performance", "upgrade/migrate" -> "Upgrades & Migration", "permission/access/role" -> "Permissions & Access Control", "integration/plugin/webhook" -> "Integrations", "data/database/storage" -> "Data Management".

Each cluster outputs: `topic`, `tickets`, `frequency`, `representative_question`.

### Step 5: Identify Top N Frequently Asked Questions

**Rank clusters using weighted scoring:**

| Signal | Weight | Description |
|--------|--------|-------------|
| Cluster size | 40% | More tickets = more frequent |
| Recency | 20% | Recent tickets are more relevant |
| Resolution rate | 20% | Answered clusters are easier to document |
| Source diversity | 10% | Cross-channel questions are more universal |
| Maintainer answered | 10% | Highest trust answers |

**Scoring formula:**
```
score = (cluster_size / max_cluster_size) * 0.4
      + (recent_ticket_ratio) * 0.2
      + (resolved_ticket_ratio) * 0.2
      + (unique_sources / total_sources) * 0.1
      + (maintainer_answered_ratio) * 0.1
```

Where `recent_ticket_ratio` is the fraction of tickets created in the last 90 days, `resolved_ticket_ratio` is the fraction with a non-null answer, and `unique_sources` is the number of distinct source types represented.

Select top N clusters (default 20). For each, choose the best answer by authority (maintainer > collaborator > community > self-resolution), recency, and code analysis confirmation. Flag clusters with no answers as `needs_answer: true`.

### Step 6: Deduplicate Against Existing Documentation

```
Call mcp__kodadocs__load_manifest with:
  project_path: "<project-root>"
```

Compare each cluster against existing `articles` in the manifest:

1. **Title match:** Check if any existing article title contains the cluster's dominant keywords
2. **Content match:** Check if any existing article body addresses the cluster's representative question
3. **Keyword overlap:** Calculate the fraction of cluster keywords that appear in each article

**Deduplication thresholds:**

| Overlap | Action |
|---------|--------|
| High (>70% keyword overlap or exact title match) | SKIP -- already documented. Log: "Cluster '<topic>' covered by '<title>'" |
| Medium (40-70% keyword overlap) | LINK -- brief entry referencing existing article |
| Low (<40% keyword overlap) | CREATE -- full new article |

Present the deduplication plan to the user before proceeding:
```
Deduplication results:
  - 3 clusters already documented (skipping)
  - 2 clusters partially covered (will link to existing articles)
  - 15 clusters need new documentation (will generate)
```

### Step 7: Analyze Codebase for Answer Context

```
Call mcp__kodadocs__analyze_codebase with:
  project_path: "<project-root>"
```

Use `code_chunks`, `error_patterns`, and `data_models` to enrich answers. For `needs_answer` clusters, synthesize answers from code and mark as "[Generated from code analysis]".

### Step 8: Generate Documentation Pages

**Full articles (CREATE clusters):**
```markdown
# <Cluster Topic Title>

<Summary paragraph.>

## <Question 1>
<Direct answer with code examples.>
Source: [#45](url), [Discussion #12](url)

---

## <Question 2>
<Answer>

---

## Related Resources
- [<Existing Article>](<link>) - <description>
```

**Brief entries (LINK clusters):**
```markdown
## <Question>
This topic is covered in our documentation. See [<Article>](<link>) for the full guide.
**Quick answer:** <1-2 sentence summary>
```

**Content generation rules:**
1. **Write the best answer, not just the ticket answer.** If the ticket answer is unclear or incomplete, rewrite it for clarity while preserving accuracy.
2. **Include code examples** when the question involves configuration, CLI commands, or API usage.
3. **Attribute sources** at the bottom of each answer: "Source: [#45](url), [Discussion #12](url)"
4. **Cross-link between clusters.** If two clusters are related, add "See also: [other topic](link)" references.
5. **Maintain consistent tone.** Write as if explaining to a capable developer who is new to the project. Be direct -- answer the question in the first sentence, then provide supporting details.
6. **Do not include internal jargon** from Discord/Issues without explanation. Replace channel-specific terms with standard terminology.

**Section index page:**
```markdown
# Community Questions
Answers to commonly asked questions from our community.

| Topic | Questions | Last Updated |
|-------|-----------|-------------|
| [Authentication & API Keys](./auth-api-keys) | 5 | 2026-03-02 |
```

### Step 9: Integrate with Doc Site

Add the generated pages to the KodaDocs documentation site.

**Create article objects for each generated page:**
```json
[
  {
    "title": "Community Questions",
    "slug": "community-questions",
    "content": "<section index page content>",
    "order": 800
  },
  {
    "title": "Authentication & API Keys - FAQ",
    "slug": "community-questions-auth-api-keys",
    "content": "<auth cluster page content>",
    "order": 801
  }
]
```

**Ordering convention:** Start community question articles at `order: 800` to place them after main documentation but before changelog (999) and other appendix-style pages.

**Reassemble the site:**
```
Call mcp__kodadocs__assemble_vitepress with:
  articles: [<all existing articles from manifest> + <new community question articles>]
  screenshots_dir: "<from manifest>"
  brand_color: "<from manifest>"
  logo_path: "<from manifest>"
  output_dir: "<from manifest>"
  project_name: "<from manifest>"
  product_summary: "<from manifest>"
  discovered_routes: [<from manifest>]
```

**Optionally redeploy:**
```
Call mcp__kodadocs__deploy_site with:
  site_dir: "<output directory>"
  project_name: "<from manifest>"
  provider: "<from manifest>"
  license_key: "<from manifest or env>"
  site_slug: "<from manifest>"
```

### Step 10: Update Manifest and Tracking State

```
Call mcp__kodadocs__save_manifest with:
  project_path: "<project-root>"
  manifest: {
    <existing fields>,
    "tickets_from_docs": {
      "last_run": "<ISO timestamp>",
      "sources_used": ["github-issue", "discord"],
      "tickets_processed": ["github-issue-45", "discord-123456789"],
      "clusters_generated": 15,
      "clusters_skipped_dedup": 3,
      "articles_created": ["community-questions", "community-questions-auth-api-keys"],
      "ticket_to_article_map": { "github-issue-45": "community-questions-auth-api-keys" }
    },
    "articles": [<complete updated list>]
  }
```

The `ticket_to_article_map` enables notifying ticket authors, linking back to docs, and tracking coverage metrics.

## Decision Trees

### Source Selection

```
Is `gh` CLI authenticated?
  YES -> Does repo have GitHub remote?
           YES -> Discussions enabled? -> Ingest Issues + Discussions / Issues only
           NO  -> Skip GitHub sources
  NO  -> Skip GitHub sources
Discord export path provided?
  YES -> File exists and valid JSON? -> Add Discord / Warn and skip
  NO  -> Skip Discord
Text/CSV path provided?
  YES -> File exists? -> Detect format (CSV/TSV/plain text) and add / Warn and skip
  NO  -> Skip text-log
Any sources available?
  YES -> Proceed    NO -> Error and exit
```

### Incremental vs. Full Run

```
Manifest has `tickets_from_docs.last_run`?
  YES -> Incremental: filter out processed IDs, process only tickets after last_run
  NO  -> Full run: process all tickets
User requested full rescan?
  YES -> Override: ignore tracking, process all tickets
```

### Cluster Quality

```
1 ticket    -> Merge into nearest related cluster or "General"
2-3 tickets -> Keep only if question addresses common workflow/pain point
4-9 tickets -> Good cluster, include in top N
10+ tickets -> High priority, always include; split if subtopics are distinct
```

### Answer Quality

```
Resolved answer exists?
  YES -> From maintainer? -> High confidence, use directly
         From community with reactions? -> Medium, verify against code
         Otherwise -> Low, cross-reference with code analysis
  NO  -> Answerable from codebase? -> Generate, mark "[Generated from code analysis]"
         Answerable from docs? -> Generate, mark "[Auto-generated]"
         Neither -> Skip cluster or create stub
```

### Conflicting Answers

```
Multiple different answers in cluster?
  YES -> Contradictory? -> Prefer: maintainer > most recent > most reactions > code-confirmed
                           Note: "Behavior may vary by configuration"
         Complementary? -> Merge into comprehensive answer, credit all sources
  NO  -> Use single answer
```

## Output Format

The skill produces:

1. **Community Questions section index** - Landing page listing all FAQ topic clusters
2. **Individual topic pages** - One page per cluster with questions, answers, and source attribution
3. **Doc site integration** - Pages added to the KodaDocs VitePress site
4. **Updated manifest** - Tracking state for incremental future runs
5. **Console summary:**

```
Docs from Support Tickets - Complete:
  Sources: GitHub Issues (156 fetched, 89 qualified), Discussions (42/31),
           Discord (340 messages, 67 questions), Text log (25 entries)
  Clustering: 212 questions -> 28 clusters -> top 20 selected
  Dedup: 4 skipped, 3 linked, 13 new articles
  Output: 14 pages (1 index + 13 topics), 87 questions documented
  Next run processes tickets after 2026-03-02T14:30:00Z
```

## Error Handling

### No tickets found
All sources return zero qualifying tickets: inform user, suggest checking labels or providing export files, exit cleanly without generating empty pages.

### GitHub API rate limit
Log warning. Continue with other sources if available. If GitHub is only source, suggest waiting or exporting Issues as CSV. Do not retry automatically.

### Malformed Discord export
Log expected format, skip Discord source, continue with other sources. Do not abort the run.

### CSV parsing errors
Auto-detect encoding (UTF-8, Latin-1, UTF-16). Ask user for column mapping if headers missing. Skip malformed rows with warning, continue with valid rows.

### Codebase analysis fails
If `mcp__kodadocs__analyze_codebase` errors: continue without code context, source answers solely from tickets, mark generated answers as lower confidence.

### Manifest not found
Valid first-run state. Skip deduplication and doc site integration. Save standalone files to `docs/community-questions/`. Inform user to run full pipeline for integration.

### Too many clusters (>50)
Increase effective min_cluster_size, merge smallest clusters into neighbors, inform user.

### Duplicate detection false positives
Respect user override ("generate it anyway"). Note force-generation in manifest.

### Network issues
Retry each failed GitHub API call once. If retry fails, skip source with warning. Continue with successfully fetched data.

## Success Criteria

The run is successful when:
- [ ] At least one ticket source was successfully ingested
- [ ] Tickets were normalized into the internal format
- [ ] Incremental filtering excluded previously processed tickets (if applicable)
- [ ] Questions were clustered into meaningful topic groups
- [ ] Top N clusters were selected based on frequency and quality scoring
- [ ] Existing documentation was checked for duplicates
- [ ] Codebase was analyzed for answer context (or fallback was used)
- [ ] Documentation pages were generated with clear, developer-friendly answers and code examples
- [ ] Source attribution and cross-links to existing docs are present
- [ ] Pages were integrated into doc site (or saved standalone if no site exists)
- [ ] Manifest was updated with tracking state (processed IDs, article map)
- [ ] Console summary was printed with complete statistics
- [ ] Subsequent runs will correctly skip already-processed tickets
