pluidr 0.7.1 → 0.7.3
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 +25 -26
- package/package.json +45 -45
- package/src/cli/commands/doctor.js +14 -2
- package/src/cli/commands/init.js +6 -2
- package/src/cli/commands/launch.js +1 -1
- package/src/cli/commands/uninstall.js +27 -0
- package/src/core/backup.js +11 -5
- package/src/core/paths.js +2 -0
- package/src/core/paths.test.js +9 -1
- package/src/core/squeezeInstaller.js +2 -5
- package/src/core/squeezeInstaller.test.js +4 -0
- package/src/core/themeWriter.js +12 -0
- package/src/core/themeWriter.test.js +29 -0
- package/src/core/tuiConfigWriter.js +22 -0
- package/src/core/tuiConfigWriter.test.js +38 -0
- package/src/templates/agent-prompts/composer.txt +1 -2
- package/src/templates/agent-prompts/debugger.txt +1 -0
- package/src/templates/agent-prompts/prober.txt +4 -0
- package/src/templates/opencode.config.json +4 -0
- package/src/templates/themes/pluidr-contrast.json +177 -0
package/README.md
CHANGED
|
@@ -12,11 +12,11 @@
|
|
|
12
12
|
|
|
13
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 |
|
|
16
|
-
|
|
17
|
-
| **Composer** |
|
|
18
|
-
| **Debugger** |
|
|
19
|
-
| **Prober** |
|
|
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 |
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
@@ -35,21 +35,20 @@ You describe a feature / idea
|
|
|
35
35
|
│ │
|
|
36
36
|
│ Delegate: Researcher │
|
|
37
37
|
│ → deep codebase + web fact-finding │
|
|
38
|
-
│ →
|
|
39
|
-
│ unknowns / risks │
|
|
38
|
+
│ → confirmed-inferred facts / risks │
|
|
40
39
|
│ │
|
|
41
40
|
│ Composer internally assesses: │
|
|
42
41
|
│ Is this feature simple or complex? │
|
|
43
42
|
└──────────┬──────────────────────────────┘
|
|
44
43
|
│
|
|
45
|
-
|
|
46
|
-
│ Simple feature
|
|
47
|
-
▼
|
|
48
|
-
GUARDRAIL GATE 1a
|
|
49
|
-
"Build directly?"
|
|
50
|
-
│
|
|
51
|
-
│ Yes
|
|
52
|
-
│
|
|
44
|
+
┌────────┴───────────────┐
|
|
45
|
+
│ Simple feature │ Complex feature
|
|
46
|
+
▼ ▼
|
|
47
|
+
GUARDRAIL GATE 1a GUARDRAIL GATE 1b
|
|
48
|
+
"Build directly?" "Write a PRD?"
|
|
49
|
+
│ │
|
|
50
|
+
│ Yes │ Yes
|
|
51
|
+
│ ▼
|
|
53
52
|
│ ┌──────────────────────────────┐
|
|
54
53
|
│ │ PLAN PHASE │
|
|
55
54
|
│ │ │
|
|
@@ -62,9 +61,9 @@ GUARDRAIL GATE 1a GUARDRAIL GATE 1b
|
|
|
62
61
|
│ │ │
|
|
63
62
|
│ │ PASS → GUARDRAIL GATE 2 │
|
|
64
63
|
│ │ "Build from this PRD?" │
|
|
65
|
-
│
|
|
66
|
-
│
|
|
67
|
-
|
|
64
|
+
│ └─────────────┬────────────────┘
|
|
65
|
+
│ │ Yes
|
|
66
|
+
└──────────┬─────────────┘
|
|
68
67
|
▼
|
|
69
68
|
┌────────────────────────────────────────────┐
|
|
70
69
|
│ BUILD PHASE │
|
|
@@ -293,12 +292,6 @@ All agents resolve conflicts using this priority order (defined in `hierarchy.tx
|
|
|
293
292
|
npm install -g pluidr
|
|
294
293
|
```
|
|
295
294
|
|
|
296
|
-
Or run directly without installing:
|
|
297
|
-
|
|
298
|
-
```sh
|
|
299
|
-
npx pluidr init
|
|
300
|
-
```
|
|
301
|
-
|
|
302
295
|
## Usage
|
|
303
296
|
|
|
304
297
|
### `pluidr`
|
|
@@ -328,6 +321,7 @@ Checks installation health and reports ✓/✗ for each component:
|
|
|
328
321
|
- `opencode.jsonc` exists
|
|
329
322
|
- All 18 prompt files present
|
|
330
323
|
- Both plugin files present
|
|
324
|
+
- `pluidr-contrast` theme file present
|
|
331
325
|
- `package.json` with `@opencode-ai/plugin` dependency
|
|
332
326
|
- squeeze binary available
|
|
333
327
|
- Config is valid JSON
|
|
@@ -343,12 +337,13 @@ Checks the npm registry for a newer version of pluidr and prompts to install it.
|
|
|
343
337
|
Restores your previous configuration:
|
|
344
338
|
|
|
345
339
|
- Finds the latest timestamped backup and restores it to `opencode.jsonc`
|
|
346
|
-
-
|
|
340
|
+
- Restores `tui.json` from the latest backup (returning the TUI theme to its pre-Pluidr state)
|
|
341
|
+
- Removes `prompts/`, `plugins/`, `bin/`, and `themes/pluidr-contrast.json` directories and files
|
|
347
342
|
- Preserves `opencode.jsonc` and `package.json`
|
|
348
343
|
|
|
349
344
|
---
|
|
350
345
|
|
|
351
|
-
## Bundled
|
|
346
|
+
## Bundled Package
|
|
352
347
|
|
|
353
348
|
### `pluidr-flow`
|
|
354
349
|
|
|
@@ -363,3 +358,7 @@ Provides subagents with cross-session context access:
|
|
|
363
358
|
Hooks into tool execution to rewrite bash commands through the `squeeze` binary, filtering verbose output and saving **60–90% of tokens** across all agents.
|
|
364
359
|
|
|
365
360
|
Both plugins and their dependency declaration are installed automatically by `pluidr init` — no extra user action required. On OpenCode's first launch, the bundled Bun runtime installs `@opencode-ai/plugin` from the generated `package.json`.
|
|
361
|
+
|
|
362
|
+
### Themes & Colors
|
|
363
|
+
|
|
364
|
+
Pluidr comes with a pre-configured OLED high-contrast dark theme called **`pluidr-contrast`** that is automatically applied to your OpenCode terminal interface.
|
package/package.json
CHANGED
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "pluidr",
|
|
3
|
-
"version": "0.7.
|
|
4
|
-
"description": "Opinionated Engineering Workflow for OpenCode —
|
|
5
|
-
"keywords": [
|
|
6
|
-
"opencode",
|
|
7
|
-
"workflow",
|
|
8
|
-
"engineering",
|
|
9
|
-
"pipeline",
|
|
10
|
-
"ai-agents",
|
|
11
|
-
"code-review",
|
|
12
|
-
"debugging",
|
|
13
|
-
"security-audit",
|
|
14
|
-
"security",
|
|
15
|
-
"planner",
|
|
16
|
-
"builder",
|
|
17
|
-
"cli"
|
|
18
|
-
],
|
|
19
|
-
"license": "MIT",
|
|
20
|
-
"type": "module",
|
|
21
|
-
"scripts": {
|
|
22
|
-
"test": "node --test"
|
|
23
|
-
},
|
|
24
|
-
"bin": {
|
|
25
|
-
"pluidr": "bin/pluidr.js"
|
|
26
|
-
},
|
|
27
|
-
"repository": {
|
|
28
|
-
"type": "git",
|
|
29
|
-
"url": "git+https://github.com/funara/pluidr.git"
|
|
30
|
-
},
|
|
31
|
-
"homepage": "https://github.com/funara/pluidr#readme",
|
|
32
|
-
"bugs": {
|
|
33
|
-
"url": "https://github.com/funara/pluidr/issues"
|
|
34
|
-
},
|
|
35
|
-
"engines": {
|
|
36
|
-
"node": ">=18"
|
|
37
|
-
},
|
|
38
|
-
"files": [
|
|
39
|
-
"bin",
|
|
40
|
-
"src"
|
|
41
|
-
],
|
|
42
|
-
"dependencies": {
|
|
43
|
-
"@clack/prompts": "^0.9"
|
|
44
|
-
}
|
|
45
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "pluidr",
|
|
3
|
+
"version": "0.7.3",
|
|
4
|
+
"description": "Opinionated Engineering Workflow for OpenCode — Plan, Build, Review.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"opencode",
|
|
7
|
+
"workflow",
|
|
8
|
+
"engineering",
|
|
9
|
+
"pipeline",
|
|
10
|
+
"ai-agents",
|
|
11
|
+
"code-review",
|
|
12
|
+
"debugging",
|
|
13
|
+
"security-audit",
|
|
14
|
+
"security",
|
|
15
|
+
"planner",
|
|
16
|
+
"builder",
|
|
17
|
+
"cli"
|
|
18
|
+
],
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"type": "module",
|
|
21
|
+
"scripts": {
|
|
22
|
+
"test": "node --test"
|
|
23
|
+
},
|
|
24
|
+
"bin": {
|
|
25
|
+
"pluidr": "bin/pluidr.js"
|
|
26
|
+
},
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git+https://github.com/funara/pluidr.git"
|
|
30
|
+
},
|
|
31
|
+
"homepage": "https://github.com/funara/pluidr#readme",
|
|
32
|
+
"bugs": {
|
|
33
|
+
"url": "https://github.com/funara/pluidr/issues"
|
|
34
|
+
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=18"
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"bin",
|
|
40
|
+
"src"
|
|
41
|
+
],
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@clack/prompts": "^0.9"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import { existsSync, readFileSync, readdirSync } from "node:fs"
|
|
2
2
|
import { join } from "node:path"
|
|
3
|
-
import { getConfigDir, getConfigPath, getPromptsDir } from "../../core/paths.js"
|
|
3
|
+
import { getConfigDir, getConfigPath, getPromptsDir, getThemesDir } from "../../core/paths.js"
|
|
4
4
|
import { findSqueezePath } from "../../core/squeezeInstaller.js"
|
|
5
5
|
|
|
6
6
|
const EXPECTED_PROMPT_COUNT = 18
|
|
7
7
|
const PLUGIN_FILES = ["pluidr-flow.js", "pluidr-squeeze.js"]
|
|
8
8
|
|
|
9
|
+
export function cleanJsonc(content) {
|
|
10
|
+
return content.replace(
|
|
11
|
+
/\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)|(,\s*(?=[\]}]))/g,
|
|
12
|
+
(m, g1, g2) => g1 ? "" : g2 ? "" : m
|
|
13
|
+
)
|
|
14
|
+
}
|
|
15
|
+
|
|
9
16
|
export function collectChecks() {
|
|
10
17
|
const configDir = getConfigDir()
|
|
11
18
|
const configPath = getConfigPath()
|
|
@@ -13,6 +20,7 @@ export function collectChecks() {
|
|
|
13
20
|
const pluginsDir = join(configDir, "plugins")
|
|
14
21
|
const packageJsonPath = join(configDir, "package.json")
|
|
15
22
|
const binDir = join(configDir, "bin")
|
|
23
|
+
const themesDir = getThemesDir()
|
|
16
24
|
|
|
17
25
|
const checks = []
|
|
18
26
|
|
|
@@ -39,6 +47,10 @@ export function collectChecks() {
|
|
|
39
47
|
}
|
|
40
48
|
checks.push({ component: "plugin files", pass: pluginsFound === PLUGIN_FILES.length, detail: `${pluginsFound}/${PLUGIN_FILES.length}` })
|
|
41
49
|
|
|
50
|
+
// 3b. Custom theme exists
|
|
51
|
+
const themeExists = existsSync(join(themesDir, "pluidr-contrast.json"))
|
|
52
|
+
checks.push({ component: "pluidr-contrast theme file", pass: themeExists })
|
|
53
|
+
|
|
42
54
|
// 4. package.json with @opencode-ai/plugin dependency
|
|
43
55
|
let pkgValid = false
|
|
44
56
|
if (existsSync(packageJsonPath)) {
|
|
@@ -67,7 +79,7 @@ export function collectChecks() {
|
|
|
67
79
|
if (configExists) {
|
|
68
80
|
try {
|
|
69
81
|
const content = readFileSync(configPath, "utf-8")
|
|
70
|
-
const cleaned = content
|
|
82
|
+
const cleaned = cleanJsonc(content)
|
|
71
83
|
JSON.parse(cleaned)
|
|
72
84
|
configValid = true
|
|
73
85
|
} catch {
|
package/src/cli/commands/init.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs"
|
|
1
|
+
import { readFileSync, writeFileSync } from "node:fs"
|
|
2
2
|
import { resolve, dirname } from "node:path"
|
|
3
3
|
import { fileURLToPath } from "node:url"
|
|
4
4
|
import { confirm, isCancel } from "@clack/prompts"
|
|
@@ -8,8 +8,10 @@ import { buildConfig } from "../../core/configBuilder.js"
|
|
|
8
8
|
import { writeConfig } from "../../core/configWriter.js"
|
|
9
9
|
import { writeAgentPrompts } from "../../core/agentPromptWriter.js"
|
|
10
10
|
import { writePluginBundle, writePluginPackageJson } from "../../core/pluginWriter.js"
|
|
11
|
+
import { writeThemes } from "../../core/themeWriter.js"
|
|
12
|
+
import { writeTuiConfig } from "../../core/tuiConfigWriter.js"
|
|
11
13
|
import { installSqueeze } from "../../core/squeezeInstaller.js"
|
|
12
|
-
import { getConfigPath } from "../../core/paths.js"
|
|
14
|
+
import { getConfigPath, getConfigDir } from "../../core/paths.js"
|
|
13
15
|
|
|
14
16
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
15
17
|
const TEMPLATES_DIR = resolve(__dirname, "../../templates")
|
|
@@ -32,6 +34,8 @@ export async function runInit() {
|
|
|
32
34
|
const configObject = buildConfig(tierChoices, TEMPLATES_DIR)
|
|
33
35
|
writeConfig(configObject)
|
|
34
36
|
writeAgentPrompts(TEMPLATES_DIR)
|
|
37
|
+
writeThemes(TEMPLATES_DIR)
|
|
38
|
+
writeTuiConfig("pluidr-contrast")
|
|
35
39
|
writePluginBundle()
|
|
36
40
|
writePluginPackageJson()
|
|
37
41
|
if (installSqueezePlugin) {
|
|
@@ -31,7 +31,7 @@ export async function runLaunch() {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
// 3. Launch opencode
|
|
34
|
-
console.log("Launching
|
|
34
|
+
console.log("Launching pluidr...")
|
|
35
35
|
const child = process.platform === "win32"
|
|
36
36
|
? spawn("opencode", { stdio: "inherit", shell: true })
|
|
37
37
|
: spawn("opencode", [], { stdio: "inherit" })
|
|
@@ -16,6 +16,20 @@ function findLatestBackup() {
|
|
|
16
16
|
return backups.length > 0 ? join(dir, backups[0]) : null
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
function findLatestTuiBackup() {
|
|
20
|
+
const dir = getConfigDir()
|
|
21
|
+
let backups = []
|
|
22
|
+
try {
|
|
23
|
+
backups = readdirSync(dir)
|
|
24
|
+
.filter((f) => f.startsWith("tui.json.bak."))
|
|
25
|
+
.sort()
|
|
26
|
+
.reverse()
|
|
27
|
+
} catch {
|
|
28
|
+
return null
|
|
29
|
+
}
|
|
30
|
+
return backups.length > 0 ? join(dir, backups[0]) : null
|
|
31
|
+
}
|
|
32
|
+
|
|
19
33
|
export async function runUninstall() {
|
|
20
34
|
const configDir = getConfigDir()
|
|
21
35
|
const configPath = getConfigPath()
|
|
@@ -32,6 +46,14 @@ export async function runUninstall() {
|
|
|
32
46
|
summary.restored = latestBackup
|
|
33
47
|
}
|
|
34
48
|
|
|
49
|
+
const latestTuiBackup = findLatestTuiBackup()
|
|
50
|
+
const tuiConfigPath = join(configDir, "tui.json")
|
|
51
|
+
if (latestTuiBackup) {
|
|
52
|
+
copyFileSync(latestTuiBackup, tuiConfigPath)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const themesDir = join(configDir, "themes")
|
|
56
|
+
|
|
35
57
|
// Remove Pluidr-installed artifacts
|
|
36
58
|
if (existsSync(promptsDir)) {
|
|
37
59
|
rmSync(promptsDir, { recursive: true, force: true })
|
|
@@ -45,6 +67,11 @@ export async function runUninstall() {
|
|
|
45
67
|
rmSync(binDir, { recursive: true, force: true })
|
|
46
68
|
summary.removed.push("bin/")
|
|
47
69
|
}
|
|
70
|
+
const customThemePath = join(themesDir, "pluidr-contrast.json")
|
|
71
|
+
if (existsSync(customThemePath)) {
|
|
72
|
+
rmSync(customThemePath, { force: true })
|
|
73
|
+
summary.removed.push("themes/pluidr-contrast.json")
|
|
74
|
+
}
|
|
48
75
|
|
|
49
76
|
// Print summary
|
|
50
77
|
console.log("Uninstall complete:")
|
package/src/core/backup.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, copyFileSync, readdirSync, unlinkSync } from "node:fs"
|
|
2
2
|
import { join, dirname } from "node:path"
|
|
3
|
-
import { getConfigPath } from "./paths.js"
|
|
3
|
+
import { getConfigPath, getTuiConfigPath } from "./paths.js"
|
|
4
4
|
|
|
5
5
|
const MAX_BACKUPS = 5
|
|
6
6
|
|
|
@@ -32,9 +32,15 @@ function rotateBackups(configPath) {
|
|
|
32
32
|
|
|
33
33
|
export function backupExistingConfig(configPath) {
|
|
34
34
|
const resolvedPath = configPath || getConfigPath()
|
|
35
|
-
if (
|
|
35
|
+
if (existsSync(resolvedPath)) {
|
|
36
|
+
rotateBackups(resolvedPath)
|
|
37
|
+
const backupPath = `${resolvedPath}.bak.${timestamp()}`
|
|
38
|
+
copyFileSync(resolvedPath, backupPath)
|
|
39
|
+
}
|
|
36
40
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
41
|
+
const tuiPath = getTuiConfigPath()
|
|
42
|
+
if (existsSync(tuiPath)) {
|
|
43
|
+
const backupTuiPath = `${tuiPath}.bak.${timestamp()}`
|
|
44
|
+
copyFileSync(tuiPath, backupTuiPath)
|
|
45
|
+
}
|
|
40
46
|
}
|
package/src/core/paths.js
CHANGED
|
@@ -5,5 +5,7 @@ const BASE = join(homedir(), ".config", "opencode")
|
|
|
5
5
|
|
|
6
6
|
export const getConfigDir = () => BASE
|
|
7
7
|
export const getConfigPath = () => join(BASE, "opencode.jsonc")
|
|
8
|
+
export const getTuiConfigPath = () => join(BASE, "tui.json")
|
|
8
9
|
export const getPromptsDir = () => join(BASE, "prompts")
|
|
10
|
+
export const getThemesDir = () => join(BASE, "themes")
|
|
9
11
|
// ponytail: getBackupPath removed (YAGNI)
|
package/src/core/paths.test.js
CHANGED
|
@@ -2,7 +2,7 @@ import { describe, it } from "node:test"
|
|
|
2
2
|
import assert from "node:assert"
|
|
3
3
|
import { homedir } from "node:os"
|
|
4
4
|
import { join } from "node:path"
|
|
5
|
-
import { getConfigDir, getConfigPath, getPromptsDir } from "./paths.js"
|
|
5
|
+
import { getConfigDir, getConfigPath, getTuiConfigPath, getPromptsDir, getThemesDir } from "./paths.js"
|
|
6
6
|
|
|
7
7
|
const BASE = join(homedir(), ".config", "opencode")
|
|
8
8
|
|
|
@@ -15,7 +15,15 @@ describe("paths", () => {
|
|
|
15
15
|
assert.strictEqual(getConfigPath(), join(BASE, "opencode.jsonc"))
|
|
16
16
|
})
|
|
17
17
|
|
|
18
|
+
it("getTuiConfigPath returns tui.json path", () => {
|
|
19
|
+
assert.strictEqual(getTuiConfigPath(), join(BASE, "tui.json"))
|
|
20
|
+
})
|
|
21
|
+
|
|
18
22
|
it("getPromptsDir returns prompts path", () => {
|
|
19
23
|
assert.strictEqual(getPromptsDir(), join(BASE, "prompts"))
|
|
20
24
|
})
|
|
25
|
+
|
|
26
|
+
it("getThemesDir returns themes path", () => {
|
|
27
|
+
assert.strictEqual(getThemesDir(), join(BASE, "themes"))
|
|
28
|
+
})
|
|
21
29
|
})
|
|
@@ -26,7 +26,7 @@ export function platformAsset() {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
export function validatePath(path) {
|
|
29
|
-
if (!/^[a-zA-Z0-9_\-\.\:\/\\]+$/.test(path)) {
|
|
29
|
+
if (!/^[a-zA-Z0-9_\-\.\:\/\\ ]+$/.test(path)) {
|
|
30
30
|
throw new Error(`Invalid path: "${path}" contains unsafe characters`)
|
|
31
31
|
}
|
|
32
32
|
}
|
|
@@ -53,10 +53,7 @@ function extract(archivePath, destDir) {
|
|
|
53
53
|
execFileSync("powershell", [
|
|
54
54
|
"-NoProfile",
|
|
55
55
|
"-Command",
|
|
56
|
-
|
|
57
|
-
"-LiteralPath", archivePath,
|
|
58
|
-
"-DestinationPath", destDir,
|
|
59
|
-
"-Force"
|
|
56
|
+
`Expand-Archive -LiteralPath '${archivePath}' -DestinationPath '${destDir}' -Force`
|
|
60
57
|
], { stdio: "ignore" })
|
|
61
58
|
} else {
|
|
62
59
|
execFileSync("tar", ["-xzf", archivePath, "-C", destDir], { stdio: "ignore" })
|
|
@@ -41,6 +41,10 @@ describe("validatePath", () => {
|
|
|
41
41
|
assert.doesNotThrow(() => validatePath("C:\\Users\\test"))
|
|
42
42
|
})
|
|
43
43
|
|
|
44
|
+
it("accepts paths with spaces", () => {
|
|
45
|
+
assert.doesNotThrow(() => validatePath("C:\\Users\\John Doe\\.config"))
|
|
46
|
+
})
|
|
47
|
+
|
|
44
48
|
it("accepts paths with dots and dashes", () => {
|
|
45
49
|
assert.doesNotThrow(() => validatePath("./dir/file-name.ext"))
|
|
46
50
|
})
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { mkdirSync, copyFileSync } from "node:fs"
|
|
2
|
+
import { resolve } from "node:path"
|
|
3
|
+
import { getThemesDir } from "./paths.js"
|
|
4
|
+
|
|
5
|
+
export function writeThemes(templatesDir, destDir) {
|
|
6
|
+
const sourcePath = resolve(templatesDir, "themes", "pluidr-contrast.json")
|
|
7
|
+
const targetDir = destDir || getThemesDir()
|
|
8
|
+
const destPath = resolve(targetDir, "pluidr-contrast.json")
|
|
9
|
+
|
|
10
|
+
mkdirSync(targetDir, { recursive: true })
|
|
11
|
+
copyFileSync(sourcePath, destPath)
|
|
12
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { describe, it, after } from "node:test"
|
|
2
|
+
import assert from "node:assert"
|
|
3
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync, mkdirSync, writeFileSync } from "node:fs"
|
|
4
|
+
import { join } from "node:path"
|
|
5
|
+
import { tmpdir } from "node:os"
|
|
6
|
+
import { writeThemes } from "./themeWriter.js"
|
|
7
|
+
|
|
8
|
+
describe("themeWriter", () => {
|
|
9
|
+
const destDir = mkdtempSync(join(tmpdir(), "pluidr-theme-dest-"))
|
|
10
|
+
|
|
11
|
+
after(() => {
|
|
12
|
+
if (existsSync(destDir)) rmSync(destDir, { recursive: true })
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it("copies theme file to destination directory", () => {
|
|
16
|
+
const tmpSrcDir = mkdtempSync(join(tmpdir(), "pluidr-theme-src-"))
|
|
17
|
+
const srcThemesDir = join(tmpSrcDir, "themes")
|
|
18
|
+
mkdirSync(srcThemesDir, { recursive: true })
|
|
19
|
+
writeFileSync(join(srcThemesDir, "pluidr-contrast.json"), "mock-theme-content", "utf-8")
|
|
20
|
+
|
|
21
|
+
writeThemes(tmpSrcDir, destDir)
|
|
22
|
+
|
|
23
|
+
const destPath = join(destDir, "pluidr-contrast.json")
|
|
24
|
+
assert.ok(existsSync(destPath), "theme file should be copied")
|
|
25
|
+
assert.strictEqual(readFileSync(destPath, "utf-8"), "mock-theme-content")
|
|
26
|
+
|
|
27
|
+
rmSync(tmpSrcDir, { recursive: true })
|
|
28
|
+
})
|
|
29
|
+
})
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync } from "node:fs"
|
|
2
|
+
import { getTuiConfigPath } from "./paths.js"
|
|
3
|
+
|
|
4
|
+
export function writeTuiConfig(themeName, customPath) {
|
|
5
|
+
const path = customPath || getTuiConfigPath()
|
|
6
|
+
let config = {
|
|
7
|
+
"$schema": "https://opencode.ai/tui.json"
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (existsSync(path)) {
|
|
11
|
+
try {
|
|
12
|
+
const content = readFileSync(path, "utf-8")
|
|
13
|
+
config = JSON.parse(content)
|
|
14
|
+
} catch {
|
|
15
|
+
// If parsing fails, fall back to default structure
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
config.theme = themeName
|
|
20
|
+
|
|
21
|
+
writeFileSync(path, JSON.stringify(config, null, 2), "utf-8")
|
|
22
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, it, after } from "node:test"
|
|
2
|
+
import assert from "node:assert"
|
|
3
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"
|
|
4
|
+
import { join } from "node:path"
|
|
5
|
+
import { tmpdir } from "node:os"
|
|
6
|
+
import { writeTuiConfig } from "./tuiConfigWriter.js"
|
|
7
|
+
|
|
8
|
+
describe("tuiConfigWriter", () => {
|
|
9
|
+
const destDir = mkdtempSync(join(tmpdir(), "pluidr-tui-dest-"))
|
|
10
|
+
const tuiPath = join(destDir, "tui.json")
|
|
11
|
+
|
|
12
|
+
after(() => {
|
|
13
|
+
if (existsSync(destDir)) rmSync(destDir, { recursive: true })
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it("writes new tui.json with theme name if it does not exist", () => {
|
|
17
|
+
writeTuiConfig("pluidr-contrast", tuiPath)
|
|
18
|
+
|
|
19
|
+
assert.ok(existsSync(tuiPath))
|
|
20
|
+
const parsed = JSON.parse(readFileSync(tuiPath, "utf-8"))
|
|
21
|
+
assert.strictEqual(parsed.theme, "pluidr-contrast")
|
|
22
|
+
assert.strictEqual(parsed["$schema"], "https://opencode.ai/tui.json")
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it("updates existing tui.json theme name while preserving other fields", () => {
|
|
26
|
+
writeFileSync(tuiPath, JSON.stringify({
|
|
27
|
+
"$schema": "https://opencode.ai/tui.json",
|
|
28
|
+
"theme": "ayu-dark",
|
|
29
|
+
"font": "monolisa"
|
|
30
|
+
}, null, 2), "utf-8")
|
|
31
|
+
|
|
32
|
+
writeTuiConfig("pluidr-contrast", tuiPath)
|
|
33
|
+
|
|
34
|
+
const parsed = JSON.parse(readFileSync(tuiPath, "utf-8"))
|
|
35
|
+
assert.strictEqual(parsed.theme, "pluidr-contrast")
|
|
36
|
+
assert.strictEqual(parsed.font, "monolisa")
|
|
37
|
+
})
|
|
38
|
+
})
|
|
@@ -394,8 +394,7 @@ elsewhere. Do not accept a report that is not in `docs/reports/`.
|
|
|
394
394
|
- **Regression Awareness** — When re-triggering coder after a FAIL, pass the
|
|
395
395
|
gap list in full so coder doesn't fix one thing and break something
|
|
396
396
|
already confirmed as PASS.
|
|
397
|
-
- **Clarification** — If anything is ambiguous at any phase,
|
|
398
|
-
using multiple-choice options (2-4 short choices per question).
|
|
397
|
+
- **Clarification** — If anything is ambiguous at any phase, avoid excessive questioning. For minor details, make a reasonable, safe engineering assumption based on existing codebase conventions and document it. Only prompt the user using multiple-choice options (2-3 short choices per question, and list your recommended option first prefixed with '(Recommended)') if there is a critical blocking issue that directly impacts the design direction or has high regression risk.
|
|
399
398
|
- **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.
|
|
400
399
|
|
|
401
400
|
---
|
|
@@ -132,6 +132,7 @@ just a technical restriction.
|
|
|
132
132
|
- **Separation of Investigation/Fix/Report** — Do not mix investigation,
|
|
133
133
|
implementation, and reporting in the same delegate call. Each subagent
|
|
134
134
|
has one job; the Debugger orchestrates the sequence.
|
|
135
|
+
- **Avoid Excessive Questioning** — Avoid excessive querying of the user for minor ambiguities. For small implementation details, make a reasonable, safe engineering assumption based on existing codebase conventions and proceed. Only prompt the user using multiple-choice options (2-3 short choices per question, and list your recommended option first prefixed with '(Recommended)') when a critical blocking issue directly prevents a safe diagnosis or fix.
|
|
135
136
|
|
|
136
137
|
## What you do NOT do
|
|
137
138
|
|
|
@@ -85,6 +85,10 @@ Do NOT remain in a completed state without offering next steps.
|
|
|
85
85
|
- You do not patch without a GUARDRAIL GATE user confirmation after TRACE.
|
|
86
86
|
- You do not loop more than 5 times on AUDIT before surfacing to the user.
|
|
87
87
|
|
|
88
|
+
## Principles you apply
|
|
89
|
+
|
|
90
|
+
- **Avoid Excessive Questioning** — Avoid excessive querying of the user for minor ambiguities. For small implementation details, make a reasonable, safe engineering assumption based on existing codebase conventions and proceed. Only prompt the user using the `question` tool with multiple-choice options (2-3 short choices per question, and list your recommended option first prefixed with '(Recommended)') for critical blocking decisions (such as the Guardrail Gate or a complete audit block).
|
|
91
|
+
|
|
88
92
|
## Conflict Resolution
|
|
89
93
|
Refer to `hierarchy.txt` (loaded globally) — you do not resolve principle
|
|
90
94
|
conflicts by your own judgment outside that hierarchy.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://opencode.ai/config.json",
|
|
3
|
+
"theme": "pluidr-contrast",
|
|
3
4
|
"instructions": ["{file:./prompts/hierarchy.txt}"],
|
|
4
5
|
"permission": {
|
|
5
6
|
"read": {
|
|
@@ -24,6 +25,7 @@
|
|
|
24
25
|
|
|
25
26
|
"composer": {
|
|
26
27
|
"mode": "primary",
|
|
28
|
+
"color": "#00b4cc",
|
|
27
29
|
"temperature": 0.7,
|
|
28
30
|
"description": "Full engineering workflow: Explore, Plan, Build — 3 phases, 7 subagents. Starts in Explore mode, transitions via guardrail gates.",
|
|
29
31
|
"prompt": "{file:./prompts/composer.txt}",
|
|
@@ -54,6 +56,7 @@
|
|
|
54
56
|
|
|
55
57
|
"debugger": {
|
|
56
58
|
"mode": "primary",
|
|
59
|
+
"color": "#ff7039",
|
|
57
60
|
"description": "Root-cause analysis via inspector/fixer/reporter. Standalone primary agent — does not depend on Composer. Pure orchestrator — all investigation, fixes, and reports delegated to subagents.",
|
|
58
61
|
"prompt": "{file:./prompts/debugger.txt}",
|
|
59
62
|
"permission": {
|
|
@@ -344,6 +347,7 @@
|
|
|
344
347
|
|
|
345
348
|
"prober": {
|
|
346
349
|
"mode": "primary",
|
|
350
|
+
"color": "#66ffbf",
|
|
347
351
|
"temperature": 0.7,
|
|
348
352
|
"description": "Standalone security and quality auditing agent. Recon and vuln trace, minimal patch application, validation gate.",
|
|
349
353
|
"prompt": "{file:./prompts/prober.txt}",
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://opencode.ai/theme.json",
|
|
3
|
+
"theme": {
|
|
4
|
+
"primary": {
|
|
5
|
+
"dark": "#ff7039",
|
|
6
|
+
"light": "#ff7039"
|
|
7
|
+
},
|
|
8
|
+
"secondary": {
|
|
9
|
+
"dark": "#99f3ff",
|
|
10
|
+
"light": "#99f3ff"
|
|
11
|
+
},
|
|
12
|
+
"accent": {
|
|
13
|
+
"dark": "#ff7039",
|
|
14
|
+
"light": "#ff7039"
|
|
15
|
+
},
|
|
16
|
+
"error": {
|
|
17
|
+
"dark": "#fc5d7c",
|
|
18
|
+
"light": "#fc5d7c"
|
|
19
|
+
},
|
|
20
|
+
"warning": {
|
|
21
|
+
"dark": "#ff669c",
|
|
22
|
+
"light": "#ff669c"
|
|
23
|
+
},
|
|
24
|
+
"success": {
|
|
25
|
+
"dark": "#00cc76",
|
|
26
|
+
"light": "#00cc76"
|
|
27
|
+
},
|
|
28
|
+
"info": {
|
|
29
|
+
"dark": "#00b4cc",
|
|
30
|
+
"light": "#00b4cc"
|
|
31
|
+
},
|
|
32
|
+
"text": {
|
|
33
|
+
"dark": "#ffffff",
|
|
34
|
+
"light": "#ffffff"
|
|
35
|
+
},
|
|
36
|
+
"textMuted": {
|
|
37
|
+
"dark": "#888888",
|
|
38
|
+
"light": "#888888"
|
|
39
|
+
},
|
|
40
|
+
"background": {
|
|
41
|
+
"dark": "#000000",
|
|
42
|
+
"light": "#000000"
|
|
43
|
+
},
|
|
44
|
+
"backgroundPanel": {
|
|
45
|
+
"dark": "#000000",
|
|
46
|
+
"light": "#000000"
|
|
47
|
+
},
|
|
48
|
+
"backgroundElement": {
|
|
49
|
+
"dark": "#000000",
|
|
50
|
+
"light": "#000000"
|
|
51
|
+
},
|
|
52
|
+
"border": {
|
|
53
|
+
"dark": "#101520",
|
|
54
|
+
"light": "#101520"
|
|
55
|
+
},
|
|
56
|
+
"borderActive": {
|
|
57
|
+
"dark": "#ff7039",
|
|
58
|
+
"light": "#ff7039"
|
|
59
|
+
},
|
|
60
|
+
"borderSubtle": {
|
|
61
|
+
"dark": "#101520",
|
|
62
|
+
"light": "#101520"
|
|
63
|
+
},
|
|
64
|
+
"diffAdded": {
|
|
65
|
+
"dark": "#00cc76",
|
|
66
|
+
"light": "#00cc76"
|
|
67
|
+
},
|
|
68
|
+
"diffRemoved": {
|
|
69
|
+
"dark": "#fc5d7c",
|
|
70
|
+
"light": "#fc5d7c"
|
|
71
|
+
},
|
|
72
|
+
"diffContext": {
|
|
73
|
+
"dark": "#888888",
|
|
74
|
+
"light": "#888888"
|
|
75
|
+
},
|
|
76
|
+
"diffAddedBg": {
|
|
77
|
+
"dark": "#1a2618",
|
|
78
|
+
"light": "#1a2618"
|
|
79
|
+
},
|
|
80
|
+
"diffRemovedBg": {
|
|
81
|
+
"dark": "#2a1a1a",
|
|
82
|
+
"light": "#2a1a1a"
|
|
83
|
+
},
|
|
84
|
+
"diffContextBg": {
|
|
85
|
+
"dark": "#000000",
|
|
86
|
+
"light": "#000000"
|
|
87
|
+
},
|
|
88
|
+
"markdownText": {
|
|
89
|
+
"dark": "#ffffff",
|
|
90
|
+
"light": "#ffffff"
|
|
91
|
+
},
|
|
92
|
+
"markdownHeading": {
|
|
93
|
+
"dark": "#ff669c",
|
|
94
|
+
"light": "#ff669c"
|
|
95
|
+
},
|
|
96
|
+
"markdownLink": {
|
|
97
|
+
"dark": "#99f3ff",
|
|
98
|
+
"light": "#99f3ff"
|
|
99
|
+
},
|
|
100
|
+
"markdownLinkText": {
|
|
101
|
+
"dark": "#ff7039",
|
|
102
|
+
"light": "#ff7039"
|
|
103
|
+
},
|
|
104
|
+
"markdownCode": {
|
|
105
|
+
"dark": "#66ffbf",
|
|
106
|
+
"light": "#66ffbf"
|
|
107
|
+
},
|
|
108
|
+
"markdownBlockQuote": {
|
|
109
|
+
"dark": "#888888",
|
|
110
|
+
"light": "#888888"
|
|
111
|
+
},
|
|
112
|
+
"markdownStrong": {
|
|
113
|
+
"dark": "#ff7039",
|
|
114
|
+
"light": "#ff7039"
|
|
115
|
+
},
|
|
116
|
+
"syntaxComment": {
|
|
117
|
+
"dark": "#888888",
|
|
118
|
+
"light": "#888888"
|
|
119
|
+
},
|
|
120
|
+
"syntaxKeyword": {
|
|
121
|
+
"dark": "#ff669c",
|
|
122
|
+
"light": "#ff669c"
|
|
123
|
+
},
|
|
124
|
+
"syntaxFunction": {
|
|
125
|
+
"dark": "#00b4cc",
|
|
126
|
+
"light": "#00b4cc"
|
|
127
|
+
},
|
|
128
|
+
"syntaxVariable": {
|
|
129
|
+
"dark": "#ffb966",
|
|
130
|
+
"light": "#ffb966"
|
|
131
|
+
},
|
|
132
|
+
"syntaxString": {
|
|
133
|
+
"dark": "#66ffbf",
|
|
134
|
+
"light": "#66ffbf"
|
|
135
|
+
},
|
|
136
|
+
"syntaxNumber": {
|
|
137
|
+
"dark": "#a000ff",
|
|
138
|
+
"light": "#a000ff"
|
|
139
|
+
},
|
|
140
|
+
"syntaxType": {
|
|
141
|
+
"dark": "#99f3ff",
|
|
142
|
+
"light": "#99f3ff"
|
|
143
|
+
},
|
|
144
|
+
"syntaxOperator": {
|
|
145
|
+
"dark": "#ff669c",
|
|
146
|
+
"light": "#ff669c"
|
|
147
|
+
},
|
|
148
|
+
"syntaxPunctuation": {
|
|
149
|
+
"dark": "#ffffff",
|
|
150
|
+
"light": "#ffffff"
|
|
151
|
+
},
|
|
152
|
+
"inputBackground": {
|
|
153
|
+
"dark": "#000000",
|
|
154
|
+
"light": "#000000"
|
|
155
|
+
},
|
|
156
|
+
"InputBorder": {
|
|
157
|
+
"dark": "#101520",
|
|
158
|
+
"light": "#101520"
|
|
159
|
+
},
|
|
160
|
+
"InputBorderActive": {
|
|
161
|
+
"dark": "#ff7039",
|
|
162
|
+
"light": "#ff7039"
|
|
163
|
+
},
|
|
164
|
+
"inputPrompt": {
|
|
165
|
+
"dark": "#ff7039",
|
|
166
|
+
"light": "#ff7039"
|
|
167
|
+
},
|
|
168
|
+
"inputCursor": {
|
|
169
|
+
"dark": "#ff7039",
|
|
170
|
+
"light": "#ff7039"
|
|
171
|
+
},
|
|
172
|
+
"inputText": {
|
|
173
|
+
"dark": "#ffffff",
|
|
174
|
+
"light": "#ffffff"
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|