pi-read-map
Advanced tools
+6
-0
@@ -5,2 +5,8 @@ # Changelog | ||
| ## [1.2.1] - 2026-02-14 | ||
| ### Fixed | ||
| - Published tarball reduced from 310 KB to 49 KB by adding `.npmignore` (excluded `.codemap/` caches, compiled Go binary, `.pi/` agent files, tests, and demo assets) | ||
| ## [1.2.0] - 2026-02-14 | ||
@@ -7,0 +13,0 @@ |
+1
-1
| { | ||
| "name": "pi-read-map", | ||
| "version": "1.2.0", | ||
| "version": "1.2.1", | ||
| "description": "Pi extension that adds structural file maps for large files", | ||
@@ -5,0 +5,0 @@ "type": "module", |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
| { | ||
| "threshold": 15, | ||
| "minTokens": 50, | ||
| "minLines": 5, | ||
| "reporters": ["console"], | ||
| "ignore": ["**/fixtures/**", "**/node_modules/**"] | ||
| } |
-158
| # AGENTS.md | ||
| > Last updated: 2026-02-14 | ||
| Pi extension that augments the built-in `read` tool with structural file maps for large files (>2,000 lines or >50 KB). Intercepts `read` calls, generates symbol maps via language-specific parsers, and sends them as separate `file-map` messages after the tool result. | ||
| ## Commands (verified 2026-02-14) | ||
| | Command | Purpose | ~Time | | ||
| |---------|---------|-------| | ||
| | `npm run test` | Unit + integration tests (vitest) | ~2s | | ||
| | `npm run test:integration` | Integration tests only | ~1s | | ||
| | `npm run test:e2e` | E2E tests (requires pi + tmux) | ~60s | | ||
| | `npm run bench` | Benchmarks | ~5s | | ||
| | `npm run typecheck` | `tsc --noEmit` | ~2s | | ||
| | `npm run lint` | oxlint (via npx) | ~1s | | ||
| | `npm run lint:fix` | Auto-fix lint issues | ~1s | | ||
| | `npm run format` | Format with oxfmt | ~1s | | ||
| | `npm run format:check` | Check formatting | ~1s | | ||
| | `npm run validate` | typecheck + lint + format:check | ~4s | | ||
| ## File Map | ||
| ``` | ||
| src/ | ||
| ├── index.ts → Extension entry: tool registration, caching, message rendering | ||
| ├── mapper.ts → Dispatcher: routes files to language mappers, fallback chain | ||
| ├── formatter.ts → Budget-aware formatting with progressive detail reduction | ||
| ├── language-detect.ts → File extension → language mapping | ||
| ├── types.ts → FileMap, FileSymbol, MapOptions, FileMapMessageDetails | ||
| ├── enums.ts → SymbolKind (21 kinds), DetailLevel (5 levels) | ||
| ├── constants.ts → THRESHOLDS: lines, bytes, budget tiers | ||
| └── mappers/ → One mapper per language (17 total) | ||
| ├── typescript.ts → ts-morph (handles TS + JS) | ||
| ├── rust.ts → tree-sitter-rust | ||
| ├── cpp.ts → tree-sitter-cpp (C++ and .h files) | ||
| ├── clojure.ts → tree-sitter-clojure (.clj, .cljs, .cljc, .edn) | ||
| ├── python.ts → subprocess: scripts/python_outline.py | ||
| ├── go.ts → subprocess: scripts/go_outline.go | ||
| ├── json.ts → subprocess: jq | ||
| ├── c.ts → Regex patterns | ||
| ├── sql.ts → Regex | ||
| ├── markdown.ts → Regex | ||
| ├── yaml.ts → Regex | ||
| ├── toml.ts → Regex | ||
| ├── csv.ts → In-process streaming | ||
| ├── jsonl.ts → In-process streaming | ||
| ├── ctags.ts → universal-ctags fallback | ||
| └── fallback.ts → Grep-based final fallback | ||
| scripts/ | ||
| ├── python_outline.py → Python AST extraction (called by python mapper) | ||
| ├── go_outline.go → Go AST extraction (compiled on first use) | ||
| └── go_outline → Compiled Go binary | ||
| tests/ | ||
| ├── unit/ → Mapper tests, formatter tests, language detection | ||
| ├── integration/ → Dispatch, caching, budget enforcement, map messages | ||
| ├── e2e/ → Real pi sessions via tmux (vitest.e2e.config.ts) | ||
| ├── fixtures/ → Sample files per language | ||
| ├── benchmarks/ → Mapper performance benchmarks | ||
| └── helpers/ → Test utilities (pi-runner, constants, tree-sitter) | ||
| docs/ | ||
| ├── plans/ → Implementation plans (phased) | ||
| ├── handoffs/ → Session handoff notes | ||
| ├── reviews/ → Phase review documents | ||
| └── todo/ → Outstanding work items | ||
| ``` | ||
| ## Architecture | ||
| Dispatch chain: `index.ts` → `mapper.ts` → language mapper → ctags → fallback. | ||
| Budget enforcement in `formatter.ts` uses progressive detail reduction: | ||
| - 10 KB: full detail (signatures, modifiers) | ||
| - 15 KB: compact (drop signatures) | ||
| - 20 KB: minimal (names + line ranges only) | ||
| - 50 KB: outline (top-level symbols only) | ||
| - 100 KB: truncated (first/last 50 symbols, hard cap) | ||
| Maps are cached in-memory by `(filePath, mtime)`. Delivered as custom `file-map` messages via `pi.sendMessage()` after the `tool_result` event. | ||
| ## Golden Samples | ||
| | For | Reference | Key patterns | | ||
| |-----|-----------|--------------| | ||
| | New mapper | `src/mappers/csv.ts` | Simple, clean, regex-free in-process parsing | | ||
| | Complex mapper | `src/mappers/typescript.ts` | ts-morph AST walk, nested symbols, modifiers | | ||
| | Tree-sitter mapper | `src/mappers/clojure.ts` | tree-sitter AST walk, reader conditionals, platform modifiers | | ||
| | Subprocess mapper | `src/mappers/python.ts` | Calls external script, parses JSON output | | ||
| | Unit test | `tests/unit/mappers/csv.test.ts` | Fixture-based, edge cases, null returns | | ||
| | Integration test | `tests/integration/budget-enforcement.test.ts` | Tests progressive detail reduction | | ||
| ## Heuristics | ||
| | When | Do | | ||
| |------|-----| | ||
| | Adding a new language | Create `src/mappers/<lang>.ts`, add to `MAPPERS` in `mapper.ts`, add extension in `language-detect.ts`, add unit test | | ||
| | Mapper returns too many symbols | Rely on `formatter.ts` budget system, don't filter in mappers | | ||
| | Mapper can't parse a file | Return `null` — the dispatch chain falls through to ctags then fallback | | ||
| | Adding a new SymbolKind | Add to `enums.ts`, update formatter if display differs | | ||
| | Testing mappers | Use fixture files in `tests/fixtures/`, never mock file reads | | ||
| | E2E tests | Require pi installed + tmux; use `tests/helpers/pi-runner.ts` | | ||
| ## Boundaries | ||
| **Always:** | ||
| - Return `FileMap` or `null` from mappers (never throw) | ||
| - Include `startLine` and `endLine` for every symbol (1-indexed) | ||
| - Run `npm run validate` before committing | ||
| - Use existing `FileSymbol` interface for all symbol data | ||
| **Ask first:** | ||
| - Changing budget thresholds in `constants.ts` | ||
| - Adding new `DetailLevel` variants | ||
| - Modifying the tool description in `index.ts` | ||
| - Changes to the `tool_result` event handler | ||
| **Never:** | ||
| - Filter symbols in mappers based on budget (formatter handles this) | ||
| - Add runtime dependencies without discussing (binary size matters for pi extensions) | ||
| - Use `any` types | ||
| - Disable lint rules | ||
| ## Codebase State | ||
| - `oxlint` installed as devDependency; `npm run lint` exits cleanly (0 errors, 0 warnings) | ||
| - `tree-sitter` pinned to 0.22.4 due to peer dependency conflicts (see `docs/todo/upgrade-tree-sitter-0.26.md`) | ||
| - `tree-sitter-clojure` pinned to commit SHA from `github:ghoseb/tree-sitter-clojure` (third-party fork) | ||
| - Go outline script auto-compiles on first use; compiled binary checked in at `scripts/go_outline` | ||
| - Phase 1-5 of implementation plan complete; remaining TODOs in `docs/todo/` | ||
| | Docstrings / JSDoc | `FileSymbol.docstring?: string` | First-line summary of doc comments | | ||
| | Exported flag | `FileSymbol.isExported?: boolean` | Whether symbol is part of public API | | ||
| | Required imports | `FileMap.imports: string[]` | Always an array, never undefined | | ||
| ## Terminology | ||
| | Docstring | First line of a JSDoc / doc comment on a symbol | | ||
| | isExported | Boolean flag: true if symbol is part of the module's public API | | ||
| | Term | Means | | ||
| |------|-------| | ||
| | Map | Structural outline of a file's symbols with line ranges | | ||
| | Mapper | Language-specific parser that produces a `FileMap` | | ||
| | Budget | Maximum byte size for formatted map output | | ||
| | Detail level | How much information each symbol carries (full → truncated) | | ||
| | Fallback chain | mapper → ctags → grep when parsers fail | | ||
| | Pending map | Map waiting to be sent after `tool_result` event fires | | ||
| ## Tech Stack | ||
| - **Runtime:** Node.js (ES2022 modules) | ||
| - **Language:** TypeScript (strict, `noUncheckedIndexedAccess`) | ||
| - **Testing:** Vitest (unit/integration: 10s timeout, e2e: 60s timeout) | ||
| - **Linting:** oxlint + oxfmt | ||
| - **Parsing:** ts-morph, tree-sitter (rust, cpp, clojure), regex, subprocess (Python/Go/jq) | ||
| - **Framework:** pi extension API (`@mariozechner/pi-coding-agent`) |
-108
| # Skipped Read Recovery | ||
| *2026-02-12T16:06:08Z* | ||
| When pi-read-map generates a file map for a large file, it sends the map via `pi.sendMessage()`. During streaming, this defaults to `deliverAs: "steer"`, which pushes the message into the agent's steering queue. After each tool execution, the agent loop checks the steering queue — if non-empty, it skips all remaining tool calls with 'Skipped due to queued user message.' | ||
| This means: if the agent requests 3 parallel reads and the first file triggers a map, the other 2 reads are cancelled. | ||
| **Skipped Read Recovery** detects these cancelled reads at `turn_end` and sends a `followUp` message instructing the agent to re-issue them. | ||
| ## How It Works | ||
| 1. A `turn_end` event handler inspects all tool results for the turn | ||
| 2. It identifies read results containing 'Skipped due to queued user message.' | ||
| 3. It extracts the file paths from the assistant message's `toolCall` entries | ||
| 4. It sends a `read-recovery` followUp message listing the interrupted paths | ||
| 5. The followUp triggers a new turn where the agent re-reads the skipped files | ||
| ## Files Changed | ||
| | File | Change | | ||
| |------|--------| | ||
| | `src/index.ts` | Added `turn_end` handler and `read-recovery` message renderer | | ||
| | `tests/integration/skipped-read-recovery.test.ts` | 9 new tests covering detection, edge cases, and rendering | | ||
| ## Tests | ||
| The new test suite covers: | ||
| ```bash | ||
| cd /home/will/projects/pi-read-map && npx vitest run tests/integration/skipped-read-recovery.test.ts 2>&1 | tail -20 | ||
| ``` | ||
| ```output | ||
| RUN v3.2.4 /home/will/projects/pi-read-map | ||
| ✓ tests/integration/skipped-read-recovery.test.ts (9 tests) 6ms | ||
| Test Files 1 passed (1) | ||
| Tests 9 passed (9) | ||
| Start at 08:06:28 | ||
| Duration 845ms (transform 139ms, setup 0ms, collect 625ms, tests 6ms, environment 0ms, prepare 61ms) | ||
| ``` | ||
| ## Full Validation | ||
| Typecheck, lint, format, and dead-code detection all pass: | ||
| ```bash | ||
| cd /home/will/projects/pi-read-map && npm run validate 2>&1 | ||
| ``` | ||
| ```output | ||
| > pi-read-map@1.0.0 validate | ||
| > npm run typecheck && npm run lint && npm run format:check && npm run dead-code | ||
| > pi-read-map@1.0.0 typecheck | ||
| > tsc --noEmit | ||
| > pi-read-map@1.0.0 lint | ||
| > oxlint -c .oxlintrc.json src tests | ||
| Found 0 warnings and 0 errors. | ||
| Finished in 214ms on 61 files with 427 rules using 16 threads. | ||
| > pi-read-map@1.0.0 format:check | ||
| > oxfmt --config .oxfmtrc.jsonc src tests --check | ||
| Checking formatting... | ||
| All matched files use the correct format. | ||
| Finished in 556ms on 73 files using 16 threads. | ||
| > pi-read-map@1.0.0 dead-code | ||
| > knip | ||
| ``` | ||
| ## Full Test Suite | ||
| All 197 tests pass (27 test files), including the 9 new recovery tests: | ||
| ```bash | ||
| cd /home/will/projects/pi-read-map && npm run test 2>&1 | grep -E "(Test Files|Tests|✓ tests/integration)" | ||
| ``` | ||
| ```output | ||
| ✓ tests/integration/mapper-dispatch.test.ts (8 tests) 627ms | ||
| ✓ tests/integration/budget-enforcement.test.ts (8 tests) 693ms | ||
| ✓ tests/integration/directory-read.test.ts (4 tests) 14ms | ||
| ✓ tests/integration/cache-behavior.test.ts (4 tests) 262ms | ||
| ✓ tests/integration/skipped-read-recovery.test.ts (9 tests) 7ms | ||
| ✓ tests/integration/separate-map-message.test.ts (6 tests) 9ms | ||
| ✓ tests/integration/extension-load.test.ts (6 tests) 6ms | ||
| Test Files 27 passed (27) | ||
| Tests 197 passed (197) | ||
| ``` | ||
| ## Implementation Detail | ||
| The recovery handler in `src/index.ts` narrows the `AgentMessage` union to `AssistantMessage` (via `role === "assistant"` check) and then matches skipped `toolCallId`s against the `toolCall` entries in the assistant content to extract file paths. The recovery message is delivered as a `followUp`, which triggers a new agent turn without entering the steering queue. | ||
| A custom `read-recovery` message renderer shows a compact summary in collapsed view (e.g., 'Recovery: 2 interrupted read(s) being re-issued') and the full path list when expanded. |
-11
| { | ||
| "$schema": "https://unpkg.com/knip@latest/schema.json", | ||
| "entry": ["src/index.ts"], | ||
| "project": ["src/**/*.ts"], | ||
| "ignore": ["scripts/**", "tests/fixtures/**"], | ||
| "ignoreDependencies": [ | ||
| "@factory/eslint-plugin", | ||
| "ultracite", | ||
| "@mariozechner/pi-tui" | ||
| ] | ||
| } |
Sorry, the diff of this file is not supported yet
212525
-86.59%29
-19.44%6765
-0.27%