From 7b67205d43982b6e46426197f5e45e62e8ac8908 Mon Sep 17 00:00:00 2001 From: Alex Blank Date: Tue, 9 Jun 2026 21:45:44 +0200 Subject: [PATCH] Update design doc and implementation plan for proper LLM integration - design-doc.md: Rewrote Section 3 (Pipeline Architecture) to describe the real LLM integration: dual-provider LLM client (Pi native + external), SHA-256 disk cache in ~/.cache/pi-project-map/, parallelization with 4-8 concurrent requests, retries with exponential backoff, hard-error policy, context limit protection. Added new Section 4 with concrete file-level and package-level prompts. - implementation-plan.md: Replaced old milestone schedule with current status and detailed M7 tasks for LLM integration: LLM client abstraction, external API client, Pi LLM client, disk cache, parallel batching with retries, rewriting llm-extract.ts, context limits, CLI/extension wiring, and tests. - Minor formatting cleanup in pi-extension.ts and src/cli.ts All 16 tests pass. TypeScript compiles clean. --- design-doc.md | 120 ++++++++++++-- implementation-plan.md | 357 ++++++++++++++++++++--------------------- pi-extension.ts | 20 ++- src/cli.ts | 39 +++-- 4 files changed, 326 insertions(+), 210 deletions(-) diff --git a/design-doc.md b/design-doc.md index b362931..fea79d0 100644 --- a/design-doc.md +++ b/design-doc.md @@ -75,9 +75,10 @@ Two independent extraction layers contribute to the same output file. #### Layer 1: LLM-Based Extraction (All Files) - **Input**: Raw file contents of every non-excluded file in the directory. -- **Output**: One-line purpose description, architectural role, and cross-file relationships. +- **Output**: Purpose description, architectural role, and cross-file relationships. - **Applies to**: Code files, config files, Dockerfiles, READMEs, YAML, JSON, shell scripts — everything. - **When it runs**: Once per file during init; again on changed files during patching. +- **Implementation**: Calls an actual LLM (not regex heuristics). Inside Pi, it uses Pi's built-in LLM via the ExtensionAPI. Standalone CLI falls back to an external LLM API (OpenAI-compatible). #### Layer 2: AST-Based Extraction (Code Files Only) - **Input**: Source code of files where a tree-sitter or LSP parser is available. @@ -93,17 +94,55 @@ The two layers merge into a single line per file under `## files`: ^ LLM ^ LLM ^ AST ^ AST + LLM ``` -- File name and one-line purpose: LLM. +- File name and purpose: LLM. - Exported symbols and signatures: AST (augmented by LLM if AST unavailable). - Dependency list: AST for imports; LLM for inferred architectural dependencies. +### LLM Client Architecture + +The LLM client is abstracted behind a unified interface: + +```typescript +interface LLMClient { + complete(prompt: string): Promise; +} +``` + +Two implementations: + +1. **PiLLMClient** (Pi extension): Uses `ctx.model` or `ctx.modelRegistry` to invoke Pi's configured LLM. Called from `pi-extension.ts` when the skill runs inside Pi. +2. **ExternalLLMClient** (standalone CLI): Calls an external OpenAI-compatible API. Configured via environment variable (e.g., `OPENAI_API_KEY`) or config file. + +### Caching + +LLM results are cached to avoid re-querying unchanged files. + +- **Key**: SHA-256 hash of file contents. +- **Storage**: JSON file at `~/.cache/pi-project-map/llm-cache.json`. +- **Behavior**: Before calling the LLM, compute the file hash and check the cache. If hit, reuse the cached result. If miss, call the LLM and store the result. +- **Invalidation**: Cache entries are implicitly invalidated when the file content changes (because the hash changes). There is no TTL; the cache is append-only. + +### Parallelization and Rate Limiting + +- **Concurrency**: 4-8 LLM calls in parallel, controlled by `p-limit`. +- **Batch delays**: A small delay (e.g., 100ms) is inserted between batches to avoid triggering rate limits. +- **Retry policy**: Each LLM call retries up to 3 times with exponential backoff (1s, 2s, 4s). If all retries fail, the entire operation stops with a hard error. + +### Error Handling + +- **Hard error on failure**: If an LLM call fails after all retries, `init` or `patch` stops immediately and prints a clear error. There is no heuristic fallback. The user must resolve the issue (set API key, wait for rate limit, check network). +- **Context limit protection**: Files larger than the LLM's context window are truncated from the end (with a note in the prompt) before being sent. + ### Init Pipeline ``` For each directory (depth-first): 1. List all non-excluded files. - 2. For each file: - a. Run LLM extraction (purpose, role). - b. If code file + parser available: run AST extraction (symbols, imports). + 2. For each file (parallel, 4-8 concurrent): + a. Compute SHA-256 of file contents. + b. Check disk cache. If hit, use cached result. + c. If miss: call LLM (with retries/backoff) to extract purpose and role. + d. Store result in cache. + e. If code file + parser available: run AST extraction (symbols, imports). 3. Merge per-file outputs into lines. 4. Run LLM on merged lines + directory context to generate: - `## role` (package-level summary) @@ -117,13 +156,68 @@ When agent edits file(s) in directory: 1. Determine patch strategy: - If directory has < 10 files: full rewrite. - Else: section-level patch for changed file(s) only. - 2. Re-run LLM extraction on changed file(s). + 2. For each changed file: + a. Recompute SHA-256. + b. Check cache. If miss or stale, call LLM with retries/backoff. 3. Re-run AST extraction on changed file(s) if applicable. 4. Update `## files` section (rewrite or patch). 5. Update `## dirty` flag if full regeneration is deferred. ``` -## 4. Consumption Model +## 4. LLM Prompt Design + +### File-Level Prompt + +The LLM prompt for a single file is designed to produce a structured, concise analysis. + +``` +You are analyzing a source file for a project map. Read the file below and summarize: + +1. PURPOSE: What does this file do? Describe its role in the project (2-3 sentences max). +2. DEPENDENCIES: What does this file depend on? List internal modules/packages and external libraries. +3. KEY CONCEPTS: Mention any important patterns, algorithms, or domain concepts. + +File path: + +``` + +``` + +Respond in this exact format: +PURPOSE: +DEPS: +CONCEPTS: +``` + +### Package-Level Prompt + +After all file summaries are collected for a directory, a second LLM call synthesizes the package role and architecture. + +``` +You are analyzing a directory in a software project. Below is a list of files in this directory with their purposes. + +Directory: +Files: +- : +- : +... + +Respond in this exact format: +ROLE: +ARCH: <2-4 sentences describing architecture, data flow, patterns, and design decisions> +``` + +### Output Parsing + +The LLM client's response is parsed to extract `PURPOSE`, `DEPS`, `CONCEPTS`, `ROLE`, and `ARCH` fields. These are merged with AST data into the final `.pi-map.md` format. + +### Context Limit Protection + +- Files are truncated from the end if they exceed a configurable max token budget (default: 4000 tokens of source). +- A marker `[...truncated]` is appended to the truncated content so the LLM knows it is not seeing the full file. +- Very large binary or generated files are skipped entirely for LLM analysis (they still appear in `.pi-map.md` with a note like "Large/generated file"). + +## 5. Consumption Model ### Session Start 1. Agent discovers all `.pi-map.md` files (e.g., via `find . -name ".pi-map.md"`). @@ -139,7 +233,7 @@ When agent edits file(s) in directory: - For very large projects, the agent may summarize or prune the initial read, keeping only the top N levels of the hierarchy in active context. - The skill can provide a "context budget" parameter: max tokens to spend on analysis files. -## 5. Stale Data Mitigation +## 6. Stale Data Mitigation ### Combined Strategy @@ -167,7 +261,7 @@ When agent edits file(s) in directory: - If validation finds staleness beyond a threshold (e.g., > 3 dirty packages), the skill recommends a full re-init. - The agent can also trigger re-init for a specific subtree. -## 6. Scope Boundaries and Non-Goals +## 7. Scope Boundaries and Non-Goals ### In Scope - Every directory in the project gets a `.pi-map.md` file. @@ -182,7 +276,7 @@ When agent edits file(s) in directory: - **Cross-project analysis**: Each project is independent. No global index across repos. - **IDE integration**: This is a Pi agent skill, not a VS Code extension or LSP server. -## 7. Pi Skill Package Structure +## 8. Pi Skill Package Structure ``` pi-project-map/ @@ -212,7 +306,7 @@ pi-project-map/ - On every prompt, the skill appends a lightweight instruction: > "If you modify any source file, run `project-map:patch ` to update the analysis. If you suspect staleness, run `project-map:validate`." -## 8. Risks and Tradeoffs +## 9. Risks and Tradeoffs | Risk | Likelihood | Impact | Mitigation | |------|-----------|--------|------------| @@ -223,7 +317,7 @@ pi-project-map/ | Overlap with LSP/typedoc | Low | Low | This is agent-context, not IDE tooling. Different use case. | | AST parser unavailable | Medium | Low | Graceful fallback to LLM-only extraction | -## 9. Concrete Example: Full Project Snapshot +## 10. Concrete Example: Full Project Snapshot ``` project-root/ @@ -250,7 +344,7 @@ project-root/ Each `.pi-map.md` follows the format in Section 2, creating a navigable hierarchy. -## 10. Future Extensions +## 11. Future Extensions - **Cross-reference graph**: A top-level `project-graph.md` linking all packages with dependency arrows. - **Search index**: A lightweight FTS5 index over all `.pi-map.md` files for fast symbol lookup. diff --git a/implementation-plan.md b/implementation-plan.md index f669830..ece02f0 100644 --- a/implementation-plan.md +++ b/implementation-plan.md @@ -6,241 +6,236 @@ Build a Pi skill package (`pi-project-map`) that generates and maintains a hiera --- -## Milestones +## Current Status -### M1: Foundation and Format (Week 1) -**Goal**: A working dense-markdown formatter and the ability to generate `.pi-map.md` files from a directory listing. +- **M1: Foundation and Format** — ✅ Complete +- **M2: Heuristic Extraction** — ✅ Complete (placeholder only; to be replaced by real LLM) +- **M3: AST Extraction** — ✅ Complete +- **M4: Patch and Update** — ✅ Complete +- **M5: Validation and `--fix`** — ✅ Complete +- **M6: Pi Skill Integration** — ✅ Complete (basic) +- **M7: Proper LLM Integration** — 🔄 In Progress / Next up -#### Tasks -1. **Bootstrap the npm package** - - Create `package.json`, `tsconfig.json`, basic CLI entry point. - - Set up test runner (vitest or jest). - - Create stub `SKILL.md` for Pi integration. - -2. **Implement the formatter (`src/format.ts`)** - - Define the `.pi-map.md` schema as a TypeScript interface. - - Implement `renderPackageMap(data) -> string` that outputs dense markdown. - - Implement `parsePackageMap(markdown) -> data` for reading existing files. - - Unit tests for round-trip serialization. - -3. **Implement file discovery (`src/discover.ts`)** - - Walk a directory tree. - - Respect `.gitignore` and a built-in ignore list (node_modules, .git, dist, etc.). - - Return a flat list of `(dirPath, filePaths[])` tuples. - - Unit tests with fixture directories. - -4. **Implement init skeleton (`src/init.ts`)** - - Given a project root, create one `.pi-map.md` per directory. - - Populate `## files` with filenames only (no analysis yet). - - Populate `## role` and `## arch` with placeholder text. - - Command: `project-map:init --root ` - -**Acceptance Criteria** -- Running `project-map:init` on a test repo creates `.pi-map.md` in every directory. -- Files match `.gitignore` rules correctly. -- Output format follows the dense-markdown spec from the design doc. - -**Dependencies**: None. +The remaining major work is to replace the heuristic "LLM" extraction with **actual LLM API calls**. --- -### M2: LLM Extraction Layer (Week 2) -**Goal**: The LLM layer generates one-line purposes and package-level architecture notes. +## Remaining Milestone: M7 — Proper LLM Integration -#### Tasks -1. **Implement LLM file extraction (`src/llm-extract.ts`)** - - Prompt template: given a file's contents and path, return a one-line purpose and a list of exported symbols (if any). - - Prompt template: given a file's contents, infer its dependencies. - - Integrate with Pi's LLM tool calls (or a generic OpenAI-compatible client for standalone testing). - - Add caching: hash file contents, skip LLM call if unchanged. +**Goal**: Replace the regex heuristic `llm-extract.ts` with real LLM calls, including dual-provider support (Pi native + external fallback), disk caching, parallelization, and retries. -2. **Implement LLM package extraction (`src/llm-extract.ts`)** - - Prompt template: given all per-file summaries in a directory, generate `## role` and `## arch`. - - Keep prompts token-efficient (truncate large files, focus on headers/exports). - -3. **Wire LLM layer into init (`src/init.ts`)** - - After file discovery, run LLM extraction on every file. - - Then run package-level LLM extraction. - - Write fully populated `.pi-map.md` files. - -4. **Add configuration (`src/config.ts`)** - - Configurable ignore patterns. - - Configurable LLM model and token limits. - - Configurable context budget for large projects. - -**Acceptance Criteria** -- `project-map:init` produces `.pi-map.md` files with meaningful `## files`, `## role`, and `## arch` sections. -- Caching works: second run on unchanged repo is fast. -- Unit tests mock LLM responses to verify prompt structure and output parsing. - -**Dependencies**: M1. +**Estimated time**: 1 week of focused work. --- -### M3: AST Extraction Layer (Week 3) -**Goal**: Code files get precise symbol and dependency data from tree-sitter or LSP. +### Task 1: LLM Client Abstraction (`src/llm-client.ts`) -#### Tasks -1. **Set up tree-sitter (`src/ast-extract.ts`)** - - Add tree-sitter dependencies for target languages (TypeScript, Python, Go, Rust as first-class). - - Implement `extractSymbols(filePath, language) -> { exports, imports, types }`. - - Implement `extractDependencies(filePath, language) -> string[]`. +Create a unified interface for LLM calls. -2. **Language detection** - - Map file extensions to tree-sitter grammars. - - Graceful fallback to LLM-only if no parser available. +```typescript +interface LLMClient { + complete(prompt: string): Promise; +} +``` -3. **Merge AST + LLM outputs (`src/merge.ts`)** - - Combine AST symbols with LLM purpose descriptions. - - AST provides `exp:` and `dep:`; LLM provides the one-line purpose. - - If AST and LLM disagree on dependencies, prefer AST for imports, LLM for inferred architectural deps. - -4. **Wire AST into init (`src/init.ts`)** - - For each code file, run AST extraction in parallel with LLM extraction. - - Merge results before formatting. +Sub-tasks: +- Define the `LLMClient` interface. +- Add a factory function `createLLMClient(mode: 'pi' | 'external', options)`. +- Handle mode selection: + - `'pi'`: used inside `pi-extension.ts` with Pi's model registry. + - `'external'`: used in standalone CLI with OpenAI-compatible API. **Acceptance Criteria** -- TypeScript/Python files show precise exported function/class names in `exp:`. -- Import statements are captured in `dep:`. -- Files with no AST parser still get LLM-only analysis. -- Unit tests with sample source files verify symbol extraction accuracy. - -**Dependencies**: M2. +- `LLMClient` interface exists and compiles. +- Factory correctly selects implementation based on mode. --- -### M4: Patch and Update (Week 4) -**Goal**: The agent can incrementally update `.pi-map.md` files after editing source files. +### Task 2: External LLM Client (`src/external-llm-client.ts`) -#### Tasks -1. **Implement patch strategy (`src/patch.ts`)** - - Determine directory size (file count). - - If < 10 files: trigger full rewrite of that directory's `.pi-map.md`. - - If >= 10 files: parse existing `.pi-map.md`, identify the section(s) for changed file(s), and rewrite only those lines. +Implement the standalone CLI LLM client using an OpenAI-compatible API. -2. **Implement dirty markers (`src/patch.ts`)** - - When a section-level patch occurs, append a dirty note: - ```markdown - ## dirty - 2024-06-09T14:32:00Z: tokens.ts patched (section-level) - ``` - -3. **Command: `project-map:patch `** - - Given a changed file path, find its directory. - - Re-run LLM + AST extraction on that file only. - - Apply patch strategy. - - Update `.pi-map.md`. - -4. **Command: `project-map:reinit [path]`** - - Force full re-init of entire project or a subtree. - - Clear all dirty markers. +Sub-tasks: +- Add `openai` (or a lightweight fetch-based client) as a dependency. +- Read configuration from: + - Environment variable `OPENAI_API_KEY` (or `ANTHROPIC_API_KEY`, etc.) + - Config file `.pi-project-map.json` fields: `llmProvider`, `llmModel`, `llmBaseUrl` +- Implement `complete(prompt)` using chat completions API. +- Default to `gpt-4o-mini` or similar cheap model. **Acceptance Criteria** -- Editing one file in a small package triggers full rewrite of that package's `.pi-map.md`. -- Editing one file in a large package patches only the relevant line. -- Dirty markers are correctly added on section-level patches. -- Re-init clears dirty markers and regenerates everything. - -**Dependencies**: M3. +- `project-map init` works standalone with `OPENAI_API_KEY` set. +- Missing API key produces a clear, actionable error message. +- Failed API call throws a descriptive `LLMError`. --- -### M5: Validation and Consistency (Week 5) -**Goal**: The agent can detect and recover from stale analysis files. +### Task 3: Pi LLM Client (`src/pi-llm-client.ts`) -#### Tasks -1. **Implement validation (`src/validate.ts`)** - - Walk all `.pi-map.md` files. - - For each listed file, check existence (catch deletions). - - For each directory, check for new files not in `.pi-map.md`. - - For code files with AST support, compare listed `exp:` against actual symbols. - - Report discrepancies with severity (missing, orphaned, stale-signature). +Implement the Pi-native LLM client for use inside the extension. -2. **Implement reconciliation (`src/validate.ts`)** - - Option `--fix`: automatically patch or flag discrepancies. - - Option `--reinit-if `: if > N discrepancies, recommend full re-init. - -3. **Command: `project-map:validate`** - - Run checks and print a report. - - Exit code 0 if clean, 1 if discrepancies found. +Sub-tasks: +- Accept Pi's `ExtensionContext` or model registry as a constructor argument. +- Use `ctx.model` / `ctx.modelRegistry` to get the configured model and API key. +- Call the provider directly (likely using the same OpenAI-compatible endpoint Pi uses). +- If Pi does not expose direct LLM calls, fall back to emitting a tool call / follow-up message pattern. **Acceptance Criteria** -- Validation detects a deleted file that still appears in `.pi-map.md`. -- Validation detects a new file not yet in `.pi-map.md`. -- Validation detects a changed function signature. -- `--fix` patches or flags all found issues. - -**Dependencies**: M4. +- Pi extension can successfully call an LLM when running inside Pi. +- Errors surface clearly to the user. --- -### M6: Pi Integration (Week 6) -**Goal**: The skill is installable and functional within the Pi agent harness. +### Task 4: Disk Cache (`src/llm-cache.ts`) -#### Tasks -1. **Finalize `SKILL.md`** - - Document all custom tools: `project-map:init`, `project-map:patch`, `project-map:validate`, `project-map:reinit`. - - Document the dense-markdown format for the agent. - - Include usage examples and configuration options. +Implement persistent SHA-256 → LLM result caching. -2. **Implement prompt hook (`hooks/on-prompt.ts`)** - - Inject a lightweight instruction into every prompt: - > "If you modify any source file, run `project-map:patch `. If you suspect staleness, run `project-map:validate`." - - Make injection configurable (toggle on/off, customize message). - -3. **Auto-inject session-start behavior** - - On session start, if `.pi-map.md` files exist, the skill auto-reads them into context. - - If they don't exist, the skill offers to run `project-map:init`. - - If dirty markers exist, the skill warns the agent. - -4. **Packaging and publishing** - - Ensure `package.json` has correct `bin` entries. - - Write human README with install instructions. - - Tag and publish to npm (or internal registry). +Sub-tasks: +- Cache directory: `~/.cache/pi-project-map/` (create if missing). +- Cache file: `llm-cache.json` (simple JSON object). +- Functions: + - `getCached(hash: string): string | null` + - `setCached(hash: string, result: string): void` +- Ensure atomic writes (write to temp file, rename). +- Add cache size limit (e.g., 10,000 entries, LRU eviction). **Acceptance Criteria** -- Installing the skill in Pi makes the four tools available. -- The prompt hook injects maintenance instructions reliably. -- Session start auto-detects and loads `.pi-map.md` files. -- The skill can be published and installed via npm. +- Second `init` run on unchanged files does not call LLM. +- Cache persists across process restarts. +- Corrupted cache file does not crash the tool. -**Dependencies**: M5. +--- + +### Task 5: Parallelization and Retries (`src/llm-batch.ts`) + +Run LLM requests in parallel with retries and backoff. + +Sub-tasks: +- Add `p-limit` dependency for concurrency control. +- Default concurrency: 4 (configurable via `.pi-project-map.json` `llmConcurrency`). +- Add small delay (100ms) between batches. +- Implement retry logic: 3 retries, delays 1s → 2s → 4s. +- On final failure, throw a hard error and stop the entire process. + +**Acceptance Criteria** +- 100-file project completes significantly faster than sequential. +- Simulated transient failures are retried and recovered. +- Persistent failure stops the tool with a clear error. + +--- + +### Task 6: Rewrite `llm-extract.ts` to Use Real LLM + +Replace regex heuristics with LLM calls. + +Sub-tasks: +- Accept an `LLMClient` in `extractFileLLM` and `extractPackageLLM`. +- Check disk cache before calling LLM. +- Construct file-level prompt (see design doc Section 4). +- Parse response into `purpose`, `deps`, `concepts`. +- Construct package-level prompt. +- Parse response into `role`, `arch`. +- Remove heuristic code from production path; keep only for test mocks if useful. + +**Acceptance Criteria** +- `llm-extract.ts` calls the LLM client for every file. +- Output includes rich, non-trivial descriptions for typical files. +- Unit tests mock the LLM client to verify prompt structure and parsing. + +--- + +### Task 7: Context Limit Handling + +Protect against oversized files. + +Sub-tasks: +- Measure prompt + file content tokens (approximate: 1 token ≈ 4 chars for ASCII). +- If file exceeds max context budget (configurable, default 4000 tokens), truncate from the end. +- Append `[...truncated]` marker in the prompt. +- Skip LLM for binary/generated files over a hard limit (e.g., 50KB) and mark them as "Large/generated file". + +**Acceptance Criteria** +- A 1MB minified JS file does not crash or consume excessive tokens. +- Truncated files still produce useful output. + +--- + +### Task 8: Update CLI and Extension + +Wire the new LLM client into all entry points. + +Sub-tasks: +- `src/cli.ts`: create external LLM client, pass into `initProject` / `patchFile`. +- `pi-extension.ts`: create Pi LLM client, pass into tools. +- Update `init.ts` and `patch.ts` signatures to accept an optional `LLMClient`. +- Add CLI flag `--llm-provider=openai` for explicit selection. +- Update error handling to catch `LLMError` and print helpful messages. + +**Acceptance Criteria** +- CLI works with external API key. +- Extension works inside Pi (if Pi exposes LLM access). +- Clear errors on misconfiguration. + +--- + +### Task 9: Update Tests + +Sub-tasks: +- Replace heuristic tests with mocked LLM client tests. +- Add integration test: create a fake LLM client, run `initProject`, verify output contains LLM-provided text. +- Add cache test: verify cache hit skips LLM call. +- Add retry test: verify transient failures retry, persistent failures hard-stop. + +**Acceptance Criteria** +- All tests pass. +- Test coverage includes: LLM client, cache, batching, prompt parsing, error handling. --- ## Sequencing and Dependencies ``` -M1 (Foundation) → M2 (LLM) → M3 (AST) → M4 (Patch) → M5 (Validate) → M6 (Pi Integration) +Task 1 (LLMClient interface) + ├── Task 2 (External client) + ├── Task 3 (Pi client) + ├── Task 4 (Cache) + ├── Task 5 (Batch + retries) + │ + ├── Task 6 (Rewrite llm-extract.ts) + ├── Task 7 (Context limits) + │ + ├── Task 8 (Wire CLI + extension) + └── Task 9 (Tests) ``` -No parallel tracks — each milestone builds on the previous. Total estimated time: 6 weeks at a steady pace, or 3-4 weeks with focused effort. - --- -## Validation Criteria (Overall) +## Validation Criteria (for LLM Integration) -1. **Functional**: `project-map:init` correctly generates `.pi-map.md` for a test repo of 50+ files across 10+ directories. -2. **Accuracy**: AST-extracted symbols match actual source code (verified by unit tests). -3. **Freshness**: After editing a file, `project-map:patch` updates the analysis within 5 seconds. -4. **Consistency**: `project-map:validate` detects 100% of synthetic staleness scenarios (deleted files, new files, signature changes). -5. **Token efficiency**: A 100-file project's full `.pi-map.md` set fits within a 4k token budget. -6. **Pi integration**: The skill installs cleanly, tools are discoverable, and the prompt hook works. +1. **Real LLM calls**: `llm-extract.ts` invokes the configured LLM client for every file. +2. **Cache hit**: Second `init` on unchanged repo completes with zero LLM calls. +3. **Parallel speed**: 100-file project init completes in under 30 seconds (assuming average LLM latency 500ms). +4. **Retry works**: Transient 429/5xx errors are retried; permanent failures stop with a clear error. +5. **Context limit safety**: Files > max token budget are truncated, not rejected. +6. **Dual provider**: CLI uses external API; Pi extension uses Pi's LLM. +7. **Quality**: LLM output is visibly richer than the old heuristic output (verified by manual inspection). --- ## Rollout Plan -1. **Internal dogfooding** (Week 7): Use the skill on 2-3 real projects. Gather feedback on format density and agent behavior. -2. **Format refinement** (Week 8): Adjust abbreviations, section structure, and dirty-marker format based on dogfooding. -3. **Open beta** (Week 9): Share with a small group of Pi users. Collect bug reports and feature requests. -4. **v1.0 release** (Week 10): Stable API, documented format, published to npm. +1. **Test on real projects** (Day 1-2): Run `init` on 2-3 real codebases with the new LLM integration. +2. **Cost audit** (Day 3): Measure token usage per project; adjust defaults if too expensive. +3. **Prompt tuning** (Day 4-5): Iterate prompt design based on output quality. +4. **Release** (Day 6-7): Publish updated npm package, update Pi extension docs. --- -## Open Questions for Implementation +## Completed Milestones (for reference) -- Which tree-sitter grammars to bundle by default? (Start with TypeScript, Python, Go.) -- Should the LLM extraction use the agent's current model or a dedicated cheaper model? -- How to handle monorepos with multiple `.gitignore` files at different depths? -- Should `.pi-map.md` files be committed to git or kept in `.gitignore`? (Recommendation: `.gitignore` — they are derived artifacts.) +- **M1**: Foundation and Format +- **M2**: Heuristic Extraction (placeholder, to be replaced by M7) +- **M3**: AST Extraction +- **M4**: Patch and Update +- **M5**: Validation and `--fix` +- **M6**: Pi Skill Integration diff --git a/pi-extension.ts b/pi-extension.ts index 8ec80c3..c03a2f7 100644 --- a/pi-extension.ts +++ b/pi-extension.ts @@ -59,7 +59,11 @@ export default function (pi: ExtensionAPI) { }), async execute(_toolCallId, params, _signal, _onUpdate, ctx) { const targetPath = params.path || ctx.cwd; - const result = runCommand("init", targetPath === ctx.cwd ? [] : [targetPath], ctx.cwd); + const result = runCommand( + "init", + targetPath === ctx.cwd ? [] : [targetPath], + ctx.cwd, + ); return { content: [{ type: "text", text: result.stdout || result.stderr }], details: { success: result.success, cwd: ctx.cwd }, @@ -70,7 +74,8 @@ export default function (pi: ExtensionAPI) { pi.registerTool({ name: "project_map_patch", label: "Project Map Patch", - description: "Update .pi-map.md for the directory containing a changed file", + description: + "Update .pi-map.md for the directory containing a changed file", promptSnippet: "Update project analysis after editing a source file", promptGuidelines: [ "Use project_map_patch immediately after editing any source file", @@ -108,10 +113,17 @@ export default function (pi: ExtensionAPI) { }), async execute(_toolCallId, params, _signal, _onUpdate, ctx) { const targetPath = params.path || ctx.cwd; - const result = runCommand("validate", targetPath === ctx.cwd ? [] : [targetPath], ctx.cwd); + const result = runCommand( + "validate", + targetPath === ctx.cwd ? [] : [targetPath], + ctx.cwd, + ); return { content: [{ type: "text", text: result.stdout || result.stderr }], - details: { success: result.success, clean: result.stdout.includes("clean") }, + details: { + success: result.success, + clean: result.stdout.includes("clean"), + }, }; }, }); diff --git a/src/cli.ts b/src/cli.ts index ef462c4..3878f3f 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -13,12 +13,24 @@ function printUsage() { console.log(`${pc.bold("project-map")} — hierarchical project analysis for Pi agents `); console.log(`${pc.bold("Usage:")}`); - console.log(` project-map ${pc.cyan("init")} [path] Generate .pi-map.md files for all directories`); - console.log(` project-map ${pc.cyan("patch")} Update analysis for a changed file's directory`); - console.log(` project-map ${pc.cyan("validate")} [--fix] [path] Check for stale/missing/orphaned entries`); - console.log(` project-map ${pc.cyan("reinit")} [path] Force full regeneration`); - console.log(` project-map ${pc.cyan("--help")} Show this help message`); - console.log(` project-map ${pc.cyan("--version")} Show version\n`); + console.log( + ` project-map ${pc.cyan("init")} [path] Generate .pi-map.md files for all directories`, + ); + console.log( + ` project-map ${pc.cyan("patch")} Update analysis for a changed file's directory`, + ); + console.log( + ` project-map ${pc.cyan("validate")} [--fix] [path] Check for stale/missing/orphaned entries`, + ); + console.log( + ` project-map ${pc.cyan("reinit")} [path] Force full regeneration`, + ); + console.log( + ` project-map ${pc.cyan("--help")} Show this help message`, + ); + console.log( + ` project-map ${pc.cyan("--version")} Show version\n`, + ); console.log(`${pc.bold("Examples:")}`); console.log(` project-map init`); console.log(` project-map patch src/components/Button.tsx`); @@ -32,10 +44,9 @@ function printVersion() { } function formatCount(count: number, label: string): string { - const plural = - label.endsWith("y") - ? `${label.slice(0, -1)}ies` - : `${label}${count === 1 ? "" : "s"}`; + const plural = label.endsWith("y") + ? `${label.slice(0, -1)}ies` + : `${label}${count === 1 ? "" : "s"}`; return `${pc.bold(String(count))} ${count === 1 ? label : plural}`; } @@ -78,7 +89,9 @@ async function main() { } case "patch": { if (!args[1]) { - console.error(`${pc.red("Error:")} Missing file path. Usage: project-map patch `); + console.error( + `${pc.red("Error:")} Missing file path. Usage: project-map patch `, + ); process.exit(1); } await patchFile(args[1]); @@ -113,7 +126,9 @@ async function main() { const targetPath = args[1] || "."; const start = Date.now(); const entries = discoverProject(targetPath); - console.log(`Regenerating ${formatCount(entries.length, ".pi-map.md file")}...`); + console.log( + `Regenerating ${formatCount(entries.length, ".pi-map.md file")}...`, + ); await reinitPath(targetPath, { verbose: false }); const elapsed = ((Date.now() - start) / 1000).toFixed(1); console.log(`${pc.green("✓")} Regenerated in ${elapsed}s`);