Semantic code editing for pi — replace text-based read/edit/write/grep tools with LSP-backed symbol-level tools for TypeScript projects.
Instead of the agent searching through files with rg and reading large file chunks, it navigates a structured code map and edits individual symbols. Imports are auto-resolved. File paths are never exposed.
# Load the extension in any pi session
pi -e ~/dev/pi-structured/src/index.ts
# Or scaffold a new project first
mkdir my-api && cd my-api
pi -e ~/dev/pi-structured/src/index.ts
# Then type: /scaffold:express-apiThe extension auto-detects pi-structured projects (by .pi-structured marker or src/{functions,classes,types,...} layout). In non-structured projects, tools are registered but session features don't activate.
The agent receives a code map at session start — a structural overview of every exported symbol, organized by kind. All edits go through semantic tools:
Instead of: Use:
─────────────────────────────────────────
read src/functions/foo.ts get_symbol("foo")
edit a source file update_symbol("foo", newBody)
write a new file create_symbol("foo", "function", body)
grep for code find_symbol("query")
manage imports (auto-resolved — no tool needed)
The agent never sees file paths. It thinks and plans in symbol-space only.
| Tool | Description |
|---|---|
code_map() |
Structural overview of all symbols, data model, impact analysis |
find_symbol("query") |
Search symbols by name (prefix/substring) |
get_symbol("name") |
Read full body + auto-resolved imports + reference count |
create_symbol(name, kind, body) |
Create symbol — imports auto-detected from body |
update_symbol(name, newBody) |
Replace body — new imports auto-added |
delete_symbol(name) |
Remove symbol, report remaining references |
get_references("name") |
Find all usages via TypeScript LanguageService |
typecheck() |
Run tsc --noEmit diagnostics |
add_dependency("pkg") |
npm install + returns version |
plan({ goal, steps }) |
Declare structured plan with progress tracking |
batch([...]) |
Multiple create/update/delete in one call |
manage_imports(name, imports) |
Manually set imports (rarely needed — auto-imports handles it) |
| Command | Creates |
|---|---|
/scaffold:express-api |
Express + TypeScript REST API |
/scaffold:web-app |
React + Vite + TypeScript frontend |
/scaffold:cli |
CLI tool |
/scaffold:library |
npm package/library |
/scaffold:vitest |
Add Vitest testing to existing project |
/scaffold |
List available scaffolds |
All scaffolds create the directory layout, package.json, tsconfig.json, initial barrel, run npm install, and git init + commit.
my-project/
├── .pi-structured # Marker file
├── tsconfig.json
├── package.json
└── src/
├── index.ts # Barrel — auto-regenerated
├── functions/ # export function ... (one per file)
├── classes/ # export class ... (one per file)
├── interfaces/ # export interface ... (one per file)
├── types/ # export type ... (one per file)
├── enums/ # export enum ... (one per file)
├── consts/ # export const ... (one per file)
├── components/ # React components (one per file)
└── __tests__/ # Mirrors src/ structure
Rules enforced:
- One exported symbol per file (private helpers allowed)
- All imports go through the barrel (
../index) - Raw
read/edit/write/grepto symbol directories is blocked bashaccess to source files triggers a warning- Imports are auto-resolved — the agent writes code, the extension handles imports
The agent writes get_symbol("foo") and references Book, userStore, etc. directly in the body. The extension parses the body, detects external references, resolves them to project symbols or npm packages, and adds the imports automatically.
code_map shows how many symbols import each one:
Book — imported by 10 symbols
⚠️ High-impact symbols (modify with care):
userStore — imported by 12 symbols
Book — imported by 10 symbols
plan({ goal: "Add pagination", steps: [
{ action: "create", symbol: "PaginatedResponse", kind: "type" },
{ action: "update", symbol: "getAllBooks", reason: "add offset/limit" },
]})Progress is auto-tracked when symbols are created/updated/deleted. Call plan() without arguments to check progress. A widget shows remaining steps.
batch({ operations: [
{ action: "create", name: "getTags", kind: "function", body: "..." },
{ action: "update", name: "main", body: "..." },
]})Multiple changes in one turn. Operations run sequentially. Later operations see newly-created symbols.
Users can type fix @lendBook in prompts — the input handler resolves it to get_symbol("lendBook").
read/writeon symbol files → ⛔ BLOCKED with redirect messageediton symbol files → ⛔ BLOCKED (built-in edit handles non-symbol files)grepon symbol dirs → ⛔ BLOCKED, suggestsfind_symbol/get_referencesbash cat/rm/mvon source files → warning appended to output
~/dev/pi-structured/
├── src/
│ ├── index.ts # Extension entry — registers tools, events, guards
│ ├── types.ts # Shared types
│ ├── services/
│ │ ├── ts-service.ts # TypeScript LanguageService (findReferences, diagnostics)
│ │ ├── project-detector.ts # Detect pi-structured projects
│ │ └── code-map.ts # Generate code map + data model + impact analysis
│ ├── tools/
│ │ ├── code-map.ts # code_map tool
│ │ ├── find-symbol.ts # find_symbol tool
│ │ ├── get-symbol.ts # get_symbol tool
│ │ ├── get-references.ts # get_references tool
│ │ ├── typecheck.ts # typecheck tool
│ │ ├── create-symbol.ts # create_symbol tool
│ │ ├── update-symbol.ts # update_symbol tool
│ │ ├── delete-symbol.ts # delete_symbol tool
│ │ ├── manage-imports.ts # manage_imports tool
│ │ ├── add-dependency.ts # add_dependency tool
│ │ ├── plan.ts # plan tool + progress tracking
│ │ └── batch.ts # batch tool
│ ├── overrides/
│ │ ├── read.ts # Read override (blocks symbol dirs)
│ │ └── edit-write.ts # Write override (blocks symbol dirs)
│ ├── scaffold/
│ │ └── index.ts # All scaffold commands
│ └── utils/
│ ├── auto-imports.ts # Body parsing → auto-import resolution
│ ├── barrel.ts # Barrel file management
│ ├── imports.ts # Import parsing utilities
│ └── service-refresh.ts # TS LanguageService lifecycle
├── DESIGN.md # Architectural design doc
├── package.json # Deps: typescript
└── node_modules/
typescript^5.8 — in-process LanguageService API@earendil-works/pi-coding-agent— provided by pi at runtimetypebox— provided by pi at runtime@earendil-works/pi-ai— provided by pi at runtime
See DESIGN.md for the full rationale, self-review, and implementation history.