From dd8ac3a6042e3ddec9bd55aa20953a83fad2568b Mon Sep 17 00:00:00 2001 From: Alex Blank Date: Wed, 10 Jun 2026 17:42:38 +0200 Subject: [PATCH] refactor: move cache to .cache/pi-project-map/ (hidden by default) - Change cache location from .pi-project-map/cache/ to .cache/pi-project-map/ - Cache is now hidden by default (dot directory) - Add .cache/ to .gitignore - Remove committed .pi-project-map/cache/ from repo --- .gitignore | 2 + .pi-project-map/cache/llm-cache.json | 62 ---------------- cache/llm-cache.json | 64 ++++++++++++++++ src/llm/llm-cache.ts | 4 +- src/llm/llm-extract.ts | 105 ++++++++++++++++++++++----- 5 files changed, 154 insertions(+), 83 deletions(-) delete mode 100644 .pi-project-map/cache/llm-cache.json diff --git a/.gitignore b/.gitignore index 99cae24..16a5226 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ coverage/ .pi-map.md # Local Pi runtime state .atl/ +.pi +.cache/ diff --git a/.pi-project-map/cache/llm-cache.json b/.pi-project-map/cache/llm-cache.json deleted file mode 100644 index 404b7fc..0000000 --- a/.pi-project-map/cache/llm-cache.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "25c2db9d6c4c2c511bec4a736f5adf77423be6011bbedd916fb55003d059b4e3": { - "result": "PURPOSE: Test file\nDEPS: none\nCONCEPTS: testing", - "ts": 1781105452802 - }, - "889300ab1d0afe082d820fdd9150229625c3ab69b3b84c2ca65d67479be388cc": { - "result": "PURPOSE: Test file\nDEPS: none\nCONCEPTS: testing", - "ts": 1781105452841 - }, - "585c5da8de102beb6a9906f0e3f07a134b5392bed52fc1a0aaef989e69aa66b6": { - "result": "PURPOSE: Test file\nDEPS: none\nCONCEPTS: testing", - "ts": 1781105452845 - }, - "a6f92e629a4c341fe1e10f92ef923c54002b8a79ec3bedb0b9079bc7aca4e2e0": { - "result": "PURPOSE: Test file\nDEPS: none\nCONCEPTS: testing", - "ts": 1781105452846 - }, - "a6c149b54aec4ced46eb38d6faf1f20ec62fd2f9a2f2dcb6a15c4f339dfb5f1e": { - "result": "PURPOSE: Test file\nDEPS: none\nCONCEPTS: testing", - "ts": 1781105452846 - }, - "4eda95f6452551a12d5b434e284c2e539ce57e0206d5ffa49609f4fa8354a966": { - "result": "PURPOSE: Test file\nDEPS: none\nCONCEPTS: testing", - "ts": 1781105452846 - }, - "510456d000664badf3cbec9f70c80769a119a590e84b1971e49ac5728882a699": { - "result": "PURPOSE: Test file\nDEPS: none\nCONCEPTS: testing", - "ts": 1781105452847 - }, - "b0f5449d50fda0b5c0cf47103188df2d4339b047e1e03cab7a15a431f2990185": { - "result": "PURPOSE: Test file\nDEPS: none\nCONCEPTS: testing", - "ts": 1781105452848 - }, - "eebe9a750792a3d802c6868f32ac264d44ef193a6f1940c4ad5e715b6e31074a": { - "result": "PURPOSE: Test file\nDEPS: none\nCONCEPTS: testing", - "ts": 1781105452848 - }, - "4958b5d7ec5b4c975bb1e2972519aff5fbd94cb7342be4f8dd854a5c7353bd26": { - "result": "PURPOSE: Test file\nDEPS: none\nCONCEPTS: testing", - "ts": 1781105452849 - }, - "389a385ef4fc5d1d253253fa3c931c505c7fbcca68db4ebe6b34392b3ca32d65": { - "result": "PURPOSE: Test file\nDEPS: none\nCONCEPTS: testing", - "ts": 1781105452849 - }, - "879d2eff31eaf101d30de95e0e2df1c1a3a305c4e3b0515a22c85969af49ff25": { - "result": "PURPOSE: Test file\nDEPS: none\nCONCEPTS: testing", - "ts": 1781105452947 - }, - "c2f0a91350b7f5e11a95848c4d806e49d2017862acbc43afb1dd52a5e73c0b24": { - "result": "PURPOSE: Test file\nDEPS: none\nCONCEPTS: testing", - "ts": 1781105452948 - }, - "037ecd1db38c230c248787e60fd7bfc0cb0101b187b59535b6e7483be762d350": { - "result": "PURPOSE: Test file\nDEPS: none\nCONCEPTS: testing", - "ts": 1781105452950 - }, - "b5d546753d33709dff508a6f2c2e547f432267fbb05b68d3da74fa5f7bda9f7a": { - "result": "PURPOSE: Test file\nDEPS: none\nCONCEPTS: testing", - "ts": 1781105452953 - } -} \ No newline at end of file diff --git a/cache/llm-cache.json b/cache/llm-cache.json index c91ca8b..17d8ce3 100644 --- a/cache/llm-cache.json +++ b/cache/llm-cache.json @@ -202,5 +202,69 @@ "64f5ab73ff71a9c41639f3c8a5e248387a01e27a9612f0ed2b682708557f6a27": { "result": "PURPOSE: Unit tests for a Pi extension that registers project map tools and lifecycle event handlers.\nDEPS: vitest, fs, path, os, @mariozechner/pi-coding-agent, typebox, @mariozechner/pi-ai, ../pi-extension.js\nCONCEPTS: unit testing, mocking with vi.mock, dependency injection, temporary filesystem fixtures, tool registration testing, event handler testing", "ts": 1781101463953 + }, + "935ebdfde630eef58b7ce919770361a192cb568352bdc4f6feb73e49712435ec": { + "result": "PURPOSE: Auto-generated dependency lock file that records exact versions of all npm packages for the \"pi-project-map\" project to ensure reproducible installs\nDEPS: npm, node, esbuild, eslint, typescript, vitest, openai, tree-sitter, tree-sitter-python, tree-sitter-typescript, p-limit, picocolors, ignore\nCONCEPTS: dependency resolution, version locking, reproducible builds, transitive dependencies, semantic versioning, package management", + "ts": 1781105803170 + }, + "c02b509bba0bcfa201a834375562034eecad9920aef5b067f9a93387ce2b342e": { + "result": "PURPOSE: Registers four Pi extension tools (project_map_init, project_map_patch, project_map_validate, project_map_reinit) for managing .pi-map.md project analysis files, plus session lifecycle hooks for auto-detection and maintenance hints.\nDEPS: @mariozechner/pi-coding-agent, typebox, fs, path, ./src/index.js, ./src/llm/llm-client.js, ./src/llm/llm-error.js\nCONCEPTS: Extension API registration, LLM client abstraction, file system traversal, schema validation with TypeBox, async tool execution, event hooks (session_start, before_agent_start), progress callbacks, error handling with custom error types", + "ts": 1781105804655 + }, + "c95b6b5a1bdb3154e1570d6d7d6375d7e8f141ece9a26e3ce3ccf3315fbb3595": { + "result": "PURPOSE: Stores cached LLM analysis results keyed by content hash with timestamps to avoid redundant API calls\nDEPS: none\nCONCEPTS: caching, content-addressable storage, memoization, key-value store, hash-based lookup", + "ts": 1781105823217 + }, + "5392cfc86e14d048ff4aa8472945d7ff1254ca160ca656d6a7a02cdfc2931240": { + "result": "PURPOSE: Stores cached LLM analysis results keyed by SHA-256 content hashes with timestamps for a project mapping tool\nDEPS: none (self-contained JSON cache file)\nCONCEPTS: content-addressable storage, caching, JSON serialization, SHA-256 hashing, timestamp tracking, key-value store", + "ts": 1781105832833 + }, + "08c8a9ec61a988cfb22e6cbe381d66ed2b2a9831ea033073a63767fb11d3054c": { + "result": "PURPOSE: Validates and optionally repairs `.pi-map.md` files against actual project state by detecting missing maps, orphaned entries, dirty markers, and stale export signatures.\nDEPS: fs, path, ./discover.js, ./format.js, ./ast/ast-extract.js, ./init.js\nCONCEPTS: AST analysis, set comparison, discrepancy detection, dry-run vs fix modes, directory iteration", + "ts": 1781105865731 + }, + "f81a700850a8ab1067d1329af2675464e6435137e278ab8d62fcd5fc0bc5a1c4": { + "result": "PURPOSE: Patches a `.pi-map.md` file for a given source file, using full rewrite for small packages or section-level merging of LLM and AST extracted data for larger packages.\nDEPS: path, fs, ./format.js, ./llm/llm-extract.js, ./ast/ast-extract.js, ./merge.js, ./init.js, ./llm/llm-client.js\nCONCEPTS: conditional logic, file I/O, async/await, AST extraction, LLM extraction, data merging, threshold-based strategy pattern, in-place array mutation", + "ts": 1781105867062 + }, + "9f9cdf5a97d15f2ada69a3290288acbbca55693293e64eaa54ed68a3b69085dd": { + "result": "PURPOSE: Orchestrates project initialization by discovering directories, extracting file metadata via LLM and AST analysis, and generating `.pi-map.md` documentation files for each package.\nDEPS: fs, path, discover.js, format.js, llm/llm-extract.js, ast/ast-extract.js, merge.js, llm/llm-batch.js, llm/llm-client.js\nCONCEPTS: async/await, concurrency control, batch processing with retry logic, progress callbacks, dependency injection, file I/O, data merging from multiple sources", + "ts": 1781105867332 + }, + "b84477286885b2bed506b8b4b80b9a9a1ca0cb3b351bcd72f938559e55b152bc": { + "result": "PURPOSE: CLI entry point for a project mapping tool that generates, patches, validates, and regenerates `.pi-map.md` files using LLM-powered analysis.\nDEPS: picocolors, process, ../init.js, ../patch.js, ../validate.js, ../discover.js, ../llm/llm-client.js, ../config.js, ../package.json\nCONCEPTS: CLI argument parsing, command dispatch pattern, async/await, error handling with custom error classes, LLM client abstraction, configuration loading, process exit codes, pluralization formatting", + "ts": 1781105883128 + }, + "cb84ff08ab46a12f7a1fb49c7c3874743d4c38dd76d22c82ccea424e295ed929": { + "result": "PURPOSE: Extracts structured metadata (purpose, dependencies, concepts) from source files and packages using LLM prompts, with binary detection, size limits, and caching.\nDEPS: fs, crypto, path, llm-client.js, llm-cache.js, llm-error.js\nCONCEPTS: LLM prompt engineering, structured output parsing, content hashing, binary file detection, caching, context window management, token budgeting", + "ts": 1781105897002 + }, + "15cf6bdf05a57be59d4729d5095808645f6c6883d2d1e8615b3b5ce12d774a81": { + "result": "PURPOSE: Unit tests for an LLM response caching module that stores and retrieves string results by hash key in a JSON file.\nDEPS: vitest, fs, path, os, ../src/llm/llm-cache.js\nCONCEPTS: unit testing, file-based caching, test isolation with beforeEach/afterEach cleanup, temporary file storage", + "ts": 1781105907011 + }, + "2c776feaa98a5da6e1e68f908d855479994633779a7b6987f3675f0052c2cd98": { + "result": "PURPOSE: Tests retry logic and parallel file processing utilities for LLM batch operations.\nDEPS: vitest, ../src/llm/llm-batch.js, ../src/llm-error.js\nCONCEPTS: unit testing, retry patterns, concurrency control, async/await, error handling", + "ts": 1781105907677 + }, + "6198821ec6dd5417980e89ed44488c1d8bd78616a8396186acaed23bb331ad8a": { + "result": "PURPOSE: Tests the AST extraction functionality for parsing TypeScript exports, imports, and unsupported file types.\nDEPS: vitest, fs, path, os, ast-extract.js\nCONCEPTS: unit testing, temporary file creation, async/await, null safety with non-null assertion operator", + "ts": 1781105907686 + }, + "9f4d46b86a312ada127f0d5d295c4933959c42b8797e67a51abeed90bb44d468": { + "result": "PURPOSE: Integration tests for a project mapping tool that verifies init, patch, and validate functionality using temporary directories and mock LLM clients.\nDEPS: vitest, fs, path, os, ../src/init.js, ../src/patch.js, ../src/validate.js, ./mock-llm.js\nCONCEPTS: integration testing, temporary filesystem fixtures, mock objects, setup/teardown hooks, async/await", + "ts": 1781105908326 + }, + "d74151fa0b1c6e8dfdb5987527a624466beb4e5b6aecd1c16f57418046613c2d": { + "result": "PURPOSE: Tests the extractFileLLM function's behavior with mock LLM clients, including parsing responses, handling missing clients, and skipping large/binary files.\nDEPS: vitest, fs, path, os, ../src/llm/llm-extract.js, ../src/llm/llm-client.js\nCONCEPTS: unit testing, mocking, async/await, temporary file fixtures, dependency injection, parser validation, edge case handling", + "ts": 1781105909833 + }, + "5694eddea81e0978855baf756c906337a6edf1c2f6f1615ab31a66d2aeb56750": { + "result": "PURPOSE: Provides mock LLM client factories for testing that return predefined analysis strings\nDEPS: ../src/llm/llm-client.js\nCONCEPTS: mocking, factory functions, dependency injection, testing", + "ts": 1781105910171 + }, + "bd21b840465df9c0972a36b707e13ed712a3186a39312876178415d73250956a": { + "result": "PURPOSE: Integration tests for LLM client functionality including Kimi API calls, file/package extraction, caching, parallel processing, and error handling\nDEPS: vitest, fs, path, os, ../src/llm/llm-client.js, ../src/llm/llm-extract.js, ../src/llm/llm-batch.js\nCONCEPTS: integration testing, environment variable management, temporary file system operations, LLM API mocking/spying, caching validation, concurrency control, error handling for missing credentials, test skipping based on environment conditions", + "ts": 1781105911634 } } \ No newline at end of file diff --git a/src/llm/llm-cache.ts b/src/llm/llm-cache.ts index f8b488a..8c8b75b 100644 --- a/src/llm/llm-cache.ts +++ b/src/llm/llm-cache.ts @@ -7,8 +7,8 @@ import { } from "fs"; import { join } from "path"; -const DEFAULT_CACHE_SUBDIR = ".pi-project-map"; -const DEFAULT_CACHE_FILE = "cache/llm-cache.json"; +const DEFAULT_CACHE_SUBDIR = ".cache/pi-project-map"; +const DEFAULT_CACHE_FILE = "llm-cache.json"; interface CacheEntry { result: string; diff --git a/src/llm/llm-extract.ts b/src/llm/llm-extract.ts index d654500..ac8c326 100644 --- a/src/llm/llm-extract.ts +++ b/src/llm/llm-extract.ts @@ -23,15 +23,58 @@ const CHARS_PER_TOKEN = 4; // approximate for ASCII // Known binary extensions — skip without reading content const BINARY_EXTENSIONS = new Set([ - ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".webp", ".ico", ".svgz", - ".mp3", ".mp4", ".avi", ".mov", ".mkv", ".flv", ".wmv", - ".wav", ".ogg", ".flac", ".aac", ".wma", - ".zip", ".tar", ".gz", ".bz2", ".xz", ".7z", ".rar", - ".exe", ".dll", ".so", ".dylib", ".bin", - ".pdf", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", - ".wasm", ".class", ".jar", ".o", ".a", - ".ttf", ".otf", ".woff", ".woff2", ".eot", - ".db", ".sqlite", ".sqlite3", + ".png", + ".jpg", + ".jpeg", + ".gif", + ".bmp", + ".webp", + ".ico", + ".svgz", + ".mp3", + ".mp4", + ".avi", + ".mov", + ".mkv", + ".flv", + ".wmv", + ".wav", + ".ogg", + ".flac", + ".aac", + ".wma", + ".zip", + ".tar", + ".gz", + ".bz2", + ".xz", + ".7z", + ".rar", + ".exe", + ".dll", + ".so", + ".dylib", + ".bin", + ".pdf", + ".doc", + ".docx", + ".xls", + ".xlsx", + ".ppt", + ".pptx", + ".wasm", + ".class", + ".jar", + ".o", + ".a", + ".ttf", + ".otf", + ".woff", + ".woff2", + ".eot", + ".db", + ".sqlite", + ".sqlite3", ]); function isBinaryFile(filePath: string): boolean { @@ -77,7 +120,11 @@ ${content} `; } -function parseFileResponse(response: string): { purpose: string; deps: string[]; concepts: string[] } { +function parseFileResponse(response: string): { + purpose: string; + deps: string[]; + concepts: string[]; +} { const lines = response.split("\n"); let purpose = ""; let deps: string[] = []; @@ -89,18 +136,35 @@ function parseFileResponse(response: string): { purpose: string; deps: string[]; purpose = trimmed.slice("PURPOSE:".length).trim(); } else if (trimmed.startsWith("DEPS:")) { const depsStr = trimmed.slice("DEPS:".length).trim(); - deps = depsStr === "none" ? [] : depsStr.split(",").map((s) => s.trim()).filter(Boolean); + deps = + depsStr === "none" + ? [] + : depsStr + .split(",") + .map((s) => s.trim()) + .filter(Boolean); } else if (trimmed.startsWith("CONCEPTS:")) { const conceptsStr = trimmed.slice("CONCEPTS:".length).trim(); - concepts = conceptsStr === "none" ? [] : conceptsStr.split(",").map((s) => s.trim()).filter(Boolean); + concepts = + conceptsStr === "none" + ? [] + : conceptsStr + .split(",") + .map((s) => s.trim()) + .filter(Boolean); } } return { purpose, deps, concepts }; } -function buildPackagePrompt(relativePath: string, fileSummaries: { name: string; purpose: string }[]): string { - const filesList = fileSummaries.map((f) => `- ${f.name}: ${f.purpose}`).join("\n"); +function buildPackagePrompt( + relativePath: string, + fileSummaries: { name: string; purpose: string }[], +): string { + const filesList = fileSummaries + .map((f) => `- ${f.name}: ${f.purpose}`) + .join("\n"); return `Analyze this code package/directory. Respond in this exact format (one line each): ROLE: ARCH: @@ -111,7 +175,10 @@ ${filesList} `; } -function parsePackageResponse(response: string): { role: string; arch: string } { +function parsePackageResponse(response: string): { + role: string; + arch: string; +} { const lines = response.split("\n"); let role = ""; let arch = ""; @@ -137,8 +204,8 @@ export async function extractFileLLM( if (!client) { throw new LLMError( "No LLM client configured. " + - "Set OPENAI_API_KEY / KIMI_API_KEY environment variable, " + - "or run inside Pi with a configured model.", + "Set OPENAI_API_KEY / KIMI_API_KEY environment variable, " + + "or run inside Pi with a configured model.", ); } @@ -201,8 +268,8 @@ export async function extractPackageLLM( if (!client) { throw new LLMError( "No LLM client configured. " + - "Set OPENAI_API_KEY / KIMI_API_KEY environment variable, " + - "or run inside Pi with a configured model.", + "Set OPENAI_API_KEY / KIMI_API_KEY environment variable, " + + "or run inside Pi with a configured model.", ); }