pi-rules-md 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +163 -2
- package/RULE-SPEC.md +191 -0
- package/dist/cli.mjs +842 -0
- package/dist/extension.js +11297 -0
- package/index.ts +165 -0
- package/package.json +52 -3
- package/shell/collectors.test.ts +172 -0
- package/shell/collectors.ts +109 -0
- package/shell/discovery.test.ts +597 -0
- package/shell/discovery.ts +348 -0
- package/shell/injection.test.ts +164 -0
- package/shell/injection.ts +88 -0
- package/shell/pipeline.test.ts +243 -0
- package/shell/pipeline.ts +200 -0
- package/shell/read-rule.test.ts +57 -0
- package/shell/read-rule.ts +49 -0
- package/shell/rules-paths.test.ts +26 -0
- package/shell/rules-paths.ts +17 -0
- package/src/cli/constants.ts +1 -0
- package/src/cli/debug.ts +6 -0
- package/src/cli/doctor.test.ts +161 -0
- package/src/cli/doctor.ts +109 -0
- package/src/cli/install.test.ts +97 -0
- package/src/cli/install.ts +63 -0
- package/src/cli/interlock.test.ts +29 -0
- package/src/cli/interlock.ts +6 -0
- package/src/cli/main.test.ts +88 -0
- package/src/cli/main.ts +123 -0
- package/src/cli/match.test.ts +86 -0
- package/src/cli/match.ts +76 -0
- package/src/cli/packaging.test.ts +47 -0
- package/src/cli/paths.test.ts +54 -0
- package/src/cli/paths.ts +45 -0
- package/src/cli/pi-runner.test.ts +79 -0
- package/src/cli/pi-runner.ts +62 -0
- package/src/cli/registry.test.ts +68 -0
- package/src/cli/registry.ts +53 -0
- package/src/cli/scopes.ts +19 -0
- package/src/cli/settings.test.ts +76 -0
- package/src/cli/settings.ts +68 -0
- package/src/cli/status.test.ts +107 -0
- package/src/cli/status.ts +73 -0
- package/src/cli/uninstall.test.ts +149 -0
- package/src/cli/uninstall.ts +123 -0
- package/src/cli/update.test.ts +126 -0
- package/src/cli/update.ts +76 -0
- package/src/conditions/predicates.test.ts +118 -0
- package/src/conditions/predicates.ts +79 -0
- package/src/conditions/table.test.ts +314 -0
- package/src/conditions/table.ts +215 -0
- package/src/engine.test.ts +476 -0
- package/src/engine.ts +165 -0
- package/src/frontmatter.test.ts +376 -0
- package/src/frontmatter.ts +303 -0
- package/src/intent.test.ts +20 -0
- package/src/intent.ts +25 -0
- package/src/match.test.ts +336 -0
- package/src/match.ts +146 -0
- package/src/render.golden/multi-rule-mixed-reasons.md +18 -0
- package/src/render.golden/no-reasons.md +6 -0
- package/src/render.golden/single-rule.md +7 -0
- package/src/render.test.ts +162 -0
- package/src/render.ts +103 -0
- package/src/result.test.ts +27 -0
- package/src/result.ts +16 -0
- package/src/select.test.ts +280 -0
- package/src/select.ts +116 -0
- package/src/state.test.ts +205 -0
- package/src/state.ts +78 -0
- package/src/types.test.ts +291 -0
- package/src/types.ts +219 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 MetalbolicX
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,164 @@
|
|
|
1
|
-
#
|
|
1
|
+
# pi-rules-md
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Pi extension that discovers markdown rule files and injects conditionally
|
|
4
|
+
matched ones into the system prompt. Rules can activate when the agent touches
|
|
5
|
+
files matching their globs, matching files exist in the project, a selected
|
|
6
|
+
tool or model matches, prompt-derived intent satisfies its glob condition, or
|
|
7
|
+
the rule is always active.
|
|
8
|
+
|
|
9
|
+
Functional core / imperative shell; pure functions and data, no classes.
|
|
10
|
+
|
|
11
|
+
## How it works
|
|
12
|
+
|
|
13
|
+
1. **Discovery** — every turn, rule files are discovered from
|
|
14
|
+
`<project>/.pi/rules/**/*.md` (when the project is trusted) and
|
|
15
|
+
`~/.pi/agent/rules/**/*.md` (always), parsed with a strict schema, and
|
|
16
|
+
cached by mtime.
|
|
17
|
+
2. **Matching** — each rule's activation is evaluated against the live
|
|
18
|
+
session: touched paths, existing files, selected tools, current model, and
|
|
19
|
+
prompt-derived intent.
|
|
20
|
+
3. **Selection** — pinned rule bodies are content-deduped, budgeted, and
|
|
21
|
+
ordered for presentation. On-demand rules are listed by path instead of
|
|
22
|
+
having their bodies injected.
|
|
23
|
+
4. **Injection** — the selected rules and on-demand index are rendered into a
|
|
24
|
+
single `pi-rules-md` system-prompt section via
|
|
25
|
+
`systemPromptOptions.sections` in `before_agent_start`. The section is
|
|
26
|
+
re-applied every turn while rules are active; identical content produces no
|
|
27
|
+
prompt diff, and the section is removed when nothing is active.
|
|
28
|
+
|
|
29
|
+
The complete rule-file contract is specified in [RULE-SPEC.md](RULE-SPEC.md).
|
|
30
|
+
|
|
31
|
+
## Quick start
|
|
32
|
+
|
|
33
|
+
```markdown
|
|
34
|
+
---
|
|
35
|
+
description: Follow repo conventions when editing core sources.
|
|
36
|
+
version: 1
|
|
37
|
+
globs:
|
|
38
|
+
- "src/**/*.ts"
|
|
39
|
+
- "!src/**/*.test.ts"
|
|
40
|
+
match: touched
|
|
41
|
+
priority: 10
|
|
42
|
+
---
|
|
43
|
+
Use tabs, keep functions pure, and never introduce classes in src/.
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Drop that file in `.pi/rules/` and start pi. When you edit any
|
|
47
|
+
non-test `src/**/*.ts` file, the rule is injected into the system prompt
|
|
48
|
+
for the session. Run `/rules` to see every rule, why each is active or
|
|
49
|
+
inactive, what the budget dropped, and any parse errors.
|
|
50
|
+
|
|
51
|
+
An `alwaysApply: true` rule skips matching entirely. `tools`, `branch`, and
|
|
52
|
+
`model` conditions are AND-ed with the activation mode; absent conditions mean
|
|
53
|
+
"any."
|
|
54
|
+
|
|
55
|
+
## Pinned and on-demand rules
|
|
56
|
+
|
|
57
|
+
Rules default to `load: pinned`: when active, their bodies are eligible for
|
|
58
|
+
injection and count toward the token budget. Use pinned rules for concise
|
|
59
|
+
guidance that should be available automatically whenever its conditions match.
|
|
60
|
+
|
|
61
|
+
Use `load: on-demand` for long references, catalogs, or specialized material
|
|
62
|
+
that should not be inserted into every prompt. A matched on-demand rule adds a
|
|
63
|
+
path-and-description entry under the **Reference rules available on demand**
|
|
64
|
+
index, not its body. The agent can fetch the full body with the `read_rule` tool
|
|
65
|
+
using that rules-relative path. On-demand bodies bypass pinned deduplication
|
|
66
|
+
and budgeting; the rendered index still contributes to the reported token
|
|
67
|
+
estimate.
|
|
68
|
+
|
|
69
|
+
```markdown
|
|
70
|
+
---
|
|
71
|
+
description: Database migration reference.
|
|
72
|
+
version: 1
|
|
73
|
+
globs:
|
|
74
|
+
- "docs/database/**/*.md"
|
|
75
|
+
load: on-demand
|
|
76
|
+
keywords: [migration, postgres]
|
|
77
|
+
---
|
|
78
|
+
Detailed migration procedures and reference material...
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Use `keywords` when a useful rule should be discoverable from task wording
|
|
82
|
+
that does not mention its file paths. A whole-word, case-insensitive keyword
|
|
83
|
+
match in the current turn prompt donates that rule's positive globs to
|
|
84
|
+
pre-loading. Keywords do not activate a rule by themselves, and donated globs
|
|
85
|
+
do not count as touched paths or expand the filesystem scan. Language words
|
|
86
|
+
also provide built-in intent globs (for example, `python` or `pytest` →
|
|
87
|
+
`*.py`; bare `go` intentionally has no trigger). See [RULE-SPEC.md §5](RULE-SPEC.md#5-activation-semantics)
|
|
88
|
+
for the full list and matching semantics.
|
|
89
|
+
|
|
90
|
+
## Token budget
|
|
91
|
+
|
|
92
|
+
Pinned rule bodies use an estimated token budget of 2000 by default. Override
|
|
93
|
+
it for the Pi process with a positive integer value, for example:
|
|
94
|
+
|
|
95
|
+
```sh
|
|
96
|
+
PI_RULES_MAX_TOKENS=3000 pi
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The shell parses the override with `parseInt`; a missing value, a value that
|
|
100
|
+
parses to `NaN`, or a non-positive parsed value uses the default. On-demand
|
|
101
|
+
bodies do not consume this budget; their rendered index contributes to the
|
|
102
|
+
final token estimate.
|
|
103
|
+
|
|
104
|
+
## Install
|
|
105
|
+
|
|
106
|
+
Recommended: install the npm package through the CLI wrapper:
|
|
107
|
+
|
|
108
|
+
```sh
|
|
109
|
+
npx pi-rules-md install
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The wrapper invokes the `pi` CLI for package changes; it never edits
|
|
113
|
+
`settings.json` directly.
|
|
114
|
+
|
|
115
|
+
| Command | Description |
|
|
116
|
+
| --- | --- |
|
|
117
|
+
| `pi-rules install` | Install pi-rules-md. |
|
|
118
|
+
| `pi-rules uninstall` | Remove pi-rules-md. |
|
|
119
|
+
| `pi-rules update` | Update an unpinned npm installation. |
|
|
120
|
+
| `pi-rules status` | Show installation status and available updates. |
|
|
121
|
+
| `pi-rules doctor` | Diagnose installation health. |
|
|
122
|
+
|
|
123
|
+
Common options: `--local` uses project scope for install, uninstall, and
|
|
124
|
+
update; `--purge` removes the package directory during uninstall; `--dry-run`
|
|
125
|
+
prints actions without changing anything; `--agent-dir <dir>` selects a Pi
|
|
126
|
+
agent directory.
|
|
127
|
+
|
|
128
|
+
For local development, load the source directly:
|
|
129
|
+
|
|
130
|
+
```sh
|
|
131
|
+
pi --extension ./index.ts
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
For a local-path install, run `pnpm build` first; the package manifest loads
|
|
135
|
+
`./dist/extension.js`. Source entries track the working tree; npm entries are
|
|
136
|
+
updated with `pi-rules update`.
|
|
137
|
+
|
|
138
|
+
## Commands
|
|
139
|
+
|
|
140
|
+
- `/rules` — report of the last evaluation: active rules with reasons,
|
|
141
|
+
inactive rules with reasons, budget drops with cause, parse/discovery
|
|
142
|
+
errors, and the injected content hash.
|
|
143
|
+
|
|
144
|
+
## Trust model
|
|
145
|
+
|
|
146
|
+
Project rules (`.pi/rules/`) load only when the project is trusted
|
|
147
|
+
(pi's `isProjectTrusted()`); global rules always load. An unlistable or
|
|
148
|
+
unreadable rule source is reported via `/rules` and never breaks the
|
|
149
|
+
session.
|
|
150
|
+
|
|
151
|
+
## Development
|
|
152
|
+
|
|
153
|
+
```sh
|
|
154
|
+
pnpm install
|
|
155
|
+
pnpm typecheck # strict TS, no emit
|
|
156
|
+
pnpm test # vitest: co-located unit tests, goldens, property tests, perf guard
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
- Functional core: `src/` (types, frontmatter parser, conditions, match,
|
|
160
|
+
select, render, engine, state reducer) — pure, total, property-tested.
|
|
161
|
+
- Imperative shell: `shell/` + `index.ts` (discovery, event collectors,
|
|
162
|
+
injection, wiring) — thin, dependency-injected, structurally mirrored
|
|
163
|
+
against the pi SDK types (no runtime pi import).
|
|
164
|
+
- ODD feature tracking: [odd/tasks/pi-rules-md.md](odd/tasks/pi-rules-md.md).
|
package/RULE-SPEC.md
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# Rule Format v1.1 (RULE-SPEC)
|
|
2
|
+
|
|
3
|
+
Normative specification for pi-rules-md rule files and runtime behavior.
|
|
4
|
+
Frontmatter shape and value constraints are enforced by the parser
|
|
5
|
+
(`src/frontmatter.ts`); the discovery, matching, selection, and rendering
|
|
6
|
+
pipeline applies the remaining semantics.
|
|
7
|
+
|
|
8
|
+
## 1. File placement and identity
|
|
9
|
+
|
|
10
|
+
| Source | Directory | Loaded when |
|
|
11
|
+
| --- | --- | --- |
|
|
12
|
+
| Project rules | `<project>/.pi/rules/**/*.md` | the project is trusted (`ctx.isProjectTrusted()`) |
|
|
13
|
+
| Global rules | `~/.pi/agent/rules/**/*.md` | always |
|
|
14
|
+
|
|
15
|
+
- Discovery is recursive (`**/*.md`); non-`.md` files are ignored; VCS
|
|
16
|
+
internals (`.git/**`) and dependency trees (`node_modules/**`) are never
|
|
17
|
+
scanned.
|
|
18
|
+
- **Rule id** = file stem, disambiguated across the whole merged pass:
|
|
19
|
+
on collision the first file in deterministic order (project before
|
|
20
|
+
global; sorted by path within each source) keeps the bare stem; later
|
|
21
|
+
collisions get `-2`, `-3`, … (e.g. project `a.md` → `a`, global
|
|
22
|
+
`a.md` → `a-2`).
|
|
23
|
+
- Project rules come first; within a source, files are sorted by path.
|
|
24
|
+
- An unlistable source (e.g. permission error) produces a
|
|
25
|
+
`source-unreadable` error entry for `/rules` and contributes no rules;
|
|
26
|
+
it never breaks the session. An unreadable file produces a
|
|
27
|
+
`read-failure` entry. A file that vanishes mid-scan is skipped
|
|
28
|
+
silently.
|
|
29
|
+
|
|
30
|
+
## 2. Document shape
|
|
31
|
+
|
|
32
|
+
A rule file is YAML frontmatter plus a markdown body:
|
|
33
|
+
|
|
34
|
+
```markdown
|
|
35
|
+
---
|
|
36
|
+
description: One line explaining what the rule enforces.
|
|
37
|
+
version: 1
|
|
38
|
+
globs:
|
|
39
|
+
- "src/**/*.ts"
|
|
40
|
+
- "!src/**/*.test.ts"
|
|
41
|
+
match: touched
|
|
42
|
+
tools: [edit, write]
|
|
43
|
+
branch: main
|
|
44
|
+
model: sonnet
|
|
45
|
+
priority: 10
|
|
46
|
+
enforcement: enforce
|
|
47
|
+
load: pinned
|
|
48
|
+
keywords: [typescript, style]
|
|
49
|
+
alwaysApply: false
|
|
50
|
+
---
|
|
51
|
+
The rule body: instructions injected verbatim into the system prompt
|
|
52
|
+
section when the rule is active.
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## 3. Keys
|
|
56
|
+
|
|
57
|
+
| Key | Type | Required | Default | Meaning |
|
|
58
|
+
| --- | --- | --- | --- | --- |
|
|
59
|
+
| `description` | string | ✔ | — | Shown in `/rules`; never injected. |
|
|
60
|
+
| `version` | number | ✔ | — | **Exactly `1`** (YAML integer `1`; `1.0` is rejected by strict equality). |
|
|
61
|
+
| `globs` | string[] | see §4 | — | Glob patterns (OR semantics); `!`-prefixed entries are negations. |
|
|
62
|
+
| `match` | `touched` \| `exists` \| `both` | ✘ | `both` | Activation mode (§5). |
|
|
63
|
+
| `tools` | string[] | ✘ | — | Active only when the current tool selection intersects. |
|
|
64
|
+
| `branch` | string | ✘ | — | Active only on this git branch (v1: no live branch source in pi; reserved). |
|
|
65
|
+
| `model` | string | ✘ | — | Active only when the current model name matches. |
|
|
66
|
+
| `priority` | integer | ✘ | `0` | Higher wins in selection; ties keep discovery order (stable). |
|
|
67
|
+
| `enforcement` | `enforce` \| `recommend` \| `inform` | ✘ | `inform` | Parsed and validated; does NOT affect selection or rendering in v1 (reserved for future emphasis). |
|
|
68
|
+
| `load` | `pinned` \| `on-demand` | ✘ | `pinned` | Loading policy metadata; materialized by parsing and does not itself alter condition matching. |
|
|
69
|
+
| `keywords` | string[] | ✘ | — | Intent-donation metadata; trimmed on parse and does not itself gate matching. When a keyword matches a whole word in the current turn prompt (case-insensitive), this rule's positive globs are donated to intent pre-loading. Empty arrays are allowed. |
|
|
70
|
+
| `alwaysApply` | boolean | ✘ | `false` | When true the rule bypasses ALL condition matching (still subject to the trust gate for project rules). |
|
|
71
|
+
|
|
72
|
+
**Unknown keys are rejected** (strict schema). Types must match exactly
|
|
73
|
+
(`globs` and `keywords` entries are strings; `load` is exactly `pinned` or
|
|
74
|
+
`on-demand`; `priority` is an integer; `version` is the number `1`).
|
|
75
|
+
|
|
76
|
+
## 4. Activation constraints
|
|
77
|
+
|
|
78
|
+
A rule must declare at least one activation trigger: non-empty `globs`,
|
|
79
|
+
non-empty `tools`, `branch`, `model`, or `alwaysApply: true`. Otherwise
|
|
80
|
+
the parse fails with `no-activation-condition`.
|
|
81
|
+
|
|
82
|
+
Special case: an explicit `match` key **requires** `globs`
|
|
83
|
+
(`match-without-globs`) — implicit default `match` without globs is fine
|
|
84
|
+
as long as another trigger exists.
|
|
85
|
+
|
|
86
|
+
Glob entries are picomatch patterns evaluated against cwd-relative,
|
|
87
|
+
`/`-separated, normalized paths. Positive patterns OR; negations
|
|
88
|
+
(`!pattern`) apply per-rule: a path is a candidate when it matches ≥1
|
|
89
|
+
positive pattern, then excluded if it matches any negation.
|
|
90
|
+
|
|
91
|
+
## 5. Activation semantics
|
|
92
|
+
|
|
93
|
+
- `touched` — active when an agent tool's pre-execution arguments name a path
|
|
94
|
+
matching the globs this session. Paths are captured from `tool_execution_start`
|
|
95
|
+
events; first occurrence wins, order preserved.
|
|
96
|
+
- `exists` — active when any path matching the globs exists on disk
|
|
97
|
+
(checked via the scan seam each evaluation).
|
|
98
|
+
- `both` — touched **OR** exists (the default).
|
|
99
|
+
- Condition keys (`tools`, `branch`, `model`) are AND-ed on top of the
|
|
100
|
+
activation mode; absent means "any".
|
|
101
|
+
- `alwaysApply: true` skips activation and conditions entirely.
|
|
102
|
+
- Before evaluation, the current turn prompt is checked for whole-word, case-insensitive language names: python/pytest → `*.py`, rust → `*.rs`, typescript → `*.ts`, javascript → `*.js`, golang → `*.go`, kotlin → `*.kt`, java → `*.java`, csharp → `*.cs`, ruby → `*.rb`. There is intentionally no bare `go` language trigger.
|
|
103
|
+
- The current turn prompt is also checked for whole-word, case-insensitive matches against each rule's `keywords`; a hit donates that rule's positive globs to the intent set. Keyword metadata does not activate the rule by itself. Intent globs can satisfy a rule's `globs` condition independently of touched/existing path matches, but do not bypass other conditions.
|
|
104
|
+
- **Intent globs never enter the context path pool** (`touchedPaths` or `existingPaths`) and do not expand the filesystem scan. They are a separate input to the globs-condition check.
|
|
105
|
+
|
|
106
|
+
### Pre-execution path capture
|
|
107
|
+
|
|
108
|
+
`tool_execution_start` is observed before tool execution. The event's `args` are
|
|
109
|
+
untrusted; missing, malformed, non-object, or non-string path arguments and
|
|
110
|
+
unknown tools are ignored. Recognized paths are normalized and deduplicated by
|
|
111
|
+
the session-state reducer.
|
|
112
|
+
|
|
113
|
+
| Pi built-in | Captured argument | Behavior |
|
|
114
|
+
| --- | --- | --- |
|
|
115
|
+
| `read`, `edit`, `write` | `path` | Captured as a file path. |
|
|
116
|
+
| `grep` | optional `path` | Captured as the search scope when present. |
|
|
117
|
+
| `find` | optional `path` | Captured as the glob-search scope when present. |
|
|
118
|
+
| `bash` | none | No capture: Pi's schema has only `command` and `timeout`. |
|
|
119
|
+
|
|
120
|
+
These keys are verified against the installed Pi schemas in
|
|
121
|
+
`dist/core/tools/{read,edit,write,grep,find,bash}.js`. Unlike OpenCode, Pi's
|
|
122
|
+
bash tool has no `workdir` argument. This is a deliberate runtime divergence:
|
|
123
|
+
we do not parse paths from shell command strings.
|
|
124
|
+
|
|
125
|
+
## 6. Selection, budget, rendering
|
|
126
|
+
|
|
127
|
+
- Every discovered rule receives a monotonic `index` in discovery order:
|
|
128
|
+
project rules first, then global rules, retaining each source's order.
|
|
129
|
+
- Content-level dedupe: rules with byte-identical full content are
|
|
130
|
+
deduplicated; higher `priority` wins, and equal priorities keep the later
|
|
131
|
+
discovery `index`. Losers are dropped with cause `duplicate`. This
|
|
132
|
+
supersedes v1's first-occurrence-wins behavior.
|
|
133
|
+
- Budget candidates are considered by `priority` DESC, then `index` ASC.
|
|
134
|
+
Greedy fill admits each candidate that fits and skips a non-fitting rule
|
|
135
|
+
while continuing to later candidates. If candidates exist, the first is
|
|
136
|
+
always kept even when it exceeds the budget; this supersedes v1's stop-at-
|
|
137
|
+
first-non-fit behavior.
|
|
138
|
+
- After budget selection, presentation order is unconditional rules first
|
|
139
|
+
(`alwaysApply: true` or no declared activation conditions), then
|
|
140
|
+
`priority` DESC, then `index` ASC.
|
|
141
|
+
- Token budget defaults to 2000 estimated tokens (`ceil(chars/4)` of each
|
|
142
|
+
pinned rule body's content). The shell parses `PI_RULES_MAX_TOKENS` with
|
|
143
|
+
`parseInt`; an absent value, a value that parses to `NaN`, or a non-positive
|
|
144
|
+
parsed value uses the default. A positive parsed integer overrides it.
|
|
145
|
+
- On-demand rules bypass pinned-rule dedupe and budget selection. Their bodies
|
|
146
|
+
are never injected or counted against that budget. Instead, the rendered
|
|
147
|
+
section appends an index headed exactly
|
|
148
|
+
`## Reference rules available on demand`, with one `- <rules-relative-path>
|
|
149
|
+
— <description> (fetch with the read_rule tool)` line per rule. No index is
|
|
150
|
+
rendered when no on-demand rule
|
|
151
|
+
matched. Each rule's `relativePath` is a POSIX path relative to its source's
|
|
152
|
+
rules root, never an absolute path.
|
|
153
|
+
- `read_rule` looks up a discovered rule by exact `relativePath` and returns
|
|
154
|
+
its parsed `content` body directly; it performs no filesystem read. Absolute
|
|
155
|
+
paths (POSIX or Windows) and any name containing a `..` segment, as well as
|
|
156
|
+
unknown names, return `Unknown rule: <name>`. The FROZEN tool description is:
|
|
157
|
+
`Fetch the full content of a reference rule by its rules-relative path. Use
|
|
158
|
+
when a "Reference rules available on demand" index entry is relevant to the
|
|
159
|
+
current task.`
|
|
160
|
+
- The evaluation trace partitions matched rules into pinned `active` and
|
|
161
|
+
`onDemand`, reports all matched rule file paths (including on-demand paths),
|
|
162
|
+
and estimates tokens over the complete rendered block including its index.
|
|
163
|
+
- Rendered as one `pi-rules-md` system-prompt section: `# Rules` header,
|
|
164
|
+
framing line, one entry per selected pinned rule (`## <description>`), optional
|
|
165
|
+
`> Triggered by: <reasons>` when reasons exist, and `---` separators.
|
|
166
|
+
Pinned rule bodies are injected verbatim with trailing blank-line
|
|
167
|
+
normalization; the section ends with exactly one trailing newline.
|
|
168
|
+
- The section is re-applied on **every** agent turn while any pinned or
|
|
169
|
+
on-demand rule is active (Pi rebuilds its prompt sections per turn); identical
|
|
170
|
+
content produces no prompt diff (zero churn). When no rule is active and one
|
|
171
|
+
was previously injected, the section is removed.
|
|
172
|
+
|
|
173
|
+
### v1 behavior superseded by v1.1
|
|
174
|
+
|
|
175
|
+
- Equal-content pinned rules no longer keep the first occurrence: the higher
|
|
176
|
+
priority wins, with the later discovery index breaking priority ties.
|
|
177
|
+
- Budget selection no longer stops at the first non-fitting candidate. It skips
|
|
178
|
+
that candidate and continues; the first priority-sorted candidate is retained
|
|
179
|
+
even when it exceeds the budget.
|
|
180
|
+
- The pinned entry heading is `## <description>` (not `## <id>`), matching the
|
|
181
|
+
renderer and its contract tests.
|
|
182
|
+
|
|
183
|
+
## 7. Errors (visible via `/rules`)
|
|
184
|
+
|
|
185
|
+
Parse and discovery errors never break the session; they are collected
|
|
186
|
+
and reported: `yaml-syntax`, `missing-frontmatter`, `unknown-key`,
|
|
187
|
+
`invalid-version`, `missing-description`, `invalid-value` (field type),
|
|
188
|
+
`match-without-globs`, `no-activation-condition`, `read-failure`,
|
|
189
|
+
`source-unreadable`. Error entries carry the offending file (or
|
|
190
|
+
directory for `source-unreadable`) and are reused from the mtime cache
|
|
191
|
+
while the file is unchanged.
|