Back to list
codename-co

knowledge-base-management

by codename-co

Delegate complex tasks to AI teams with this browser-based orchestration platform that reproduces real organizational methodologies.

4🍴 0📅 Jan 24, 2026

SKILL.md


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

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

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:

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

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

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

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)

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:

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:

// 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

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

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' })
  })
})

Score

Total Score

75/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

+10
説明文

100文字以上の説明がある

+10
人気

GitHub Stars 100以上

0/15
最近の活動

1ヶ月以内に更新

+10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

+5

Reviews

💬

Reviews coming soon