5.3 KiB
AGENTS.md — pi-dynamic-context-pruning
Reference for agentic coding agents operating in this repository.
Project Overview
A pi coding agent extension (TypeScript/ESM) that implements Dynamic Context Pruning (DCP).
Pi loads extension .ts files directly — there is no build step and no compiled output.
Runtime: Bun (used to run tests and the extension).
Package type: "type": "module" — all files are ES modules.
Commands
| Task | Command |
|---|---|
| Run tests | bun run pruner.test.ts |
| Build | (none — pi loads .ts directly) |
| Lint | (no lint config present) |
| Format | (no formatter config present) |
Single test: All tests live in pruner.test.ts. There is no test framework — tests use Node.js assert and plain console.log. To isolate one test scenario, comment out other {} blocks in that file.
Module Structure
| File | Purpose |
|---|---|
index.ts |
Extension entry point; registers all hooks with the pi ExtensionAPI |
config.ts |
JSONC config loading with 4-layer merge (defaults → global → env → project) |
state.ts |
DcpState type + createState / resetState / createInputFingerprint |
pruner.ts |
applyPruning, injectNudge, getNudgeType, estimateTokens |
compress-tool.ts |
Registers the compress tool with the pi tool registry |
commands.ts |
Registers /dcp slash commands |
prompts.ts |
All system prompt strings and nudge text constants |
pruner.test.ts |
Self-contained tests for applyPruning |
Imports
- Always use
.jsextension for local imports, even when the source file is.ts:import { loadConfig } from "./config.js" - Use
import typefor type-only imports:import type { DcpState } from "./state.js" import type { ExtensionAPI } from "@mariozechner/pi-coding-agent" - Named imports preferred; default exports only for the extension entry point (
index.ts). - Import order: Node built-ins → external packages → local modules.
Code Style
Naming
| Kind | Convention | Examples |
|---|---|---|
| Files | kebab-case or camelCase | compress-tool.ts, pruner.ts |
| Interfaces / Types | PascalCase | DcpState, CompressionBlock, ToolRecord |
| Functions | camelCase | applyPruning, loadConfig, createState |
| Constants (module-level) | UPPER_SNAKE_CASE | ALWAYS_PROTECTED_DEDUP, DEFAULT_CONFIG, SYSTEM_PROMPT |
| Variables / parameters | camelCase | activeBlocks, toolCallId, contextPercent |
Section separators
Use the long-dash pattern with a label for logical sections within a file:
// ---------------------------------------------------------------------------
// Section Name
// ---------------------------------------------------------------------------
Use // ── Label ──────────... for subsections within index.ts event handler blocks.
JSDoc
Add JSDoc comments to all exported functions and non-trivial interfaces. Keep them concise and factual.
Type annotations
- Explicit return types on all exported functions.
- Use
unknowninstead ofanywhen the shape is genuinely unknown, unless you are working with external API message shapes (message content arrays), whereanyis acceptable at the boundary. - Prefer
as constfor literal arrays (e.g.,["compress", "write", "edit"] as const).
TypeBox schemas
Use @sinclair/typebox Type.* helpers for tool input schemas in compress-tool.ts.
Error Handling
Two established patterns — do not mix them:
-
Silent/best-effort (config loading, file I/O):
try { raw = fs.readFileSync(filePath, "utf8") } catch { return {} }Use when failure is non-fatal and a safe default can be returned.
-
Throw domain errors (ID resolution, invalid tool args):
throw new Error(`Unknown message ID: ${id}`)Use when the caller must handle the failure explicitly.
- No silent swallowing of errors that indicate programming mistakes.
console.error/console.warnare not used; errors surface via throws or safe returns.
Key Architectural Constraints
- Message timestamps are the stable identifier for positioning compression blocks. Never use array indices as durable references; always use
timestamp. assistant+toolResultpairs must be removed atomically. If a compression range covers atoolResult, the precedingassistantmessage (with the matchingtoolCallblock) must be included in the range — see backward-expansion logic inpruner.ts.- State is mutated in-place by
resetStateso all module references stay valid; do not replacestatewith a new object. - Config is read-only after
loadConfig. Never mutate the returned config object. - No external runtime dependencies beyond
jsonc-parser. Do not add newdependencies; preferpeerDependenciesfor pi-ecosystem packages.
Dependencies
| Package | Role |
|---|---|
jsonc-parser |
Parse JSONC config files |
@mariozechner/pi-coding-agent |
Peer — ExtensionAPI, event types |
@mariozechner/pi-tui |
Peer — AutocompleteItem, UI types |
@sinclair/typebox |
Peer — Type.* schema builders for tool registration |