docs(prompt): align prompt injection guidance and runtime copy

This commit is contained in:
2026-06-11 23:21:19 +02:00
parent 0cf4060c4b
commit 11365fa4ed
4 changed files with 175 additions and 12 deletions
+73 -2
View File
@@ -33,12 +33,76 @@ Read actual source, tests, config, and docs before editing or making exact runti
**Trust boundary:** index routes, map orients, source decides. **Trust boundary:** index routes, map orients, source decides.
### Retrieval ## Prompt Injection Policy
The Pi extension can automatically inject lightweight project-map guidance into the agent context according to the configured `promptInjectionMode`.
### Before init
When no `.pi-map.md` / `.pi-map.index.md` artifacts exist, only a visible startup hint is injected. It tells the agent that the project-map extension is active and to run `project_map_init`. No synthetic or fake map content is ever injected before real artifacts exist.
### After init
Once real artifacts exist, the runtime guarantees that the root pair is loaded before any budgeted expansion:
- root `.pi-map.index.md`
- root `.pi-map.md`
Additional directory pairs are expanded only while the configured context budget allows, in shallow-first order.
### Trust boundary
Injected maps and indexes are navigation and orientation aids, not final authority:
> **index routes, map orients, source decides**
If an injected artifact and the source disagree, source wins. Always verify critical behavior from source before editing or making exact runtime claims.
### Mode ladder
- `off`: no automatic injection beyond existing tool/docs discovery.
- `advisory`: inject startup/init hints and allow optional root-pair preload; use light reminders.
- `strong` (default): inject the root pair, expand under the configured budget, run reinjection checks on relevant turns, and remind before edits or architecture-sensitive reasoning.
- `strict`: same as `strong`, plus require an explicit bypass justification before sensitive edits or architectural claims when the protocol path is missing.
The **protocol path** is present when the outgoing context contains the canonical injected root-pair block and the trust-boundary instruction. In `strict` mode, sensitive actions without it are blocked unless the agent includes `[PI_MAP_BYPASS: <brief justification>]`.
### Context budget
The default budget is **15% of the active model context window**, capped at **100k tokens**. The smaller of the relative and absolute values wins. If the runtime cannot discover the active model's context window, it falls back to the absolute cap.
Configure it in `.pi-project-map.json`:
```json
{
"promptInjectionMode": "strong",
"contextBudgetPercent": 15,
"contextBudgetMaxTokens": 100000
}
```
## Retrieval
When you have a specific query (e.g. "authentication logic" or "routing metadata"): When you have a specific query (e.g. "authentication logic" or "routing metadata"):
1. Run `project-map context <query>` or use the Pi tool `project_map_context` 1. Run `project-map context <query>` or use the Pi tool `project_map_context`
2. Read the returned **Context bundle** — it contains relevant indexes, maps, likely files, and symbols 2. Read the returned **Context bundle** — it contains relevant indexes, maps, likely files, and symbols
3. Always verify critical behavior from source before editing 3. Always verify critical behavior from source before editing
Retrieval via `project_map_context` (tool) or `project-map context` (CLI) remains a separate, on-demand path. Use it for targeted navigation when you have a specific query; automatic injection does not replace it.
### Integration-test expectations and known limitations
The implementation is validated by integration tests covering:
- pre-init hint behavior,
- post-init root-pair preload,
- budgeted expansion behavior,
- canonical-marker dedupe,
- reinjection after compaction,
- reinjection after root-pair artifact changes,
- mixed visibility behavior,
- guidance-mode differences,
- strict-mode bypass behavior.
Known limitations:
- Token estimation is best-effort (≈ 4 chars per token); actual provider token counts may differ.
- Relevant-turn detection uses explicit event types when available and falls back to heuristics on generic turns.
- Message-layer scanning is preferred; provider-payload serialization quirks require a fallback scan path.
## Commands ## Commands
```bash ```bash
@@ -64,10 +128,17 @@ Create `.pi-project-map.json` in the project root:
"contextBudget": 4000, "contextBudget": 4000,
"autoInjectPrompt": true, "autoInjectPrompt": true,
"tagCap": 8, "tagCap": 8,
"workflowHintCap": 5 "workflowHintCap": 5,
"promptInjectionMode": "strong",
"contextBudgetPercent": 15,
"contextBudgetMaxTokens": 100000
} }
``` ```
- `promptInjectionMode`: `off`, `advisory`, `strong` (default), or `strict`.
- `contextBudgetPercent`: relative share of the active model context window used for automatic map/index injection.
- `contextBudgetMaxTokens`: hard absolute cap on the injection budget.
## Design ## Design
See [design-doc.md](design-doc.md) for the full specification. See [design-doc.md](design-doc.md) for the full specification.
+36 -2
View File
@@ -41,6 +41,32 @@ Read actual source before editing or asserting exact behavior.
**Trust boundary:** index routes, map orients, source decides. **Trust boundary:** index routes, map orients, source decides.
## Prompt Injection Policy
This skill can automatically inject lightweight project-map guidance into your context. The behavior is controlled by `promptInjectionMode` in `.pi-project-map.json`.
### Before init
When no `.pi-map.md` / `.pi-map.index.md` artifacts exist, you see a visible startup hint telling you to run `project_map_init`. No synthetic map content is injected before real artifacts exist.
### After init
Once artifacts exist, the runtime guarantees that the root pair is loaded first:
- root `.pi-map.index.md`
- root `.pi-map.md`
Additional directory pairs may be added while the configured context budget allows.
### Mode ladder
- `off`: no automatic injection beyond existing tool/docs discovery.
- `advisory`: startup/init hints are shown; you may read the root pair manually when you want routing/orientation context.
- `strong` (default): the root pair is injected automatically, expansion stays within the context budget, and reinjection checks run on relevant turns (agent start, before edits, before architecture-sensitive reasoning, after compaction, after root-pair artifact changes).
- `strict`: same as `strong`, but before sensitive edits or architectural claims you must either have the protocol path in context or include an explicit bypass marker: `[PI_MAP_BYPASS: <brief justification>]`.
The **protocol path** means the outgoing context contains the canonical injected root-pair block and the trust-boundary instruction (`index routes, map orients, source decides`).
### Context budget
Default budget: **15% of the active model context window**, capped at **100k tokens**. The smaller of the relative and absolute values wins. If the runtime cannot discover the active model's context window, it uses the absolute cap.
## Configuration ## Configuration
Create `.pi-project-map.json` in the project root: Create `.pi-project-map.json` in the project root:
@@ -48,10 +74,17 @@ Create `.pi-project-map.json` in the project root:
```json ```json
{ {
"tagCap": 8, "tagCap": 8,
"workflowHintCap": 5 "workflowHintCap": 5,
"promptInjectionMode": "strong",
"contextBudgetPercent": 15,
"contextBudgetMaxTokens": 100000
} }
``` ```
- `promptInjectionMode`: `off`, `advisory`, `strong` (default), or `strict`.
- `contextBudgetPercent`: relative share of the active model context window to use for automatic map/index injection.
- `contextBudgetMaxTokens`: hard absolute cap on the injection budget.
## Agent Instructions ## Agent Instructions
When project map artifacts exist in the repo: When project map artifacts exist in the repo:
@@ -61,7 +94,8 @@ When project map artifacts exist in the repo:
3. Read the local `.pi-map.md` plus source before editing 3. Read the local `.pi-map.md` plus source before editing
4. Run `project-map patch <path>` after editing source 4. Run `project-map patch <path>` after editing source
5. Run `project-map validate` before freshness-sensitive architectural decisions 5. Run `project-map validate` before freshness-sensitive architectural decisions
6. For **targeted navigation**, use `project_map_context` (Pi tool) or `project-map context` (CLI) with a natural-language query. It returns a compact markdown bundle with the strongest-match indexes, maps, likely files, and symbols. 6. In `strict` mode, include `[PI_MAP_BYPASS: <brief justification>]` only when you deliberately need to proceed without the protocol path
7. For **targeted navigation**, use `project_map_context` (Pi tool) or `project-map context` (CLI) with a natural-language query. It returns a compact markdown bundle with the strongest-match indexes, maps, likely files, and symbols.
## Retrieval Model ## Retrieval Model
+63 -5
View File
@@ -252,6 +252,39 @@ The LLM client's response is parsed to extract `PURPOSE`, `DEPS`, `CONCEPTS`, `R
2. For architecture/system or ambiguous tasks, agent also reads the root `.pi-map.md`. 2. For architecture/system or ambiguous tasks, agent also reads the root `.pi-map.md`.
3. Agent does **not** preload every directory map by default. 3. Agent does **not** preload every directory map by default.
In the Pi extension, this session-start consumption is assisted by automatic prompt injection:
- **Before init**: only a lightweight visible startup hint is injected, telling the agent to run `project_map_init`. No synthetic map content is injected.
- **After init**: the root pair (`.pi-map.index.md` + `.pi-map.md`) is guaranteed to be preloaded automatically. Additional directory pairs are expanded only while the configured context budget allows.
### Automatic Prompt Injection
The Pi extension uses event hooks (`before_agent_start`, `context`, etc.) to maintain guidance context.
- The **mode ladder** controls how much is injected:
- `off`: no automatic injection.
- `advisory`: visible startup/init hints and optional root-pair preload.
- `strong` (default): root pair + budgeted expansion + relevant-turn reinjection checks.
- `strict`: same as `strong`, plus explicit bypass justification for sensitive edits/architectural claims when the protocol path is missing.
- The **protocol path** requires both the canonical injected root-pair block and the trust-boundary instruction (`index routes, map orients, source decides`) to be present in outgoing context.
- **Reinjection avoidance** scans actual outgoing messages (and falls back to provider payload) for a stable canonical marker before adding the root pair again.
- **Relevant-turn triggers** are: agent start, before edits, before architecture-sensitive reasoning, after compaction, and after root-pair artifact changes.
- **Visibility** is mixed: startup/init hints are user-visible; raw injected artifact blocks are agent-visible by default.
### Context Budget
Default automatic-injection budget: **15% of the active model context window**, capped at **100k tokens**. The smaller of the relative and absolute values wins. If the runtime cannot discover the active model's context window, it falls back to the absolute cap.
Configurable via `.pi-project-map.json`:
```json
{
"promptInjectionMode": "strong",
"contextBudgetPercent": 15,
"contextBudgetMaxTokens": 100000
}
```
### During Session ### During Session
- Use directory indexes first to decide what to open next. - Use directory indexes first to decide what to open next.
- For **targeted queries**, run `project_map_context` (tool) or `project-map context` (CLI). The retrieval engine scores all paired metadata and returns a compact markdown bundle with the top-3 strongest matches: indexes, maps, likely files, and symbols. - For **targeted queries**, run `project_map_context` (tool) or `project-map context` (CLI). The retrieval engine scores all paired metadata and returns a compact markdown bundle with the top-3 strongest matches: indexes, maps, likely files, and symbols.
@@ -262,6 +295,7 @@ The LLM client's response is parsed to extract `PURPOSE`, `DEPS`, `CONCEPTS`, `R
- Tier 0 stays tiny and stable. - Tier 0 stays tiny and stable.
- Tier 1 loads only likely relevant indexes/maps. - Tier 1 loads only likely relevant indexes/maps.
- Tier 2 is real source, tests, config, and docs. - Tier 2 is real source, tests, config, and docs.
- Automatic injection stays within the configured budget and avoids redundant reinjection by scanning outgoing context.
## 6. Stale Data Mitigation ## 6. Stale Data Mitigation
@@ -320,9 +354,10 @@ pi-project-map/
│ ├── llm-extract.ts # LLM prompt templates for extraction │ ├── llm-extract.ts # LLM prompt templates for extraction
│ ├── merge.ts # Merge AST + LLM outputs │ ├── merge.ts # Merge AST + LLM outputs
│ ├── format.ts # Dense markdown formatter │ ├── format.ts # Dense markdown formatter
── config.ts # Skill configuration (thresholds, ignore patterns) ── config.ts # Skill configuration (thresholds, ignore patterns)
│ └── prompt-injection.ts # Runtime guidance injection policy and helpers
├── hooks/ ├── hooks/
│ └── on-prompt.ts # Injects maintenance command into prompts │ └── on-prompt.ts # Injects maintenance command into prompts (legacy; Pi extension uses event hooks)
└── README.md # Setup and usage for humans └── README.md # Setup and usage for humans
``` ```
@@ -333,9 +368,19 @@ pi-project-map/
- `project-map:context <query>` — Retrieve a compact markdown bundle of the most relevant directories, files, and symbols for a natural-language query. - `project-map:context <query>` — Retrieve a compact markdown bundle of the most relevant directories, files, and symbols for a natural-language query.
- `project-map:reinit [path]` — Force re-initialization of entire project or subtree. - `project-map:reinit [path]` — Force re-initialization of entire project or subtree.
### Prompt Hook ### Prompt Injection Hooks
- On every prompt, the skill appends a lightweight instruction:
> "If you modify any source file, run `project-map:patch <path>` to update the analysis. If you suspect staleness, run `project-map:validate`." The Pi extension registers event hooks instead of a single per-prompt append:
- `before_agent_start`: emits the pre-init hint when no artifacts exist, or preloads the root pair (plus budgeted expansion) after init.
- `context`: performs relevant-turn reinjection checks, detects compaction/artifact-change invalidation, and enforces `strict`-mode bypass guards.
- `before_provider_request`: optional fallback for marker scanning when message-layer detection is insufficient.
The injected maintenance reminder is:
> Start with the root `.pi-map.index.md`, use indexes first for routing, read the local `.pi-map.md` plus source before edits, run `project_map_patch` after source edits, and run `project_map_validate` before freshness-sensitive architectural handoff.
This is layered on top of the canonical root-pair block, which includes the trust boundary (`index routes, map orients, source decides`).
## 9. Risks and Tradeoffs ## 9. Risks and Tradeoffs
@@ -347,6 +392,19 @@ pi-project-map/
| Expensive init on large repos | Medium | Medium | Parallelization, caching, optional incremental init | | Expensive init on large repos | Medium | Medium | Parallelization, caching, optional incremental init |
| Overlap with LSP/typedoc | Low | Low | This is agent-context, not IDE tooling. Different use case. | | Overlap with LSP/typedoc | Low | Low | This is agent-context, not IDE tooling. Different use case. |
| AST parser unavailable | Medium | Low | Graceful fallback to LLM-only extraction | | AST parser unavailable | Medium | Low | Graceful fallback to LLM-only extraction |
| Message-level marker scanning misses provider serialization quirks | Medium | Medium | Add payload fallback scanning |
| Root-pair marker becomes brittle | Low | Medium | Stable deterministic boundaries and normalized artifact identity lines |
| 15% / 100k default budget too aggressive for some fleets | Low | Medium | Both knobs are configurable |
| Relevant-turn detection fuzzy | Medium | Medium | Centralized heuristics + extensive integration tests |
| `strict` mode friction | Low | Medium | Keep `strong` as default; isolate strict-only bypass behavior |
### Prompt Injection Known Limitations
- Token estimation is best-effort (≈ 4 chars per token); actual provider token counts may differ.
- Relevant-turn detection relies on explicit event types when available, with heuristic fallback for generic turns.
- Provider payload serialization may require the fallback scan path.
- The mode ladder is config-driven in v1; future UX may expose runtime controls.
- Retrieval (`project_map_context`) remains separate from automatic injection.
## 10. Concrete Example: Full Project Snapshot ## 10. Concrete Example: Full Project Snapshot
+3 -3
View File
@@ -255,8 +255,8 @@ export function buildPreInitHint(): string {
return [ return [
"📋 Project maps not initialized.", "📋 Project maps not initialized.",
"", "",
"Run `project_map_init` to generate paired `.pi-map.md` and `.pi-map.index.md` artifacts for this project.", "The project-map extension is active. Run `project_map_init` to generate paired `.pi-map.md` and `.pi-map.index.md` artifacts for this project.",
"After init, the agent will automatically use the root index for routing and the root map for orientation.", "After init, the root pair will be preloaded automatically (default mode: strong). Source remains the final authority before edits.",
].join("\n"); ].join("\n");
} }
@@ -638,7 +638,7 @@ export function buildInjectionPayload(
// Maintenance reminder (lightweight) // Maintenance reminder (lightweight)
const reminder = const reminder =
"📋 Project map active: Start with the root `.pi-map.index.md`, use indexes first for routing, read the local `.pi-map.md` plus source before edits, run `project_map_patch` after source edits, and run `project_map_validate` before freshness-sensitive architectural handoff."; "📋 Project map active: Start with the root `.pi-map.index.md`, use indexes first for routing, read the local `.pi-map.md` plus source before edits, run `project_map_patch` after source edits, and run `project_map_validate` before freshness-sensitive architectural handoff. Trust boundary: index routes, map orients, source decides.";
parts.push(reminder); parts.push(reminder);
usedTokens += estimateTokens(reminder); usedTokens += estimateTokens(reminder);