mirror of
https://github.com/samiyev/puaros.git
synced 2025-12-27 23:06:54 +05:00
Compare commits
2 Commits
ipuaro-v0.
...
ipuaro-v0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d63d85d850 | ||
|
|
41cfc21f20 |
@@ -5,6 +5,58 @@ 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
|
||||
|
||||
@@ -1884,14 +1884,12 @@ Enhance initial context for LLM: add function signatures, interface field types,
|
||||
|
||||
---
|
||||
|
||||
## Version 0.25.0 - Graph Metrics in Context 📊
|
||||
## Version 0.27.0 - Inline Dependency Graph 📊 ✅
|
||||
|
||||
**Priority:** MEDIUM
|
||||
**Status:** Planned
|
||||
**Status:** Complete (v0.27.0 released)
|
||||
|
||||
Add graph metrics to initial context: dependency graph, circular dependencies, impact score.
|
||||
|
||||
### 0.25.1 - Inline Dependency Graph
|
||||
### Description
|
||||
|
||||
**Problem:** LLM doesn't see file relationships without tool calls
|
||||
**Solution:** Show dependency graph in context
|
||||
@@ -1906,14 +1904,25 @@ Add graph metrics to initial context: dependency graph, circular dependencies, i
|
||||
```
|
||||
|
||||
**Changes:**
|
||||
- [ ] Add `formatDependencyGraph()` to prompts.ts
|
||||
- [ ] Use data from `FileMeta.dependencies` and `FileMeta.dependents`
|
||||
- [ ] Group by hub files (many connections)
|
||||
- [ ] Add `includeDepsGraph: boolean` option to config
|
||||
- [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.
|
||||
|
||||
### 0.25.2 - Circular Dependencies in Context
|
||||
---
|
||||
|
||||
## 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
|
||||
@@ -1933,7 +1942,14 @@ Add graph metrics to initial context: dependency graph, circular dependencies, i
|
||||
|
||||
**Why:** LLM immediately sees architecture problems.
|
||||
|
||||
### 0.25.3 - Impact Score
|
||||
---
|
||||
|
||||
## 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)
|
||||
@@ -1957,7 +1973,14 @@ Add graph metrics to initial context: dependency graph, circular dependencies, i
|
||||
|
||||
**Why:** LLM understands which files are critical for changes.
|
||||
|
||||
### 0.25.4 - Transitive Dependencies Count
|
||||
---
|
||||
|
||||
## Version 0.30.0 - Transitive Dependencies Count 🔢
|
||||
|
||||
**Priority:** MEDIUM
|
||||
**Status:** Planned
|
||||
|
||||
### Description
|
||||
|
||||
**Problem:** Currently only counting direct dependencies
|
||||
**Solution:** Add transitive dependencies to meta
|
||||
@@ -1977,8 +2000,7 @@ interface FileMeta {
|
||||
- [ ] Store in FileMeta
|
||||
|
||||
**Tests:**
|
||||
- [ ] Unit tests for graph metrics computation
|
||||
- [ ] Unit tests for new context sections
|
||||
- [ ] Unit tests for transitive dependencies computation
|
||||
- [ ] Performance tests for large codebases
|
||||
|
||||
---
|
||||
@@ -1998,8 +2020,8 @@ interface FileMeta {
|
||||
- [x] 0 ESLint errors ✅
|
||||
- [x] Examples working ✅ (v0.18.0)
|
||||
- [x] CHANGELOG.md up to date ✅
|
||||
- [x] Rich initial context (v0.24.0) — function signatures, interface fields, enum values, decorators ✅
|
||||
- [ ] Graph metrics in context (v0.25.0) — dependency graph, circular deps, impact score
|
||||
- [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
|
||||
|
||||
---
|
||||
|
||||
@@ -2078,7 +2100,7 @@ sessions:list # List<session_id>
|
||||
|
||||
**Last Updated:** 2025-12-05
|
||||
**Target Version:** 1.0.0
|
||||
**Current Version:** 0.26.0
|
||||
**Next Milestones:** v0.25.0 (Graph Metrics - 0/4 complete)
|
||||
**Current Version:** 0.27.0
|
||||
**Next Milestones:** v0.28.0 (Circular Deps), v0.29.0 (Impact Score), v0.30.0 (Transitive Deps)
|
||||
|
||||
> **Note:** v0.24.0 complete ✅. v0.25.0 (Graph Metrics) is required for 1.0.0 release. It enables LLM to see architecture without tool calls.
|
||||
> **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.
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@samiyev/ipuaro",
|
||||
"version": "0.26.0",
|
||||
"version": "0.27.0",
|
||||
"description": "Local AI agent for codebase operations with infinite context feeling",
|
||||
"author": "Fozilbek Samiyev <fozilbek.samiyev@gmail.com>",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -16,6 +16,7 @@ export interface ProjectStructure {
|
||||
*/
|
||||
export interface BuildContextOptions {
|
||||
includeSignatures?: boolean
|
||||
includeDepsGraph?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -127,11 +128,19 @@ export function buildInitialContext(
|
||||
): 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, includeSignatures))
|
||||
|
||||
if (includeDepsGraph && metas && metas.size > 0) {
|
||||
const depsGraph = formatDependencyGraph(metas)
|
||||
if (depsGraph) {
|
||||
sections.push(depsGraph)
|
||||
}
|
||||
}
|
||||
|
||||
return sections.join("\n\n")
|
||||
}
|
||||
|
||||
@@ -414,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.
|
||||
*/
|
||||
|
||||
@@ -115,6 +115,7 @@ export const ContextConfigSchema = z.object({
|
||||
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),
|
||||
})
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
buildInitialContext,
|
||||
buildFileContext,
|
||||
truncateContext,
|
||||
formatDependencyGraph,
|
||||
type ProjectStructure,
|
||||
} from "../../../../src/infrastructure/llm/prompts.js"
|
||||
import type { FileAST } from "../../../../src/domain/value-objects/FileAST.js"
|
||||
@@ -2013,4 +2014,386 @@ describe("prompts", () => {
|
||||
expect(context).not.toContain("@")
|
||||
})
|
||||
})
|
||||
|
||||
describe("dependency graph (0.27.0)", () => {
|
||||
describe("formatDependencyGraph", () => {
|
||||
it("should return null for empty metas", () => {
|
||||
const metas = new Map<string, FileMeta>()
|
||||
|
||||
const result = formatDependencyGraph(metas)
|
||||
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it("should return null when no files have dependencies or dependents", () => {
|
||||
const metas = new Map<string, FileMeta>([
|
||||
[
|
||||
"src/isolated.ts",
|
||||
{
|
||||
complexity: { loc: 10, nesting: 1, cyclomaticComplexity: 1, score: 10 },
|
||||
dependencies: [],
|
||||
dependents: [],
|
||||
isHub: false,
|
||||
isEntryPoint: true,
|
||||
fileType: "source",
|
||||
},
|
||||
],
|
||||
])
|
||||
|
||||
const result = formatDependencyGraph(metas)
|
||||
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it("should format file with only dependencies", () => {
|
||||
const metas = new Map<string, FileMeta>([
|
||||
[
|
||||
"src/services/user.ts",
|
||||
{
|
||||
complexity: { loc: 50, nesting: 2, cyclomaticComplexity: 5, score: 30 },
|
||||
dependencies: ["src/types/user.ts", "src/utils/validation.ts"],
|
||||
dependents: [],
|
||||
isHub: false,
|
||||
isEntryPoint: false,
|
||||
fileType: "source",
|
||||
},
|
||||
],
|
||||
])
|
||||
|
||||
const result = formatDependencyGraph(metas)
|
||||
|
||||
expect(result).toContain("## Dependency Graph")
|
||||
expect(result).toContain("services/user: → types/user, utils/validation")
|
||||
})
|
||||
|
||||
it("should format file with only dependents", () => {
|
||||
const metas = new Map<string, FileMeta>([
|
||||
[
|
||||
"src/types/user.ts",
|
||||
{
|
||||
complexity: { loc: 20, nesting: 1, cyclomaticComplexity: 1, score: 10 },
|
||||
dependencies: [],
|
||||
dependents: ["src/services/user.ts", "src/controllers/user.ts"],
|
||||
isHub: false,
|
||||
isEntryPoint: false,
|
||||
fileType: "types",
|
||||
},
|
||||
],
|
||||
])
|
||||
|
||||
const result = formatDependencyGraph(metas)
|
||||
|
||||
expect(result).toContain("## Dependency Graph")
|
||||
expect(result).toContain("types/user: ← services/user, controllers/user")
|
||||
})
|
||||
|
||||
it("should format file with both dependencies and dependents", () => {
|
||||
const metas = new Map<string, FileMeta>([
|
||||
[
|
||||
"src/services/user.ts",
|
||||
{
|
||||
complexity: { loc: 80, nesting: 3, cyclomaticComplexity: 10, score: 50 },
|
||||
dependencies: ["src/types/user.ts", "src/utils/validation.ts"],
|
||||
dependents: ["src/controllers/user.ts", "src/api/routes.ts"],
|
||||
isHub: false,
|
||||
isEntryPoint: false,
|
||||
fileType: "source",
|
||||
},
|
||||
],
|
||||
])
|
||||
|
||||
const result = formatDependencyGraph(metas)
|
||||
|
||||
expect(result).toContain("## Dependency Graph")
|
||||
expect(result).toContain(
|
||||
"services/user: → types/user, utils/validation ← controllers/user, api/routes",
|
||||
)
|
||||
})
|
||||
|
||||
it("should sort hub files first", () => {
|
||||
const metas = new Map<string, FileMeta>([
|
||||
[
|
||||
"src/utils/helpers.ts",
|
||||
{
|
||||
complexity: { loc: 30, nesting: 1, cyclomaticComplexity: 3, score: 20 },
|
||||
dependencies: [],
|
||||
dependents: [
|
||||
"a.ts",
|
||||
"b.ts",
|
||||
"c.ts",
|
||||
"d.ts",
|
||||
"e.ts",
|
||||
"f.ts",
|
||||
"g.ts",
|
||||
],
|
||||
isHub: true,
|
||||
isEntryPoint: false,
|
||||
fileType: "source",
|
||||
},
|
||||
],
|
||||
[
|
||||
"src/services/user.ts",
|
||||
{
|
||||
complexity: { loc: 50, nesting: 2, cyclomaticComplexity: 5, score: 30 },
|
||||
dependencies: ["src/types/user.ts"],
|
||||
dependents: ["src/controllers/user.ts"],
|
||||
isHub: false,
|
||||
isEntryPoint: false,
|
||||
fileType: "source",
|
||||
},
|
||||
],
|
||||
])
|
||||
|
||||
const result = formatDependencyGraph(metas)
|
||||
|
||||
expect(result).not.toBeNull()
|
||||
const lines = result!.split("\n")
|
||||
const hubIndex = lines.findIndex((l) => l.includes("utils/helpers"))
|
||||
const serviceIndex = lines.findIndex((l) => l.includes("services/user"))
|
||||
expect(hubIndex).toBeLessThan(serviceIndex)
|
||||
})
|
||||
|
||||
it("should sort by total connections (descending) for non-hubs", () => {
|
||||
const metas = new Map<string, FileMeta>([
|
||||
[
|
||||
"src/a.ts",
|
||||
{
|
||||
complexity: { loc: 10, nesting: 1, cyclomaticComplexity: 1, score: 10 },
|
||||
dependencies: ["x.ts"],
|
||||
dependents: [],
|
||||
isHub: false,
|
||||
isEntryPoint: false,
|
||||
fileType: "source",
|
||||
},
|
||||
],
|
||||
[
|
||||
"src/b.ts",
|
||||
{
|
||||
complexity: { loc: 10, nesting: 1, cyclomaticComplexity: 1, score: 10 },
|
||||
dependencies: ["x.ts", "y.ts"],
|
||||
dependents: ["z.ts"],
|
||||
isHub: false,
|
||||
isEntryPoint: false,
|
||||
fileType: "source",
|
||||
},
|
||||
],
|
||||
])
|
||||
|
||||
const result = formatDependencyGraph(metas)
|
||||
|
||||
expect(result).not.toBeNull()
|
||||
const lines = result!.split("\n")
|
||||
const aIndex = lines.findIndex((l) => l.startsWith("a:"))
|
||||
const bIndex = lines.findIndex((l) => l.startsWith("b:"))
|
||||
expect(bIndex).toBeLessThan(aIndex)
|
||||
})
|
||||
|
||||
it("should shorten src/ prefix", () => {
|
||||
const metas = new Map<string, FileMeta>([
|
||||
[
|
||||
"src/index.ts",
|
||||
{
|
||||
complexity: { loc: 10, nesting: 1, cyclomaticComplexity: 1, score: 10 },
|
||||
dependencies: ["src/utils/helpers.ts"],
|
||||
dependents: [],
|
||||
isHub: false,
|
||||
isEntryPoint: true,
|
||||
fileType: "source",
|
||||
},
|
||||
],
|
||||
])
|
||||
|
||||
const result = formatDependencyGraph(metas)
|
||||
|
||||
expect(result).toContain("index: → utils/helpers")
|
||||
expect(result).not.toContain("src/")
|
||||
})
|
||||
|
||||
it("should remove file extensions", () => {
|
||||
const metas = new Map<string, FileMeta>([
|
||||
[
|
||||
"lib/utils.ts",
|
||||
{
|
||||
complexity: { loc: 10, nesting: 1, cyclomaticComplexity: 1, score: 10 },
|
||||
dependencies: ["lib/helpers.tsx", "lib/types.js"],
|
||||
dependents: [],
|
||||
isHub: false,
|
||||
isEntryPoint: false,
|
||||
fileType: "source",
|
||||
},
|
||||
],
|
||||
])
|
||||
|
||||
const result = formatDependencyGraph(metas)
|
||||
|
||||
expect(result).toContain("lib/utils: → lib/helpers, lib/types")
|
||||
expect(result).not.toContain(".ts")
|
||||
expect(result).not.toContain(".tsx")
|
||||
expect(result).not.toContain(".js")
|
||||
})
|
||||
|
||||
it("should remove /index suffix", () => {
|
||||
const metas = new Map<string, FileMeta>([
|
||||
[
|
||||
"src/components/index.ts",
|
||||
{
|
||||
complexity: { loc: 10, nesting: 1, cyclomaticComplexity: 1, score: 10 },
|
||||
dependencies: ["src/utils/index.ts"],
|
||||
dependents: [],
|
||||
isHub: false,
|
||||
isEntryPoint: true,
|
||||
fileType: "source",
|
||||
},
|
||||
],
|
||||
])
|
||||
|
||||
const result = formatDependencyGraph(metas)
|
||||
|
||||
expect(result).toContain("components: → utils")
|
||||
expect(result).not.toContain("/index")
|
||||
})
|
||||
|
||||
it("should handle multiple files in graph", () => {
|
||||
const metas = new Map<string, FileMeta>([
|
||||
[
|
||||
"src/services/user.ts",
|
||||
{
|
||||
complexity: { loc: 50, nesting: 2, cyclomaticComplexity: 5, score: 30 },
|
||||
dependencies: ["src/types/user.ts"],
|
||||
dependents: ["src/controllers/user.ts"],
|
||||
isHub: false,
|
||||
isEntryPoint: false,
|
||||
fileType: "source",
|
||||
},
|
||||
],
|
||||
[
|
||||
"src/services/auth.ts",
|
||||
{
|
||||
complexity: { loc: 40, nesting: 2, cyclomaticComplexity: 4, score: 25 },
|
||||
dependencies: ["src/services/user.ts", "src/utils/jwt.ts"],
|
||||
dependents: ["src/controllers/auth.ts"],
|
||||
isHub: false,
|
||||
isEntryPoint: false,
|
||||
fileType: "source",
|
||||
},
|
||||
],
|
||||
])
|
||||
|
||||
const result = formatDependencyGraph(metas)
|
||||
|
||||
expect(result).toContain("## Dependency Graph")
|
||||
expect(result).toContain("services/user: → types/user ← controllers/user")
|
||||
expect(result).toContain(
|
||||
"services/auth: → services/user, utils/jwt ← controllers/auth",
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("buildInitialContext with includeDepsGraph", () => {
|
||||
const structure: ProjectStructure = {
|
||||
name: "test-project",
|
||||
rootPath: "/test",
|
||||
files: ["src/index.ts"],
|
||||
directories: ["src"],
|
||||
}
|
||||
|
||||
const asts = new Map<string, FileAST>([
|
||||
[
|
||||
"src/index.ts",
|
||||
{
|
||||
imports: [],
|
||||
exports: [],
|
||||
functions: [],
|
||||
classes: [],
|
||||
interfaces: [],
|
||||
typeAliases: [],
|
||||
parseError: false,
|
||||
},
|
||||
],
|
||||
])
|
||||
|
||||
it("should include dependency graph by default", () => {
|
||||
const metas = new Map<string, FileMeta>([
|
||||
[
|
||||
"src/index.ts",
|
||||
{
|
||||
complexity: { loc: 10, nesting: 1, cyclomaticComplexity: 1, score: 10 },
|
||||
dependencies: ["src/utils.ts"],
|
||||
dependents: [],
|
||||
isHub: false,
|
||||
isEntryPoint: true,
|
||||
fileType: "source",
|
||||
},
|
||||
],
|
||||
])
|
||||
|
||||
const context = buildInitialContext(structure, asts, metas)
|
||||
|
||||
expect(context).toContain("## Dependency Graph")
|
||||
expect(context).toContain("index: → utils")
|
||||
})
|
||||
|
||||
it("should exclude dependency graph when includeDepsGraph is false", () => {
|
||||
const metas = new Map<string, FileMeta>([
|
||||
[
|
||||
"src/index.ts",
|
||||
{
|
||||
complexity: { loc: 10, nesting: 1, cyclomaticComplexity: 1, score: 10 },
|
||||
dependencies: ["src/utils.ts"],
|
||||
dependents: [],
|
||||
isHub: false,
|
||||
isEntryPoint: true,
|
||||
fileType: "source",
|
||||
},
|
||||
],
|
||||
])
|
||||
|
||||
const context = buildInitialContext(structure, asts, metas, {
|
||||
includeDepsGraph: false,
|
||||
})
|
||||
|
||||
expect(context).not.toContain("## Dependency Graph")
|
||||
})
|
||||
|
||||
it("should not include dependency graph when metas is undefined", () => {
|
||||
const context = buildInitialContext(structure, asts, undefined, {
|
||||
includeDepsGraph: true,
|
||||
})
|
||||
|
||||
expect(context).not.toContain("## Dependency Graph")
|
||||
})
|
||||
|
||||
it("should not include dependency graph when metas is empty", () => {
|
||||
const emptyMetas = new Map<string, FileMeta>()
|
||||
|
||||
const context = buildInitialContext(structure, asts, emptyMetas, {
|
||||
includeDepsGraph: true,
|
||||
})
|
||||
|
||||
expect(context).not.toContain("## Dependency Graph")
|
||||
})
|
||||
|
||||
it("should not include dependency graph when no files have connections", () => {
|
||||
const metas = new Map<string, FileMeta>([
|
||||
[
|
||||
"src/index.ts",
|
||||
{
|
||||
complexity: { loc: 10, nesting: 1, cyclomaticComplexity: 1, score: 10 },
|
||||
dependencies: [],
|
||||
dependents: [],
|
||||
isHub: false,
|
||||
isEntryPoint: true,
|
||||
fileType: "source",
|
||||
},
|
||||
],
|
||||
])
|
||||
|
||||
const context = buildInitialContext(structure, asts, metas, {
|
||||
includeDepsGraph: true,
|
||||
})
|
||||
|
||||
expect(context).not.toContain("## Dependency Graph")
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -16,6 +16,7 @@ describe("ContextConfigSchema", () => {
|
||||
autoCompressAt: 0.8,
|
||||
compressionMethod: "llm-summary",
|
||||
includeSignatures: true,
|
||||
includeDepsGraph: true,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -28,6 +29,7 @@ describe("ContextConfigSchema", () => {
|
||||
autoCompressAt: 0.8,
|
||||
compressionMethod: "llm-summary",
|
||||
includeSignatures: true,
|
||||
includeDepsGraph: true,
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -165,6 +167,7 @@ describe("ContextConfigSchema", () => {
|
||||
autoCompressAt: 0.8,
|
||||
compressionMethod: "llm-summary",
|
||||
includeSignatures: true,
|
||||
includeDepsGraph: true,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -179,6 +182,7 @@ describe("ContextConfigSchema", () => {
|
||||
autoCompressAt: 0.9,
|
||||
compressionMethod: "llm-summary",
|
||||
includeSignatures: true,
|
||||
includeDepsGraph: true,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -194,6 +198,7 @@ describe("ContextConfigSchema", () => {
|
||||
autoCompressAt: 0.8,
|
||||
compressionMethod: "truncate",
|
||||
includeSignatures: true,
|
||||
includeDepsGraph: true,
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -206,6 +211,7 @@ describe("ContextConfigSchema", () => {
|
||||
autoCompressAt: 0.85,
|
||||
compressionMethod: "truncate" as const,
|
||||
includeSignatures: false,
|
||||
includeDepsGraph: false,
|
||||
}
|
||||
|
||||
const result = ContextConfigSchema.parse(config)
|
||||
@@ -219,6 +225,7 @@ describe("ContextConfigSchema", () => {
|
||||
autoCompressAt: 0.8,
|
||||
compressionMethod: "llm-summary" as const,
|
||||
includeSignatures: true,
|
||||
includeDepsGraph: true,
|
||||
}
|
||||
|
||||
const result = ContextConfigSchema.parse(config)
|
||||
@@ -250,4 +257,29 @@ describe("ContextConfigSchema", () => {
|
||||
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()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user