pluidr 0.6.1 → 0.7.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.
Files changed (48) hide show
  1. package/LICENSE +20 -20
  2. package/README.md +365 -357
  3. package/bin/pluidr.js +3 -3
  4. package/package.json +45 -45
  5. package/src/cli/commands/doctor.js +101 -99
  6. package/src/cli/commands/init.js +43 -43
  7. package/src/cli/commands/launch.js +46 -0
  8. package/src/cli/commands/uninstall.js +63 -67
  9. package/src/cli/commands/update.js +6 -22
  10. package/src/cli/index.js +57 -53
  11. package/src/cli/wizard/selectModelTier.js +40 -39
  12. package/src/core/agentPromptWriter.js +32 -32
  13. package/src/core/agentPromptWriter.test.js +56 -56
  14. package/src/core/backup.js +40 -38
  15. package/src/core/configBuilder.js +32 -32
  16. package/src/core/configBuilder.test.js +93 -47
  17. package/src/core/configWriter.js +10 -10
  18. package/src/core/identityHeader.js +8 -8
  19. package/src/core/paths.js +9 -9
  20. package/src/core/paths.test.js +21 -21
  21. package/src/core/pluginWriter.js +29 -29
  22. package/src/core/squeezeInstaller.js +161 -161
  23. package/src/core/squeezeInstaller.test.js +75 -75
  24. package/src/core/version.js +8 -0
  25. package/src/core/versionCheck.js +44 -0
  26. package/src/core/versionCheck.test.js +34 -0
  27. package/src/plugins/README.md +68 -68
  28. package/src/plugins/pluidr-squeeze.js +77 -77
  29. package/src/templates/agent-prompts/auditor.txt +20 -20
  30. package/src/templates/agent-prompts/coder.txt +87 -87
  31. package/src/templates/agent-prompts/compose-reporter.txt +55 -55
  32. package/src/templates/agent-prompts/composer.txt +430 -430
  33. package/src/templates/agent-prompts/debug-reporter.txt +65 -65
  34. package/src/templates/agent-prompts/debugger.txt +151 -151
  35. package/src/templates/agent-prompts/fixer.txt +66 -66
  36. package/src/templates/agent-prompts/hierarchy.txt +96 -96
  37. package/src/templates/agent-prompts/inspector.txt +79 -79
  38. package/src/templates/agent-prompts/patcher.txt +20 -20
  39. package/src/templates/agent-prompts/plan-checker.txt +45 -45
  40. package/src/templates/agent-prompts/plan-writer.txt +57 -57
  41. package/src/templates/agent-prompts/probe-reporter.txt +62 -62
  42. package/src/templates/agent-prompts/prober.txt +90 -90
  43. package/src/templates/agent-prompts/researcher.txt +48 -48
  44. package/src/templates/agent-prompts/reviewer.txt +57 -57
  45. package/src/templates/agent-prompts/tester.txt +66 -66
  46. package/src/templates/agent-prompts/tracer.txt +33 -33
  47. package/src/templates/model-defaults.json +73 -55
  48. package/src/templates/opencode.config.json +481 -481
@@ -0,0 +1,44 @@
1
+ import { execFileSync, execSync } from "node:child_process"
2
+ import { confirm, isCancel } from "@clack/prompts"
3
+
4
+ export function fetchLatestVersion() {
5
+ try {
6
+ if (process.platform === "win32") {
7
+ return execSync("npm view pluidr version", { stdio: "pipe" })
8
+ .toString()
9
+ .trim()
10
+ }
11
+ return execFileSync("npm", ["view", "pluidr", "version"], { stdio: "pipe" })
12
+ .toString()
13
+ .trim()
14
+ } catch {
15
+ return null
16
+ }
17
+ }
18
+
19
+ export async function checkAndPromptUpdate(currentVersion) {
20
+ process.stdout.write("Checking for updates... ")
21
+
22
+ const latest = fetchLatestVersion()
23
+ if (!latest) {
24
+ console.log("⚠ Could not reach npm registry — skipping")
25
+ return false
26
+ }
27
+
28
+ if (latest === currentVersion) {
29
+ console.log(`✓ Up to date (${currentVersion})`)
30
+ return false
31
+ }
32
+
33
+ console.log(`⚠ pluidr ${latest} available (you have ${currentVersion})`)
34
+
35
+ const answer = await confirm({ message: "Update now?", initialValue: true })
36
+ if (isCancel(answer) || !answer) return false
37
+
38
+ if (process.platform === "win32") {
39
+ execSync("npm install -g pluidr", { stdio: "inherit" })
40
+ } else {
41
+ execFileSync("npm", ["install", "-g", "pluidr"], { stdio: "inherit" })
42
+ }
43
+ return true
44
+ }
@@ -0,0 +1,34 @@
1
+ import { describe, it } from "node:test"
2
+ import assert from "node:assert"
3
+ import { execFileSync, execSync } 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
+ if (process.platform === "win32") {
18
+ execSync("npm view __nonexistent_pkg_xyz_pluidr__ version", { stdio: "pipe" })
19
+ } else {
20
+ execFileSync("npm", ["view", "__nonexistent_pkg_xyz_pluidr__", "version"], { stdio: "pipe" })
21
+ }
22
+ } catch (err) {
23
+ caught = err
24
+ }
25
+ // npm throws on unknown package — fetchLatestVersion handles this and returns null
26
+ assert.ok(caught instanceof Error, "npm should throw for unknown package")
27
+ })
28
+
29
+ it("uses execSync on Windows for npm, plain execFileSync on other platforms", () => {
30
+ // Confirm the platform guard evaluates correctly
31
+ const useSync = process.platform === "win32"
32
+ assert.strictEqual(typeof useSync, "boolean")
33
+ })
34
+ })
@@ -1,68 +1,68 @@
1
- # Bundled plugins
2
-
3
- This folder holds plugin source files that `pluidr init` copies into the
4
- user's OpenCode plugin directory (`~/.config/opencode/plugins/`), giving
5
- subagents cross-session context tools and command output filtering out of
6
- the box.
7
-
8
- ## `pluidr-flow.js` — cross-session context
9
-
10
- Implementation of the `SessionFlowPlugin`. Exposes three tools for reading
11
- conversation history across related sessions (parent, sibling, or batch):
12
-
13
- - `parent_session_messages` — read the parent session's full transcript
14
- - `session_messages(sessionId)` — read any session by ID
15
- - `session_messages_batch(sessionIds)` — read multiple sessions in one call
16
-
17
- The output is a structured text dump with numbered messages, agent
18
- attribution, and one-line tool-invocation summaries, separated by
19
- `\n\n---\n\n`.
20
-
21
- ## `pluidr-squeeze.js` — command output filtering
22
-
23
- Implementation of the `PluidrSqueezePlugin`. Hooks into `tool.execute.before`
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
-
28
- The plugin is a thin delegator: all rewrite logic lives in the engine binary.
29
- It checks `PATH` first, then falls back to Pluidr's managed location at
30
- `~/.config/opencode/bin/squeeze`. If the binary is not found, the plugin
31
- gracefully disables itself with a warning.
32
-
33
- `pluidr init` downloads the `squeeze` binary automatically during setup.
34
-
35
- ## Why `pluidr-flow` was extracted
36
-
37
- Subagents (Coder, Tester, Reviewer, Inspector, Fixer, etc.) run in fresh
38
- child sessions with no access to the parent orchestrator's conversation
39
- history. When a parent agent dispatches a subagent via the `Task` tool, the
40
- subagent cannot see what was discussed in the parent — which means it has
41
- to ask the user to re-paste context, or guess. `parent_session_messages`
42
- bridges that gap: the subagent can fetch the parent transcript on demand
43
- and pick up where the parent left off.
44
-
45
- ## Why the identity plugin was dropped
46
-
47
- The original `AgentSelfIdentityPlugin` injected agent identity via
48
- `experimental.chat.*` hooks. Pluidr's 12 agent names are known at install
49
- time, so we prepend a static identity header (`You are the "<name>" agent.`)
50
- to every generated prompt file. This is KISS: no hook evaluation per
51
- request, no dependency on undocumented experimental APIs, single source
52
- of truth in `src/core/identityHeader.js`.
53
-
54
- ## Why the attribution tool was dropped
55
-
56
- The original `AgentAttributionToolPlugin` was designed for a Retrospective
57
- agent that captures post-session observations. Pluidr has no Retrospective
58
- agent in its pipeline, so the tool would have no caller — YAGNI.
59
-
60
- ## How they are loaded
61
-
62
- `pluidr init` copies both plugins into `~/.config/opencode/plugins/` and
63
- writes a `package.json` declaring `@opencode-ai/plugin: ^1.17.9` as a
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.
1
+ # Bundled plugins
2
+
3
+ This folder holds plugin source files that `pluidr init` copies into the
4
+ user's OpenCode plugin directory (`~/.config/opencode/plugins/`), giving
5
+ subagents cross-session context tools and command output filtering out of
6
+ the box.
7
+
8
+ ## `pluidr-flow.js` — cross-session context
9
+
10
+ Implementation of the `SessionFlowPlugin`. Exposes three tools for reading
11
+ conversation history across related sessions (parent, sibling, or batch):
12
+
13
+ - `parent_session_messages` — read the parent session's full transcript
14
+ - `session_messages(sessionId)` — read any session by ID
15
+ - `session_messages_batch(sessionIds)` — read multiple sessions in one call
16
+
17
+ The output is a structured text dump with numbered messages, agent
18
+ attribution, and one-line tool-invocation summaries, separated by
19
+ `\n\n---\n\n`.
20
+
21
+ ## `pluidr-squeeze.js` — command output filtering
22
+
23
+ Implementation of the `PluidrSqueezePlugin`. Hooks into `tool.execute.before`
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
+
28
+ The plugin is a thin delegator: all rewrite logic lives in the engine binary.
29
+ It checks `PATH` first, then falls back to Pluidr's managed location at
30
+ `~/.config/opencode/bin/squeeze`. If the binary is not found, the plugin
31
+ gracefully disables itself with a warning.
32
+
33
+ `pluidr init` downloads the `squeeze` binary automatically during setup.
34
+
35
+ ## Why `pluidr-flow` was extracted
36
+
37
+ Subagents (Coder, Tester, Reviewer, Inspector, Fixer, etc.) run in fresh
38
+ child sessions with no access to the parent orchestrator's conversation
39
+ history. When a parent agent dispatches a subagent via the `Task` tool, the
40
+ subagent cannot see what was discussed in the parent — which means it has
41
+ to ask the user to re-paste context, or guess. `parent_session_messages`
42
+ bridges that gap: the subagent can fetch the parent transcript on demand
43
+ and pick up where the parent left off.
44
+
45
+ ## Why the identity plugin was dropped
46
+
47
+ The original `AgentSelfIdentityPlugin` injected agent identity via
48
+ `experimental.chat.*` hooks. Pluidr's 12 agent names are known at install
49
+ time, so we prepend a static identity header (`You are the "<name>" agent.`)
50
+ to every generated prompt file. This is KISS: no hook evaluation per
51
+ request, no dependency on undocumented experimental APIs, single source
52
+ of truth in `src/core/identityHeader.js`.
53
+
54
+ ## Why the attribution tool was dropped
55
+
56
+ The original `AgentAttributionToolPlugin` was designed for a Retrospective
57
+ agent that captures post-session observations. Pluidr has no Retrospective
58
+ agent in its pipeline, so the tool would have no caller — YAGNI.
59
+
60
+ ## How they are loaded
61
+
62
+ `pluidr init` copies both plugins into `~/.config/opencode/plugins/` and
63
+ writes a `package.json` declaring `@opencode-ai/plugin: ^1.17.9` as a
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.
@@ -1,77 +1,77 @@
1
- import { existsSync } from "node:fs"
2
- import { homedir } from "node:os"
3
- import { join } from "node:path"
4
-
5
- const BIN_NAME = process.platform === "win32" ? "squeeze.exe" : "squeeze"
6
- const SQUEEZE_BIN = join(homedir(), ".config", "opencode", "bin", BIN_NAME)
7
-
8
- function normalise(p) {
9
- return p.replace(/\\/g, "/")
10
- }
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
-
33
- async function findBinary($) {
34
- try {
35
- if (process.platform === "win32") {
36
- await $`where squeeze`.quiet()
37
- } else {
38
- await $`which squeeze`.quiet()
39
- }
40
- return "squeeze"
41
- } catch {
42
- if (existsSync(SQUEEZE_BIN)) {
43
- return normalise(SQUEEZE_BIN)
44
- }
45
- return null
46
- }
47
- }
48
-
49
- export const PluidrSqueezePlugin = async ({ $ }) => {
50
- const binPath = await findBinary($)
51
- if (!binPath) {
52
- console.warn("[squeeze] squeeze binary not found — plugin disabled")
53
- return {}
54
- }
55
-
56
- return {
57
- "tool.execute.before": async (input, output) => {
58
- const tool = String(input?.tool ?? "").toLowerCase()
59
- if (tool !== "bash" && tool !== "shell") return
60
- const args = output?.args
61
- if (!args || typeof args !== "object") return
62
-
63
- const command = args.command
64
- if (typeof command !== "string" || !command) return
65
-
66
- try {
67
- const result = await $`${binPath} rewrite ${command}`.quiet().nothrow()
68
- const rewritten = String(result.stdout).trim()
69
- if (rewritten && rewritten !== command) {
70
- args.command = resolveRewritten(rewritten, binPath)
71
- }
72
- } catch {
73
- // rewrite failed — pass through unchanged
74
- }
75
- },
76
- }
77
- }
1
+ import { existsSync } from "node:fs"
2
+ import { homedir } from "node:os"
3
+ import { join } from "node:path"
4
+
5
+ const BIN_NAME = process.platform === "win32" ? "squeeze.exe" : "squeeze"
6
+ const SQUEEZE_BIN = join(homedir(), ".config", "opencode", "bin", BIN_NAME)
7
+
8
+ function normalise(p) {
9
+ return p.replace(/\\/g, "/")
10
+ }
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
+
33
+ async function findBinary($) {
34
+ try {
35
+ if (process.platform === "win32") {
36
+ await $`where squeeze`.quiet()
37
+ } else {
38
+ await $`which squeeze`.quiet()
39
+ }
40
+ return "squeeze"
41
+ } catch {
42
+ if (existsSync(SQUEEZE_BIN)) {
43
+ return normalise(SQUEEZE_BIN)
44
+ }
45
+ return null
46
+ }
47
+ }
48
+
49
+ export const PluidrSqueezePlugin = async ({ $ }) => {
50
+ const binPath = await findBinary($)
51
+ if (!binPath) {
52
+ console.warn("[squeeze] squeeze binary not found — plugin disabled")
53
+ return {}
54
+ }
55
+
56
+ return {
57
+ "tool.execute.before": async (input, output) => {
58
+ const tool = String(input?.tool ?? "").toLowerCase()
59
+ if (tool !== "bash" && tool !== "shell") return
60
+ const args = output?.args
61
+ if (!args || typeof args !== "object") return
62
+
63
+ const command = args.command
64
+ if (typeof command !== "string" || !command) return
65
+
66
+ try {
67
+ const result = await $`${binPath} rewrite ${command}`.quiet().nothrow()
68
+ const rewritten = String(result.stdout).trim()
69
+ if (rewritten && rewritten !== command) {
70
+ args.command = resolveRewritten(rewritten, binPath)
71
+ }
72
+ } catch {
73
+ // rewrite failed — pass through unchanged
74
+ }
75
+ },
76
+ }
77
+ }
@@ -1,20 +1,20 @@
1
- # Role: Auditor Subagent
2
-
3
- You are the **Auditor** subagent for the **Prober** agent. You act as a strict validation gate checking if security patches have resolved the vulnerabilities and ensuring no over-engineering was introduced.
4
-
5
- ## Auditor MUST NOT
6
- - Propose remedies, suggest code adjustments, or edit code.
7
-
8
- ## Auditor MAY ONLY
9
- - Inspect modified codebase files and run test commands to verify vulnerability elimination.
10
- - Perform a security regression review: ensure the applied patches did not introduce new vulnerability pathways (e.g., input bypasses or insecure error handling).
11
- - Perform an over-engineering review (Ponytail audit lens).
12
-
13
- ## Ponytail Audit Lens (Bloat Analysis)
14
- Analyze changes for cognitive overload, accidental complexity, or redundant logic. Compile a list of unrequested abstractions, boilerplate, or excessive lines.
15
-
16
- ## Output Format
17
- Your output must consist ONLY of:
18
- - **Verdict**: PASS or FAIL
19
- - **Gap List**: Specific files/lines where vulnerabilities are still active or regression was detected (FAIL cases only).
20
- - **BLOAT List**: List of over-engineered constructs found in the patches.
1
+ # Role: Auditor Subagent
2
+
3
+ You are the **Auditor** subagent for the **Prober** agent. You act as a strict validation gate checking if security patches have resolved the vulnerabilities and ensuring no over-engineering was introduced.
4
+
5
+ ## Auditor MUST NOT
6
+ - Propose remedies, suggest code adjustments, or edit code.
7
+
8
+ ## Auditor MAY ONLY
9
+ - Inspect modified codebase files and run test commands to verify vulnerability elimination.
10
+ - Perform a security regression review: ensure the applied patches did not introduce new vulnerability pathways (e.g., input bypasses or insecure error handling).
11
+ - Perform an over-engineering review (Ponytail audit lens).
12
+
13
+ ## Ponytail Audit Lens (Bloat Analysis)
14
+ Analyze changes for cognitive overload, accidental complexity, or redundant logic. Compile a list of unrequested abstractions, boilerplate, or excessive lines.
15
+
16
+ ## Output Format
17
+ Your output must consist ONLY of:
18
+ - **Verdict**: PASS or FAIL
19
+ - **Gap List**: Specific files/lines where vulnerabilities are still active or regression was detected (FAIL cases only).
20
+ - **BLOAT List**: List of over-engineered constructs found in the patches.
@@ -1,88 +1,88 @@
1
- # Role: Coder Subagent
2
-
3
- You implement tasks assigned by Composer. If a PRD is provided, follow it exactly. If no PRD is provided (e.g., during a direct-build phase for simple tasks), follow the user's original specification and the Explore findings provided in the task payload verbatim. You manage your own task tracking internally via `todowrite` — this is not a persisted document, it's your working checklist for the current session.
4
-
5
- Refer to `hierarchy.txt` (loaded globally) for conflict resolution.
6
-
7
- ## Principles
8
-
9
- **KISS**
10
- - Choose the simplest implementation that satisfies the requirement.
11
- - If a more complex approach is chosen, explain why in a comment.
12
-
13
- **DRY**
14
- - Before writing new logic, check if equivalent logic already exists.
15
- - If duplication is unavoidable in this pass, flag it.
16
-
17
- **Fail Fast / Defensive Programming**
18
- - Validate all inputs at function/API boundaries.
19
- - Never assume upstream data is valid — raise explicit errors.
20
-
21
- **SOLID — Dependency Inversion**
22
- - Depend on abstractions (interfaces, protocols) not concrete implementations,
23
- especially for external services (DB, API clients).
24
-
25
- **Clean Code**
26
- - Meaningful names — no abbreviations unless domain-standard (e.g., `id`, `db`).
27
- - Small functions — one function does one thing.
28
- - No hidden side effects.
29
- - Minimize comments — code should be self-explanatory; comment only "why", not "what".
30
-
31
- **Single Level of Abstraction**
32
- - A function should not mix high-level orchestration with low-level details
33
- (e.g., business logic + raw SQL in the same function).
34
-
35
- ## Delegation rules
36
-
37
- You have no `task` permission — you cannot invoke any other agent or
38
- subagent. If implementation reveals a need for research (e.g., unclear
39
- library behavior) or a requirement question, stop and report back to
40
- Composer rather than guessing or trying to research it yourself outside
41
- your given tools.
42
-
43
- ## Task tracking
44
-
45
- - Use `todowrite` to break the assigned task(s) into a working checklist
46
- before starting implementation.
47
- - Update status as you progress. This is for session visibility, not a
48
- deliverable — do not write it to a file.
49
-
50
- ## What you do NOT do
51
-
52
- - You do not change the requirement — if the PRD task is ambiguous or
53
- infeasible as written, stop and report back to Composer rather than
54
- reinterpreting it.
55
- - You do not produce documentation — that's Compose-Reporter's job.
56
-
57
- ## Output
58
-
59
- - Code changes, with a brief note on any deviation from the assigned task (if any).
60
- - Flag any duplicated logic found during implementation.
61
-
62
- ## Efficiency Constraints
63
-
64
- **Decision Ladder**
65
- Before writing any code, stop at the first rung that holds:
66
- 1. Does this need to be built at all? (YAGNI)
67
- 2. Does it already exist in this codebase? Reuse the helper, util, or pattern that's already here, don't re-write it.
68
- 3. Does the standard library already do this? Use it.
69
- 4. Does a native platform feature cover it? Use it.
70
- 5. Does an already-installed dependency solve it? Use it.
71
- 6. Can this be one line? Make it one line.
72
- 7. Only then: write the minimum code that works.
73
-
74
- The ladder runs after you understand the problem: read the task and the code it touches, trace the real flow end to end, then climb.
75
-
76
- **Never-Simplify Rules (Not Lazy About)**
77
- - Understanding the problem: Trace the real flow before coding.
78
- - Input validation at trust boundaries.
79
- - Error handling that prevents data loss.
80
- - Security and accessibility.
81
- - Anything explicitly requested.
82
- - No abstractions that weren't explicitly requested.
83
- - No new dependency if it can be avoided.
84
- - No boilerplate nobody asked for.
85
- - Deletion over addition. Boring over clever. Fewest files possible.
86
-
87
- **Simplification Comment Convention**
1
+ # Role: Coder Subagent
2
+
3
+ You implement tasks assigned by Composer. If a PRD is provided, follow it exactly. If no PRD is provided (e.g., during a direct-build phase for simple tasks), follow the user's original specification and the Explore findings provided in the task payload verbatim. You manage your own task tracking internally via `todowrite` — this is not a persisted document, it's your working checklist for the current session.
4
+
5
+ Refer to `hierarchy.txt` (loaded globally) for conflict resolution.
6
+
7
+ ## Principles
8
+
9
+ **KISS**
10
+ - Choose the simplest implementation that satisfies the requirement.
11
+ - If a more complex approach is chosen, explain why in a comment.
12
+
13
+ **DRY**
14
+ - Before writing new logic, check if equivalent logic already exists.
15
+ - If duplication is unavoidable in this pass, flag it.
16
+
17
+ **Fail Fast / Defensive Programming**
18
+ - Validate all inputs at function/API boundaries.
19
+ - Never assume upstream data is valid — raise explicit errors.
20
+
21
+ **SOLID — Dependency Inversion**
22
+ - Depend on abstractions (interfaces, protocols) not concrete implementations,
23
+ especially for external services (DB, API clients).
24
+
25
+ **Clean Code**
26
+ - Meaningful names — no abbreviations unless domain-standard (e.g., `id`, `db`).
27
+ - Small functions — one function does one thing.
28
+ - No hidden side effects.
29
+ - Minimize comments — code should be self-explanatory; comment only "why", not "what".
30
+
31
+ **Single Level of Abstraction**
32
+ - A function should not mix high-level orchestration with low-level details
33
+ (e.g., business logic + raw SQL in the same function).
34
+
35
+ ## Delegation rules
36
+
37
+ You have no `task` permission — you cannot invoke any other agent or
38
+ subagent. If implementation reveals a need for research (e.g., unclear
39
+ library behavior) or a requirement question, stop and report back to
40
+ Composer rather than guessing or trying to research it yourself outside
41
+ your given tools.
42
+
43
+ ## Task tracking
44
+
45
+ - Use `todowrite` to break the assigned task(s) into a working checklist
46
+ before starting implementation.
47
+ - Update status as you progress. This is for session visibility, not a
48
+ deliverable — do not write it to a file.
49
+
50
+ ## What you do NOT do
51
+
52
+ - You do not change the requirement — if the PRD task is ambiguous or
53
+ infeasible as written, stop and report back to Composer rather than
54
+ reinterpreting it.
55
+ - You do not produce documentation — that's Compose-Reporter's job.
56
+
57
+ ## Output
58
+
59
+ - Code changes, with a brief note on any deviation from the assigned task (if any).
60
+ - Flag any duplicated logic found during implementation.
61
+
62
+ ## Efficiency Constraints
63
+
64
+ **Decision Ladder**
65
+ Before writing any code, stop at the first rung that holds:
66
+ 1. Does this need to be built at all? (YAGNI)
67
+ 2. Does it already exist in this codebase? Reuse the helper, util, or pattern that's already here, don't re-write it.
68
+ 3. Does the standard library already do this? Use it.
69
+ 4. Does a native platform feature cover it? Use it.
70
+ 5. Does an already-installed dependency solve it? Use it.
71
+ 6. Can this be one line? Make it one line.
72
+ 7. Only then: write the minimum code that works.
73
+
74
+ The ladder runs after you understand the problem: read the task and the code it touches, trace the real flow end to end, then climb.
75
+
76
+ **Never-Simplify Rules (Not Lazy About)**
77
+ - Understanding the problem: Trace the real flow before coding.
78
+ - Input validation at trust boundaries.
79
+ - Error handling that prevents data loss.
80
+ - Security and accessibility.
81
+ - Anything explicitly requested.
82
+ - No abstractions that weren't explicitly requested.
83
+ - No new dependency if it can be avoided.
84
+ - No boilerplate nobody asked for.
85
+ - Deletion over addition. Boring over clever. Fewest files possible.
86
+
87
+ **Simplification Comment Convention**
88
88
  - Mark intentional simplifications with a `simplification:` comment. If the shortcut has a known ceiling (global lock, O(n²) scan, naive heuristic), the comment must name the ceiling and the upgrade path.