pluidr 0.6.0 → 0.7.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 +32 -24
- package/package.json +1 -1
- package/src/cli/commands/doctor.js +17 -15
- package/src/cli/commands/init.js +1 -1
- package/src/cli/commands/launch.js +44 -0
- package/src/cli/commands/uninstall.js +2 -6
- package/src/cli/commands/update.js +3 -19
- package/src/cli/index.js +16 -12
- package/src/cli/wizard/selectModelTier.js +3 -2
- package/src/core/backup.js +3 -1
- package/src/core/configBuilder.test.js +48 -2
- package/src/core/squeezeInstaller.js +28 -8
- package/src/core/squeezeInstaller.test.js +4 -6
- package/src/core/version.js +8 -0
- package/src/core/versionCheck.js +35 -0
- package/src/core/versionCheck.test.js +24 -0
- package/src/plugins/README.md +11 -11
- package/src/plugins/pluidr-squeeze.js +27 -6
- package/src/templates/agent-prompts/composer.txt +11 -9
- package/src/templates/agent-prompts/researcher.txt +1 -0
- package/src/templates/model-defaults.json +27 -9
- package/src/templates/opencode.config.json +108 -80
package/README.md
CHANGED
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
|
|
11
11
|
## What is Pluidr?
|
|
12
12
|
|
|
13
|
-
Pluidr installs
|
|
13
|
+
Pluidr installs **3 primary agents + 14 subagents** into OpenCode — each primary agent owns its exclusive subagents, scoped permissions, and enforced workflow rules. No shared subagents. No ad-hoc delegation.
|
|
14
14
|
|
|
15
|
-
| Primary Agent | Purpose | Subagents |
|
|
16
|
-
|
|
17
|
-
| **Composer** | Feature work — Explore → Plan → Build | Researcher, Plan-Writer, Plan-Checker, Coder, Tester, Reviewer, Compose-Reporter |
|
|
18
|
-
| **Debugger** | Bug investigation — Investigate → Fix → Report | Inspector, Fixer, Debug-Reporter |
|
|
19
|
-
| **Prober** | Security audit — Trace → Patch → Audit | Tracer, Patcher, Auditor, Probe-Reporter |
|
|
15
|
+
| Primary Agent | Model Tier | Purpose | Subagents |
|
|
16
|
+
|---|---|---|---|
|
|
17
|
+
| **Composer** | 🧠 Reasoning | Feature work — Explore → Plan → Build | Researcher, Plan-Writer, Plan-Checker, Coder, Tester, Reviewer, Compose-Reporter |
|
|
18
|
+
| **Debugger** | 🧠 Reasoning | Bug investigation — Investigate → Fix → Report | Inspector, Fixer, Debug-Reporter |
|
|
19
|
+
| **Prober** | 🧠 Reasoning | Security audit — Trace → Patch → Audit | Tracer, Patcher, Auditor, Probe-Reporter |
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
@@ -231,22 +231,22 @@ You trigger a security audit
|
|
|
231
231
|
|
|
232
232
|
Each subagent belongs to exactly one primary agent and cannot be invoked by anyone else:
|
|
233
233
|
|
|
234
|
-
| Primary | Phase | Subagent | Role |
|
|
235
|
-
|
|
236
|
-
| Composer | EXPLORE | Researcher | Fact-finding: confirmed_facts / inferred_facts / unknowns / risks |
|
|
237
|
-
| Composer | PLAN | Plan-Writer | **Formatter** — writes PRD to `docs/plans/`, missing input = TBD |
|
|
238
|
-
| Composer | PLAN | Plan-Checker | **Gate** — PASS/FAIL + gap list only, no suggestions |
|
|
239
|
-
| Composer | BUILD | Coder | Implements code from PRD |
|
|
240
|
-
| Composer | BUILD | Tester | PASS/FAIL/BLOCKED + coverage gaps only |
|
|
241
|
-
| Composer | BUILD | Reviewer | **Gate** — PASS/FAIL + gap list only, no suggestions |
|
|
242
|
-
| Composer | BUILD | Compose-Reporter | **Formatter** — completion report to `docs/reports/` |
|
|
243
|
-
| Debugger |
|
|
244
|
-
| Debugger |
|
|
245
|
-
| Debugger |
|
|
246
|
-
| Prober | TRACE | Tracer | WSTG recon + vuln path tracing, no remedies |
|
|
247
|
-
| Prober | PATCH | Patcher | Minimal, security-targeted fix (Ponytail, max 10 lines) |
|
|
248
|
-
| Prober | AUDIT | Auditor | **Gate** — PASS/FAIL + Gap List + BLOAT List only |
|
|
249
|
-
| Prober | AUDIT | Probe-Reporter | **Formatter** — security audit report to `docs/reports/` |
|
|
234
|
+
| Primary | Phase | Subagent | Model Tier | Role |
|
|
235
|
+
|---------|-------|----------|------------|------|
|
|
236
|
+
| Composer | EXPLORE | Researcher | 🧠 Reasoning | Fact-finding: confirmed_facts / inferred_facts / unknowns / risks |
|
|
237
|
+
| Composer | PLAN | Plan-Writer | ⚡ Fast | **Formatter** — writes PRD to `docs/plans/`, missing input = TBD |
|
|
238
|
+
| Composer | PLAN | Plan-Checker | 🧠 Reasoning | **Gate** — PASS/FAIL + gap list only, no suggestions |
|
|
239
|
+
| Composer | BUILD | Coder | ⚡ Fast | Implements code from PRD |
|
|
240
|
+
| Composer | BUILD | Tester | 🎯 Precision | PASS/FAIL/BLOCKED + coverage gaps only |
|
|
241
|
+
| Composer | BUILD | Reviewer | 🎯 Precision | **Gate** — PASS/FAIL + gap list only, no suggestions |
|
|
242
|
+
| Composer | BUILD | Compose-Reporter | ⚡ Fast | **Formatter** — completion report to `docs/reports/` |
|
|
243
|
+
| Debugger | INVESTIGATE | Inspector | 🧠 Reasoning | Brooks-Lint RCA (Iron Law + 6 decay risks + 4 review modes) |
|
|
244
|
+
| Debugger | FIX | Fixer | ⚡ Fast | Minimal, root-cause-targeted fix |
|
|
245
|
+
| Debugger | REPORT | Debug-Reporter | ⚡ Fast | **Formatter** — Iron Law diagnosis report to `docs/reports/` |
|
|
246
|
+
| Prober | TRACE | Tracer | 🎯 Precision | WSTG recon + vuln path tracing, no remedies |
|
|
247
|
+
| Prober | PATCH | Patcher | ⚡ Fast | Minimal, security-targeted fix (Ponytail, max 10 lines) |
|
|
248
|
+
| Prober | AUDIT | Auditor | 🎯 Precision | **Gate** — PASS/FAIL + Gap List + BLOAT List only |
|
|
249
|
+
| Prober | AUDIT | Probe-Reporter | ⚡ Fast | **Formatter** — security audit report to `docs/reports/` |
|
|
250
250
|
|
|
251
251
|
---
|
|
252
252
|
|
|
@@ -301,9 +301,17 @@ npx pluidr init
|
|
|
301
301
|
|
|
302
302
|
## Usage
|
|
303
303
|
|
|
304
|
+
### `pluidr`
|
|
305
|
+
|
|
306
|
+
The everyday command. Runs three steps in sequence:
|
|
307
|
+
|
|
308
|
+
1. **Update check** — compares local version against npm registry; prompts to `npm install -g pluidr` if a new version is available
|
|
309
|
+
2. **Doctor check** — verifies installation health; prompts to repair with `pluidr init` if any checks fail
|
|
310
|
+
3. **Launch** — spawns `opencode`
|
|
311
|
+
|
|
304
312
|
### `pluidr init`
|
|
305
313
|
|
|
306
|
-
Prompts you to select models for
|
|
314
|
+
Prompts you to select models for three agent tiers (reasoning, precision, and fast), then:
|
|
307
315
|
|
|
308
316
|
- Asks whether to install the pluidr-squeeze plugin (can decline)
|
|
309
317
|
- Builds a complete `opencode.jsonc` config with the chosen models injected into the right agents
|
|
@@ -328,7 +336,7 @@ Exits with code `0` if all pass, `1` if any fail.
|
|
|
328
336
|
|
|
329
337
|
### `pluidr update`
|
|
330
338
|
|
|
331
|
-
|
|
339
|
+
Checks the npm registry for a newer version of pluidr and prompts to install it.
|
|
332
340
|
|
|
333
341
|
### `pluidr uninstall`
|
|
334
342
|
|
package/package.json
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
import { existsSync, readFileSync, readdirSync } from "node:fs"
|
|
2
|
-
import { join
|
|
3
|
-
import { fileURLToPath } from "node:url"
|
|
4
|
-
import { execFileSync } from "node:child_process"
|
|
2
|
+
import { join } from "node:path"
|
|
5
3
|
import { getConfigDir, getConfigPath, getPromptsDir } from "../../core/paths.js"
|
|
6
|
-
import {
|
|
4
|
+
import { findSqueezePath } from "../../core/squeezeInstaller.js"
|
|
7
5
|
|
|
8
|
-
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
9
6
|
const EXPECTED_PROMPT_COUNT = 18
|
|
10
7
|
const PLUGIN_FILES = ["pluidr-flow.js", "pluidr-squeeze.js"]
|
|
11
8
|
|
|
12
|
-
export
|
|
9
|
+
export function collectChecks() {
|
|
13
10
|
const configDir = getConfigDir()
|
|
14
11
|
const configPath = getConfigPath()
|
|
15
12
|
const promptsDir = getPromptsDir()
|
|
@@ -54,16 +51,16 @@ export async function runDoctor() {
|
|
|
54
51
|
}
|
|
55
52
|
checks.push({ component: "package.json + plugin dep", pass: pkgValid })
|
|
56
53
|
|
|
57
|
-
// 5.
|
|
58
|
-
let
|
|
59
|
-
const
|
|
60
|
-
if (
|
|
61
|
-
|
|
54
|
+
// 5. Squeeze binary exists
|
|
55
|
+
let squeezeFound = false
|
|
56
|
+
const squeezeOnPath = findSqueezePath()
|
|
57
|
+
if (squeezeOnPath) {
|
|
58
|
+
squeezeFound = true
|
|
62
59
|
} else {
|
|
63
|
-
const binName = process.platform === "win32" ? "
|
|
64
|
-
|
|
60
|
+
const binName = process.platform === "win32" ? "squeeze.exe" : "squeeze"
|
|
61
|
+
squeezeFound = existsSync(join(binDir, binName))
|
|
65
62
|
}
|
|
66
|
-
checks.push({ component: "squeeze binary", pass:
|
|
63
|
+
checks.push({ component: "squeeze binary tested", pass: squeezeFound })
|
|
67
64
|
|
|
68
65
|
// 6. Config is valid JSON/JSONC
|
|
69
66
|
let configValid = false
|
|
@@ -79,8 +76,13 @@ export async function runDoctor() {
|
|
|
79
76
|
}
|
|
80
77
|
checks.push({ component: "config JSON validity", pass: configValid })
|
|
81
78
|
|
|
82
|
-
|
|
79
|
+
return checks
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export async function runDoctor() {
|
|
83
|
+
const checks = collectChecks()
|
|
83
84
|
const allPass = checks.every((c) => c.pass)
|
|
85
|
+
|
|
84
86
|
console.log("\nPluidr Doctor — Installation Health Check\n")
|
|
85
87
|
for (const c of checks) {
|
|
86
88
|
const icon = c.pass ? "\u2713" : "\u2717"
|
package/src/cli/commands/init.js
CHANGED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { spawn } from "node:child_process"
|
|
2
|
+
import { confirm, isCancel } from "@clack/prompts"
|
|
3
|
+
import { checkAndPromptUpdate } from "../../core/versionCheck.js"
|
|
4
|
+
import { version } from "../../core/version.js"
|
|
5
|
+
import { collectChecks } from "./doctor.js"
|
|
6
|
+
import { runInit } from "./init.js"
|
|
7
|
+
|
|
8
|
+
export async function runLaunch() {
|
|
9
|
+
// 1. Update check
|
|
10
|
+
await checkAndPromptUpdate(version)
|
|
11
|
+
|
|
12
|
+
// 2. Doctor check
|
|
13
|
+
process.stdout.write("Running doctor... ")
|
|
14
|
+
const checks = collectChecks()
|
|
15
|
+
const allPass = checks.every((c) => c.pass)
|
|
16
|
+
|
|
17
|
+
if (allPass) {
|
|
18
|
+
console.log("✓ All checks passed")
|
|
19
|
+
} else {
|
|
20
|
+
const failed = checks.filter((c) => !c.pass)
|
|
21
|
+
console.log(`✗ ${failed.length} check${failed.length > 1 ? "s" : ""} failed`)
|
|
22
|
+
for (const c of failed) {
|
|
23
|
+
const detail = c.detail ? ` (${c.detail})` : ""
|
|
24
|
+
console.log(` ✗ ${c.component}${detail}`)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const answer = await confirm({ message: "Repair with pluidr init?", initialValue: true })
|
|
28
|
+
if (!isCancel(answer) && answer) {
|
|
29
|
+
await runInit()
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// 3. Launch opencode
|
|
34
|
+
console.log("Launching opencode...")
|
|
35
|
+
const child = spawn("opencode", [], { stdio: "inherit" })
|
|
36
|
+
child.on("error", (err) => {
|
|
37
|
+
if (err.code === "ENOENT") {
|
|
38
|
+
console.error("Error: opencode not found on PATH. Install it from https://opencode.ai")
|
|
39
|
+
} else {
|
|
40
|
+
console.error(`Error launching opencode: ${err.message}`)
|
|
41
|
+
}
|
|
42
|
+
process.exit(1)
|
|
43
|
+
})
|
|
44
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, copyFileSync, rmSync, readdirSync } from "node:fs"
|
|
2
|
-
import { join
|
|
2
|
+
import { join } from "node:path"
|
|
3
3
|
import { getConfigDir, getConfigPath } from "../../core/paths.js"
|
|
4
4
|
|
|
5
5
|
function findLatestBackup() {
|
|
@@ -27,11 +27,7 @@ export async function runUninstall() {
|
|
|
27
27
|
|
|
28
28
|
// Restore latest backup
|
|
29
29
|
const latestBackup = findLatestBackup()
|
|
30
|
-
if (latestBackup
|
|
31
|
-
copyFileSync(latestBackup, configPath)
|
|
32
|
-
summary.restored = latestBackup
|
|
33
|
-
} else if (latestBackup) {
|
|
34
|
-
// Config doesn't exist but backup does — restore anyway
|
|
30
|
+
if (latestBackup) {
|
|
35
31
|
copyFileSync(latestBackup, configPath)
|
|
36
32
|
summary.restored = latestBackup
|
|
37
33
|
}
|
|
@@ -1,22 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { getConfigPath } from "../../core/paths.js"
|
|
4
|
-
import { runInit } from "./init.js"
|
|
1
|
+
import { checkAndPromptUpdate } from "../../core/versionCheck.js"
|
|
2
|
+
import { version } from "../../core/version.js"
|
|
5
3
|
|
|
6
4
|
export async function runUpdate() {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
if (existsSync(configPath)) {
|
|
10
|
-
const answer = await confirm({
|
|
11
|
-
message: "Existing Pluidr config found. Overwrite? (Y/n)",
|
|
12
|
-
initialValue: true,
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
if (isCancel(answer) || !answer) {
|
|
16
|
-
console.log("Update cancelled.")
|
|
17
|
-
return
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
await runInit()
|
|
5
|
+
await checkAndPromptUpdate(version)
|
|
22
6
|
}
|
package/src/cli/index.js
CHANGED
|
@@ -5,30 +5,33 @@ import { runInit } from "./commands/init.js"
|
|
|
5
5
|
import { runUninstall } from "./commands/uninstall.js"
|
|
6
6
|
import { runUpdate } from "./commands/update.js"
|
|
7
7
|
import { runDoctor } from "./commands/doctor.js"
|
|
8
|
+
import { runLaunch } from "./commands/launch.js"
|
|
9
|
+
import { version } from "../core/version.js"
|
|
8
10
|
|
|
9
|
-
const
|
|
10
|
-
const pkg = JSON.parse(readFileSync(resolve(__dirname, "../../package.json"), "utf-8"))
|
|
11
|
+
const HELP = `Usage: pluidr [command]
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
uninstall
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
--version, -v Print version number
|
|
20
|
-
--help, -h Show this help message`
|
|
13
|
+
pluidr Check for updates, run doctor, launch opencode
|
|
14
|
+
pluidr init Set up OpenCode with Pluidr's 17-agent pipeline
|
|
15
|
+
pluidr update Check for pluidr updates
|
|
16
|
+
pluidr doctor Verify installation health
|
|
17
|
+
pluidr uninstall Remove Pluidr artifacts and restore previous config
|
|
18
|
+
--version, -v Print version number
|
|
19
|
+
--help, -h Show this help message`
|
|
21
20
|
|
|
22
21
|
export function run(argv) {
|
|
23
22
|
const cmd = argv[2]
|
|
24
23
|
|
|
24
|
+
if (!cmd) {
|
|
25
|
+
return runLaunch()
|
|
26
|
+
}
|
|
27
|
+
|
|
25
28
|
if (cmd === "--help" || cmd === "-h") {
|
|
26
29
|
console.log(HELP)
|
|
27
30
|
return
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
if (cmd === "--version" || cmd === "-v") {
|
|
31
|
-
console.log(
|
|
34
|
+
console.log(version)
|
|
32
35
|
return
|
|
33
36
|
}
|
|
34
37
|
|
|
@@ -48,6 +51,7 @@ export function run(argv) {
|
|
|
48
51
|
return runDoctor()
|
|
49
52
|
}
|
|
50
53
|
|
|
54
|
+
console.error(`Unknown command: ${cmd}\n`)
|
|
51
55
|
console.log(HELP)
|
|
52
56
|
process.exit(1)
|
|
53
57
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { select, text, isCancel } from "@clack/prompts"
|
|
2
2
|
|
|
3
3
|
const TIER_LABELS = {
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
reasoning: "Model for reasoning task (orchestrators + deep analysis)",
|
|
5
|
+
precision: "Model for precision task (quality gates + verification)",
|
|
6
|
+
fast: "Model for fast/cheap task (builders + formatters)",
|
|
6
7
|
}
|
|
7
8
|
|
|
8
9
|
export async function selectModelTier(modelDefaults) {
|
package/src/core/backup.js
CHANGED
|
@@ -20,7 +20,9 @@ function rotateBackups(configPath) {
|
|
|
20
20
|
.filter((f) => f.startsWith(baseName + "."))
|
|
21
21
|
.map((f) => join(dir, f))
|
|
22
22
|
.sort()
|
|
23
|
-
} catch {
|
|
23
|
+
} catch (err) {
|
|
24
|
+
if (err.code !== "ENOENT") throw err
|
|
25
|
+
}
|
|
24
26
|
|
|
25
27
|
while (backups.length >= MAX_BACKUPS) {
|
|
26
28
|
const oldest = backups.shift()
|
|
@@ -12,15 +12,21 @@ describe("configBuilder", () => {
|
|
|
12
12
|
const configJson = {
|
|
13
13
|
agent: {
|
|
14
14
|
composer: {},
|
|
15
|
+
reviewer: {},
|
|
15
16
|
coder: {},
|
|
16
17
|
},
|
|
17
18
|
}
|
|
18
19
|
const defaultsJson = {
|
|
19
|
-
|
|
20
|
+
reasoning: {
|
|
20
21
|
provider: "test",
|
|
21
22
|
model: "big-model",
|
|
22
23
|
agents: ["composer"],
|
|
23
24
|
},
|
|
25
|
+
precision: {
|
|
26
|
+
provider: "test",
|
|
27
|
+
model: "mid-model",
|
|
28
|
+
agents: ["reviewer"],
|
|
29
|
+
},
|
|
24
30
|
fast: {
|
|
25
31
|
provider: "test",
|
|
26
32
|
model: "small-model",
|
|
@@ -33,15 +39,55 @@ describe("configBuilder", () => {
|
|
|
33
39
|
|
|
34
40
|
const result = buildConfig(
|
|
35
41
|
{
|
|
36
|
-
|
|
42
|
+
reasoning: "test/big-model",
|
|
43
|
+
precision: "test/mid-model",
|
|
37
44
|
fast: "test/small-model",
|
|
38
45
|
},
|
|
39
46
|
tmpDir,
|
|
40
47
|
)
|
|
41
48
|
|
|
42
49
|
assert.strictEqual(result.agent.composer.model, "test/big-model")
|
|
50
|
+
assert.strictEqual(result.agent.reviewer.model, "test/mid-model")
|
|
43
51
|
assert.strictEqual(result.agent.coder.model, "test/small-model")
|
|
44
52
|
|
|
45
53
|
rmSync(tmpDir, { recursive: true })
|
|
46
54
|
})
|
|
55
|
+
|
|
56
|
+
it("throws an error if a defaulted agent is missing from config template", () => {
|
|
57
|
+
const tmpDir = mkdtempSync(join(tmpdir(), "configbuilder-test-err-"))
|
|
58
|
+
|
|
59
|
+
const configJson = {
|
|
60
|
+
agent: {
|
|
61
|
+
// missing 'composer'
|
|
62
|
+
coder: {},
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
const defaultsJson = {
|
|
66
|
+
reasoning: {
|
|
67
|
+
provider: "test",
|
|
68
|
+
model: "big-model",
|
|
69
|
+
agents: ["composer"],
|
|
70
|
+
},
|
|
71
|
+
fast: {
|
|
72
|
+
provider: "test",
|
|
73
|
+
model: "small-model",
|
|
74
|
+
agents: ["coder"],
|
|
75
|
+
},
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
writeFileSync(join(tmpDir, "opencode.config.json"), JSON.stringify(configJson), "utf-8")
|
|
79
|
+
writeFileSync(join(tmpDir, "model-defaults.json"), JSON.stringify(defaultsJson), "utf-8")
|
|
80
|
+
|
|
81
|
+
assert.throws(() => {
|
|
82
|
+
buildConfig(
|
|
83
|
+
{
|
|
84
|
+
reasoning: "test/big-model",
|
|
85
|
+
fast: "test/small-model",
|
|
86
|
+
},
|
|
87
|
+
tmpDir,
|
|
88
|
+
)
|
|
89
|
+
}, /Template validation failed: agent\(s\) not found/)
|
|
90
|
+
|
|
91
|
+
rmSync(tmpDir, { recursive: true })
|
|
92
|
+
})
|
|
47
93
|
})
|
|
@@ -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
|
-
|
|
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
|
|
34
|
+
export function findSqueezePath() {
|
|
32
35
|
try {
|
|
33
36
|
const [cmd, ...args] =
|
|
34
|
-
process.platform === "win32" ? ["where", "
|
|
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 =
|
|
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 —
|
|
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
|
-
|
|
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(
|
|
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,
|
|
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("
|
|
70
|
-
it("returns null when
|
|
71
|
-
|
|
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
|
})
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs"
|
|
2
|
+
import { resolve, dirname } from "node:path"
|
|
3
|
+
import { fileURLToPath } from "node:url"
|
|
4
|
+
|
|
5
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
6
|
+
export const version = JSON.parse(
|
|
7
|
+
readFileSync(resolve(__dirname, "../../package.json"), "utf-8")
|
|
8
|
+
).version
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process"
|
|
2
|
+
import { confirm, isCancel } from "@clack/prompts"
|
|
3
|
+
|
|
4
|
+
export function fetchLatestVersion() {
|
|
5
|
+
try {
|
|
6
|
+
return execFileSync("npm", ["view", "pluidr", "version"], { stdio: "pipe" })
|
|
7
|
+
.toString()
|
|
8
|
+
.trim()
|
|
9
|
+
} catch {
|
|
10
|
+
return null
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function checkAndPromptUpdate(currentVersion) {
|
|
15
|
+
process.stdout.write("Checking for updates... ")
|
|
16
|
+
|
|
17
|
+
const latest = fetchLatestVersion()
|
|
18
|
+
if (!latest) {
|
|
19
|
+
console.log("⚠ Could not reach npm registry — skipping")
|
|
20
|
+
return false
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (latest === currentVersion) {
|
|
24
|
+
console.log(`✓ Up to date (${currentVersion})`)
|
|
25
|
+
return false
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
console.log(`⚠ pluidr ${latest} available (you have ${currentVersion})`)
|
|
29
|
+
|
|
30
|
+
const answer = await confirm({ message: "Update now?", initialValue: true })
|
|
31
|
+
if (isCancel(answer) || !answer) return false
|
|
32
|
+
|
|
33
|
+
execFileSync("npm", ["install", "-g", "pluidr"], { stdio: "inherit" })
|
|
34
|
+
return true
|
|
35
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { describe, it } from "node:test"
|
|
2
|
+
import assert from "node:assert"
|
|
3
|
+
import { execFileSync } from "node:child_process"
|
|
4
|
+
import { fetchLatestVersion } from "./versionCheck.js"
|
|
5
|
+
|
|
6
|
+
describe("fetchLatestVersion", () => {
|
|
7
|
+
it("returns a string or null", () => {
|
|
8
|
+
// Either reaches npm registry (string) or fails gracefully (null)
|
|
9
|
+
const result = fetchLatestVersion()
|
|
10
|
+
assert.ok(result === null || typeof result === "string")
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it("returns null when npm command throws", () => {
|
|
14
|
+
// Verify the try/catch in fetchLatestVersion swallows errors
|
|
15
|
+
let caught = null
|
|
16
|
+
try {
|
|
17
|
+
execFileSync("npm", ["view", "__nonexistent_pkg_xyz_pluidr__", "version"], { stdio: "pipe" })
|
|
18
|
+
} catch (err) {
|
|
19
|
+
caught = err
|
|
20
|
+
}
|
|
21
|
+
// npm throws on unknown package — fetchLatestVersion handles this and returns null
|
|
22
|
+
assert.ok(caught instanceof Error, "npm should throw for unknown package")
|
|
23
|
+
})
|
|
24
|
+
})
|
package/src/plugins/README.md
CHANGED
|
@@ -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 `
|
|
25
|
-
groups, truncates, and deduplicates command
|
|
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
|
|
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/
|
|
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 `
|
|
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 `
|
|
65
|
-
to `~/.config/opencode/bin/`. On OpenCode's first launch, the
|
|
66
|
-
runtime detects the `package.json` and runs `bun install`
|
|
67
|
-
no user action required. Once installed, both plugins are
|
|
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" ? "
|
|
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
|
|
36
|
+
await $`where squeeze`.quiet()
|
|
16
37
|
} else {
|
|
17
|
-
await $`which
|
|
38
|
+
await $`which squeeze`.quiet()
|
|
18
39
|
}
|
|
19
|
-
return "
|
|
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]
|
|
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.
|
|
145
|
+
the PRD phase.
|
|
146
146
|
|
|
147
|
-
|
|
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
|
-
|
|
155
|
-
|
|
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
|
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
|
-
"
|
|
2
|
+
"reasoning": {
|
|
3
3
|
"provider": "opencode",
|
|
4
4
|
"model": "big-pickle",
|
|
5
5
|
"agents": [
|
|
6
6
|
"composer",
|
|
7
7
|
"debugger",
|
|
8
|
+
"prober",
|
|
8
9
|
"researcher",
|
|
9
10
|
"plan-checker",
|
|
10
|
-
"inspector"
|
|
11
|
-
"prober"
|
|
11
|
+
"inspector"
|
|
12
12
|
],
|
|
13
13
|
|
|
14
14
|
"recommended": [
|
|
@@ -23,20 +23,38 @@
|
|
|
23
23
|
{ "value": "openai/gpt-5.5", "label": "openai/gpt-5.5" }
|
|
24
24
|
]
|
|
25
25
|
},
|
|
26
|
+
"precision": {
|
|
27
|
+
"provider": "opencode",
|
|
28
|
+
"model": "big-pickle",
|
|
29
|
+
"agents": [
|
|
30
|
+
"tester",
|
|
31
|
+
"reviewer",
|
|
32
|
+
"auditor",
|
|
33
|
+
"tracer"
|
|
34
|
+
],
|
|
35
|
+
|
|
36
|
+
"recommended": [
|
|
37
|
+
{
|
|
38
|
+
"value": "opencode/big-pickle",
|
|
39
|
+
"label": "default (opencode/big-pickle)"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"value": "anthropic/claude-sonnet-4-5",
|
|
43
|
+
"label": "anthropic/claude-sonnet-4-5"
|
|
44
|
+
},
|
|
45
|
+
{ "value": "openai/gpt-5.1", "label": "openai/gpt-5.1" }
|
|
46
|
+
]
|
|
47
|
+
},
|
|
26
48
|
"fast": {
|
|
27
49
|
"provider": "opencode",
|
|
28
50
|
"model": "deepseek-v4-flash-free",
|
|
29
51
|
"agents": [
|
|
30
52
|
"coder",
|
|
31
|
-
"tester",
|
|
32
|
-
"reviewer",
|
|
33
|
-
"compose-reporter",
|
|
34
53
|
"fixer",
|
|
54
|
+
"patcher",
|
|
35
55
|
"plan-writer",
|
|
56
|
+
"compose-reporter",
|
|
36
57
|
"debug-reporter",
|
|
37
|
-
"tracer",
|
|
38
|
-
"patcher",
|
|
39
|
-
"auditor",
|
|
40
58
|
"probe-reporter"
|
|
41
59
|
],
|
|
42
60
|
|
|
@@ -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
|
-
|
|
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
|
-
"
|
|
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,
|
|
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":
|
|
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
|
|
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":
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
"
|
|
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":
|
|
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":
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
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":
|
|
313
|
-
|
|
314
|
-
|
|
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":
|
|
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
|
-
|
|
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
|
-
"
|
|
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":
|
|
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":
|
|
438
|
-
|
|
439
|
-
|
|
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"
|