@ibartel74/pi-automode-ext 1.0.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/CHANGELOG.md ADDED
@@ -0,0 +1,81 @@
1
+ # Changelog
2
+
3
+ This is a fork of [`czottmann/pi-automode`](https://github.com/czottmann/pi-automode) (MIT). 1.0.0 is this fork's first release; it includes upstream 1.16.0 plus the changes below. Earlier version links point at the upstream repository.
4
+
5
+ All notable changes to this project are documented in this file.
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [1.0.0] - 2026-09-20
10
+
11
+ ## New features
12
+
13
+ - **Jev classifier backend (OpenRouter + TypeSafe-native)** — Classify with TypeSafe's Jev decision model. One typed-questions call replaces both LLM stages; decisions are computed locally from calibrated probabilities and fail closed. Two transports: `openrouter/typesafe/jev-1.13` uses OpenRouter's Decisions API (key from `OPENROUTER_API_KEY` or a registered `openrouter` provider key; the alpha endpoint intermittently answers a valid key with 401 "User not found", so a 401 is retried once before failing closed), and `typesafe/jev-latest` calls TypeSafe's native System One API directly (`TYPESAFE_API_KEY` or a registered `typesafe` provider key).
14
+ - **Interactive confirmation of classifier blocks** — Classifier blocks (`soft_deny`, `hard_deny`, and fail-closed errors) now prompt the user for confirmation when a UI is available instead of stopping the agent outright, like Claude Code's auto mode. Approval allows the action and is recorded as a `user-confirmed` decision with a `uc:` status counter; declining or running without a UI blocks as before. On by default; opt out with `autoMode.interactiveConfirm: false`. Deterministic denies remain unconditional.
15
+ - **Persistent approvals in the confirmation dialog** — The classifier-block dialog now offers "Always allow" choices that persist an exact-match `permissions.allow` rule to the global config or the project-local `.pi/automode.local.json`, reloading the effective config so the rule applies immediately.
16
+ - **Dev checkouts editable under auto mode** — The deterministic safety-control path check no longer hard-denies every path containing `/pi-automode/`; it now matches installed locations (the `~/.pi/agent/extensions/` prefix and `node_modules` vendoring), so a source checkout of this extension stays editable under auto mode.
17
+
18
+ ## [1.16.0] - 2026-09-07
19
+
20
+ ## New features
21
+
22
+ - **[Bounded existing-file authorization](docs/defaults.md#soft_deny)** — Allow user authorization for pre-existing local-file changes that names task, worktree, path scope, and allowed operation. (#32)
23
+
24
+ ## Bug fixes
25
+
26
+ - **OpenCode classifier routing** — Add OpenCode session headers to every classifier completion path. Preserve Pi's header precedence and exact host matching. (#35)
27
+ - **OMP 18 project trust compatibility** — Treat runtimes without `isProjectTrusted()` as untrusted. Prevent startup and configuration commands from throwing. (#34)
28
+
29
+ ## [1.15.0] - 2026-08-28
30
+
31
+ ## Bug fixes
32
+
33
+ - **OMP 18 classifier compatibility** — Support OMP 18 model registries that lack `complete()` and `getProvider()`. Load the legacy completion API only for these registries. Keep current Pi on its runtime registry path so extension-registered providers remain available. Thanks, @NarryG! (#29)
34
+
35
+ ## [1.14.0] - 2026-08-27
36
+
37
+ ## Bug fixes
38
+
39
+ - **Classifier stream timeout** — Apply `classifierTimeoutMs` to the full response stream. Provider behavior cannot keep classifier calls pending after the deadline. Parent cancellation remains active. Reject values above the Node.js timer limit. (#30)
40
+ - **OS temp-directory deletes** — Stop hard-denying recursive-delete subtrees under `os.tmpdir()` and `/tmp`. On macOS these resolve into `/private/tmp` and `/private/var/folders`, which matched the `/private` system root and blocked every temp cleanup. Deleting a temp root itself stays blocked. (#31)
41
+ - **Validated temp-root declarations** — Derive the exempt temp roots only from launcher-declared values that stay safe: reject values such as `/`, empty strings, aliases of `HOME`, `/`, or a system root, and ancestors of `HOME`. Without validation, a malformed `TMPDIR` could disable deterministic denials for protected targets, and a broad `permissions.allow` rule could then allow the action without classifier review. Recompute candidates when the effective tmpdir changes. (#31)
42
+
43
+ ## [1.13.0] - 2026-08-25
44
+
45
+ ## New features
46
+
47
+ - **Bash AST analysis** — Replace handwritten shell parsing with `unbash`. Permission and hard-deny checks now inspect command structure, nested commands, wrappers, redirects, and malformed input. Bash allow rules require complete structural coverage and fail closed when analysis is unsafe. (#26)
48
+ - **Extension-owned global config** — Store global settings at `~/.pi/agent/extensions/pi-automode/config.json`. Migrate the legacy file automatically, preserve a safe fallback after migration errors, and report conflicts through notifications and diagnostics. (#27)
49
+
50
+ ## Bug fixes
51
+
52
+ - **Conservative permission rules** — Malformed deny and ask patterns block actions. Malformed patterns do not expand allow rules. Permission checks examine each Bash subcommand and normalize whitespace. Path checks resolve symlinks and normalize `file://` and Windows paths. (#22)
53
+ - **Recursive deletion hard-deny** — Detect uppercase flags and GNU abbreviations for recursive `rm`, including commands behind `command`, `exec`, and `env`. Parse the `--` delimiter and shell tilde expansion. Protect Linux and macOS system roots without blocking active-home subdirectories, `/opt`, or `/srv`. (#23)
54
+ - **Global Pi safety-control paths** — Hard-deny direct writes and edits to `~/.pi/agent/extensions/`, `~/.pi/agent/settings.json`, and `~/.pi/agent/settings/`. Resolve case variants and symlink targets before matching. (#24)
55
+ - **Case-insensitive protected paths** — Match protected paths without case distinctions and normalize Unicode spellings. This closes path bypasses on case-insensitive filesystems. (#25)
56
+
57
+ ## [1.12.0] - 2026-08-23
58
+
59
+ ## New features
60
+
61
+ - **Deterministic permission allows** — Add user-owned `permissions.allow` patterns that skip classifier review after deterministic checks pass. Accepted ask rules still require classifier review. Thank you, @sergeykonkin! (#14)
62
+ - **Configurable classifier request timeout** — new `autoMode.classifierTimeoutMs` setting. The default is 20000 ms. The timeout applies to each classifier request. The fast stage and the detailed stage each have their own budget. A request that exceeds the timeout is aborted. Auto mode fails closed and blocks the action.
63
+ - **Read-only agent diagnostics** — Add `automode_inspect` tool for status, configuration, defaults, and recent denial metadata. Thank you, @blalor! (#11)
64
+
65
+ ## Bug fixes
66
+
67
+ - **Reject invalid config values** — Invalid boolean and log config values (e.g. `enabled: 0`, `log.enabled: 1`) are now rejected at merge time instead of being applied with diagnostics only. (#20)
68
+ - **Preserve defaults for malformed rule lists** — A malformed `hard_deny` entry like `[42]` no longer strips all built-in hard-deny rules. Malformed entries are rejected and defaults preserved conservatively. (#21)
69
+ - **Runtime classifier providers** — Dispatch classifier calls through Pi's runtime model registry so providers registered with `pi.registerProvider()` work immediately. Preserve normalized reasoning and header-only authentication on the temporary simple-completion bridge. (#15)
70
+ - **Bounded wildcard matching** — Replace regex-based permission and denied-path globs with a linear-time matcher. Reject oversized patterns and fail closed for oversized runtime inputs. (#19)
71
+ - **Complete classifier action input** — Send the exact current tool input to both classifier stages in a dedicated message. Block the action if it cannot fit without truncation. (#17)
72
+ - **Path policy normalization** — Use Pi-compatible resolution for file-tool paths, including file URLs, `@` and tilde aliases, and read fallback names. Enforce denied paths across omitted and recursive search scopes and both sides of symlink aliases. (#18)
73
+ - **Project config trust gate** — Ignore `.pi/automode.local.json` and `.pi/automode.json` until Pi trusts the project. Apply the trust gate during startup and config reloads. (#16)
74
+ [Unreleased]: https://github.com/ibartel/pi-automode-ext/compare/v1.0.0...HEAD
75
+ [1.0.0]: https://github.com/ibartel/pi-automode-ext/compare/v1.16.0...v1.0.0
76
+
77
+ [1.16.0]: https://github.com/czottmann/pi-automode/compare/v1.15.0...v1.16.0
78
+ [1.15.0]: https://github.com/czottmann/pi-automode/compare/v1.14.0...v1.15.0
79
+ [1.14.0]: https://github.com/czottmann/pi-automode/compare/v1.13.0...v1.14.0
80
+ [1.13.0]: https://github.com/czottmann/pi-automode/compare/v1.12.0...v1.13.0
81
+ [1.12.0]: https://github.com/czottmann/pi-automode/compare/v1.11.0...v1.12.0
package/LICENSE.md ADDED
@@ -0,0 +1,22 @@
1
+ # MIT License
2
+
3
+ Copyright (c) 2026 Carlo Zottmann
4
+ Copyright (c) 2026 Ingo Bartel
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,262 @@
1
+ # pi-automode-ext
2
+
3
+ Claude Code-style auto mode for Pi.
4
+
5
+ This is a fork of [`czottmann/pi-automode`](https://github.com/czottmann/pi-automode) by Ingo Bartel, published as `@ibartel74/pi-automode-ext`.
6
+
7
+ This is a guardrail extension. It intercepts agent tool calls before execution and blocks actions that match permission deny rules, deterministic hard-deny checks, or the auto-mode classifier's block decision.
8
+
9
+ It is not a sandbox. Extensions run in the Pi process. A malicious extension can do anything that your user account can do.
10
+
11
+ Pi-automode does not guard user `!` or `!!` shell commands. It guards only agent tool calls. Use it to reduce unsafe autonomous tool use. Do not use it as an OS security boundary.
12
+
13
+ ## Compatibility
14
+
15
+ Pi-automode supports Pi and Oh My Pi (OMP) 18. It automatically uses OMP's legacy completion API. The integration needs no OMP-specific configuration.
16
+
17
+ ## Differences from upstream
18
+
19
+ Everything in upstream 1.16.0 is included. On top of that, this fork adds:
20
+
21
+ - **Jev classifier backend** — classify with TypeSafe's Jev decision model via `openrouter/typesafe/jev-1.13` (OpenRouter Decisions API) or `typesafe/jev-latest` (native System One API). One typed-questions call replaces both LLM stages; decisions are computed locally from calibrated probabilities and fail closed. See [docs/configuration.md](docs/configuration.md).
22
+ - **Interactive confirmation** — classifier blocks prompt for confirmation when a UI is available (like Claude Code's auto mode) instead of stopping the agent outright. On by default; opt out with `autoMode.interactiveConfirm: false`. Deterministic denies remain unconditional.
23
+ - **Persistent approvals** — the confirmation dialog offers "Always allow" choices that persist an exact-match `permissions.allow` rule to the global config or the project-local `.pi/automode.local.json` and apply it immediately.
24
+ - **Editable dev checkouts** — the deterministic safety-control path check no longer hard-denies a source checkout of this extension under auto mode; only installed locations stay protected.
25
+
26
+ ## Install
27
+
28
+ From npm:
29
+
30
+ ```bash
31
+ pi install npm:@ibartel74/pi-automode-ext
32
+ ```
33
+
34
+ From a local checkout:
35
+
36
+ ```bash
37
+ pi install .
38
+ ```
39
+
40
+ For one run from a local checkout:
41
+
42
+ ```bash
43
+ pi -e ./extensions/auto-mode.ts
44
+ ```
45
+
46
+ ## Commands
47
+
48
+ ```text
49
+ /automode status # current state, rules, and classifier
50
+ /automode on # re-enable for this session
51
+ /automode off # disable for this session
52
+ /automode reload # reload config from disk
53
+ /automode reset # reset denial counters only
54
+ /automode defaults # print the built-in rule lists
55
+ /automode config # effective config, resolved log file path, + diagnostics
56
+ /automode denials # denial history for this session
57
+ /automode model # open classifier model selector and save to ~/.pi/agent/extensions/pi-automode/config.json
58
+ /automode model provider/model-id # save classifier model to ~/.pi/agent/extensions/pi-automode/config.json
59
+ ```
60
+
61
+ `/auto-mode` is an alias.
62
+
63
+ ## Agent diagnostics
64
+
65
+ The package registers one model-callable, read-only tool:
66
+
67
+ `automode_inspect` accepts one `action`:
68
+
69
+ - `status`: active state and counters
70
+ - `config`: active effective configuration, log path, and diagnostics
71
+ - `defaults`: built-in rule lists
72
+ - `denials`: recent denial timestamps, kinds, and tool names
73
+
74
+ The tool reads the same in-memory configuration and state that the guardrail enforces. Permission and deterministic checks run before the bypass.
75
+
76
+ The bypass does not change automode counters, persisted state, or observability logs. The extension verifies the source of the tool before it applies the exemption. Thus, a name collision does not exempt a tool from another extension.
77
+
78
+ The tool cannot enable or disable auto mode. It cannot reload configuration, reset state, select a model, or change configuration.
79
+
80
+ Pi sends tool output to the current model. The `status` and `denials` views omit denial reasons and action payloads.
81
+
82
+ If a diagnosis requires a reason, inspect a known-safe entry in the local observability log. The `config` view includes effective rule text. It removes raw JSON parser details from diagnostics.
83
+
84
+ Do not put credentials or other secrets in automode rules.
85
+
86
+ The bundled `automode-diagnostics` skill uses this tool to diagnose unexpected decisions without asking the user to copy output from slash commands. Configuration edits and automode state changes remain user-controlled. See [Agent diagnostics](docs/diagnostics.md) for the inspection contract, privacy limits, and diagnosis workflow.
87
+
88
+ ## Status line
89
+
90
+ When the Pi TUI is available, the extension renders a persistent status line:
91
+
92
+ ```text
93
+ AM● a:12 d:2 ca:5 cd:1
94
+ ```
95
+
96
+ - `AM` — auto-mode prefix. `●` means enabled. `○` means disabled through configuration or `/automode off`.
97
+ - `a:` — actions allowed so far (checked minus denied).
98
+ - `d:` — actions denied so far, for any reason (permission rule, deterministic hard-deny, or classifier).
99
+ - `ca:` / `cd:` — classifier decisions split into allowed and denied. These segments appear after the first classifier call. `d:` counts all denials, so `d:` is always `>= cd:`.
100
+ - `uc:` — actions allowed after you approved a classifier block in an interactive confirmation. Appears after the first approval.
101
+
102
+ ## Jev classifier (OpenRouter or TypeSafe-native)
103
+
104
+ Instead of an LLM, pi-automode can classify with TypeSafe's Jev decision model through OpenRouter's Decisions API. Jev answers typed questions with calibrated probabilities; pi-automode makes the allow/block decision locally from them.
105
+
106
+ Set `OPENROUTER_API_KEY` in Pi's environment (from <https://openrouter.ai/keys>). When the variable is absent, pi-automode falls back to any `openrouter` provider key registered in Pi's model registry (for example one configured through OMP). Then run:
107
+
108
+ ```text
109
+ /automode model openrouter/typesafe/jev-1.13
110
+ ```
111
+
112
+ `openrouter/~typesafe/jev-latest` also works, but the alias moves between releases; a safety classifier should be reproducible. See [Configuration](docs/configuration.md#jev-classifier-openrouter) for how Jev answers map to decisions.
113
+
114
+ With a TypeSafe API key instead, bypass OpenRouter entirely: set `TYPESAFE_API_KEY` in Pi's environment (from <https://console.typesafe.ai/keys>) and run:
115
+
116
+ ```text
117
+ /automode model typesafe/jev-latest
118
+ ```
119
+
120
+ `typesafe/...` specs call TypeSafe's System One API directly. When the variable is absent, pi-automode falls back to any `typesafe` provider key registered in Pi's model registry. GUI-launched sessions (for example Orca) do not inherit shell profile variables — set the variable where the host application sees it, for example `launchctl setenv TYPESAFE_API_KEY <key>` before launching it.
121
+
122
+ ## Docs
123
+
124
+ - [Configuration](docs/configuration.md)
125
+ - [Defaults and rule-list behavior](docs/defaults.md)
126
+ - [Auto-mode classifier flow](docs/automode-classifier-flow.md)
127
+ - [Observability logging](docs/observability-logging.md)
128
+ - [Architecture decisions](docs/adr/INDEX.md)
129
+
130
+ ## What runs before the classifier
131
+
132
+ The extension blocks these before any allow or classifier decision:
133
+
134
+ - `permissions.deny` matches
135
+ - declined `permissions.ask` matches
136
+ - shell profile writes
137
+ - SSH `authorized_keys` writes
138
+ - cron, launch agent, and system service persistence
139
+ - TLS/certificate/auth weakening patterns
140
+ - root, home, and system-path destructive deletes. Subtrees of validated launcher-declared temp directories are treated as disposable. Declared roots that alias `HOME`, `/`, or a system root, or that contain `HOME`, are rejected instead.
141
+ - edits to `.pi/automode*`, `.pi` auto-mode files, and this extension's safety-control files
142
+
143
+ After these checks, pi-automode applies `permissions.allow`. Protected `write` and `edit` targets continue to the classifier.
144
+
145
+ Accepted ask rules also continue to the classifier. They cannot use an allow tier. Pi-automode then allows the read-only tools `read`, `grep`, `find`, and `ls`. Every remaining action goes to the classifier.
146
+
147
+ A `permissions.allow` rule intentionally skips classifier policy, including classifier `hard_deny` rules. Use narrow patterns and user-owned configuration. Deterministic hard-deny and path controls remain unconditional.
148
+
149
+ Set `classifyReadOnlyTools: true` (default `false`) to route read-only tools through the classifier. This configuration value increases model calls, latency, and session cost.
150
+
151
+ Pi-automode blocks a `deniedPaths` match before classifier review or an allow tier. Thus, file tools cannot send matching secret or system paths to the model.
152
+
153
+ The list does not govern `bash`. The classifier and deterministic hard-deny checks govern shell access to these paths.
154
+
155
+ The value `allowInsideWorkingDirectory: true` allows file access inside the working directory locally. Pi-automode sends all outside file access to the classifier, including reads.
156
+
157
+ Classification starts with a conservative one-token filter. If the filter requests review, pi-automode runs structured review.
158
+
159
+ Both stages receive the complete current tool input in a dedicated message. Transcript truncation cannot remove action content. If the exact input cannot fit in the classifier context, auto mode blocks the call.
160
+
161
+ Both stages use a classifier-specific session key. They request short cache retention from providers that support it. A missing model, provider failure, or malformed response blocks the action.
162
+
163
+ Pi-automode parses Bash structure with `unbash` before permission and deterministic hard-deny checks. The analysis includes nested commands and literal shell-wrapper scripts. A Bash parse error blocks the action.
164
+
165
+ ## Interactive confirmation of classifier blocks
166
+
167
+ When the classifier blocks an action, pi-automode can ask you instead of stopping the agent outright — like Claude Code's auto mode. The dialog shows the block tier, the classifier's reason, and the action summary, with these choices:
168
+
169
+ - **Allow once** — run the action now.
170
+ - **Always allow (global)** — also persist an exact-match `permissions.allow` rule to the global config, so identical future actions skip classifier review everywhere.
171
+ - **Always allow (this project)** — the same rule, persisted to the project's `.pi/automode.local.json`. In an untrusted project the rule is written but stays inert until the project is trusted.
172
+ - **Custom allow rule (this project / global)** — edit the rule before saving. The dialog is prefilled with the exact rule; you decide the scope, e.g. changing `bash(npm test)` to `bash(npm test*)`. The rule must stay scoped to the same tool; an invalid entry warns and re-prompts, and cancelling the input returns to the choice dialog.
173
+ - **Block** — decline; the block keeps the classifier's reason. Cancelling the dialog also blocks.
174
+
175
+ The exact-match "always allow" choices persist the literal command or path: commands or paths containing wildcards or pattern syntax are not offered as exact permanent choices. Custom rules are the escape hatch when you want a wider pattern — the wildcard is always typed by you, never inferred. Tools without a patternable argument only offer allow-once and block. Saved rules are `permissions.allow` entries, so they skip classifier review — including classifier `hard_deny` rules — for future matching actions.
176
+
177
+ This applies to every classifier block: `soft_deny`, `hard_deny`, and fail-closed errors (classifier unavailable, malformed responses, exceeded budgets). It never applies to deterministic blocks: `permissions.deny`, deterministic hard-deny checks, and `deniedPaths` remain unconditional.
178
+
179
+ Confirmation is on by default when a UI is available. Without a UI (print or JSON mode) the block stands; nothing is silently allowed. Set `autoMode.interactiveConfirm: false` to make classifier blocks unconditional again.
180
+
181
+ Approvals stay auditable: they are logged as `user-confirmed` allow decisions in the observability log and counted as `uc:` in the status line.
182
+
183
+ ## Examples
184
+
185
+ - `examples/automode.local.json`: copy to `.pi/automode.local.json` in a project and edit the domains, buckets, and source-control org.
186
+
187
+ ### Pro tip
188
+
189
+ The default rule set might cause denials like this:
190
+
191
+ > Auto mode blocked subagent: Delegated workers would modify pre-existing files outside the user's bounded authorization, including migrations, CLI code, and approval documentation.
192
+
193
+ This is to be expected as the included classifier rules are very conservative and err on the side of caution! To explicitly give your robot more leeway, adjust your global `autoMode.allow` entry (or its project-level counterparts). For example, my personal `~/.pi/agent/extensions/pi-automode/config.json` file looks like this:
194
+
195
+ ```json
196
+ {
197
+ "autoMode": {
198
+ "allow": [
199
+ "$defaults",
200
+ "Creating, modifying, and deleting local files within the Git repository or worktree. This includes pre-existing source code, migrations, CLI code, tests, and project documentation. This permission applies only to local implementation work and excludes files outside the assigned repository/worktree, external systems, credentials, safety controls, and Git history changes."
201
+ ],
202
+
203
+ }
204
+ }
205
+ ```
206
+
207
+ ## Known limits
208
+
209
+ Claude Code's real classifier and exact built-in rules are private. This package implements the documented precedence and configuration behavior, with a local classifier prompt and deterministic hard-deny checks.
210
+
211
+ ## Development
212
+
213
+ ```bash
214
+ npm run check
215
+ npm test
216
+ npm pack --dry-run
217
+ ```
218
+
219
+ The tests cover these safety-sensitive areas:
220
+
221
+ - scoped permission matching
222
+ - the `permissions.allow` tier and its precedence
223
+ - configuration-source precedence and diagnostics
224
+ - `$defaults` behavior
225
+ - deterministic hard-deny checks and Bash AST analysis
226
+ - classifier routing for `write` and `edit`
227
+ - symlink-aware safety-control checks
228
+ - token-budgeted transcript selection
229
+ - staged classifier parsing and cache behavior
230
+ - hook-level allow and block behavior
231
+
232
+ ## Publishing
233
+
234
+ When a maintainer publishes a GitHub Release, GitHub Actions publishes the package to npm. The release tag must match `package.json` exactly. The forms `v1.0.0` and `1.0.0` both match version `1.0.0`.
235
+
236
+ The workflow uses npm Trusted Publishing, so it does not need an npm token secret. Configure this package on npm with this repository and workflow file (`.github/workflows/publish.yml`). The workflow builds the package, runs `npm run check`, and publishes with npm provenance.
237
+
238
+ ### Release tag must point at the version bump
239
+
240
+ The publish workflow checks the commit that the release tag identifies. It compares the version in `package.json` with the tag name.
241
+
242
+ **The tag must identify a commit that contains the new version.**
243
+
244
+ 1. Commit the version change with `chore: release X.Y.Z`.
245
+ 2. Push `main`.
246
+ 3. Create the GitHub release from that commit.
247
+
248
+ If the tag identifies an older version, the `Check release tag` step fails. It reports `Release tag (vX.Y.Z) does not match package.json version (x.y.z)`.
249
+
250
+ CAUTION: Do not move a correct release tag. Moving the tag changes a published reference.
251
+
252
+ 1. If the tag identifies the wrong commit, force-move it to the version-change commit.
253
+ 2. Force-push the corrected tag.
254
+ 3. Run `gh workflow run publish.yml --ref vX.Y.Z`.
255
+
256
+ When GitHub creates the tag, the `release` event occurs. A rerun of a failed `release` workflow uses the original reference. It does not use the moved tag.
257
+
258
+ ## Credits
259
+
260
+ **pi-automode-ext** is maintained by Ingo Bartel ([@ibartel](https://github.com/ibartel)).
261
+
262
+ It is a fork of **pi-automode** by Carlo Zottmann, <carlo@zottmann.dev> — Website: https://actions.work, GitHub: https://github.com/czottmann, Bluesky: https://bsky.app/profile/zottmann.dev, Mastodon: https://norden.social/@czottmann. The original project is MIT-licensed; its notice is retained in [LICENSE.md](LICENSE.md).
@@ -0,0 +1,41 @@
1
+ # Glossary
2
+
3
+ This glossary defines project terms for `@ibartel74/pi-automode-ext`. It does not define standard technical words. Each entry links to a longer explanation where applicable.
4
+
5
+ ## Enforcement flow
6
+
7
+ The enforcement flow is the ordered pipeline that runs before each agent tool call. See [Auto-mode classifier flow](automode-classifier-flow.md).
8
+
9
+ **Auto mode** — A Claude Code-style guardrail posture. A pre-execution classifier allows routine, reversible actions and blocks risky actions. It replaces routine permission prompts.
10
+
11
+ **Deterministic hard-deny** — Local code checks that block high-risk actions before classifier review. A user or model cannot override them. They are independent of the classifier-level [hard_deny](#classifier-policy-and-rules) rules.
12
+
13
+ **`permissions.allow` tier** — A user-owned list of scoped tool patterns. A match skips classifier review after deterministic checks pass. An accepted `permissions.ask` rule disables this tier for the current call. The tier cannot override a denial or cover protected `write` and `edit` targets. See [ADR-001](adr/ADR-001-permission-precedence-and-trust-boundaries.md).
14
+
15
+ **Read-only bypass** — The default allow tier for `read`, `grep`, `find`, and `ls`. Permission and deterministic checks run before this tier. `classifyReadOnlyTools` sends these calls to the classifier instead.
16
+
17
+ **Staged classifier** — A two-stage safety classifier. A conservative one-token filter controls access to an optional structured review. See [Fast stage](#enforcement-flow) and [Detailed stage](#enforcement-flow).
18
+
19
+ **Fast stage** — The first classifier stage. It returns `0` for a clearly allowed action or `1` for an action that can require review.
20
+
21
+ **Detailed stage** — The second classifier stage. It runs after a fast-stage review result and returns a structured allow or block decision.
22
+
23
+ **Interactive confirmation** — The user prompt shown when the classifier blocks an action and `interactiveConfirm` is on (the default). Choices: allow once, always allow (persisting an exact-match `permissions.allow` rule globally or for this project), or block. A decline, cancel, or missing UI keeps the block. Deterministic denials never prompt.
24
+
25
+ ## Classifier policy and rules
26
+
27
+ The classifier policy defines denial tiers and rule-list syntax. See [Defaults and rule-list behavior](defaults.md).
28
+
29
+ **hard_deny** — Classifier rules that block unconditionally against transcript- or model-based overrides. With `interactiveConfirm` on (default), a live user can still approve a blocked action in an [interactive confirmation](#enforcement-flow). They are independent of the code-level [deterministic hard-deny](#enforcement-flow) checks.
30
+
31
+ **soft_deny** — Classifier rules that normally block but support defined overrides, including a live [interactive confirmation](#enforcement-flow). Unlike [hard_deny](#classifier-policy-and-rules), these rules are not unconditional.
32
+
33
+ **explicit_intent** — A classifier tier for direct user authorization in the retained user transcript. It authorizes an action that matches a [soft_deny](#classifier-policy-and-rules) rule. A later user instruction that narrows or revokes authorization controls. For a pre-existing local file outside the repository or worktree, see [Defaults and rule-list behavior](defaults.md).
34
+
35
+ **allow exception** (`autoMode.allow`) — A prose rule that overrides a matching [soft_deny](#classifier-policy-and-rules) rule. It cannot override [hard_deny](#classifier-policy-and-rules). It is independent of the [`permissions.allow` tier](#enforcement-flow).
36
+
37
+ **`$defaults`** — A section-local marker in a rule list. It expands to the built-in entries for that list.
38
+
39
+ ## Status
40
+
41
+ **AM status line** — The persistent TUI footer that starts with `AM`. It reports the auto-mode state and action counts.
@@ -0,0 +1,46 @@
1
+ # ADR-001: Permission precedence and configuration trust boundaries
2
+
3
+ **Date:** 2026-08-23
4
+
5
+ ## Context
6
+
7
+ Pi-automode supports `permissions.deny`, `permissions.ask`, and `permissions.allow` tool patterns. These rules affect whether a tool call reaches the classifier.
8
+
9
+ An allow rule reduces classifier coverage. A checked-in repository file must not reduce that coverage. An accepted ask rule must also keep its promise of classifier review.
10
+
11
+ The wildcard matcher limits its input size to keep matching time bounded. Deny and ask rules must fail closed for oversized inputs. The same behavior is unsafe for allow rules.
12
+
13
+ ## Decision
14
+
15
+ Pi-automode uses this permission policy:
16
+
17
+ 1. A matching `permissions.deny` rule blocks the tool call.
18
+ 2. A matching `permissions.ask` rule requires user confirmation.
19
+ 3. If the user declines, pi-automode blocks the tool call.
20
+ 4. If the user accepts, deterministic denial checks continue.
21
+ 5. After these checks pass, the classifier reviews the accepted tool call.
22
+ 6. An accepted ask rule disables all later deterministic allow tiers for that call.
23
+ 7. If no ask rule matched, a `permissions.allow` rule can skip classifier review.
24
+ 8. Deterministic hard-deny checks, path-deny checks, and protected-path controls take precedence over allow rules.
25
+
26
+ Pi-automode reads `permissions.allow` only from user-owned configuration sources:
27
+
28
+ - `~/.pi/agent/automode.json`
29
+ - trusted `.pi/automode.local.json`
30
+ - `PI_AUTOMODE_SETTINGS_JSON`
31
+
32
+ Shared `.pi/automode.json` can add deny and ask rules after project trust. It cannot add allow rules.
33
+
34
+ [ADR-002](ADR-002-global-config-in-extension-data-directory.md) replaces only the global configuration path. Permission precedence and trust boundaries stay unchanged.
35
+
36
+ For an oversized matcher input, deny and ask rules return a match. Allow rules return no match. This behavior keeps denial rules fail-closed without broadening allow rules.
37
+
38
+ ## Consequences
39
+
40
+ A repository cannot use checked-in configuration to remove classifier review. Users can still add project-specific allow rules in the local configuration file.
41
+
42
+ An accepted ask rule always causes classifier review after deterministic checks pass. This behavior is stricter than a model where confirmation grants direct permission.
43
+
44
+ The matcher needs separate overflow behavior for denial and allow contexts. Tests must cover both behaviors.
45
+
46
+ The permission lists are not symmetric across configuration sources. The documentation and diagnostics must make this difference clear.
@@ -0,0 +1,60 @@
1
+ # ADR-002: Store the global config in the extension data directory
2
+
3
+ **Date:** 2026-08-25
4
+
5
+ ## Context
6
+
7
+ Pi-automode stores its global configuration at `~/.pi/agent/automode.json`. The extension stores application-owned logs below `~/.pi/agent/extensions/pi-automode/logs/`.
8
+
9
+ The separate global configuration path makes extension-owned data harder to identify and manage. The global configuration belongs beside the existing log directory.
10
+
11
+ An existing installation can contain the legacy file. Migration must not discard configuration data or create different read and write sources.
12
+
13
+ This decision refines the global user-owned configuration source from [ADR-001](ADR-001-permission-precedence-and-trust-boundaries.md). It does not change permission precedence or project trust boundaries.
14
+
15
+ Tracking issue: [#27](https://github.com/czottmann/pi-automode/issues/27)
16
+
17
+ ## Decision
18
+
19
+ Pi-automode stores the global configuration at:
20
+
21
+ ```text
22
+ ~/.pi/agent/extensions/pi-automode/config.json
23
+ ```
24
+
25
+ The existing log directory stays at:
26
+
27
+ ```text
28
+ ~/.pi/agent/extensions/pi-automode/logs/
29
+ ```
30
+
31
+ Project configuration files keep their current paths and semantics:
32
+
33
+ - `.pi/automode.local.json`
34
+ - `.pi/automode.json`
35
+
36
+ At startup, pi-automode selects one active global configuration path for the session:
37
+
38
+ 1. If only the new file is present, pi-automode uses the new file.
39
+ 2. If only the legacy file is present, pi-automode moves it to the new path before loading configuration.
40
+ 3. If the two files are present, pi-automode uses the new file and does not change the legacy file.
41
+ 4. If migration fails, pi-automode uses the legacy file for reads and writes during that session.
42
+ 5. If neither file is present, pi-automode uses the new path for the next write.
43
+
44
+ Pi-automode attempts migration once per session. A later session retries a failed migration.
45
+
46
+ A successful migration produces one UI notification. A path conflict produces a warning during every startup. A migration error produces one warning per session.
47
+
48
+ Conflict and migration error details also appear in configuration diagnostics. This behavior makes the details available in headless sessions.
49
+
50
+ ## Consequences
51
+
52
+ All extension-owned global data has one identifiable root directory. Configuration backups and extension cleanup become easier to understand.
53
+
54
+ The new configuration inherits the deterministic protection for `~/.pi/agent/extensions/`. Direct agent file tools cannot modify the configuration.
55
+
56
+ The extension must pass the selected active path to configuration reads and writes. This requirement prevents split-brain configuration during migration errors.
57
+
58
+ The migration is one-way. A downgraded pi-automode version does not automatically find the new configuration path.
59
+
60
+ Startup gains a small filesystem operation and migration state. Tests must cover migration, conflicts, errors, notifications, diagnostics, and unchanged project precedence.
@@ -0,0 +1,6 @@
1
+ # Architecture Decision Records
2
+
3
+ | # | Decision | Date |
4
+ |---|----------|------|
5
+ | [ADR-001](ADR-001-permission-precedence-and-trust-boundaries.md) | Permission precedence and configuration trust boundaries | 2026-08-23 |
6
+ | [ADR-002](ADR-002-global-config-in-extension-data-directory.md) | Store the global config in the extension data directory | 2026-08-25 |