@czottmann/pi-automode 1.4.0 → 1.6.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/README.md CHANGED
@@ -43,6 +43,19 @@ pi -e ./extensions/auto-mode.ts
43
43
 
44
44
  `/auto-mode` is an alias.
45
45
 
46
+ ## Status line
47
+
48
+ When the Pi TUI is available, the extension renders a persistent status line:
49
+
50
+ ```text
51
+ AM● a:12 d:2 ca:5 cd:1
52
+ ```
53
+
54
+ - `AM` — auto-mode prefix; `●` when enabled, `○` when disabled (via config or `/automode off`).
55
+ - `a:` — actions allowed so far (checked minus blocked).
56
+ - `d:` — actions blocked so far, for any reason (permission rule, deterministic hard-deny, or classifier).
57
+ - `ca:` / `cd:` — classifier decisions split into allowed vs denied. These segments appear only after the classifier has run at least once; `d:` counts all blocks, so `d:` is always `>= cd:`.
58
+
46
59
  ## Docs
47
60
 
48
61
  - [Defaults and rule-list behavior](docs/defaults.md)
@@ -141,6 +154,12 @@ GitHub Actions publishes the package to npm when a GitHub Release is published.
141
154
 
142
155
  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.
143
156
 
157
+ ### Release tag must point at the version bump
158
+
159
+ The publish workflow checks out the commit the release tag points at and compares `package.json` against the tag name. **The tag must point at a commit where `package.json` already carries the new version.** Concretely: commit the version bump (`chore: release X.Y.Z`), push `main`, then create the GitHub release targeting that pushed commit. Creating the release before pushing the version bump — or targeting a commit that still has the old version — fails the `Check release tag` step with `Release tag (vX.Y.Z) does not match package.json version (x.y.z)`.
160
+
161
+ If the tag was cut against the wrong commit, fix it by force-moving it to the version-bump commit and pushing, then trigger the workflow via `gh workflow run publish.yml --ref vX.Y.Z` (the `release` event fires on tag creation; re-running a failed `release`-triggered run reuses the original ref and won't pick up the moved tag).
162
+
144
163
  ## Author
145
164
 
146
165
  Carlo Zottmann, <carlo@zottmann.dev>
@@ -69,6 +69,8 @@ export function createPiAutomode(options: PiAutomodeOptions = {}) {
69
69
  let state: AutoModeState = {
70
70
  checkedActions: 0,
71
71
  blockedActions: 0,
72
+ classifierAllowed: 0,
73
+ classifierDenied: 0,
72
74
  recentDenials: [],
73
75
  };
74
76
  let loadedContext = "";
@@ -217,12 +219,14 @@ export function createPiAutomode(options: PiAutomodeOptions = {}) {
217
219
  if (path && isProtectedPath(path, ctx.cwd, cfg.protectedPaths)) {
218
220
  const decision = await classify(ctx, cfg, summary, loadedContext);
219
221
  if (decision.decision === "allow") {
222
+ state.classifierAllowed += 1;
220
223
  state.lastDecision = "allow";
221
224
  state.lastReason = decision.reason;
222
225
  persist();
223
226
  updateUi(ctx);
224
227
  return undefined;
225
228
  }
229
+ state.classifierDenied += 1;
226
230
  return block(ctx, {
227
231
  timestamp: Date.now(),
228
232
  toolName: event.toolName,
@@ -235,6 +239,7 @@ export function createPiAutomode(options: PiAutomodeOptions = {}) {
235
239
 
236
240
  const decision = await classify(ctx, cfg, summary, loadedContext);
237
241
  if (decision.decision === "allow") {
242
+ state.classifierAllowed += 1;
238
243
  state.lastDecision = "allow";
239
244
  state.lastReason = decision.reason;
240
245
  persist();
@@ -242,6 +247,7 @@ export function createPiAutomode(options: PiAutomodeOptions = {}) {
242
247
  return undefined;
243
248
  }
244
249
 
250
+ state.classifierDenied += 1;
245
251
  return block(ctx, {
246
252
  timestamp: Date.now(),
247
253
  toolName: event.toolName,
@@ -295,6 +301,8 @@ export function createPiAutomode(options: PiAutomodeOptions = {}) {
295
301
  state = {
296
302
  checkedActions: 0,
297
303
  blockedActions: 0,
304
+ classifierAllowed: 0,
305
+ classifierDenied: 0,
298
306
  recentDenials: [],
299
307
  enabledOverride: state.enabledOverride,
300
308
  };
@@ -15,15 +15,12 @@ export function statusLine(
15
15
  state: AutoModeState,
16
16
  ): string {
17
17
  const enabled = state.enabledOverride ?? config.enabled;
18
- if (!enabled) return "Auto-mode off";
19
- let line = `Auto-mode on • checked: ${state.checkedActions}`;
20
- if (state.blockedActions > 0) {
21
- line =
22
- `Auto-mode on · blocked: ${state.blockedActions}/${state.checkedActions}`;
23
- const last = state.recentDenials.at(-1);
24
- if (last) line += ` · last: ${truncateMiddle(last.reason, 60)}`;
25
- }
26
- return line;
18
+ const circle = enabled ? "●" : "○";
19
+ const allowed = state.checkedActions - state.blockedActions;
20
+ const classifier = state.classifierAllowed > 0 || state.classifierDenied > 0
21
+ ? ` ca:${state.classifierAllowed} cd:${state.classifierDenied}`
22
+ : "";
23
+ return `AM${circle} a:${allowed} d:${state.blockedActions}${classifier}`;
27
24
  }
28
25
 
29
26
  export function statusText(
@@ -35,6 +32,8 @@ export function statusText(
35
32
  `classifier: ${config.classifierModel ?? "current session model"}`,
36
33
  `checked actions: ${state.checkedActions}`,
37
34
  `blocked actions: ${state.blockedActions}`,
35
+ `classifier allowed: ${state.classifierAllowed}`,
36
+ `classifier denied: ${state.classifierDenied}`,
38
37
  `permissions.deny rules: ${config.permissionDeny.length}`,
39
38
  `permissions.ask rules: ${config.permissionAsk.length}`,
40
39
  `environment entries: ${config.environment.length}`,
@@ -90,10 +89,12 @@ export function restoreState(ctx: ExtensionContext): AutoModeState {
90
89
  lastReason: entry.data.lastReason,
91
90
  checkedActions: entry.data.checkedActions ?? 0,
92
91
  blockedActions: entry.data.blockedActions ?? 0,
92
+ classifierAllowed: entry.data.classifierAllowed ?? 0,
93
+ classifierDenied: entry.data.classifierDenied ?? 0,
93
94
  recentDenials: Array.isArray(entry.data.recentDenials)
94
95
  ? entry.data.recentDenials.slice(-DENIAL_HISTORY_LIMIT)
95
96
  : [],
96
97
  };
97
98
  }
98
- return { checkedActions: 0, blockedActions: 0, recentDenials: [] };
99
+ return { checkedActions: 0, blockedActions: 0, classifierAllowed: 0, classifierDenied: 0, recentDenials: [] };
99
100
  }
@@ -52,6 +52,8 @@ export type AutoModeState = {
52
52
  lastReason?: string;
53
53
  checkedActions: number;
54
54
  blockedActions: number;
55
+ classifierAllowed: number;
56
+ classifierDenied: number;
55
57
  recentDenials: DenialRecord[];
56
58
  };
57
59
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@czottmann/pi-automode",
3
- "version": "1.4.0",
3
+ "version": "1.6.0",
4
4
  "description": "Claude Code-style auto mode guardrail for pi.",
5
5
  "repository": {
6
6
  "url": "https://github.com/czottmann/pi-automode"