mirror of
https://github.com/samiyev/puaros.git
synced 2025-12-27 23:06:54 +05:00
Compare commits
3 Commits
ipuaro-v0.
...
ipuaro-v0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1489b69e69 | ||
|
|
2dcb22812c | ||
|
|
7d7c99fe4d |
@@ -5,6 +5,77 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.24.0] - 2025-12-04 - Rich Initial Context: Function Signatures
|
||||
|
||||
### Added
|
||||
|
||||
- **Function Signatures in Context (0.24.1)**
|
||||
- Full function signatures with parameter types and return types in initial context
|
||||
- New format: `async getUser(id: string): Promise<User>` instead of `fn: getUser`
|
||||
- Classes show inheritance: `class UserService extends BaseService implements IService`
|
||||
- Interfaces show extends: `interface AdminUser extends User, Admin`
|
||||
- Optional parameters marked with `?`: `format(value: string, options?: FormatOptions)`
|
||||
|
||||
- **BuildContextOptions Interface**
|
||||
- New `includeSignatures?: boolean` option for `buildInitialContext()`
|
||||
- Controls signature vs compact format (default: `true` for signatures)
|
||||
|
||||
- **Configuration**
|
||||
- Added `includeSignatures: boolean` to `ContextConfigSchema` (default: `true`)
|
||||
- Users can disable signatures to save tokens: `context.includeSignatures: false`
|
||||
|
||||
### Changed
|
||||
|
||||
- **ASTParser**
|
||||
- Arrow functions now extract `returnType` in `extractLexicalDeclaration()`
|
||||
- Return type format normalized (strips leading `: `)
|
||||
|
||||
- **prompts.ts**
|
||||
- New `formatFunctionSignature()` helper function
|
||||
- `formatFileSummary()` now shows full signatures by default
|
||||
- Added `formatFileSummaryCompact()` for legacy format
|
||||
- `formatFileOverview()` accepts `includeSignatures` parameter
|
||||
- Defensive handling for missing interface `extends` array
|
||||
|
||||
### New Context Format (default)
|
||||
|
||||
```
|
||||
### src/services/user.ts
|
||||
- async getUser(id: string): Promise<User>
|
||||
- async createUser(data: UserDTO): Promise<User>
|
||||
- validateEmail(email: string): boolean
|
||||
- class UserService extends BaseService
|
||||
- interface IUserService extends IService
|
||||
- type UserId
|
||||
```
|
||||
|
||||
### Compact Format (includeSignatures: false)
|
||||
|
||||
```
|
||||
- src/services/user.ts [fn: getUser, createUser | class: UserService | interface: IUserService | type: UserId]
|
||||
```
|
||||
|
||||
### Technical Details
|
||||
|
||||
- Total tests: 1702 passed (was 1687, +15 new tests)
|
||||
- 8 new tests for function signature formatting
|
||||
- 5 new tests for `includeSignatures` configuration
|
||||
- 1 new test for compact format
|
||||
- 1 new test for undefined AST entries
|
||||
- Coverage: 97.54% lines, 91.14% branches, 98.59% functions
|
||||
- 0 ESLint errors, 2 warnings (complexity in ASTParser and prompts)
|
||||
- Build successful
|
||||
|
||||
### Notes
|
||||
|
||||
This is the first part of v0.24.0 Rich Initial Context milestone:
|
||||
- ✅ 0.24.1 - Function Signatures with Types
|
||||
- ⏳ 0.24.2 - Interface/Type Field Definitions
|
||||
- ⏳ 0.24.3 - Enum Value Definitions
|
||||
- ⏳ 0.24.4 - Decorator Extraction
|
||||
|
||||
---
|
||||
|
||||
## [0.23.0] - 2025-12-04 - JSON/YAML & Symlinks
|
||||
|
||||
### Added
|
||||
|
||||
@@ -1779,6 +1779,211 @@ export interface ScanResult {
|
||||
|
||||
---
|
||||
|
||||
## Version 0.24.0 - Rich Initial Context 📋
|
||||
|
||||
**Priority:** HIGH
|
||||
**Status:** In Progress (1/4 complete)
|
||||
|
||||
Улучшение initial context для LLM: добавление сигнатур функций, типов интерфейсов и значений enum. Это позволит LLM отвечать на вопросы о типах и параметрах без tool calls.
|
||||
|
||||
### 0.24.1 - Function Signatures with Types ⭐ ✅
|
||||
|
||||
**Проблема:** Сейчас LLM видит только имена функций: `fn: getUser, createUser`
|
||||
**Решение:** Показать полные сигнатуры: `async getUser(id: string): Promise<User>`
|
||||
|
||||
```typescript
|
||||
// src/infrastructure/llm/prompts.ts changes
|
||||
|
||||
// БЫЛО:
|
||||
// - src/services/user.ts [fn: getUser, createUser]
|
||||
|
||||
// СТАНЕТ:
|
||||
// ### src/services/user.ts
|
||||
// - async getUser(id: string): Promise<User>
|
||||
// - async createUser(data: UserDTO): Promise<User>
|
||||
// - validateEmail(email: string): boolean
|
||||
```
|
||||
|
||||
**Изменения:**
|
||||
- [x] Расширить `FunctionInfo` в FileAST для хранения типов параметров и return type (already existed)
|
||||
- [x] Обновить `ASTParser.ts` для извлечения типов параметров и return types (arrow functions fixed)
|
||||
- [x] Обновить `formatFileSummary()` в prompts.ts для вывода сигнатур
|
||||
- [x] Добавить опцию `includeSignatures: boolean` в config
|
||||
|
||||
**Зачем:** LLM не будет галлюцинировать параметры и return types.
|
||||
|
||||
### 0.24.2 - Interface/Type Field Definitions ⭐
|
||||
|
||||
**Проблема:** LLM видит только `interface: User, UserDTO`
|
||||
**Решение:** Показать поля: `User { id: string, name: string, email: string }`
|
||||
|
||||
```typescript
|
||||
// БЫЛО:
|
||||
// - src/types/user.ts [interface: User, UserDTO]
|
||||
|
||||
// СТАНЕТ:
|
||||
// ### src/types/user.ts
|
||||
// - interface User { id: string, name: string, email: string, createdAt: Date }
|
||||
// - interface UserDTO { name: string, email: string }
|
||||
// - type UserId = string
|
||||
```
|
||||
|
||||
**Изменения:**
|
||||
- [ ] Расширить `InterfaceInfo` в FileAST для хранения полей с типами
|
||||
- [ ] Обновить `ASTParser.ts` для извлечения полей интерфейсов
|
||||
- [ ] Обновить `formatFileSummary()` для вывода полей
|
||||
- [ ] Обработка type aliases с их определениями
|
||||
|
||||
**Зачем:** LLM знает структуру данных, не придумывает поля.
|
||||
|
||||
### 0.24.3 - Enum Value Definitions
|
||||
|
||||
**Проблема:** LLM видит только `type: Status`
|
||||
**Решение:** Показать значения: `Status { Active=1, Inactive=0, Pending=2 }`
|
||||
|
||||
```typescript
|
||||
// БЫЛО:
|
||||
// - src/types/enums.ts [type: Status, Role]
|
||||
|
||||
// СТАНЕТ:
|
||||
// ### src/types/enums.ts
|
||||
// - enum Status { Active=1, Inactive=0, Pending=2 }
|
||||
// - enum Role { Admin="admin", User="user" }
|
||||
```
|
||||
|
||||
**Изменения:**
|
||||
- [ ] Добавить `EnumInfo` в FileAST с members и values
|
||||
- [ ] Обновить `ASTParser.ts` для извлечения enum members
|
||||
- [ ] Обновить `formatFileSummary()` для вывода enum values
|
||||
|
||||
**Зачем:** LLM знает допустимые значения enum.
|
||||
|
||||
### 0.24.4 - Decorator Extraction
|
||||
|
||||
**Проблема:** LLM не видит декораторы (важно для NestJS, Angular)
|
||||
**Решение:** Показать декораторы в контексте
|
||||
|
||||
```typescript
|
||||
// СТАНЕТ:
|
||||
// ### src/controllers/user.controller.ts
|
||||
// - @Controller('users') class UserController
|
||||
// - @Get(':id') async getUser(id: string): Promise<User>
|
||||
// - @Post() @Body() async createUser(data: UserDTO): Promise<User>
|
||||
```
|
||||
|
||||
**Изменения:**
|
||||
- [ ] Добавить `decorators: string[]` в FunctionInfo и ClassInfo
|
||||
- [ ] Обновить `ASTParser.ts` для извлечения декораторов
|
||||
- [ ] Обновить контекст для отображения декораторов
|
||||
|
||||
**Зачем:** LLM понимает роутинг, DI, guards в NestJS/Angular.
|
||||
|
||||
**Tests:**
|
||||
- [ ] Unit tests for enhanced ASTParser
|
||||
- [ ] Unit tests for new context format
|
||||
- [ ] Integration tests for full flow
|
||||
|
||||
---
|
||||
|
||||
## Version 0.25.0 - Graph Metrics in Context 📊
|
||||
|
||||
**Priority:** MEDIUM
|
||||
**Status:** Planned
|
||||
|
||||
Добавление графовых метрик в initial context: граф зависимостей, circular dependencies, impact score.
|
||||
|
||||
### 0.25.1 - Inline Dependency Graph
|
||||
|
||||
**Проблема:** LLM не видит связи между файлами без tool calls
|
||||
**Решение:** Показать граф зависимостей в контексте
|
||||
|
||||
```typescript
|
||||
// Добавить в initial context:
|
||||
|
||||
// ## Dependency Graph
|
||||
// src/services/user.ts: → types/user, utils/validation ← controllers/user, api/routes
|
||||
// src/services/auth.ts: → services/user, utils/jwt ← controllers/auth
|
||||
// src/utils/validation.ts: ← services/user, services/auth, controllers/*
|
||||
```
|
||||
|
||||
**Изменения:**
|
||||
- [ ] Добавить `formatDependencyGraph()` в prompts.ts
|
||||
- [ ] Использовать данные из `FileMeta.dependencies` и `FileMeta.dependents`
|
||||
- [ ] Группировать по hub files (много connections)
|
||||
- [ ] Добавить опцию `includeDepsGraph: boolean` в config
|
||||
|
||||
**Зачем:** LLM видит архитектуру без tool call.
|
||||
|
||||
### 0.25.2 - Circular Dependencies in Context
|
||||
|
||||
**Проблема:** Circular deps вычисляются, но не показываются в контексте
|
||||
**Решение:** Показать циклы сразу
|
||||
|
||||
```typescript
|
||||
// Добавить в initial context:
|
||||
|
||||
// ## ⚠️ Circular Dependencies
|
||||
// - services/user → services/auth → services/user
|
||||
// - utils/a → utils/b → utils/c → utils/a
|
||||
```
|
||||
|
||||
**Изменения:**
|
||||
- [ ] Добавить `formatCircularDeps()` в prompts.ts
|
||||
- [ ] Получать circular deps из IndexBuilder
|
||||
- [ ] Хранить в Redis как отдельный ключ или в meta
|
||||
|
||||
**Зачем:** LLM сразу видит проблемы архитектуры.
|
||||
|
||||
### 0.25.3 - Impact Score
|
||||
|
||||
**Проблема:** LLM не знает какие файлы критичные
|
||||
**Решение:** Показать impact score (% кодовой базы, который зависит от файла)
|
||||
|
||||
```typescript
|
||||
// Добавить в initial context:
|
||||
|
||||
// ## High Impact Files
|
||||
// | File | Impact | Dependents |
|
||||
// |------|--------|------------|
|
||||
// | src/utils/validation.ts | 67% | 12 files |
|
||||
// | src/types/user.ts | 45% | 8 files |
|
||||
// | src/services/user.ts | 34% | 6 files |
|
||||
```
|
||||
|
||||
**Изменения:**
|
||||
- [ ] Добавить `impactScore: number` в FileMeta (0-100)
|
||||
- [ ] Вычислять в MetaAnalyzer: (transitiveDepByCount / totalFiles) * 100
|
||||
- [ ] Добавить `formatHighImpactFiles()` в prompts.ts
|
||||
- [ ] Показывать top-10 high impact files
|
||||
|
||||
**Зачем:** LLM понимает какие файлы критичные для изменений.
|
||||
|
||||
### 0.25.4 - Transitive Dependencies Count
|
||||
|
||||
**Проблема:** Сейчас считаем только прямые зависимости
|
||||
**Решение:** Добавить транзитивные зависимости в meta
|
||||
|
||||
```typescript
|
||||
// FileMeta additions:
|
||||
interface FileMeta {
|
||||
// existing...
|
||||
transitiveDepCount: number; // сколько файлов зависит от этого (транзитивно)
|
||||
transitiveDepByCount: number; // от скольких файлов зависит этот (транзитивно)
|
||||
}
|
||||
```
|
||||
|
||||
**Изменения:**
|
||||
- [ ] Добавить `computeTransitiveDeps()` в MetaAnalyzer
|
||||
- [ ] Использовать DFS с memoization для эффективности
|
||||
- [ ] Сохранять в FileMeta
|
||||
|
||||
**Tests:**
|
||||
- [ ] Unit tests for graph metrics computation
|
||||
- [ ] Unit tests for new context sections
|
||||
- [ ] Performance tests for large codebases
|
||||
|
||||
---
|
||||
|
||||
## Version 1.0.0 - Production Ready 🚀
|
||||
|
||||
**Target:** Stable release
|
||||
@@ -1794,6 +1999,8 @@ export interface ScanResult {
|
||||
- [x] 0 ESLint errors ✅
|
||||
- [x] Examples working ✅ (v0.18.0)
|
||||
- [x] CHANGELOG.md up to date ✅
|
||||
- [ ] Rich initial context (v0.24.0) — function signatures, interface fields, enum values
|
||||
- [ ] Graph metrics in context (v0.25.0) — dependency graph, circular deps, impact score
|
||||
|
||||
---
|
||||
|
||||
@@ -1862,13 +2069,17 @@ sessions:list # List<session_id>
|
||||
| Component | Tokens | % |
|
||||
|-----------|--------|---|
|
||||
| System prompt | ~2,000 | 1.5% |
|
||||
| Structure + AST | ~10,000 | 8% |
|
||||
| **Available** | ~116,000 | 90% |
|
||||
| Structure + AST (v0.23) | ~10,000 | 8% |
|
||||
| Signatures + Types (v0.24) | ~5,000 | 4% |
|
||||
| Graph Metrics (v0.25) | ~3,000 | 2.5% |
|
||||
| **Total Initial Context** | ~20,000 | 16% |
|
||||
| **Available for Chat** | ~108,000 | 84% |
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** 2025-12-04
|
||||
**Target Version:** 1.0.0
|
||||
**Current Version:** 0.23.0
|
||||
**Current Version:** 0.24.0
|
||||
**Next Milestones:** v0.24.0 (Rich Context - 1/4 complete), v0.25.0 (Graph Metrics)
|
||||
|
||||
> **Note:** Versions 0.20.0, 0.21.0, 0.22.0, 0.23.0 were implemented but ROADMAP was not updated. All features verified as complete.
|
||||
> **Note:** v0.24.0 and v0.25.0 are required for 1.0.0 release. They enable LLM to answer questions about types, signatures, and architecture without tool calls.
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@samiyev/ipuaro",
|
||||
"version": "0.23.0",
|
||||
"version": "0.24.0",
|
||||
"description": "Local AI agent for codebase operations with infinite context feeling",
|
||||
"author": "Fozilbek Samiyev <fozilbek.samiyev@gmail.com>",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -332,6 +332,7 @@ export class ASTParser {
|
||||
) {
|
||||
const params = this.extractParameters(valueNode)
|
||||
const isAsync = valueNode.children.some((c) => c.type === NodeType.ASYNC)
|
||||
const returnTypeNode = valueNode.childForFieldName(FieldName.RETURN_TYPE)
|
||||
|
||||
ast.functions.push({
|
||||
name: nameNode?.text ?? "",
|
||||
@@ -340,6 +341,7 @@ export class ASTParser {
|
||||
params,
|
||||
isAsync,
|
||||
isExported,
|
||||
returnType: returnTypeNode?.text?.replace(/^:\s*/, ""),
|
||||
})
|
||||
|
||||
if (isExported) {
|
||||
|
||||
@@ -11,6 +11,13 @@ export interface ProjectStructure {
|
||||
directories: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for building initial context.
|
||||
*/
|
||||
export interface BuildContextOptions {
|
||||
includeSignatures?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* System prompt for the ipuaro AI agent.
|
||||
*/
|
||||
@@ -116,12 +123,14 @@ export function buildInitialContext(
|
||||
structure: ProjectStructure,
|
||||
asts: Map<string, FileAST>,
|
||||
metas?: Map<string, FileMeta>,
|
||||
options?: BuildContextOptions,
|
||||
): string {
|
||||
const sections: string[] = []
|
||||
const includeSignatures = options?.includeSignatures ?? true
|
||||
|
||||
sections.push(formatProjectHeader(structure))
|
||||
sections.push(formatDirectoryTree(structure))
|
||||
sections.push(formatFileOverview(asts, metas))
|
||||
sections.push(formatFileOverview(asts, metas, includeSignatures))
|
||||
|
||||
return sections.join("\n\n")
|
||||
}
|
||||
@@ -157,7 +166,11 @@ function formatDirectoryTree(structure: ProjectStructure): string {
|
||||
/**
|
||||
* Format file overview with AST summaries.
|
||||
*/
|
||||
function formatFileOverview(asts: Map<string, FileAST>, metas?: Map<string, FileMeta>): string {
|
||||
function formatFileOverview(
|
||||
asts: Map<string, FileAST>,
|
||||
metas?: Map<string, FileMeta>,
|
||||
includeSignatures = true,
|
||||
): string {
|
||||
const lines: string[] = ["## Files", ""]
|
||||
|
||||
const sortedPaths = [...asts.keys()].sort()
|
||||
@@ -168,16 +181,87 @@ function formatFileOverview(asts: Map<string, FileAST>, metas?: Map<string, File
|
||||
}
|
||||
|
||||
const meta = metas?.get(path)
|
||||
lines.push(formatFileSummary(path, ast, meta))
|
||||
lines.push(formatFileSummary(path, ast, meta, includeSignatures))
|
||||
}
|
||||
|
||||
return lines.join("\n")
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a single file's AST summary.
|
||||
* Format a function signature.
|
||||
*/
|
||||
function formatFileSummary(path: string, ast: FileAST, meta?: FileMeta): string {
|
||||
function formatFunctionSignature(fn: FileAST["functions"][0]): string {
|
||||
const asyncPrefix = fn.isAsync ? "async " : ""
|
||||
const params = fn.params
|
||||
.map((p) => {
|
||||
const optional = p.optional ? "?" : ""
|
||||
const type = p.type ? `: ${p.type}` : ""
|
||||
return `${p.name}${optional}${type}`
|
||||
})
|
||||
.join(", ")
|
||||
const returnType = fn.returnType ? `: ${fn.returnType}` : ""
|
||||
return `${asyncPrefix}${fn.name}(${params})${returnType}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a single file's AST summary.
|
||||
* When includeSignatures is true, shows full function signatures.
|
||||
* When false, shows compact format with just names.
|
||||
*/
|
||||
function formatFileSummary(
|
||||
path: string,
|
||||
ast: FileAST,
|
||||
meta?: FileMeta,
|
||||
includeSignatures = true,
|
||||
): string {
|
||||
const flags = formatFileFlags(meta)
|
||||
|
||||
if (!includeSignatures) {
|
||||
return formatFileSummaryCompact(path, ast, flags)
|
||||
}
|
||||
|
||||
const lines: string[] = []
|
||||
lines.push(`### ${path}${flags}`)
|
||||
|
||||
if (ast.functions.length > 0) {
|
||||
for (const fn of ast.functions) {
|
||||
lines.push(`- ${formatFunctionSignature(fn)}`)
|
||||
}
|
||||
}
|
||||
|
||||
if (ast.classes.length > 0) {
|
||||
for (const cls of ast.classes) {
|
||||
const ext = cls.extends ? ` extends ${cls.extends}` : ""
|
||||
const impl = cls.implements.length > 0 ? ` implements ${cls.implements.join(", ")}` : ""
|
||||
lines.push(`- class ${cls.name}${ext}${impl}`)
|
||||
}
|
||||
}
|
||||
|
||||
if (ast.interfaces.length > 0) {
|
||||
for (const iface of ast.interfaces) {
|
||||
const extList = iface.extends ?? []
|
||||
const ext = extList.length > 0 ? ` extends ${extList.join(", ")}` : ""
|
||||
lines.push(`- interface ${iface.name}${ext}`)
|
||||
}
|
||||
}
|
||||
|
||||
if (ast.typeAliases.length > 0) {
|
||||
for (const type of ast.typeAliases) {
|
||||
lines.push(`- type ${type.name}`)
|
||||
}
|
||||
}
|
||||
|
||||
if (lines.length === 1) {
|
||||
return `- ${path}${flags}`
|
||||
}
|
||||
|
||||
return lines.join("\n")
|
||||
}
|
||||
|
||||
/**
|
||||
* Format file summary in compact mode (just names, no signatures).
|
||||
*/
|
||||
function formatFileSummaryCompact(path: string, ast: FileAST, flags: string): string {
|
||||
const parts: string[] = []
|
||||
|
||||
if (ast.functions.length > 0) {
|
||||
@@ -201,8 +285,6 @@ function formatFileSummary(path: string, ast: FileAST, meta?: FileMeta): string
|
||||
}
|
||||
|
||||
const summary = parts.length > 0 ? ` [${parts.join(" | ")}]` : ""
|
||||
const flags = formatFileFlags(meta)
|
||||
|
||||
return `- ${path}${summary}${flags}`
|
||||
}
|
||||
|
||||
|
||||
@@ -114,6 +114,7 @@ export const ContextConfigSchema = z.object({
|
||||
maxContextUsage: z.number().min(0).max(1).default(0.8),
|
||||
autoCompressAt: z.number().min(0).max(1).default(0.8),
|
||||
compressionMethod: z.enum(["llm-summary", "truncate"]).default("llm-summary"),
|
||||
includeSignatures: z.boolean().default(true),
|
||||
})
|
||||
|
||||
/**
|
||||
|
||||
@@ -108,13 +108,23 @@ describe("prompts", () => {
|
||||
expect(context).toContain("tests/")
|
||||
})
|
||||
|
||||
it("should include file overview with AST summaries", () => {
|
||||
it("should include file overview with AST summaries (signatures format)", () => {
|
||||
const context = buildInitialContext(structure, asts)
|
||||
|
||||
expect(context).toContain("## Files")
|
||||
expect(context).toContain("src/index.ts")
|
||||
expect(context).toContain("### src/index.ts")
|
||||
expect(context).toContain("- main()")
|
||||
expect(context).toContain("### src/utils.ts")
|
||||
expect(context).toContain("- class Helper")
|
||||
})
|
||||
|
||||
it("should use compact format when includeSignatures is false", () => {
|
||||
const context = buildInitialContext(structure, asts, undefined, {
|
||||
includeSignatures: false,
|
||||
})
|
||||
|
||||
expect(context).toContain("## Files")
|
||||
expect(context).toContain("fn: main")
|
||||
expect(context).toContain("src/utils.ts")
|
||||
expect(context).toContain("class: Helper")
|
||||
})
|
||||
|
||||
@@ -506,7 +516,16 @@ describe("prompts", () => {
|
||||
exports: [],
|
||||
functions: [],
|
||||
classes: [],
|
||||
interfaces: [{ name: "IFoo", lineStart: 1, lineEnd: 5, isExported: true }],
|
||||
interfaces: [
|
||||
{
|
||||
name: "IFoo",
|
||||
lineStart: 1,
|
||||
lineEnd: 5,
|
||||
properties: [],
|
||||
extends: [],
|
||||
isExported: true,
|
||||
},
|
||||
],
|
||||
typeAliases: [],
|
||||
parseError: false,
|
||||
},
|
||||
@@ -515,6 +534,44 @@ describe("prompts", () => {
|
||||
|
||||
const context = buildInitialContext(structure, asts)
|
||||
|
||||
expect(context).toContain("- interface IFoo")
|
||||
})
|
||||
|
||||
it("should handle file with only interfaces (compact format)", () => {
|
||||
const structure: ProjectStructure = {
|
||||
name: "test",
|
||||
rootPath: "/test",
|
||||
files: ["types.ts"],
|
||||
directories: [],
|
||||
}
|
||||
const asts = new Map<string, FileAST>([
|
||||
[
|
||||
"types.ts",
|
||||
{
|
||||
imports: [],
|
||||
exports: [],
|
||||
functions: [],
|
||||
classes: [],
|
||||
interfaces: [
|
||||
{
|
||||
name: "IFoo",
|
||||
lineStart: 1,
|
||||
lineEnd: 5,
|
||||
properties: [],
|
||||
extends: [],
|
||||
isExported: true,
|
||||
},
|
||||
],
|
||||
typeAliases: [],
|
||||
parseError: false,
|
||||
},
|
||||
],
|
||||
])
|
||||
|
||||
const context = buildInitialContext(structure, asts, undefined, {
|
||||
includeSignatures: false,
|
||||
})
|
||||
|
||||
expect(context).toContain("interface: IFoo")
|
||||
})
|
||||
|
||||
@@ -534,9 +591,7 @@ describe("prompts", () => {
|
||||
functions: [],
|
||||
classes: [],
|
||||
interfaces: [],
|
||||
typeAliases: [
|
||||
{ name: "MyType", lineStart: 1, lineEnd: 1, isExported: true },
|
||||
],
|
||||
typeAliases: [{ name: "MyType", line: 1, isExported: true }],
|
||||
parseError: false,
|
||||
},
|
||||
],
|
||||
@@ -544,6 +599,35 @@ describe("prompts", () => {
|
||||
|
||||
const context = buildInitialContext(structure, asts)
|
||||
|
||||
expect(context).toContain("- type MyType")
|
||||
})
|
||||
|
||||
it("should handle file with only type aliases (compact format)", () => {
|
||||
const structure: ProjectStructure = {
|
||||
name: "test",
|
||||
rootPath: "/test",
|
||||
files: ["types.ts"],
|
||||
directories: [],
|
||||
}
|
||||
const asts = new Map<string, FileAST>([
|
||||
[
|
||||
"types.ts",
|
||||
{
|
||||
imports: [],
|
||||
exports: [],
|
||||
functions: [],
|
||||
classes: [],
|
||||
interfaces: [],
|
||||
typeAliases: [{ name: "MyType", line: 1, isExported: true }],
|
||||
parseError: false,
|
||||
},
|
||||
],
|
||||
])
|
||||
|
||||
const context = buildInitialContext(structure, asts, undefined, {
|
||||
includeSignatures: false,
|
||||
})
|
||||
|
||||
expect(context).toContain("type: MyType")
|
||||
})
|
||||
|
||||
@@ -686,6 +770,22 @@ describe("prompts", () => {
|
||||
expect(context).toContain("exists.ts")
|
||||
expect(context).not.toContain("missing.ts")
|
||||
})
|
||||
|
||||
it("should skip undefined AST entries", () => {
|
||||
const structure: ProjectStructure = {
|
||||
name: "test",
|
||||
rootPath: "/test",
|
||||
files: ["file.ts"],
|
||||
directories: [],
|
||||
}
|
||||
const asts = new Map<string, FileAST>()
|
||||
asts.set("file.ts", undefined as unknown as FileAST)
|
||||
|
||||
const context = buildInitialContext(structure, asts)
|
||||
|
||||
expect(context).toContain("## Files")
|
||||
expect(context).not.toContain("file.ts")
|
||||
})
|
||||
})
|
||||
|
||||
describe("truncateContext", () => {
|
||||
@@ -714,4 +814,276 @@ describe("prompts", () => {
|
||||
expect(result).toContain("truncated")
|
||||
})
|
||||
})
|
||||
|
||||
describe("function signatures with types", () => {
|
||||
it("should format function with typed parameters", () => {
|
||||
const structure: ProjectStructure = {
|
||||
name: "test",
|
||||
rootPath: "/test",
|
||||
files: ["user.ts"],
|
||||
directories: [],
|
||||
}
|
||||
const asts = new Map<string, FileAST>([
|
||||
[
|
||||
"user.ts",
|
||||
{
|
||||
imports: [],
|
||||
exports: [],
|
||||
functions: [
|
||||
{
|
||||
name: "getUser",
|
||||
lineStart: 1,
|
||||
lineEnd: 5,
|
||||
params: [
|
||||
{
|
||||
name: "id",
|
||||
type: "string",
|
||||
optional: false,
|
||||
hasDefault: false,
|
||||
},
|
||||
],
|
||||
isAsync: false,
|
||||
isExported: true,
|
||||
},
|
||||
],
|
||||
classes: [],
|
||||
interfaces: [],
|
||||
typeAliases: [],
|
||||
parseError: false,
|
||||
},
|
||||
],
|
||||
])
|
||||
|
||||
const context = buildInitialContext(structure, asts)
|
||||
|
||||
expect(context).toContain("- getUser(id: string)")
|
||||
})
|
||||
|
||||
it("should format async function with return type", () => {
|
||||
const structure: ProjectStructure = {
|
||||
name: "test",
|
||||
rootPath: "/test",
|
||||
files: ["user.ts"],
|
||||
directories: [],
|
||||
}
|
||||
const asts = new Map<string, FileAST>([
|
||||
[
|
||||
"user.ts",
|
||||
{
|
||||
imports: [],
|
||||
exports: [],
|
||||
functions: [
|
||||
{
|
||||
name: "getUser",
|
||||
lineStart: 1,
|
||||
lineEnd: 5,
|
||||
params: [
|
||||
{
|
||||
name: "id",
|
||||
type: "string",
|
||||
optional: false,
|
||||
hasDefault: false,
|
||||
},
|
||||
],
|
||||
isAsync: true,
|
||||
isExported: true,
|
||||
returnType: "Promise<User>",
|
||||
},
|
||||
],
|
||||
classes: [],
|
||||
interfaces: [],
|
||||
typeAliases: [],
|
||||
parseError: false,
|
||||
},
|
||||
],
|
||||
])
|
||||
|
||||
const context = buildInitialContext(structure, asts)
|
||||
|
||||
expect(context).toContain("- async getUser(id: string): Promise<User>")
|
||||
})
|
||||
|
||||
it("should format function with optional parameters", () => {
|
||||
const structure: ProjectStructure = {
|
||||
name: "test",
|
||||
rootPath: "/test",
|
||||
files: ["utils.ts"],
|
||||
directories: [],
|
||||
}
|
||||
const asts = new Map<string, FileAST>([
|
||||
[
|
||||
"utils.ts",
|
||||
{
|
||||
imports: [],
|
||||
exports: [],
|
||||
functions: [
|
||||
{
|
||||
name: "format",
|
||||
lineStart: 1,
|
||||
lineEnd: 5,
|
||||
params: [
|
||||
{
|
||||
name: "value",
|
||||
type: "string",
|
||||
optional: false,
|
||||
hasDefault: false,
|
||||
},
|
||||
{
|
||||
name: "options",
|
||||
type: "FormatOptions",
|
||||
optional: true,
|
||||
hasDefault: false,
|
||||
},
|
||||
],
|
||||
isAsync: false,
|
||||
isExported: true,
|
||||
returnType: "string",
|
||||
},
|
||||
],
|
||||
classes: [],
|
||||
interfaces: [],
|
||||
typeAliases: [],
|
||||
parseError: false,
|
||||
},
|
||||
],
|
||||
])
|
||||
|
||||
const context = buildInitialContext(structure, asts)
|
||||
|
||||
expect(context).toContain("- format(value: string, options?: FormatOptions): string")
|
||||
})
|
||||
|
||||
it("should format function with multiple typed parameters", () => {
|
||||
const structure: ProjectStructure = {
|
||||
name: "test",
|
||||
rootPath: "/test",
|
||||
files: ["api.ts"],
|
||||
directories: [],
|
||||
}
|
||||
const asts = new Map<string, FileAST>([
|
||||
[
|
||||
"api.ts",
|
||||
{
|
||||
imports: [],
|
||||
exports: [],
|
||||
functions: [
|
||||
{
|
||||
name: "createUser",
|
||||
lineStart: 1,
|
||||
lineEnd: 10,
|
||||
params: [
|
||||
{
|
||||
name: "name",
|
||||
type: "string",
|
||||
optional: false,
|
||||
hasDefault: false,
|
||||
},
|
||||
{
|
||||
name: "email",
|
||||
type: "string",
|
||||
optional: false,
|
||||
hasDefault: false,
|
||||
},
|
||||
{
|
||||
name: "age",
|
||||
type: "number",
|
||||
optional: true,
|
||||
hasDefault: false,
|
||||
},
|
||||
],
|
||||
isAsync: true,
|
||||
isExported: true,
|
||||
returnType: "Promise<User>",
|
||||
},
|
||||
],
|
||||
classes: [],
|
||||
interfaces: [],
|
||||
typeAliases: [],
|
||||
parseError: false,
|
||||
},
|
||||
],
|
||||
])
|
||||
|
||||
const context = buildInitialContext(structure, asts)
|
||||
|
||||
expect(context).toContain(
|
||||
"- async createUser(name: string, email: string, age?: number): Promise<User>",
|
||||
)
|
||||
})
|
||||
|
||||
it("should format function without types (JavaScript style)", () => {
|
||||
const structure: ProjectStructure = {
|
||||
name: "test",
|
||||
rootPath: "/test",
|
||||
files: ["legacy.js"],
|
||||
directories: [],
|
||||
}
|
||||
const asts = new Map<string, FileAST>([
|
||||
[
|
||||
"legacy.js",
|
||||
{
|
||||
imports: [],
|
||||
exports: [],
|
||||
functions: [
|
||||
{
|
||||
name: "doSomething",
|
||||
lineStart: 1,
|
||||
lineEnd: 5,
|
||||
params: [
|
||||
{ name: "x", optional: false, hasDefault: false },
|
||||
{ name: "y", optional: false, hasDefault: false },
|
||||
],
|
||||
isAsync: false,
|
||||
isExported: true,
|
||||
},
|
||||
],
|
||||
classes: [],
|
||||
interfaces: [],
|
||||
typeAliases: [],
|
||||
parseError: false,
|
||||
},
|
||||
],
|
||||
])
|
||||
|
||||
const context = buildInitialContext(structure, asts)
|
||||
|
||||
expect(context).toContain("- doSomething(x, y)")
|
||||
})
|
||||
|
||||
it("should format interface with extends", () => {
|
||||
const structure: ProjectStructure = {
|
||||
name: "test",
|
||||
rootPath: "/test",
|
||||
files: ["types.ts"],
|
||||
directories: [],
|
||||
}
|
||||
const asts = new Map<string, FileAST>([
|
||||
[
|
||||
"types.ts",
|
||||
{
|
||||
imports: [],
|
||||
exports: [],
|
||||
functions: [],
|
||||
classes: [],
|
||||
interfaces: [
|
||||
{
|
||||
name: "AdminUser",
|
||||
lineStart: 1,
|
||||
lineEnd: 5,
|
||||
properties: [],
|
||||
extends: ["User", "Admin"],
|
||||
isExported: true,
|
||||
},
|
||||
],
|
||||
typeAliases: [],
|
||||
parseError: false,
|
||||
},
|
||||
],
|
||||
])
|
||||
|
||||
const context = buildInitialContext(structure, asts)
|
||||
|
||||
expect(context).toContain("- interface AdminUser extends User, Admin")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -15,6 +15,7 @@ describe("ContextConfigSchema", () => {
|
||||
maxContextUsage: 0.8,
|
||||
autoCompressAt: 0.8,
|
||||
compressionMethod: "llm-summary",
|
||||
includeSignatures: true,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -26,6 +27,7 @@ describe("ContextConfigSchema", () => {
|
||||
maxContextUsage: 0.8,
|
||||
autoCompressAt: 0.8,
|
||||
compressionMethod: "llm-summary",
|
||||
includeSignatures: true,
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -162,6 +164,7 @@ describe("ContextConfigSchema", () => {
|
||||
maxContextUsage: 0.8,
|
||||
autoCompressAt: 0.8,
|
||||
compressionMethod: "llm-summary",
|
||||
includeSignatures: true,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -175,6 +178,7 @@ describe("ContextConfigSchema", () => {
|
||||
maxContextUsage: 0.8,
|
||||
autoCompressAt: 0.9,
|
||||
compressionMethod: "llm-summary",
|
||||
includeSignatures: true,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -189,6 +193,7 @@ describe("ContextConfigSchema", () => {
|
||||
maxContextUsage: 0.7,
|
||||
autoCompressAt: 0.8,
|
||||
compressionMethod: "truncate",
|
||||
includeSignatures: true,
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -200,6 +205,7 @@ describe("ContextConfigSchema", () => {
|
||||
maxContextUsage: 0.9,
|
||||
autoCompressAt: 0.85,
|
||||
compressionMethod: "truncate" as const,
|
||||
includeSignatures: false,
|
||||
}
|
||||
|
||||
const result = ContextConfigSchema.parse(config)
|
||||
@@ -212,10 +218,36 @@ describe("ContextConfigSchema", () => {
|
||||
maxContextUsage: 0.8,
|
||||
autoCompressAt: 0.8,
|
||||
compressionMethod: "llm-summary" as const,
|
||||
includeSignatures: true,
|
||||
}
|
||||
|
||||
const result = ContextConfigSchema.parse(config)
|
||||
expect(result).toEqual(config)
|
||||
})
|
||||
})
|
||||
|
||||
describe("includeSignatures", () => {
|
||||
it("should accept true", () => {
|
||||
const result = ContextConfigSchema.parse({ includeSignatures: true })
|
||||
expect(result.includeSignatures).toBe(true)
|
||||
})
|
||||
|
||||
it("should accept false", () => {
|
||||
const result = ContextConfigSchema.parse({ includeSignatures: false })
|
||||
expect(result.includeSignatures).toBe(false)
|
||||
})
|
||||
|
||||
it("should default to true", () => {
|
||||
const result = ContextConfigSchema.parse({})
|
||||
expect(result.includeSignatures).toBe(true)
|
||||
})
|
||||
|
||||
it("should reject non-boolean", () => {
|
||||
expect(() => ContextConfigSchema.parse({ includeSignatures: "true" })).toThrow()
|
||||
})
|
||||
|
||||
it("should reject number", () => {
|
||||
expect(() => ContextConfigSchema.parse({ includeSignatures: 1 })).toThrow()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user