pluidr 0.6.0 → 0.6.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pluidr",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "Opinionated Engineering Workflow for OpenCode — Explore, Plan, Build, Debug, and Security-Audit.",
5
5
  "keywords": [
6
6
  "opencode",
@@ -3,7 +3,7 @@ import { join, dirname } from "node:path"
3
3
  import { fileURLToPath } from "node:url"
4
4
  import { execFileSync } from "node:child_process"
5
5
  import { getConfigDir, getConfigPath, getPromptsDir } from "../../core/paths.js"
6
- import { findRtkPath } from "../../core/squeezeInstaller.js"
6
+ import { findSqueezePath } from "../../core/squeezeInstaller.js"
7
7
 
8
8
  const __dirname = dirname(fileURLToPath(import.meta.url))
9
9
  const EXPECTED_PROMPT_COUNT = 18
@@ -54,16 +54,16 @@ export async function runDoctor() {
54
54
  }
55
55
  checks.push({ component: "package.json + plugin dep", pass: pkgValid })
56
56
 
57
- // 5. RTK binary exists
58
- let rtkFound = false
59
- const rtkOnPath = findRtkPath()
60
- if (rtkOnPath) {
61
- rtkFound = true
57
+ // 5. Squeeze binary exists
58
+ let squeezeFound = false
59
+ const squeezeOnPath = findSqueezePath()
60
+ if (squeezeOnPath) {
61
+ squeezeFound = true
62
62
  } else {
63
- const binName = process.platform === "win32" ? "rtk.exe" : "rtk"
64
- rtkFound = existsSync(join(binDir, binName))
63
+ const binName = process.platform === "win32" ? "squeeze.exe" : "squeeze"
64
+ squeezeFound = existsSync(join(binDir, binName))
65
65
  }
66
- checks.push({ component: "squeeze binary", pass: rtkFound })
66
+ checks.push({ component: "squeeze binary tested", pass: squeezeFound })
67
67
 
68
68
  // 6. Config is valid JSON/JSONC
69
69
  let configValid = false
@@ -1,5 +1,5 @@
1
1
  import { execFileSync } from "node:child_process"
2
- import { chmodSync, mkdirSync, existsSync, writeFileSync, unlinkSync, readFileSync } from "node:fs"
2
+ import { chmodSync, mkdirSync, existsSync, writeFileSync, unlinkSync, readFileSync, renameSync } from "node:fs"
3
3
  import { extname, join, resolve, dirname } from "node:path"
4
4
  import { fileURLToPath } from "node:url"
5
5
  import { createHash } from "node:crypto"
@@ -7,7 +7,10 @@ import { getConfigDir } from "./paths.js"
7
7
 
8
8
  const __dirname = dirname(fileURLToPath(import.meta.url))
9
9
 
10
- const BIN_NAME = process.platform === "win32" ? "rtk.exe" : "rtk"
10
+ // Local binary name friendlier than the upstream "rtk" name
11
+ const BIN_NAME = process.platform === "win32" ? "squeeze.exe" : "squeeze"
12
+ // Name of the binary inside the downloaded archive (upstream uses "rtk")
13
+ const RTK_BIN_NAME = process.platform === "win32" ? "rtk.exe" : "rtk"
11
14
  export const ASSETS = {
12
15
  "darwin-arm64": "rtk-aarch64-apple-darwin.tar.gz",
13
16
  "darwin-x64": "rtk-x86_64-apple-darwin.tar.gz",
@@ -28,10 +31,10 @@ export function validatePath(path) {
28
31
  }
29
32
  }
30
33
 
31
- export function findRtkPath() {
34
+ export function findSqueezePath() {
32
35
  try {
33
36
  const [cmd, ...args] =
34
- process.platform === "win32" ? ["where", "rtk"] : ["which", "rtk"]
37
+ process.platform === "win32" ? ["where", "squeeze"] : ["which", "squeeze"]
35
38
  return execFileSync(cmd, args, { stdio: "pipe" })
36
39
  .toString()
37
40
  .trim()
@@ -85,13 +88,13 @@ function verifyChecksum(buffer, assetName) {
85
88
  }
86
89
 
87
90
  export async function installSqueeze() {
88
- let rtkPath = findRtkPath()
91
+ let rtkPath = findSqueezePath()
89
92
 
90
93
  if (!rtkPath) {
91
94
  // Not on PATH — download it to the managed location
92
95
  const asset = platformAsset()
93
96
  if (!asset) {
94
- console.warn("⚠ Squeeze: unsupported platform — install rtk manually")
97
+ console.warn("⚠ Squeeze: unsupported platform — squeeze binary unavailable")
95
98
  return
96
99
  }
97
100
 
@@ -127,14 +130,31 @@ export async function installSqueeze() {
127
130
 
128
131
  try { unlinkSync(archivePath) } catch {}
129
132
 
133
+ // Archive contains "rtk"/"rtk.exe" — rename to local BIN_NAME ("squeeze")
134
+ const extracted = join(binDir, RTK_BIN_NAME)
130
135
  rtkPath = join(binDir, BIN_NAME)
136
+ if (existsSync(extracted) && extracted !== rtkPath) {
137
+ renameSync(extracted, rtkPath)
138
+ }
131
139
 
132
140
  if (process.platform !== "win32" && existsSync(rtkPath)) {
133
141
  chmodSync(rtkPath, 0o755)
134
142
  }
135
- } catch {
143
+
144
+ // Verify that the downloaded binary is executable and runs on this platform
145
+ try {
146
+ execFileSync(rtkPath, ["--help"], { stdio: "ignore" })
147
+ } catch (err) {
148
+ // If it throws because of execution format or permissions (e.g. ENOEXEC, EACCES, ENOENT)
149
+ if (err.code === "ENOEXEC" || err.code === "EACCES" || err.code === "ENOENT") {
150
+ try { unlinkSync(rtkPath) } catch {}
151
+ throw new Error(`Downloaded binary is not executable on this platform: ${err.message}`)
152
+ }
153
+ // If it ran but exited with non-zero (like returning exit code 1 for --help), that's fine, it means it is executable!
154
+ }
155
+ } catch (err) {
136
156
  try { unlinkSync(archivePath) } catch {}
137
- console.warn("⚠ Squeeze: download failed after 3 attempts install rtk manually")
157
+ console.warn(`⚠ Squeeze: installation failed (${err.message})squeeze plugin disabled`)
138
158
  return
139
159
  }
140
160
  }
@@ -1,6 +1,6 @@
1
1
  import { describe, it } from "node:test"
2
2
  import assert from "node:assert"
3
- import { platformAsset, validatePath, findRtkPath, ASSETS } from "./squeezeInstaller.js"
3
+ import { platformAsset, validatePath, findSqueezePath, ASSETS } from "./squeezeInstaller.js"
4
4
 
5
5
  describe("platformAsset", () => {
6
6
  it("returns a string or null for current platform", () => {
@@ -66,11 +66,9 @@ describe("validatePath", () => {
66
66
  })
67
67
  })
68
68
 
69
- describe("findRtkPath", () => {
70
- it("returns null when rtk is not available on PATH", () => {
71
- // This test is safe because if rtk IS on PATH, we still get a
72
- // string back — but the test doesn't crash either way.
73
- const result = findRtkPath()
69
+ describe("findSqueezePath", () => {
70
+ it("returns null when squeeze is not available on PATH", () => {
71
+ const result = findSqueezePath()
74
72
  // Either null (not on PATH) or a non-empty string (found)
75
73
  assert.ok(result === null || typeof result === "string")
76
74
  })
@@ -21,16 +21,16 @@ attribution, and one-line tool-invocation summaries, separated by
21
21
  ## `pluidr-squeeze.js` — command output filtering
22
22
 
23
23
  Implementation of the `PluidrSqueezePlugin`. Hooks into `tool.execute.before`
24
- to rewrite bash commands through the `rtk` binary. The binary filters,
25
- groups, truncates, and deduplicates command output — saving 60-90% of
26
- tokens across all agents.
24
+ to rewrite bash commands through the local `squeeze` binary (built on the `rtk`
25
+ engine). The binary filters, groups, truncates, and deduplicates command
26
+ output — saving 60-90% of tokens across all agents.
27
27
 
28
- The plugin is a thin delegator: all rewrite logic lives in the `rtk` binary.
28
+ The plugin is a thin delegator: all rewrite logic lives in the engine binary.
29
29
  It checks `PATH` first, then falls back to Pluidr's managed location at
30
- `~/.config/opencode/bin/rtk`. If the binary is not found, the plugin
30
+ `~/.config/opencode/bin/squeeze`. If the binary is not found, the plugin
31
31
  gracefully disables itself with a warning.
32
32
 
33
- `pluidr init` downloads the `rtk` binary automatically during setup.
33
+ `pluidr init` downloads the `squeeze` binary automatically during setup.
34
34
 
35
35
  ## Why `pluidr-flow` was extracted
36
36
 
@@ -61,8 +61,8 @@ agent in its pipeline, so the tool would have no caller — YAGNI.
61
61
 
62
62
  `pluidr init` copies both plugins into `~/.config/opencode/plugins/` and
63
63
  writes a `package.json` declaring `@opencode-ai/plugin: ^1.17.9` as a
64
- dependency. For `pluidr-squeeze`, it also downloads the `rtk` engine binary
65
- to `~/.config/opencode/bin/`. On OpenCode's first launch, the bundled Bun
66
- runtime detects the `package.json` and runs `bun install` automatically —
67
- no user action required. Once installed, both plugins are available to
68
- every agent that has the appropriate permissions.
64
+ dependency. For `pluidr-squeeze`, it also downloads and extracts the `squeeze`
65
+ engine binary to `~/.config/opencode/bin/`. On OpenCode's first launch, the
66
+ bundled Bun runtime detects the `package.json` and runs `bun install`
67
+ automatically — no user action required. Once installed, both plugins are
68
+ available to every agent that has the appropriate permissions.
@@ -2,21 +2,42 @@ import { existsSync } from "node:fs"
2
2
  import { homedir } from "node:os"
3
3
  import { join } from "node:path"
4
4
 
5
- const BIN_NAME = process.platform === "win32" ? "rtk.exe" : "rtk"
5
+ const BIN_NAME = process.platform === "win32" ? "squeeze.exe" : "squeeze"
6
6
  const SQUEEZE_BIN = join(homedir(), ".config", "opencode", "bin", BIN_NAME)
7
7
 
8
8
  function normalise(p) {
9
9
  return p.replace(/\\/g, "/")
10
10
  }
11
11
 
12
+ // Quote a path if it contains spaces so the shell treats it as one token.
13
+ export function shellQuote(p) {
14
+ return p.includes(" ") ? `"${p}"` : p
15
+ }
16
+
17
+ // Exported for testing. When binPath is the managed full path (not "squeeze"
18
+ // on PATH), the binary may output "rtk <cmd>" or "squeeze <cmd>" depending on
19
+ // how it resolves its own name. Replace the leading token with the actual
20
+ // binPath (quoted if it contains spaces) so the shell can find it. When
21
+ // squeeze is on PATH, normalise any "rtk" prefix to "squeeze".
22
+ export function resolveRewritten(rewritten, binPath) {
23
+ const m = rewritten.match(/^(rtk|squeeze)(\s|$)/)
24
+ if (!m) return rewritten
25
+ if (binPath === "squeeze") {
26
+ // squeeze is on PATH — normalise any leading binary name to "squeeze"
27
+ return "squeeze" + rewritten.slice(m[1].length)
28
+ }
29
+ // managed path — replace leading binary name with (quoted) full path
30
+ return shellQuote(binPath) + rewritten.slice(m[1].length)
31
+ }
32
+
12
33
  async function findBinary($) {
13
34
  try {
14
35
  if (process.platform === "win32") {
15
- await $`where rtk`.quiet()
36
+ await $`where squeeze`.quiet()
16
37
  } else {
17
- await $`which rtk`.quiet()
38
+ await $`which squeeze`.quiet()
18
39
  }
19
- return "rtk"
40
+ return "squeeze"
20
41
  } catch {
21
42
  if (existsSync(SQUEEZE_BIN)) {
22
43
  return normalise(SQUEEZE_BIN)
@@ -28,7 +49,7 @@ async function findBinary($) {
28
49
  export const PluidrSqueezePlugin = async ({ $ }) => {
29
50
  const binPath = await findBinary($)
30
51
  if (!binPath) {
31
- console.warn("[squeeze] rtk binary not found — plugin disabled")
52
+ console.warn("[squeeze] squeeze binary not found — plugin disabled")
32
53
  return {}
33
54
  }
34
55
 
@@ -46,7 +67,7 @@ export const PluidrSqueezePlugin = async ({ $ }) => {
46
67
  const result = await $`${binPath} rewrite ${command}`.quiet().nothrow()
47
68
  const rewritten = String(result.stdout).trim()
48
69
  if (rewritten && rewritten !== command) {
49
- args.command = rewritten
70
+ args.command = resolveRewritten(rewritten, binPath)
50
71
  }
51
72
  } catch {
52
73
  // rewrite failed — pass through unchanged
@@ -142,17 +142,18 @@ only). That's it — you have no `read`, `glob`, `grep`, `webfetch`,
142
142
  **Guardrail Gate 1 — EXPLORE to PLAN or BUILD transition**:
143
143
  After you have gathered sufficient context and produced recommendations,
144
144
  you MUST internally assess whether the feature is simple enough to skip
145
- the PRD phase. This is YOUR judgment, not the user's.
145
+ the PRD phase.
146
146
 
147
- A feature is **simple** when ALL of these apply:
148
- - The approach is well-understood (no research unknowns)
149
- - Few files to change (1-3 files)
150
- - Low risk of regressions or side effects
151
- - Clear, unambiguous requirements
152
- - No complex dependencies or integration concerns
147
+ **Bias heavily toward the PLAN phase.** Writing a PRD is the safest default. You must default to the PLAN (PRD) phase unless the feature is extremely simple.
153
148
 
154
- If ANY of these is uncertain or the feature touches multiple systems,
155
- it is **complex** proceed to PLAN phase.
149
+ A feature is **simple** ONLY when ALL of these apply:
150
+ - It is an extremely trivial change (e.g., fixing a typo, updating a single configuration value, changing a single line of text).
151
+ - It changes only 1 single file.
152
+ - The approach is completely obvious and well-understood (no research unknowns).
153
+ - Zero risk of regressions or side effects.
154
+ - No new files or new tests are created.
155
+
156
+ If the change involves writing new logic, adding new functions, creating a new file, or modifying multiple files, it is **complex** by definition — you MUST proceed to the PLAN phase. Do not offer a direct build shortcut.
156
157
 
157
158
  Based on your assessment:
158
159
 
@@ -395,6 +396,7 @@ elsewhere. Do not accept a report that is not in `docs/reports/`.
395
396
  already confirmed as PASS.
396
397
  - **Clarification** — If anything is ambiguous at any phase, ask the user
397
398
  using multiple-choice options (2-4 short choices per question).
399
+ - **Front-End / UI Mockups** — If the user asks for a design, mockup, or anything related to the Front-End (FE), you must provide a detailed ASCII mockup of the UI structure in your output.
398
400
 
399
401
  ---
400
402
 
@@ -14,6 +14,7 @@ You have no `task` permission — you cannot invoke any other agent or subagent.
14
14
  - **Principle of Least Astonishment** — Prioritize patterns that already exist in the codebase over introducing new ones. The most predictable solution is the one the codebase already uses.
15
15
  - **DRY** — Before researching a solution, check if a precedent already exists in the codebase. Do not research from scratch if prior art exists.
16
16
  - **Source Verification** — Explicitly distinguish between "confirmed from codebase/docs" (with exact source citation) and "inferred/recommended" (with rationale). Never blend the two.
17
+ - **Front-End / UI Mockups** — If the research request or context involves UI components, mockups, or Front-End (FE) design elements, include a detailed ASCII mockup representing the visual layout in your output.
17
18
 
18
19
  ## Mandatory Output Schema
19
20
 
@@ -6,6 +6,16 @@
6
6
  "*.env": "deny",
7
7
  "*.env.*": "deny",
8
8
  "secrets/**": "deny"
9
+ },
10
+ "edit": {
11
+ "*.env": "deny",
12
+ "*.env.*": "deny",
13
+ "secrets/**": "deny"
14
+ },
15
+ "write": {
16
+ "*.env": "deny",
17
+ "*.env.*": "deny",
18
+ "secrets/**": "deny"
9
19
  }
10
20
  },
11
21
  "agent": {
@@ -75,17 +85,8 @@
75
85
  "read": "allow",
76
86
  "glob": "allow",
77
87
  "grep": "allow",
78
- "edit": {
79
- "*": "deny",
80
- "src/**": "allow",
81
- "*.{json,md,txt}": "allow",
82
- "package.json": "allow"
83
- },
84
- "write": {
85
- "*": "deny",
86
- "src/**": "allow",
87
- "*.{json,md,txt}": "allow"
88
- },
88
+ "edit": "allow",
89
+ "write": "allow",
89
90
  "todowrite": "allow",
90
91
  "bash": {
91
92
  "*": "deny",
@@ -99,17 +100,10 @@
99
100
  "mv *": "allow",
100
101
  "cat *": "allow",
101
102
  "ls *": "allow",
102
- "pwd": "allow",
103
- "echo *": "allow",
104
103
  "rg *": "allow",
105
104
  "grep *": "allow",
106
105
  "find *": "allow",
107
- "which *": "allow",
108
- "where *": "allow",
109
- "dir *": "allow",
110
- "type *": "allow",
111
- "node --test": "allow",
112
- "cd *": "allow"
106
+ "node --test": "allow"
113
107
  },
114
108
  "task": "deny"
115
109
  }
@@ -117,7 +111,7 @@
117
111
 
118
112
  "tester": {
119
113
  "mode": "subagent",
120
- "description": "Runs tests on implemented code and reports results (PASS/FAIL/BLOCKED) with coverage gaps. Cannot fix code, redesign tests, or decide next steps.",
114
+ "description": "Runs tests on implemented code and reports results (PASS/FAIL/BLOCKED) with coverage gaps. Cannot fix code, install deps, or decide next steps.",
121
115
  "prompt": "{file:./prompts/tester.txt}",
122
116
  "permission": {
123
117
  "read": "allow",
@@ -126,7 +120,19 @@
126
120
  "edit": "deny",
127
121
  "write": "deny",
128
122
  "todowrite": "deny",
129
- "bash": "allow",
123
+ "bash": {
124
+ "*": "deny",
125
+ "node --test": "allow",
126
+ "npm test": "allow",
127
+ "npx *": "allow",
128
+ "rg *": "allow",
129
+ "grep *": "allow",
130
+ "find *": "allow",
131
+ "cat *": "allow",
132
+ "ls *": "allow",
133
+ "git diff*": "allow",
134
+ "git log*": "allow"
135
+ },
130
136
  "task": "deny"
131
137
  }
132
138
  },
@@ -156,14 +162,23 @@
156
162
 
157
163
  "compose-reporter": {
158
164
  "mode": "subagent",
159
- "description": "Text-file executor for Composer. Writes and edits .md/.txt files across the project like coder but for documents only. No bash.",
165
+ "description": "Text-file executor for Composer. Writes and edits .md/.txt files under docs/stateless formatter for documents only. No bash.",
160
166
  "prompt": "{file:./prompts/compose-reporter.txt}",
161
167
  "permission": {
162
- "read": "allow",
163
- "glob": "allow",
164
- "grep": "allow",
165
- "edit": "allow",
166
- "write": "allow",
168
+ "read": {
169
+ "*": "deny",
170
+ "docs/**": "allow"
171
+ },
172
+ "glob": "deny",
173
+ "grep": "deny",
174
+ "edit": {
175
+ "*": "deny",
176
+ "docs/**": "allow"
177
+ },
178
+ "write": {
179
+ "*": "deny",
180
+ "docs/**": "allow"
181
+ },
167
182
  "bash": "deny",
168
183
  "todowrite": "deny",
169
184
  "task": "deny"
@@ -181,7 +196,16 @@
181
196
  "grep": "allow",
182
197
  "edit": "deny",
183
198
  "write": "deny",
184
- "bash": "deny",
199
+ "bash": {
200
+ "*": "deny",
201
+ "rg *": "allow",
202
+ "grep *": "allow",
203
+ "find *": "allow",
204
+ "cat *": "allow",
205
+ "ls *": "allow",
206
+ "node --version": "allow",
207
+ "npm --version": "allow"
208
+ },
185
209
  "webfetch": "allow",
186
210
  "websearch": "allow",
187
211
  "task": "deny"
@@ -193,9 +217,12 @@
193
217
  "description": "Stateless formatter for Composer. Produces PRD documents from structured input — no inference, no decisions. PRD mode only.",
194
218
  "prompt": "{file:./prompts/plan-writer.txt}",
195
219
  "permission": {
196
- "read": "allow",
197
- "glob": "allow",
198
- "grep": "allow",
220
+ "read": {
221
+ "*": "deny",
222
+ "docs/plans/**": "allow"
223
+ },
224
+ "glob": "deny",
225
+ "grep": "deny",
199
226
  "edit": {
200
227
  "*": "deny",
201
228
  "docs/plans/**": "allow"
@@ -264,41 +291,28 @@
264
291
  "read": "allow",
265
292
  "glob": "allow",
266
293
  "grep": "allow",
267
- "edit": {
268
- "*": "deny",
269
- "src/**": "allow",
270
- "*.{json,md,txt}": "allow",
271
- "package.json": "allow"
272
- },
273
- "write": {
274
- "*": "deny",
275
- "src/**": "allow",
276
- "*.{json,md,txt}": "allow"
277
- },
294
+ "edit": "allow",
295
+ "write": "allow",
278
296
  "todowrite": "allow",
279
297
  "bash": {
280
298
  "*": "deny",
299
+ "node --test": "allow",
300
+ "npm test": "allow",
281
301
  "npm *": "allow",
282
302
  "npx *": "allow",
283
303
  "node *": "allow",
284
- "git *": "allow",
304
+ "git diff*": "allow",
305
+ "git log*": "allow",
306
+ "git show*": "allow",
285
307
  "mkdir *": "allow",
286
308
  "cp *": "allow",
287
309
  "rm *": "allow",
288
310
  "mv *": "allow",
289
311
  "cat *": "allow",
290
312
  "ls *": "allow",
291
- "pwd": "allow",
292
- "echo *": "allow",
293
313
  "rg *": "allow",
294
314
  "grep *": "allow",
295
- "find *": "allow",
296
- "which *": "allow",
297
- "where *": "allow",
298
- "dir *": "allow",
299
- "type *": "allow",
300
- "node --test": "allow",
301
- "cd *": "allow"
315
+ "find *": "allow"
302
316
  },
303
317
  "task": "deny"
304
318
  }
@@ -309,9 +323,12 @@
309
323
  "description": "Stateless formatter for Debugger. Produces diagnosis + remedy reports from structured input — no inference, no decisions. Summary mode only.",
310
324
  "prompt": "{file:./prompts/debug-reporter.txt}",
311
325
  "permission": {
312
- "read": "allow",
313
- "glob": "allow",
314
- "grep": "allow",
326
+ "read": {
327
+ "*": "deny",
328
+ "docs/reports/**": "allow"
329
+ },
330
+ "glob": "deny",
331
+ "grep": "deny",
315
332
  "edit": {
316
333
  "*": "deny",
317
334
  "docs/reports/**": "allow"
@@ -361,7 +378,22 @@
361
378
  "grep": "allow",
362
379
  "edit": "deny",
363
380
  "write": "deny",
364
- "bash": "allow",
381
+ "bash": {
382
+ "*": "deny",
383
+ "rg *": "allow",
384
+ "grep *": "allow",
385
+ "find *": "allow",
386
+ "cat *": "allow",
387
+ "ls *": "allow",
388
+ "git log*": "allow",
389
+ "git diff*": "allow",
390
+ "git show*": "allow",
391
+ "git blame*": "allow",
392
+ "node --version": "allow",
393
+ "npm --version": "allow",
394
+ "which *": "allow",
395
+ "where *": "allow"
396
+ },
365
397
  "task": "deny"
366
398
  }
367
399
  },
@@ -374,18 +406,8 @@
374
406
  "read": "allow",
375
407
  "glob": "allow",
376
408
  "grep": "allow",
377
- "edit": {
378
- "*": "deny",
379
- "src/**": "allow",
380
- "*.{json,md,txt}": "allow",
381
- "package.json": "allow"
382
- },
383
- "write": {
384
- "*": "deny",
385
- "src/**": "allow",
386
- "*.{json,md,txt}": "allow"
387
- },
388
- "todowrite": "allow",
409
+ "edit": "allow",
410
+ "write": "allow",
389
411
  "bash": {
390
412
  "*": "deny",
391
413
  "npm *": "allow",
@@ -398,17 +420,10 @@
398
420
  "mv *": "allow",
399
421
  "cat *": "allow",
400
422
  "ls *": "allow",
401
- "pwd": "allow",
402
- "echo *": "allow",
403
423
  "rg *": "allow",
404
424
  "grep *": "allow",
405
425
  "find *": "allow",
406
- "which *": "allow",
407
- "where *": "allow",
408
- "dir *": "allow",
409
- "type *": "allow",
410
- "node --test": "allow",
411
- "cd *": "allow"
426
+ "node --test": "allow"
412
427
  },
413
428
  "task": "deny"
414
429
  }
@@ -424,7 +439,17 @@
424
439
  "grep": "allow",
425
440
  "edit": "deny",
426
441
  "write": "deny",
427
- "bash": "allow",
442
+ "bash": {
443
+ "*": "deny",
444
+ "rg *": "allow",
445
+ "grep *": "allow",
446
+ "find *": "allow",
447
+ "cat *": "allow",
448
+ "git diff*": "allow",
449
+ "git log*": "allow",
450
+ "node --test": "allow",
451
+ "npm test": "allow"
452
+ },
428
453
  "task": "deny"
429
454
  }
430
455
  },
@@ -434,9 +459,12 @@
434
459
  "description": "Stateless formatter for Prober. Produces security audit reports under docs/reports/.",
435
460
  "prompt": "{file:./prompts/probe-reporter.txt}",
436
461
  "permission": {
437
- "read": "allow",
438
- "glob": "allow",
439
- "grep": "allow",
462
+ "read": {
463
+ "*": "deny",
464
+ "docs/reports/**": "allow"
465
+ },
466
+ "glob": "deny",
467
+ "grep": "deny",
440
468
  "edit": {
441
469
  "*": "deny",
442
470
  "docs/reports/**": "allow"