feat(ipuaro): add interface fields and type alias definitions to context

- Add interface field display in initial context: interface User { id: string, name: string }
- Add type alias definition display: type UserId = string
- Support readonly fields, extends, union/intersection types
- Add definition field to TypeAliasInfo in FileAST
- Update ASTParser to extract type alias definitions
- Add formatInterfaceSignature() and formatTypeAliasSignature() helpers
- Truncate long type definitions at 80 characters
- Translate ROADMAP.md from Russian to English
- Add 18 new tests for interface fields and type aliases
This commit is contained in:
imfozilbek
2025-12-04 22:49:03 +05:00
parent 1489b69e69
commit 12197a9624
7 changed files with 711 additions and 129 deletions

View File

@@ -5,6 +5,72 @@ 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/), 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). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.25.0] - 2025-12-04 - Rich Initial Context: Interface Fields & Type Definitions
### Added
- **Interface Field Definitions (0.24.2)**
- Interfaces now show their fields in initial context
- New format: `interface User { id: string, name: string, email: string }`
- Readonly fields marked: `interface Config { readonly version: string }`
- Extends still supported: `interface AdminUser extends User { role: string }`
- **Type Alias Definitions (0.24.2)**
- Type aliases now show their definitions in initial context
- Simple types: `type UserId = string`
- Union types: `type Status = "pending" | "active" | "done"`
- Intersection types: `type AdminUser = User & Admin`
- Function types: `type Handler = (event: Event) => void`
- Long definitions truncated at 80 characters with `...`
- **New Helper Functions in prompts.ts**
- `formatInterfaceSignature()` - formats interface with fields
- `formatTypeAliasSignature()` - formats type alias with definition
- `truncateDefinition()` - truncates long type definitions
### Changed
- **FileAST.ts**
- Added `definition?: string` field to `TypeAliasInfo` interface
- **ASTParser.ts**
- `extractTypeAlias()` now extracts the type definition via `childForFieldName(VALUE)`
- Supports all type kinds: simple, union, intersection, object, function, generic, array, tuple
- **prompts.ts**
- `formatFileSummary()` now uses `formatInterfaceSignature()` for interfaces
- `formatFileSummary()` now uses `formatTypeAliasSignature()` for type aliases
### New Context Format
```
### src/types/user.ts
- interface User { id: string, name: string, email: string }
- interface UserDTO { name: string, email?: string }
- type UserId = string
- type Status = "pending" | "active" | "done"
- type AdminUser = User & Admin
```
### Technical Details
- Total tests: 1720 passed (was 1702, +18 new tests)
- 10 new tests for interface field formatting
- 8 new tests for type alias definition extraction
- Coverage: 97.5% lines, 91.04% branches, 98.6% functions
- 0 ESLint errors, 1 warning (pre-existing complexity in ASTParser)
- Build successful
### Notes
This completes the second part of 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.24.0] - 2025-12-04 - Rich Initial Context: Function Signatures ## [0.24.0] - 2025-12-04 - Rich Initial Context: Function Signatures
### Added ### Added

View File

@@ -1333,40 +1333,40 @@ class ErrorHandler {
**Priority:** HIGH **Priority:** HIGH
**Status:** Complete (v0.19.0 released) **Status:** Complete (v0.19.0 released)
Рефакторинг: переход на чистый XML формат для tool calls (как в CONCEPT.md). Refactoring: transition to pure XML format for tool calls (as in CONCEPT.md).
### Текущая проблема ### Current Problem
OllamaClient использует Ollama native tool calling (JSON Schema), а ResponseParser реализует XML парсинг. Это создаёт путаницу и не соответствует CONCEPT.md. OllamaClient uses Ollama native tool calling (JSON Schema), while ResponseParser implements XML parsing. This creates confusion and doesn't match CONCEPT.md.
### 0.19.1 - OllamaClient Refactor ### 0.19.1 - OllamaClient Refactor
```typescript ```typescript
// src/infrastructure/llm/OllamaClient.ts // src/infrastructure/llm/OllamaClient.ts
// БЫЛО: // BEFORE:
// - Передаём tools в Ollama SDK format // - Pass tools in Ollama SDK format
// - Извлекаем tool_calls из response.message.tool_calls // - Extract tool_calls from response.message.tool_calls
// СТАНЕТ: // AFTER:
// - НЕ передаём tools в SDK // - DON'T pass tools to SDK
// - Tools описаны в system prompt как XML // - Tools described in system prompt as XML
// - LLM возвращает XML в content // - LLM returns XML in content
// - Парсим через ResponseParser // - Parse via ResponseParser
``` ```
**Изменения:** **Changes:**
- [x] Удалить `convertTools()` метод - [x] Remove `convertTools()` method
- [x] Удалить `extractToolCalls()` метод - [x] Remove `extractToolCalls()` method
- [x] Убрать передачу `tools` в `client.chat()` - [x] Remove `tools` from `client.chat()` call
- [x] Возвращать только `content` без `toolCalls` - [x] Return only `content` without `toolCalls`
### 0.19.2 - System Prompt Update ### 0.19.2 - System Prompt Update
```typescript ```typescript
// src/infrastructure/llm/prompts.ts // src/infrastructure/llm/prompts.ts
// Добавить в SYSTEM_PROMPT полное описание XML формата: // Add full XML format description to SYSTEM_PROMPT:
const TOOL_FORMAT_INSTRUCTIONS = ` const TOOL_FORMAT_INSTRUCTIONS = `
## Tool Calling Format ## Tool Calling Format
@@ -1397,73 +1397,73 @@ Always wait for tool results before making conclusions.
` `
``` ```
**Изменения:** **Changes:**
- [x] Добавить `TOOL_FORMAT_INSTRUCTIONS` в prompts.ts - [x] Add `TOOL_FORMAT_INSTRUCTIONS` to prompts.ts
- [x] Включить в `SYSTEM_PROMPT` - [x] Include in `SYSTEM_PROMPT`
- [x] Добавить примеры для всех 18 tools - [x] Add examples for all 18 tools
### 0.19.3 - HandleMessage Simplification ### 0.19.3 - HandleMessage Simplification
```typescript ```typescript
// src/application/use-cases/HandleMessage.ts // src/application/use-cases/HandleMessage.ts
// БЫЛО: // BEFORE:
// const response = await this.llm.chat(messages) // const response = await this.llm.chat(messages)
// const parsed = parseToolCalls(response.content) // const parsed = parseToolCalls(response.content)
// СТАНЕТ: // AFTER:
// const response = await this.llm.chat(messages) // без tools // const response = await this.llm.chat(messages) // without tools
// const parsed = parseToolCalls(response.content) // единственный источник // const parsed = parseToolCalls(response.content) // single source
``` ```
**Изменения:** **Changes:**
- [x] Убрать передачу tool definitions в `llm.chat()` - [x] Remove tool definitions from `llm.chat()`
- [x] ResponseParser — единственный источник tool calls - [x] ResponseParser — single source of tool calls
- [x] Упростить логику обработки - [x] Simplify processing logic
### 0.19.4 - ILLMClient Interface Update ### 0.19.4 - ILLMClient Interface Update
```typescript ```typescript
// src/domain/services/ILLMClient.ts // src/domain/services/ILLMClient.ts
// БЫЛО: // BEFORE:
interface ILLMClient { interface ILLMClient {
chat(messages: ChatMessage[], tools?: ToolDef[]): Promise<LLMResponse> chat(messages: ChatMessage[], tools?: ToolDef[]): Promise<LLMResponse>
} }
// СТАНЕТ: // AFTER:
interface ILLMClient { interface ILLMClient {
chat(messages: ChatMessage[]): Promise<LLMResponse> chat(messages: ChatMessage[]): Promise<LLMResponse>
// tools больше не передаются - они в system prompt // tools no longer passed - they're in system prompt
} }
``` ```
**Изменения:** **Changes:**
- [x] Убрать `tools` параметр из `chat()` - [x] Remove `tools` parameter from `chat()`
- [x] Убрать `toolCalls` из `LLMResponse` (парсятся из content) - [x] Remove `toolCalls` from `LLMResponse` (parsed from content)
- [x] Обновить все реализации - [x] Update all implementations
### 0.19.5 - ResponseParser Enhancements ### 0.19.5 - ResponseParser Enhancements
```typescript ```typescript
// src/infrastructure/llm/ResponseParser.ts // src/infrastructure/llm/ResponseParser.ts
// Улучшения: // Improvements:
// - Лучшая обработка ошибок парсинга // - Better error handling for parsing
// - Поддержка CDATA для многострочного content // - CDATA support for multiline content
// - Валидация имён tools // - Tool name validation
``` ```
**Изменения:** **Changes:**
- [x] Добавить поддержку `<![CDATA[...]]>` для content - [x] Add `<![CDATA[...]]>` support for content
- [x] Валидация: tool name должен быть из известного списка - [x] Validation: tool name must be from known list
- [x] Улучшить сообщения об ошибках парсинга - [x] Improve parsing error messages
**Tests:** **Tests:**
- [x] Обновить тесты OllamaClient - [x] Update OllamaClient tests
- [x] Обновить тесты HandleMessage - [x] Update HandleMessage tests
- [x] Добавить тесты ResponseParser для edge cases - [x] Add ResponseParser tests for edge cases
- [ ] E2E тест полного flow с XML (опционально, может быть в 0.20.0) - [ ] E2E test for full XML flow (optional, may be in 0.20.0)
--- ---
@@ -1782,101 +1782,101 @@ export interface ScanResult {
## Version 0.24.0 - Rich Initial Context 📋 ## Version 0.24.0 - Rich Initial Context 📋
**Priority:** HIGH **Priority:** HIGH
**Status:** In Progress (1/4 complete) **Status:** In Progress (2/4 complete)
Улучшение initial context для LLM: добавление сигнатур функций, типов интерфейсов и значений enum. Это позволит LLM отвечать на вопросы о типах и параметрах без tool calls. Enhance initial context for LLM: add function signatures, interface field types, and enum values. This allows LLM to answer questions about types and parameters without tool calls.
### 0.24.1 - Function Signatures with Types ⭐ ✅ ### 0.24.1 - Function Signatures with Types ⭐ ✅
**Проблема:** Сейчас LLM видит только имена функций: `fn: getUser, createUser` **Problem:** Currently LLM only sees function names: `fn: getUser, createUser`
**Решение:** Показать полные сигнатуры: `async getUser(id: string): Promise<User>` **Solution:** Show full signatures: `async getUser(id: string): Promise<User>`
```typescript ```typescript
// src/infrastructure/llm/prompts.ts changes // src/infrastructure/llm/prompts.ts changes
// БЫЛО: // BEFORE:
// - src/services/user.ts [fn: getUser, createUser] // - src/services/user.ts [fn: getUser, createUser]
// СТАНЕТ: // AFTER:
// ### src/services/user.ts // ### src/services/user.ts
// - async getUser(id: string): Promise<User> // - async getUser(id: string): Promise<User>
// - async createUser(data: UserDTO): Promise<User> // - async createUser(data: UserDTO): Promise<User>
// - validateEmail(email: string): boolean // - validateEmail(email: string): boolean
``` ```
**Изменения:** **Changes:**
- [x] Расширить `FunctionInfo` в FileAST для хранения типов параметров и return type (already existed) - [x] Extend `FunctionInfo` in FileAST for parameter types and return type (already existed)
- [x] Обновить `ASTParser.ts` для извлечения типов параметров и return types (arrow functions fixed) - [x] Update `ASTParser.ts` to extract parameter types and return types (arrow functions fixed)
- [x] Обновить `formatFileSummary()` в prompts.ts для вывода сигнатур - [x] Update `formatFileSummary()` in prompts.ts to output signatures
- [x] Добавить опцию `includeSignatures: boolean` в config - [x] Add `includeSignatures: boolean` option to config
**Зачем:** LLM не будет галлюцинировать параметры и return types. **Why:** LLM won't hallucinate parameters and return types.
### 0.24.2 - Interface/Type Field Definitions ⭐ ### 0.24.2 - Interface/Type Field Definitions ⭐
**Проблема:** LLM видит только `interface: User, UserDTO` **Problem:** LLM only sees `interface: User, UserDTO`
**Решение:** Показать поля: `User { id: string, name: string, email: string }` **Solution:** Show fields: `User { id: string, name: string, email: string }`
```typescript ```typescript
// БЫЛО: // BEFORE:
// - src/types/user.ts [interface: User, UserDTO] // - src/types/user.ts [interface: User, UserDTO]
// СТАНЕТ: // AFTER:
// ### src/types/user.ts // ### src/types/user.ts
// - interface User { id: string, name: string, email: string, createdAt: Date } // - interface User { id: string, name: string, email: string, createdAt: Date }
// - interface UserDTO { name: string, email: string } // - interface UserDTO { name: string, email: string }
// - type UserId = string // - type UserId = string
``` ```
**Изменения:** **Changes:**
- [ ] Расширить `InterfaceInfo` в FileAST для хранения полей с типами - [x] Extend `InterfaceInfo` in FileAST for field types (already existed)
- [ ] Обновить `ASTParser.ts` для извлечения полей интерфейсов - [x] Update `ASTParser.ts` to extract interface fields (already existed)
- [ ] Обновить `formatFileSummary()` для вывода полей - [x] Update `formatFileSummary()` to output fields
- [ ] Обработка type aliases с их определениями - [x] Handle type aliases with their definitions
**Зачем:** LLM знает структуру данных, не придумывает поля. **Why:** LLM knows data structure, won't invent fields.
### 0.24.3 - Enum Value Definitions ### 0.24.3 - Enum Value Definitions
**Проблема:** LLM видит только `type: Status` **Problem:** LLM only sees `type: Status`
**Решение:** Показать значения: `Status { Active=1, Inactive=0, Pending=2 }` **Solution:** Show values: `Status { Active=1, Inactive=0, Pending=2 }`
```typescript ```typescript
// БЫЛО: // BEFORE:
// - src/types/enums.ts [type: Status, Role] // - src/types/enums.ts [type: Status, Role]
// СТАНЕТ: // AFTER:
// ### src/types/enums.ts // ### src/types/enums.ts
// - enum Status { Active=1, Inactive=0, Pending=2 } // - enum Status { Active=1, Inactive=0, Pending=2 }
// - enum Role { Admin="admin", User="user" } // - enum Role { Admin="admin", User="user" }
``` ```
**Изменения:** **Changes:**
- [ ] Добавить `EnumInfo` в FileAST с members и values - [ ] Add `EnumInfo` to FileAST with members and values
- [ ] Обновить `ASTParser.ts` для извлечения enum members - [ ] Update `ASTParser.ts` to extract enum members
- [ ] Обновить `formatFileSummary()` для вывода enum values - [ ] Update `formatFileSummary()` to output enum values
**Зачем:** LLM знает допустимые значения enum. **Why:** LLM knows valid enum values.
### 0.24.4 - Decorator Extraction ### 0.24.4 - Decorator Extraction
**Проблема:** LLM не видит декораторы (важно для NestJS, Angular) **Problem:** LLM doesn't see decorators (important for NestJS, Angular)
**Решение:** Показать декораторы в контексте **Solution:** Show decorators in context
```typescript ```typescript
// СТАНЕТ: // AFTER:
// ### src/controllers/user.controller.ts // ### src/controllers/user.controller.ts
// - @Controller('users') class UserController // - @Controller('users') class UserController
// - @Get(':id') async getUser(id: string): Promise<User> // - @Get(':id') async getUser(id: string): Promise<User>
// - @Post() @Body() async createUser(data: UserDTO): Promise<User> // - @Post() @Body() async createUser(data: UserDTO): Promise<User>
``` ```
**Изменения:** **Changes:**
- [ ] Добавить `decorators: string[]` в FunctionInfo и ClassInfo - [ ] Add `decorators: string[]` to FunctionInfo and ClassInfo
- [ ] Обновить `ASTParser.ts` для извлечения декораторов - [ ] Update `ASTParser.ts` to extract decorators
- [ ] Обновить контекст для отображения декораторов - [ ] Update context to display decorators
**Зачем:** LLM понимает роутинг, DI, guards в NestJS/Angular. **Why:** LLM understands routing, DI, guards in NestJS/Angular.
**Tests:** **Tests:**
- [ ] Unit tests for enhanced ASTParser - [ ] Unit tests for enhanced ASTParser
@@ -1890,15 +1890,15 @@ export interface ScanResult {
**Priority:** MEDIUM **Priority:** MEDIUM
**Status:** Planned **Status:** Planned
Добавление графовых метрик в initial context: граф зависимостей, circular dependencies, impact score. Add graph metrics to initial context: dependency graph, circular dependencies, impact score.
### 0.25.1 - Inline Dependency Graph ### 0.25.1 - Inline Dependency Graph
**Проблема:** LLM не видит связи между файлами без tool calls **Problem:** LLM doesn't see file relationships without tool calls
**Решение:** Показать граф зависимостей в контексте **Solution:** Show dependency graph in context
```typescript ```typescript
// Добавить в initial context: // Add to initial context:
// ## Dependency Graph // ## Dependency Graph
// src/services/user.ts: → types/user, utils/validation ← controllers/user, api/routes // src/services/user.ts: → types/user, utils/validation ← controllers/user, api/routes
@@ -1906,41 +1906,41 @@ export interface ScanResult {
// src/utils/validation.ts: ← services/user, services/auth, controllers/* // src/utils/validation.ts: ← services/user, services/auth, controllers/*
``` ```
**Изменения:** **Changes:**
- [ ] Добавить `formatDependencyGraph()` в prompts.ts - [ ] Add `formatDependencyGraph()` to prompts.ts
- [ ] Использовать данные из `FileMeta.dependencies` и `FileMeta.dependents` - [ ] Use data from `FileMeta.dependencies` and `FileMeta.dependents`
- [ ] Группировать по hub files (много connections) - [ ] Group by hub files (many connections)
- [ ] Добавить опцию `includeDepsGraph: boolean` в config - [ ] Add `includeDepsGraph: boolean` option to config
**Зачем:** LLM видит архитектуру без tool call. **Why:** LLM sees architecture without tool call.
### 0.25.2 - Circular Dependencies in Context ### 0.25.2 - Circular Dependencies in Context
**Проблема:** Circular deps вычисляются, но не показываются в контексте **Problem:** Circular deps are computed but not shown in context
**Решение:** Показать циклы сразу **Solution:** Show cycles immediately
```typescript ```typescript
// Добавить в initial context: // Add to initial context:
// ## ⚠️ Circular Dependencies // ## ⚠️ Circular Dependencies
// - services/user → services/auth → services/user // - services/user → services/auth → services/user
// - utils/a → utils/b → utils/c → utils/a // - utils/a → utils/b → utils/c → utils/a
``` ```
**Изменения:** **Changes:**
- [ ] Добавить `formatCircularDeps()` в prompts.ts - [ ] Add `formatCircularDeps()` to prompts.ts
- [ ] Получать circular deps из IndexBuilder - [ ] Get circular deps from IndexBuilder
- [ ] Хранить в Redis как отдельный ключ или в meta - [ ] Store in Redis as separate key or in meta
**Зачем:** LLM сразу видит проблемы архитектуры. **Why:** LLM immediately sees architecture problems.
### 0.25.3 - Impact Score ### 0.25.3 - Impact Score
**Проблема:** LLM не знает какие файлы критичные **Problem:** LLM doesn't know which files are critical
**Решение:** Показать impact score (% кодовой базы, который зависит от файла) **Solution:** Show impact score (% of codebase that depends on file)
```typescript ```typescript
// Добавить в initial context: // Add to initial context:
// ## High Impact Files // ## High Impact Files
// | File | Impact | Dependents | // | File | Impact | Dependents |
@@ -1950,32 +1950,32 @@ export interface ScanResult {
// | src/services/user.ts | 34% | 6 files | // | src/services/user.ts | 34% | 6 files |
``` ```
**Изменения:** **Changes:**
- [ ] Добавить `impactScore: number` в FileMeta (0-100) - [ ] Add `impactScore: number` to FileMeta (0-100)
- [ ] Вычислять в MetaAnalyzer: (transitiveDepByCount / totalFiles) * 100 - [ ] Compute in MetaAnalyzer: (transitiveDepByCount / totalFiles) * 100
- [ ] Добавить `formatHighImpactFiles()` в prompts.ts - [ ] Add `formatHighImpactFiles()` to prompts.ts
- [ ] Показывать top-10 high impact files - [ ] Show top-10 high impact files
**Зачем:** LLM понимает какие файлы критичные для изменений. **Why:** LLM understands which files are critical for changes.
### 0.25.4 - Transitive Dependencies Count ### 0.25.4 - Transitive Dependencies Count
**Проблема:** Сейчас считаем только прямые зависимости **Problem:** Currently only counting direct dependencies
**Решение:** Добавить транзитивные зависимости в meta **Solution:** Add transitive dependencies to meta
```typescript ```typescript
// FileMeta additions: // FileMeta additions:
interface FileMeta { interface FileMeta {
// existing... // existing...
transitiveDepCount: number; // сколько файлов зависит от этого (транзитивно) transitiveDepCount: number; // how many files depend on this (transitively)
transitiveDepByCount: number; // от скольких файлов зависит этот (транзитивно) transitiveDepByCount: number; // how many files this depends on (transitively)
} }
``` ```
**Изменения:** **Changes:**
- [ ] Добавить `computeTransitiveDeps()` в MetaAnalyzer - [ ] Add `computeTransitiveDeps()` to MetaAnalyzer
- [ ] Использовать DFS с memoization для эффективности - [ ] Use DFS with memoization for efficiency
- [ ] Сохранять в FileMeta - [ ] Store in FileMeta
**Tests:** **Tests:**
- [ ] Unit tests for graph metrics computation - [ ] Unit tests for graph metrics computation
@@ -2079,7 +2079,7 @@ sessions:list # List<session_id>
**Last Updated:** 2025-12-04 **Last Updated:** 2025-12-04
**Target Version:** 1.0.0 **Target Version:** 1.0.0
**Current Version:** 0.24.0 **Current Version:** 0.25.0
**Next Milestones:** v0.24.0 (Rich Context - 1/4 complete), v0.25.0 (Graph Metrics) **Next Milestones:** v0.24.0 (Rich Context - 2/4 complete), v0.25.0 (Graph Metrics)
> **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. > **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.

View File

@@ -129,6 +129,8 @@ export interface TypeAliasInfo {
line: number line: number
/** Whether it's exported */ /** Whether it's exported */
isExported: boolean isExported: boolean
/** Type definition (e.g., "string", "User & Admin", "{ id: string }") */
definition?: string
} }
export interface FileAST { export interface FileAST {

View File

@@ -554,10 +554,14 @@ export class ASTParser {
return return
} }
const valueNode = node.childForFieldName(FieldName.VALUE)
const definition = valueNode?.text
ast.typeAliases.push({ ast.typeAliases.push({
name: nameNode.text, name: nameNode.text,
line: node.startPosition.row + 1, line: node.startPosition.row + 1,
isExported, isExported,
definition,
}) })
} }

View File

@@ -203,6 +203,54 @@ function formatFunctionSignature(fn: FileAST["functions"][0]): string {
return `${asyncPrefix}${fn.name}(${params})${returnType}` return `${asyncPrefix}${fn.name}(${params})${returnType}`
} }
/**
* Format an interface signature with fields.
* Example: "interface User extends Base { id: string, name: string, email?: string }"
*/
function formatInterfaceSignature(iface: FileAST["interfaces"][0]): string {
const extList = iface.extends ?? []
const ext = extList.length > 0 ? ` extends ${extList.join(", ")}` : ""
if (iface.properties.length === 0) {
return `interface ${iface.name}${ext}`
}
const fields = iface.properties
.map((p) => {
const readonly = p.isReadonly ? "readonly " : ""
const optional = p.name.endsWith("?") ? "" : ""
const type = p.type ? `: ${p.type}` : ""
return `${readonly}${p.name}${optional}${type}`
})
.join(", ")
return `interface ${iface.name}${ext} { ${fields} }`
}
/**
* Format a type alias signature with definition.
* Example: "type UserId = string" or "type Handler = (event: Event) => void"
*/
function formatTypeAliasSignature(type: FileAST["typeAliases"][0]): string {
if (!type.definition) {
return `type ${type.name}`
}
const definition = truncateDefinition(type.definition, 80)
return `type ${type.name} = ${definition}`
}
/**
* Truncate long type definitions for display.
*/
function truncateDefinition(definition: string, maxLength: number): string {
const normalized = definition.replace(/\s+/g, " ").trim()
if (normalized.length <= maxLength) {
return normalized
}
return `${normalized.slice(0, maxLength - 3)}...`
}
/** /**
* Format a single file's AST summary. * Format a single file's AST summary.
* When includeSignatures is true, shows full function signatures. * When includeSignatures is true, shows full function signatures.
@@ -239,15 +287,13 @@ function formatFileSummary(
if (ast.interfaces.length > 0) { if (ast.interfaces.length > 0) {
for (const iface of ast.interfaces) { for (const iface of ast.interfaces) {
const extList = iface.extends ?? [] lines.push(`- ${formatInterfaceSignature(iface)}`)
const ext = extList.length > 0 ? ` extends ${extList.join(", ")}` : ""
lines.push(`- interface ${iface.name}${ext}`)
} }
} }
if (ast.typeAliases.length > 0) { if (ast.typeAliases.length > 0) {
for (const type of ast.typeAliases) { for (const type of ast.typeAliases) {
lines.push(`- type ${type.name}`) lines.push(`- ${formatTypeAliasSignature(type)}`)
} }
} }

View File

@@ -224,6 +224,62 @@ describe("ASTParser", () => {
const ast = parser.parse(code, "ts") const ast = parser.parse(code, "ts")
expect(ast.typeAliases[0].isExported).toBe(true) expect(ast.typeAliases[0].isExported).toBe(true)
}) })
it("should extract type alias definition (simple)", () => {
const code = `type UserId = string`
const ast = parser.parse(code, "ts")
expect(ast.typeAliases).toHaveLength(1)
expect(ast.typeAliases[0].definition).toBe("string")
})
it("should extract type alias definition (union)", () => {
const code = `type Status = "pending" | "active" | "done"`
const ast = parser.parse(code, "ts")
expect(ast.typeAliases).toHaveLength(1)
expect(ast.typeAliases[0].definition).toBe('"pending" | "active" | "done"')
})
it("should extract type alias definition (intersection)", () => {
const code = `type AdminUser = User & Admin`
const ast = parser.parse(code, "ts")
expect(ast.typeAliases).toHaveLength(1)
expect(ast.typeAliases[0].definition).toBe("User & Admin")
})
it("should extract type alias definition (object type)", () => {
const code = `type Point = { x: number; y: number }`
const ast = parser.parse(code, "ts")
expect(ast.typeAliases).toHaveLength(1)
expect(ast.typeAliases[0].definition).toBe("{ x: number; y: number }")
})
it("should extract type alias definition (function type)", () => {
const code = `type Handler = (event: Event) => void`
const ast = parser.parse(code, "ts")
expect(ast.typeAliases).toHaveLength(1)
expect(ast.typeAliases[0].definition).toBe("(event: Event) => void")
})
it("should extract type alias definition (generic)", () => {
const code = `type Result<T> = { success: boolean; data: T }`
const ast = parser.parse(code, "ts")
expect(ast.typeAliases).toHaveLength(1)
expect(ast.typeAliases[0].definition).toBe("{ success: boolean; data: T }")
})
it("should extract type alias definition (array)", () => {
const code = `type UserIds = string[]`
const ast = parser.parse(code, "ts")
expect(ast.typeAliases).toHaveLength(1)
expect(ast.typeAliases[0].definition).toBe("string[]")
})
it("should extract type alias definition (tuple)", () => {
const code = `type Pair = [string, number]`
const ast = parser.parse(code, "ts")
expect(ast.typeAliases).toHaveLength(1)
expect(ast.typeAliases[0].definition).toBe("[string, number]")
})
}) })
describe("exports", () => { describe("exports", () => {

View File

@@ -1086,4 +1086,412 @@ describe("prompts", () => {
expect(context).toContain("- interface AdminUser extends User, Admin") expect(context).toContain("- interface AdminUser extends User, Admin")
}) })
}) })
describe("interface fields (0.24.2)", () => {
it("should format interface with fields", () => {
const structure: ProjectStructure = {
name: "test",
rootPath: "/test",
files: ["user.ts"],
directories: [],
}
const asts = new Map<string, FileAST>([
[
"user.ts",
{
imports: [],
exports: [],
functions: [],
classes: [],
interfaces: [
{
name: "User",
lineStart: 1,
lineEnd: 5,
properties: [
{
name: "id",
line: 2,
type: "string",
visibility: "public",
isStatic: false,
isReadonly: false,
},
{
name: "name",
line: 3,
type: "string",
visibility: "public",
isStatic: false,
isReadonly: false,
},
{
name: "email",
line: 4,
type: "string",
visibility: "public",
isStatic: false,
isReadonly: false,
},
],
extends: [],
isExported: true,
},
],
typeAliases: [],
parseError: false,
},
],
])
const context = buildInitialContext(structure, asts)
expect(context).toContain("interface User { id: string, name: string, email: string }")
})
it("should format interface with extends and fields", () => {
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: [
{
name: "role",
line: 2,
type: "string",
visibility: "public",
isStatic: false,
isReadonly: false,
},
],
extends: ["User"],
isExported: true,
},
],
typeAliases: [],
parseError: false,
},
],
])
const context = buildInitialContext(structure, asts)
expect(context).toContain(
"interface AdminUser extends User { role: string }",
)
})
it("should format interface with readonly fields", () => {
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: "Config",
lineStart: 1,
lineEnd: 3,
properties: [
{
name: "version",
line: 2,
type: "string",
visibility: "public",
isStatic: false,
isReadonly: true,
},
],
extends: [],
isExported: true,
},
],
typeAliases: [],
parseError: false,
},
],
])
const context = buildInitialContext(structure, asts)
expect(context).toContain("interface Config { readonly version: string }")
})
it("should format interface with no type annotation", () => {
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: "Loose",
lineStart: 1,
lineEnd: 3,
properties: [
{
name: "data",
line: 2,
visibility: "public",
isStatic: false,
isReadonly: false,
},
],
extends: [],
isExported: true,
},
],
typeAliases: [],
parseError: false,
},
],
])
const context = buildInitialContext(structure, asts)
expect(context).toContain("interface Loose { data }")
})
})
describe("type alias definitions (0.24.2)", () => {
it("should format type alias with definition", () => {
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: "UserId",
line: 1,
isExported: true,
definition: "string",
},
],
parseError: false,
},
],
])
const context = buildInitialContext(structure, asts)
expect(context).toContain("- type UserId = string")
})
it("should format union type alias", () => {
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: "Status",
line: 1,
isExported: true,
definition: '"pending" | "active" | "done"',
},
],
parseError: false,
},
],
])
const context = buildInitialContext(structure, asts)
expect(context).toContain('- type Status = "pending" | "active" | "done"')
})
it("should format intersection type alias", () => {
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: "AdminUser",
line: 1,
isExported: true,
definition: "User & Admin",
},
],
parseError: false,
},
],
])
const context = buildInitialContext(structure, asts)
expect(context).toContain("- type AdminUser = User & Admin")
})
it("should truncate long type definitions", () => {
const structure: ProjectStructure = {
name: "test",
rootPath: "/test",
files: ["types.ts"],
directories: [],
}
const longDefinition =
"{ id: string, name: string, email: string, phone: string, address: string, city: string, country: string, zip: string }"
const asts = new Map<string, FileAST>([
[
"types.ts",
{
imports: [],
exports: [],
functions: [],
classes: [],
interfaces: [],
typeAliases: [
{
name: "BigType",
line: 1,
isExported: true,
definition: longDefinition,
},
],
parseError: false,
},
],
])
const context = buildInitialContext(structure, asts)
expect(context).toContain("- type BigType = { id: string")
expect(context).toContain("...")
})
it("should format type alias without definition (fallback)", () => {
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: "Unknown",
line: 1,
isExported: true,
},
],
parseError: false,
},
],
])
const context = buildInitialContext(structure, asts)
expect(context).toContain("- type Unknown")
expect(context).not.toContain("- type Unknown =")
})
it("should format function type alias", () => {
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: "Handler",
line: 1,
isExported: true,
definition: "(event: Event) => void",
},
],
parseError: false,
},
],
])
const context = buildInitialContext(structure, asts)
expect(context).toContain("- type Handler = (event: Event) => void")
})
})
}) })