# Knowledge Base Management

> Guide for working with the Knowledge Base system in DEVS. Use this when asked to add knowledge management features, file handling, or document processing.

- Skill: `codename-co/knowledge-base-management` (Agent Skill)
- Install (CLI): `npx skillmds@latest add codename-co/knowledge-base-management`
- Raw SKILL.md: https://api.skillmd.com/api/skills/codename-co/knowledge-base-management/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Research & Search
- Author: codename-co (https://skillmd.com/u/codename-co)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/codename-co/knowledge-base-management

---


# Knowledge Base Management for DEVS

The Knowledge Base system allows users to store and manage files, documents, and other knowledge items that agents can reference during conversations.

## Core Concepts

### KnowledgeItem Interface

```typescript
interface KnowledgeItem {
  id: string
  name: string
  type: 'file' | 'folder'
  fileType?: 'document' | 'image' | 'text'
  content?: string // Text content or base64 for files
  contentHash?: string // SHA-256 hash for deduplication
  mimeType?: string
  size?: number
  path: string // Virtual path in knowledge base
  parentId?: string // Parent folder ID
  lastModified: Date
  createdAt: Date
  tags?: string[]
  description?: string
  syncSource?: 'manual' | 'filesystem_api' | 'connector'
  fileSystemHandle?: string // For File System API sync
  watchId?: string
  lastSyncCheck?: Date
}
```

## File Type Detection

Supported file types in `src/lib/knowledge-utils.ts`:

```typescript
const TEXT_EXTENSIONS = [
  '.txt',
  '.md',
  '.js',
  '.ts',
  '.jsx',
  '.tsx',
  '.css',
  '.html',
  '.xml',
  '.csv',
  '.yaml',
  '.yml',
  '.log',
]

const IMAGE_EXTENSIONS = [
  '.jpg',
  '.jpeg',
  '.png',
  '.gif',
  '.bmp',
  '.webp',
  '.svg',
  '.ico',
  '.tiff',
  '.tif',
]

const DOCUMENT_EXTENSIONS = [
  '.pdf',
  '.doc',
  '.docx',
  '.xls',
  '.xlsx',
  '.ppt',
  '.pptx',
  '.rtf',
  '.epub',
]

function getFileType(
  filename: string,
): 'text' | 'image' | 'document' | 'unknown' {
  const ext = filename.toLowerCase().split('.').pop()
  if (TEXT_EXTENSIONS.includes(`.${ext}`)) return 'text'
  if (IMAGE_EXTENSIONS.includes(`.${ext}`)) return 'image'
  if (DOCUMENT_EXTENSIONS.includes(`.${ext}`)) return 'document'
  return 'unknown'
}
```

## Database Operations

```typescript
import { db } from '@/lib/db'

// Create knowledge item
async function createKnowledgeItem(
  item: Omit<KnowledgeItem, 'id'>,
): Promise<string> {
  const id = crypto.randomUUID()
  await db.knowledge.add({ ...item, id })
  return id
}

// Get items by path
async function getItemsByPath(path: string): Promise<KnowledgeItem[]> {
  return db.knowledge.where('path').startsWith(path).toArray()
}

// Get folder contents
async function getFolderContents(folderId?: string): Promise<KnowledgeItem[]> {
  if (folderId) {
    return db.knowledge.where('parentId').equals(folderId).toArray()
  }
  return db.knowledge.filter((item) => !item.parentId).toArray()
}

// Search by content hash (deduplication)
async function findDuplicate(
  contentHash: string,
): Promise<KnowledgeItem | undefined> {
  return db.knowledge.where('contentHash').equals(contentHash).first()
}
```

## Content Hashing for Deduplication

```typescript
async function generateContentHash(
  content: string | ArrayBuffer,
): Promise<string> {
  const data =
    typeof content === 'string'
      ? new TextEncoder().encode(content)
      : new Uint8Array(content)

  const hashBuffer = await crypto.subtle.digest('SHA-256', data)
  const hashArray = Array.from(new Uint8Array(hashBuffer))
  return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('')
}
```

## File Upload Handling

```typescript
async function handleFileUpload(
  file: File,
  parentId?: string,
): Promise<KnowledgeItem> {
  const content = await readFileContent(file)
  const contentHash = await generateContentHash(content)

  // Check for duplicates
  const existing = await findDuplicate(contentHash)
  if (existing) {
    toast.info('File already exists in knowledge base')
    return existing
  }

  const item: KnowledgeItem = {
    id: crypto.randomUUID(),
    name: file.name,
    type: 'file',
    fileType: getFileType(file.name),
    content: typeof content === 'string' ? content : btoa(content),
    contentHash,
    mimeType: file.type,
    size: file.size,
    path: parentId ? `${getPath(parentId)}/${file.name}` : `/${file.name}`,
    parentId,
    lastModified: new Date(file.lastModified),
    createdAt: new Date(),
    syncSource: 'manual',
  }

  await db.knowledge.add(item)
  return item
}

async function readFileContent(file: File): Promise<string | ArrayBuffer> {
  return new Promise((resolve, reject) => {
    const reader = new FileReader()

    if (file.type.startsWith('text/') || getFileType(file.name) === 'text') {
      reader.onload = () => resolve(reader.result as string)
      reader.onerror = reject
      reader.readAsText(file)
    } else {
      reader.onload = () => resolve(reader.result as ArrayBuffer)
      reader.onerror = reject
      reader.readAsArrayBuffer(file)
    }
  })
}
```

## Folder Watching (File System API)

```typescript
import { db } from '@/lib/db'

async function watchFolder(handle: FileSystemDirectoryHandle): Promise<void> {
  // Request permission
  const permission = await handle.requestPermission({ mode: 'read' })
  if (permission !== 'granted') {
    throw new Error('Permission denied')
  }

  // Scan and sync folder
  await syncFolder(handle, undefined)

  // Store handle for persistence
  await db.knowledge.add({
    id: crypto.randomUUID(),
    name: handle.name,
    type: 'folder',
    path: `/${handle.name}`,
    createdAt: new Date(),
    lastModified: new Date(),
    syncSource: 'filesystem_api',
    fileSystemHandle: handle.name,
  })
}

async function syncFolder(
  handle: FileSystemDirectoryHandle,
  parentId?: string,
): Promise<void> {
  for await (const entry of handle.values()) {
    if (entry.kind === 'file') {
      const file = await entry.getFile()
      await handleFileUpload(file, parentId)
    } else if (entry.kind === 'directory') {
      // Create folder and recurse
      const folderId = await createFolder(entry.name, parentId)
      await syncFolder(entry, folderId)
    }
  }
}
```

## Context Injection for Agents

When agents need knowledge context:

```typescript
import { db } from '@/lib/db'

async function getRelevantKnowledge(
  query: string,
  agentId: string,
): Promise<KnowledgeItem[]> {
  // Get agent's assigned knowledge items
  const agentKnowledge = await getAgentKnowledge(agentId)

  // Search for relevant items
  const searchTerms = query.toLowerCase().split(' ')

  return agentKnowledge.filter((item) => {
    const searchText =
      `${item.name} ${item.description || ''} ${item.tags?.join(' ') || ''}`.toLowerCase()
    return searchTerms.some((term) => searchText.includes(term))
  })
}

function formatKnowledgeForContext(items: KnowledgeItem[]): string {
  return items
    .map((item) => {
      return `## ${item.name}
${item.description || ''}
${item.content || '[Binary content]'}
---`
    })
    .join('\n\n')
}
```

## Document Processing

For processing different document types:

```typescript
// src/lib/document-processor.ts
export async function processDocument(item: KnowledgeItem): Promise<string> {
  switch (item.fileType) {
    case 'text':
      return item.content || ''

    case 'document':
      return await extractTextFromDocument(item)

    case 'image':
      // Could use OCR or image description
      return `[Image: ${item.name}]`

    default:
      return `[Unsupported file type: ${item.mimeType}]`
  }
}
```

## Component Integration

```tsx
import { useCallback, useState } from 'react'
import { Button, Card, Progress } from '@heroui/react'
import { Icon } from '@/components/Icon'
import { toast } from '@/lib/toast'

function KnowledgeUploader() {
  const [isUploading, setIsUploading] = useState(false)
  const [progress, setProgress] = useState(0)

  const handleDrop = useCallback(async (e: React.DragEvent) => {
    e.preventDefault()
    const files = Array.from(e.dataTransfer.files)

    setIsUploading(true)
    setProgress(0)

    for (let i = 0; i < files.length; i++) {
      await handleFileUpload(files[i])
      setProgress(((i + 1) / files.length) * 100)
    }

    setIsUploading(false)
    toast.success(`Uploaded ${files.length} files`)
  }, [])

  return (
    <Card
      onDrop={handleDrop}
      onDragOver={(e) => e.preventDefault()}
      className="border-dashed border-2 p-8 text-center"
    >
      <Icon name="Upload" size={48} className="mx-auto mb-4" />
      <p>Drop files here to upload</p>
      {isUploading && <Progress value={progress} className="mt-4" />}
    </Card>
  )
}
```

## Testing

```typescript
import { describe, it, expect, beforeEach, vi } from 'vitest'
import { db } from '@/lib/db'

vi.mock('@/lib/db')

describe('Knowledge Base', () => {
  beforeEach(() => {
    vi.clearAllMocks()
  })

  it('should detect file types correctly', () => {
    expect(getFileType('document.md')).toBe('text')
    expect(getFileType('image.png')).toBe('image')
    expect(getFileType('file.pdf')).toBe('document')
    expect(getFileType('data.bin')).toBe('unknown')
  })

  it('should generate consistent content hashes', async () => {
    const content = 'test content'
    const hash1 = await generateContentHash(content)
    const hash2 = await generateContentHash(content)
    expect(hash1).toBe(hash2)
  })

  it('should detect duplicates', async () => {
    const hash = 'abc123'
    vi.mocked(db.knowledge.where).mockReturnValue({
      equals: vi.fn().mockReturnValue({
        first: vi.fn().mockResolvedValue({ id: 'existing' }),
      }),
    } as any)

    const result = await findDuplicate(hash)
    expect(result).toEqual({ id: 'existing' })
  })
})
```

