Compare commits

..

5 Commits

Author SHA1 Message Date
alex 97a4dd22f5 docs: refresh README 2026-07-27 15:24:01 +02:00
Developer 3e7410b6bd chore: track .pi-map.md and .pi-map.index.md artifacts
Remove the map files from .gitignore so they are committed as project
navigation artifacts, and also unignore them in the sample fixture.
Regenerate all maps so the committed versions reflect the current source.
2026-06-16 14:38:42 +00:00
Developer cb581f44b9 feat: smart subtree-aware reinit and validate --fix
- project_map_reinit now regenerates only the target subtree + ancestors
  by default, falling back to full reinit when subtree file count exceeds
  reinitFullThresholdPercent (default 10%).
- Add reinitFullThresholdPercent config option.
- Expose fix=true on project_map_validate Pi tool for localized repair.
- Update docs and runtime guidance to prefer patch / validate --fix
  before full reinit.
- Add integration tests for smart reinit and update typebox mock.
2026-06-16 11:46:48 +00:00
Developer 5f1c107667 feat: avoid duplicate project-map hint injection by checking context
The before_agent_start handler now scans the active session context via
ctx.sessionManager.buildSessionContext() for an existing pi-project-map-hint
custom message and skips injection when one is already present in the
current branch. This prevents duplicate visible hints in advisory/pre-init
modes and duplicate hidden hints in strong/strict modes. The hint is
automatically re-injected after compaction or /tree navigation removes it
from the active path.

Also removes the unused hooks/on-prompt.ts prompt-text injector.
2026-06-14 08:57:23 +00:00
Developer 6bc7be4c21 feat: avoid duplicate project-map hint injection by checking context
The before_agent_start handler now inspects the current session context
and skips injection if a pi-project-map-hint custom message is already
present in the active branch. This prevents duplicate hints on every
prompt while still re-injecting after compaction or tree navigation.

Also removes the unused hooks/on-prompt.ts prompt-text injector.
2026-06-14 08:53:26 +00:00
60 changed files with 1672 additions and 197 deletions
+18
View File
@@ -0,0 +1,18 @@
# .atl (index)
dir: .atl
## role
Empty directory placeholder, likely intended for Atlassian tool configuration or automation artifacts that have not yet been populated.
## parent
index: ./.pi-map.index.md
map: ./.pi-map.md
## children
-
## files
## links
index: .atl/.pi-map.index.md
map: .atl/.pi-map.md
## workflows
-
## dirty
-
+18
View File
@@ -0,0 +1,18 @@
# .atl
dir: .atl
index: .atl/.pi-map.index.md
## role
Empty directory placeholder, likely intended for Atlassian tool configuration or automation artifacts that have not yet been populated.
## files
## arch
N/A - no files or architectural patterns present in this directory.
## tags
-
## symbols
-
## workflows
-
## dirty
-
-2
View File
@@ -4,8 +4,6 @@ coverage/
*.log *.log
.DS_Store .DS_Store
.env .env
.pi-map.md
.pi-map.index.md
# Local Pi runtime state # Local Pi runtime state
.atl/ .atl/
.pi .pi
+61
View File
@@ -0,0 +1,61 @@
# . (index)
dir: .
## Project Map Protocol
1. Read this protocol and the root `.pi-map.index.md` first.
2. Use `index:` / `map:` references to open relevant directory indexes and maps.
3. Load indexes before rich maps during task-start navigation.
4. Read the local rich map and actual source before editing.
5. Treat non-empty `## dirty` sections in either artifact as stale.
6. If source and generated artifacts disagree, trust source.
7. If map and index disagree, trust neither blindly; verify from source and regenerate the pair.
8. After editing source, run `project_map_patch` for each changed file.
9. Before broad architectural claims or final handoff, run `project_map_validate` when freshness matters.
Trust boundary: index routes, map orients, source decides.
## role
A TypeScript/Node.js CLI tool and Pi extension that generates paired markdown analysis artifacts (.pi-map.index.md and .pi-map.md) to provide hierarchical codebase navigation and contextual orientation for AI coding agents.
## parent
-
## children
- .atl
index: .atl/.pi-map.index.md
map: .atl/.pi-map.md
- fixtures
index: fixtures/.pi-map.index.md
map: fixtures/.pi-map.md
- openspec
index: openspec/.pi-map.index.md
map: openspec/.pi-map.md
- src
index: src/.pi-map.index.md
map: src/.pi-map.md
- tests
index: tests/.pi-map.index.md
map: tests/.pi-map.md
## files
- .gitignore
- .npmrc
- README.md
- SKILL.md
- design-doc.md
- package-lock.json
- package.json
- pi-extension.ts
- troubleshooting.md
- tsconfig.json
- usage-guide.md
## links
index: ./.pi-map.index.md
map: ./.pi-map.md
## workflows
- change project behavior
read: .gitignore, .npmrc, pi-extension.ts
- change project config
read: package-lock.json, package.json, tsconfig.json
- explore project subdirectories
index: .atl/.pi-map.index.md, fixtures/.pi-map.index.md, openspec/.pi-map.index.md
## dirty
-
+48
View File
@@ -0,0 +1,48 @@
# .
dir: .
index: ./.pi-map.index.md
## Project Map Protocol
1. Read this protocol and the root `.pi-map.index.md` first.
2. Use `index:` / `map:` references to open relevant directory indexes and maps.
3. Load indexes before rich maps during task-start navigation.
4. Read the local rich map and actual source before editing.
5. Treat non-empty `## dirty` sections in either artifact as stale.
6. If source and generated artifacts disagree, trust source.
7. If map and index disagree, trust neither blindly; verify from source and regenerate the pair.
8. After editing source, run `project_map_patch` for each changed file.
9. Before broad architectural claims or final handoff, run `project_map_validate` when freshness matters.
Trust boundary: index routes, map orients, source decides.
## role
A TypeScript/Node.js CLI tool and Pi extension that generates paired markdown analysis artifacts (.pi-map.index.md and .pi-map.md) to provide hierarchical codebase navigation and contextual orientation for AI coding agents.
## files
- .gitignore | Specifies files and directories for Git to ignore in a Node.js/TypeScript project with Pi tooling integration | dep: git
- .npmrc | Configures npm to use legacy peer dependency resolution behavior | dep: npm
- README.md | Documents a CLI tool and Pi extension that generates paired machine-readable analysis artifacts (.pi-map.index.md and .pi-map.md) for hierarchical codebase navigation and agent orientation. | dep: npm, Node.js, Pi runtime environment, LLM provider (OpenAI, etc.), file system
- SKILL.md | Defines a Pi skill that generates and maintains hierarchical paired project-analysis artifacts (`.pi-map.index.md` + `.pi-map.md`) to enable AI agents to navigate codebases without reading every source file. | dep: markdown, AST parsing, LLM API, JSON configuration, CLI/tool interface
- design-doc.md | A design document explaining the internal architecture of `pi-project-map`, a TypeScript/Node.js tool that generates and maintains hierarchical paired markdown artifacts (`.pi-map.index.md` and `.pi-map.md`) to serve as navigation aids for AI coding agents, with both CLI and Pi extension runtime modes. | dep: TypeScript, Node.js, tree-sitter, ignore, LLM client (PiLLMClient/ExternalLLMClient/KimiLLMClient)
- package-lock.json | Auto-generated npm lock file that records exact dependency versions and tree structure for reproducible installs of the "pi-project-map" Node.js CLI tool. | dep: npm, esbuild, eslint, typescript, vitest, openai, tree-sitter, tree-sitter-python, tree-sitter-typescript, p-limit, picocolors, ignore
- package.json | Pi skill for hierarchical project analysis that generates and maintains .pi-map.md files | dep: ignore, openai, p-limit, picocolors, tree-sitter, tree-sitter-python, tree-sitter-typescript, typescript, vitest, eslint, @types/node, @typescript-eslint
- pi-extension.ts | Pi extension that registers tools for managing project map artifacts (.pi-map.md/.pi-map.index.md) and injects contextual hints into agent sessions based on configuration modes. | dep: @mariozechner/pi-coding-agent, typebox, fs, path, ./src/index.js, ./src/config.js, ./src/llm/llm-client.js, ./src/llm/llm-error.js
- troubleshooting.md | Troubleshooting guide for diagnosing and resolving issues with the pi-project-map tool across validation, prompt injection, strict mode, LLM provider, and testing scenarios. | dep: project_map_validate, project_map_patch, project_map_reinit, LLM client, Pi runtime, tree-sitter, npm
- tsconfig.json | Configures TypeScript compiler options for a Node.js project targeting ES2022 with strict type checking and declaration output
- usage-guide.md | User documentation explaining how to use the pi-project-map tool for navigating and maintaining project-map artifacts in codebases.
## arch
Dual-runtime architecture supporting both standalone CLI and Pi extension modes, using a hierarchical paired-file pattern (index + detail) with TypeScript/Node.js, strict type checking, and configuration-driven behavior injection for agent session integration.
## tags
map, project, tree, typescript, sitter, node, npm, js
## symbols
-
## workflows
- change project behavior
read: .gitignore, .npmrc, pi-extension.ts
- change project config
read: package-lock.json, package.json, tsconfig.json
- explore project subdirectories
index: .atl/.pi-map.index.md, fixtures/.pi-map.index.md, openspec/.pi-map.index.md
## dirty
-
+49 -125
View File
@@ -1,140 +1,74 @@
# pi-project-map # pi-project-map
> Pi skill and CLI for hierarchical project analysis. A Pi extension and command-line tool that generates paired project-navigation artifacts: `.pi-map.index.md` files for routing and `.pi-map.md` files for directory orientation. The artifacts help agents navigate a codebase; source remains the authority.
`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. ## Status and limitations
## What it is - Generation and repair commands call an LLM and write map artifacts into the target project.
- `validate` without `--fix` checks artifacts without creating an LLM client; `validate --fix` can modify them and requires an LLM.
- Token budgeting and relevant-turn detection are best-effort. Retrieved maps route and orient work but do not replace source verification.
- No Node.js version requirement or deployment configuration is declared in this repository.
For every non-ignored directory, the tool produces two files: ## Install and develop
| File | Purpose | Install the published CLI globally:
|------|---------|
| `.pi-map.index.md` | Routing-first index for deciding what to open next. |
| `.pi-map.md` | Orientation-first rich map for understanding a directory. |
Together they form a **paired artifact model**:
- indexes are small and routing-optimized
- maps are denser and architecture-optimized
- source remains the final authority
`pi-project-map` runs as both:
- a **CLI** (`project-map`)
- a **Pi extension** (`pi-extension.ts`) that registers tools and optional runtime prompt injection
## Quick start
Install:
```bash ```bash
npm install -g pi-project-map npm install -g pi-project-map
``` ```
Generate paired artifacts in a repo: For local development, install dependencies and build from this repository:
```bash ```bash
cd my-project npm install
project-map init npm run build
``` ```
For standalone CLI usage, provide an LLM provider/API key. For example: Available development checks:
```bash ```bash
export OPENAI_API_KEY=... npm run test
project-map init npm run lint
npm run typecheck
npm run dev
``` ```
Inside Pi, the extension uses Pi's configured model automatically. `npm run dev` runs TypeScript in watch mode. The project declares no deployment command.
## Command overview ## Use the CLI
| CLI command | Pi tool | Purpose | Run the installed `project-map` command from the project to map. Use `--help` or `--version` for CLI metadata.
|-------------|---------|---------|
| `project-map init [path]` | `project_map_init` | Generate paired artifacts for the whole project or a subdirectory. | ```bash
| `project-map patch <file>` | `project_map_patch` | Regenerate artifacts for the directory containing the changed file and refresh ancestors appropriately. | project-map init [path]
| `project-map validate [--fix]` | `project_map_validate` | Check paired artifacts for staleness or inconsistency. | project-map patch <file>
| `project-map reinit [path]` | `project_map_reinit` | Force full regeneration of all artifacts. | project-map validate [--fix] [path]
| `project-map context <query>` | `project_map_context` | Return a ranked context bundle for a natural-language query. | project-map reinit [path]
project-map context <query>
```
Typical workflow: Typical workflow:
1. `project-map init` on first use 1. Run `project-map init` to generate maps for a project.
2. after editing source, `project-map patch <changed-file>` 2. After editing a file, run `project-map patch <file>`.
3. before broad architectural decisions, `project-map validate` 3. Run `project-map validate` before relying on maps; use `--fix` only when you intend to repair artifacts.
4. for targeted exploration, `project-map context "<query>"` 4. Use `project-map context "<query>"` to retrieve a ranked context bundle.
## Operating model `init`, `patch`, `reinit`, and `validate --fix` mutate `.pi-map.md` and/or `.pi-map.index.md` artifacts. `context` retrieves from existing artifacts.
Follow a three-tier model when consuming project maps: ### Pi extension
1. **Tier 0 — Protocol and root index** The repository exposes `pi-extension.ts` as a Pi extension. Inside Pi it uses Pi's configured model and registers equivalent `project_map_*` tools. The extension can also inject project-map guidance according to `promptInjectionMode`.
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: ## LLM configuration
> **index routes, map orients, source decides.** Standalone CLI use needs credentials for the selected provider. Do not commit keys.
## Prompt injection policy - `openai` (the default) reads `OPENAI_API_KEY`; its model can be set with `OPENAI_MODEL` or `LLM_MODEL`.
- `kimi` reads `KIMI_API_KEY` (or `KIMI_COM_API_KEY`); its model can be set with `KIMI_MODEL` or `LLM_MODEL`.
- CLI options `--llm-provider=openai|kimi`, `--llm-model=<model>`, and `--llm-base-url=<url>` override corresponding settings for a command.
The Pi extension can automatically inject lightweight project-map guidance into the agent context. Behavior is controlled by `promptInjectionMode` in `.pi-project-map.json`. Create an optional `.pi-project-map.json` in the project being mapped. It is merged with the defaults:
### Before init
No synthetic map content is injected. The agent sees only a visible startup hint telling it to run `project_map_init`.
### After init
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.
### Mode ladder
| 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
Default automatic-injection budget is the smaller of:
- **15%** of the active model context window
- **100,000 tokens** absolute cap
If the runtime cannot discover the model context window, it falls back to the absolute cap.
## Retrieval is separate
`project-map context <query>` and `project_map_context` are **separate, on-demand retrieval** paths. They do **not** replace automatic prompt injection.
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
Use retrieval for targeted navigation when you already have a specific question.
## Configuration overview
Create `.pi-project-map.json` in the project root:
```json ```json
{ {
@@ -149,26 +83,16 @@ Create `.pi-project-map.json` in the project root:
} }
``` ```
Providing `ignorePatterns` replaces the built-in default list, so include any defaults you want to keep. `promptInjectionMode` accepts `off`, `advisory`, `strong`, or `strict`. Supplying `ignorePatterns` **replaces** the built-in ignore list rather than extending it. Additional supported settings include `llmBaseUrl` and `reinitFullThresholdPercent`.
Key knobs: ## Repository layout
- `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
## Documentation map - `src/` — CLI, map generation, validation, retrieval, configuration, and LLM clients
- `pi-extension.ts` — Pi extension entry point
- `SKILL.md` — Pi skill definition and operator guidance
- `usage-guide.md`, `design-doc.md`, `troubleshooting.md` — additional usage and design documentation
- `package.json` — package metadata, dependencies, and npm scripts
- [`SKILL.md`](SKILL.md) — skill definition and agent/operator instructions ## Operations
- [`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
## Known limitations There is no server or deployment manifest. Operate the tool from the project being mapped and keep generated maps under the target project's normal review and version-control practices.
- 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
+8 -3
View File
@@ -44,8 +44,10 @@ When project-map artifacts exist in the repo:
3. read the local `.pi-map.md` plus relevant source before editing 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 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 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) 6. if validation shows localized discrepancies, run `project_map_validate` with `fix=true` (tool) or `project-map validate --fix` (CLI)
7. in `strict` mode, only bypass the protocol-path guard with an explicit marker: `[PI_MAP_BYPASS: <brief justification>]` 7. only if discrepancies are widespread or structural, run `project_map_reinit` (tool) or `project-map reinit` (CLI)
8. for targeted exploration, use `project_map_context <query>` (tool) or `project-map context <query>` (CLI)
9. in `strict` mode, only bypass the protocol-path guard with an explicit marker: `[PI_MAP_BYPASS: <brief justification>]`
## Prompt injection modes ## Prompt injection modes
@@ -102,12 +104,15 @@ Create `.pi-project-map.json` in the project root:
"workflowHintCap": 5, "workflowHintCap": 5,
"llmProvider": "openai", "llmProvider": "openai",
"llmModel": "gpt-4o-mini", "llmModel": "gpt-4o-mini",
"reinitFullThresholdPercent": 10,
"ignorePatterns": ["node_modules", ".git", "dist", "build"] "ignorePatterns": ["node_modules", ".git", "dist", "build"]
} }
``` ```
Providing `ignorePatterns` replaces the built-in default list, so include any defaults you want to keep. Providing `ignorePatterns` replaces the built-in default list, so include any defaults you want to keep.
- `reinitFullThresholdPercent` — when `project_map_reinit` targets a subtree that covers more than this percentage of project files, it falls back to full regeneration
Knobs that matter in practice: Knobs that matter in practice:
- `promptInjectionMode``off` | `advisory` | `strong` | `strict` - `promptInjectionMode``off` | `advisory` | `strong` | `strict`
- `contextBudgetPercent` / `contextBudgetMaxTokens` — caps automatic map/index injection - `contextBudgetPercent` / `contextBudgetMaxTokens` — caps automatic map/index injection
@@ -122,5 +127,5 @@ Knobs that matter in practice:
| `project_map_init` | `project-map init [path]` | Generate all paired artifacts. | | `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_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_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_reinit` | `project-map reinit [path]` | Smart regeneration: recomputes the target subtree plus ancestors, and only falls back to full regeneration when the subtree covers more than `reinitFullThresholdPercent` of project files (default 10%). |
| `project_map_context` | `project-map context <query>` | Retrieve a ranked context bundle for a natural-language query. | | `project_map_context` | `project-map context <query>` | Retrieve a ranked context bundle for a natural-language query. |
+2
View File
@@ -267,6 +267,8 @@ Payload fallback scanning is handled inside `context`-level decision logic; ther
`detectEditIntent()` and `detectArchitectureSensitiveReasoning()` provide heuristic fallback for generic turns. `detectEditIntent()` and `detectArchitectureSensitiveReasoning()` provide heuristic fallback for generic turns.
In addition to marker-based deduplication, `before_agent_start` scans the active session context via `ctx.sessionManager.buildSessionContext()` for an existing `pi-project-map-hint` custom message. If one is already present in the current branch, the handler skips injection entirely. This prevents duplicate visible hints in advisory/pre-init modes and duplicate hidden hints in strong/strict modes when the session context already contains the guidance. The hint is automatically re-injected after compaction or `/tree` navigation removes it from the active path.
### 7.4 Protocol path and strict bypass ### 7.4 Protocol path and strict bypass
The **protocol path** is present when outgoing context contains: The **protocol path** is present when outgoing context contains:
+20
View File
@@ -0,0 +1,20 @@
# fixtures (index)
dir: fixtures
## role
Provides test data and setup utilities for automated testing across the project.
## parent
index: ./.pi-map.index.md
map: ./.pi-map.md
## children
- fixtures/sample-project
index: fixtures/sample-project/.pi-map.index.md
map: fixtures/sample-project/.pi-map.md
## files
## links
index: fixtures/.pi-map.index.md
map: fixtures/.pi-map.md
## workflows
-
## dirty
-
+18
View File
@@ -0,0 +1,18 @@
# fixtures
dir: fixtures
index: fixtures/.pi-map.index.md
## role
Provides test data and setup utilities for automated testing across the project.
## files
## arch
Simple static data fixtures with possible factory/helper patterns for consistent test state generation.
## tags
-
## symbols
-
## workflows
-
## dirty
-
-1
View File
@@ -1,4 +1,3 @@
dist/ dist/
node_modules/ node_modules/
.pi-map.md
.env .env
+30
View File
@@ -0,0 +1,30 @@
# fixtures/sample-project (index)
dir: fixtures/sample-project
## role
Provides a minimal sample Node.js/TypeScript project fixture for testing and demonstrating the pi-project-map functionality.
## parent
index: fixtures/.pi-map.index.md
map: fixtures/.pi-map.md
## children
- fixtures/sample-project/docs
index: fixtures/sample-project/docs/.pi-map.index.md
map: fixtures/sample-project/docs/.pi-map.md
- fixtures/sample-project/src
index: fixtures/sample-project/src/.pi-map.index.md
map: fixtures/sample-project/src/.pi-map.md
- fixtures/sample-project/tests
index: fixtures/sample-project/tests/.pi-map.index.md
map: fixtures/sample-project/tests/.pi-map.md
## files
- .gitignore
- README.md
- package.json
- tsconfig.json
## links
index: fixtures/sample-project/.pi-map.index.md
map: fixtures/sample-project/.pi-map.md
## workflows
-
## dirty
-
+22
View File
@@ -0,0 +1,22 @@
# fixtures/sample-project
dir: fixtures/sample-project
index: fixtures/sample-project/.pi-map.index.md
## role
Provides a minimal sample Node.js/TypeScript project fixture for testing and demonstrating the pi-project-map functionality.
## files
- .gitignore | Specifies files and directories for Git to ignore in version control | dep: git
- README.md | Provides a brief overview and directory structure for a small test project related to pi-project-map functionality.
- package.json | Defines a sample Node.js project configuration with TypeScript build and Vitest testing scripts. | dep: typescript, vitest
- tsconfig.json | Configures TypeScript compiler options for a Node.js project targeting ES2022 with strict type checking.
## arch
Standard Node.js project structure using TypeScript with strict compilation, Vitest for testing, and ES2022 module output.
## tags
project, typescript, git, readme, node, vitest, package, tsconfig
## symbols
-
## workflows
-
## dirty
-
@@ -0,0 +1,19 @@
# fixtures/sample-project/docs (index)
dir: fixtures/sample-project/docs
## role
Provides API documentation and usage examples for the sample project's user management functionality.
## parent
index: fixtures/sample-project/.pi-map.index.md
map: fixtures/sample-project/.pi-map.md
## children
-
## files
- API.md
## links
index: fixtures/sample-project/docs/.pi-map.index.md
map: fixtures/sample-project/docs/.pi-map.md
## workflows
-
## dirty
-
+19
View File
@@ -0,0 +1,19 @@
# fixtures/sample-project/docs
dir: fixtures/sample-project/docs
index: fixtures/sample-project/docs/.pi-map.index.md
## role
Provides API documentation and usage examples for the sample project's user management functionality.
## files
- API.md | Documents a user management API with user creation/serialization functions and validation utilities
## arch
Documentation-as-code pattern with markdown-based reference material for external API consumers.
## tags
api, user, documents, management, creation, serialization, validation, utilities
## symbols
-
## workflows
-
## dirty
-
@@ -0,0 +1,30 @@
# fixtures/sample-project/src (index)
dir: fixtures/sample-project/src
## role
Entry point module that demonstrates user creation, email validation, and logging for a sample project.
## parent
index: fixtures/sample-project/.pi-map.index.md
map: fixtures/sample-project/.pi-map.md
## children
- fixtures/sample-project/src/components
index: fixtures/sample-project/src/components/.pi-map.index.md
map: fixtures/sample-project/src/components/.pi-map.md
- fixtures/sample-project/src/models
index: fixtures/sample-project/src/models/.pi-map.index.md
map: fixtures/sample-project/src/models/.pi-map.md
- fixtures/sample-project/src/utils
index: fixtures/sample-project/src/utils/.pi-map.index.md
map: fixtures/sample-project/src/utils/.pi-map.md
## files
- index.ts
## links
index: fixtures/sample-project/src/.pi-map.index.md
map: fixtures/sample-project/src/.pi-map.md
## workflows
- change src behavior
read: index.ts
- explore src subdirectories
index: fixtures/sample-project/src/components/.pi-map.index.md, fixtures/sample-project/src/models/.pi-map.index.md, fixtures/sample-project/src/utils/.pi-map.index.md
## dirty
-
+26
View File
@@ -0,0 +1,26 @@
# fixtures/sample-project/src
dir: fixtures/sample-project/src
index: fixtures/sample-project/src/.pi-map.index.md
## role
Entry point module that demonstrates user creation, email validation, and logging for a sample project.
## files
- index.ts | Entry point that creates a user, validates their email, and logs the result | exp: func:main(), call:createUser, call:validateEmail, call:logger.error, call:logger.info | dep: ./models/user.js, ./utils/validation.js, ./utils/logger.js
## arch
Simple procedural script with direct function calls and sequential execution pattern.
## tags
user, email, js, main, call:create, call:validate, call:logger.error, call:logger.info
## symbols
- main
- call:createUser
- call:validateEmail
- call:logger.error
- call:logger.info
## workflows
- change src behavior
read: index.ts
- explore src subdirectories
index: fixtures/sample-project/src/components/.pi-map.index.md, fixtures/sample-project/src/models/.pi-map.index.md, fixtures/sample-project/src/utils/.pi-map.index.md
## dirty
-
@@ -0,0 +1,21 @@
# fixtures/sample-project/src/components (index)
dir: fixtures/sample-project/src/components
## role
Provides reusable UI components for building the application's interface.
## parent
index: fixtures/sample-project/src/.pi-map.index.md
map: fixtures/sample-project/src/.pi-map.md
## children
-
## files
- Button.tsx
- UserCard.tsx
## links
index: fixtures/sample-project/src/components/.pi-map.index.md
map: fixtures/sample-project/src/components/.pi-map.md
## workflows
- change components behavior
read: Button.tsx, UserCard.tsx
## dirty
-
@@ -0,0 +1,26 @@
# fixtures/sample-project/src/components
dir: fixtures/sample-project/src/components
index: fixtures/sample-project/src/components/.pi-map.index.md
## role
Provides reusable UI components for building the application's interface.
## files
- Button.tsx | A reusable React button component that renders a styled button with configurable label, visual variant, click handler, and disabled state. | exp: ButtonProps, func:Button({ label, variant = "primary", onClick, disabled = false, }: ButtonProps) → JSX.Element | dep: react, React
- UserCard.tsx | Renders a user card component with optional edit and delete action buttons. | exp: UserCardProps, func:UserCard({ user, onEdit, onDelete }: UserCardProps) → JSX.Element, call:onEdit, call:onDelete | dep: react, ../models/user.js, React
## arch
Simple functional React components with props-based configuration, following a basic presentational component pattern.
## tags
button, user, card, react, props, call:on, edit, delete
## symbols
- Button
- UserCard
- ButtonProps
- UserCardProps
- call:onEdit
- call:onDelete
## workflows
- change components behavior
read: Button.tsx, UserCard.tsx
## dirty
-
@@ -0,0 +1,20 @@
# fixtures/sample-project/src/models (index)
dir: fixtures/sample-project/src/models
## role
Defines the User data model with creation and serialization capabilities including email validation.
## parent
index: fixtures/sample-project/src/.pi-map.index.md
map: fixtures/sample-project/src/.pi-map.md
## children
-
## files
- user.ts
## links
index: fixtures/sample-project/src/models/.pi-map.index.md
map: fixtures/sample-project/src/models/.pi-map.md
## workflows
- change models behavior
read: user.ts
## dirty
-
@@ -0,0 +1,26 @@
# fixtures/sample-project/src/models
dir: fixtures/sample-project/src/models
index: fixtures/sample-project/src/models/.pi-map.index.md
## role
Defines the User data model with creation and serialization capabilities including email validation.
## files
- user.ts | Defines a User interface and provides functions to create and serialize users with email validation. | exp: User, func:createUser(data: Omit<User, "id" | "createdAt">) → User, call:validateEmail, call:crypto.randomUUID, raise:Error, func:serializeUser(user: User) → string, call:JSON.stringify | dep: ../utils/validation.js
## arch
Domain model pattern with interface-based typing, pure functions for data transformation, and embedded validation logic.
## tags
user, create, serialize, email, validation, call:validate, call:crypto.random, uuid
## symbols
- createUser
- serializeUser
- User
- call:validateEmail
- call:crypto.randomUUID
- raise:Error
- call:JSON.stringify
## workflows
- change models behavior
read: user.ts
## dirty
-
@@ -0,0 +1,21 @@
# fixtures/sample-project/src/utils (index)
dir: fixtures/sample-project/src/utils
## role
Provides foundational cross-cutting utility functions for logging and input validation used throughout the application.
## parent
index: fixtures/sample-project/src/.pi-map.index.md
map: fixtures/sample-project/src/.pi-map.md
## children
-
## files
- logger.ts
- validation.ts
## links
index: fixtures/sample-project/src/utils/.pi-map.index.md
map: fixtures/sample-project/src/utils/.pi-map.md
## workflows
- change utils behavior
read: logger.ts, validation.ts
## dirty
-
@@ -0,0 +1,28 @@
# fixtures/sample-project/src/utils
dir: fixtures/sample-project/src/utils
index: fixtures/sample-project/src/utils/.pi-map.index.md
## role
Provides foundational cross-cutting utility functions for logging and input validation used throughout the application.
## files
- logger.ts | Provides a simple typed console logger with timestamps and convenience wrappers for different log levels. | exp: LogLevel, func:log(level: LogLevel, message: string) → void, call:new Date().toISOString, call:console.log, call:level.toUpperCase, func:debug(message: string) → void, call:log, func:info(message: string) → void, call:log, func:warn(message: string) → void, call:log, func:error(message: string) → void, call:log
- validation.ts | Provides basic string validation utilities for emails, non-empty checks, and minimum length requirements. | exp: func:validateEmail(email: string) → boolean, call:EMAIL_REGEX.test, func:validateNotEmpty(value: string) → boolean, call:value.trim, func:validateMinLength(value: string, min: number) → boolean
## arch
Flat utility module pattern with pure functions, no dependencies between modules, each exporting independent typed helper functions.
## tags
call:log, validate, log, logger, validation, empty, length, provides
## symbols
- log
- debug
- info
- warn
- error
- validateEmail
- validateNotEmpty
- validateMinLength
## workflows
- change utils behavior
read: logger.ts, validation.ts
## dirty
-
@@ -0,0 +1,21 @@
# fixtures/sample-project/tests (index)
dir: fixtures/sample-project/tests
## role
Provides unit test coverage for user model behavior and string validation utilities in the sample project.
## parent
index: fixtures/sample-project/.pi-map.index.md
map: fixtures/sample-project/.pi-map.md
## children
-
## files
- user.test.ts
- validation.test.ts
## links
index: fixtures/sample-project/tests/.pi-map.index.md
map: fixtures/sample-project/tests/.pi-map.md
## workflows
- update tests tests
read: user.test.ts, validation.test.ts
## dirty
-
+21
View File
@@ -0,0 +1,21 @@
# fixtures/sample-project/tests
dir: fixtures/sample-project/tests
index: fixtures/sample-project/tests/.pi-map.index.md
## role
Provides unit test coverage for user model behavior and string validation utilities in the sample project.
## files
- user.test.ts | Unit tests for user model creation, validation, and serialization | dep: vitest, ../src/models/user.js
- validation.test.ts | Unit tests for string validation utility functions | dep: vitest, ../src/utils/validation.js
## arch
Standard test suite using isolated unit tests with file-based grouping by domain concern (model vs. utility).
## tags
validation, unit, tests, user, vitest, src, js, user.test
## symbols
-
## workflows
- update tests tests
read: user.test.ts, validation.test.ts
## dirty
-
-11
View File
@@ -1,11 +0,0 @@
// Pi skill prompt hook
// Injected into every prompt to remind the agent to maintain .pi-map.md files
export const MAINTENANCE_INSTRUCTION = `
If you modify any source file, run \`project-map:patch <file-path>\` to update the analysis.
If you suspect staleness, run \`project-map:validate\`.
`;
export function injectPrompt(originalPrompt: string): string {
return `${originalPrompt}\n\n---\n${MAINTENANCE_INSTRUCTION}`;
}
+22
View File
@@ -0,0 +1,22 @@
# openspec (index)
dir: openspec
## role
Defines configuration and project conventions for a TypeScript CLI tool that generates AI-oriented codebase orientation maps for coding agents.
## parent
index: ./.pi-map.index.md
map: ./.pi-map.md
## children
- openspec/changes
index: openspec/changes/.pi-map.index.md
map: openspec/changes/.pi-map.md
## files
- config.yaml
- project.md
## links
index: openspec/.pi-map.index.md
map: openspec/.pi-map.md
## workflows
-
## dirty
-
+20
View File
@@ -0,0 +1,20 @@
# openspec
dir: openspec
index: openspec/.pi-map.index.md
## role
Defines configuration and project conventions for a TypeScript CLI tool that generates AI-oriented codebase orientation maps for coding agents.
## files
- config.yaml | Defines project configuration, stack metadata, and software-driven development (SDD) workflow rules for a TypeScript-based CLI tool that generates AI-oriented codebase map files. | dep: TypeScript, Node.js, Vitest, npm, openspec
- project.md | Defines project context and conventions for pi-project-map, a Pi skill and CLI tool that generates hierarchical `.pi-map.md` orientation files for coding agents.
## arch
YAML-driven configuration with software-driven development (SDD) workflow rules, hierarchical markdown output generation, and Pi skill integration for AI agent context provision.
## tags
project, map, defines, typescript, cli, tool, generates, config
## symbols
-
## workflows
-
## dirty
-
+20
View File
@@ -0,0 +1,20 @@
# openspec/changes (index)
dir: openspec/changes
## role
Manages change tracking, versioning, and audit history for OpenAPI specification modifications
## parent
index: openspec/.pi-map.index.md
map: openspec/.pi-map.md
## children
- openspec/changes/archive
index: openspec/changes/archive/.pi-map.index.md
map: openspec/changes/archive/.pi-map.md
## files
## links
index: openspec/changes/.pi-map.index.md
map: openspec/changes/.pi-map.md
## workflows
-
## dirty
-
+18
View File
@@ -0,0 +1,18 @@
# openspec/changes
dir: openspec/changes
index: openspec/changes/.pi-map.index.md
## role
Manages change tracking, versioning, and audit history for OpenAPI specification modifications
## files
## arch
Event-sourced or changelog-based pattern with immutable change records and versioned snapshots
## tags
-
## symbols
-
## workflows
-
## dirty
-
+26
View File
@@ -0,0 +1,26 @@
# openspec/changes/archive (index)
dir: openspec/changes/archive
## role
Provides persistent storage and retrieval of historical change records in an archived format for audit trails and long-term data retention.
## parent
index: openspec/changes/.pi-map.index.md
map: openspec/changes/.pi-map.md
## children
- openspec/changes/archive/2026-06-11-layered-map-protocol
index: openspec/changes/archive/2026-06-11-layered-map-protocol/.pi-map.index.md
map: openspec/changes/archive/2026-06-11-layered-map-protocol/.pi-map.md
- openspec/changes/archive/2026-06-11-map-context-retrieval
index: openspec/changes/archive/2026-06-11-map-context-retrieval/.pi-map.index.md
map: openspec/changes/archive/2026-06-11-map-context-retrieval/.pi-map.md
- openspec/changes/archive/2026-06-11-project-map-prompt-injection
index: openspec/changes/archive/2026-06-11-project-map-prompt-injection/.pi-map.index.md
map: openspec/changes/archive/2026-06-11-project-map-prompt-injection/.pi-map.md
## files
## links
index: openspec/changes/archive/.pi-map.index.md
map: openspec/changes/archive/.pi-map.md
## workflows
-
## dirty
-
+18
View File
@@ -0,0 +1,18 @@
# openspec/changes/archive
dir: openspec/changes/archive
index: openspec/changes/archive/.pi-map.index.md
## role
Provides persistent storage and retrieval of historical change records in an archived format for audit trails and long-term data retention.
## files
## arch
Simple archive storage pattern using file-based serialization with read/write operations for immutable change log records, likely with date-based or sequential naming conventions.
## tags
-
## symbols
-
## workflows
-
## dirty
-
@@ -0,0 +1,26 @@
# openspec/changes/archive/2026-06-11-layered-map-protocol (index)
dir: openspec/changes/archive/2026-06-11-layered-map-protocol
## role
Contains archived specification documents for a deprecated layered map protocol that introduced paired navigation artifacts to replace bulk-loading of map files with a tiered directory-level routing system.
## parent
index: openspec/changes/archive/.pi-map.index.md
map: openspec/changes/archive/.pi-map.md
## children
-
## files
- apply-progress.md
- archive-report.md
- design.md
- proposal.md
- spec.md
- sync-report.md
- tasks.md
- verify-report.md
## links
index: openspec/changes/archive/2026-06-11-layered-map-protocol/.pi-map.index.md
map: openspec/changes/archive/2026-06-11-layered-map-protocol/.pi-map.md
## workflows
-
## dirty
-
@@ -0,0 +1,26 @@
# openspec/changes/archive/2026-06-11-layered-map-protocol
dir: openspec/changes/archive/2026-06-11-layered-map-protocol
index: openspec/changes/archive/2026-06-11-layered-map-protocol/.pi-map.index.md
## role
Contains archived specification documents for a deprecated layered map protocol that introduced paired navigation artifacts to replace bulk-loading of map files with a tiered directory-level routing system.
## files
- apply-progress.md | Documents the completion status and summary of implemented features for an "Apply Progress" project or milestone.
- archive-report.md | Documents the archival status and metadata for a deprecated layered map protocol specification directory.
- design.md | Design document for a paired navigation-first artifact model that generates both routing indexes and orientation rich-maps from a shared intermediate directory model while preserving existing pipeline behavior. | dep: spec.md, src/format.ts, src/init.ts, src/patch.ts, src/validate.ts, pi-extension.ts, README.md, SKILL.md, design-doc.md, CLI argument parsing, YAML config handling
- proposal.md | Proposes a layered navigation protocol using paired index/map artifacts to replace bulk-loading of map files with a tiered, directory-level routing system.
- spec.md | Specifies a layered navigation protocol for project maps using paired index and rich map artifacts per directory with defined generation, patching, and validation behaviors.
- sync-report.md | Documents that a canonical spec synchronization was not performed due to legacy flat change artifact structure, with user-approved archival fallback
- tasks.md | Defines a phased task plan for implementing a layered paired-map protocol with directory-level `.pi-map.md` and `.pi-map.index.md` artifacts, routing metadata, patch sizing, validation, and documentation. | dep: design.md, pi-extension.ts, SKILL.md, README.md, design-doc.md, npm/node toolchain
- verify-report.md | Documents verification results for a code change implementing a layered map protocol with paired artifacts. | dep: npm, vitest, node, typescript
## arch
Document-driven specification archive using a layered architecture with paired index/map artifacts per directory, phased implementation tasks, and formal verification/synchronization reporting, preserved for historical reference despite legacy flat structure preventing canonical sync.
## tags
map, md, report, design, ts, layered, protocol, directory
## symbols
-
## workflows
-
## dirty
-
@@ -0,0 +1,26 @@
# openspec/changes/archive/2026-06-11-map-context-retrieval (index)
dir: openspec/changes/archive/2026-06-11-map-context-retrieval
## role
Archives a completed specification change package for implementing metadata-driven context retrieval capabilities in a project-map tool.
## parent
index: openspec/changes/archive/.pi-map.index.md
map: openspec/changes/archive/.pi-map.md
## children
-
## files
- apply-progress.md
- archive-report.md
- design.md
- proposal.md
- spec.md
- sync-report.md
- tasks.md
- verify-report.md
## links
index: openspec/changes/archive/2026-06-11-map-context-retrieval/.pi-map.index.md
map: openspec/changes/archive/2026-06-11-map-context-retrieval/.pi-map.md
## workflows
-
## dirty
-
@@ -0,0 +1,26 @@
# openspec/changes/archive/2026-06-11-map-context-retrieval
dir: openspec/changes/archive/2026-06-11-map-context-retrieval
index: openspec/changes/archive/2026-06-11-map-context-retrieval/.pi-map.index.md
## role
Archives a completed specification change package for implementing metadata-driven context retrieval capabilities in a project-map tool.
## files
- apply-progress.md | Documents the completion status of a project implementing deterministic index-first context retrieval via tool and CLI, along with retrieval documentation and skill guidance.
- archive-report.md | Documents the archival of a set of specification change documents including metadata about archive location, mode, and preserved inputs.
- design.md | Design document for a lightweight context retrieval system that scans paired project-map metadata to find and return relevant code context as a markdown bundle for AI agents. | dep: pi-extension.ts, src/index.ts, src/cli/*, paired-artifact parser/model from layered protocol, .pi-map.index.md, .pi-map.md
- proposal.md | Proposes a "Map Context Retrieval" tool that enables natural-language queries to return compact, metadata-driven context bundles from a layered map protocol. | dep: layered-map-protocol, Pi tool, CLI
- spec.md | Defines a specification for adding a retrieval-oriented `context` command to a project-map tool that converts user tasks into compact routing bundles for LLM agents | dep: layered-map-protocol, project-map (Pi tool/CLI), markdown output formatting
- sync-report.md | Documents that a canonical spec synchronization was not performed due to legacy flat change artifact structure, with user-approved archival fallback
- tasks.md | Defines implementation tasks for adding map context retrieval functionality to a Pi tool, using paired index/map metadata for ranked, query-based context retrieval. | dep: design.md, Pi tool, project-map context, npm (typecheck, test, lint)
- verify-report.md | Documents verification results for a code change implementing deterministic index-first context retrieval. | dep: npm, vitest, node, CLI tooling
## arch
Flat archival directory structure preserving legacy change artifacts (specification, design, proposal, tasks, verification, sync, apply-progress, archive-report) without hierarchical organization.
## tags
map, context, retrieval, report, documents, project, index, tool
## symbols
-
## workflows
-
## dirty
-
@@ -0,0 +1,26 @@
# openspec/changes/archive/2026-06-11-project-map-prompt-injection (index)
dir: openspec/changes/archive/2026-06-11-project-map-prompt-injection
## role
Archives a completed prompt-injection feature specification that enables runtime injection of project-map artifacts into LLM context with configurable guidance modes and budget controls.
## parent
index: openspec/changes/archive/.pi-map.index.md
map: openspec/changes/archive/.pi-map.md
## children
-
## files
- apply-progress.md
- archive-report.md
- design.md
- proposal.md
- spec.md
- sync-report.md
- tasks.md
- verify-report.md
## links
index: openspec/changes/archive/2026-06-11-project-map-prompt-injection/.pi-map.index.md
map: openspec/changes/archive/2026-06-11-project-map-prompt-injection/.pi-map.md
## workflows
-
## dirty
-
@@ -0,0 +1,26 @@
# openspec/changes/archive/2026-06-11-project-map-prompt-injection
dir: openspec/changes/archive/2026-06-11-project-map-prompt-injection
index: openspec/changes/archive/2026-06-11-project-map-prompt-injection/.pi-map.index.md
## role
Archives a completed prompt-injection feature specification that enables runtime injection of project-map artifacts into LLM context with configurable guidance modes and budget controls.
## files
- apply-progress.md | Documents the completion status and implementation details of a prompt-injection feature delivered across five incremental slices.
- archive-report.md | Documents the archival status and metadata of a deprecated project change specification directory.
- design.md | Design document for adding a runtime prompt injection layer that guides LLM behavior using paired map/index artifacts through configurable modes, canonical markers, and budgeted context expansion. | dep: pi-extension.ts, src/config.ts, spec.md, event.messages, before_agent_start, before_provider_request, context hooks, .pi-project-map.json, .pi-map.index.md, .pi-map.md
- proposal.md | Proposes a runtime prompt-injection policy for project-map artifacts with configurable guidance modes, hybrid context budgets, and reinjection avoidance
- spec.md | Defines a specification for automatic runtime prompt injection of project map/index artifacts with configurable guidance modes, budgeted expansion, and reinjection avoidance based on actual outgoing context scanning.
- sync-report.md | Documents that a canonical spec synchronization was not performed due to legacy flat change artifact structure, with user-approved archival fallback
- tasks.md | Defines phased implementation tasks for a prompt injection policy system that controls how project map artifacts are injected into LLM context with budget constraints, mode semantics, and reinjection avoidance.
- verify-report.md | Documents verification results for a prompt-injection security feature implementation in a software project.
## arch
Document-driven specification architecture using phased slice-based delivery (proposal → design → spec → tasks → verification), with flat artifact structure and canonical marker-based context expansion patterns.
## tags
map, prompt, injection, project, report, documents, artifacts, context
## symbols
-
## workflows
-
## dirty
-
+49 -11
View File
@@ -81,6 +81,25 @@ function renderProgressBar(
return `[${bar}] ${completed}/${total}${file}`; return `[${bar}] ${completed}/${total}${file}`;
} }
const HINT_CUSTOM_TYPE = "pi-project-map-hint";
function hintAlreadyInContext(ctx: any): boolean {
const manager = ctx?.sessionManager;
if (!manager || typeof manager.buildSessionContext !== "function") {
return false;
}
const { messages } = manager.buildSessionContext();
if (!Array.isArray(messages)) return false;
return messages.some(
(m: any) =>
m &&
m.role === "custom" &&
m.customType === HINT_CUSTOM_TYPE,
);
}
export default function (pi: ExtensionAPI) { export default function (pi: ExtensionAPI) {
let lastRootPairMtimes: import("./src/index.js").RootPairMtimes = {}; let lastRootPairMtimes: import("./src/index.js").RootPairMtimes = {};
pi.registerTool({ pi.registerTool({
@@ -190,11 +209,12 @@ export default function (pi: ExtensionAPI) {
name: "project_map_validate", name: "project_map_validate",
label: "Project Map Validate", label: "Project Map Validate",
description: description:
"Check all .pi-map.md / .pi-map.index.md files for staleness and discrepancies", "Check all .pi-map.md / .pi-map.index.md files for staleness and discrepancies. Optionally repair them.",
promptSnippet: "Validate paired project map/index artifacts for accuracy", promptSnippet: "Validate paired project map/index artifacts for accuracy",
promptGuidelines: [ promptGuidelines: [
"Use project_map_validate before making architectural decisions if you suspect stale data", "Use project_map_validate before making architectural decisions if you suspect stale data",
"Use project_map_validate to detect files that were deleted or added outside the agent", "Use project_map_validate to detect files that were deleted or added outside the agent",
"Set fix=true to repair localized discrepancies without running a full project_map_reinit",
], ],
parameters: Type.Object({ parameters: Type.Object({
path: Type.Optional( path: Type.Optional(
@@ -202,13 +222,23 @@ export default function (pi: ExtensionAPI) {
description: "Project root path (default: current directory)", description: "Project root path (default: current directory)",
}), }),
), ),
fix: Type.Optional(
Type.Boolean({
description:
"Repair discrepancies automatically (default: false). Requires an LLM client.",
default: false,
}),
),
}), }),
async execute(_toolCallId, params, _signal, _onUpdate, ctx) { async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
try { try {
const targetPath = params.path || ctx.cwd; const targetPath = params.path || ctx.cwd;
const client = params.fix ? getPiLLMClient(ctx) : undefined;
const result = await validateMaps(targetPath, { const result = await validateMaps(targetPath, {
fix: false, fix: params.fix ?? false,
verbose: false, verbose: false,
llmClient: client,
cacheDir: ctx.cwd,
}); });
const text = result.clean const text = result.clean
? "All .pi-map.md files are clean." ? "All .pi-map.md files are clean."
@@ -234,12 +264,14 @@ export default function (pi: ExtensionAPI) {
name: "project_map_reinit", name: "project_map_reinit",
label: "Project Map Reinit", label: "Project Map Reinit",
description: description:
"Force full regeneration of all .pi-map.md / .pi-map.index.md artifacts", "Regenerate .pi-map.md / .pi-map.index.md artifacts for a subtree plus its ancestors, falling back to full regeneration only when the subtree covers more than the configured percentage of project files (default: 10%)",
promptSnippet: promptSnippet:
"Force full regeneration of paired project map/index artifacts", "Regenerate paired project map/index artifacts for a subtree or the whole project",
promptGuidelines: [ promptGuidelines: [
"Use project_map_reinit when validation shows widespread staleness", "Use project_map_reinit only after project_map_patch and project_map_validate --fix cannot resolve the staleness",
"Use project_map_reinit after pulling major changes from version control", "For localized changes, prefer project_map_patch <changed-file> or project_map_validate with fix=true",
"Use project_map_reinit for widespread structural damage (e.g. broken links across many directories) or after large merges",
"When reinit falls back to full regeneration, it is because the target subtree covers more than the configured reinitFullThresholdPercent of project files",
], ],
parameters: Type.Object({ parameters: Type.Object({
path: Type.Optional( path: Type.Optional(
@@ -345,13 +377,15 @@ export default function (pi: ExtensionAPI) {
if (dirtyFiles.length > 0) { if (dirtyFiles.length > 0) {
ctx.ui.notify( ctx.ui.notify(
`pi-project-map: ${dirtyFiles.length} dirty packages detected. Run project_map_validate or project_map_reinit.`, `pi-project-map: ${dirtyFiles.length} dirty package(s) detected. Run project_map_validate first; use project_map_reinit only if staleness is widespread.`,
"warning", "warning",
); );
} }
}); });
// Inject maintenance instructions before agent starts // Inject maintenance instructions before agent starts, but only once
// within the current branch of context. Re-inject after compaction or
// tree navigation removes the hint from the active path.
pi.on("before_agent_start", async (_event, _ctx) => { pi.on("before_agent_start", async (_event, _ctx) => {
const config = loadConfig(_ctx.cwd); const config = loadConfig(_ctx.cwd);
const mapFiles = findPiMapFiles(_ctx.cwd); const mapFiles = findPiMapFiles(_ctx.cwd);
@@ -366,9 +400,10 @@ export default function (pi: ExtensionAPI) {
if (!modeAllowsPreInitHint(config.promptInjectionMode)) { if (!modeAllowsPreInitHint(config.promptInjectionMode)) {
return {}; return {};
} }
if (hintAlreadyInContext(_ctx)) return {};
return { return {
message: { message: {
customType: "pi-project-map-hint", customType: HINT_CUSTOM_TYPE,
content: buildPreInitHint(), content: buildPreInitHint(),
display: true, display: true,
}, },
@@ -378,9 +413,10 @@ export default function (pi: ExtensionAPI) {
// Slice 4: advisory mode shows a visible lightweight reminder after init. // Slice 4: advisory mode shows a visible lightweight reminder after init.
// No root-pair preload, no per-turn reinjection. // No root-pair preload, no per-turn reinjection.
if (config.promptInjectionMode === "advisory") { if (config.promptInjectionMode === "advisory") {
if (hintAlreadyInContext(_ctx)) return {};
return { return {
message: { message: {
customType: "pi-project-map-hint", customType: HINT_CUSTOM_TYPE,
content: buildAdvisoryReminder(), content: buildAdvisoryReminder(),
display: true, display: true,
}, },
@@ -414,12 +450,14 @@ export default function (pi: ExtensionAPI) {
return {}; return {};
} }
if (hintAlreadyInContext(_ctx)) return {};
// Slice 2: post-init root-pair preload + budgeted expansion // Slice 2: post-init root-pair preload + budgeted expansion
const contextWindow = discoverContextWindow(_ctx); const contextWindow = discoverContextWindow(_ctx);
const payload = buildInjectionPayload(_ctx.cwd, config, contextWindow); const payload = buildInjectionPayload(_ctx.cwd, config, contextWindow);
return { return {
message: { message: {
customType: "pi-project-map-hint", customType: HINT_CUSTOM_TYPE,
content: payload.content, content: payload.content,
display: payload.display, display: payload.display,
}, },
+49
View File
@@ -0,0 +1,49 @@
# src (index)
dir: src
## role
A project mapping and codebase navigation system that generates, maintains, and queries AI-readable documentation artifacts for software projects.
## parent
index: ./.pi-map.index.md
map: ./.pi-map.md
## children
- src/ast
index: src/ast/.pi-map.index.md
map: src/ast/.pi-map.md
- src/cli
index: src/cli/.pi-map.index.md
map: src/cli/.pi-map.md
- src/llm
index: src/llm/.pi-map.index.md
map: src/llm/.pi-map.md
- src/types
index: src/types/.pi-map.index.md
map: src/types/.pi-map.md
## files
- cli.ts
- config.ts
- directory-model.ts
- discover.ts
- format.ts
- index.ts
- init.ts
- merge.ts
- patch.ts
- prompt-injection.ts
- retrieve.ts
- routing-metadata.ts
- validate.ts
## links
index: src/.pi-map.index.md
map: src/.pi-map.md
## workflows
- change src behavior
read: cli.ts, config.ts, directory-model.ts
- change src CLI
read: cli.ts
- change src config
read: config.ts
- explore src subdirectories
index: src/ast/.pi-map.index.md, src/cli/.pi-map.index.md, src/llm/.pi-map.index.md
## dirty
-
+45
View File
File diff suppressed because one or more lines are too long
+20
View File
@@ -0,0 +1,20 @@
# src/ast (index)
dir: src/ast
## role
Extracts structured metadata from source code ASTs across multiple languages to enable code analysis and dependency understanding.
## parent
index: src/.pi-map.index.md
map: src/.pi-map.md
## children
-
## files
- ast-extract.ts
## links
index: src/ast/.pi-map.index.md
map: src/ast/.pi-map.md
## workflows
- change ast behavior
read: ast-extract.ts
## dirty
-
+27
View File
@@ -0,0 +1,27 @@
# src/ast
dir: src/ast
index: src/ast/.pi-map.index.md
## role
Extracts structured metadata from source code ASTs across multiple languages to enable code analysis and dependency understanding.
## files
- ast-extract.ts | Extracts AST-based metadata (exports, dependencies, classes, functions, method calls, and exceptions) from source code files across multiple languages using Tree-sitter parsers. | exp: ASTFileData, func:extractFileAST(filePath: string) → Promise<ASTFileData | null>, call:extname(filePath).toLowerCase, call:require, call:parser.setLanguage, call:readFileSync, call:parser.parse, call:extractPythonData, call:extractTypeScriptData, call:extractGoData, call:extractExportsFromTree, call:extractDepsFromTree | dep: fs, path, tree-sitter, tree-sitter-typescript, tree-sitter-python, tree-sitter-go, tree-sitter-rust, tree-sitter-java, tree-sitter-c, tree-sitter-cpp, tree-sitter-ruby
## arch
Language-agnostic parser abstraction using Tree-sitter grammars with unified extraction pipeline for cross-language code analysis.
## tags
tree, sitter, call:extract, data, ast, extract, python, go
## symbols
- extractFileAST
- ASTFileData
- call:extname(filePath).toLowerCase
- call:require
- call:parser.setLanguage
- call:readFileSync
- call:parser.parse
- call:extractPythonData
## workflows
- change ast behavior
read: ast-extract.ts
## dirty
-
+22
View File
@@ -0,0 +1,22 @@
# src/cli (index)
dir: src/cli
## role
Command-line interface entry point for a project mapping tool that manages hierarchical `.pi-map.md` files through generation, patching, validation, and LLM-powered context retrieval.
## parent
index: src/.pi-map.index.md
map: src/.pi-map.md
## children
-
## files
- cli.ts
## links
index: src/cli/.pi-map.index.md
map: src/cli/.pi-map.md
## workflows
- change cli behavior
read: cli.ts
- change cli CLI
read: cli.ts
## dirty
-
+22
View File
@@ -0,0 +1,22 @@
# src/cli
dir: src/cli
index: src/cli/.pi-map.index.md
## role
Command-line interface entry point for a project mapping tool that manages hierarchical `.pi-map.md` files through generation, patching, validation, and LLM-powered context retrieval.
## files
- cli.ts | CLI entry point for a project mapping tool that generates, patches, validates, and retrieves context from hierarchical `.pi-map.md` files using LLM-powered analysis. | dep: ../init.js, ../patch.js, ../validate.js, ../discover.js, ../retrieve.js, ../llm/llm-client.js, ../config.js, picocolors, process, fs (implied via require)
## arch
Single-file CLI facade with command routing to core engine services, likely using a command pattern or direct service delegation for map lifecycle operations.
## tags
js, cli, llm, entry, point, project, mapping, tool
## symbols
-
## workflows
- change cli behavior
read: cli.ts
- change cli CLI
read: cli.ts
## dirty
-
+2 -2
View File
@@ -28,7 +28,7 @@ function printUsage() {
` project-map ${pc.cyan("validate")} [--fix] [path] Check for stale/missing/orphaned entries`, ` project-map ${pc.cyan("validate")} [--fix] [path] Check for stale/missing/orphaned entries`,
); );
console.log( console.log(
` project-map ${pc.cyan("reinit")} [path] Force full regeneration`, ` project-map ${pc.cyan("reinit")} [path] Regenerate a subtree or the whole project`,
); );
console.log( console.log(
` project-map ${pc.cyan("--help")} Show this help message`, ` project-map ${pc.cyan("--help")} Show this help message`,
@@ -252,7 +252,7 @@ async function main() {
await reinitPath(targetPath, { await reinitPath(targetPath, {
verbose: false, verbose: false,
llmClient: client, llmClient: client,
cacheDir: targetPath, cacheDir: process.cwd(),
onProgress: (info) => { onProgress: (info) => {
const line = renderProgressBar( const line = renderProgressBar(
info.completed, info.completed,
+3
View File
@@ -16,6 +16,8 @@ export interface SkillConfig {
promptInjectionMode: PromptInjectionMode; promptInjectionMode: PromptInjectionMode;
contextBudgetPercent: number; contextBudgetPercent: number;
contextBudgetMaxTokens: number; contextBudgetMaxTokens: number;
/** Percentage of project files a target subtree must cover before reinit falls back to full regeneration (default: 10). */
reinitFullThresholdPercent: number;
} }
export const DEFAULT_CONFIG: SkillConfig = { export const DEFAULT_CONFIG: SkillConfig = {
@@ -51,6 +53,7 @@ export const DEFAULT_CONFIG: SkillConfig = {
promptInjectionMode: "strong", promptInjectionMode: "strong",
contextBudgetPercent: 15, contextBudgetPercent: 15,
contextBudgetMaxTokens: 100_000, contextBudgetMaxTokens: 100_000,
reinitFullThresholdPercent: 10,
}; };
export function loadConfig(cwd: string = process.cwd()): SkillConfig { export function loadConfig(cwd: string = process.cwd()): SkillConfig {
+131 -4
View File
@@ -7,8 +7,8 @@ import { extractFileLLM, extractPackageLLM } from "./llm/llm-extract.js";
import { extractFileAST } from "./ast/ast-extract.js"; import { extractFileAST } from "./ast/ast-extract.js";
import { mergeFileData } from "./merge.js"; import { mergeFileData } from "./merge.js";
import { processFiles } from "./llm/llm-batch.js"; import { processFiles } from "./llm/llm-batch.js";
import { writeFileSync } from "fs"; import { existsSync, writeFileSync } from "fs";
import { join } from "path"; import { join, relative, resolve } from "path";
import type { LLMClient } from "./llm/llm-client.js"; import type { LLMClient } from "./llm/llm-client.js";
import { import {
createDirectoryModel, createDirectoryModel,
@@ -105,6 +105,60 @@ export interface DirectoryContext {
isRoot: boolean; isRoot: boolean;
} }
export function getAncestorEntries(
entries: DirectoryEntry[],
ctx: DirectoryContext,
entry: DirectoryEntry,
): DirectoryEntry[] {
const result: DirectoryEntry[] = [];
let parent = ctx.parentMap.get(entry.relativePath);
while (parent) {
const ancestor = entries.find(
(candidate) => candidate.relativePath === parent,
);
if (ancestor) {
result.push(ancestor);
}
parent = ctx.parentMap.get(parent);
}
return result;
}
function countFiles(entries: DirectoryEntry[]): number {
return entries.reduce((sum, e) => sum + e.files.length, 0);
}
function isSubdirectory(parent: string, child: string): boolean {
if (parent === ".") return true;
if (child === parent) return true;
return child.startsWith(`${parent}/`);
}
function findProjectRoot(targetPath: string): string {
let current = resolve(targetPath);
while (true) {
if (existsSync(join(current, ".pi-project-map.json"))) {
return current;
}
if (existsSync(join(current, ".git"))) {
return current;
}
if (existsSync(join(current, "package.json"))) {
return current;
}
const parent = resolve(current, "..");
if (parent === current) {
return resolve(targetPath);
}
current = parent;
}
}
function normalizeRelativePath(relPath: string): string {
if (!relPath || relPath === ".") return ".";
return relPath.replace(/\\/g, "/");
}
export type ArtifactWriteMode = "both" | "map" | "index"; export type ArtifactWriteMode = "both" | "map" | "index";
export function buildDirectoryContext( export function buildDirectoryContext(
@@ -244,8 +298,81 @@ export async function reinitPath(
path: string, path: string,
options: InitOptions = {}, options: InitOptions = {},
): Promise<void> { ): Promise<void> {
// Full regeneration clears all dirty markers by overwriting every map/index pair const targetPath = resolve(path);
await initProject(path, options); const rootPath = findProjectRoot(targetPath);
const targetRelPath =
rootPath === targetPath
? "."
: normalizeRelativePath(relative(rootPath, targetPath));
const entries = discoverProject(rootPath);
const config = loadConfig(rootPath);
const threshold = config.reinitFullThresholdPercent;
const totalFiles = countFiles(entries);
if (totalFiles === 0 || targetRelPath === ".") {
await initProject(rootPath, options);
return;
}
const subtreeEntries = entries.filter((entry) =>
isSubdirectory(targetRelPath, entry.relativePath),
);
const subtreeFiles = countFiles(subtreeEntries);
const percentage = (subtreeFiles / totalFiles) * 100;
if (percentage > threshold) {
await initProject(rootPath, options);
return;
}
const routingOpts: RoutingMetadataOptions = {
tagCap: options.tagCap ?? config.tagCap,
workflowHintCap: options.workflowHintCap ?? config.workflowHintCap,
};
const targetEntry = entries.find((e) => e.relativePath === targetRelPath);
if (!targetEntry) {
await initProject(rootPath, options);
return;
}
const changedCtx = buildDirectoryContext(entries, targetEntry);
const ancestors = getAncestorEntries(entries, changedCtx, targetEntry);
const dirsToRegenerate = new Set<DirectoryEntry>([
...subtreeEntries,
...ancestors,
]);
const dirs = Array.from(dirsToRegenerate);
const filesToRegenerate = countFiles(dirs);
let completedFiles = 0;
for (const entry of dirs) {
const ctx = buildDirectoryContext(entries, entry);
await generateDirectoryArtifacts(
entry,
ctx,
options.llmClient,
options.cacheDir,
(info) => {
options.onProgress?.({
...info,
completed: completedFiles + info.completed,
total: filesToRegenerate,
dir: entry.relativePath,
});
},
routingOpts,
"both",
);
completedFiles += entry.files.length;
}
if (options.verbose !== false) {
console.log(
`Smart reinit: regenerated ${dirsToRegenerate.size} directories under ${targetRelPath} (${subtreeFiles}/${totalFiles} files, ${percentage.toFixed(1)}%).`,
);
}
} }
// Backward-compatible wrapper for patch/validate compatibility // Backward-compatible wrapper for patch/validate compatibility
+29
View File
@@ -0,0 +1,29 @@
# src/llm (index)
dir: src/llm
## role
Provides a unified abstraction layer for interacting with multiple LLM providers (OpenAI, Kimi, Pi) with caching, batching, and structured response extraction capabilities.
## parent
index: src/.pi-map.index.md
map: src/.pi-map.md
## children
-
## files
- external-llm-client.ts
- kimi-llm-client.ts
- llm-batch.ts
- llm-cache.ts
- llm-client.ts
- llm-error.ts
- llm-extract.ts
- pi-llm-client.ts
## links
index: src/llm/.pi-map.index.md
map: src/llm/.pi-map.md
## workflows
- change llm behavior
read: external-llm-client.ts, kimi-llm-client.ts, llm-batch.ts
- change llm CLI
read: external-llm-client.ts, kimi-llm-client.ts, llm-client.ts
## dirty
-
+36
View File
@@ -0,0 +1,36 @@
# src/llm
dir: src/llm
index: src/llm/.pi-map.index.md
## role
Provides a unified abstraction layer for interacting with multiple LLM providers (OpenAI, Kimi, Pi) with caching, batching, and structured response extraction capabilities.
## files
- external-llm-client.ts | Implements an LLM client adapter for OpenAI's API to send code analysis prompts and return structured responses. | exp: class:ExternalLLMClient, method:constructor(options: LLMClientOptions), raise:LLMError, method:complete(prompt: string) → Promise<string>, call:this.client.chat.completions.create, call:response.choices[0]?.message?.content?.trim, raise:LLMError | dep: openai, ./llm-error.js, ./llm-client.js
- kimi-llm-client.ts | Implements an LLM client for the Kimi.com API using an Anthropic-compatible HTTP interface. | exp: class:KimiLLMClient, method:constructor(options: LLMClientOptions), raise:LLMError, method:complete(prompt: string) → Promise<string>, call:fetch, call:JSON.stringify, call:response.text, call:response.json, call:data.content?.[0]?.text?.trim, raise:LLMError, raise:err | dep: ./llm-error.js, ./llm-client.js, llm-error.js, llm-client.js
- llm-batch.ts | Provides batched, concurrent file processing with retry logic and progress callbacks for LLM operations. | exp: BatchOptions, func:withRetry(fn: () => Promise<T>, options: Pick<BatchOptions, "maxRetries" | "retryDelaysMs">) → Promise<T>, call:fn, call:sleep, raise:lastError, func:processFiles(files: T[], processor: (file: T) => Promise<R>, options: BatchOptions, onProgress: (completed: number, total: number, currentFile: T) => void) → Promise<R[]>, call:pLimit, call:files.map, call:limit, call:sleep, call:withRetry, call:processor, call:onProgress, call:Promise.all | dep: p-limit, ./llm-error.js
- llm-cache.ts | Provides a persistent file-based caching system for LLM responses keyed by hash, storing results in JSON with atomic writes and automatic directory creation. | exp: func:getCached(hash: string, cacheDir: string) → string | undefined, call:getCachePath, call:loadCache, func:setCached(hash: string, result: string, cacheDir: string) → void, call:getCachePath, call:loadCache, call:Date.now, call:saveCache | dep: fs, path
- llm-client.ts | Factory for creating LLM client instances based on different provider modes (pi, openai, kimi). | exp: LLMClient, LLMClientOptions, func:createLLMClient(mode: "pi" | "openai" | "kimi", options: LLMClientOptions) → LLMClient | dep: ./llm-error.js, ./external-llm-client.js, ./kimi-llm-client.js, ./pi-llm-client.js, LLMError, ExternalLLMClient, KimiLLMClient, PiLLMClient
- llm-error.ts | Defines a custom error class for LLM-related errors with optional cause chaining | exp: class:LLMError, method:constructor(message: string, cause: unknown)
- llm-extract.ts | Extracts structured metadata (purpose, dependencies, concepts) from source files and packages using an LLM client, with binary detection, caching, and context window management. | exp: func:extractFileLLM(filePath: string, client: LLMClient, cacheDir: string) → Promise<LLMFileData>, call:isBinaryFile, call:readFileSync, call:createHash("sha256").update(content).digest, call:getCached, call:parseFileResponse, call:statSync, call:buildFilePrompt, call:truncateForContext, call:client.complete, call:setCached, raise:LLMError, func:extractPackageLLM(relativePath: string, fileData: { name: string; purpose: string }[], client: LLMClient, _cacheDir: string) → Promise<LLMPackageData>, call:buildPackagePrompt, call:client.complete, call:parsePackageResponse, call:basename, raise:LLMError | dep: fs, crypto, path, ./llm-client.js, ./llm-cache.js, ./llm-error.js
- pi-llm-client.ts | Implements an LLM client adapter that bridges to Pi's internal AI runtime using its built-in `complete()` function | exp: class:PiLLMClient, method:constructor(extensionContext: unknown), method:complete(prompt: string) → Promise<string>, call:ctx.modelRegistry?.get, call:ctx.modelRegistry?.getApiKeyAndHeaders, call:complete, call:Date.now, call:response.content .filter((c: any) => c.type === "text") .map((c: any) => c.text) .join("") .trim, raise:LLMError, raise:err | dep: ./llm-error.js, ./llm-client.js, @mariozechner/pi-ai
## arch
Adapter pattern for provider-specific LLM clients with a factory; decorator/wrapper pattern for cross-cutting concerns (caching, batching, retries, error handling); functional pipeline for file extraction with binary detection and context window management.
## tags
llm, client, js, raise:llmerror, cache, llmclient, error, constructor
## symbols
- ExternalLLMClient
- KimiLLMClient
- LLMError
- PiLLMClient
- constructor
- complete
- withRetry
- processFiles
## workflows
- change llm behavior
read: external-llm-client.ts, kimi-llm-client.ts, llm-batch.ts
- change llm CLI
read: external-llm-client.ts, kimi-llm-client.ts, llm-client.ts
## dirty
-
+1 -19
View File
@@ -5,6 +5,7 @@ import {
discoverProject, discoverProject,
generateDirectoryArtifacts, generateDirectoryArtifacts,
buildDirectoryContext, buildDirectoryContext,
getAncestorEntries,
} from "./init.js"; } from "./init.js";
import type { DirectoryEntry } from "./discover.js"; import type { DirectoryEntry } from "./discover.js";
import type { LLMClient } from "./llm/llm-client.js"; import type { LLMClient } from "./llm/llm-client.js";
@@ -133,25 +134,6 @@ function countDirectChildren(rootPath: string, relDir: string): number {
}).length; }).length;
} }
function getAncestorEntries(
entries: DirectoryEntry[],
ctx: ReturnType<typeof buildDirectoryContext>,
entry: DirectoryEntry,
): DirectoryEntry[] {
const result: DirectoryEntry[] = [];
let parent = ctx.parentMap.get(entry.relativePath);
while (parent) {
const ancestor = entries.find(
(candidate) => candidate.relativePath === parent,
);
if (ancestor) {
result.push(ancestor);
}
parent = ctx.parentMap.get(parent);
}
return result;
}
function normalizeRelativePath(relPath: string): string { function normalizeRelativePath(relPath: string): string {
if (!relPath || relPath === ".") return "."; if (!relPath || relPath === ".") return ".";
return relPath.replace(/\\/g, "/"); return relPath.replace(/\\/g, "/");
+1 -1
View File
@@ -291,6 +291,6 @@ function renderNoResultsBundle(query: string): string {
"-", "-",
"", "",
"## instructions", "## instructions",
"No relevant directories found. Try rephrasing the query or run `project_map_reinit` if artifacts are stale.", "No relevant directories found. Try rephrasing the query, or run `project_map_validate` to check whether artifacts are stale.",
].join("\n"); ].join("\n");
} }
+20
View File
@@ -0,0 +1,20 @@
# src/types (index)
dir: src/types
## role
Provides TypeScript type declarations for the Pi AI module's LLM chat completion functionality within the Pi runtime.
## parent
index: src/.pi-map.index.md
map: src/.pi-map.md
## children
-
## files
- pi-ai.d.ts
## links
index: src/types/.pi-map.index.md
map: src/types/.pi-map.md
## workflows
- change types behavior
read: pi-ai.d.ts
## dirty
-
+20
View File
@@ -0,0 +1,20 @@
# src/types
dir: src/types
index: src/types/.pi-map.index.md
## role
Provides TypeScript type declarations for the Pi AI module's LLM chat completion functionality within the Pi runtime.
## files
- pi-ai.d.ts | TypeScript declaration file for the Pi AI module's `complete` function that provides LLM chat completions within the Pi runtime | exp: complete
## arch
Minimal declaration-only types package using ambient module declarations (.d.ts) to define external API interfaces without implementation.
## tags
complete, pi, ai.d, typescript, declaration, provides, llm, chat
## symbols
- complete
## workflows
- change types behavior
read: pi-ai.d.ts
## dirty
-
+37
View File
@@ -0,0 +1,37 @@
# tests (index)
dir: tests
## role
Comprehensive test suite for a project mapping tool that generates AI-readable codebase documentation with LLM integration, caching, and CLI query capabilities.
## parent
index: ./.pi-map.index.md
map: ./.pi-map.md
## children
-
## files
- ast-extract.test.ts
- cli.test.ts
- format.test.ts
- integration.test.ts
- llm-batch.test.ts
- llm-cache.test.ts
- llm-extract.test.ts
- llm-integration.test.ts
- merge.test.ts
- mock-llm.ts
- pi-extension.test.ts
- prompt-injection.test.ts
- retrieve.test.ts
- routing-metadata.test.ts
## links
index: tests/.pi-map.index.md
map: tests/.pi-map.md
## workflows
- change tests behavior
read: mock-llm.ts
- update tests tests
read: ast-extract.test.ts, cli.test.ts, format.test.ts
- change tests CLI
read: cli.test.ts
## dirty
-
+38
View File
@@ -0,0 +1,38 @@
# tests
dir: tests
index: tests/.pi-map.index.md
## role
Comprehensive test suite for a project mapping tool that generates AI-readable codebase documentation with LLM integration, caching, and CLI query capabilities.
## files
- ast-extract.test.ts | Tests AST extraction of TypeScript exports, imports, and dependency resolution with fallback for unsupported file types | dep: vitest, ../src/ast/ast-extract.js, fs, path, os, ast-extract.js
- cli.test.ts | Integration tests for a CLI tool that queries project context bundles from `.pi-map.md` and `.pi-map.index.md` files | dep: vitest, fs, path, os, child_process, url
- format.test.ts | Tests markdown rendering and parsing functions for package maps, directory maps, and directory indexes in a project mapping tool. | dep: vitest, ../src/format.js, ../src/directory-model.js
- integration.test.ts | Integration tests for a project mapping tool that generates and maintains .pi-map.md and .pi-map.index.md files across a codebase. | dep: vitest, fs, path, os, ../src/init.js, ../src/patch.js, ../src/validate.js, ./mock-llm.js, ../src/format.js
- llm-batch.test.ts | Unit tests for retry and batch processing utilities in an LLM module | dep: vitest, ../src/llm/llm-batch.js, ../src/llm-error.js
- llm-cache.test.ts | Tests a file-based caching system for LLM responses with get/set operations and cleanup. | dep: vitest, ../src/llm/llm-cache.js, fs, path, os
- llm-extract.test.ts | Unit tests for LLM-based file extraction with mock client, testing file size limits, binary detection, and response parsing | dep: vitest, ../src/llm/llm-extract.js, fs, path, os, ../src/llm/llm-client.js
- llm-integration.test.ts | Integration tests for LLM client functionality including Kimi API calls, file/package extraction, caching, parallel processing, and error handling | dep: vitest, fs, path, os, ../src/llm/llm-client.js, ../src/llm/llm-extract.js, ../src/llm/llm-batch.js, llm-client, llm-extract, llm-batch
- merge.test.ts | Tests that mergeFileData normalizes multi-line function/method parameters and return types into single-line export signatures | dep: vitest, ../src/merge.js
- mock-llm.ts | Provides mock LLM client implementations for testing purposes | exp: func:createMockFileClient(purpose) → LLMClient, func:createMockPackageClient() → LLMClient | dep: ../src/llm/llm-client.js, llm-client.js
- pi-extension.test.ts | Tests a Pi coding agent extension that manages project map initialization, patching, validation, reinitialization, and context retrieval with configurable prompt injection modes. | dep: vitest, fs, path, os, ../src/prompt-injection.js, ../pi-extension.js, @mariozechner/pi-coding-agent, @mariozechner/pi-ai, typebox
- prompt-injection.test.ts | Tests a prompt injection mitigation system that manages root-pair markers, context budgets, mode-based injection policies, and bypass detection for LLM interactions. | dep: vitest, fs, path, os, ../src/prompt-injection.js
- retrieve.test.ts | Tests the `retrieveContext` function that searches and ranks project map files to build context bundles for AI queries. | dep: vitest, fs, path, os, ../src/retrieve.js
- routing-metadata.test.ts | Tests the `populateRoutingMetadata` function which generates tags, symbols, and workflow hints from directory models for code navigation/routing purposes | dep: vitest, ../src/directory-model.js, ../src/routing-metadata.js
## arch
Layered testing architecture with unit, integration, and mock layers; uses file-based fixtures, mock LLM clients, and tests across AST extraction, LLM batching/caching, markdown rendering, context retrieval, and prompt injection security.
## tags
llm, js, src, tests, vitest, client, fs, path
## symbols
- createMockFileClient
- createMockPackageClient
## workflows
- change tests behavior
read: mock-llm.ts
- update tests tests
read: ast-extract.test.ts, cli.test.ts, format.test.ts
- change tests CLI
read: cli.test.ts
## dirty
-
+186 -1
View File
@@ -8,7 +8,7 @@ import {
} from "fs"; } from "fs";
import { join } from "path"; import { join } from "path";
import { tmpdir } from "os"; import { tmpdir } from "os";
import { initProject } from "../src/init.js"; import { initProject, reinitPath } from "../src/init.js";
import { patchFile } from "../src/patch.js"; import { patchFile } from "../src/patch.js";
import { validateMaps } from "../src/validate.js"; import { validateMaps } from "../src/validate.js";
import { createMockFileClient } from "./mock-llm.js"; import { createMockFileClient } from "./mock-llm.js";
@@ -356,4 +356,189 @@ describe("integration", () => {
readFileSync(join(dir, "src", ".pi-map.index.md"), "utf8"), readFileSync(join(dir, "src", ".pi-map.index.md"), "utf8"),
).toContain("# src (index)"); ).toContain("# src (index)");
}); });
it("reinit on root regenerates all artifacts", async () => {
mkdirSync(join(dir, "src"));
writeFileSync(join(dir, "src", "a.ts"), `export const a = 1;\n`);
const client = createMockFileClient();
await initProject(dir, { llmClient: client, verbose: false });
writeFileSync(
join(dir, ".pi-map.md"),
readFileSync(join(dir, ".pi-map.md"), "utf8") + "\nCORRUPTED",
);
await reinitPath(dir, { llmClient: client, verbose: false });
expect(readFileSync(join(dir, ".pi-map.md"), "utf8")).not.toContain(
"CORRUPTED",
);
});
it("reinit on a small subtree regenerates subtree and ancestors but not siblings", async () => {
mkdirSync(join(dir, "src"));
mkdirSync(join(dir, "lib"));
writeFileSync(join(dir, "src", "a.ts"), `export const a = 1;\n`);
for (let i = 0; i < 10; i++) {
writeFileSync(
join(dir, "lib", `b${i}.ts`),
`export const b${i} = ${i};\n`,
);
}
writeFileSync(
join(dir, ".pi-project-map.json"),
JSON.stringify({ reinitFullThresholdPercent: 10 }),
);
const client = createMockFileClient();
await initProject(dir, { llmClient: client, verbose: false });
writeFileSync(
join(dir, "src", ".pi-map.md"),
readFileSync(join(dir, "src", ".pi-map.md"), "utf8") + "\nSUBTREE_MARKER",
);
writeFileSync(
join(dir, "lib", ".pi-map.md"),
readFileSync(join(dir, "lib", ".pi-map.md"), "utf8") + "\nSIBLING_MARKER",
);
await reinitPath(join(dir, "src"), {
llmClient: client,
verbose: false,
});
expect(readFileSync(join(dir, "src", ".pi-map.md"), "utf8")).not.toContain(
"SUBTREE_MARKER",
);
expect(readFileSync(join(dir, "lib", ".pi-map.md"), "utf8")).toContain(
"SIBLING_MARKER",
);
});
it("reinit falls back to full regeneration when subtree exceeds threshold", async () => {
mkdirSync(join(dir, "src"));
mkdirSync(join(dir, "lib"));
for (let i = 0; i < 11; i++) {
writeFileSync(
join(dir, "src", `a${i}.ts`),
`export const a${i} = ${i};\n`,
);
}
writeFileSync(join(dir, "lib", "b.ts"), `export const b = 1;\n`);
writeFileSync(
join(dir, ".pi-project-map.json"),
JSON.stringify({ reinitFullThresholdPercent: 10 }),
);
const client = createMockFileClient();
await initProject(dir, { llmClient: client, verbose: false });
writeFileSync(
join(dir, "lib", ".pi-map.md"),
readFileSync(join(dir, "lib", ".pi-map.md"), "utf8") + "\nSIBLING_MARKER",
);
await reinitPath(join(dir, "src"), {
llmClient: client,
verbose: false,
});
expect(readFileSync(join(dir, "lib", ".pi-map.md"), "utf8")).not.toContain(
"SIBLING_MARKER",
);
});
it("reinit respects a custom reinitFullThresholdPercent", async () => {
mkdirSync(join(dir, "src"));
mkdirSync(join(dir, "lib"));
writeFileSync(join(dir, "src", "a.ts"), `export const a = 1;\n`);
writeFileSync(join(dir, "lib", "b.ts"), `export const b = 1;\n`);
writeFileSync(
join(dir, ".pi-project-map.json"),
JSON.stringify({ reinitFullThresholdPercent: 50 }),
);
const client = createMockFileClient();
await initProject(dir, { llmClient: client, verbose: false });
writeFileSync(
join(dir, "lib", ".pi-map.md"),
readFileSync(join(dir, "lib", ".pi-map.md"), "utf8") + "\nSIBLING_MARKER",
);
await reinitPath(join(dir, "src"), {
llmClient: client,
verbose: false,
});
expect(readFileSync(join(dir, "lib", ".pi-map.md"), "utf8")).toContain(
"SIBLING_MARKER",
);
});
it("reinit on a small subtree regenerates ancestors up to the root", async () => {
mkdirSync(join(dir, "src"));
mkdirSync(join(dir, "lib"));
writeFileSync(join(dir, "src", "a.ts"), `export const a = 1;\n`);
for (let i = 0; i < 10; i++) {
writeFileSync(
join(dir, "lib", `b${i}.ts`),
`export const b${i} = ${i};\n`,
);
}
writeFileSync(
join(dir, ".pi-project-map.json"),
JSON.stringify({ reinitFullThresholdPercent: 10 }),
);
const client = createMockFileClient();
await initProject(dir, { llmClient: client, verbose: false });
writeFileSync(
join(dir, ".pi-map.md"),
readFileSync(join(dir, ".pi-map.md"), "utf8") + "\nROOT_MARKER",
);
writeFileSync(
join(dir, "lib", ".pi-map.md"),
readFileSync(join(dir, "lib", ".pi-map.md"), "utf8") + "\nSIBLING_MARKER",
);
await reinitPath(join(dir, "src"), {
llmClient: client,
verbose: false,
});
expect(readFileSync(join(dir, ".pi-map.md"), "utf8")).not.toContain(
"ROOT_MARKER",
);
expect(readFileSync(join(dir, "lib", ".pi-map.md"), "utf8")).toContain(
"SIBLING_MARKER",
);
});
it("reinit on a non-existent path falls back to full regeneration", async () => {
mkdirSync(join(dir, "src"));
mkdirSync(join(dir, "lib"));
writeFileSync(join(dir, "src", "a.ts"), `export const a = 1;\n`);
writeFileSync(join(dir, "lib", "b.ts"), `export const b = 1;\n`);
writeFileSync(
join(dir, ".pi-project-map.json"),
JSON.stringify({ reinitFullThresholdPercent: 10 }),
);
const client = createMockFileClient();
await initProject(dir, { llmClient: client, verbose: false });
writeFileSync(
join(dir, "lib", ".pi-map.md"),
readFileSync(join(dir, "lib", ".pi-map.md"), "utf8") + "\nSIBLING_MARKER",
);
await reinitPath(join(dir, "does-not-exist"), {
llmClient: client,
verbose: false,
});
expect(readFileSync(join(dir, "lib", ".pi-map.md"), "utf8")).not.toContain(
"SIBLING_MARKER",
);
});
}); });
+52 -1
View File
@@ -16,6 +16,7 @@ vi.mock("typebox", () => ({
Object: (props: unknown) => props, Object: (props: unknown) => props,
Optional: (prop: unknown) => prop, Optional: (prop: unknown) => prop,
String: (opts: unknown) => ({ type: "string", ...(opts as object) }), String: (opts: unknown) => ({ type: "string", ...(opts as object) }),
Boolean: (opts: unknown) => ({ type: "boolean", ...(opts as object) }),
}, },
})); }));
@@ -70,6 +71,9 @@ describe("pi-extension", () => {
})), })),
}, },
ui: { notify: mockNotify }, ui: { notify: mockNotify },
sessionManager: {
buildSessionContext: vi.fn(() => ({ messages: [] })),
},
}; };
const mockPi = { const mockPi = {
@@ -248,7 +252,7 @@ describe("pi-extension", () => {
await handler(null, mockCtx); await handler(null, mockCtx);
expect(mockNotify).toHaveBeenCalledWith( expect(mockNotify).toHaveBeenCalledWith(
expect.stringContaining("1 dirty packages detected"), expect.stringContaining("1 dirty package(s) detected"),
"warning", "warning",
); );
}); });
@@ -505,6 +509,53 @@ describe("pi-extension", () => {
expect(result.message.content).toContain("Updated"); expect(result.message.content).toContain("Updated");
expect(result.message.content).toContain("TestIndex"); expect(result.message.content).toContain("TestIndex");
}); });
it("skips injection when hint is already present in session context", async () => {
const dir = mkdtempSync(join(tmpdir(), "pi-ext-test-"));
writeFileSync(join(dir, ".pi-map.md"), "# .\n## role\nTest\n");
writeFileSync(
join(dir, ".pi-project-map.json"),
JSON.stringify({ promptInjectionMode: "advisory" }),
);
mockCtx.cwd = dir;
mockCtx.sessionManager.buildSessionContext = vi.fn(() => ({
messages: [
{
role: "custom",
customType: "pi-project-map-hint",
content: "existing hint",
display: false,
},
],
}));
const handler = registeredEvents.before_agent_start;
const result = await handler(null, mockCtx);
expect(result).toEqual({});
});
it("injects hint again when session context does not contain it", async () => {
const dir = mkdtempSync(join(tmpdir(), "pi-ext-test-"));
writeFileSync(join(dir, ".pi-map.md"), "# .\n## role\nTest\n");
writeFileSync(
join(dir, ".pi-project-map.json"),
JSON.stringify({ promptInjectionMode: "advisory" }),
);
mockCtx.cwd = dir;
mockCtx.sessionManager.buildSessionContext = vi.fn(() => ({
messages: [
{ role: "user", content: "hello" },
{ role: "assistant", content: [{ type: "text", text: "hi" }] },
],
}));
const handler = registeredEvents.before_agent_start;
const result = await handler(null, mockCtx);
expect(result).toHaveProperty("message");
expect(result.message.customType).toBe("pi-project-map-hint");
});
}); });
describe("context event", () => { describe("context event", () => {
+5 -4
View File
@@ -10,7 +10,7 @@ A source file was edited, added, or deleted without running `project_map_patch`
### What to do ### What to do
1. run `project_map_validate` to inspect discrepancies 1. run `project_map_validate` to inspect discrepancies
2. if the list is small and localized, run `project_map_patch <changed-file>` 2. if the list is small and localized, run `project_map_patch <changed-file>` or `project_map_validate` with `fix=true`
3. if the list is large or structural, run `project_map_reinit` 3. if the list is large or structural, run `project_map_reinit`
4. re-run `project_map_validate` to confirm clean state 4. re-run `project_map_validate` to confirm clean state
@@ -38,14 +38,14 @@ Many `stale-signature` discrepancies appear.
AST exports no longer match listed exports, or generated signatures are stale. AST exports no longer match listed exports, or generated signatures are stale.
### Fix ### Fix
- patch the changed file/directory - patch the changed file/directory, or run `project-map validate --fix`
- if widespread, reinit and validate again - if widespread, reinit and validate again
### Symptom ### Symptom
`broken-link` appears after moving directories. `broken-link` appears after moving directories.
### Fix ### Fix
Run `project_map_reinit`. Run `project-map validate --fix` first. If the damage spans many directories, run `project_map_reinit`.
## Prompt injection mode surprises ## Prompt injection mode surprises
@@ -67,12 +67,13 @@ No project-map context appears in a Pi session.
Root pair is injected repeatedly. Root pair is injected repeatedly.
### Likely cause ### Likely cause
Marker scanning failed or artifact invalidation forced reinjection. Marker scanning failed or artifact invalidation forced reinjection, often because `project_map_reinit` is being used instead of `project_map_patch` or `project_map_validate --fix`.
### Fix ### Fix
- inspect whether `<!-- PI_MAP_ROOT_PAIR_START -->` is present in outgoing context - inspect whether `<!-- PI_MAP_ROOT_PAIR_START -->` is present in outgoing context
- check whether root artifacts changed on disk - check whether root artifacts changed on disk
- confirm payload/message serialization still exposes marker text - confirm payload/message serialization still exposes marker text
- prefer `project_map_patch <file>` for edits and `project_map_validate` with `fix=true` for localized discrepancies
### Symptom ### Symptom
`advisory` mode shows a reminder but no maps are loaded. `advisory` mode shows a reminder but no maps are loaded.
+25 -12
View File
@@ -9,7 +9,8 @@ For Pi agents and advanced users who want predictable, low-friction navigation a
| New project, no `.pi-map.md` files yet, or artifacts are severely outdated | `project_map_init` / `project-map init` | | 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 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` | | 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` | | Localized discrepancies detected by validate | `project_map_validate` with `fix=true` / `project-map validate --fix` |
| Widespread structural 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>` | | You have a specific question like "where is auth handled?" | `project_map_context <query>` / `project-map context <query>` |
## Command behavior ## Command behavior
@@ -18,11 +19,11 @@ For Pi agents and advanced users who want predictable, low-friction navigation a
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. 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 ### patch
Run **immediately after editing a source file**. The command regenerates the pair for that files directory and refreshes ancestor artifacts. Run **immediately after editing a source file**. The command regenerates the pair for that file's directory and refreshes ancestor artifacts.
Patch mode is chosen automatically: Patch mode is chosen automatically:
- **small** refresh ancestor indexes only - **small** - refresh ancestor indexes only
- **structural** refresh ancestor map/index pairs - **structural** - refresh ancestor map/index pairs
You can force a mode with `project-map patch <file> --patch-mode=small|structural`. You can force a mode with `project-map patch <file> --patch-mode=small|structural`.
`auto` remains the default. `auto` remains the default.
@@ -38,7 +39,9 @@ Run before architectural decisions, broad refactors, or final handoff. It checks
Use `project-map validate --fix` to repair affected chains. `--fix` requires an LLM client. Use `project-map validate --fix` to repair affected chains. `--fix` requires an LLM client.
### reinit ### reinit
Use sparingly. It regenerates every pair from scratch and is the blunt instrument for widespread staleness. Use as a last resort. By default, `project_map_reinit` (or `project-map reinit [path]`) regenerates only the target subtree plus its ancestor directories up to the root. It falls back to full regeneration only when the target subtree covers more than `reinitFullThresholdPercent` of the project's files (default: 10%).
Reach for reinit when `project_map_patch` and `project_map_validate --fix` cannot repair widespread structural damage (e.g. broken parent/child links across many directories) or after a large merge.
### context ### context
Use when you know what you are looking for: Use when you know what you are looking for:
@@ -57,12 +60,14 @@ The returned bundle is deterministic and ranked. Read indexes first, then strong
1. read the root `.pi-map.index.md` and the `Project Map Protocol` 1. read the root `.pi-map.index.md` and the `Project Map Protocol`
2. use the root index to find the relevant child directory 2. use the root index to find the relevant child directory
3. read that directorys `.pi-map.index.md`, then its `.pi-map.md` 3. read that directory's `.pi-map.index.md`, then its `.pi-map.md`
4. read the relevant source files 4. read the relevant source files
5. edit source 5. edit source
6. run `project_map_patch <changed-file>` 6. run `project_map_patch <changed-file>`
7. run tests/build 7. run tests/build
8. run `project_map_validate` before architectural summary or handoff 8. run `project_map_validate` before architectural summary or handoff
9. if validation reports localized discrepancies, run `project_map_validate` with `fix=true` (or `project-map validate --fix`)
10. only if discrepancies are widespread or structural, run `project_map_reinit`
### Exploring an unfamiliar area ### Exploring an unfamiliar area
@@ -76,7 +81,9 @@ Treat the returned bundle as a ranked entry point, not as truth.
```bash ```bash
project-map validate project-map validate
# if many discrepancies: # if localized discrepancies:
project-map validate --fix
# if widespread structural damage:
project-map reinit project-map reinit
``` ```
@@ -84,7 +91,9 @@ project-map reinit
```bash ```bash
project-map validate project-map validate
# if needed: # if localized discrepancies:
project-map validate --fix
# if widespread structural damage:
project-map reinit project-map reinit
``` ```
@@ -155,9 +164,10 @@ Use bypass markers sparingly.
```text ```text
1. project-map validate 1. project-map validate
2. If clean, read root .pi-map.md and key directory maps 2. If localized discrepancies: project-map validate --fix
3. Cross-check claims against source 3. If clean, read root .pi-map.md and key directory maps
4. If stale, run project-map reinit first 4. Cross-check claims against source
5. Only if widespread: run project-map reinit
``` ```
## Retrieval vs automatic injection ## Retrieval vs automatic injection
@@ -175,7 +185,8 @@ Use both together: injection for baseline orientation, retrieval for focused ent
- patch after every edit - patch after every edit
- validate before architectural claims - validate before architectural claims
- reinit when many artifacts are stale or after large merges - use `validate --fix` for localized discrepancies
- use reinit only for widespread structural staleness or after large merges
- watch for the Pi extension warning about dirty packages on session start - watch for the Pi extension warning about dirty packages on session start
## Configuration quick reference ## Configuration quick reference
@@ -187,6 +198,7 @@ Use both together: injection for baseline orientation, retrieval for focused ent
"contextBudgetMaxTokens": 100000, "contextBudgetMaxTokens": 100000,
"tagCap": 8, "tagCap": 8,
"workflowHintCap": 5, "workflowHintCap": 5,
"reinitFullThresholdPercent": 10,
"ignorePatterns": ["node_modules", ".git", "dist", "build"] "ignorePatterns": ["node_modules", ".git", "dist", "build"]
} }
``` ```
@@ -195,4 +207,5 @@ Providing `ignorePatterns` replaces the built-in default list, so include any de
- lower `contextBudgetPercent` / `contextBudgetMaxTokens` to reduce token use - lower `contextBudgetPercent` / `contextBudgetMaxTokens` to reduce token use
- raise them if you want deeper auto-loaded context in large projects - raise them if you want deeper auto-loaded context in large projects
- `reinitFullThresholdPercent` controls when `project-map reinit [path]` falls back to full regeneration
- `strict` is the safest enforcement mode; `strong` is the best default for everyday work - `strict` is the safest enforcement mode; `strong` is the best default for everyday work