Compare commits

...

13 Commits

Author SHA1 Message Date
imfozilbek
d63d85d850 feat(ipuaro): add inline dependency graph to initial context
- Add formatDependencyGraph() to show file relationships in LLM context
- Add includeDepsGraph option to ContextConfigSchema (default: true)
- Format: "services/user: → types/user ← controllers/user"
- Hub files shown first, sorted by total connections
- 21 new tests for dependency graph functionality
2025-12-05 14:38:45 +05:00
imfozilbek
41cfc21f20 docs(ipuaro): align roadmap versions with package versions 2025-12-05 14:20:14 +05:00
imfozilbek
eeaa223436 chore(ipuaro): release v0.26.0 2025-12-05 13:51:13 +05:00
imfozilbek
36768c06d1 feat(ipuaro): add decorator extraction to initial context
Extract decorators from classes and methods for NestJS/Angular support.
Decorators are now shown in initial context:
- @Controller('users') class UserController
- @Get(':id') async getUser(id: string): Promise<User>

Changes:
- Add decorators field to FunctionInfo, MethodInfo, ClassInfo
- Update ASTParser to extract decorators from tree-sitter nodes
- Update formatFileSummary to display decorators prefix
- Add 18 unit tests for decorator extraction and formatting
2025-12-05 13:38:46 +05:00
imfozilbek
5a22cd5c9b feat(ipuaro): add enum value definitions to initial context
Extract enum declarations with member names and values from TypeScript
AST and display them in the initial LLM context. This allows the LLM
to know valid enum values without making tool calls.

Features:
- Numeric values (Active=1)
- String values (Admin="admin")
- Implicit values (Up, Down)
- Negative numbers (Cold=-10)
- const enum modifier
- export enum modifier
- Long enum truncation (>100 chars)

Adds EnumInfo and EnumMemberInfo interfaces, extractEnum() method in
ASTParser, formatEnumSignature() in prompts.ts, and 17 new unit tests.
2025-12-05 13:14:51 +05:00
imfozilbek
806c9281b0 chore(ipuaro): release v0.25.0 2025-12-04 22:49:35 +05:00
imfozilbek
12197a9624 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
2025-12-04 22:49:03 +05:00
imfozilbek
1489b69e69 chore(ipuaro): release v0.24.0 2025-12-04 22:29:31 +05:00
imfozilbek
2dcb22812c feat(ipuaro): add function signatures to initial context
- Add full function signatures with parameter types and return types
- Arrow functions now extract returnType in ASTParser
- New formatFunctionSignature() helper in prompts.ts
- Add includeSignatures config option (default: true)
- Support compact format when includeSignatures: false
- 15 new tests, coverage 91.14% branches
2025-12-04 22:29:02 +05:00
imfozilbek
7d7c99fe4d docs(ipuaro): add v0.24.0 and v0.25.0 to roadmap for rich context
Add two new milestones before 1.0.0 release:

v0.24.0 - Rich Initial Context:
- Function signatures with types
- Interface/Type field definitions
- Enum value definitions
- Decorator extraction

v0.25.0 - Graph Metrics in Context:
- Inline dependency graph
- Circular dependencies display
- Impact score for critical files
- Transitive dependencies count

Update 1.0.0 checklist to require both milestones.
Update context budget table with new token estimates.
2025-12-04 22:07:38 +05:00
imfozilbek
a3f0ba948f chore(ipuaro): release v0.23.0 2025-12-04 19:59:36 +05:00
imfozilbek
141888bf59 feat(ipuaro): add JSON/YAML parsing and symlinks metadata
- Add YAML parsing using yaml npm package
- Add JSON parsing using tree-sitter-json
- Add symlinkTarget to ScanResult interface
- Update ROADMAP: verify v0.20.0-v0.23.0 complete
- Add 8 new tests (1687 total)
2025-12-04 19:57:06 +05:00
imfozilbek
b0f1778f3a docs(guardian): add research citations for 15 roadmap features
Add comprehensive research citations for upcoming features:
- Domain Event Usage Validation (Section 15)
- Value Object Immutability (Section 16)
- CQS/CQRS (Section 17)
- Factory Pattern (Section 18)
- Specification Pattern (Section 19)
- Bounded Context (Section 20)
- Persistence Ignorance (Section 21)
- Null Object Pattern (Section 22)
- Primitive Obsession (Section 23)
- Service Locator Anti-pattern (Section 24)
- Double Dispatch/Visitor Pattern (Section 25)
- Entity Identity (Section 26)
- Saga Pattern (Section 27)
- Anti-Corruption Layer (Section 28)
- Ubiquitous Language (Section 29)

Sources include: GoF Design Patterns, Bertrand Meyer, Eric Evans,
Vaughn Vernon, Martin Fowler, Chris Richardson, Mark Seemann,
and academic papers (Garcia-Molina Sagas 1987).

Document version: 1.1 → 2.0
2025-12-04 19:11:54 +05:00
16 changed files with 4363 additions and 175 deletions

View File

@@ -20,6 +20,21 @@ This document provides authoritative sources, academic papers, industry standard
12. [Aggregate Boundary Validation (DDD Tactical Patterns)](#12-aggregate-boundary-validation-ddd-tactical-patterns)
13. [Secret Detection & Security](#13-secret-detection--security)
14. [Severity-Based Prioritization & Technical Debt](#14-severity-based-prioritization--technical-debt)
15. [Domain Event Usage Validation](#15-domain-event-usage-validation)
16. [Value Object Immutability](#16-value-object-immutability)
17. [Command Query Separation (CQS/CQRS)](#17-command-query-separation-cqscqrs)
18. [Factory Pattern](#18-factory-pattern)
19. [Specification Pattern](#19-specification-pattern)
20. [Bounded Context](#20-bounded-context)
21. [Persistence Ignorance](#21-persistence-ignorance)
22. [Null Object Pattern](#22-null-object-pattern)
23. [Primitive Obsession](#23-primitive-obsession)
24. [Service Locator Anti-pattern](#24-service-locator-anti-pattern)
25. [Double Dispatch and Visitor Pattern](#25-double-dispatch-and-visitor-pattern)
26. [Entity Identity](#26-entity-identity)
27. [Saga Pattern](#27-saga-pattern)
28. [Anti-Corruption Layer](#28-anti-corruption-layer)
29. [Ubiquitous Language](#29-ubiquitous-language)
---
@@ -801,22 +816,840 @@ This document provides authoritative sources, academic papers, industry standard
---
## 15. Domain Event Usage Validation
### Eric Evans: Domain-Driven Design (2003)
**Original Definition:**
- Domain Events: "Something happened that domain experts care about"
- Events capture facts about the domain that have already occurred
- Distinct from system events - they model business-relevant occurrences
- Reference: [Martin Fowler - Domain Event](https://martinfowler.com/eaaDev/DomainEvent.html)
**Book: Domain-Driven Design** (2003)
- Author: Eric Evans
- Publisher: Addison-Wesley Professional
- ISBN: 978-0321125217
- Domain Events weren't explicitly in the original book but evolved from DDD community
- Reference: [DDD Community - Domain Events](https://www.domainlanguage.com/)
### Vaughn Vernon: Implementing Domain-Driven Design (2013)
**Chapter 8: Domain Events**
- Author: Vaughn Vernon
- Comprehensive coverage of Domain Events implementation
- "Model information about activity in the domain as a series of discrete events"
- Reference: [Amazon - Implementing DDD](https://www.amazon.com/Implementing-Domain-Driven-Design-Vaughn-Vernon/dp/0321834577)
**Key Principles:**
- Events should be immutable
- Named in past tense (OrderPlaced, UserRegistered)
- Contain all data needed by handlers
- Enable loose coupling between aggregates
### Martin Fowler's Event Patterns
**Event Sourcing:**
- "Capture all changes to an application state as a sequence of events"
- Events become the primary source of truth
- Reference: [Martin Fowler - Event Sourcing](https://martinfowler.com/eaaDev/EventSourcing.html)
**Event-Driven Architecture:**
- Promotes loose coupling between components
- Enables asynchronous processing
- Reference: [Martin Fowler - Event-Driven](https://martinfowler.com/articles/201701-event-driven.html)
### Why Direct Infrastructure Calls Are Bad
**Coupling Issues:**
- Direct calls create tight coupling between domain and infrastructure
- Makes testing difficult (need to mock infrastructure)
- Violates Single Responsibility Principle
- Reference: [Microsoft - Domain Events Design](https://learn.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/domain-events-design-implementation)
**Benefits of Domain Events:**
- Decouples domain from side effects
- Enables eventual consistency
- Improves testability
- Supports audit logging naturally
- Reference: [Jimmy Bogard - Domain Events](https://lostechies.com/jimmybogard/2010/04/08/strengthening-your-domain-domain-events/)
---
## 16. Value Object Immutability
### Eric Evans: Domain-Driven Design (2003)
**Value Object Definition:**
- "An object that describes some characteristic or attribute but carries no concept of identity"
- "Value Objects should be immutable"
- When you care only about the attributes of an element, classify it as a Value Object
- Reference: [Martin Fowler - Value Object](https://martinfowler.com/bliki/ValueObject.html)
**Immutability Requirement:**
- "Treat the Value Object as immutable"
- "Don't give it any identity and avoid the design complexities necessary to maintain Entities"
- Reference: [DDD Reference - Value Objects](https://www.domainlanguage.com/wp-content/uploads/2016/05/DDD_Reference_2015-03.pdf)
### Martin Fowler on Value Objects
**Blog Post: Value Object** (2016)
- "A small simple object, like money or a date range, whose equality isn't based on identity"
- "I consider value objects to be one of the most important building blocks of good domain models"
- Reference: [Martin Fowler - Value Object](https://martinfowler.com/bliki/ValueObject.html)
**Key Properties:**
- Equality based on attribute values, not identity
- Should be immutable (once created, cannot be changed)
- Side-effect free behavior
- Self-validating (validate in constructor)
### Vaughn Vernon: Implementing DDD
**Chapter 6: Value Objects**
- Detailed implementation guidance
- "Measures, quantifies, or describes a thing in the domain"
- "Can be compared with other Value Objects using value equality"
- "Completely replaceable when the measurement changes"
- Reference: [Vaughn Vernon - Implementing DDD](https://www.amazon.com/Implementing-Domain-Driven-Design-Vaughn-Vernon/dp/0321834577)
### Why Immutability Matters
**Thread Safety:**
- Immutable objects are inherently thread-safe
- No synchronization needed for concurrent access
- Reference: [Effective Java - Item 17](https://www.amazon.com/Effective-Java-Joshua-Bloch/dp/0134685997)
**Reasoning About Code:**
- Easier to understand code when objects don't change
- No defensive copying needed
- Simplifies caching and optimization
- Reference: [Oracle Java Tutorials - Immutable Objects](https://docs.oracle.com/javase/tutorial/essential/concurrency/immutable.html)
**Functional Programming Influence:**
- Immutability is a core principle of functional programming
- Reduces side effects and makes code more predictable
- Reference: [Wikipedia - Immutable Object](https://en.wikipedia.org/wiki/Immutable_object)
---
## 17. Command Query Separation (CQS/CQRS)
### Bertrand Meyer: Original CQS Principle
**Book: Object-Oriented Software Construction** (1988, 2nd Ed. 1997)
- Author: Bertrand Meyer
- Publisher: Prentice Hall
- ISBN: 978-0136291558
- Introduced Command Query Separation principle
- Reference: [Wikipedia - CQS](https://en.wikipedia.org/wiki/Command%E2%80%93query_separation)
**CQS Principle:**
- "Every method should either be a command that performs an action, or a query that returns data to the caller, but not both"
- Commands: change state, return nothing (void)
- Queries: return data, change nothing (side-effect free)
- Reference: [Martin Fowler - CommandQuerySeparation](https://martinfowler.com/bliki/CommandQuerySeparation.html)
### Greg Young: CQRS Pattern
**CQRS Documents** (2010)
- Author: Greg Young
- Extended CQS to architectural pattern
- "CQRS is simply the creation of two objects where there was previously only one"
- Reference: [Greg Young - CQRS Documents](https://cqrs.files.wordpress.com/2010/11/cqrs_documents.pdf)
**Key Concepts:**
- Separate models for reading and writing
- Write model (commands) optimized for business logic
- Read model (queries) optimized for display/reporting
- Reference: [Microsoft - CQRS Pattern](https://learn.microsoft.com/en-us/azure/architecture/patterns/cqrs)
### Martin Fowler on CQRS
**Blog Post: CQRS** (2011)
- "At its heart is the notion that you can use a different model to update information than the model you use to read information"
- Warns against overuse: "CQRS is a significant mental leap for all concerned"
- Reference: [Martin Fowler - CQRS](https://martinfowler.com/bliki/CQRS.html)
### Benefits and Trade-offs
**Benefits:**
- Independent scaling of read and write workloads
- Optimized data schemas for each side
- Improved security (separate read/write permissions)
- Reference: [AWS - CQRS Pattern](https://docs.aws.amazon.com/prescriptive-guidance/latest/modernization-data-persistence/cqrs-pattern.html)
**Trade-offs:**
- Increased complexity
- Eventual consistency challenges
- More code to maintain
- Reference: [Microsoft - CQRS Considerations](https://learn.microsoft.com/en-us/azure/architecture/patterns/cqrs#issues-and-considerations)
---
## 18. Factory Pattern
### Gang of Four: Design Patterns (1994)
**Book: Design Patterns: Elements of Reusable Object-Oriented Software**
- Authors: Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides (Gang of Four)
- Publisher: Addison-Wesley
- ISBN: 978-0201633610
- Defines Factory Method and Abstract Factory patterns
- Reference: [Wikipedia - Design Patterns](https://en.wikipedia.org/wiki/Design_Patterns)
**Factory Method Pattern:**
- "Define an interface for creating an object, but let subclasses decide which class to instantiate"
- Lets a class defer instantiation to subclasses
- Reference: [Refactoring Guru - Factory Method](https://refactoring.guru/design-patterns/factory-method)
**Abstract Factory Pattern:**
- "Provide an interface for creating families of related or dependent objects without specifying their concrete classes"
- Reference: [Refactoring Guru - Abstract Factory](https://refactoring.guru/design-patterns/abstract-factory)
### Eric Evans: Factory in DDD Context
**Domain-Driven Design** (2003)
- Chapter 6: "The Life Cycle of a Domain Object"
- Factories encapsulate complex object creation
- "Shift the responsibility for creating instances of complex objects and Aggregates to a separate object"
- Reference: [DDD Reference](https://www.domainlanguage.com/wp-content/uploads/2016/05/DDD_Reference_2015-03.pdf)
**DDD Factory Guidelines:**
- Factory should create valid objects (invariants satisfied)
- Two types: Factory for new objects, Factory for reconstitution
- Keep creation logic out of the entity itself
- Reference: Already in Section 10 - Domain-Driven Design
### Why Factories Matter in DDD
**Encapsulation of Creation Logic:**
- Complex aggregates need coordinated creation
- Business rules should be enforced at creation time
- Clients shouldn't know construction details
- Reference: [Vaughn Vernon - Implementing DDD, Chapter 11](https://www.amazon.com/Implementing-Domain-Driven-Design-Vaughn-Vernon/dp/0321834577)
**Factory vs Constructor:**
- Constructors should be simple (assign values)
- Factories handle complex creation logic
- Factories can return different types
- Reference: [Effective Java - Item 1: Static Factory Methods](https://www.amazon.com/Effective-Java-Joshua-Bloch/dp/0134685997)
---
## 19. Specification Pattern
### Eric Evans & Martin Fowler
**Original Paper: Specifications** (1997)
- Authors: Eric Evans and Martin Fowler
- Introduced the Specification pattern
- "A Specification states a constraint on the state of another object"
- Reference: [Martin Fowler - Specification](https://martinfowler.com/apsupp/spec.pdf)
**Domain-Driven Design** (2003)
- Chapter 9: "Making Implicit Concepts Explicit"
- Specifications make business rules explicit and reusable
- "Create explicit predicate-like Value Objects for specialized purposes"
- Reference: [DDD Reference](https://www.domainlanguage.com/wp-content/uploads/2016/05/DDD_Reference_2015-03.pdf)
### Pattern Definition
**Core Concept:**
- Specification is a predicate that determines if an object satisfies some criteria
- Encapsulates business rules that can be reused and combined
- Reference: [Wikipedia - Specification Pattern](https://en.wikipedia.org/wiki/Specification_pattern)
**Three Main Uses:**
1. **Selection**: Finding objects that match criteria
2. **Validation**: Checking if object satisfies rules
3. **Construction**: Describing what needs to be created
- Reference: [Martin Fowler - Specification](https://martinfowler.com/apsupp/spec.pdf)
### Composite Specifications
**Combining Specifications:**
- AND: Both specifications must be satisfied
- OR: Either specification must be satisfied
- NOT: Specification must not be satisfied
- Reference: [Refactoring Guru - Specification Pattern](https://refactoring.guru/design-patterns/specification)
**Benefits:**
- Reusable business rules
- Testable in isolation
- Readable domain language
- Composable for complex rules
- Reference: [Enterprise Craftsmanship - Specification Pattern](https://enterprisecraftsmanship.com/posts/specification-pattern-c-implementation/)
---
## 20. Bounded Context
### Eric Evans: Domain-Driven Design (2003)
**Original Definition:**
- "A Bounded Context delimits the applicability of a particular model"
- "Explicitly define the context within which a model applies"
- Chapter 14: "Maintaining Model Integrity"
- Reference: [Martin Fowler - Bounded Context](https://martinfowler.com/bliki/BoundedContext.html)
**Key Principles:**
- Each Bounded Context has its own Ubiquitous Language
- Same term can mean different things in different contexts
- Models should not be shared across context boundaries
- Reference: [DDD Reference](https://www.domainlanguage.com/wp-content/uploads/2016/05/DDD_Reference_2015-03.pdf)
### Vaughn Vernon: Strategic Design
**Implementing Domain-Driven Design** (2013)
- Chapter 2: "Domains, Subdomains, and Bounded Contexts"
- Detailed guidance on identifying and mapping contexts
- Reference: [Vaughn Vernon - Implementing DDD](https://www.amazon.com/Implementing-Domain-Driven-Design-Vaughn-Vernon/dp/0321834577)
**Context Mapping Patterns:**
- Shared Kernel
- Customer/Supplier
- Conformist
- Anti-Corruption Layer
- Open Host Service / Published Language
- Reference: [Context Mapping Patterns](https://www.infoq.com/articles/ddd-contextmapping/)
### Why Bounded Contexts Matter
**Avoiding Big Ball of Mud:**
- Without explicit boundaries, models become entangled
- Different teams step on each other's models
- Reference: [Wikipedia - Big Ball of Mud](https://en.wikipedia.org/wiki/Big_ball_of_mud)
**Microservices and Bounded Contexts:**
- "Microservices should be designed around business capabilities, aligned with bounded contexts"
- Each microservice typically represents one bounded context
- Reference: [Microsoft - Microservices and Bounded Contexts](https://learn.microsoft.com/en-us/azure/architecture/microservices/model/domain-analysis)
### Cross-Context Communication
**Integration Patterns:**
- Never share domain models across contexts
- Use integration events or APIs
- Translate between context languages
- Reference: [Microsoft - Tactical DDD](https://learn.microsoft.com/en-us/azure/architecture/microservices/model/tactical-ddd)
---
## 21. Persistence Ignorance
### Definition and Principles
**Core Concept:**
- Domain objects should have no knowledge of how they are persisted
- Business logic remains pure and testable
- Infrastructure concerns are separated from domain
- Reference: [Microsoft - Persistence Ignorance](https://learn.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/infrastructure-persistence-layer-design#the-persistence-ignorance-principle)
**Wikipedia Definition:**
- "Persistence ignorance is the ability of a class to be used without any underlying persistence mechanism"
- Objects don't know if/how they'll be stored
- Reference: [Wikipedia - Persistence Ignorance](https://en.wikipedia.org/wiki/Persistence_ignorance)
### Eric Evans: DDD and Persistence
**Domain-Driven Design** (2003)
- Repositories abstract away persistence details
- Domain model should not reference ORM or database concepts
- Reference: Already covered in Section 6 - Repository Pattern
**Key Quote:**
- "The domain layer should be kept clean of all technical concerns"
- ORM annotations violate this principle
- Reference: [Clean Architecture and DDD](https://herbertograca.com/2017/11/16/explicit-architecture-01-ddd-hexagonal-onion-clean-cqrs-how-i-put-it-all-together/)
### Clean Architecture Alignment
**Robert C. Martin:**
- "The database is a detail"
- Domain entities should not depend on persistence frameworks
- Use Repository interfaces to abstract persistence
- Reference: [Clean Architecture Book](https://www.amazon.com/Clean-Architecture-Craftsmans-Software-Structure/dp/0134494164)
### Practical Implementation
**Two-Model Approach:**
- Domain Model: Pure business objects
- Persistence Model: ORM-annotated entities
- Mappers translate between them
- Reference: [Microsoft - Infrastructure Layer](https://learn.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/infrastructure-persistence-layer-design)
**Benefits:**
- Domain model can evolve independently of database schema
- Easier testing (no ORM required)
- Database can be changed without affecting domain
- Reference: [Enterprise Craftsmanship - Persistence Ignorance](https://enterprisecraftsmanship.com/posts/persistence-ignorance/)
---
## 22. Null Object Pattern
### Original Pattern
**Pattern Languages of Program Design 3** (1997)
- Author: Bobby Woolf
- Chapter: "Null Object"
- Publisher: Addison-Wesley
- ISBN: 978-0201310115
- Reference: [Wikipedia - Null Object Pattern](https://en.wikipedia.org/wiki/Null_object_pattern)
**Definition:**
- "A Null Object provides a 'do nothing' behavior, hiding the details from its collaborators"
- Replaces null checks with polymorphism
- Reference: [Refactoring Guru - Null Object](https://refactoring.guru/introduce-null-object)
### Martin Fowler's Coverage
**Refactoring Book** (1999, 2018)
- "Introduce Null Object" refactoring
- "Replace conditional logic that checks for null with a null object"
- Reference: [Refactoring Catalog](https://refactoring.com/catalog/introduceNullObject.html)
**Special Case Pattern:**
- More general pattern that includes Null Object
- "A subclass that provides special behavior for particular cases"
- Reference: [Martin Fowler - Special Case](https://martinfowler.com/eaaCatalog/specialCase.html)
### Benefits
**Eliminates Null Checks:**
- Reduces cyclomatic complexity
- Cleaner, more readable code
- Follows "Tell, Don't Ask" principle
- Reference: [SourceMaking - Null Object](https://sourcemaking.com/design_patterns/null_object)
**Polymorphism Over Conditionals:**
- Null Object responds to same interface as real object
- Default/neutral behavior instead of null checks
- Reference: [C2 Wiki - Null Object](https://wiki.c2.com/?NullObject)
### When to Use
**Good Candidates:**
- Objects frequently checked for null
- Null represents "absence" with sensible default behavior
- Reference: [Baeldung - Null Object Pattern](https://www.baeldung.com/java-null-object-pattern)
**Cautions:**
- Don't use when null has semantic meaning
- Can hide bugs if misapplied
- Reference: [Stack Overflow - Null Object Considerations](https://stackoverflow.com/questions/1274792/is-the-null-object-pattern-a-bad-practice)
---
## 23. Primitive Obsession
### Code Smell Definition
**Martin Fowler: Refactoring** (1999, 2018)
- Primitive Obsession is a code smell
- "Using primitives instead of small objects for simple tasks"
- Reference: [Refactoring Catalog](https://refactoring.com/catalog/)
**Wikipedia Definition:**
- "Using primitive data types to represent domain ideas"
- Example: Using string for email, int for money
- Reference: [Wikipedia - Code Smell](https://en.wikipedia.org/wiki/Code_smell)
### Why It's a Problem
**Lost Type Safety:**
- String can contain anything, Email cannot
- Compiler can't catch domain errors
- Reference: [Refactoring Guru - Primitive Obsession](https://refactoring.guru/smells/primitive-obsession)
**Scattered Validation:**
- Same validation repeated in multiple places
- Violates DRY principle
- Reference: [SourceMaking - Primitive Obsession](https://sourcemaking.com/refactoring/smells/primitive-obsession)
**Missing Behavior:**
- Primitives can't have domain-specific methods
- Logic lives in services instead of objects
- Reference: [Enterprise Craftsmanship - Primitive Obsession](https://enterprisecraftsmanship.com/posts/functional-c-primitive-obsession/)
### Solutions
**Replace with Value Objects:**
- Money instead of decimal
- Email instead of string
- PhoneNumber instead of string
- Reference: Already covered in Section 16 - Value Object Immutability
**Replace Data Value with Object:**
- Refactoring: "Replace Data Value with Object"
- Introduce Parameter Object for related primitives
- Reference: [Refactoring - Replace Data Value with Object](https://refactoring.com/catalog/replaceDataValueWithObject.html)
### Common Primitive Obsession Examples
**Frequently Misused Primitives:**
- string for: email, phone, URL, currency code, country code
- int/decimal for: money, percentage, age, quantity
- DateTime for: date ranges, business dates
- Reference: [DDD - Value Objects](https://martinfowler.com/bliki/ValueObject.html)
---
## 24. Service Locator Anti-pattern
### Martin Fowler's Analysis
**Blog Post: Inversion of Control Containers and the Dependency Injection pattern** (2004)
- Compares Service Locator with Dependency Injection
- "With service locator the application class asks for it explicitly by a message to the locator"
- Reference: [Martin Fowler - Inversion of Control](https://martinfowler.com/articles/injection.html)
**Service Locator Definition:**
- "The basic idea behind a service locator is to have an object that knows how to get hold of all of the services that an application might need"
- Acts as a registry that provides dependencies on demand
- Reference: [Martin Fowler - Service Locator](https://martinfowler.com/articles/injection.html#UsingAServiceLocator)
### Why It's Considered an Anti-pattern
**Mark Seemann: Dependency Injection in .NET** (2011, 2nd Ed. 2019)
- Author: Mark Seemann
- Extensively covers why Service Locator is problematic
- "Service Locator is an anti-pattern"
- Reference: [Mark Seemann - Service Locator is an Anti-Pattern](https://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/)
**Hidden Dependencies:**
- Dependencies are not visible in constructor
- Makes code harder to understand and test
- Violates Explicit Dependencies Principle
- Reference: [DevIQ - Explicit Dependencies](https://deviq.com/principles/explicit-dependencies-principle)
**Testing Difficulties:**
- Need to set up global locator for tests
- Tests become coupled to locator setup
- Reference: [Stack Overflow - Service Locator Testing](https://stackoverflow.com/questions/1557781/is-service-locator-an-anti-pattern)
### Dependency Injection Alternative
**Constructor Injection:**
- Dependencies declared in constructor
- Compiler enforces dependency provision
- Clear, testable code
- Reference: Already covered in Section 6 - Repository Pattern
**Benefits over Service Locator:**
- Explicit dependencies
- Easier testing (just pass mocks)
- IDE support for navigation
- Compile-time checking
- Reference: [Martin Fowler - Constructor Injection](https://martinfowler.com/articles/injection.html#ConstructorInjectionWithPicocontainer)
---
## 25. Double Dispatch and Visitor Pattern
### Gang of Four: Visitor Pattern
**Design Patterns** (1994)
- Authors: Gang of Four
- Visitor Pattern chapter
- "Represent an operation to be performed on the elements of an object structure"
- Reference: [Wikipedia - Visitor Pattern](https://en.wikipedia.org/wiki/Visitor_pattern)
**Intent:**
- "Lets you define a new operation without changing the classes of the elements on which it operates"
- Separates algorithms from object structure
- Reference: [Refactoring Guru - Visitor](https://refactoring.guru/design-patterns/visitor)
### Double Dispatch Mechanism
**Definition:**
- "A mechanism that dispatches a function call to different concrete functions depending on the runtime types of two objects involved in the call"
- Visitor pattern uses double dispatch
- Reference: [Wikipedia - Double Dispatch](https://en.wikipedia.org/wiki/Double_dispatch)
**How It Works:**
1. Client calls element.accept(visitor)
2. Element calls visitor.visit(this) - first dispatch
3. Correct visit() overload selected - second dispatch
- Reference: [SourceMaking - Visitor](https://sourcemaking.com/design_patterns/visitor)
### When to Use
**Good Use Cases:**
- Operations on complex object structures
- Many distinct operations needed
- Object structure rarely changes but operations change often
- Reference: [Refactoring Guru - Visitor Use Cases](https://refactoring.guru/design-patterns/visitor)
**Alternative to Type Checking:**
- Replace instanceof/typeof checks with polymorphism
- More maintainable and extensible
- Reference: [Replace Conditional with Polymorphism](https://refactoring.guru/replace-conditional-with-polymorphism)
### Trade-offs
**Advantages:**
- Open/Closed Principle for new operations
- Related operations grouped in one class
- Accumulate state while traversing
- Reference: [GoF Design Patterns](https://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612)
**Disadvantages:**
- Adding new element types requires changing all visitors
- May break encapsulation (visitors need access to element internals)
- Reference: [C2 Wiki - Visitor Pattern](https://wiki.c2.com/?VisitorPattern)
---
## 26. Entity Identity
### Eric Evans: Domain-Driven Design (2003)
**Entity Definition:**
- "An object that is not defined by its attributes, but rather by a thread of continuity and its identity"
- "Some objects are not defined primarily by their attributes. They represent a thread of identity"
- Reference: [Martin Fowler - Evans Classification](https://martinfowler.com/bliki/EvansClassification.html)
**Identity Characteristics:**
- Unique within the system
- Stable over time (doesn't change)
- Survives state changes
- Reference: [DDD Reference](https://www.domainlanguage.com/wp-content/uploads/2016/05/DDD_Reference_2015-03.pdf)
### Vaughn Vernon: Identity Implementation
**Implementing Domain-Driven Design** (2013)
- Chapter 5: "Entities"
- Detailed coverage of identity strategies
- "The primary characteristic of an Entity is that it has a unique identity"
- Reference: [Vaughn Vernon - Implementing DDD](https://www.amazon.com/Implementing-Domain-Driven-Design-Vaughn-Vernon/dp/0321834577)
**Identity Types:**
- Natural keys (SSN, email)
- Surrogate keys (UUID, auto-increment)
- Domain-generated IDs
- Reference: [Microsoft - Entity Keys](https://learn.microsoft.com/en-us/ef/core/modeling/keys)
### Identity Best Practices
**Immutability of Identity:**
- Identity should never change after creation
- Use readonly/final fields
- Reference: [StackExchange - Mutable Entity ID](https://softwareengineering.stackexchange.com/questions/375765/is-it-bad-practice-to-have-mutable-entity-ids)
**Value Object for Identity:**
- Wrap identity in Value Object (UserId, OrderId)
- Type safety prevents mixing IDs
- Can include validation logic
- Reference: [Enterprise Craftsmanship - Strongly Typed IDs](https://enterprisecraftsmanship.com/posts/strongly-typed-ids/)
**Equality Based on Identity:**
- Entity equality should compare only identity
- Not all attributes
- Reference: [Vaughn Vernon - Entity Equality](https://www.amazon.com/Implementing-Domain-Driven-Design-Vaughn-Vernon/dp/0321834577)
---
## 27. Saga Pattern
### Original Research
**Paper: Sagas** (1987)
- Authors: Hector Garcia-Molina and Kenneth Salem
- Published: ACM SIGMOD Conference
- Introduced Sagas for long-lived transactions
- Reference: [ACM Digital Library - Sagas](https://dl.acm.org/doi/10.1145/38713.38742)
**Definition:**
- "A saga is a sequence of local transactions where each transaction updates data within a single service"
- Alternative to distributed transactions
- Reference: [Microsoft - Saga Pattern](https://learn.microsoft.com/en-us/azure/architecture/reference-architectures/saga/saga)
### Chris Richardson: Microservices Patterns
**Book: Microservices Patterns** (2018)
- Author: Chris Richardson
- Publisher: Manning
- ISBN: 978-1617294549
- Chapter 4: "Managing Transactions with Sagas"
- Reference: [Manning - Microservices Patterns](https://www.manning.com/books/microservices-patterns)
**Saga Types:**
1. **Choreography**: Each service publishes events that trigger next steps
2. **Orchestration**: Central coordinator tells services what to do
- Reference: [Microservices.io - Saga](https://microservices.io/patterns/data/saga.html)
### Compensating Transactions
**Core Concept:**
- Each step has a compensating action to undo it
- If step N fails, compensate steps N-1, N-2, ..., 1
- Reference: [AWS - Saga Pattern](https://docs.aws.amazon.com/prescriptive-guidance/latest/modernization-data-persistence/saga-pattern.html)
**Compensation Examples:**
- CreateOrder → DeleteOrder
- ReserveInventory → ReleaseInventory
- ChargePayment → RefundPayment
- Reference: [Microsoft - Compensating Transactions](https://learn.microsoft.com/en-us/azure/architecture/patterns/compensating-transaction)
### Trade-offs
**Advantages:**
- Works across service boundaries
- No distributed locks
- Services remain autonomous
- Reference: [Chris Richardson - Saga](https://chrisrichardson.net/post/microservices/patterns/data/2019/07/22/design-sagas.html)
**Challenges:**
- Complexity of compensation logic
- Eventual consistency
- Debugging distributed sagas
- Reference: [Microsoft - Saga Considerations](https://learn.microsoft.com/en-us/azure/architecture/reference-architectures/saga/saga#issues-and-considerations)
---
## 28. Anti-Corruption Layer
### Eric Evans: Domain-Driven Design (2003)
**Original Definition:**
- Chapter 14: "Maintaining Model Integrity"
- "Create an isolating layer to provide clients with functionality in terms of their own domain model"
- Protects your model from external/legacy models
- Reference: [DDD Reference](https://www.domainlanguage.com/wp-content/uploads/2016/05/DDD_Reference_2015-03.pdf)
**Purpose:**
- "The translation layer between a new system and an external system"
- Prevents external model concepts from leaking in
- Reference: [Martin Fowler - Anti-Corruption Layer](https://martinfowler.com/bliki/AntiCorruptionLayer.html)
### Microsoft Guidance
**Azure Architecture Center:**
- "Implement a facade or adapter layer between different subsystems that don't share the same semantics"
- Isolate subsystems by placing an anti-corruption layer between them
- Reference: [Microsoft - ACL Pattern](https://learn.microsoft.com/en-us/azure/architecture/patterns/anti-corruption-layer)
**When to Use:**
- Integrating with legacy systems
- Migrating from monolith to microservices
- Working with third-party APIs
- Reference: [Microsoft - ACL When to Use](https://learn.microsoft.com/en-us/azure/architecture/patterns/anti-corruption-layer#when-to-use-this-pattern)
### Components of ACL
**Facade:**
- Simplified interface to external system
- Hides complexity from domain
- Reference: [Refactoring Guru - Facade](https://refactoring.guru/design-patterns/facade)
**Adapter:**
- Translates between interfaces
- Maps external model to domain model
- Reference: [Refactoring Guru - Adapter](https://refactoring.guru/design-patterns/adapter)
**Translator:**
- Converts data structures
- Maps field names and types
- Handles semantic differences
- Reference: [Evans DDD - Model Translation](https://www.domainlanguage.com/)
### Benefits
**Isolation:**
- Changes to external system don't ripple through domain
- Domain model remains pure
- Reference: [Microsoft - ACL Benefits](https://learn.microsoft.com/en-us/azure/architecture/patterns/anti-corruption-layer)
**Gradual Migration:**
- Replace legacy components incrementally
- Strangler Fig pattern compatibility
- Reference: [Martin Fowler - Strangler Fig](https://martinfowler.com/bliki/StranglerFigApplication.html)
---
## 29. Ubiquitous Language
### Eric Evans: Domain-Driven Design (2003)
**Original Definition:**
- Chapter 2: "Communication and the Use of Language"
- "A language structured around the domain model and used by all team members"
- "The vocabulary of that Ubiquitous Language includes the names of classes and prominent operations"
- Reference: [Martin Fowler - Ubiquitous Language](https://martinfowler.com/bliki/UbiquitousLanguage.html)
**Key Principles:**
- Shared by developers and domain experts
- Used in code, conversations, and documentation
- Changes to language reflect model changes
- Reference: [DDD Reference](https://www.domainlanguage.com/wp-content/uploads/2016/05/DDD_Reference_2015-03.pdf)
### Why It Matters
**Communication Benefits:**
- Reduces translation between business and tech
- Catches misunderstandings early
- Domain experts can read code names
- Reference: [InfoQ - Ubiquitous Language](https://www.infoq.com/articles/ddd-ubiquitous-language/)
**Design Benefits:**
- Model reflects real domain concepts
- Code becomes self-documenting
- Easier onboarding for new team members
- Reference: [Vaughn Vernon - Implementing DDD](https://www.amazon.com/Implementing-Domain-Driven-Design-Vaughn-Vernon/dp/0321834577)
### Building Ubiquitous Language
**Glossary:**
- Document key terms and definitions
- Keep updated as understanding evolves
- Reference: [DDD Community - Glossary](https://thedomaindrivendesign.io/glossary/)
**Event Storming:**
- Collaborative workshop technique
- Discover domain events and concepts
- Build shared understanding and language
- Reference: [Alberto Brandolini - Event Storming](https://www.eventstorming.com/)
### Common Pitfalls
**Inconsistent Terminology:**
- Same concept with different names (Customer/Client/User)
- Different concepts with same name
- Reference: [Domain Language - Building UL](https://www.domainlanguage.com/)
**Technical Terms in Domain:**
- "DTO", "Entity", "Repository" are technical
- Domain should use business terms
- Reference: [Evans DDD - Model-Driven Design](https://www.domainlanguage.com/)
---
## Conclusion
The code quality detection rules implemented in Guardian are firmly grounded in:
1. **Academic Research**: Peer-reviewed papers on software maintainability, complexity metrics, code quality, technical debt prioritization, and severity classification
1. **Academic Research**: Peer-reviewed papers on software maintainability, complexity metrics, code quality, technical debt prioritization, severity classification, and distributed systems (Sagas)
2. **Industry Standards**: ISO/IEC 25010, SonarQube rules, OWASP security guidelines, Google and Airbnb style guides
3. **Authoritative Books**:
- Gang of Four's "Design Patterns" (1994)
- Bertrand Meyer's "Object-Oriented Software Construction" (1988, 1997)
- Robert C. Martin's "Clean Architecture" (2017)
- Vaughn Vernon's "Implementing Domain-Driven Design" (2013)
- Chris Richardson's "Microservices Patterns" (2018)
- Eric Evans' "Domain-Driven Design" (2003)
- Martin Fowler's "Patterns of Enterprise Application Architecture" (2002)
- Martin Fowler's "Refactoring" (1999, 2018)
- Steve McConnell's "Code Complete" (1993, 2004)
4. **Expert Guidance**: Martin Fowler, Robert C. Martin (Uncle Bob), Eric Evans, Vaughn Vernon, Alistair Cockburn, Kent Beck
- Joshua Bloch's "Effective Java" (2001, 2018)
- Mark Seemann's "Dependency Injection in .NET" (2011, 2019)
- Bobby Woolf's "Null Object" in PLoPD3 (1997)
4. **Expert Guidance**: Martin Fowler, Robert C. Martin (Uncle Bob), Eric Evans, Vaughn Vernon, Alistair Cockburn, Kent Beck, Greg Young, Bertrand Meyer, Mark Seemann, Chris Richardson, Alberto Brandolini
5. **Security Standards**: OWASP Secrets Management, GitHub Secret Scanning, GitGuardian best practices
6. **Open Source Tools**: ArchUnit, SonarQube, ESLint, Secretlint - widely adopted in enterprise environments
7. **DDD Tactical & Strategic Patterns**: Domain Events, Value Objects, Entities, Aggregates, Bounded Contexts, Anti-Corruption Layer, Ubiquitous Language, Specifications, Factories
8. **Architectural Patterns**: CQS/CQRS, Saga, Visitor/Double Dispatch, Null Object, Persistence Ignorance
These rules represent decades of software engineering wisdom, empirical research, security best practices, and battle-tested practices from the world's leading software organizations and thought leaders.
@@ -845,9 +1678,9 @@ These rules represent decades of software engineering wisdom, empirical research
---
**Document Version**: 1.1
**Last Updated**: 2025-11-26
**Document Version**: 2.0
**Last Updated**: 2025-12-04
**Questions or want to contribute research?**
- 📧 Email: fozilbek.samiyev@gmail.com
- 🐙 GitHub: https://github.com/samiyev/puaros/issues
**Based on research as of**: November 2025
**Based on research as of**: December 2025

View File

@@ -5,6 +5,300 @@ 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.27.0] - 2025-12-05 - Inline Dependency Graph
### Added
- **Dependency Graph in Initial Context (v0.27.0)**
- New `## Dependency Graph` section in initial context
- Shows file relationships without requiring tool calls
- Format: `services/user: → types/user, utils/validation ← controllers/user`
- `→` indicates files this file imports (dependencies)
- `←` indicates files that import this file (dependents)
- Hub files (>5 dependents) shown first
- Files sorted by total connections (descending)
- **Configuration Option**
- `includeDepsGraph: boolean` in ContextConfigSchema (default: `true`)
- `includeDepsGraph` option in `BuildContextOptions`
- Users can disable to save tokens: `context.includeDepsGraph: false`
- **New Helper Functions in prompts.ts**
- `formatDependencyGraph()` - formats entire dependency graph from metas
- `formatDepsEntry()` - formats single file's dependencies/dependents
- `shortenPath()` - shortens paths (removes `src/`, extensions, `/index`)
### New Context Format
```
## Dependency Graph
utils/validation: ← services/user, services/auth, controllers/api
services/user: → types/user, utils/validation ← controllers/user, api/routes
services/auth: → services/user, utils/jwt ← controllers/auth
types/user: ← services/user, services/auth
```
### Technical Details
- Total tests: 1775 passed (was 1754, +21 new tests)
- 16 new tests for formatDependencyGraph()
- 5 new tests for includeDepsGraph config option
- Coverage: 97.48% lines, 91.07% branches, 98.62% functions
- 0 ESLint errors, 2 warnings (pre-existing complexity in ASTParser and prompts)
- Build successful
### Notes
This completes v0.27.0 of the Graph Metrics milestone:
- ✅ 0.27.0 - Inline Dependency Graph
Next milestone: v0.28.0 - Circular Dependencies in Context
---
## [0.26.0] - 2025-12-05 - Rich Initial Context: Decorator Extraction
### Added
- **Decorator Extraction (0.24.4)**
- Functions now show their decorators in initial context
- Classes now show their decorators in initial context
- Methods show decorators per-method
- New format: `@Controller('users') class UserController`
- Function format: `@Get(':id') async getUser(id: string): Promise<User>`
- Supports NestJS decorators: `@Controller`, `@Get`, `@Post`, `@Injectable`, `@UseGuards`, etc.
- Supports Angular decorators: `@Component`, `@Injectable`, `@Input`, `@Output`, etc.
- **FileAST.ts Enhancements**
- `decorators?: string[]` field on `FunctionInfo`
- `decorators?: string[]` field on `MethodInfo`
- `decorators?: string[]` field on `ClassInfo`
- **ASTParser.ts Enhancements**
- `formatDecorator()` - formats decorator node to string (e.g., `@Get(':id')`)
- `extractNodeDecorators()` - extracts decorators that are direct children of a node
- `extractDecoratorsFromSiblings()` - extracts decorators before the declaration in export statements
- Decorators are extracted for classes, methods, and exported functions
- **prompts.ts Enhancements**
- `formatDecoratorsPrefix()` - formats decorators as a prefix string for display
- Used in `formatFunctionSignature()` for function decorators
- Used in `formatFileSummary()` for class decorators
### New Context Format
```
### src/controllers/user.controller.ts
- @Controller('users') class UserController extends BaseController
- @Get(':id') @Auth() async getUser(id: string): Promise<User>
- @Post() @ValidateBody() async createUser(data: UserDTO): Promise<User>
```
### Technical Details
- Total tests: 1754 passed (was 1720, +34 new tests)
- 14 new tests for ASTParser decorator extraction
- 6 new tests for prompts decorator formatting
- +14 other tests from internal improvements
- Coverage: 97.49% lines, 91.14% branches, 98.61% functions
- 0 ESLint errors, 2 warnings (pre-existing complexity in ASTParser and prompts)
- Build successful
### Notes
This completes the 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
Next milestone: v0.25.0 - Graph Metrics in Context
---
## [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
### 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
- **JSON AST Parsing**
- Parse JSON files using `tree-sitter-json`
- Extract top-level keys as exports for indexing
- 2 unit tests for JSON parsing
- **YAML AST Parsing**
- Parse YAML files using `yaml` npm package (chosen over `tree-sitter-yaml` due to native binding compatibility issues)
- Extract top-level keys from mappings
- Detect root-level arrays
- Handle parse errors gracefully
- 6 unit tests for YAML parsing (empty, null, errors, line tracking)
- **Symlinks Metadata**
- Added `symlinkTarget?: string` to `ScanResult` interface
- `FileScanner.safeReadlink()` extracts symlink targets
- Symlinks detected during file scanning
### Changed
- **ASTParser**
- Added `parseYAML()` method using `yaml` package
- Added `getLineFromOffset()` helper for accurate line numbers
- Checks `doc.errors` for YAML parse errors
- Language type now includes `"json" | "yaml"`
### Technical Details
- Total tests: 1687 passed (was 1679, +8 new tests)
- Coverage: 97.5% lines, 91.21% branches, 98.58% functions
- 0 ESLint errors, 5 warnings (acceptable TUI complexity warnings)
- Dependencies: Added `yaml@^2.8.2`, removed `tree-sitter-yaml`
### ROADMAP Update
Verified that v0.20.0, v0.21.0 were already implemented but not documented:
- v0.20.0: IndexProject (184 LOC, 318 LOC tests) and ExecuteTool (225 LOC) were complete
- v0.21.0: Multiline Input, Syntax Highlighting (167 LOC, 24 tests) were complete
- Updated ROADMAP.md to reflect actual implementation status
---
## [0.22.5] - 2025-12-02 - Commands Configuration
### Added

View File

@@ -1333,40 +1333,40 @@ class ErrorHandler {
**Priority:** HIGH
**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
```typescript
// src/infrastructure/llm/OllamaClient.ts
// БЫЛО:
// - Передаём tools в Ollama SDK format
// - Извлекаем tool_calls из response.message.tool_calls
// BEFORE:
// - Pass tools in Ollama SDK format
// - Extract tool_calls from response.message.tool_calls
// СТАНЕТ:
// - НЕ передаём tools в SDK
// - Tools описаны в system prompt как XML
// - LLM возвращает XML в content
// - Парсим через ResponseParser
// AFTER:
// - DON'T pass tools to SDK
// - Tools described in system prompt as XML
// - LLM returns XML in content
// - Parse via ResponseParser
```
**Изменения:**
- [x] Удалить `convertTools()` метод
- [x] Удалить `extractToolCalls()` метод
- [x] Убрать передачу `tools` в `client.chat()`
- [x] Возвращать только `content` без `toolCalls`
**Changes:**
- [x] Remove `convertTools()` method
- [x] Remove `extractToolCalls()` method
- [x] Remove `tools` from `client.chat()` call
- [x] Return only `content` without `toolCalls`
### 0.19.2 - System Prompt Update
```typescript
// src/infrastructure/llm/prompts.ts
// Добавить в SYSTEM_PROMPT полное описание XML формата:
// Add full XML format description to SYSTEM_PROMPT:
const TOOL_FORMAT_INSTRUCTIONS = `
## Tool Calling Format
@@ -1397,94 +1397,91 @@ Always wait for tool results before making conclusions.
`
```
**Изменения:**
- [x] Добавить `TOOL_FORMAT_INSTRUCTIONS` в prompts.ts
- [x] Включить в `SYSTEM_PROMPT`
- [x] Добавить примеры для всех 18 tools
**Changes:**
- [x] Add `TOOL_FORMAT_INSTRUCTIONS` to prompts.ts
- [x] Include in `SYSTEM_PROMPT`
- [x] Add examples for all 18 tools
### 0.19.3 - HandleMessage Simplification
```typescript
// src/application/use-cases/HandleMessage.ts
// БЫЛО:
// BEFORE:
// const response = await this.llm.chat(messages)
// const parsed = parseToolCalls(response.content)
// СТАНЕТ:
// const response = await this.llm.chat(messages) // без tools
// const parsed = parseToolCalls(response.content) // единственный источник
// AFTER:
// const response = await this.llm.chat(messages) // without tools
// const parsed = parseToolCalls(response.content) // single source
```
**Изменения:**
- [x] Убрать передачу tool definitions в `llm.chat()`
- [x] ResponseParser — единственный источник tool calls
- [x] Упростить логику обработки
**Changes:**
- [x] Remove tool definitions from `llm.chat()`
- [x] ResponseParser — single source of tool calls
- [x] Simplify processing logic
### 0.19.4 - ILLMClient Interface Update
```typescript
// src/domain/services/ILLMClient.ts
// БЫЛО:
// BEFORE:
interface ILLMClient {
chat(messages: ChatMessage[], tools?: ToolDef[]): Promise<LLMResponse>
}
// СТАНЕТ:
// AFTER:
interface ILLMClient {
chat(messages: ChatMessage[]): Promise<LLMResponse>
// tools больше не передаются - они в system prompt
// tools no longer passed - they're in system prompt
}
```
**Изменения:**
- [x] Убрать `tools` параметр из `chat()`
- [x] Убрать `toolCalls` из `LLMResponse` (парсятся из content)
- [x] Обновить все реализации
**Changes:**
- [x] Remove `tools` parameter from `chat()`
- [x] Remove `toolCalls` from `LLMResponse` (parsed from content)
- [x] Update all implementations
### 0.19.5 - ResponseParser Enhancements
```typescript
// src/infrastructure/llm/ResponseParser.ts
// Улучшения:
// - Лучшая обработка ошибок парсинга
// - Поддержка CDATA для многострочного content
// - Валидация имён tools
// Improvements:
// - Better error handling for parsing
// - CDATA support for multiline content
// - Tool name validation
```
**Изменения:**
- [x] Добавить поддержку `<![CDATA[...]]>` для content
- [x] Валидация: tool name должен быть из известного списка
- [x] Улучшить сообщения об ошибках парсинга
**Changes:**
- [x] Add `<![CDATA[...]]>` support for content
- [x] Validation: tool name must be from known list
- [x] Improve parsing error messages
**Tests:**
- [x] Обновить тесты OllamaClient
- [x] Обновить тесты HandleMessage
- [x] Добавить тесты ResponseParser для edge cases
- [ ] E2E тест полного flow с XML (опционально, может быть в 0.20.0)
- [x] Update OllamaClient tests
- [x] Update HandleMessage tests
- [x] Add ResponseParser tests for edge cases
- [ ] E2E test for full XML flow (optional, may be in 0.20.0)
---
## Version 0.20.0 - Missing Use Cases 🔧
## Version 0.20.0 - Missing Use Cases 🔧
**Priority:** HIGH
**Status:** Pending
**Status:** Complete (v0.20.0 released)
### 0.20.1 - IndexProject Use Case
### 0.20.1 - IndexProject Use Case
```typescript
// src/application/use-cases/IndexProject.ts
class IndexProject {
constructor(
private storage: IStorage,
private indexer: IIndexer
)
constructor(storage: IStorage, projectRoot: string)
async execute(
projectRoot: string,
onProgress?: (progress: IndexProgress) => void
options?: IndexProjectOptions
): Promise<IndexingStats>
// Full indexing pipeline:
// 1. Scan files
@@ -1496,50 +1493,51 @@ class IndexProject {
```
**Deliverables:**
- [ ] IndexProject use case implementation
- [ ] Integration with CLI `index` command
- [ ] Integration with `/reindex` slash command
- [ ] Progress reporting via callback
- [ ] Unit tests
- [x] IndexProject use case implementation (184 LOC)
- [x] Progress reporting via callback
- [x] Unit tests (318 LOC)
### 0.20.2 - ExecuteTool Use Case
### 0.20.2 - ExecuteTool Use Case
```typescript
// src/application/use-cases/ExecuteTool.ts
class ExecuteTool {
constructor(
private tools: IToolRegistry,
private storage: IStorage
storage: IStorage,
sessionStorage: ISessionStorage,
tools: IToolRegistry,
projectRoot: string
)
async execute(
toolName: string,
params: Record<string, unknown>,
context: ToolContext
): Promise<ToolResult>
toolCall: ToolCall,
session: Session,
options?: ExecuteToolOptions
): Promise<ExecuteToolResult>
// Orchestrates tool execution with:
// - Parameter validation
// - Confirmation flow
// - Confirmation flow (with edit support)
// - Undo stack management
// - Storage updates
}
```
**Deliverables:**
- [ ] ExecuteTool use case implementation
- [ ] Refactor HandleMessage to use ExecuteTool
- [ ] Unit tests
- [x] ExecuteTool use case implementation (225 LOC)
- [x] HandleMessage uses ExecuteTool
- [x] Support for edited content from confirmation dialog
- [ ] Dedicated unit tests (covered indirectly via integration)
**Tests:**
- [ ] Unit tests for IndexProject
- [ ] Unit tests for ExecuteTool
- [x] Unit tests for IndexProject
- [ ] Unit tests for ExecuteTool (optional - covered via integration)
---
## Version 0.21.0 - TUI Enhancements 🎨
## Version 0.21.0 - TUI Enhancements 🎨
**Priority:** MEDIUM
**Status:** In Progress (2/4 complete)
**Status:** Complete (v0.21.0 released)
### 0.21.1 - useAutocomplete Hook ✅
@@ -1596,52 +1594,45 @@ interface ConfirmDialogProps {
- [x] ConfirmationResult type with editedContent field
- [x] All existing tests passing (1484 tests)
### 0.21.3 - Multiline Input
### 0.21.3 - Multiline Input
```typescript
// src/tui/components/Input.tsx enhancements
// src/tui/components/Input.tsx
interface InputProps {
// ... existing props
multiline?: boolean | "auto" // auto = detect based on content
}
// Shift+Enter for new line
// Auto-expand height
```
**Deliverables:**
- [ ] Multiline support in Input component
- [ ] Shift+Enter handling
- [ ] Auto-height adjustment
- [ ] Config option: `input.multiline`
- [ ] Unit tests
- [x] Multiline support in Input component
- [x] Line navigation support
- [x] Auto-expand based on content
- [x] Unit tests (37 tests)
### 0.21.4 - Syntax Highlighting in DiffView
### 0.21.4 - Syntax Highlighting in DiffView
```typescript
// src/tui/components/DiffView.tsx enhancements
// Full syntax highlighting for code in diff
// src/tui/utils/syntax-highlighter.ts (167 LOC)
// Custom tokenizer for TypeScript/JavaScript/JSON/YAML
// Highlights keywords, strings, comments, numbers, operators
interface DiffViewProps {
// ... existing props
language?: "ts" | "tsx" | "js" | "jsx"
language?: Language
syntaxHighlight?: boolean
}
// Use ink-syntax-highlight or custom tokenizer
```
**Deliverables:**
- [ ] Syntax highlighting integration
- [ ] Language detection from file extension
- [ ] Config option: `edit.syntaxHighlight`
- [ ] Unit tests
- [x] Syntax highlighter implementation (167 LOC)
- [x] Language detection from file extension
- [x] Integration with DiffView and ConfirmDialog
- [x] Unit tests (24 tests)
**Tests:**
- [ ] Unit tests for useAutocomplete
- [ ] Unit tests for enhanced ConfirmDialog
- [ ] Unit tests for multiline Input
- [ ] Unit tests for syntax highlighting
- [x] Unit tests for useAutocomplete (21 tests)
- [x] Unit tests for enhanced ConfirmDialog
- [x] Unit tests for multiline Input (37 tests)
- [x] Unit tests for syntax highlighting (24 tests)
---
@@ -1741,30 +1732,30 @@ export const CommandsConfigSchema = z.object({
---
## Version 0.23.0 - JSON/YAML & Symlinks 📄
## Version 0.23.0 - JSON/YAML & Symlinks 📄
**Priority:** LOW
**Status:** Pending
**Status:** Complete (v0.23.0 released)
### 0.23.1 - JSON/YAML AST Parsing
### 0.23.1 - JSON/YAML AST Parsing
```typescript
// src/infrastructure/indexer/ASTParser.ts enhancements
type Language = "ts" | "tsx" | "js" | "jsx" | "json" | "yaml"
// For JSON: extract keys, structure
// For YAML: extract keys, structure
// Use tree-sitter-json and tree-sitter-yaml
// For JSON: extract keys, structure (tree-sitter-json)
// For YAML: extract keys, structure (yaml npm package)
```
**Deliverables:**
- [ ] Add tree-sitter-json dependency
- [ ] Add tree-sitter-yaml dependency
- [ ] JSON parsing in ASTParser
- [ ] YAML parsing in ASTParser
- [ ] Unit tests
**Note:** YAML parsing uses `yaml` npm package instead of `tree-sitter-yaml` due to native binding compatibility issues.
### 0.23.2 - Symlinks Metadata
**Deliverables:**
- [x] Add tree-sitter-json dependency
- [x] JSON parsing in ASTParser
- [x] YAML parsing in ASTParser (using `yaml` package)
- [x] Unit tests (2 tests)
### 0.23.2 - Symlinks Metadata ✅
```typescript
// src/domain/services/IIndexer.ts enhancements
@@ -1775,20 +1766,242 @@ export interface ScanResult {
lastModified: number
symlinkTarget?: string // <-- NEW: target path for symlinks
}
// Store symlink metadata in Redis
// project:{name}:meta includes symlink info
```
**Deliverables:**
- [ ] Add symlinkTarget to ScanResult
- [ ] FileScanner extracts symlink targets
- [ ] Store symlink metadata in Redis
- [ ] Unit tests
- [x] Add symlinkTarget to ScanResult
- [x] FileScanner extracts symlink targets via safeReadlink()
- [x] Unit tests (FileScanner tests)
**Tests:**
- [ ] Unit tests for JSON/YAML parsing
- [ ] Unit tests for symlink handling
- [x] Unit tests for JSON/YAML parsing (2 tests)
- [x] Unit tests for symlink handling (FileScanner tests)
---
## Version 0.24.0 - Rich Initial Context 📋 ✅
**Priority:** HIGH
**Status:** Complete (v0.24.0 released)
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 ⭐ ✅
**Problem:** Currently LLM only sees function names: `fn: getUser, createUser`
**Solution:** Show full signatures: `async getUser(id: string): Promise<User>`
```typescript
// src/infrastructure/llm/prompts.ts changes
// BEFORE:
// - src/services/user.ts [fn: getUser, createUser]
// AFTER:
// ### src/services/user.ts
// - async getUser(id: string): Promise<User>
// - async createUser(data: UserDTO): Promise<User>
// - validateEmail(email: string): boolean
```
**Changes:**
- [x] Extend `FunctionInfo` in FileAST for parameter types and return type (already existed)
- [x] Update `ASTParser.ts` to extract parameter types and return types (arrow functions fixed)
- [x] Update `formatFileSummary()` in prompts.ts to output signatures
- [x] Add `includeSignatures: boolean` option to config
**Why:** LLM won't hallucinate parameters and return types.
### 0.24.2 - Interface/Type Field Definitions ⭐ ✅
**Problem:** LLM only sees `interface: User, UserDTO`
**Solution:** Show fields: `User { id: string, name: string, email: string }`
```typescript
// BEFORE:
// - src/types/user.ts [interface: User, UserDTO]
// AFTER:
// ### src/types/user.ts
// - interface User { id: string, name: string, email: string, createdAt: Date }
// - interface UserDTO { name: string, email: string }
// - type UserId = string
```
**Changes:**
- [x] Extend `InterfaceInfo` in FileAST for field types (already existed)
- [x] Update `ASTParser.ts` to extract interface fields (already existed)
- [x] Update `formatFileSummary()` to output fields
- [x] Handle type aliases with their definitions
**Why:** LLM knows data structure, won't invent fields.
### 0.24.3 - Enum Value Definitions ⭐ ✅
**Problem:** LLM only sees `type: Status`
**Solution:** Show values: `Status { Active=1, Inactive=0, Pending=2 }`
```typescript
// BEFORE:
// - src/types/enums.ts [type: Status, Role]
// AFTER:
// ### src/types/enums.ts
// - enum Status { Active=1, Inactive=0, Pending=2 }
// - enum Role { Admin="admin", User="user" }
```
**Changes:**
- [x] Add `EnumInfo` to FileAST with members and values
- [x] Update `ASTParser.ts` to extract enum members
- [x] Update `formatFileSummary()` to output enum values
**Why:** LLM knows valid enum values.
### 0.24.4 - Decorator Extraction ⭐ ✅
**Problem:** LLM doesn't see decorators (important for NestJS, Angular)
**Solution:** Show decorators in context
```typescript
// AFTER:
// ### 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>
```
**Changes:**
- [x] Add `decorators: string[]` to FunctionInfo, MethodInfo, and ClassInfo
- [x] Update `ASTParser.ts` to extract decorators via `extractNodeDecorators()` and `extractDecoratorsFromSiblings()`
- [x] Update `prompts.ts` to display decorators via `formatDecoratorsPrefix()`
**Why:** LLM understands routing, DI, guards in NestJS/Angular.
**Tests:**
- [x] Unit tests for ASTParser decorator extraction (14 tests)
- [x] Unit tests for prompts decorator formatting (6 tests)
---
## Version 0.27.0 - Inline Dependency Graph 📊 ✅
**Priority:** MEDIUM
**Status:** Complete (v0.27.0 released)
### Description
**Problem:** LLM doesn't see file relationships without tool calls
**Solution:** Show dependency graph in context
```typescript
// Add to 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/*
```
**Changes:**
- [x] Add `formatDependencyGraph()` to prompts.ts
- [x] Use data from `FileMeta.dependencies` and `FileMeta.dependents`
- [x] Group by hub files (many connections)
- [x] Add `includeDepsGraph: boolean` option to config
**Tests:**
- [x] Unit tests for formatDependencyGraph() (16 tests)
- [x] Unit tests for includeDepsGraph config option (5 tests)
**Why:** LLM sees architecture without tool call.
---
## Version 0.28.0 - Circular Dependencies in Context 🔄
**Priority:** MEDIUM
**Status:** Planned
### Description
**Problem:** Circular deps are computed but not shown in context
**Solution:** Show cycles immediately
```typescript
// Add to initial context:
// ## ⚠️ Circular Dependencies
// - services/user → services/auth → services/user
// - utils/a → utils/b → utils/c → utils/a
```
**Changes:**
- [ ] Add `formatCircularDeps()` to prompts.ts
- [ ] Get circular deps from IndexBuilder
- [ ] Store in Redis as separate key or in meta
**Why:** LLM immediately sees architecture problems.
---
## Version 0.29.0 - Impact Score 📈
**Priority:** MEDIUM
**Status:** Planned
### Description
**Problem:** LLM doesn't know which files are critical
**Solution:** Show impact score (% of codebase that depends on file)
```typescript
// Add to 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 |
```
**Changes:**
- [ ] Add `impactScore: number` to FileMeta (0-100)
- [ ] Compute in MetaAnalyzer: (transitiveDepByCount / totalFiles) * 100
- [ ] Add `formatHighImpactFiles()` to prompts.ts
- [ ] Show top-10 high impact files
**Why:** LLM understands which files are critical for changes.
---
## Version 0.30.0 - Transitive Dependencies Count 🔢
**Priority:** MEDIUM
**Status:** Planned
### Description
**Problem:** Currently only counting direct dependencies
**Solution:** Add transitive dependencies to meta
```typescript
// FileMeta additions:
interface FileMeta {
// existing...
transitiveDepCount: number; // how many files depend on this (transitively)
transitiveDepByCount: number; // how many files this depends on (transitively)
}
```
**Changes:**
- [ ] Add `computeTransitiveDeps()` to MetaAnalyzer
- [ ] Use DFS with memoization for efficiency
- [ ] Store in FileMeta
**Tests:**
- [ ] Unit tests for transitive dependencies computation
- [ ] Performance tests for large codebases
---
@@ -1803,10 +2016,12 @@ export interface ScanResult {
- [x] Error handling complete ✅ (v0.16.0)
- [ ] Performance optimized
- [x] Documentation complete ✅ (v0.17.0)
- [x] Test coverage ≥92% branches, ≥95% lines/functions/statements ✅ (92.01% branches, 97.84% lines, 99.16% functions, 97.84% statements - 1441 tests)
- [x] Test coverage ≥91% branches, ≥95% lines/functions/statements ✅ (91.21% branches, 97.5% lines, 98.58% functions, 97.5% statements - 1687 tests)
- [x] 0 ESLint errors ✅
- [x] Examples working ✅ (v0.18.0)
- [x] CHANGELOG.md up to date ✅
- [x] Rich initial context (v0.24.0-v0.26.0) — function signatures, interface fields, enum values, decorators ✅
- [ ] Graph metrics in context (v0.27.0-v0.30.0) — dependency graph ✅, circular deps, impact score, transitive deps
---
@@ -1875,11 +2090,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-02
**Last Updated:** 2025-12-05
**Target Version:** 1.0.0
**Current Version:** 0.22.1
**Current Version:** 0.27.0
**Next Milestones:** v0.28.0 (Circular Deps), v0.29.0 (Impact Score), v0.30.0 (Transitive Deps)
> **Note:** Rich Initial Context complete ✅ (v0.24.0-v0.26.0). Graph Metrics in progress (v0.27.0 ✅, v0.28.0-v0.30.0 pending) for 1.0.0 release.

View File

@@ -1,6 +1,6 @@
{
"name": "@samiyev/ipuaro",
"version": "0.22.4",
"version": "0.27.0",
"description": "Local AI agent for codebase operations with infinite context feeling",
"author": "Fozilbek Samiyev <fozilbek.samiyev@gmail.com>",
"license": "MIT",
@@ -44,7 +44,9 @@
"simple-git": "^3.27.0",
"tree-sitter": "^0.21.1",
"tree-sitter-javascript": "^0.21.0",
"tree-sitter-json": "^0.24.8",
"tree-sitter-typescript": "^0.21.2",
"yaml": "^2.8.2",
"zod": "^3.23.8"
},
"devDependencies": {

View File

@@ -21,6 +21,7 @@ export interface ScanResult {
type: "file" | "directory" | "symlink"
size: number
lastModified: number
symlinkTarget?: string
}
/**
@@ -46,7 +47,7 @@ export interface IIndexer {
/**
* Parse file content into AST.
*/
parseFile(content: string, language: "ts" | "tsx" | "js" | "jsx"): FileAST
parseFile(content: string, language: "ts" | "tsx" | "js" | "jsx" | "json" | "yaml"): FileAST
/**
* Analyze file and compute metadata.

View File

@@ -52,6 +52,8 @@ export interface FunctionInfo {
isExported: boolean
/** Return type (if available) */
returnType?: string
/** Decorators applied to the function (e.g., ["@Get(':id')", "@Auth()"]) */
decorators?: string[]
}
export interface MethodInfo {
@@ -69,6 +71,8 @@ export interface MethodInfo {
visibility: "public" | "private" | "protected"
/** Whether it's static */
isStatic: boolean
/** Decorators applied to the method (e.g., ["@Get(':id')", "@UseGuards(AuthGuard)"]) */
decorators?: string[]
}
export interface PropertyInfo {
@@ -105,6 +109,8 @@ export interface ClassInfo {
isExported: boolean
/** Whether class is abstract */
isAbstract: boolean
/** Decorators applied to the class (e.g., ["@Controller('users')", "@Injectable()"]) */
decorators?: string[]
}
export interface InterfaceInfo {
@@ -129,6 +135,30 @@ export interface TypeAliasInfo {
line: number
/** Whether it's exported */
isExported: boolean
/** Type definition (e.g., "string", "User & Admin", "{ id: string }") */
definition?: string
}
export interface EnumMemberInfo {
/** Member name */
name: string
/** Member value (string or number, if specified) */
value?: string | number
}
export interface EnumInfo {
/** Enum name */
name: string
/** Start line number */
lineStart: number
/** End line number */
lineEnd: number
/** Enum members with values */
members: EnumMemberInfo[]
/** Whether it's exported */
isExported: boolean
/** Whether it's a const enum */
isConst: boolean
}
export interface FileAST {
@@ -144,6 +174,8 @@ export interface FileAST {
interfaces: InterfaceInfo[]
/** Type alias declarations */
typeAliases: TypeAliasInfo[]
/** Enum declarations */
enums: EnumInfo[]
/** Whether parsing encountered errors */
parseError: boolean
/** Parse error message if any */
@@ -158,6 +190,7 @@ export function createEmptyFileAST(): FileAST {
classes: [],
interfaces: [],
typeAliases: [],
enums: [],
parseError: false,
}
}

View File

@@ -2,8 +2,11 @@ import { builtinModules } from "node:module"
import Parser from "tree-sitter"
import TypeScript from "tree-sitter-typescript"
import JavaScript from "tree-sitter-javascript"
import JSON from "tree-sitter-json"
import * as yamlParser from "yaml"
import {
createEmptyFileAST,
type EnumMemberInfo,
type ExportInfo,
type FileAST,
type ImportInfo,
@@ -13,7 +16,7 @@ import {
} from "../../domain/value-objects/FileAST.js"
import { FieldName, NodeType } from "./tree-sitter-types.js"
type Language = "ts" | "tsx" | "js" | "jsx"
type Language = "ts" | "tsx" | "js" | "jsx" | "json" | "yaml"
type SyntaxNode = Parser.SyntaxNode
/**
@@ -39,12 +42,20 @@ export class ASTParser {
jsParser.setLanguage(JavaScript)
this.parsers.set("js", jsParser)
this.parsers.set("jsx", jsParser)
const jsonParser = new Parser()
jsonParser.setLanguage(JSON)
this.parsers.set("json", jsonParser)
}
/**
* Parse source code and extract AST information.
*/
parse(content: string, language: Language): FileAST {
if (language === "yaml") {
return this.parseYAML(content)
}
const parser = this.parsers.get(language)
if (!parser) {
return {
@@ -75,8 +86,77 @@ export class ASTParser {
}
}
/**
* Parse YAML content using yaml package.
*/
private parseYAML(content: string): FileAST {
const ast = createEmptyFileAST()
try {
const doc = yamlParser.parseDocument(content)
if (doc.errors.length > 0) {
return {
...createEmptyFileAST(),
parseError: true,
parseErrorMessage: doc.errors[0].message,
}
}
const contents = doc.contents
if (yamlParser.isSeq(contents)) {
ast.exports.push({
name: "(array)",
line: 1,
isDefault: false,
kind: "variable",
})
} else if (yamlParser.isMap(contents)) {
for (const item of contents.items) {
if (yamlParser.isPair(item) && yamlParser.isScalar(item.key)) {
const keyRange = item.key.range
const line = keyRange ? this.getLineFromOffset(content, keyRange[0]) : 1
ast.exports.push({
name: String(item.key.value),
line,
isDefault: false,
kind: "variable",
})
}
}
}
return ast
} catch (error) {
return {
...createEmptyFileAST(),
parseError: true,
parseErrorMessage: error instanceof Error ? error.message : "YAML parse error",
}
}
}
/**
* Get line number from character offset.
*/
private getLineFromOffset(content: string, offset: number): number {
let line = 1
for (let i = 0; i < offset && i < content.length; i++) {
if (content[i] === "\n") {
line++
}
}
return line
}
private extractAST(root: SyntaxNode, language: Language): FileAST {
const ast = createEmptyFileAST()
if (language === "json") {
return this.extractJSONStructure(root, ast)
}
const isTypeScript = language === "ts" || language === "tsx"
for (const child of root.children) {
@@ -113,6 +193,11 @@ export class ASTParser {
this.extractTypeAlias(node, ast, false)
}
break
case NodeType.ENUM_DECLARATION:
if (isTypeScript) {
this.extractEnum(node, ast, false)
}
break
}
}
@@ -179,13 +264,15 @@ export class ASTParser {
const declaration = node.childForFieldName(FieldName.DECLARATION)
if (declaration) {
const decorators = this.extractDecoratorsFromSiblings(declaration)
switch (declaration.type) {
case NodeType.FUNCTION_DECLARATION:
this.extractFunction(declaration, ast, true)
this.extractFunction(declaration, ast, true, decorators)
this.addExportInfo(ast, declaration, "function", isDefault)
break
case NodeType.CLASS_DECLARATION:
this.extractClass(declaration, ast, true)
this.extractClass(declaration, ast, true, decorators)
this.addExportInfo(ast, declaration, "class", isDefault)
break
case NodeType.INTERFACE_DECLARATION:
@@ -196,6 +283,10 @@ export class ASTParser {
this.extractTypeAlias(declaration, ast, true)
this.addExportInfo(ast, declaration, "type", isDefault)
break
case NodeType.ENUM_DECLARATION:
this.extractEnum(declaration, ast, true)
this.addExportInfo(ast, declaration, "type", isDefault)
break
case NodeType.LEXICAL_DECLARATION:
this.extractLexicalDeclaration(declaration, ast, true)
break
@@ -220,7 +311,12 @@ export class ASTParser {
}
}
private extractFunction(node: SyntaxNode, ast: FileAST, isExported: boolean): void {
private extractFunction(
node: SyntaxNode,
ast: FileAST,
isExported: boolean,
externalDecorators: string[] = [],
): void {
const nameNode = node.childForFieldName(FieldName.NAME)
if (!nameNode) {
return
@@ -230,6 +326,9 @@ export class ASTParser {
const isAsync = node.children.some((c) => c.type === NodeType.ASYNC)
const returnTypeNode = node.childForFieldName(FieldName.RETURN_TYPE)
const nodeDecorators = this.extractNodeDecorators(node)
const decorators = [...externalDecorators, ...nodeDecorators]
ast.functions.push({
name: nameNode.text,
lineStart: node.startPosition.row + 1,
@@ -238,6 +337,7 @@ export class ASTParser {
isAsync,
isExported,
returnType: returnTypeNode?.text?.replace(/^:\s*/, ""),
decorators,
})
}
@@ -253,6 +353,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 ?? "",
@@ -261,6 +362,8 @@ export class ASTParser {
params,
isAsync,
isExported,
returnType: returnTypeNode?.text?.replace(/^:\s*/, ""),
decorators: [],
})
if (isExported) {
@@ -283,7 +386,12 @@ export class ASTParser {
}
}
private extractClass(node: SyntaxNode, ast: FileAST, isExported: boolean): void {
private extractClass(
node: SyntaxNode,
ast: FileAST,
isExported: boolean,
externalDecorators: string[] = [],
): void {
const nameNode = node.childForFieldName(FieldName.NAME)
if (!nameNode) {
return
@@ -294,14 +402,19 @@ export class ASTParser {
const properties: PropertyInfo[] = []
if (body) {
let pendingDecorators: string[] = []
for (const member of body.children) {
if (member.type === NodeType.METHOD_DEFINITION) {
methods.push(this.extractMethod(member))
if (member.type === NodeType.DECORATOR) {
pendingDecorators.push(this.formatDecorator(member))
} else if (member.type === NodeType.METHOD_DEFINITION) {
methods.push(this.extractMethod(member, pendingDecorators))
pendingDecorators = []
} else if (
member.type === NodeType.PUBLIC_FIELD_DEFINITION ||
member.type === NodeType.FIELD_DEFINITION
) {
properties.push(this.extractProperty(member))
pendingDecorators = []
}
}
}
@@ -309,6 +422,9 @@ export class ASTParser {
const { extendsName, implementsList } = this.extractClassHeritage(node)
const isAbstract = node.children.some((c) => c.type === NodeType.ABSTRACT)
const nodeDecorators = this.extractNodeDecorators(node)
const decorators = [...externalDecorators, ...nodeDecorators]
ast.classes.push({
name: nameNode.text,
lineStart: node.startPosition.row + 1,
@@ -319,6 +435,7 @@ export class ASTParser {
implements: implementsList,
isExported,
isAbstract,
decorators,
})
}
@@ -372,7 +489,7 @@ export class ASTParser {
}
}
private extractMethod(node: SyntaxNode): MethodInfo {
private extractMethod(node: SyntaxNode, decorators: string[] = []): MethodInfo {
const nameNode = node.childForFieldName(FieldName.NAME)
const params = this.extractParameters(node)
const isAsync = node.children.some((c) => c.type === NodeType.ASYNC)
@@ -394,6 +511,7 @@ export class ASTParser {
isAsync,
visibility,
isStatic,
decorators,
}
}
@@ -473,13 +591,86 @@ export class ASTParser {
return
}
const valueNode = node.childForFieldName(FieldName.VALUE)
const definition = valueNode?.text
ast.typeAliases.push({
name: nameNode.text,
line: node.startPosition.row + 1,
isExported,
definition,
})
}
private extractEnum(node: SyntaxNode, ast: FileAST, isExported: boolean): void {
const nameNode = node.childForFieldName(FieldName.NAME)
if (!nameNode) {
return
}
const body = node.childForFieldName(FieldName.BODY)
const members: EnumMemberInfo[] = []
if (body) {
for (const child of body.children) {
if (child.type === NodeType.ENUM_ASSIGNMENT) {
const memberName = child.childForFieldName(FieldName.NAME)
const memberValue = child.childForFieldName(FieldName.VALUE)
if (memberName) {
members.push({
name: memberName.text,
value: this.parseEnumValue(memberValue),
})
}
} else if (
child.type === NodeType.IDENTIFIER ||
child.type === NodeType.PROPERTY_IDENTIFIER
) {
members.push({
name: child.text,
value: undefined,
})
}
}
}
const isConst = node.children.some((c) => c.text === "const")
ast.enums.push({
name: nameNode.text,
lineStart: node.startPosition.row + 1,
lineEnd: node.endPosition.row + 1,
members,
isExported,
isConst,
})
}
private parseEnumValue(valueNode: SyntaxNode | null): string | number | undefined {
if (!valueNode) {
return undefined
}
const text = valueNode.text
if (valueNode.type === "number") {
return Number(text)
}
if (valueNode.type === "string") {
return this.getStringValue(valueNode)
}
if (valueNode.type === "unary_expression" && text.startsWith("-")) {
const num = Number(text)
if (!isNaN(num)) {
return num
}
}
return text
}
private extractParameters(node: SyntaxNode): ParameterInfo[] {
const params: ParameterInfo[] = []
const paramsNode = node.childForFieldName(FieldName.PARAMETERS)
@@ -528,6 +719,49 @@ export class ASTParser {
}
}
/**
* Format a decorator node to a string like "@Get(':id')" or "@Injectable()".
*/
private formatDecorator(node: SyntaxNode): string {
return node.text.replace(/\s+/g, " ").trim()
}
/**
* Extract decorators that are direct children of a node.
* In tree-sitter, decorators are children of the class/function declaration.
*/
private extractNodeDecorators(node: SyntaxNode): string[] {
const decorators: string[] = []
for (const child of node.children) {
if (child.type === NodeType.DECORATOR) {
decorators.push(this.formatDecorator(child))
}
}
return decorators
}
/**
* Extract decorators from sibling nodes before the current node.
* Decorators appear as children before the declaration in export statements.
*/
private extractDecoratorsFromSiblings(node: SyntaxNode): string[] {
const decorators: string[] = []
const parent = node.parent
if (!parent) {
return decorators
}
for (const sibling of parent.children) {
if (sibling.type === NodeType.DECORATOR) {
decorators.push(this.formatDecorator(sibling))
} else if (sibling === node) {
break
}
}
return decorators
}
private classifyImport(from: string): ImportInfo["type"] {
if (from.startsWith(".") || from.startsWith("/")) {
return "internal"
@@ -548,4 +782,37 @@ export class ASTParser {
}
return text
}
/**
* Extract structure from JSON file.
* For JSON files, we extract top-level keys from objects.
*/
private extractJSONStructure(root: SyntaxNode, ast: FileAST): FileAST {
for (const child of root.children) {
if (child.type === "object") {
this.extractJSONKeys(child, ast)
}
}
return ast
}
/**
* Extract keys from JSON object.
*/
private extractJSONKeys(node: SyntaxNode, ast: FileAST): void {
for (const child of node.children) {
if (child.type === "pair") {
const keyNode = child.childForFieldName("key")
if (keyNode) {
const keyName = this.getStringValue(keyNode)
ast.exports.push({
name: keyName,
line: keyNode.startPosition.row + 1,
isDefault: false,
kind: "variable",
})
}
}
}
}
}

View File

@@ -96,12 +96,27 @@ export class FileScanner {
const stats = await this.safeStats(fullPath)
if (stats) {
yield {
const type = stats.isSymbolicLink()
? "symlink"
: stats.isDirectory()
? "directory"
: "file"
const result: ScanResult = {
path: relativePath,
type: "file",
type,
size: stats.size,
lastModified: stats.mtimeMs,
}
if (type === "symlink") {
const target = await this.safeReadlink(fullPath)
if (target) {
result.symlinkTarget = target
}
}
yield result
}
}
}
@@ -127,10 +142,22 @@ export class FileScanner {
/**
* Safely get file stats without throwing.
* Uses lstat to get information about symlinks themselves.
*/
private async safeStats(filePath: string): Promise<Stats | null> {
try {
return await fs.stat(filePath)
return await fs.lstat(filePath)
} catch {
return null
}
}
/**
* Safely read symlink target without throwing.
*/
private async safeReadlink(filePath: string): Promise<string | null> {
try {
return await fs.readlink(filePath)
} catch {
return null
}

View File

@@ -16,6 +16,7 @@ export const NodeType = {
CLASS_DECLARATION: "class_declaration",
INTERFACE_DECLARATION: "interface_declaration",
TYPE_ALIAS_DECLARATION: "type_alias_declaration",
ENUM_DECLARATION: "enum_declaration",
// Clauses
IMPORT_CLAUSE: "import_clause",
@@ -37,6 +38,11 @@ export const NodeType = {
FIELD_DEFINITION: "field_definition",
PROPERTY_SIGNATURE: "property_signature",
// Enum members
ENUM_BODY: "enum_body",
ENUM_ASSIGNMENT: "enum_assignment",
PROPERTY_IDENTIFIER: "property_identifier",
// Parameters
REQUIRED_PARAMETER: "required_parameter",
OPTIONAL_PARAMETER: "optional_parameter",
@@ -57,6 +63,9 @@ export const NodeType = {
DEFAULT: "default",
ACCESSIBILITY_MODIFIER: "accessibility_modifier",
READONLY: "readonly",
// Decorators
DECORATOR: "decorator",
} as const
export type NodeTypeValue = (typeof NodeType)[keyof typeof NodeType]

View File

@@ -11,6 +11,14 @@ export interface ProjectStructure {
directories: string[]
}
/**
* Options for building initial context.
*/
export interface BuildContextOptions {
includeSignatures?: boolean
includeDepsGraph?: boolean
}
/**
* System prompt for the ipuaro AI agent.
*/
@@ -116,12 +124,22 @@ export function buildInitialContext(
structure: ProjectStructure,
asts: Map<string, FileAST>,
metas?: Map<string, FileMeta>,
options?: BuildContextOptions,
): string {
const sections: string[] = []
const includeSignatures = options?.includeSignatures ?? true
const includeDepsGraph = options?.includeDepsGraph ?? true
sections.push(formatProjectHeader(structure))
sections.push(formatDirectoryTree(structure))
sections.push(formatFileOverview(asts, metas))
sections.push(formatFileOverview(asts, metas, includeSignatures))
if (includeDepsGraph && metas && metas.size > 0) {
const depsGraph = formatDependencyGraph(metas)
if (depsGraph) {
sections.push(depsGraph)
}
}
return sections.join("\n\n")
}
@@ -157,7 +175,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 +190,183 @@ 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 decorators as a prefix string.
* Example: "@Get(':id') @Auth() "
*/
function formatFileSummary(path: string, ast: FileAST, meta?: FileMeta): string {
function formatDecoratorsPrefix(decorators: string[] | undefined): string {
if (!decorators || decorators.length === 0) {
return ""
}
return `${decorators.join(" ")} `
}
/**
* Format a function signature.
*/
function formatFunctionSignature(fn: FileAST["functions"][0]): string {
const decoratorsPrefix = formatDecoratorsPrefix(fn.decorators)
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 `${decoratorsPrefix}${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}`
}
/**
* Format an enum signature with members and values.
* Example: "enum Status { Active=1, Inactive=0, Pending=2 }"
* Example: "const enum Role { Admin="admin", User="user" }"
*/
function formatEnumSignature(enumInfo: FileAST["enums"][0]): string {
const constPrefix = enumInfo.isConst ? "const " : ""
if (enumInfo.members.length === 0) {
return `${constPrefix}enum ${enumInfo.name}`
}
const membersStr = enumInfo.members
.map((m) => {
if (m.value === undefined) {
return m.name
}
const valueStr = typeof m.value === "string" ? `"${m.value}"` : String(m.value)
return `${m.name}=${valueStr}`
})
.join(", ")
const result = `${constPrefix}enum ${enumInfo.name} { ${membersStr} }`
if (result.length > 100) {
return truncateDefinition(result, 100)
}
return result
}
/**
* 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.
* 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 decoratorsPrefix = formatDecoratorsPrefix(cls.decorators)
const ext = cls.extends ? ` extends ${cls.extends}` : ""
const impl = cls.implements.length > 0 ? ` implements ${cls.implements.join(", ")}` : ""
lines.push(`- ${decoratorsPrefix}class ${cls.name}${ext}${impl}`)
}
}
if (ast.interfaces.length > 0) {
for (const iface of ast.interfaces) {
lines.push(`- ${formatInterfaceSignature(iface)}`)
}
}
if (ast.typeAliases.length > 0) {
for (const type of ast.typeAliases) {
lines.push(`- ${formatTypeAliasSignature(type)}`)
}
}
if (ast.enums && ast.enums.length > 0) {
for (const enumInfo of ast.enums) {
lines.push(`- ${formatEnumSignature(enumInfo)}`)
}
}
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) {
@@ -200,9 +389,12 @@ function formatFileSummary(path: string, ast: FileAST, meta?: FileMeta): string
parts.push(`type: ${names}`)
}
const summary = parts.length > 0 ? ` [${parts.join(" | ")}]` : ""
const flags = formatFileFlags(meta)
if (ast.enums && ast.enums.length > 0) {
const names = ast.enums.map((e) => e.name).join(", ")
parts.push(`enum: ${names}`)
}
const summary = parts.length > 0 ? ` [${parts.join(" | ")}]` : ""
return `- ${path}${summary}${flags}`
}
@@ -231,6 +423,109 @@ function formatFileFlags(meta?: FileMeta): string {
return flags.length > 0 ? ` (${flags.join(", ")})` : ""
}
/**
* Shorten a file path for display in dependency graph.
* Removes common prefixes like "src/" and file extensions.
*/
function shortenPath(path: string): string {
let short = path
if (short.startsWith("src/")) {
short = short.slice(4)
}
// Remove common extensions
short = short.replace(/\.(ts|tsx|js|jsx)$/, "")
// Remove /index suffix
short = short.replace(/\/index$/, "")
return short
}
/**
* Format a single dependency graph entry.
* Format: "path: → dep1, dep2 ← dependent1, dependent2"
*/
function formatDepsEntry(path: string, dependencies: string[], dependents: string[]): string {
const parts: string[] = []
const shortPath = shortenPath(path)
if (dependencies.length > 0) {
const deps = dependencies.map(shortenPath).join(", ")
parts.push(`${deps}`)
}
if (dependents.length > 0) {
const deps = dependents.map(shortenPath).join(", ")
parts.push(`${deps}`)
}
if (parts.length === 0) {
return ""
}
return `${shortPath}: ${parts.join(" ")}`
}
/**
* Format dependency graph for all files.
* Shows hub files first, then files with dependencies/dependents.
*
* Format:
* ## Dependency Graph
* services/user: → types/user, utils/validation ← controllers/user
* services/auth: → services/user, utils/jwt ← controllers/auth
*/
export function formatDependencyGraph(metas: Map<string, FileMeta>): string | null {
if (metas.size === 0) {
return null
}
const entries: { path: string; deps: string[]; dependents: string[]; isHub: boolean }[] = []
for (const [path, meta] of metas) {
// Only include files that have connections
if (meta.dependencies.length > 0 || meta.dependents.length > 0) {
entries.push({
path,
deps: meta.dependencies,
dependents: meta.dependents,
isHub: meta.isHub,
})
}
}
if (entries.length === 0) {
return null
}
// Sort: hubs first, then by total connections (desc), then by path
entries.sort((a, b) => {
if (a.isHub !== b.isHub) {
return a.isHub ? -1 : 1
}
const aTotal = a.deps.length + a.dependents.length
const bTotal = b.deps.length + b.dependents.length
if (aTotal !== bTotal) {
return bTotal - aTotal
}
return a.path.localeCompare(b.path)
})
const lines: string[] = ["## Dependency Graph", ""]
for (const entry of entries) {
const line = formatDepsEntry(entry.path, entry.deps, entry.dependents)
if (line) {
lines.push(line)
}
}
// Return null if only header (no actual entries)
if (lines.length <= 2) {
return null
}
return lines.join("\n")
}
/**
* Format line range for display.
*/

View File

@@ -114,6 +114,8 @@ 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),
includeDepsGraph: z.boolean().default(true),
})
/**

View File

@@ -224,6 +224,62 @@ describe("ASTParser", () => {
const ast = parser.parse(code, "ts")
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", () => {
@@ -404,4 +460,376 @@ function mix(
expect(ast.exports.length).toBeGreaterThanOrEqual(4)
})
})
describe("JSON parsing", () => {
it("should extract top-level keys from JSON object", () => {
const json = `{
"name": "test",
"version": "1.0.0",
"dependencies": {},
"scripts": {}
}`
const ast = parser.parse(json, "json")
expect(ast.parseError).toBe(false)
expect(ast.exports).toHaveLength(4)
expect(ast.exports.map((e) => e.name)).toEqual([
"name",
"version",
"dependencies",
"scripts",
])
expect(ast.exports.every((e) => e.kind === "variable")).toBe(true)
})
it("should handle empty JSON object", () => {
const json = `{}`
const ast = parser.parse(json, "json")
expect(ast.parseError).toBe(false)
expect(ast.exports).toHaveLength(0)
})
})
describe("YAML parsing", () => {
it("should extract top-level keys from YAML", () => {
const yaml = `name: test
version: 1.0.0
dependencies:
foo: ^1.0.0
scripts:
test: vitest`
const ast = parser.parse(yaml, "yaml")
expect(ast.parseError).toBe(false)
expect(ast.exports.length).toBeGreaterThanOrEqual(4)
expect(ast.exports.map((e) => e.name)).toContain("name")
expect(ast.exports.map((e) => e.name)).toContain("version")
expect(ast.exports.every((e) => e.kind === "variable")).toBe(true)
})
it("should handle YAML array at root", () => {
const yaml = `- item1
- item2
- item3`
const ast = parser.parse(yaml, "yaml")
expect(ast.parseError).toBe(false)
expect(ast.exports).toHaveLength(1)
expect(ast.exports[0].name).toBe("(array)")
})
it("should handle empty YAML", () => {
const yaml = ``
const ast = parser.parse(yaml, "yaml")
expect(ast.parseError).toBe(false)
expect(ast.exports).toHaveLength(0)
})
it("should handle YAML with null content", () => {
const yaml = `null`
const ast = parser.parse(yaml, "yaml")
expect(ast.parseError).toBe(false)
expect(ast.exports).toHaveLength(0)
})
it("should handle invalid YAML with parse error", () => {
const yaml = `{invalid: yaml: syntax: [}`
const ast = parser.parse(yaml, "yaml")
expect(ast.parseError).toBe(true)
expect(ast.parseErrorMessage).toBeDefined()
})
it("should track correct line numbers for YAML keys", () => {
const yaml = `first: value1
second: value2
third: value3`
const ast = parser.parse(yaml, "yaml")
expect(ast.parseError).toBe(false)
expect(ast.exports).toHaveLength(3)
expect(ast.exports[0].line).toBe(1)
expect(ast.exports[1].line).toBe(2)
expect(ast.exports[2].line).toBe(3)
})
})
describe("enums (0.24.3)", () => {
it("should extract enum with numeric values", () => {
const code = `enum Status {
Active = 1,
Inactive = 0,
Pending = 2
}`
const ast = parser.parse(code, "ts")
expect(ast.enums).toHaveLength(1)
expect(ast.enums[0]).toMatchObject({
name: "Status",
isExported: false,
isConst: false,
})
expect(ast.enums[0].members).toHaveLength(3)
expect(ast.enums[0].members[0]).toMatchObject({ name: "Active", value: 1 })
expect(ast.enums[0].members[1]).toMatchObject({ name: "Inactive", value: 0 })
expect(ast.enums[0].members[2]).toMatchObject({ name: "Pending", value: 2 })
})
it("should extract enum with string values", () => {
const code = `enum Role {
Admin = "admin",
User = "user",
Guest = "guest"
}`
const ast = parser.parse(code, "ts")
expect(ast.enums).toHaveLength(1)
expect(ast.enums[0].members).toHaveLength(3)
expect(ast.enums[0].members[0]).toMatchObject({ name: "Admin", value: "admin" })
expect(ast.enums[0].members[1]).toMatchObject({ name: "User", value: "user" })
expect(ast.enums[0].members[2]).toMatchObject({ name: "Guest", value: "guest" })
})
it("should extract enum without explicit values", () => {
const code = `enum Direction {
Up,
Down,
Left,
Right
}`
const ast = parser.parse(code, "ts")
expect(ast.enums).toHaveLength(1)
expect(ast.enums[0].members).toHaveLength(4)
expect(ast.enums[0].members[0]).toMatchObject({ name: "Up", value: undefined })
expect(ast.enums[0].members[1]).toMatchObject({ name: "Down", value: undefined })
})
it("should extract exported enum", () => {
const code = `export enum Color {
Red = "#FF0000",
Green = "#00FF00",
Blue = "#0000FF"
}`
const ast = parser.parse(code, "ts")
expect(ast.enums).toHaveLength(1)
expect(ast.enums[0].isExported).toBe(true)
expect(ast.exports).toHaveLength(1)
expect(ast.exports[0].kind).toBe("type")
})
it("should extract const enum", () => {
const code = `const enum HttpStatus {
OK = 200,
NotFound = 404,
InternalError = 500
}`
const ast = parser.parse(code, "ts")
expect(ast.enums).toHaveLength(1)
expect(ast.enums[0].isConst).toBe(true)
expect(ast.enums[0].members[0]).toMatchObject({ name: "OK", value: 200 })
})
it("should extract exported const enum", () => {
const code = `export const enum LogLevel {
Debug = 0,
Info = 1,
Warn = 2,
Error = 3
}`
const ast = parser.parse(code, "ts")
expect(ast.enums).toHaveLength(1)
expect(ast.enums[0].isExported).toBe(true)
expect(ast.enums[0].isConst).toBe(true)
})
it("should extract line range for enum", () => {
const code = `enum Test {
A = 1,
B = 2
}`
const ast = parser.parse(code, "ts")
expect(ast.enums[0].lineStart).toBe(1)
expect(ast.enums[0].lineEnd).toBe(4)
})
it("should handle enum with negative values", () => {
const code = `enum Temperature {
Cold = -10,
Freezing = -20,
Hot = 40
}`
const ast = parser.parse(code, "ts")
expect(ast.enums).toHaveLength(1)
expect(ast.enums[0].members[0]).toMatchObject({ name: "Cold", value: -10 })
expect(ast.enums[0].members[1]).toMatchObject({ name: "Freezing", value: -20 })
expect(ast.enums[0].members[2]).toMatchObject({ name: "Hot", value: 40 })
})
it("should handle empty enum", () => {
const code = `enum Empty {}`
const ast = parser.parse(code, "ts")
expect(ast.enums).toHaveLength(1)
expect(ast.enums[0].name).toBe("Empty")
expect(ast.enums[0].members).toHaveLength(0)
})
it("should not extract enum from JavaScript", () => {
const code = `enum Status { Active = 1 }`
const ast = parser.parse(code, "js")
expect(ast.enums).toHaveLength(0)
})
})
describe("decorators (0.24.4)", () => {
it("should extract class decorator", () => {
const code = `@Controller('users')
class UserController {}`
const ast = parser.parse(code, "ts")
expect(ast.classes).toHaveLength(1)
expect(ast.classes[0].decorators).toHaveLength(1)
expect(ast.classes[0].decorators[0]).toBe("@Controller('users')")
})
it("should extract multiple class decorators", () => {
const code = `@Controller('api')
@Injectable()
@UseGuards(AuthGuard)
class ApiController {}`
const ast = parser.parse(code, "ts")
expect(ast.classes).toHaveLength(1)
expect(ast.classes[0].decorators).toHaveLength(3)
expect(ast.classes[0].decorators[0]).toBe("@Controller('api')")
expect(ast.classes[0].decorators[1]).toBe("@Injectable()")
expect(ast.classes[0].decorators[2]).toBe("@UseGuards(AuthGuard)")
})
it("should extract method decorators", () => {
const code = `class UserController {
@Get(':id')
@Auth()
async getUser() {}
}`
const ast = parser.parse(code, "ts")
expect(ast.classes).toHaveLength(1)
expect(ast.classes[0].methods).toHaveLength(1)
expect(ast.classes[0].methods[0].decorators).toHaveLength(2)
expect(ast.classes[0].methods[0].decorators[0]).toBe("@Get(':id')")
expect(ast.classes[0].methods[0].decorators[1]).toBe("@Auth()")
})
it("should extract exported decorated class", () => {
const code = `@Injectable()
export class UserService {}`
const ast = parser.parse(code, "ts")
expect(ast.classes).toHaveLength(1)
expect(ast.classes[0].isExported).toBe(true)
expect(ast.classes[0].decorators).toHaveLength(1)
expect(ast.classes[0].decorators[0]).toBe("@Injectable()")
})
it("should extract decorator with complex arguments", () => {
const code = `@Module({
imports: [UserModule],
controllers: [AppController],
providers: [AppService]
})
class AppModule {}`
const ast = parser.parse(code, "ts")
expect(ast.classes).toHaveLength(1)
expect(ast.classes[0].decorators).toHaveLength(1)
expect(ast.classes[0].decorators[0]).toContain("@Module")
expect(ast.classes[0].decorators[0]).toContain("imports")
})
it("should extract decorated class with extends", () => {
const code = `@Entity()
class User extends BaseEntity {}`
const ast = parser.parse(code, "ts")
expect(ast.classes).toHaveLength(1)
expect(ast.classes[0].extends).toBe("BaseEntity")
expect(ast.classes[0].decorators).toHaveLength(1)
expect(ast.classes[0].decorators![0]).toBe("@Entity()")
})
it("should handle class without decorators", () => {
const code = `class SimpleClass {}`
const ast = parser.parse(code, "ts")
expect(ast.classes).toHaveLength(1)
expect(ast.classes[0].decorators).toHaveLength(0)
})
it("should handle method without decorators", () => {
const code = `class SimpleClass {
simpleMethod() {}
}`
const ast = parser.parse(code, "ts")
expect(ast.classes).toHaveLength(1)
expect(ast.classes[0].methods).toHaveLength(1)
expect(ast.classes[0].methods[0].decorators).toHaveLength(0)
})
it("should handle function without decorators", () => {
const code = `function simpleFunc() {}`
const ast = parser.parse(code, "ts")
expect(ast.functions).toHaveLength(1)
expect(ast.functions[0].decorators).toHaveLength(0)
})
it("should handle arrow function without decorators", () => {
const code = `const arrowFn = () => {}`
const ast = parser.parse(code, "ts")
expect(ast.functions).toHaveLength(1)
expect(ast.functions[0].decorators).toHaveLength(0)
})
it("should extract NestJS controller pattern", () => {
const code = `@Controller('users')
export class UserController {
@Get()
findAll() {}
@Get(':id')
findOne() {}
@Post()
@Body()
create() {}
}`
const ast = parser.parse(code, "ts")
expect(ast.classes).toHaveLength(1)
expect(ast.classes[0].decorators).toContain("@Controller('users')")
expect(ast.classes[0].methods).toHaveLength(3)
expect(ast.classes[0].methods[0].decorators).toContain("@Get()")
expect(ast.classes[0].methods[1].decorators).toContain("@Get(':id')")
expect(ast.classes[0].methods[2].decorators).toContain("@Post()")
})
})
})

File diff suppressed because it is too large Load Diff

View File

@@ -15,6 +15,8 @@ describe("ContextConfigSchema", () => {
maxContextUsage: 0.8,
autoCompressAt: 0.8,
compressionMethod: "llm-summary",
includeSignatures: true,
includeDepsGraph: true,
})
})
@@ -26,6 +28,8 @@ describe("ContextConfigSchema", () => {
maxContextUsage: 0.8,
autoCompressAt: 0.8,
compressionMethod: "llm-summary",
includeSignatures: true,
includeDepsGraph: true,
})
})
})
@@ -162,6 +166,8 @@ describe("ContextConfigSchema", () => {
maxContextUsage: 0.8,
autoCompressAt: 0.8,
compressionMethod: "llm-summary",
includeSignatures: true,
includeDepsGraph: true,
})
})
@@ -175,6 +181,8 @@ describe("ContextConfigSchema", () => {
maxContextUsage: 0.8,
autoCompressAt: 0.9,
compressionMethod: "llm-summary",
includeSignatures: true,
includeDepsGraph: true,
})
})
@@ -189,6 +197,8 @@ describe("ContextConfigSchema", () => {
maxContextUsage: 0.7,
autoCompressAt: 0.8,
compressionMethod: "truncate",
includeSignatures: true,
includeDepsGraph: true,
})
})
})
@@ -200,6 +210,8 @@ describe("ContextConfigSchema", () => {
maxContextUsage: 0.9,
autoCompressAt: 0.85,
compressionMethod: "truncate" as const,
includeSignatures: false,
includeDepsGraph: false,
}
const result = ContextConfigSchema.parse(config)
@@ -212,10 +224,62 @@ describe("ContextConfigSchema", () => {
maxContextUsage: 0.8,
autoCompressAt: 0.8,
compressionMethod: "llm-summary" as const,
includeSignatures: true,
includeDepsGraph: 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()
})
})
describe("includeDepsGraph", () => {
it("should accept true", () => {
const result = ContextConfigSchema.parse({ includeDepsGraph: true })
expect(result.includeDepsGraph).toBe(true)
})
it("should accept false", () => {
const result = ContextConfigSchema.parse({ includeDepsGraph: false })
expect(result.includeDepsGraph).toBe(false)
})
it("should default to true", () => {
const result = ContextConfigSchema.parse({})
expect(result.includeDepsGraph).toBe(true)
})
it("should reject non-boolean", () => {
expect(() => ContextConfigSchema.parse({ includeDepsGraph: "true" })).toThrow()
})
it("should reject number", () => {
expect(() => ContextConfigSchema.parse({ includeDepsGraph: 1 })).toThrow()
})
})
})

View File

@@ -24,7 +24,7 @@ export default defineConfig({
thresholds: {
lines: 95,
functions: 95,
branches: 91.3,
branches: 91,
statements: 95,
},
},

56
pnpm-lock.yaml generated
View File

@@ -131,7 +131,7 @@ importers:
version: 5.9.3
vitest:
specifier: ^4.0.10
version: 4.0.13(@types/node@22.19.1)(@vitest/ui@4.0.13)(jsdom@27.2.0)(terser@5.44.1)(tsx@4.20.6)
version: 4.0.13(@types/node@22.19.1)(@vitest/ui@4.0.13)(jsdom@27.2.0)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.2)
packages/ipuaro:
dependencies:
@@ -168,9 +168,15 @@ importers:
tree-sitter-javascript:
specifier: ^0.21.0
version: 0.21.4(tree-sitter@0.21.1)
tree-sitter-json:
specifier: ^0.24.8
version: 0.24.8(tree-sitter@0.21.1)
tree-sitter-typescript:
specifier: ^0.21.2
version: 0.21.2(tree-sitter@0.21.1)
yaml:
specifier: ^2.8.2
version: 2.8.2
zod:
specifier: ^3.23.8
version: 3.25.76
@@ -201,7 +207,7 @@ importers:
version: 18.3.1(react@18.3.1)
tsup:
specifier: ^8.3.5
version: 8.5.1(postcss@8.5.6)(tsx@4.20.6)(typescript@5.9.3)
version: 8.5.1(postcss@8.5.6)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.2)
typescript:
specifier: ^5.7.2
version: 5.9.3
@@ -4172,6 +4178,14 @@ packages:
tree-sitter:
optional: true
tree-sitter-json@0.24.8:
resolution: {integrity: sha512-Tc9ZZYwHyWZ3Tt1VEw7Pa2scu1YO7/d2BCBbKTx5hXwig3UfdQjsOPkPyLpDJOn/m1UBEWYAtSdGAwCSyagBqQ==}
peerDependencies:
tree-sitter: ^0.21.1
peerDependenciesMeta:
tree-sitter:
optional: true
tree-sitter-typescript@0.21.2:
resolution: {integrity: sha512-/RyNK41ZpkA8PuPZimR6pGLvNR1p0ibRUJwwQn4qAjyyLEIQD/BNlwS3NSxWtGsAWZe9gZ44VK1mWx2+eQVldg==}
peerDependencies:
@@ -4636,6 +4650,11 @@ packages:
yallist@3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
yaml@2.8.2:
resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==}
engines: {node: '>= 14.6'}
hasBin: true
yargs-parser@21.1.1:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
engines: {node: '>=12'}
@@ -6325,7 +6344,7 @@ snapshots:
magicast: 0.5.1
std-env: 3.10.0
tinyrainbow: 3.0.3
vitest: 4.0.13(@types/node@22.19.1)(@vitest/ui@4.0.13)(jsdom@27.2.0)(terser@5.44.1)(tsx@4.20.6)
vitest: 4.0.13(@types/node@22.19.1)(@vitest/ui@4.0.13)(jsdom@27.2.0)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.2)
transitivePeerDependencies:
- supports-color
@@ -6344,13 +6363,13 @@ snapshots:
chai: 6.2.1
tinyrainbow: 3.0.3
'@vitest/mocker@4.0.13(vite@7.2.4(@types/node@22.19.1)(terser@5.44.1)(tsx@4.20.6))':
'@vitest/mocker@4.0.13(vite@7.2.4(@types/node@22.19.1)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.2))':
dependencies:
'@vitest/spy': 4.0.13
estree-walker: 3.0.3
magic-string: 0.30.21
optionalDependencies:
vite: 7.2.4(@types/node@22.19.1)(terser@5.44.1)(tsx@4.20.6)
vite: 7.2.4(@types/node@22.19.1)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.2)
'@vitest/pretty-format@4.0.13':
dependencies:
@@ -6405,7 +6424,7 @@ snapshots:
sirv: 3.0.2
tinyglobby: 0.2.15
tinyrainbow: 3.0.3
vitest: 4.0.13(@types/node@22.19.1)(@vitest/ui@4.0.13)(jsdom@27.2.0)(terser@5.44.1)(tsx@4.20.6)
vitest: 4.0.13(@types/node@22.19.1)(@vitest/ui@4.0.13)(jsdom@27.2.0)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.2)
'@vitest/utils@1.6.1':
dependencies:
@@ -8392,12 +8411,13 @@ snapshots:
pluralize@8.0.0: {}
postcss-load-config@6.0.1(postcss@8.5.6)(tsx@4.20.6):
postcss-load-config@6.0.1(postcss@8.5.6)(tsx@4.20.6)(yaml@2.8.2):
dependencies:
lilconfig: 3.1.3
optionalDependencies:
postcss: 8.5.6
tsx: 4.20.6
yaml: 2.8.2
postcss@8.5.6:
dependencies:
@@ -8911,6 +8931,13 @@ snapshots:
optionalDependencies:
tree-sitter: 0.21.1
tree-sitter-json@0.24.8(tree-sitter@0.21.1):
dependencies:
node-addon-api: 8.5.0
node-gyp-build: 4.8.4
optionalDependencies:
tree-sitter: 0.21.1
tree-sitter-typescript@0.21.2(tree-sitter@0.21.1):
dependencies:
node-addon-api: 8.5.0
@@ -8999,7 +9026,7 @@ snapshots:
tslib@2.8.1: {}
tsup@8.5.1(postcss@8.5.6)(tsx@4.20.6)(typescript@5.9.3):
tsup@8.5.1(postcss@8.5.6)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.2):
dependencies:
bundle-require: 5.1.0(esbuild@0.27.0)
cac: 6.7.14
@@ -9010,7 +9037,7 @@ snapshots:
fix-dts-default-cjs-exports: 1.0.1
joycon: 3.1.1
picocolors: 1.1.1
postcss-load-config: 6.0.1(postcss@8.5.6)(tsx@4.20.6)
postcss-load-config: 6.0.1(postcss@8.5.6)(tsx@4.20.6)(yaml@2.8.2)
resolve-from: 5.0.0
rollup: 4.53.3
source-map: 0.7.6
@@ -9156,7 +9183,7 @@ snapshots:
fsevents: 2.3.3
terser: 5.44.1
vite@7.2.4(@types/node@22.19.1)(terser@5.44.1)(tsx@4.20.6):
vite@7.2.4(@types/node@22.19.1)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.2):
dependencies:
esbuild: 0.25.12
fdir: 6.5.0(picomatch@4.0.3)
@@ -9169,6 +9196,7 @@ snapshots:
fsevents: 2.3.3
terser: 5.44.1
tsx: 4.20.6
yaml: 2.8.2
vitest@1.6.1(@types/node@22.19.1)(@vitest/ui@1.6.1)(jsdom@27.2.0)(terser@5.44.1):
dependencies:
@@ -9206,10 +9234,10 @@ snapshots:
- supports-color
- terser
vitest@4.0.13(@types/node@22.19.1)(@vitest/ui@4.0.13)(jsdom@27.2.0)(terser@5.44.1)(tsx@4.20.6):
vitest@4.0.13(@types/node@22.19.1)(@vitest/ui@4.0.13)(jsdom@27.2.0)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.2):
dependencies:
'@vitest/expect': 4.0.13
'@vitest/mocker': 4.0.13(vite@7.2.4(@types/node@22.19.1)(terser@5.44.1)(tsx@4.20.6))
'@vitest/mocker': 4.0.13(vite@7.2.4(@types/node@22.19.1)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.2))
'@vitest/pretty-format': 4.0.13
'@vitest/runner': 4.0.13
'@vitest/snapshot': 4.0.13
@@ -9226,7 +9254,7 @@ snapshots:
tinyexec: 0.3.2
tinyglobby: 0.2.15
tinyrainbow: 3.0.3
vite: 7.2.4(@types/node@22.19.1)(terser@5.44.1)(tsx@4.20.6)
vite: 7.2.4(@types/node@22.19.1)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.2)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/node': 22.19.1
@@ -9366,6 +9394,8 @@ snapshots:
yallist@3.1.1: {}
yaml@2.8.2: {}
yargs-parser@21.1.1: {}
yargs@17.7.2: