diff --git a/README.md b/README.md index cb4bdaa..47089d5 100644 --- a/README.md +++ b/README.md @@ -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. -### 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: ]`. + +### 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"): 1. Run `project-map context ` or use the Pi tool `project_map_context` 2. Read the returned **Context bundle** — it contains relevant indexes, maps, likely files, and symbols 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 ```bash @@ -64,10 +128,17 @@ Create `.pi-project-map.json` in the project root: "contextBudget": 4000, "autoInjectPrompt": true, "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 See [design-doc.md](design-doc.md) for the full specification. diff --git a/SKILL.md b/SKILL.md index d2fc7dc..98d1a88 100644 --- a/SKILL.md +++ b/SKILL.md @@ -41,6 +41,32 @@ Read actual source before editing or asserting exact behavior. **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: ]`. + +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 Create `.pi-project-map.json` in the project root: @@ -48,10 +74,17 @@ Create `.pi-project-map.json` in the project root: ```json { "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 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 4. Run `project-map patch ` after editing source 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: ]` 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 diff --git a/design-doc.md b/design-doc.md index 000681b..fe2cf65 100644 --- a/design-doc.md +++ b/design-doc.md @@ -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`. 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 - 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. @@ -262,6 +295,7 @@ The LLM client's response is parsed to extract `PURPOSE`, `DEPS`, `CONCEPTS`, `R - Tier 0 stays tiny and stable. - Tier 1 loads only likely relevant indexes/maps. - 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 @@ -320,9 +354,10 @@ pi-project-map/ │ ├── llm-extract.ts # LLM prompt templates for extraction │ ├── merge.ts # Merge AST + LLM outputs │ ├── 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/ -│ └── 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 ``` @@ -333,9 +368,19 @@ pi-project-map/ - `project-map:context ` — 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. -### Prompt Hook -- On every prompt, the skill appends a lightweight instruction: - > "If you modify any source file, run `project-map:patch ` to update the analysis. If you suspect staleness, run `project-map:validate`." +### Prompt Injection Hooks + +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 @@ -347,6 +392,19 @@ pi-project-map/ | 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. | | 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 diff --git a/src/prompt-injection.ts b/src/prompt-injection.ts index 4c36d03..442929a 100644 --- a/src/prompt-injection.ts +++ b/src/prompt-injection.ts @@ -255,8 +255,8 @@ export function buildPreInitHint(): string { return [ "📋 Project maps not initialized.", "", - "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.", + "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 root pair will be preloaded automatically (default mode: strong). Source remains the final authority before edits.", ].join("\n"); } @@ -638,7 +638,7 @@ export function buildInjectionPayload( // Maintenance reminder (lightweight) 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); usedTokens += estimateTokens(reminder);