docs: rewrite documentation system

This commit is contained in:
2026-06-12 12:04:28 +02:00
parent 842dcc6235
commit fb302a033e
6 changed files with 872 additions and 799 deletions
+121 -95
View File
@@ -1,148 +1,174 @@
# pi-project-map # pi-project-map
Pi skill for hierarchical project analysis. > Pi skill and CLI for hierarchical project analysis.
## What it does `pi-project-map` generates and maintains paired, machine-readable analysis artifacts throughout a codebase so agents can navigate quickly, orient themselves, and then verify details from source.
Generates **paired** project-analysis artifacts throughout your project: ## What it is
- `.pi-map.index.md` — routing-first index for deciding what to open next For every non-ignored directory, the tool produces two files:
- `.pi-map.md` — orientation-first rich map for understanding a directory
This gives Pi agents fast navigation plus deeper architectural context without reading every source file up front. | File | Purpose |
|------|---------|
| `.pi-map.index.md` | Routing-first index for deciding what to open next. |
| `.pi-map.md` | Orientation-first rich map for understanding a directory. |
## Quick Start Together they form a **paired artifact model**:
- indexes are small and routing-optimized
- maps are denser and architecture-optimized
- source remains the final authority
`pi-project-map` runs as both:
- a **CLI** (`project-map`)
- a **Pi extension** (`pi-extension.ts`) that registers tools and optional runtime prompt injection
## Quick start
Install:
```bash ```bash
npm install -g pi-project-map npm install -g pi-project-map
```
Generate paired artifacts in a repo:
```bash
cd my-project
project-map init project-map init
``` ```
## Agent operating model For standalone CLI usage, provide an LLM provider/API key. For example:
### Tier 0 ```bash
Always start with: export OPENAI_API_KEY=...
- `Project Map Protocol` project-map init
- root `.pi-map.index.md` ```
### Tier 1 Inside Pi, the extension uses Pi's configured model automatically.
Load likely relevant directory indexes first, then open the strongest-match rich maps.
### Tier 2 ## Command overview
Read actual source, tests, config, and docs before editing or making exact runtime claims.
**Trust boundary:** index routes, map orients, source decides. | CLI command | Pi tool | Purpose |
|-------------|---------|---------|
| `project-map init [path]` | `project_map_init` | Generate paired artifacts for the whole project or a subdirectory. |
| `project-map patch <file>` | `project_map_patch` | Regenerate artifacts for the directory containing the changed file and refresh ancestors appropriately. |
| `project-map validate [--fix]` | `project_map_validate` | Check paired artifacts for staleness or inconsistency. |
| `project-map reinit [path]` | `project_map_reinit` | Force full regeneration of all artifacts. |
| `project-map context <query>` | `project_map_context` | Return a ranked context bundle for a natural-language query. |
## Prompt Injection Policy Typical workflow:
The Pi extension can automatically inject lightweight project-map guidance into the agent context according to the configured `promptInjectionMode`. 1. `project-map init` on first use
2. after editing source, `project-map patch <changed-file>`
3. before broad architectural decisions, `project-map validate`
4. for targeted exploration, `project-map context "<query>"`
## Operating model
Follow a three-tier model when consuming project maps:
1. **Tier 0 — Protocol and root index**
Start with the root `Project Map Protocol` and root `.pi-map.index.md`.
2. **Tier 1 — Indexes and maps**
Use indexes to route, then open the strongest-match `.pi-map.md` files for orientation.
3. **Tier 2 — Source and tests**
Read actual source, config, tests, and docs before editing or making exact runtime claims.
The trust boundary is always:
> **index routes, map orients, source decides.**
## Prompt injection policy
The Pi extension can automatically inject lightweight project-map guidance into the agent context. Behavior is controlled by `promptInjectionMode` in `.pi-project-map.json`.
### Before init ### Before init
When no `.pi-map.md` / `.pi-map.index.md` artifacts exist, only a visible startup hint is injected. It tells the agent that the project-map extension is active and to run `project_map_init`. No synthetic or fake map content is ever injected before real artifacts exist.
No synthetic map content is injected. The agent sees only a visible startup hint telling it to run `project_map_init`.
### After init ### After init
Once real artifacts exist, the runtime guarantees that the root pair is loaded before any budgeted expansion:
The root pair is guaranteed to load first:
- root `.pi-map.index.md` - root `.pi-map.index.md`
- root `.pi-map.md` - root `.pi-map.md`
Additional directory pairs are expanded only while the configured context budget allows, in shallow-first order. Additional directory pairs are expanded only while the configured context budget allows.
### Trust boundary
Injected maps and indexes are navigation and orientation aids, not final authority:
> **index routes, map orients, source decides**
If an injected artifact and the source disagree, source wins. Always verify critical behavior from source before editing or making exact runtime claims.
### Mode ladder ### Mode ladder
- `off`: no automatic injection beyond existing tool/docs discovery.
- `advisory`: inject startup/init hints and allow optional root-pair preload; use light reminders.
- `strong` (default): inject the root pair, expand under the configured budget, run reinjection checks on relevant turns, and remind before edits or architecture-sensitive reasoning.
- `strict`: same as `strong`, plus require an explicit bypass justification before sensitive edits or architectural claims when the protocol path is missing.
The **protocol path** is present when the outgoing context contains the canonical injected root-pair block and the trust-boundary instruction. In `strict` mode, sensitive actions without it are blocked unless the agent includes `[PI_MAP_BYPASS: <brief justification>]`. | Mode | Behavior |
|------|----------|
| `off` | No automatic injection. |
| `advisory` | Visible hints/reminders only; maps are read manually. |
| `strong` | Root pair injection, budgeted expansion, reinjection on relevant turns. |
| `strict` | Same as `strong`, plus a visible guard for sensitive turns when the protocol path is missing. |
The **protocol path** is present when outgoing context contains:
- the canonical root-pair marker/block
- the trust-boundary instruction
In `strict` mode, a sensitive action can be bypassed explicitly with:
```text
[PI_MAP_BYPASS: <brief justification>]
```
### Context budget ### Context budget
The default budget is **15% of the active model context window**, capped at **100k tokens**. The smaller of the relative and absolute values wins. If the runtime cannot discover the active model's context window, it falls back to the absolute cap.
Configure it in `.pi-project-map.json`: Default automatic-injection budget is the smaller of:
- **15%** of the active model context window
- **100,000 tokens** absolute cap
```json If the runtime cannot discover the model context window, it falls back to the absolute cap.
{
"promptInjectionMode": "strong",
"contextBudgetPercent": 15,
"contextBudgetMaxTokens": 100000
}
```
## Retrieval ## Retrieval is separate
When you have a specific query (e.g. "authentication logic" or "routing metadata"):
1. Run `project-map context <query>` or use the Pi tool `project_map_context`
2. Read the returned **Context bundle** — it contains relevant indexes, maps, likely files, and symbols
3. Always verify critical behavior from source before editing
Retrieval via `project_map_context` (tool) or `project-map context` (CLI) remains a separate, on-demand path. Use it for targeted navigation when you have a specific query; automatic injection does not replace it. `project-map context <query>` and `project_map_context` are **separate, on-demand retrieval** paths. They do **not** replace automatic prompt injection.
### Integration-test expectations and known limitations Retrieval is deterministic and metadata-driven:
The implementation is validated by integration tests covering: 1. score every directory's paired map/index metadata against the query
2. keep the top matches (default: 3)
3. return a compact markdown bundle with indexes, maps, likely files, and symbols
- pre-init hint behavior, Use retrieval for targeted navigation when you already have a specific question.
- post-init root-pair preload,
- budgeted expansion behavior,
- canonical-marker dedupe,
- reinjection after compaction,
- reinjection after root-pair artifact changes,
- mixed visibility behavior,
- guidance-mode differences,
- strict-mode bypass behavior.
Known limitations: ## Configuration overview
- Token estimation is best-effort (≈ 4 chars per token); actual provider token counts may differ.
- Relevant-turn detection uses explicit event types when available and falls back to heuristics on generic turns.
- Message-layer scanning is preferred; provider-payload serialization quirks require a fallback scan path.
## Commands
```bash
project-map init
project-map patch <file>
project-map validate [--fix]
project-map reinit
project-map context <query>
```
### Context retrieval
`project-map context <query>` searches the paired map/index artifacts and returns a compact markdown bundle with the most relevant directories, files, and symbols. No LLM call is needed — it uses deterministic metadata scoring.
## Configuration
Create `.pi-project-map.json` in the project root: Create `.pi-project-map.json` in the project root:
```json ```json
{ {
"ignorePatterns": ["node_modules", ".git"],
"smallPackageThreshold": 10,
"contextBudget": 4000,
"autoInjectPrompt": true,
"tagCap": 8,
"workflowHintCap": 5,
"promptInjectionMode": "strong", "promptInjectionMode": "strong",
"contextBudgetPercent": 15, "contextBudgetPercent": 15,
"contextBudgetMaxTokens": 100000 "contextBudgetMaxTokens": 100000,
"llmProvider": "openai",
"llmModel": "gpt-4o-mini",
"ignorePatterns": ["node_modules", ".git"],
"tagCap": 8,
"workflowHintCap": 5
} }
``` ```
- `promptInjectionMode`: `off`, `advisory`, `strong` (default), or `strict`. Providing `ignorePatterns` replaces the built-in default list, so include any defaults you want to keep.
- `contextBudgetPercent`: relative share of the active model context window used for automatic map/index injection.
- `contextBudgetMaxTokens`: hard absolute cap on the injection budget.
## Design Key knobs:
- `promptInjectionMode``off`, `advisory`, `strong`, `strict`
- `contextBudgetPercent` — relative share of model context used for automatic injection
- `contextBudgetMaxTokens` — hard absolute cap on automatic injection
- `llmProvider` / `llmModel` / `llmBaseUrl` — standalone CLI provider settings
- `ignorePatterns` — directories/files to skip
- `tagCap` / `workflowHintCap` — routing metadata limits
See [design-doc.md](design-doc.md) for the full specification. ## Documentation map
## Implementation Plan - [`SKILL.md`](SKILL.md) — skill definition and agent/operator instructions
- [`usage-guide.md`](usage-guide.md) — practical workflows and examples
- [`design-doc.md`](design-doc.md) — architecture and implementation details
- [`troubleshooting.md`](troubleshooting.md) — common issues and recovery steps
See [implementation-plan.md](implementation-plan.md) for the engineering roadmap. ## Known limitations
- token budgeting is best-effort, not tokenizer-exact
- relevant-turn detection uses explicit event types plus heuristics
- provider payload fallback depends on runtime serialization shapes
- retrieval routes and orients; it never replaces source verification
+80 -67
View File
@@ -1,71 +1,93 @@
--- ---
name: pi-map name: pi-map
description: Generates and maintains hierarchical, machine-readable paired project analysis artifacts (.pi-map.index.md and .pi-map.md) for fast codebase navigation and orientation. description: Generates and maintains hierarchical paired project-analysis artifacts (`.pi-map.index.md` + `.pi-map.md`) so Pi agents can navigate and orient in a codebase without reading every source file.
--- ---
# pi-project-map # pi-project-map
A Pi skill that generates and maintains a paired analysis for each non-ignored directory: A Pi skill that creates and maintains one paired analysis artifact set per non-ignored directory:
- `.pi-map.index.md` for routing - `.pi-map.index.md` routing-first index
- `.pi-map.md` for orientation - `.pi-map.md` orientation-first rich map
## What It Does ## What it does
- **Scans** your project and creates one paired map/index artifact set per directory - scans the project and emits one paired map/index set per directory
- **Extracts** exports, imports, and dependencies via AST parsing and LLM heuristics - extracts per-file purpose, dependencies, and concepts via an LLM
- **Updates** generated artifacts after source edits - extracts exact exports/imports via AST parsing where supported
- **Validates** stale, missing, broken, or inconsistent paired artifacts - patches artifacts incrementally after source edits
- **Retrieves** relevant context on demand via deterministic metadata scoring over the paired artifacts - validates stale, missing, broken, or inconsistent paired artifacts
- retrieves relevant context on demand via deterministic metadata scoring
- injects lightweight project-map guidance into agent context according to a configurable mode ladder
## Quick Start ## Operating model
```bash ### Tier 0 — protocol
project-map init Always read the root `.pi-map.index.md` and the `Project Map Protocol` first.
project-map patch src/components/Button.tsx
project-map validate
project-map reinit
project-map context "authentication logic"
```
## Operating Model ### Tier 1 — routing
Use indexes first to decide where to go next. Open the strongest-match `.pi-map.md` files for orientation.
### Tier 0 ### Tier 2 — source
Read the root `.pi-map.index.md` and the `Project Map Protocol` first. Read actual source, tests, config, and docs before editing or asserting exact runtime behavior.
### Tier 1 **Trust boundary:**
Use indexes first for routing. Open the strongest-match `.pi-map.md` files next.
### Tier 2 > **index routes, map orients, source decides**
Read actual source before editing or asserting exact behavior.
**Trust boundary:** index routes, map orients, source decides. ## Agent instructions
## Prompt Injection Policy When project-map artifacts exist in the repo:
This skill can automatically inject lightweight project-map guidance into your context. The behavior is controlled by `promptInjectionMode` in `.pi-project-map.json`. 1. start with the root `.pi-map.index.md` and the `Project Map Protocol`
2. use indexes first to route into the right directory
3. read the local `.pi-map.md` plus relevant source before editing
4. run `project_map_patch <file>` (tool) or `project-map patch <file>` (CLI) after each source edit
5. run `project_map_validate` (tool) or `project-map validate` (CLI) before freshness-sensitive architectural decisions or final handoff
6. for targeted exploration, use `project_map_context <query>` (tool) or `project-map context <query>` (CLI)
7. in `strict` mode, only bypass the protocol-path guard with an explicit marker: `[PI_MAP_BYPASS: <brief justification>]`
## Prompt injection modes
The Pi extension can inject project-map guidance automatically. Behavior is controlled by `promptInjectionMode` in `.pi-project-map.json`.
### Before init ### Before init
When no `.pi-map.md` / `.pi-map.index.md` artifacts exist, you see a visible startup hint telling you to run `project_map_init`. No synthetic map content is injected before real artifacts exist. If no `.pi-map.md` / `.pi-map.index.md` artifacts exist, the extension emits a lightweight visible hint to run `project_map_init`. No synthetic or fake map content is injected.
### After init ### After init
Once artifacts exist, the runtime guarantees that the root pair is loaded first: Once real artifacts exist, the runtime guarantees that the **root pair** is loaded first:
- root `.pi-map.index.md` - root `.pi-map.index.md`
- root `.pi-map.md` - root `.pi-map.md`
Additional directory pairs may be added while the configured context budget allows. Additional directory pairs may be expanded while the configured context budget allows, in shallow-first order.
### Mode ladder ### Mode ladder
- `off`: no automatic injection beyond existing tool/docs discovery.
- `advisory`: startup/init hints are shown; you may read the root pair manually when you want routing/orientation context.
- `strong` (default): the root pair is injected automatically, expansion stays within the context budget, and reinjection checks run on relevant turns (agent start, before edits, before architecture-sensitive reasoning, after compaction, after root-pair artifact changes).
- `strict`: same as `strong`, but before sensitive edits or architectural claims you must either have the protocol path in context or include an explicit bypass marker: `[PI_MAP_BYPASS: <brief justification>]`.
The **protocol path** means the outgoing context contains the canonical injected root-pair block and the trust-boundary instruction (`index routes, map orients, source decides`). | Mode | Behavior |
|------|----------|
| `off` | No automatic injection. Use tools/CLI manually. |
| `advisory` | Startup/init hints are shown. Root pair is not auto-loaded; read it manually when needed. |
| `strong` (default) | Root pair is auto-loaded, expansion stays within budget, and reinjection runs on relevant turns. |
| `strict` | Same as `strong`, but sensitive edits or architectural claims are guarded unless the protocol path is present or a bypass marker is provided. |
The **protocol path** means the outgoing context contains the canonical injected root-pair block and the trust-boundary text.
### Context budget ### Context budget
Default budget: **15% of the active model context window**, capped at **100k tokens**. The smaller of the relative and absolute values wins. If the runtime cannot discover the active model's context window, it uses the absolute cap. Default budget: **15% of the active model context window**, capped at **100k tokens**. The smaller of the relative and absolute values wins. If the runtime cannot discover the context window, it uses the absolute cap.
## Retrieval usage
`project_map_context` (tool) and `project-map context` (CLI) are **on-demand retrieval**, separate from automatic injection.
They do not call the LLM. They score paired metadata against the query and return a compact markdown bundle with:
- relevant indexes
- relevant maps
- likely files
- relevant symbols when useful
Always read the suggested indexes first, then maps, then verify critical behavior from source.
## Configuration ## Configuration
@@ -73,41 +95,32 @@ Create `.pi-project-map.json` in the project root:
```json ```json
{ {
"tagCap": 8,
"workflowHintCap": 5,
"promptInjectionMode": "strong", "promptInjectionMode": "strong",
"contextBudgetPercent": 15, "contextBudgetPercent": 15,
"contextBudgetMaxTokens": 100000 "contextBudgetMaxTokens": 100000,
"tagCap": 8,
"workflowHintCap": 5,
"llmProvider": "openai",
"llmModel": "gpt-4o-mini",
"ignorePatterns": ["node_modules", ".git", "dist", "build"]
} }
``` ```
- `promptInjectionMode`: `off`, `advisory`, `strong` (default), or `strict`. Providing `ignorePatterns` replaces the built-in default list, so include any defaults you want to keep.
- `contextBudgetPercent`: relative share of the active model context window to use for automatic map/index injection.
- `contextBudgetMaxTokens`: hard absolute cap on the injection budget.
## Agent Instructions Knobs that matter in practice:
- `promptInjectionMode``off` | `advisory` | `strong` | `strict`
- `contextBudgetPercent` / `contextBudgetMaxTokens` — caps automatic map/index injection
- `tagCap` / `workflowHintCap` — caps routing metadata per directory
- `llmProvider` / `llmModel` / `llmBaseUrl` — standalone CLI only
- `ignorePatterns` — discovery exclusions
When project map artifacts exist in the repo: ## Tools and commands
1. Start with the root `.pi-map.index.md` | Tool (Pi) | CLI command | Purpose |
2. Use indexes first to route into the right directory |-----------|-------------|---------|
3. Read the local `.pi-map.md` plus source before editing | `project_map_init` | `project-map init [path]` | Generate all paired artifacts. |
4. Run `project-map patch <path>` after editing source | `project_map_patch` | `project-map patch <file>` | Regenerate the pair for the changed file's directory and refresh ancestors. |
5. Run `project-map validate` before freshness-sensitive architectural decisions | `project_map_validate` | `project-map validate [--fix]` | Check paired artifacts for staleness and discrepancies; optionally repair. |
6. In `strict` mode, include `[PI_MAP_BYPASS: <brief justification>]` only when you deliberately need to proceed without the protocol path | `project_map_reinit` | `project-map reinit [path]` | Force full regeneration. |
7. For **targeted navigation**, use `project_map_context` (Pi tool) or `project-map context` (CLI) with a natural-language query. It returns a compact markdown bundle with the strongest-match indexes, maps, likely files, and symbols. | `project_map_context` | `project-map context <query>` | Retrieve a ranked context bundle for a natural-language query. |
## Retrieval Model
`project_map_context` and `project-map context` implement **index-first retrieval**:
1. Score every directory's paired map/index metadata against the query
2. Keep the top 3 strongest matches
3. Expand those matches into:
- Relevant indexes (routing-first)
- Relevant maps (orientation-first)
- Likely files
- Relevant symbols (only when useful)
4. Return a stable markdown bundle titled `# Context bundle: <query>`
**Trust boundary still applies:** the bundle routes and orients, but source decides. Always read actual source before editing.
+288 -396
View File
@@ -1,441 +1,333 @@
# Design Doc: Hierarchical Project Analysis Skill for Pi # Design Reference: pi-project-map
## 1. Goals and Success Criteria > Audience: maintainers and contributors.
> Purpose: explain how `pi-project-map` works internally, not how to install or use it.
### Primary Goal ## 1. Overview
Enable a Pi coding agent to understand a software project's architecture and code relationships without scanning the entire repository. The agent should have a compact, hierarchical "internal representation" of the project that it can consume in-context.
### Success Criteria `pi-project-map` is a TypeScript/Node.js skill package that generates and maintains hierarchical, paired project-map artifacts for AI coding agents:
- The agent can orient itself in a new or familiar project without reading dozens of source files.
- The agent understands cross-package dependencies, data flows, and architectural patterns from the analysis files alone.
- Analysis files stay sufficiently fresh that the agent does not make decisions based on stale information.
- The representation is token-dense: maximum information per token, optimized for LLM consumption, not human readability.
## 2. Format Specification: Dense Markdown with Conventions - `.pi-map.index.md` — routing-first, sparse directory metadata
- `.pi-map.md` — orientation-first, richer directory metadata
### Design Rationale It runs as both:
- **Not JSON/YAML**: Brackets, quotes, and indentation add token overhead with no benefit to LLM comprehension. - a standalone CLI (`project-map`)
- **Not a custom DSL**: Fragile, requires a parser, and LLMs may hallucinate syntax. - a Pi extension (`pi-extension.ts`)
- **Dense markdown**: Hierarchical headings, bullet points, and abbreviations are natively understood by LLMs and extremely token-efficient.
### Structure The extension registers tools and event hooks that keep the artifacts fresh and can inject them into agent context at runtime.
Each non-ignored directory in the project gets **two** hidden analysis files:
- `.pi-map.index.md` — routing-first index ### Core design principle
- `.pi-map.md` — orientation-first rich map
```markdown The artifacts are **navigation aids, not source-of-truth**. Source code is always the final authority.
# <relative-path> (index)
dir: <relative-path>
## role
<short routing summary>
## parent
<parent links or ->
## children
<child links or ->
## files
<likely files>
## links
index: <self-index>
map: <self-map>
## workflows
<compact task -> route hints>
## dirty
<timestamp or ->
```
```markdown > **index routes, map orients, source decides.**
# <relative-path>
dir: <relative-path>
index: <sibling-index>
## role
<one-line package role>
## files
- <filename> | <one-line purpose> | exp: <exported symbols> | dep: <internal/external deps>
## arch
<free-form architectural notes>
## tags
<compact tags>
## symbols
<prioritized symbols>
## workflows
<compact workflow hints>
## dirty
<timestamp or ->
```
### Abbreviation Conventions ## 2. Artifact model
| Abbreviation | Meaning |
|-------------|---------|
| `exp:` | exported symbols (functions, classes, types, constants) |
| `dep:` | dependencies (other packages, files, or external libs) |
| `pkg/` | project-internal package reference |
| `ext/` | external dependency reference |
| `->` | data flow direction |
| `|` | field delimiter within a line |
### Example Each non-ignored directory receives a matched pair.
```markdown ### 2.1 Shared model
# pkg/auth
## role
Auth layer: JWT issuance, validation, refresh. Stateless. Dep: pkg/crypto, pkg/db.
## files
- tokens.ts | JWT gen/val | exp: issueToken, verifyToken, refreshToken | dep: crypto/hmac, db/sessions
- middleware.ts | HTTP auth guard | exp: requireAuth, requireRole | dep: tokens/verifyToken
- types.ts | shared auth types | exp: AuthToken, UserClaims, Role
## arch
Guard pattern on routes. Tokens short-lived (15m), refresh long-lived (7d). Rotation on every use.
Session state stored in Redis via db/sessions. No server-side JWT storage.
## dirty
-
```
### Rules Both files are generated from the same in-memory `DirectoryArtifactModel`:
- One file per directory, placed inside that directory.
- Every non-excluded file in the directory gets one bullet under `## files`.
- Subdirectories are referenced in `## role` via `Dep:` or in `## arch` as structural notes, not duplicated.
- The `## dirty` section is empty (`-`) when clean, or contains a timestamp/flag when stale.
## 3. Pipeline Architecture ```ts
interface DirectoryArtifactModel {
dir: string;
role: string;
files: FileEntry[];
arch: string;
dirty?: string;
isRoot: boolean;
parent?: string;
children: string[];
tags: string[];
symbols: string[];
workflows: WorkflowHint[];
}
### Hybrid Extraction: LLM + AST interface FileEntry {
name: string;
Two independent extraction layers contribute to the same output file. purpose: string;
exports: string[];
#### Layer 1: LLM-Based Extraction (All Files) deps: string[];
- **Input**: Raw file contents of every non-excluded file in the directory.
- **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.
- **Output**: Precise symbol lists (functions, classes, types), signatures, import/export graphs, class hierarchies.
- **Applies to**: Supported languages only (TypeScript, Python, Go, Rust, etc.).
- **When it runs**: Once per file during init; again on changed files during patching.
#### Merging
The two layers merge into a single line per file under `## files`:
```
- tokens.ts | JWT gen/val | exp: issueToken, verifyToken, refreshToken | dep: crypto/hmac, db/sessions
^ LLM ^ LLM ^ AST ^ AST + 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<string>;
} }
``` ```
Two implementations: ### 2.2 `.pi-map.md` (rich map)
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. Rendered by `src/format.ts` `renderDirectoryMap()`.
2. **ExternalLLMClient** (standalone CLI): Calls an external OpenAI-compatible API. Configured via environment variable (e.g., `OPENAI_API_KEY`) or config file.
### Caching Contains:
- `dir:` line and sibling `index:` link
- `Project Map Protocol` (root only)
- `## role`
- `## files`
- `## arch`
- `## tags`
- `## symbols`
- `## workflows`
- `## dirty`
LLM results are cached to avoid re-querying unchanged files. ### 2.3 `.pi-map.index.md` (index)
- **Key**: SHA-256 hash of file contents. Rendered by `src/format.ts``renderDirectoryIndex()`.
- **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 Contains:
- same protocol (root only)
- `## role`
- `## parent`
- `## children`
- `## files`
- `## links`
- `## workflows`
- `## dirty`
- **Concurrency**: 4-8 LLM calls in parallel, controlled by `p-limit`. ### 2.4 Why a paired format?
- **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 - **indexes are cheap** — many can be loaded without consuming much context
- **maps are dense** — loaded only after an index suggests relevance
- **paired generation** guarantees structural consistency
- **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). ## 3. High-level architecture
- **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 ### 3.1 Main modules
```
For each directory (depth-first): ```text
1. List all non-excluded files. discover → directory tree, .gitignore-aware
2. For each file (parallel, 4-8 concurrent): init → full generation
a. Compute SHA-256 of file contents. llm-extract → LLM-based file/package analysis
b. Check disk cache. If hit, use cached result. ast-extract → tree-sitter parsing
c. If miss: call LLM (with retries/backoff) to extract purpose and role. merge → combine LLM + AST into FileEntry
d. Store result in cache. routing-metadata → tags, symbols, workflow hints
e. If code file + parser available: run AST extraction (symbols, imports). format → render / parse the markdown pair
3. Merge per-file outputs into lines. patch → incremental update after edits
4. Run LLM on merged lines + directory context to generate: validate → consistency checking with optional repair
- `## role` (package-level summary) retrieve → deterministic query scoring
- `## arch` (architectural notes) prompt-injection → runtime context policy
5. Write `.pi-map.md` to directory. pi-extension → Pi tool/event registration
cli → standalone command dispatcher
config → defaults and .pi-project-map.json loader
``` ```
### Patch Pipeline ### 3.2 Runtime modes
```
When agent edits file(s): | Mode | Entry point | LLM client |
1. Classify patch mode: auto, small, or structural. |------|-------------|------------|
2. Always regenerate the changed directory pair: | Pi extension | `pi-extension.ts` | `PiLLMClient` via Pi runtime |
- `.pi-map.md` | Standalone CLI | `src/cli/cli.ts` | `ExternalLLMClient` or `KimiLLMClient` |
- `.pi-map.index.md`
3. For small changes: refresh ancestor indexes. ## 4. Extraction pipeline
4. For structural changes: refresh ancestor map/index pairs.
5. Use `validate --fix` to repair affected chains when paired artifacts are stale or missing. ### 4.1 Discovery
`src/discover.ts` walks the filesystem with `ignore`, merging built-in exclusions and `.gitignore`.
### 4.2 Per-directory generation
`src/init.ts``generateDirectoryArtifacts()`:
1. `processFiles()` runs in parallel over directory files
2. for each file:
- `extractFileLLM()` gets `purpose`, `deps`, `concepts`
- `extractFileAST()` gets exports/imports/calls where possible
- `mergeFileData()` combines both into a `FileEntry`
3. `extractPackageLLM()` produces directory `role` and `arch`
4. `createDirectoryModel()` builds the shared model
5. `populateRoutingMetadata()` derives `tags`, `symbols`, `workflows`
6. `writeDirectoryArtifacts()` writes both `.pi-map.md` and `.pi-map.index.md`
Directories are processed sequentially; files within a directory are processed concurrently.
### 4.3 LLM extraction
`src/llm/llm-extract.ts`:
- prompts are minimal and line-oriented
- binary files are skipped
- files over 500KB are labeled large and skipped
- source is truncated before prompting
- results are cached by SHA-256 of file content
- missing client throws `LLMError`
### 4.4 AST extraction
`src/ast/ast-extract.ts` uses `tree-sitter` for supported languages to extract:
- imports / requires
- exported classes, functions, constants
- methods, parameters, return types
- direct calls and raised exceptions
Unsupported languages fall back to LLM-only extraction.
### 4.5 Merging
`src/merge.ts`:
- purpose/concepts come from the LLM
- exports come from AST when available
- rich AST symbols are encoded as compact DSL:
- `class:Foo`
- `method:bar(a: string) → number`
- `call:baz`
- `raise:Error`
- deps are deduplicated union of AST + LLM deps
### 4.6 Routing metadata
`src/routing-metadata.ts` generates deterministic metadata used by retrieval and injection:
- **tags**
- **symbols**
- **workflow hints**
Caps are configurable via `tagCap` and `workflowHintCap`.
## 5. Patch / validate / reinit behavior
### 5.1 Patch
`src/patch.ts`:
1. resolve directory containing changed file
2. rediscover project tree
3. regenerate changed directory pair
4. refresh ancestors according to patch mode
Patch mode:
- **small** — refresh ancestor indexes only
- **structural** — refresh ancestor map/index pairs
### 5.2 Validate
`src/validate.ts` compares artifacts against filesystem and AST.
Important discrepancy types:
- `missing`
- `orphaned`
- `stale-signature`
- `dirty`
- `stale-map`
- `stale-index`
- `broken-link`
- `structural`
With `--fix`, validate builds a repair plan and regenerates directories deepest-first.
### 5.3 Reinit
`reinitPath()` is the blunt instrument for widespread staleness.
## 6. Retrieval architecture
`src/retrieve.ts` implements deterministic, index-first context retrieval.
1. walk the project for paired artifacts
2. parse indexes/maps into `DirectoryArtifactModel`
3. normalize the query
4. score every directory
5. return top-K (default: 3) as a markdown bundle with:
- relevant indexes
- relevant maps
- likely files
- relevant symbols
- instructions to verify from source
No LLM is used during retrieval. It is intentionally separate from automatic prompt injection.
## 7. Prompt injection architecture
`src/prompt-injection.ts` and `pi-extension.ts` implement runtime guidance injection.
### 7.1 Mode ladder
| Mode | Behavior |
|------|----------|
| `off` | No automatic injection |
| `advisory` | Visible startup/init hints; no artifact preload |
| `strong` (default) | Root pair preloaded, budgeted expansion, reinjection on relevant turns |
| `strict` | Same as strong, plus bypass guard for sensitive edits/architecture reasoning without protocol path |
### 7.2 Event hooks
The extension currently registers:
- `session_start`
- `before_agent_start`
- `context`
Payload fallback scanning is handled inside `context`-level decision logic; there is no separately registered `before_provider_request` hook in the current implementation.
### 7.3 Reinjection policy
`shouldReinjectForEvent()` decides whether to inject:
- only active in `strong` or `strict`
- skips if the canonical marker is already present in outgoing messages or payload
- triggers on:
- `agent_start`
- `edit_intent`
- `architecture_sensitive`
- `compaction`
- `artifact_change`
- `artifact_change` always forces reinjection
`detectEditIntent()` and `detectArchitectureSensitiveReasoning()` provide heuristic fallback for generic turns.
### 7.4 Protocol path and strict bypass
The **protocol path** is present when outgoing context contains:
1. the canonical root-pair marker/block
2. the trust-boundary text
In `strict` mode, a sensitive turn without the protocol path is blocked with a visible guard. The agent can override with:
```text
[PI_MAP_BYPASS: brief justification]
``` ```
## 4. LLM Prompt Design Empty or whitespace reasons are rejected.
### File-Level Prompt ### 7.5 Budgeted expansion
The LLM prompt for a single file is designed to produce a structured, concise analysis. `buildInjectionPayload()`:
- computes budget as `min(relative, absolute)`
- default is 15% of context window, capped at 100k tokens
- always includes the root pair
- adds additional pairs shallow-first until budget is exhausted
- prepends a maintenance reminder
``` Token estimation is best-effort: `ceil(char_count / 4)`.
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). ### 7.6 Context-window discovery
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: <file-path> `discoverContextWindow()` inspects the Pi runtime model for context metadata and falls back to the absolute cap when unavailable.
``` ## 8. Known limits and tradeoffs
<file-contents-truncated>
```
Respond in this exact format: ### Correctness vs cost
PURPOSE: <concise description> - init/patch/repair make LLM calls
DEPS: <comma-separated list, or "none"> - large repositories can be expensive
CONCEPTS: <comma-separated list, or "none"> - caching reduces duplicate work
```
### Package-Level Prompt ### AST coverage
- TypeScript/TSX, Python, and Go have the richest support
- other languages may be partial or LLM-only
After all file summaries are collected for a directory, a second LLM call synthesizes the package role and architecture. ### Token estimation
- 4 chars/token is only a heuristic
- oversized files may be truncated or skipped
``` ### Staleness
You are analyzing a directory in a software project. Below is a list of files in this directory with their purposes. - there is no filesystem watcher
- maps go stale when edits happen outside the patch flow
- validate detects but does not prevent staleness
Directory: <dir-path> ### Patch mode inference
Files: - auto-mode heuristics are good but imperfect
- <file1>: <purpose1> - contributors can force structural mode when needed
- <file2>: <purpose2>
...
Respond in this exact format: ### Strict mode ergonomics
ROLE: <one-line description of this directory's role in the project> - strict guards can be surprising on casual phrasing
ARCH: <2-4 sentences describing architecture, data flow, patterns, and design decisions> - bypass markers are intentionally explicit and user-visible
```
### Output Parsing ### Retrieval scoring
- deterministic scoring is reproducible but not semantic-search-smart
- broader queries may still need manual browsing
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. ### Cache
- cache grows unless manually cleaned
### Context Limit Protection - corrupted cache files are recovered by starting fresh
- 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 reads the root `Project Map Protocol` and root `.pi-map.index.md`.
2. For architecture/system or ambiguous tasks, agent also reads the root `.pi-map.md`.
3. Agent does **not** preload every directory map by default.
In the Pi extension, this session-start consumption is assisted by automatic prompt injection:
- **Before init**: only a lightweight visible startup hint is injected, telling the agent to run `project_map_init`. No synthetic map content is injected.
- **After init**: the root pair (`.pi-map.index.md` + `.pi-map.md`) is guaranteed to be preloaded automatically. Additional directory pairs are expanded only while the configured context budget allows.
### Automatic Prompt Injection
The Pi extension uses event hooks (`before_agent_start`, `context`, etc.) to maintain guidance context.
- The **mode ladder** controls how much is injected:
- `off`: no automatic injection.
- `advisory`: visible startup/init hints and optional root-pair preload.
- `strong` (default): root pair + budgeted expansion + relevant-turn reinjection checks.
- `strict`: same as `strong`, plus explicit bypass justification for sensitive edits/architectural claims when the protocol path is missing.
- The **protocol path** requires both the canonical injected root-pair block and the trust-boundary instruction (`index routes, map orients, source decides`) to be present in outgoing context.
- **Reinjection avoidance** scans actual outgoing messages (and falls back to provider payload) for a stable canonical marker before adding the root pair again.
- **Relevant-turn triggers** are: agent start, before edits, before architecture-sensitive reasoning, after compaction, and after root-pair artifact changes.
- **Visibility** is mixed: startup/init hints are user-visible; raw injected artifact blocks are agent-visible by default.
### Context Budget
Default automatic-injection budget: **15% of the active model context window**, capped at **100k tokens**. The smaller of the relative and absolute values wins. If the runtime cannot discover the active model's context window, it falls back to the absolute cap.
Configurable via `.pi-project-map.json`:
```json
{
"promptInjectionMode": "strong",
"contextBudgetPercent": 15,
"contextBudgetMaxTokens": 100000
}
```
### During Session
- Use directory indexes first to decide what to open next.
- For **targeted queries**, run `project_map_context` (tool) or `project-map context` (CLI). The retrieval engine scores all paired metadata and returns a compact markdown bundle with the top-3 strongest matches: indexes, maps, likely files, and symbols.
- Open the strongest-match `.pi-map.md` files for richer orientation.
- Read actual source before editing or making exact runtime claims.
### Context Management
- Tier 0 stays tiny and stable.
- Tier 1 loads only likely relevant indexes/maps.
- Tier 2 is real source, tests, config, and docs.
- Automatic injection stays within the configured budget and avoids redundant reinjection by scanning outgoing context.
## 6. Stale Data Mitigation
### Combined Strategy
#### 5.1 Dirty Markers
- Whenever the agent edits a file, it appends a dirty flag to the directory's `.pi-map.md`:
```markdown
## dirty
2024-06-09T14:32:00Z: tokens.ts modified
```
- A background or post-session reconciliation step regenerates dirty files.
- The agent can also be instructed to reconcile before making architectural decisions.
#### 5.2 Periodic Full Re-init
- On every new session start, or on a configurable schedule (e.g., daily), the skill offers to run a full re-scan.
- This catches any changes made outside the agent's awareness (e.g., by other developers).
#### 5.3 Validation Command
- A `validate` tool/command that the agent can invoke:
- Checks for missing files (new files not in `.pi-map.md`).
- Checks for orphaned entries (files listed but deleted).
- Checks for changed signatures (AST mismatch between listed symbols and actual code).
- Reports discrepancies and suggests corrections.
### Recovery
- 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.
## 7. Scope Boundaries and Non-Goals
### In Scope
- Every directory in the project gets a `.pi-map.md` file.
- Every non-excluded file gets analyzed by the LLM layer.
- Code files get augmented by the AST layer where parsers exist.
- Respect `.gitignore` and known junk patterns (node_modules, .git, dist, build, coverage, .next, .venv, __pycache__, .DS_Store).
### Out of Scope (Non-Goals)
- **Human-readable documentation**: These files are machine-only. Human docs live elsewhere.
- **Line-by-line code explanation**: The format captures symbols and architecture, not implementation details.
- **Auto-regeneration on filesystem events**: The skill relies on agent-initiated updates and periodic re-init, not filesystem watchers.
- **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.
## 8. Pi Skill Package Structure
```
pi-project-map/
├── SKILL.md # Skill definition for Pi
├── package.json # npm package metadata
├── src/
│ ├── init.ts # Full project scan + generation
│ ├── patch.ts # Incremental patch logic
│ ├── validate.ts # Consistency checker
│ ├── ast-extract.ts # Tree-sitter / LSP wrappers
│ ├── llm-extract.ts # LLM prompt templates for extraction
│ ├── merge.ts # Merge AST + LLM outputs
│ ├── format.ts # Dense markdown formatter
│ ├── config.ts # Skill configuration (thresholds, ignore patterns)
│ └── prompt-injection.ts # Runtime guidance injection policy and helpers
├── hooks/
│ └── on-prompt.ts # Injects maintenance command into prompts (legacy; Pi extension uses event hooks)
└── README.md # Setup and usage for humans
```
### Custom Tools
- `project-map:init` — Run full project scan. Creates all paired map/index artifacts.
- `project-map:patch <file-path>` — Update analysis for a specific file/directory.
- `project-map:validate` — Run consistency check across all paired artifacts.
- `project-map:context <query>` — Retrieve a compact markdown bundle of the most relevant directories, files, and symbols for a natural-language query.
- `project-map:reinit [path]` — Force re-initialization of entire project or subtree.
### Prompt Injection Hooks
The Pi extension registers event hooks instead of a single per-prompt append:
- `before_agent_start`: emits the pre-init hint when no artifacts exist, or preloads the root pair (plus budgeted expansion) after init.
- `context`: performs relevant-turn reinjection checks, detects compaction/artifact-change invalidation, and enforces `strict`-mode bypass guards.
- `before_provider_request`: optional fallback for marker scanning when message-layer detection is insufficient.
The injected maintenance reminder is:
> Start with the root `.pi-map.index.md`, use indexes first for routing, read the local `.pi-map.md` plus source before edits, run `project_map_patch` after source edits, and run `project_map_validate` before freshness-sensitive architectural handoff.
This is layered on top of the canonical root-pair block, which includes the trust boundary (`index routes, map orients, source decides`).
## 9. Risks and Tradeoffs
| Risk | Likelihood | Impact | Mitigation |
|------|-----------|--------|------------|
| Token bloat (1000+ dirs) | Medium | High | Summary mode, lazy loading, context budget |
| Stale analysis files | High | High | Dirty markers + periodic re-init + validation |
| Agent trusts stale data | Medium | High | Clear instructions to validate before architectural decisions |
| Expensive init on large repos | Medium | Medium | Parallelization, caching, optional incremental init |
| 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 |
| Message-level marker scanning misses provider serialization quirks | Medium | Medium | Add payload fallback scanning |
| Root-pair marker becomes brittle | Low | Medium | Stable deterministic boundaries and normalized artifact identity lines |
| 15% / 100k default budget too aggressive for some fleets | Low | Medium | Both knobs are configurable |
| Relevant-turn detection fuzzy | Medium | Medium | Centralized heuristics + extensive integration tests |
| `strict` mode friction | Low | Medium | Keep `strong` as default; isolate strict-only bypass behavior |
### Prompt Injection Known Limitations
- Token estimation is best-effort (≈ 4 chars per token); actual provider token counts may differ.
- Relevant-turn detection relies on explicit event types when available, with heuristic fallback for generic turns.
- Provider payload serialization may require the fallback scan path.
- The mode ladder is config-driven in v1; future UX may expose runtime controls.
- Retrieval (`project_map_context`) remains separate from automatic injection.
## 10. Concrete Example: Full Project Snapshot
```
project-root/
├── .pi-map.md
├── src/
│ ├── .pi-map.md
│ ├── auth/
│ │ ├── .pi-map.md
│ │ ├── tokens.ts
│ │ ├── middleware.ts
│ │ └── types.ts
│ └── db/
│ ├── .pi-map.md
│ ├── connection.ts
│ └── migrations/
│ ├── .pi-map.md
│ └── 001_init.sql
├── docker/
│ ├── .pi-map.md
│ ├── Dockerfile
│ └── docker-compose.yml
└── README.md
```
Each `.pi-map.md` follows the format in Section 2, creating a navigable hierarchy.
## 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-aware patching**: Only re-run LLM on changed functions, not entire files.
- **Multi-repo workspaces**: Support monorepos with independent package boundaries.
-241
View File
@@ -1,241 +0,0 @@
# Implementation Plan: Hierarchical Project Analysis Skill for Pi
## Overview
Build a Pi skill package (`pi-project-map`) that generates and maintains a hierarchical, machine-readable analysis of a software project. Each directory gets a `.pi-map.md` file. The skill provides custom tools for init, patch, validate, and re-init, plus a prompt hook that ensures the agent keeps analysis files in sync.
---
## Current Status
- **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
The remaining major work is to replace the heuristic "LLM" extraction with **actual LLM API calls**.
---
## Remaining Milestone: M7 — Proper LLM Integration
**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.
**Estimated time**: 1 week of focused work.
---
### Task 1: LLM Client Abstraction (`src/llm-client.ts`)
Create a unified interface for LLM calls.
```typescript
interface LLMClient {
complete(prompt: string): Promise<string>;
}
```
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**
- `LLMClient` interface exists and compiles.
- Factory correctly selects implementation based on mode.
---
### Task 2: External LLM Client (`src/external-llm-client.ts`)
Implement the standalone CLI LLM client using an OpenAI-compatible API.
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**
- `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`.
---
### Task 3: Pi LLM Client (`src/pi-llm-client.ts`)
Implement the Pi-native LLM client for use inside the extension.
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**
- Pi extension can successfully call an LLM when running inside Pi.
- Errors surface clearly to the user.
---
### Task 4: Disk Cache (`src/llm-cache.ts`)
Implement persistent SHA-256 → LLM result caching.
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**
- Second `init` run on unchanged files does not call LLM.
- Cache persists across process restarts.
- Corrupted cache file does not crash the tool.
---
### 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
```
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)
```
---
## Validation Criteria (for LLM Integration)
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. **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.
---
## Completed Milestones (for reference)
- **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
+185
View File
@@ -0,0 +1,185 @@
# Troubleshooting pi-project-map
## Stale map issues
### Symptom
`project_map_validate` reports `missing`, `orphaned`, or `stale-signature` discrepancies, or a directory map describes files that no longer exist.
### Likely cause
A source file was edited, added, or deleted without running `project_map_patch` or `project_map_reinit`.
### What to do
1. run `project_map_validate` to inspect discrepancies
2. if the list is small and localized, run `project_map_patch <changed-file>`
3. if the list is large or structural, run `project_map_reinit`
4. re-run `project_map_validate` to confirm clean state
### Prevention
- patch immediately after editing source
- in Pi, use the `project_map_patch` tool with the changed file path
- watch for the `session_start` dirty warning
## Validate discrepancies
### Symptom
`validate --fix` fails with `validate --fix requires an LLM client`.
### Cause
Repair needs an LLM client to regenerate stale artifacts.
### Fix
- CLI: provide `--llm-provider=...` and the required API key
- Pi: use the tool surface so the runtime provides the LLM client
### Symptom
Many `stale-signature` discrepancies appear.
### Cause
AST exports no longer match listed exports, or generated signatures are stale.
### Fix
- patch the changed file/directory
- if widespread, reinit and validate again
### Symptom
`broken-link` appears after moving directories.
### Fix
Run `project_map_reinit`.
## Prompt injection mode surprises
### Symptom
No project-map context appears in a Pi session.
### Checks before init
1. confirm `.pi-project-map.json` does not set `promptInjectionMode` to `off`
2. confirm root `.pi-map.md` / `.pi-map.index.md` do not exist yet
3. confirm the session is at agent start; the pre-init hint is emitted from `before_agent_start`
### Checks after init
1. confirm root `.pi-map.md` and `.pi-map.index.md` exist
2. confirm mode is `strong` or `strict` if you expect automatic artifact injection
3. confirm the outgoing context does not already contain the root-pair marker
4. confirm the turn is actually relevant (`agent_start`, `edit_intent`, `architecture_sensitive`, `compaction`, `artifact_change`)
### Symptom
Root pair is injected repeatedly.
### Likely cause
Marker scanning failed or artifact invalidation forced reinjection.
### Fix
- inspect whether `<!-- PI_MAP_ROOT_PAIR_START -->` is present in outgoing context
- check whether root artifacts changed on disk
- confirm payload/message serialization still exposes marker text
### Symptom
`advisory` mode shows a reminder but no maps are loaded.
### Expected behavior
That is intentional. Advisory mode is reminder-only; read the root pair manually.
## Strict bypass confusion
### Symptom
A sensitive edit is blocked with the strict guard.
### What is happening
In `strict` mode, the turn was classified as sensitive and the protocol path is missing from outgoing context.
### Resolution options
1. restore the protocol path by ensuring the root pair and trust boundary are present in context
2. include an explicit bypass marker:
```text
[PI_MAP_BYPASS: emergency fix needed without full context]
```
3. switch to `strong` mode if strict is too noisy for the task
### Symptom
Bypass marker is ignored.
### Cause
The marker must contain a non-empty reason. Empty or whitespace-only reasons are rejected.
### Symptom
Strict guard triggers on obviously non-sensitive turns.
### Cause
Heuristic sensitive-turn detection can false-positive on words like `refactor`, `redesign`, or `dependency graph`.
## LLM / provider / cache issues
### Symptom
`project-map init` fails with `No LLM client configured`.
### CLI fix
- set `OPENAI_API_KEY` for OpenAI provider
- set `KIMI_API_KEY` for Kimi provider
- or configure provider/model in `.pi-project-map.json`
### Pi fix
- select a model in Pi
- log in if the provider requires credentials
- ensure Pi runtime LLM access is working
### Symptom
External/provider request fails.
### Checks
1. API key validity
2. network connectivity
3. `llmBaseUrl` correctness if using a proxy
4. provider rate limits
### Symptom
Second init still makes many LLM calls.
### Cause
Cache miss.
### Checks
- consistent cache directory between runs
- cache file exists and is readable
- file contents did not change
### Symptom
Cache file is huge.
### Cause
There is no eviction policy yet.
### Fix
Remove the local cache directory if needed.
## Test / lint expectations
### Main checks
```bash
npm test
npm run typecheck
npm run build
node dist/cli.js validate .
```
### Lint note
`npm run lint` is currently not a reliable gate if the repo has no ESLint config. Treat missing-lint-config failures as repository hygiene issues, not feature regressions.
### Common failures
| Failure | Likely cause |
|---------|--------------|
| Pi LLM client tests fail | Pi runtime or mocks drifted |
| Prompt-injection sequence tests fail | Reinjection / strict logic changed |
| Validate tests fail | Format parsing, merge encoding, or discovery rules changed |
| AST tests fail | tree-sitter grammar/runtime drift |
## Known limitations
- token budgeting is heuristic, not tokenizer-exact
- retrieval is deterministic, not semantic-search-driven
- strict mode is a prompt-level guard, not a hard tool sandbox
- payload scanning assumes string or JSON-serializable structures
+198
View File
@@ -0,0 +1,198 @@
# pi-project-map Usage Guide
For Pi agents and advanced users who want predictable, low-friction navigation and maintenance of project-map artifacts.
## When to use each command
| Situation | Command |
|-----------|---------|
| New project, no `.pi-map.md` files yet, or artifacts are severely outdated | `project_map_init` / `project-map init` |
| You just edited one or more source files | `project_map_patch <file>` / `project-map patch <file>` |
| You suspect stale data, or you are about to make an architectural decision | `project_map_validate` / `project-map validate` |
| Validation shows widespread staleness, or you pulled major changes from version control | `project_map_reinit` / `project-map reinit` |
| You have a specific question like "where is auth handled?" | `project_map_context <query>` / `project-map context <query>` |
## Command behavior
### init
Run once when you start work on a repo, or after large restructuring. It discovers every non-ignored directory, analyzes files with LLM + AST, and writes both `.pi-map.md` and `.pi-map.index.md` for every directory.
### patch
Run **immediately after editing a source file**. The command regenerates the pair for that files directory and refreshes ancestor artifacts.
Patch mode is chosen automatically:
- **small** — refresh ancestor indexes only
- **structural** — refresh ancestor map/index pairs
You can force a mode with `project-map patch <file> --patch-mode=small|structural`.
`auto` remains the default.
### validate
Run before architectural decisions, broad refactors, or final handoff. It checks for:
- missing/orphaned files
- stale signatures
- dirty markers
- broken parent/children/sibling links
- map/index disagreements
Use `project-map validate --fix` to repair affected chains. `--fix` requires an LLM client.
### reinit
Use sparingly. It regenerates every pair from scratch and is the blunt instrument for widespread staleness.
### context
Use when you know what you are looking for:
```bash
project-map context "authentication logic"
project-map context "routing metadata generation"
project-map context "LLM client error handling"
```
The returned bundle is deterministic and ranked. Read indexes first, then strongest-match maps, then the actual source files.
## Practical workflows
### Starting a new task in a mapped project
1. read the root `.pi-map.index.md` and the `Project Map Protocol`
2. use the root index to find the relevant child directory
3. read that directorys `.pi-map.index.md`, then its `.pi-map.md`
4. read the relevant source files
5. edit source
6. run `project_map_patch <changed-file>`
7. run tests/build
8. run `project_map_validate` before architectural summary or handoff
### Exploring an unfamiliar area
```bash
project-map context "how is validation implemented"
```
Treat the returned bundle as a ranked entry point, not as truth.
### After pulling changes from version control
```bash
project-map validate
# if many discrepancies:
project-map reinit
```
### Before a big refactor
```bash
project-map validate
# if needed:
project-map reinit
```
## Prompt injection behavior before and after init
### Before init
If no paired artifacts exist:
- `off`: nothing happens
- `advisory`, `strong`, `strict`: a visible hint appears telling you to run `project_map_init`
No map content is fabricated.
### After init
Once the root pair exists:
- `off`: no automatic injection
- `advisory`: a visible reminder that maps are available, but the root pair is **not** auto-loaded
- `strong`: root pair is auto-loaded; additional pairs are added within the budget; reinjection happens on relevant turns
- `strict`: same as `strong`, plus enforcement of the protocol path for sensitive actions
Relevant turns that trigger reinjection in `strong`/`strict`:
- agent start
- edit intent
- architecture-sensitive reasoning
- compaction
- root-pair artifact changes
## Advisory vs strong vs strict in practice
| Concern | Use |
|---------|-----|
| You want maps available but do not want automatic context expansion | `advisory` |
| Normal daily work; you want routing/orientation preloaded without friction | `strong` (default) |
| High-stakes codebase or you want explicit justification before bypassing context discipline | `strict` |
| You prefer fully manual control | `off` |
In `strict`, if you attempt a sensitive edit or architectural claim without the protocol path in context, a guard appears. To proceed, either restore the project-map context or include:
```text
[PI_MAP_BYPASS: editing a one-line comment, map context not needed]
```
Use bypass markers sparingly.
## Example task flows
### Fix a bug in `src/utils/validation.ts`
```text
1. project-map context "validation utilities"
2. Read src/utils/.pi-map.index.md and .pi-map.md
3. Read src/utils/validation.ts
4. Edit the file
5. project_map_patch src/utils/validation.ts
6. Run tests
7. project_map_validate
```
### Add a new file to `src/llm/`
```text
1. Create src/llm/new-client.ts
2. Implement the file
3. project_map_patch src/llm/new-client.ts
4. project_map_validate
```
### Review architecture before approving a PR
```text
1. project-map validate
2. If clean, read root .pi-map.md and key directory maps
3. Cross-check claims against source
4. If stale, run project-map reinit first
```
## Retrieval vs automatic injection
| | Automatic injection | `project_map_context` / `project-map context` |
|---|---|---|
| Trigger | Configured mode + relevant turn | Explicit request |
| Content | Root pair + budgeted expansion | Top-ranked directories for a query |
| Cost | No LLM call; reads artifacts | No LLM call; deterministic scoring |
| Best use | Maintain baseline orientation | Targeted navigation for a specific task |
Use both together: injection for baseline orientation, retrieval for focused entry points.
## Keeping artifacts fresh
- patch after every edit
- validate before architectural claims
- reinit when many artifacts are stale or after large merges
- watch for the Pi extension warning about dirty packages on session start
## Configuration quick reference
```json
{
"promptInjectionMode": "strong",
"contextBudgetPercent": 15,
"contextBudgetMaxTokens": 100000,
"tagCap": 8,
"workflowHintCap": 5,
"ignorePatterns": ["node_modules", ".git", "dist", "build"]
}
```
Providing `ignorePatterns` replaces the built-in default list, so include any defaults you want to keep.
- lower `contextBudgetPercent` / `contextBudgetMaxTokens` to reduce token use
- raise them if you want deeper auto-loaded context in large projects
- `strict` is the safest enforcement mode; `strong` is the best default for everyday work