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 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
- `.pi-map.md` — orientation-first rich map for understanding a directory
For every non-ignored directory, the tool produces two files:
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
npm install -g pi-project-map
```
Generate paired artifacts in a repo:
```bash
cd my-project
project-map init
```
## Agent operating model
For standalone CLI usage, provide an LLM provider/API key. For example:
### Tier 0
Always start with:
- `Project Map Protocol`
- root `.pi-map.index.md`
```bash
export OPENAI_API_KEY=...
project-map init
```
### Tier 1
Load likely relevant directory indexes first, then open the strongest-match rich maps.
Inside Pi, the extension uses Pi's configured model automatically.
### Tier 2
Read actual source, tests, config, and docs before editing or making exact runtime claims.
## Command overview
**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
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
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.md`
Additional directory pairs are expanded only while the configured context budget allows, in shallow-first order.
### 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.
Additional directory pairs are expanded only while the configured context budget allows.
### 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
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
{
"promptInjectionMode": "strong",
"contextBudgetPercent": 15,
"contextBudgetMaxTokens": 100000
}
```
If the runtime cannot discover the model context window, it falls back to the absolute cap.
## Retrieval
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 is separate
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
The implementation is validated by integration tests covering:
Retrieval is deterministic and metadata-driven:
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,
- 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.
Use retrieval for targeted navigation when you already have a specific question.
Known limitations:
- 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
## Configuration overview
Create `.pi-project-map.json` in the project root:
```json
{
"ignorePatterns": ["node_modules", ".git"],
"smallPackageThreshold": 10,
"contextBudget": 4000,
"autoInjectPrompt": true,
"tagCap": 8,
"workflowHintCap": 5,
"promptInjectionMode": "strong",
"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`.
- `contextBudgetPercent`: relative share of the active model context window used for automatic map/index injection.
- `contextBudgetMaxTokens`: hard absolute cap on the injection budget.
Providing `ignorePatterns` replaces the built-in default list, so include any defaults you want to keep.
## 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
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
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.md` for orientation
- `.pi-map.index.md` routing-first index
- `.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
- **Extracts** exports, imports, and dependencies via AST parsing and LLM heuristics
- **Updates** generated artifacts after source edits
- **Validates** stale, missing, broken, or inconsistent paired artifacts
- **Retrieves** relevant context on demand via deterministic metadata scoring over the paired artifacts
- scans the project and emits one paired map/index set per directory
- extracts per-file purpose, dependencies, and concepts via an LLM
- extracts exact exports/imports via AST parsing where supported
- patches artifacts incrementally after source edits
- 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
project-map init
project-map patch src/components/Button.tsx
project-map validate
project-map reinit
project-map context "authentication logic"
```
### Tier 0 — protocol
Always read the root `.pi-map.index.md` and the `Project Map Protocol` first.
## 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
Read the root `.pi-map.index.md` and the `Project Map Protocol` first.
### Tier 2 — source
Read actual source, tests, config, and docs before editing or asserting exact runtime behavior.
### Tier 1
Use indexes first for routing. Open the strongest-match `.pi-map.md` files next.
**Trust boundary:**
### Tier 2
Read actual source before editing or asserting exact behavior.
> **index routes, map orients, source decides**
**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
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
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.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
- `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
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
@@ -73,41 +95,32 @@ Create `.pi-project-map.json` in the project root:
```json
{
"tagCap": 8,
"workflowHintCap": 5,
"promptInjectionMode": "strong",
"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`.
- `contextBudgetPercent`: relative share of the active model context window to use for automatic map/index injection.
- `contextBudgetMaxTokens`: hard absolute cap on the injection budget.
Providing `ignorePatterns` replaces the built-in default list, so include any defaults you want to keep.
## 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`
2. Use indexes first to route into the right directory
3. Read the local `.pi-map.md` plus source before editing
4. Run `project-map patch <path>` after editing source
5. Run `project-map validate` before freshness-sensitive architectural decisions
6. In `strict` mode, include `[PI_MAP_BYPASS: <brief justification>]` only when you deliberately need to proceed without the protocol path
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.
## 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.
| Tool (Pi) | CLI command | Purpose |
|-----------|-------------|---------|
| `project_map_init` | `project-map init [path]` | Generate all paired artifacts. |
| `project_map_patch` | `project-map patch <file>` | Regenerate the pair for the changed file's directory and refresh ancestors. |
| `project_map_validate` | `project-map validate [--fix]` | Check paired artifacts for staleness and discrepancies; optionally repair. |
| `project_map_reinit` | `project-map reinit [path]` | Force full regeneration. |
| `project_map_context` | `project-map context <query>` | Retrieve a ranked context bundle for a natural-language query. |
+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
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.
## 1. Overview
### Success Criteria
- 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.
`pi-project-map` is a TypeScript/Node.js skill package that generates and maintains hierarchical, paired project-map artifacts for AI coding agents:
## 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
- **Not JSON/YAML**: Brackets, quotes, and indentation add token overhead with no benefit to LLM comprehension.
- **Not a custom DSL**: Fragile, requires a parser, and LLMs may hallucinate syntax.
- **Dense markdown**: Hierarchical headings, bullet points, and abbreviations are natively understood by LLMs and extremely token-efficient.
It runs as both:
- a standalone CLI (`project-map`)
- a Pi extension (`pi-extension.ts`)
### Structure
Each non-ignored directory in the project gets **two** hidden analysis files:
The extension registers tools and event hooks that keep the artifacts fresh and can inject them into agent context at runtime.
- `.pi-map.index.md` — routing-first index
- `.pi-map.md` — orientation-first rich map
### Core design principle
```markdown
# <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 ->
```
The artifacts are **navigation aids, not source-of-truth**. Source code is always the final authority.
```markdown
# <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 ->
```
> **index routes, map orients, source decides.**
### Abbreviation Conventions
| 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 |
## 2. Artifact model
### Example
Each non-ignored directory receives a matched pair.
```markdown
# 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
-
```
### 2.1 Shared model
### Rules
- 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.
Both files are generated from the same in-memory `DirectoryArtifactModel`:
## 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
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**: 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>;
interface FileEntry {
name: string;
purpose: string;
exports: string[];
deps: 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.
2. **ExternalLLMClient** (standalone CLI): Calls an external OpenAI-compatible API. Configured via environment variable (e.g., `OPENAI_API_KEY`) or config file.
Rendered by `src/format.ts` `renderDirectoryMap()`.
### 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.
- **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.
Rendered by `src/format.ts``renderDirectoryIndex()`.
### 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`.
- **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.
### 2.4 Why a paired format?
### 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).
- **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.
## 3. High-level architecture
### Init Pipeline
```
For each directory (depth-first):
1. List all non-excluded files.
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)
- `## arch` (architectural notes)
5. Write `.pi-map.md` to directory.
### 3.1 Main modules
```text
discover → directory tree, .gitignore-aware
init → full generation
llm-extract → LLM-based file/package analysis
ast-extract → tree-sitter parsing
merge → combine LLM + AST into FileEntry
routing-metadata → tags, symbols, workflow hints
format → render / parse the markdown pair
patch → incremental update after edits
validate → consistency checking with optional repair
retrieve → deterministic query scoring
prompt-injection → runtime context policy
pi-extension → Pi tool/event registration
cli → standalone command dispatcher
config → defaults and .pi-project-map.json loader
```
### Patch Pipeline
```
When agent edits file(s):
1. Classify patch mode: auto, small, or structural.
2. Always regenerate the changed directory pair:
- `.pi-map.md`
- `.pi-map.index.md`
3. For small changes: refresh ancestor indexes.
4. For structural changes: refresh ancestor map/index pairs.
5. Use `validate --fix` to repair affected chains when paired artifacts are stale or missing.
### 3.2 Runtime modes
| Mode | Entry point | LLM client |
|------|-------------|------------|
| Pi extension | `pi-extension.ts` | `PiLLMClient` via Pi runtime |
| Standalone CLI | `src/cli/cli.ts` | `ExternalLLMClient` or `KimiLLMClient` |
## 4. Extraction pipeline
### 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
```
You are analyzing a source file for a project map. Read the file below and summarize:
Token estimation is best-effort: `ceil(char_count / 4)`.
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.
### 7.6 Context-window discovery
File path: <file-path>
`discoverContextWindow()` inspects the Pi runtime model for context metadata and falls back to the absolute cap when unavailable.
```
<file-contents-truncated>
```
## 8. Known limits and tradeoffs
Respond in this exact format:
PURPOSE: <concise description>
DEPS: <comma-separated list, or "none">
CONCEPTS: <comma-separated list, or "none">
```
### Correctness vs cost
- init/patch/repair make LLM calls
- large repositories can be expensive
- 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
```
You are analyzing a directory in a software project. Below is a list of files in this directory with their purposes.
### Staleness
- 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>
Files:
- <file1>: <purpose1>
- <file2>: <purpose2>
...
### Patch mode inference
- auto-mode heuristics are good but imperfect
- contributors can force structural mode when needed
Respond in this exact format:
ROLE: <one-line description of this directory's role in the project>
ARCH: <2-4 sentences describing architecture, data flow, patterns, and design decisions>
```
### Strict mode ergonomics
- strict guards can be surprising on casual phrasing
- 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.
### 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 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.
### Cache
- cache grows unless manually cleaned
- corrupted cache files are recovered by starting fresh
-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