Posted on
Artificial Intelligence

Artificial Intelligence Enterprise Documentation

Author
  • User
    linuxbash
    Posts by this author
    Posts by this author

AI-Ready Enterprise Documentation on Linux: A Bash-First Playbook

If your organization’s documentation is scattered across wikis, Markdown repos, PDFs, and tickets, your teams are probably wasting time searching, duplicating work, and missing critical context. The promise of “AI for documentation” sounds great—but where do you actually start, and how do you do it using the Linux command line you already trust?

This article shows you how to turn your existing docs into an AI-ready, queryable knowledge base using Linux tools and a small amount of Python. You’ll get a practical, Bash-first workflow that:

  • Normalizes docs from multiple sources

  • Builds a searchable embeddings index locally

  • Answers questions with Retrieval-Augmented Generation (RAG) via a local or remote LLM

  • Adds governance (style, links, spellcheck) to keep docs high-quality

We’ll cover installation on apt, dnf, and zypper systems, and provide ready-to-run scripts.

Why AI for Enterprise Docs Is Worth Your Time

  • Findability beats volume: If your best docs are buried, they don’t exist for the people who need them. AI-powered search (with retrieval) lifts the right snippet at the right time.

  • Continuity and onboarding: New engineers onboard faster when they can ask, “How do we deploy service X?” and get a precise, linked answer from your actual runbooks and RFCs.

  • Safety and governance: A local, Linux-native pipeline preserves control. You choose what’s indexed and which model runs—on-prem or in the cloud.

  • Compounding returns: The same normalized corpus feeds search, chatbots, summarization, and QA, not just one team or one use case.

What You’ll Build

  • A normalized plaintext corpus of your docs

  • An embeddings index (FAISS) for fast semantic search

  • A Bash-friendly CLI that answers questions using your docs (RAG)

  • Linting and link checking so your AI isn’t trained on bad inputs

Prerequisites: Install the Basics

Install core tools on your distro. If a package isn’t available, see the pip fallback notes.

Debian/Ubuntu (apt):

sudo apt update
sudo apt install -y git python3 python3-venv python3-pip pandoc ripgrep jq curl make graphviz codespell linkchecker

Fedora/RHEL/CentOS (dnf):

sudo dnf install -y git python3 python3-pip pandoc ripgrep jq curl make graphviz codespell linkchecker
# If `python3 -m venv` fails, also install:
# sudo dnf install -y python3-virtualenv

openSUSE Leap/Tumbleweed (zypper):

sudo zypper install -y git python3 python3-pip pandoc ripgrep jq curl make graphviz codespell linkchecker
# If `python3 -m venv` fails, also install:
# sudo zypper install -y python3-virtualenv

If codespell or linkchecker are unavailable in your repos:

python3 -m pip install --user codespell linkchecker

Optional: Local LLM runtime (Ollama) for private, on-prem inference:

curl -fsSL https://ollama.com/install.sh | sh
# After install:
# ollama pull llama3.1

Alternatively, you can use a hosted API (e.g., OpenAI) from the CLI. Keep compliance in mind and never send sensitive content to third-party APIs without approval.

Step 1: Normalize and Stage Your Docs

Choose a root directory with your documentation sources:

mkdir -p ~/ai-docs/{docs,build/plain,build/index}
cd ~/ai-docs

Put your Markdown, reStructuredText, and text files under docs/. Use Pandoc to convert everything to clean plaintext for indexing:

find docs -type f \( -name "*.md" -o -name "*.rst" -o -name "*.txt" \) -print0 | while IFS= read -r -d '' f; do
  rel="${f#docs/}"
  out="build/plain/${rel%.*}.txt"
  mkdir -p "$(dirname "$out")"
  pandoc -f markdown -t plain "$f" -o "$out" 2>/dev/null || cp "$f" "$out"
done

Quick sanity checks:

  • Count files:
find build/plain -type f | wc -l
  • Find oversized files (may need chunking tweaks later):
find build/plain -type f -size +1M -print

Step 2: Create an Embeddings Index (FAISS)

Set up a Python virtual environment and install dependencies:

python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install --upgrade pip
pip install sentence-transformers faiss-cpu pandas tqdm pyyaml

Create scripts/build_index.py:

#!/usr/bin/env python3
import os, json, argparse, pathlib
from tqdm import tqdm
import numpy as np
import faiss
from sentence_transformers import SentenceTransformer

def read_text(p):
    with open(p, "r", encoding="utf-8", errors="ignore") as f:
        return f.read()

def chunk(text, max_words=200):
    # Simple chunker by paragraph groups
    paras = [p.strip() for p in text.split("\n\n") if p.strip()]
    chunks, cur = [], []
    count = 0
    for p in paras:
        w = len(p.split())
        if count + w > max_words and cur:
            chunks.append("\n\n".join(cur))
            cur, count = [], 0
        cur.append(p)
        count += w
    if cur:
        chunks.append("\n\n".join(cur))
    return chunks

def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--plain-dir", default="build/plain")
    ap.add_argument("--index-dir", default="build/index")
    ap.add_argument("--model", default="sentence-transformers/all-MiniLM-L6-v2")
    ap.add_argument("--k", type=int, default=5)
    args = ap.parse_args()

    pathlib.Path(args.index_dir).mkdir(parents=True, exist_ok=True)
    model = SentenceTransformer(args.model)

    files = []
    for root, _, fns in os.walk(args.plain_dir):
        for fn in fns:
            if fn.endswith(".txt"):
                files.append(os.path.join(root, fn))

    texts, meta = [], []
    for fp in tqdm(files, desc="Reading and chunking"):
        rel = os.path.relpath(fp, args.plain_dir)
        t = read_text(fp)
        for i, c in enumerate(chunk(t, max_words=220)):
            texts.append(c)
            meta.append({"source": rel, "chunk_id": i})

    # Compute embeddings
    emb = model.encode(texts, show_progress_bar=True, batch_size=64, normalize_embeddings=True)
    emb = np.array(emb).astype("float32")
    index = faiss.IndexFlatIP(emb.shape[1])  # cosine sim with normalized vectors
    index.add(emb)

    faiss.write_index(index, os.path.join(args.index_dir, "docs.faiss"))
    with open(os.path.join(args.index_dir, "meta.jsonl"), "w", encoding="utf-8") as f:
        for m in meta:
            f.write(json.dumps(m) + "\n")
    with open(os.path.join(args.index_dir, "texts.jsonl"), "w", encoding="utf-8") as f:
        for t in texts:
            f.write(json.dumps({"text": t}) + "\n")

    print(f"Indexed {len(texts)} chunks from {len(files)} files.")

if __name__ == "__main__":
    main()

Run the index build:

chmod +x scripts/build_index.py
./scripts/build_index.py --plain-dir build/plain --index-dir build/index

Step 3: Ask Questions via RAG (Local or Remote LLM)

We’ll build a small retriever and a Bash-friendly CLI that: 1) Finds the top-k relevant chunks 2) Assembles a grounded prompt 3) Calls your LLM (local via Ollama or remote via API)

Create scripts/retrieve.py:

#!/usr/bin/env python3
import argparse, json, os
import numpy as np
import faiss
from sentence_transformers import SentenceTransformer

def read_jsonl(p):
    with open(p, "r", encoding="utf-8") as f:
        return [json.loads(l) for l in f]

def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--index-dir", default="build/index")
    ap.add_argument("--model", default="sentence-transformers/all-MiniLM-L6-v2")
    ap.add_argument("--query", required=True)
    ap.add_argument("--k", type=int, default=5)
    args = ap.parse_args()

    index = faiss.read_index(os.path.join(args.index_dir, "docs.faiss"))
    metas = read_jsonl(os.path.join(args.index_dir, "meta.jsonl"))
    texts = [j["text"] for j in read_jsonl(os.path.join(args.index_dir, "texts.jsonl"))]

    st = SentenceTransformer(args.model)
    q_emb = st.encode([args.query], normalize_embeddings=True).astype("float32")
    D, I = index.search(q_emb, args.k)

    results = []
    for i, score in zip(I[0], D[0]):
        if i == -1:
            continue
        results.append({
            "score": float(score),
            "source": metas[i]["source"],
            "chunk_id": metas[i]["chunk_id"],
            "text": texts[i],
        })
    print(json.dumps(results, indent=2))

if __name__ == "__main__":
    main()

Now add a simple Bash CLI rag.sh that supports Ollama by default. It retrieves the context and sends it to an LLM:

#!/usr/bin/env bash
set -euo pipefail

Q="${*:-}"
if [[ -z "$Q" ]]; then
  echo "Usage: $0 <your question>" >&2
  exit 1
fi

RES=$(./scripts/retrieve.py --query "$Q" --k 5)
CONTEXT=$(echo "$RES" | jq -r '.[].text' | awk 'BEGIN{ORS="\n\n"} {print}')
SOURCES=$(echo "$RES" | jq -r '.[].source' | sort -u | awk '{print "- " $0}')

PROMPT="You are a helpful assistant. Use the provided enterprise documentation only.
If the answer is not in the context, say you do not know.

Question:
$Q

Context:
$CONTEXT

Answer with citations by listing the file paths you used."

# Option A: Local model via Ollama (default)
if command -v ollama >/dev/null 2>&1; then
  curl -s http://localhost:11434/api/generate -d "{
    \"model\": \"llama3.1\",
    \"prompt\": $(jq -Rs . <<< \"$PROMPT\")
  }" | jq -r '.response'
  echo -e "\n\nSources:\n$SOURCES"
  exit 0
fi

# Option B: Remote API (example with OpenAI; requires OPENAI_API_KEY env)
if [[ -n "${OPENAI_API_KEY:-}" ]]; then
  curl -s https://api.openai.com/v1/chat/completions \
    -H "Authorization: Bearer $OPENAI_API_KEY" \
    -H "Content-Type: application/json" \
    -d "{
      \"model\": \"gpt-4o-mini\",
      \"messages\": [
        {\"role\": \"system\", \"content\": \"You are a helpful assistant. Use the provided enterprise documentation only.\"},
        {\"role\": \"user\", \"content\": $(jq -Rs . <<< \"$PROMPT\")}
      ],
      \"temperature\": 0.2
    }" | jq -r '.choices[0].message.content'
  echo -e "\n\nSources:\n$SOURCES"
  exit 0
fi

echo "No LLM configured. Install Ollama or export OPENAI_API_KEY." >&2
exit 1

Make it executable:

chmod +x scripts/retrieve.py rag.sh

Try it out:

./rag.sh "How do we rotate API keys for service X?"

Tip: Tune chunk size, k, and model to balance precision/recall and speed.

Step 4: Governance: Lint, Spellcheck, and Link Health

Good inputs = good AI outputs. Add fast checks:

  • Spellcheck:
codespell -q 3 -S "build,venv,.venv,*.png,*.jpg"
  • Link checking (on static Markdown):
linkchecker --ignore-url "mailto:" docs/
  • Find TODOs and large files:
rg -n "TODO|FIXME" docs/
find docs -type f -size +2M -print
  • Diagram generation (Graphviz) usage example in docs CI:
dot -Tpng diagrams/arch.dot -o build/diagrams/arch.png

Optional: Auto-format Markdown consistently (pip install if needed):

python3 -m pip install --user mdformat mdformat-gfm
mdformat docs/

Step 5: Automate with a Makefile

Create a Makefile to make routine tasks one-liners:

SHELL := /bin/bash
VENV := .venv
PY := $(VENV)/bin/python

.PHONY: venv normalize index qa lint links clean

venv:
    python3 -m venv $(VENV); \
    source $(VENV)/bin/activate && pip install -U pip && \
    pip install sentence-transformers faiss-cpu pandas tqdm pyyaml

normalize:
    mkdir -p build/plain
    find docs -type f \( -name "*.md" -o -name "*.rst" -o -name "*.txt" \) -print0 | \
    while IFS= read -r -d '' f; do \
      rel="$${f#docs/}"; out="build/plain/$${rel%.*}.txt"; \
      mkdir -p "$$(dirname "$$out")"; \
      pandoc -f markdown -t plain "$$f" -o "$$out" 2>/dev/null || cp "$$f" "$$out"; \
    done

index: venv
    ./scripts/build_index.py --plain-dir build/plain --index-dir build/index

qa:
    ./rag.sh "Summarize our incident response process and cite sources."

lint:
    codespell -q 3 -S "build,venv,.venv,*.png,*.jpg" || true

links:
    linkchecker --ignore-url "mailto:" docs/ || true

clean:
    rm -rf build/index build/plain

Now you can run:

make normalize
make index
make qa
make lint
make links

Real-World Patterns That Work

  • Platform teams: Point this workflow at runbooks, on-call docs, and RFCs. Engineers stop guessing—answers become repeatable and sourced.

  • Customer support: Index troubleshooting guides and known issues. New tickets can be triaged with suggested answers and doc links.

  • Security and compliance: Keep embeddings local; add a pre-index redaction step for PII/secrets; log sources for every answer.

Common Pitfalls (and Fixes)

  • Stale or noisy docs: Add link and spell checks to CI. Fail the build on broken links in critical paths.

  • Embeddings mismatch: Use domain-tuned models if needed (swap the SentenceTransformers model) and test recall with a curated query set.

  • Over-long chunks or too many: Start with 150–250 words per chunk and k=5. Adjust based on answer relevance.

  • Governance gaps: Track provenance. Always print file sources with answers—developers must verify before acting.

Conclusion and Call to Action

You don’t need a monolithic “AI platform” to boost doc productivity. With a handful of Linux tools, a slim Python indexer, and a local or remote LLM, you can ship an AI-ready documentation stack that:

  • Normalizes and indexes your knowledge

  • Answers questions with citations

  • Enforces quality through linting and link checks

Your next steps: 1) Bootstrap the repo structure and install dependencies using apt/dnf/zypper. 2) Normalize and index a subset of your most valuable docs. 3) Wire up the RAG CLI and validate with 10–20 real queries from your team. 4) Add Makefile targets to your CI so the index stays fresh and your docs stay clean.

When you’re ready, expand coverage, tune models, and add guardrails. The Bash-friendly foundation you’ve built will scale with your enterprise needs—securely, transparently, and under your control.