pi-better-rules
Pi extension that surfaces global and project rules per prompt (spec .scratch/rules-extension/spec.md §5; loader manifest: package.json ‘pi.extensions’ -> ./src/index.ts per docs/extensions.md + docs/packages.md).
Package details
Install pi-better-rules from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:pi-better-rules- Package
pi-better-rules- Version
0.2.1- Published
- Sep 10, 2026
- Downloads
- 291/mo · 291/wk
- Author
- rianico
- License
- unknown
- Types
- extension
- Size
- 357.3 KB
- Dependencies
- 0 dependencies · 0 peers
Pi manifest JSON
{
"extensions": [
"./src/index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-better-rules
Pi extension that loads persistent rule memory from markdown files into every prompt — global conventions plus project conventions, with always-on rules and path-scoped activation.
Rule memory (pi-better-rules)
What it does. On session_start the extension scans two rule trees, caches the result in memory, appends unscoped full content to the systemPrompt on every before_agent_start, and appends newly activated scoped rules to the triggering tool_result content same-turn (cumulative inject-once). After loading it reports pi-rules: N rule(s) — U unscoped, S scoped at info level, followed by one detail line per rule and the trigger reason (full scan, checksum refresh, compaction retention); the scan summary also persists as a pi-rules.scan timeline entry, and /rules reprints load state on demand. Unscoped rules live in the system prompt; scoped rules ride tool results — never as user-role text.
Two rule locations. Global rules live under ~/.pi/agent/rules; project rules under <project>/.pi/rules. Both trees are scanned recursively for **/*.md, including subdirectories. Global loads first, project second, concatenated — on conflict project wins: an identical relative path in both trees is a shadow (the project copy replaces the global copy, no merge) and a load-time warning names the shadowed file. Files larger than 4 MiB are hard-skipped.
Scope model. paths: is optional scoping with ** / * / ? / {a,b} / [...] / backslash-escape glob syntax (an invalid pattern matches nothing while siblings keep working). Absent paths: means always-on; present paths: means conditional:
| Scope | Where it renders | When |
|---|---|---|
Unscoped (paths: absent) |
Full content appended to the system prompt | Every prompt |
Scoped (paths: present) |
Full content appended to the triggering tool result (## Rules (scoped — matched for <file>)) |
Same-turn, once per rule (inject-once) |
Keep the always-on (unscoped) set minimal — invariants only. Domain rules belong in paths:-scoped rules. Every paths:-scoped rule is compaction-evictable: persistent invariants belong in unscoped rules. Each touched file matches against two bases — its repo-relative path and its bare filename — so bare paths: entries (e.g. pyproject.toml) fire for nested files. Paths come from the tool_result event (read/edit input.path plus details.filePath, write filePath/path), relativized against the session cwd, so absolute tool paths match too. Every injection warns +N scoped rule(s) matched for <file>, matched pattern: <p> with one bare - <rel> bullet per rule. bash carries no path, and error results never inject — documented limitations.
Freshness: snapshot, not live re-read. The filesystem scan happens once per session and lives in module state; the per-prompt handler is cheap string concat. Edits to rule files mid-session do not take effect until a refresh:
/reloadwipes extension memory, so checksums persist on disk aspi-better-rules-checksums.json— global copy under~/.pi/agent/cache/, project copy under.pi/.cache/.- On
session_startwithreason === "reload"the extension verifies checksums (list → stat pre-filter → checksum candidates): changed files reload, deleted files drop, and it reportsrefreshed / added / removedwith one~/+/-line per changed file plus the full rule list — unchanged rules keep byte-identical text. When nothing changed it reportsunchanged (checksums verified, no rescan)plus the rule list and skips the rescan entirely. Other reasons (startup | new | resume | fork) always rescan with a full rule list. Asession_compacthandler notifies retention (cache untouched, no rescan). - A corrupt checksum cache is treated as everything-changed (rebuild) with a warning. Rule-load warnings (over-budget globs, corrupt cache, shadowed files) surface via
notify(message, "warning"). Scoped injections name the matched file and the activating file.
[!tip] Writing rules? See the rule-authoring guide — filename style, frontmatter examples, the shared-rules-via-symlink pattern, and version-control etiquette.