Kode-cli/docs/develop/modules/context-system.md
CrazyBoyM d8f0a22233 feat: Implement intelligent completion system with advanced fuzzy matching
- Add advanced fuzzy matching with 7+ strategies (exact, prefix, substring, acronym, initials, fuzzy, Levenshtein)
- Create comprehensive database of 500+ common Unix commands for smart autocompletion
- Implement intelligent Tab completion with @ prefix injection for agents and files
- Add sophisticated input pattern recognition for commands like "dao", "gp5", "py3"
- Enhance mention system with TaskProgressMessage component for better user feedback
- Update documentation with comprehensive intelligent completion guide
- Clean up 21 temporary markdown files to maintain repository cleanliness
- Improve project structure and configuration documentation
- Optimize completion system performance with advanced caching and scoring
2025-08-22 13:07:48 +08:00

858 lines
22 KiB
Markdown

# Context System
## Overview
The Context System (`src/context.ts`) manages all contextual information about the project and environment that gets injected into AI conversations. It provides automatic context gathering, caching, and intelligent injection to improve AI response quality.
## Architecture
### Core Context Manager
```typescript
interface ContextManager {
// Context gathering
getContext(): Promise<CompleteContext>
getGitContext(): Promise<GitContext>
getProjectContext(): Promise<ProjectContext>
getSystemContext(): SystemContext
// Context files
loadContextFile(): Promise<string | null>
loadClaudeFile(): Promise<string | null>
// Context manipulation
setContext(key: string, value: any): void
removeContext(key: string): void
clearContext(): void
// Caching
invalidateCache(): void
getCacheStatus(): CacheStatus
}
```
## Context Types
### Complete Context Structure
```typescript
interface CompleteContext {
// Project information
projectName?: string
projectDescription?: string
projectType?: string
// Git information
gitStatus?: string
recentCommits?: string
currentBranch?: string
remoteUrl?: string
// Directory structure
directoryStructure?: string
importantFiles?: string[]
// Code style and patterns
codeStyle?: CodeStyle
dependencies?: Dependencies
// Documentation
contextFile?: string // AGENTS.md content
claudeFile?: string // CLAUDE.md content
readmeContent?: string // README.md content
// System information
platform?: string
cwd?: string
timestamp?: string
// Custom context
customContext?: Record<string, any>
}
```
### Git Context
```typescript
interface GitContext {
isGitRepo: boolean
branch?: string
status?: string
recentCommits?: Commit[]
modifiedFiles?: string[]
untrackedFiles?: string[]
stagedFiles?: string[]
remotes?: Remote[]
lastCommitInfo?: {
hash: string
author: string
date: string
message: string
}
}
async function getGitContext(): Promise<GitContext> {
const isGitRepo = await checkIsGitRepo()
if (!isGitRepo) {
return { isGitRepo: false }
}
const [status, branch, commits, remotes] = await Promise.all([
getGitStatus(),
getCurrentBranch(),
getRecentCommits(10),
getRemotes()
])
return {
isGitRepo: true,
branch,
status,
recentCommits: commits,
modifiedFiles: parseModifiedFiles(status),
untrackedFiles: parseUntrackedFiles(status),
stagedFiles: parseStagedFiles(status),
remotes,
lastCommitInfo: commits[0]
}
}
```
### Project Context
```typescript
interface ProjectContext {
type: ProjectType
framework?: string
language?: string
packageManager?: PackageManager
testFramework?: string
buildTool?: string
dependencies?: Record<string, string>
devDependencies?: Record<string, string>
scripts?: Record<string, string>
configuration?: ProjectConfig
}
class ProjectAnalyzer {
async analyze(): Promise<ProjectContext> {
const files = await this.discoverProjectFiles()
const type = this.detectProjectType(files)
switch (type) {
case 'node':
return this.analyzeNodeProject()
case 'python':
return this.analyzePythonProject()
case 'rust':
return this.analyzeRustProject()
case 'go':
return this.analyzeGoProject()
default:
return this.analyzeGenericProject()
}
}
private async analyzeNodeProject(): Promise<ProjectContext> {
const packageJson = await this.readPackageJson()
return {
type: 'node',
framework: this.detectFramework(packageJson),
language: this.detectLanguage(packageJson),
packageManager: this.detectPackageManager(),
testFramework: this.detectTestFramework(packageJson),
buildTool: this.detectBuildTool(packageJson),
dependencies: packageJson.dependencies,
devDependencies: packageJson.devDependencies,
scripts: packageJson.scripts,
configuration: await this.loadNodeConfig()
}
}
private detectFramework(pkg: PackageJson): string | undefined {
const deps = { ...pkg.dependencies, ...pkg.devDependencies }
if (deps['react']) return 'react'
if (deps['vue']) return 'vue'
if (deps['@angular/core']) return 'angular'
if (deps['svelte']) return 'svelte'
if (deps['next']) return 'nextjs'
if (deps['nuxt']) return 'nuxt'
if (deps['express']) return 'express'
if (deps['fastify']) return 'fastify'
if (deps['koa']) return 'koa'
if (deps['nest']) return 'nestjs'
return undefined
}
}
```
## Context Files
### AGENTS.md
```typescript
class ContextFileLoader {
private readonly CONTEXT_PATHS = [
'AGENTS.md',
'.claude/AGENTS.md',
'docs/AGENTS.md',
'.github/AGENTS.md'
]
async loadContextFile(): Promise<string | null> {
for (const path of this.CONTEXT_PATHS) {
const fullPath = join(getCwd(), path)
if (existsSync(fullPath)) {
try {
const content = await fs.readFile(fullPath, 'utf-8')
return this.processContextFile(content)
} catch (error) {
console.warn(`Failed to read ${path}:`, error)
}
}
}
return null
}
private processContextFile(content: string): string {
// Process includes
content = this.processIncludes(content)
// Process variables
content = this.processVariables(content)
// Process conditionals
content = this.processConditionals(content)
return content
}
private processIncludes(content: string): string {
const INCLUDE_REGEX = /<!-- include: (.+) -->/g
return content.replace(INCLUDE_REGEX, (match, filePath) => {
try {
const fullPath = join(getCwd(), filePath.trim())
if (existsSync(fullPath)) {
return readFileSync(fullPath, 'utf-8')
}
} catch (error) {
console.warn(`Failed to include ${filePath}:`, error)
}
return match
})
}
private processVariables(content: string): string {
const variables = {
PROJECT_NAME: this.getProjectName(),
CWD: getCwd(),
DATE: new Date().toISOString(),
GIT_BRANCH: this.getCurrentBranch(),
NODE_VERSION: process.version
}
return content.replace(/\{\{(\w+)\}\}/g, (match, key) => {
return variables[key] || match
})
}
}
```
### CLAUDE.md
```typescript
class ClaudeFileLoader {
private readonly CLAUDE_PATHS = [
'CLAUDE.md',
'.claude/CLAUDE.md'
]
private readonly GLOBAL_CLAUDE_PATH = join(homedir(), '.claude', 'CLAUDE.md')
async loadClaudeFile(): Promise<ClaudeFileContent> {
const [projectFile, globalFile] = await Promise.all([
this.loadProjectClaudeFile(),
this.loadGlobalClaudeFile()
])
return {
project: projectFile,
global: globalFile,
merged: this.mergeClaudeFiles(projectFile, globalFile)
}
}
private async loadProjectClaudeFile(): Promise<string | null> {
for (const path of this.CLAUDE_PATHS) {
const fullPath = join(getCwd(), path)
if (existsSync(fullPath)) {
return fs.readFile(fullPath, 'utf-8')
}
}
return null
}
private async loadGlobalClaudeFile(): Promise<string | null> {
if (existsSync(this.GLOBAL_CLAUDE_PATH)) {
return fs.readFile(this.GLOBAL_CLAUDE_PATH, 'utf-8')
}
return null
}
private mergeClaudeFiles(
project: string | null,
global: string | null
): string {
const parts: string[] = []
if (global) {
parts.push('# Global Instructions\n\n' + global)
}
if (project) {
parts.push('# Project Instructions\n\n' + project)
}
return parts.join('\n\n---\n\n')
}
}
```
## Directory Structure Analysis
### Directory Scanner
```typescript
class DirectoryStructureAnalyzer {
private readonly IGNORE_PATTERNS = [
'node_modules',
'.git',
'dist',
'build',
'coverage',
'.next',
'__pycache__',
'.pytest_cache',
'venv',
'.venv',
'target',
'.idea',
'.vscode'
]
private readonly MAX_DEPTH = 4
private readonly MAX_FILES = 1000
async analyze(rootPath: string = getCwd()): Promise<DirectoryStructure> {
const structure = await this.scanDirectory(rootPath, 0)
const summary = this.generateSummary(structure)
const tree = this.generateTree(structure)
return {
structure,
summary,
tree,
importantFiles: this.identifyImportantFiles(structure)
}
}
private async scanDirectory(
path: string,
depth: number
): Promise<DirectoryNode> {
if (depth >= this.MAX_DEPTH) {
return { path, type: 'directory', truncated: true }
}
const entries = await fs.readdir(path, { withFileTypes: true })
const children: DirectoryNode[] = []
let fileCount = 0
for (const entry of entries) {
if (this.shouldIgnore(entry.name)) continue
if (fileCount >= this.MAX_FILES) break
const fullPath = join(path, entry.name)
if (entry.isDirectory()) {
const child = await this.scanDirectory(fullPath, depth + 1)
children.push(child)
} else {
children.push({
path: fullPath,
name: entry.name,
type: 'file',
size: await this.getFileSize(fullPath),
extension: extname(entry.name)
})
fileCount++
}
}
return {
path,
type: 'directory',
children,
fileCount,
totalSize: await this.calculateTotalSize(children)
}
}
private generateTree(node: DirectoryNode, prefix = ''): string {
const lines: string[] = []
if (node.type === 'file') {
lines.push(prefix + node.name)
} else if (node.children) {
for (let i = 0; i < node.children.length; i++) {
const child = node.children[i]
const isLast = i === node.children.length - 1
const connector = isLast ? '└── ' : '├── '
const extension = isLast ? ' ' : '│ '
lines.push(prefix + connector + basename(child.path))
if (child.type === 'directory' && child.children) {
const subtree = this.generateTree(child, prefix + extension)
lines.push(subtree)
}
}
}
return lines.join('\n')
}
private identifyImportantFiles(structure: DirectoryNode): string[] {
const important: string[] = []
const importantPatterns = [
/^package\.json$/,
/^tsconfig\.json$/,
/^README\.md$/i,
/^CONTEXT\.md$/,
/^CLAUDE\.md$/,
/^\.env(\.example)?$/,
/^docker-compose\.yml$/,
/^Dockerfile$/,
/^requirements\.txt$/,
/^pyproject\.toml$/,
/^Cargo\.toml$/,
/^go\.mod$/,
/^pom\.xml$/,
/^build\.gradle$/
]
function traverse(node: DirectoryNode) {
if (node.type === 'file') {
const name = basename(node.path)
if (importantPatterns.some(pattern => pattern.test(name))) {
important.push(node.path)
}
} else if (node.children) {
node.children.forEach(traverse)
}
}
traverse(structure)
return important
}
}
```
## Code Style Detection
### Style Analyzer
```typescript
class CodeStyleAnalyzer {
async analyze(): Promise<CodeStyle> {
const files = await this.findSourceFiles()
const samples = await this.takeSamples(files, 10)
return {
indentation: this.detectIndentation(samples),
quotes: this.detectQuotes(samples),
semicolons: this.detectSemicolons(samples),
lineEndings: this.detectLineEndings(samples),
trailingCommas: this.detectTrailingCommas(samples),
bracketSpacing: this.detectBracketSpacing(samples),
naming: this.detectNamingConventions(samples),
maxLineLength: this.detectMaxLineLength(samples),
fileNaming: this.detectFileNaming(files)
}
}
private detectIndentation(samples: string[]): IndentationStyle {
let spaces = 0
let tabs = 0
let twoSpaces = 0
let fourSpaces = 0
for (const sample of samples) {
const lines = sample.split('\n')
for (const line of lines) {
if (line.startsWith('\t')) {
tabs++
} else if (line.startsWith(' ')) {
fourSpaces++
spaces++
} else if (line.startsWith(' ')) {
twoSpaces++
spaces++
}
}
}
if (tabs > spaces) {
return { type: 'tabs', size: 1 }
} else if (fourSpaces > twoSpaces) {
return { type: 'spaces', size: 4 }
} else {
return { type: 'spaces', size: 2 }
}
}
private detectNamingConventions(samples: string[]): NamingConventions {
const patterns = {
camelCase: /[a-z][a-zA-Z0-9]*/g,
PascalCase: /[A-Z][a-zA-Z0-9]*/g,
snake_case: /[a-z]+(_[a-z]+)+/g,
kebab_case: /[a-z]+(-[a-z]+)+/g,
SCREAMING_SNAKE: /[A-Z]+(_[A-Z]+)+/g
}
const counts: Record<string, number> = {}
for (const sample of samples) {
for (const [name, pattern] of Object.entries(patterns)) {
const matches = sample.match(pattern) || []
counts[name] = (counts[name] || 0) + matches.length
}
}
return {
variables: this.getMostCommon(counts, ['camelCase', 'snake_case']),
functions: this.getMostCommon(counts, ['camelCase', 'snake_case']),
classes: this.getMostCommon(counts, ['PascalCase', 'camelCase']),
constants: this.getMostCommon(counts, ['SCREAMING_SNAKE', 'camelCase']),
files: this.getMostCommon(counts, ['kebab_case', 'snake_case', 'camelCase'])
}
}
}
```
## Context Injection
### Message Context Builder
```typescript
class MessageContextBuilder {
buildSystemContext(context: CompleteContext): string {
const sections: string[] = []
// Add CLAUDE.md instructions first (highest priority)
if (context.claudeFile) {
sections.push(context.claudeFile)
}
// Add project context
if (context.contextFile) {
sections.push('# Project Context\n\n' + context.contextFile)
}
// Add git status
if (context.gitStatus) {
sections.push('# Git Status\n\n```\n' + context.gitStatus + '\n```')
}
// Add directory structure
if (context.directoryStructure) {
sections.push('# Directory Structure\n\n```\n' + context.directoryStructure + '\n```')
}
// Add important files list
if (context.importantFiles?.length) {
sections.push('# Important Files\n\n' + context.importantFiles.map(f => `- ${f}`).join('\n'))
}
// Add code style
if (context.codeStyle) {
sections.push('# Code Style\n\n' + this.formatCodeStyle(context.codeStyle))
}
// Add custom context
if (context.customContext) {
sections.push('# Additional Context\n\n' + JSON.stringify(context.customContext, null, 2))
}
return sections.join('\n\n---\n\n')
}
private formatCodeStyle(style: CodeStyle): string {
return `
- Indentation: ${style.indentation.type} (${style.indentation.size})
- Quotes: ${style.quotes}
- Semicolons: ${style.semicolons}
- Line endings: ${style.lineEndings}
- Trailing commas: ${style.trailingCommas}
- Bracket spacing: ${style.bracketSpacing}
- Max line length: ${style.maxLineLength}
`
}
}
```
### Smart Context Injection
```typescript
class SmartContextInjector {
inject(
messages: Message[],
context: CompleteContext,
options: InjectionOptions = {}
): Message[] {
const injector = new ContextInjector(context, options)
// Determine what context to include based on conversation
const relevantContext = injector.analyzeRelevance(messages)
// Build system message with relevant context
const systemMessage = injector.buildSystemMessage(relevantContext)
// Inject at appropriate position
return injector.injectAtPosition(messages, systemMessage, options.position || 'start')
}
private analyzeRelevance(messages: Message[]): RelevantContext {
const keywords = this.extractKeywords(messages)
const topics = this.identifyTopics(keywords)
return {
includeGit: topics.includes('version-control') || keywords.has('commit'),
includeStructure: topics.includes('architecture') || keywords.has('structure'),
includeStyle: topics.includes('formatting') || keywords.has('style'),
includeDependencies: topics.includes('packages') || keywords.has('install'),
includeTests: topics.includes('testing') || keywords.has('test'),
includeConfig: topics.includes('configuration') || keywords.has('config')
}
}
private extractKeywords(messages: Message[]): Set<string> {
const keywords = new Set<string>()
const commonWords = new Set(['the', 'a', 'an', 'is', 'are', 'was', 'were'])
for (const message of messages) {
const words = message.content
.toLowerCase()
.split(/\W+/)
.filter(word => word.length > 2 && !commonWords.has(word))
words.forEach(word => keywords.add(word))
}
return keywords
}
}
```
## Caching
### Context Cache
```typescript
class ContextCache {
private cache: Map<string, CachedContext> = new Map()
private readonly TTL = 60000 // 1 minute
get(key: string): CompleteContext | null {
const cached = this.cache.get(key)
if (!cached) return null
if (Date.now() - cached.timestamp > this.TTL) {
this.cache.delete(key)
return null
}
return cached.context
}
set(key: string, context: CompleteContext): void {
this.cache.set(key, {
context,
timestamp: Date.now()
})
// Limit cache size
if (this.cache.size > 10) {
const oldest = Array.from(this.cache.entries())
.sort((a, b) => a[1].timestamp - b[1].timestamp)[0]
this.cache.delete(oldest[0])
}
}
invalidate(pattern?: string): void {
if (!pattern) {
this.cache.clear()
return
}
for (const key of this.cache.keys()) {
if (key.includes(pattern)) {
this.cache.delete(key)
}
}
}
getCacheKey(options: ContextOptions): string {
return JSON.stringify({
cwd: getCwd(),
includeGit: options.includeGit,
includeStructure: options.includeStructure,
includeStyle: options.includeStyle
})
}
}
```
### Lazy Loading
```typescript
class LazyContextLoader {
private loaders: Map<string, () => Promise<any>> = new Map()
private results: Map<string, any> = new Map()
constructor() {
this.registerLoaders()
}
private registerLoaders(): void {
this.loaders.set('git', () => getGitContext())
this.loaders.set('project', () => new ProjectAnalyzer().analyze())
this.loaders.set('structure', () => new DirectoryStructureAnalyzer().analyze())
this.loaders.set('style', () => new CodeStyleAnalyzer().analyze())
this.loaders.set('contextFile', () => new ContextFileLoader().loadContextFile())
this.loaders.set('claudeFile', () => new ClaudeFileLoader().loadClaudeFile())
}
async load(keys: string[]): Promise<Record<string, any>> {
const promises = keys.map(async key => {
if (this.results.has(key)) {
return { key, value: this.results.get(key) }
}
const loader = this.loaders.get(key)
if (!loader) {
console.warn(`No loader for context key: ${key}`)
return { key, value: null }
}
try {
const value = await loader()
this.results.set(key, value)
return { key, value }
} catch (error) {
console.error(`Failed to load context ${key}:`, error)
return { key, value: null }
}
})
const results = await Promise.all(promises)
return results.reduce((acc, { key, value }) => {
acc[key] = value
return acc
}, {} as Record<string, any>)
}
clear(): void {
this.results.clear()
}
}
```
## Performance Monitoring
### Context Metrics
```typescript
class ContextMetrics {
private metrics: Map<string, Metric> = new Map()
async measure<T>(
name: string,
operation: () => Promise<T>
): Promise<T> {
const start = Date.now()
try {
const result = await operation()
const duration = Date.now() - start
this.record(name, duration, 'success')
return result
} catch (error) {
const duration = Date.now() - start
this.record(name, duration, 'error')
throw error
}
}
private record(
name: string,
duration: number,
status: 'success' | 'error'
): void {
const metric = this.metrics.get(name) || {
count: 0,
totalDuration: 0,
avgDuration: 0,
maxDuration: 0,
minDuration: Infinity,
errors: 0
}
metric.count++
metric.totalDuration += duration
metric.avgDuration = metric.totalDuration / metric.count
metric.maxDuration = Math.max(metric.maxDuration, duration)
metric.minDuration = Math.min(metric.minDuration, duration)
if (status === 'error') {
metric.errors++
}
this.metrics.set(name, metric)
// Log slow operations
if (duration > 1000) {
console.warn(`Slow context operation ${name}: ${duration}ms`)
}
}
getReport(): MetricsReport {
return {
operations: Array.from(this.metrics.entries()).map(([name, metric]) => ({
name,
...metric
})),
totalOperations: Array.from(this.metrics.values()).reduce((sum, m) => sum + m.count, 0),
totalDuration: Array.from(this.metrics.values()).reduce((sum, m) => sum + m.totalDuration, 0)
}
}
}
```
The Context System provides comprehensive project understanding through automatic discovery, intelligent caching, and smart injection, ensuring AI responses are always contextually relevant and accurate.