@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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
}
|