@delt/claude-jev-advisor 0.2.0 → 0.3.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
@@ -5,21 +5,21 @@ Unofficial helpers for [Claude Code](https://claude.com/claude-code), installed
5
5
  What is sent to TypeSafe's Jev API (`api.typesafe.ai`):
6
6
 
7
7
  - `context`: your last three requests (typed by you, or sent through a channel or from another Claude Code session), each cut to its first 1,000 characters, and Claude's last reply, cut to its first and last 1,500 characters.
8
- - `rm`: only for a delete it would otherwise ask about, and only when the target is a file this session made or a folder git ignores. It sends the path, the `rm` command and its description, up to three tool calls of this session that name the target (each cut to 600 characters), and for a folder up to ten of its file names.
8
+ - `rm`: only for a delete it would otherwise ask about, and only when the target is a file this session made or a folder git ignores. It sends the path, the delete command and its description, up to three tool calls of this session that name the target (each cut to 600 characters), and for a folder up to ten of its file names.
9
9
 
10
10
  Not affiliated with Anthropic or TypeSafe.
11
11
 
12
12
  | Helper | What it does | Systems |
13
13
  |---|---|---|
14
14
  | `context` | At the end of each turn, suggests `/compact` or `/clear` when the conversation is large and the work has reached a stopping point. Shown at the end of Claude Code's bottom row. | Any |
15
- | `rm` | Asks before a Bash `rm` deletes real files. Lets temp files through and refuses an `rm` whose targets it cannot work out. Jev can lift the ask for a test file this session made or a folder git ignores. | Windows |
15
+ | `rm` | Asks before a Bash or PowerShell command deletes real files (`rm`, `Remove-Item`, `del`, `rd`, also inside `sh -c`, `powershell -Command` or `cmd /c`). Lets temp files through and refuses a delete whose targets it cannot work out. Jev can lift the ask for a test file this session made or a folder git ignores. | Windows |
16
16
 
17
17
  ## Requirements
18
18
 
19
19
  - Node.js 18 or later
20
20
  - Claude Code (the bottom-row display uses Claude Code mods, an early-access feature that may change between releases)
21
21
  - A TypeSafe API key for the `context` helper and for the Jev check of the `rm` helper, in `TYPESAFE_API_KEY` or in a key file (`--key-file`). Without a key, `rm` asks about every real file.
22
- - Windows for the `rm` helper. On other systems `install` skips it and says why.
22
+ - Windows for the `rm` helper. On other systems `install` skips it and says why. It reads PowerShell commands with Windows PowerShell, which comes with Windows, or with PowerShell 7 when `pwsh` is on `PATH`, the same one Claude Code uses. It was checked with Windows PowerShell 5.1; PowerShell 7 was not tested.
23
23
 
24
24
  ## Install
25
25
 
@@ -35,7 +35,9 @@ claude-jev-advisor install
35
35
 
36
36
  When the context helper is installed and no key is found, `install` asks for your TypeSafe API key. What you type is hidden. It checks the key with one small Jev call and saves it to `~/.claude/claude-jev-advisor/jev-key.env`. Press Enter to skip; `claude-jev-advisor key` asks again later. Instead of typing it, you can set `TYPESAFE_API_KEY`, or pass `--key-file <path>` to a file holding a line `TYPESAFE_API_KEY=...` (then only that path is saved). The key is never printed or logged. On Windows that file is protected by your user folder's permissions only.
37
37
 
38
- Before it changes `~/.claude/settings.json`, `install` copies it to `~/.claude/backups/settings.json.<YYYY-MM-DD-HHmmss>-before-claude-jev-advisor`. If a backup from the same second already exists, it adds `-2`, `-3` and so on instead of overwriting it. It then adds or replaces only this package's entries. Running it again changes nothing.
38
+ Before it changes `~/.claude/settings.json`, `install` copies it to `~/.claude/backups/settings.json.<YYYY-MM-DD-HHmmss>-before-claude-jev-advisor`. If a backup from the same second already exists, it adds `-2`, `-3` and so on instead of overwriting it. It then adds or replaces only this package's entries. Running it again with the same version changes nothing.
39
+
40
+ After upgrading from 0.2 or earlier, run `claude-jev-advisor install rm` again so that the rm hook also covers the PowerShell tool. Until you do, `status` shows `Bash only`.
39
41
 
40
42
  The hooks also reach Claude Code sessions that are already open; the bottom-row display starts with the next new session. Turning a helper on or off applies at once, even to open sessions.
41
43
 
@@ -84,7 +86,11 @@ Judging starts at 250k because a conversation passes 100k after a request or two
84
86
 
85
87
  ## The rm helper
86
88
 
87
- It runs before every Bash tool call (`PreToolUse`, matcher `Bash`, 15-second timeout) and looks only at `rm`, `rmdir` and `xargs`.
89
+ It runs before every Bash and PowerShell tool call (`PreToolUse`, matcher `Bash|PowerShell`, 15-second timeout) and looks at:
90
+
91
+ - Bash: `rm`, `rmdir` and `xargs`
92
+ - PowerShell: `Remove-Item` and its aliases `ri`, `rm`, `del`, `erase`, `rd` and `rmdir`, and `[System.IO.File]::Delete` and `[System.IO.Directory]::Delete`. PowerShell's own parser and parameter binder read the command, so aliases, shortened parameter names, positional paths and nested blocks count as PowerShell reads them. PowerShell is started only when the command holds one of these words or a shell name, and takes about 0.3 seconds.
93
+ - shells started inside a command, up to three levels deep: `sh -c` and `bash -c`, `powershell -Command`, `pwsh -c` and `-EncodedCommand`, and `del`, `erase`, `rd` and `rmdir` in `cmd /c`
88
94
 
89
95
  | Target | Result |
90
96
  |---|---|
@@ -92,23 +98,30 @@ It runs before every Bash tool call (`PreToolUse`, matcher `Bash`, 15-second tim
92
98
  | A path with a `.superpowers` folder in it | No decision |
93
99
  | A path that does not exist | No decision |
94
100
  | A git-ignored path inside a build folder (`target`, `build`, `dist`, `out`, `node_modules`, `coverage`, `__pycache__`, `.pytest_cache`, `.gradle`, `.next`, `.nuxt`, `.turbo`, `.cache`, `bin`, `obj`) | No decision |
101
+ | A PowerShell path on a drive that is not a file system (`Env:`, `Alias:`, `Function:`, `Variable:`, `HKCU:`, `HKLM:`, `Cert:`, `WSMan:`, `Registry::`) | No decision |
102
+ | `Remove-Item … -WhatIf` | No decision |
103
+ | A network path (`\\server\share\…`) | `ask`, without looking the path up |
95
104
  | Any other existing file or folder | `ask`, with the reason `실제 파일 삭제: <paths>` ("deleting real files"), unless Jev lifts it (below) |
105
+ | A PowerShell command its parser cannot read (a syntax error, PowerShell not answering within 3 seconds, a nested PowerShell command left when the hook has spent 9 seconds reading, an answer it does not expect), or one that deletes and runs code it cannot follow (`& $name`, `Invoke-Expression`, `Start-Process` or `Set-Alias` with an argument it cannot work out, `[scriptblock]::Create`, `.Invoke()`, a `Delete()` method in a command that gets file objects from `Get-Item`, `Get-ChildItem`, `New-Item` or `-PassThru`), or makes a drive with `New-PSDrive` | `ask`, with the reason `삭제 명령을 분석하지 못함: <start of the command>` ("could not read the delete command"). Jev is not asked. |
96
106
  | A target it cannot work out | `deny`, with a hint to rewrite the command using literal paths |
97
107
 
98
108
  A target cannot be worked out when it uses:
99
109
 
100
- - shell variables, other than literal assignments in the same command and `TEMP`, `TMP`, `TMPDIR`, `HOME`, `USERPROFILE` and `PWD`
101
- - command substitution
102
- - `xargs` feeding `rm`
110
+ - in Bash, shell variables other than literal assignments in the same command and `TEMP`, `TMP`, `TMPDIR`, `HOME`, `USERPROFILE` and `PWD`; in PowerShell, variables other than one plain top-level assignment in the same command (a variable written any other way, such as `+=`, a loop, a function parameter, `Set-Variable`, `$script:`, `-OutVariable`, or its name handed to a command as text, is unknown), `$env:TEMP`, `$env:TMP`, `$env:TMPDIR`, `$env:HOME`, `$env:USERPROFILE`, `$HOME` and `$PWD` (unless the command assigns it); in `cmd`, variables other than `%TEMP%`, `%TMP%`, `%USERPROFILE%` and `%CD%`
111
+ - command substitution, or in PowerShell any expression such as `(Join-Path …)`, `$(…)` or `@splat`
112
+ - `xargs` feeding `rm` or a shell that deletes, PowerShell pipeline input feeding `Remove-Item` (`Get-ChildItem … | Remove-Item`), or a shell given no script of its own, which reads one from a pipe, a heredoc or a here-string (`… | bash`, `bash <<EOF`, `powershell -`), in a command that holds a delete word
103
113
  - brace expansion
104
114
  - a wildcard in a folder name
105
- - a `cd` to such a path earlier in the command
115
+ - a `cd`, `pushd`, `Set-Location` or `Push-Location` to such a path earlier in the command, a `popd` or `Pop-Location`, or a location change inside a PowerShell block; in PowerShell also a relative path inside a loop, a function or a script block when the command changes location anywhere
116
+ - a PowerShell drive other than a drive letter and the non-file drives above, such as one made with `New-PSDrive`
117
+ - a nested shell whose script is not fixed text and holds a delete word, or shells nested more than three deep that delete
118
+ - in `cmd`, a delete on a line that uses `if`, `else`, `for`, `call`, `start` or unquoted parentheses, or starts a command with a name it cannot read: write plain `del` or `rd` lines instead, with names that hold parentheses in double quotes
106
119
 
107
120
  It does not see deletions made any other way:
108
121
 
109
- - through the PowerShell tool (`Remove-Item` and its aliases). On Windows with Git Bash installed, Claude Code turns this tool on by default for claude.ai and Console accounts and uses PowerShell as its main shell, so many deletions go through it.
110
- - through a nested shell: `sh -c`, `bash -c`, `powershell -Command`, `pwsh -c`, `cmd /c del`
111
- - with other commands or programs, such as `find -delete`, `git clean` or a script that deletes files
122
+ - in PowerShell, `Remove-ItemProperty`, `Clear-RecycleBin`, an object's `Delete()` in a command that gets no file objects, COM file objects (`Scripting.FileSystemObject`'s `DeleteFile` and `DeleteFolder`), provider methods such as `$ExecutionContext.InvokeProvider.Item.Remove`, and code that builds a command's name or text from pieces while it runs (`[char]` codes, joined strings); the reader is not a sandbox
123
+ - script files: `powershell -File`, `pwsh script.ps1`, `bash script.sh`, a `.bat` file
124
+ - other commands or programs, such as `find -delete`, `git clean`, or Node or Python code that deletes files
112
125
 
113
126
  What its decisions do depends on Claude Code's permission mode:
114
127
 
@@ -124,7 +137,7 @@ The facts are checked by code; Jev only judges what the target is for. Jev is as
124
137
 
125
138
  | Target | Checked first | Jev is asked |
126
139
  |---|---|---|
127
- | A file or folder this session made | Not tracked by git; the first call of this session that names it created it: a Write that made a new file, or a shell command whose `>` redirect, `touch`, `mkdir` or `curl -o` makes it (deletes do not count, and a Read, an Edit or any other shell command first means it was already there); made after the session started; for a folder, every file inside too, and at most 50 files | Is it a throwaway made only to try something out? |
140
+ | A file or folder this session made | Not tracked by git; the first call of this session that names it created it: a Write that made a new file, or a shell command whose `>` redirect, `touch`, `mkdir` or `curl -o` makes it (deletes do not count, and a Read, an Edit or any other shell command first means it was already there; PowerShell's `New-Item`, `Set-Content` and `Out-File` are not recognized yet, so a file made with them keeps the ask); a folder holding a git repository never counts; made after the session started; for a folder, every file inside too, and at most 50 files | Is it a throwaway made only to try something out? |
128
141
  | A folder git ignores, outside the build folders above | It is a folder (a single ignored file such as `.env` still asks) | Is it generated output or a cache that is made again? |
129
142
 
130
143
  When Jev answers 0.8 or more for every target, the hook makes no decision (the last column of the table above), and a line like `[jev-advisor] Jev가 이 세션의 시험 파일로 판단해 묻지 않고 지웁니다: out.json (0.93)` is shown. Otherwise the ask stays, with Jev's answer added: `실제 파일 삭제: …\out.json · Jev: 시험용 파일일 확률 0.42`. Without a key, when Jev fails, or with `"jev": false`, every real file asks as before. A `deny` is never sent to Jev.
@@ -152,7 +165,7 @@ A key saved by `install` or `key` stays in `~/.claude/claude-jev-advisor/jev-key
152
165
  |---|---|
153
166
  | `~/.claude/claude-jev-advisor/config.json` | Switches and settings that differ from the defaults. A missing or broken file reads as the defaults. A value of the wrong type falls back to its default, so only `"enabled": false` turns a helper off. |
154
167
  | `~/.claude/claude-jev-advisor/state/<session>.json` | The last judgment of each open session, read by the display. Removed when the session ends. |
155
- | `~/.claude/claude-jev-advisor/log/YYYY-MM.jsonl` | One line per context judgment and per `rm` Jev check: the size or the command, what was sent to Jev (parts of your conversation), the answers and the result. Never the key. |
168
+ | `~/.claude/claude-jev-advisor/log/YYYY-MM.jsonl` | One line per context judgment and per `rm` Jev check: the size, or the tool (`Bash` or `PowerShell`) and the command, what was sent to Jev (parts of your conversation), the answers and the result. Never the key. |
156
169
  | `~/.claude/claude-jev-advisor/statusline-before.json` | Your own status line while `--display statusline` is in use |
157
170
  | `~/.claude/claude-jev-advisor/jev-key.env` | Your TypeSafe API key, when you typed it in `install` or `key` |
158
171
  | `~/.claude/backups/settings.json.*-before-claude-jev-advisor` | Copies of `settings.json` from before each change |
package/dist/cli.js CHANGED
@@ -164,7 +164,7 @@ function updateConfig(home, change) {
164
164
  // src/features.ts
165
165
  var FEATURES = ["rm", "context"];
166
166
  var HOOK_SPECS = {
167
- rm: [{ event: "PreToolUse", matcher: "Bash", script: "rm-hook.js", args: [] }],
167
+ rm: [{ event: "PreToolUse", matcher: "Bash|PowerShell", script: "rm-hook.js", args: [] }],
168
168
  context: [
169
169
  { event: "Stop", script: "context-hook.js", args: ["stop"] },
170
170
  { event: "SessionEnd", script: "context-hook.js", args: ["session-end"] }
@@ -842,6 +842,14 @@ function rmJevLine(config, env) {
842
842
  const key = readJevKey(env, config.keyFile) ? "" : " (no key - every real file asks)";
843
843
  return ` jev: on, lifts the ask from ${config.rm.throwawayYes}${key}`;
844
844
  }
845
+ function coversPowerShell(matcher) {
846
+ if (!matcher || matcher === "*") return true;
847
+ try {
848
+ return new RegExp(`^(?:${matcher})$`).test("PowerShell");
849
+ } catch {
850
+ return false;
851
+ }
852
+ }
845
853
  function statusLines(home, env = process.env) {
846
854
  const lines = [`settings: ${settingsPath(home)}`, `config: ${configPath(home)}`];
847
855
  let settings;
@@ -866,7 +874,12 @@ function statusLines(home, env = process.env) {
866
874
  }
867
875
  }
868
876
  if (feature === "context") lines.push(...contextLines(home, settings, config, env));
869
- if (feature === "rm") lines.push(rmJevLine(config, env));
877
+ if (feature === "rm") {
878
+ lines.push(rmJevLine(config, env));
879
+ if (!commands.some((c) => coversPowerShell(c.matcher))) {
880
+ lines.push(' Bash only: run "claude-jev-advisor install rm" to cover PowerShell');
881
+ }
882
+ }
870
883
  }
871
884
  if (findCommands(settings, LEGACY_RM_GUARD).length) {
872
885
  lines.push('legacy rm-guard hook (~/.claude/hooks/rm-guard) is still registered - "claude-jev-advisor install rm" replaces it');