pluidr 0.4.1 → 0.6.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.
Files changed (49) hide show
  1. package/README.md +294 -73
  2. package/package.json +7 -2
  3. package/src/cli/commands/doctor.js +99 -0
  4. package/src/cli/commands/init.js +14 -7
  5. package/src/cli/commands/uninstall.js +67 -0
  6. package/src/cli/commands/update.js +22 -0
  7. package/src/cli/index.js +31 -1
  8. package/src/cli/wizard/selectModelTier.js +22 -31
  9. package/src/core/agentPromptWriter.js +2 -2
  10. package/src/core/agentPromptWriter.test.js +56 -0
  11. package/src/core/backup.js +35 -5
  12. package/src/core/backup.test.js +51 -0
  13. package/src/core/configBuilder.js +13 -0
  14. package/src/core/configBuilder.test.js +47 -0
  15. package/src/core/configWriter.js +7 -4
  16. package/src/core/configWriter.test.js +26 -0
  17. package/src/core/identityHeader.test.js +15 -0
  18. package/src/core/paths.js +4 -15
  19. package/src/core/paths.test.js +21 -0
  20. package/src/core/pluginWriter.js +12 -8
  21. package/src/core/pluginWriter.test.js +41 -0
  22. package/src/core/squeezeInstaller.js +141 -0
  23. package/src/core/squeezeInstaller.test.js +77 -0
  24. package/src/plugins/README.md +29 -15
  25. package/src/plugins/{parent-session.js → pluidr-flow.js} +1 -6
  26. package/src/plugins/pluidr-squeeze.js +56 -0
  27. package/src/templates/agent-prompts/auditor.txt +20 -0
  28. package/src/templates/agent-prompts/coder.txt +33 -7
  29. package/src/templates/agent-prompts/{writer.txt → compose-reporter.txt} +7 -7
  30. package/src/templates/agent-prompts/composer.txt +428 -0
  31. package/src/templates/agent-prompts/{reporter.txt → debug-reporter.txt} +3 -7
  32. package/src/templates/agent-prompts/debugger.txt +64 -18
  33. package/src/templates/agent-prompts/fixer.txt +7 -0
  34. package/src/templates/agent-prompts/hierarchy.txt +29 -15
  35. package/src/templates/agent-prompts/patcher.txt +20 -0
  36. package/src/templates/agent-prompts/plan-checker.txt +5 -5
  37. package/src/templates/agent-prompts/plan-writer.txt +3 -3
  38. package/src/templates/agent-prompts/probe-reporter.txt +62 -0
  39. package/src/templates/agent-prompts/prober.txt +90 -0
  40. package/src/templates/agent-prompts/researcher.txt +3 -3
  41. package/src/templates/agent-prompts/reviewer.txt +16 -4
  42. package/src/templates/agent-prompts/tester.txt +10 -1
  43. package/src/templates/agent-prompts/tracer.txt +33 -0
  44. package/src/templates/model-defaults.json +45 -2
  45. package/src/templates/opencode.config.json +227 -73
  46. package/src/templates/rtk-checksums.json +7 -0
  47. package/src/templates/agent-prompts/builder.txt +0 -107
  48. package/src/templates/agent-prompts/explorer.txt +0 -53
  49. package/src/templates/agent-prompts/planner.txt +0 -126
@@ -0,0 +1,141 @@
1
+ import { execFileSync } from "node:child_process"
2
+ import { chmodSync, mkdirSync, existsSync, writeFileSync, unlinkSync, readFileSync } from "node:fs"
3
+ import { extname, join, resolve, dirname } from "node:path"
4
+ import { fileURLToPath } from "node:url"
5
+ import { createHash } from "node:crypto"
6
+ import { getConfigDir } from "./paths.js"
7
+
8
+ const __dirname = dirname(fileURLToPath(import.meta.url))
9
+
10
+ const BIN_NAME = process.platform === "win32" ? "rtk.exe" : "rtk"
11
+ export const ASSETS = {
12
+ "darwin-arm64": "rtk-aarch64-apple-darwin.tar.gz",
13
+ "darwin-x64": "rtk-x86_64-apple-darwin.tar.gz",
14
+ "linux-x64": "rtk-x86_64-unknown-linux-musl.tar.gz",
15
+ "linux-arm64": "rtk-aarch64-unknown-linux-gnu.tar.gz",
16
+ "win32-x64": "rtk-x86_64-pc-windows-msvc.zip",
17
+ }
18
+ const RTK_RELEASE_BASE = "https://github.com/rtk-ai/rtk/releases/latest/download"
19
+
20
+ export function platformAsset() {
21
+ const key = `${process.platform}-${process.arch}`
22
+ return ASSETS[key] ?? null
23
+ }
24
+
25
+ export function validatePath(path) {
26
+ if (!/^[a-zA-Z0-9_\-\.\:\/\\]+$/.test(path)) {
27
+ throw new Error(`Invalid path: "${path}" contains unsafe characters`)
28
+ }
29
+ }
30
+
31
+ export function findRtkPath() {
32
+ try {
33
+ const [cmd, ...args] =
34
+ process.platform === "win32" ? ["where", "rtk"] : ["which", "rtk"]
35
+ return execFileSync(cmd, args, { stdio: "pipe" })
36
+ .toString()
37
+ .trim()
38
+ .split(/\r?\n/)[0]
39
+ } catch {
40
+ return null
41
+ }
42
+ }
43
+
44
+ function extract(archivePath, destDir) {
45
+ validatePath(archivePath)
46
+ validatePath(destDir)
47
+ const isZip = extname(archivePath) === ".zip"
48
+
49
+ if (isZip && process.platform === "win32") {
50
+ execFileSync("powershell", [
51
+ "-NoProfile",
52
+ "-Command",
53
+ "Expand-Archive",
54
+ "-LiteralPath", archivePath,
55
+ "-DestinationPath", destDir,
56
+ "-Force"
57
+ ], { stdio: "ignore" })
58
+ } else {
59
+ execFileSync("tar", ["-xzf", archivePath, "-C", destDir], { stdio: "ignore" })
60
+ }
61
+ }
62
+
63
+ function verifyChecksum(buffer, assetName) {
64
+ const checksumsPath = resolve(__dirname, "../templates/rtk-checksums.json")
65
+ let checksums
66
+ try {
67
+ checksums = JSON.parse(readFileSync(checksumsPath, "utf-8"))
68
+ } catch {
69
+ // No checksums file — skip verification
70
+ return
71
+ }
72
+
73
+ const expectedHash = checksums[assetName]
74
+ if (expectedHash === null || expectedHash === undefined) {
75
+ // Hash not yet populated for this platform — silently skip
76
+ return
77
+ }
78
+
79
+ const computed = createHash("sha256").update(buffer).digest("hex")
80
+ if (computed !== expectedHash) {
81
+ throw new Error(
82
+ `SHA256 mismatch for ${assetName}\n expected: ${expectedHash}\n computed: ${computed}`
83
+ )
84
+ }
85
+ }
86
+
87
+ export async function installSqueeze() {
88
+ let rtkPath = findRtkPath()
89
+
90
+ if (!rtkPath) {
91
+ // Not on PATH — download it to the managed location
92
+ const asset = platformAsset()
93
+ if (!asset) {
94
+ console.warn("⚠ Squeeze: unsupported platform — install rtk manually")
95
+ return
96
+ }
97
+
98
+ const binDir = join(getConfigDir(), "bin")
99
+ mkdirSync(binDir, { recursive: true })
100
+
101
+ const url = `${RTK_RELEASE_BASE}/${asset}`
102
+ const archivePath = join(binDir, asset)
103
+
104
+ try {
105
+ let response
106
+ for (let attempt = 1; attempt <= 3; attempt++) {
107
+ try {
108
+ response = await fetch(url)
109
+ if (!response.ok) throw new Error(`HTTP ${response.status}`)
110
+ break
111
+ } catch (err) {
112
+ if (attempt < 3) {
113
+ // Retry on any error (network, HTTP, etc.) with exponential backoff
114
+ const delay = Math.pow(2, attempt - 1) * 1000
115
+ await new Promise(r => setTimeout(r, delay))
116
+ continue
117
+ }
118
+ throw err
119
+ }
120
+ }
121
+
122
+ const buffer = Buffer.from(await response.arrayBuffer())
123
+ verifyChecksum(buffer, asset)
124
+ writeFileSync(archivePath, buffer)
125
+
126
+ extract(archivePath, binDir)
127
+
128
+ try { unlinkSync(archivePath) } catch {}
129
+
130
+ rtkPath = join(binDir, BIN_NAME)
131
+
132
+ if (process.platform !== "win32" && existsSync(rtkPath)) {
133
+ chmodSync(rtkPath, 0o755)
134
+ }
135
+ } catch {
136
+ try { unlinkSync(archivePath) } catch {}
137
+ console.warn("⚠ Squeeze: download failed after 3 attempts — install rtk manually")
138
+ return
139
+ }
140
+ }
141
+ }
@@ -0,0 +1,77 @@
1
+ import { describe, it } from "node:test"
2
+ import assert from "node:assert"
3
+ import { platformAsset, validatePath, findRtkPath, ASSETS } from "./squeezeInstaller.js"
4
+
5
+ describe("platformAsset", () => {
6
+ it("returns a string or null for current platform", () => {
7
+ const result = platformAsset()
8
+ // On a supported platform: string; on unsupported: null
9
+ assert.ok(result === null || typeof result === "string")
10
+ })
11
+
12
+ it("returns known asset names for mapped platforms", () => {
13
+ // Verify the ASSETS map has entries for all known platform combos
14
+ const knownCombos = [
15
+ { plat: "darwin", arch: "arm64" },
16
+ { plat: "darwin", arch: "x64" },
17
+ { plat: "linux", arch: "x64" },
18
+ { plat: "linux", arch: "arm64" },
19
+ { plat: "win32", arch: "x64" },
20
+ ]
21
+ for (const { plat, arch } of knownCombos) {
22
+ const key = `${plat}-${arch}`
23
+ const asset = ASSETS[key]
24
+ assert.ok(asset, `ASSETS map should have entry for ${key}`)
25
+ assert.ok(asset.endsWith(".tar.gz") || asset.endsWith(".zip"),
26
+ `Asset "${asset}" should end with .tar.gz or .zip`)
27
+ }
28
+ })
29
+ })
30
+
31
+ describe("validatePath", () => {
32
+ it("accepts simple relative paths", () => {
33
+ assert.doesNotThrow(() => validatePath("foo/bar"))
34
+ })
35
+
36
+ it("accepts absolute POSIX paths", () => {
37
+ assert.doesNotThrow(() => validatePath("/home/user/.config"))
38
+ })
39
+
40
+ it("accepts absolute Windows paths", () => {
41
+ assert.doesNotThrow(() => validatePath("C:\\Users\\test"))
42
+ })
43
+
44
+ it("accepts paths with dots and dashes", () => {
45
+ assert.doesNotThrow(() => validatePath("./dir/file-name.ext"))
46
+ })
47
+
48
+ it("allows .. in paths (shell metacharacter check only, not path traversal)", () => {
49
+ assert.doesNotThrow(() => validatePath("../evil"))
50
+ })
51
+
52
+ it("rejects paths with semicolons", () => {
53
+ assert.throws(() => validatePath("foo;rm -rf /"), /unsafe characters/)
54
+ })
55
+
56
+ it("rejects paths with backticks", () => {
57
+ assert.throws(() => validatePath("foo`id`"), /unsafe characters/)
58
+ })
59
+
60
+ it("rejects paths with dollar signs", () => {
61
+ assert.throws(() => validatePath("foo$PATH"), /unsafe characters/)
62
+ })
63
+
64
+ it("rejects paths with pipe characters", () => {
65
+ assert.throws(() => validatePath("foo|bar"), /unsafe characters/)
66
+ })
67
+ })
68
+
69
+ describe("findRtkPath", () => {
70
+ it("returns null when rtk is not available on PATH", () => {
71
+ // This test is safe because if rtk IS on PATH, we still get a
72
+ // string back — but the test doesn't crash either way.
73
+ const result = findRtkPath()
74
+ // Either null (not on PATH) or a non-empty string (found)
75
+ assert.ok(result === null || typeof result === "string")
76
+ })
77
+ })
@@ -2,13 +2,13 @@
2
2
 
3
3
  This folder holds plugin source files that `pluidr init` copies into the
4
4
  user's OpenCode plugin directory (`~/.config/opencode/plugins/`), giving
5
- subagents cross-session context tools out of the box.
5
+ subagents cross-session context tools and command output filtering out of
6
+ the box.
6
7
 
7
- ## What is `parent-session.js`?
8
+ ## `pluidr-flow.js` cross-session context
8
9
 
9
- It is the implementation of the `ParentSessionPlugin`, registered by
10
- OpenCode at startup. It exposes three tools that subagents use to read
11
- the conversation history of related sessions (parent, sibling, or batch):
10
+ Implementation of the `SessionFlowPlugin`. Exposes three tools for reading
11
+ conversation history across related sessions (parent, sibling, or batch):
12
12
 
13
13
  - `parent_session_messages` — read the parent session's full transcript
14
14
  - `session_messages(sessionId)` — read any session by ID
@@ -18,7 +18,21 @@ The output is a structured text dump with numbered messages, agent
18
18
  attribution, and one-line tool-invocation summaries, separated by
19
19
  `\n\n---\n\n`.
20
20
 
21
- ## Why `parent-session` was extracted
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 `rtk` binary. The binary filters,
25
+ groups, truncates, and deduplicates command output — saving 60-90% of
26
+ tokens across all agents.
27
+
28
+ The plugin is a thin delegator: all rewrite logic lives in the `rtk` binary.
29
+ It checks `PATH` first, then falls back to Pluidr's managed location at
30
+ `~/.config/opencode/bin/rtk`. If the binary is not found, the plugin
31
+ gracefully disables itself with a warning.
32
+
33
+ `pluidr init` downloads the `rtk` binary automatically during setup.
34
+
35
+ ## Why `pluidr-flow` was extracted
22
36
 
23
37
  Subagents (Coder, Tester, Reviewer, Inspector, Fixer, etc.) run in fresh
24
38
  child sessions with no access to the parent orchestrator's conversation
@@ -31,7 +45,7 @@ and pick up where the parent left off.
31
45
  ## Why the identity plugin was dropped
32
46
 
33
47
  The original `AgentSelfIdentityPlugin` injected agent identity via
34
- `experimental.chat.*` hooks. Pluidr's 14 agent names are known at install
48
+ `experimental.chat.*` hooks. Pluidr's 12 agent names are known at install
35
49
  time, so we prepend a static identity header (`You are the "<name>" agent.`)
36
50
  to every generated prompt file. This is KISS: no hook evaluation per
37
51
  request, no dependency on undocumented experimental APIs, single source
@@ -43,12 +57,12 @@ The original `AgentAttributionToolPlugin` was designed for a Retrospective
43
57
  agent that captures post-session observations. Pluidr has no Retrospective
44
58
  agent in its pipeline, so the tool would have no caller — YAGNI.
45
59
 
46
- ## How it is loaded
60
+ ## How they are loaded
47
61
 
48
- `pluidr init` copies `parent-session.js` into
49
- `~/.config/opencode/plugins/` and writes a `package.json` next to it
50
- declaring `@opencode-ai/plugin: ^1.17.9` as a dependency. On OpenCode's
51
- first launch, the bundled Bun runtime detects the `package.json` and runs
52
- `bun install` automatically — no user action required. Once installed,
53
- the plugin's three tools are available to every agent that has the
54
- appropriate permissions.
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 the `rtk` engine binary
65
+ to `~/.config/opencode/bin/`. On OpenCode's first launch, the bundled Bun
66
+ runtime detects the `package.json` and runs `bun install` automatically —
67
+ no user action required. Once installed, both plugins are available to
68
+ every agent that has the appropriate permissions.
@@ -1,8 +1,3 @@
1
- // Parent-session plugin: provides tools for cross-session context access in
2
- // subagent workflows. Translated from the original TypeScript implementation
3
- // in addition/opencode-session-context/src/parent-session.ts to plain ESM JS
4
- // (no build step required). Behavior, formatting, and tool names are unchanged.
5
-
6
1
  import { tool } from "@opencode-ai/plugin"
7
2
 
8
3
  function formatInput(input) {
@@ -56,7 +51,7 @@ function formatMessage(msg, index) {
56
51
  return `${num}. ${msg.info.role}\n${formatParts(msg.parts)}`
57
52
  }
58
53
 
59
- export const ParentSessionPlugin = async ({ client }) => {
54
+ export const SessionFlowPlugin = async ({ client }) => {
60
55
  async function formatSessionMessages(sessionID, emptyMessage) {
61
56
  const response = await client.session.messages({
62
57
  path: { id: sessionID },
@@ -0,0 +1,56 @@
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" ? "rtk.exe" : "rtk"
6
+ const SQUEEZE_BIN = join(homedir(), ".config", "opencode", "bin", BIN_NAME)
7
+
8
+ function normalise(p) {
9
+ return p.replace(/\\/g, "/")
10
+ }
11
+
12
+ async function findBinary($) {
13
+ try {
14
+ if (process.platform === "win32") {
15
+ await $`where rtk`.quiet()
16
+ } else {
17
+ await $`which rtk`.quiet()
18
+ }
19
+ return "rtk"
20
+ } catch {
21
+ if (existsSync(SQUEEZE_BIN)) {
22
+ return normalise(SQUEEZE_BIN)
23
+ }
24
+ return null
25
+ }
26
+ }
27
+
28
+ export const PluidrSqueezePlugin = async ({ $ }) => {
29
+ const binPath = await findBinary($)
30
+ if (!binPath) {
31
+ console.warn("[squeeze] rtk binary not found — plugin disabled")
32
+ return {}
33
+ }
34
+
35
+ return {
36
+ "tool.execute.before": async (input, output) => {
37
+ const tool = String(input?.tool ?? "").toLowerCase()
38
+ if (tool !== "bash" && tool !== "shell") return
39
+ const args = output?.args
40
+ if (!args || typeof args !== "object") return
41
+
42
+ const command = args.command
43
+ if (typeof command !== "string" || !command) return
44
+
45
+ try {
46
+ const result = await $`${binPath} rewrite ${command}`.quiet().nothrow()
47
+ const rewritten = String(result.stdout).trim()
48
+ if (rewritten && rewritten !== command) {
49
+ args.command = rewritten
50
+ }
51
+ } catch {
52
+ // rewrite failed — pass through unchanged
53
+ }
54
+ },
55
+ }
56
+ }
@@ -0,0 +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,8 +1,6 @@
1
1
  # Role: Coder Subagent
2
2
 
3
- You implement tasks assigned by Builder, following the PRD exactly. You
4
- manage your own task tracking internally via `todowrite` — this is not a
5
- persisted document, it's your working checklist for the current session.
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.
6
4
 
7
5
  Refer to `hierarchy.txt` (loaded globally) for conflict resolution.
8
6
 
@@ -39,7 +37,7 @@ Refer to `hierarchy.txt` (loaded globally) for conflict resolution.
39
37
  You have no `task` permission — you cannot invoke any other agent or
40
38
  subagent. If implementation reveals a need for research (e.g., unclear
41
39
  library behavior) or a requirement question, stop and report back to
42
- Builder rather than guessing or trying to research it yourself outside
40
+ Composer rather than guessing or trying to research it yourself outside
43
41
  your given tools.
44
42
 
45
43
  ## Task tracking
@@ -52,11 +50,39 @@ your given tools.
52
50
  ## What you do NOT do
53
51
 
54
52
  - You do not change the requirement — if the PRD task is ambiguous or
55
- infeasible as written, stop and report back to Builder rather than
53
+ infeasible as written, stop and report back to Composer rather than
56
54
  reinterpreting it.
57
- - You do not produce documentation — that's Writer's job.
55
+ - You do not produce documentation — that's Compose-Reporter's job.
58
56
 
59
57
  ## Output
60
58
 
61
59
  - Code changes, with a brief note on any deviation from the assigned task (if any).
62
- - Flag any duplicated logic found during implementation.
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
+ - 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.
@@ -1,29 +1,29 @@
1
- # Role: Writer Subagent
1
+ # Role: Compose-Reporter Subagent
2
2
 
3
- You are the **Writer** subagent, the text-file executor for the **Builder**
3
+ You are the **Compose-Reporter** subagent, the text-file executor for the **Composer**
4
4
  agent. You write and edit `.md` and `.txt` files across the project — like
5
5
  Coder but for documents only. You do not run bash commands.
6
6
 
7
7
  ## Mode: Document Creation / Editing
8
8
 
9
9
  You create and modify documentation files (.md, .txt) as instructed by
10
- Builder. You may also be invoked in Summary mode to produce completion
10
+ Composer. You may also be invoked in Summary mode to produce completion
11
11
  reports from structured input.
12
12
 
13
13
  ## Delegation rules
14
14
 
15
15
  You have no `task` permission — you cannot invoke any other agent or
16
- subagent. If Builder's instructions are insufficient to produce the
16
+ subagent. If Composer's instructions are insufficient to produce the
17
17
  required document, mark missing sections as `TBD` — do not infer, research,
18
18
  or invent content.
19
19
 
20
20
  ## Principles
21
21
 
22
- - **Audience-First** — Structure the document for its reader. If Builder specifies an audience (technical lead, maintainer, end-user), tailor detail level and vocabulary accordingly. If no audience is specified, default to technical summary.
22
+ - **Audience-First** — Structure the document for its reader. If Composer specifies an audience (technical lead, maintainer, end-user), tailor detail level and vocabulary accordingly. If no audience is specified, default to technical summary.
23
23
  - **DRY for Docs** — Each finding or verdict appears exactly once with cross-references. Do not repeat the same information across sections — it creates maintenance burden and risks inconsistency.
24
24
  - **KISS** — Prefer simple, flat structure over nested hierarchies. A reader should grasp the outcome from the first paragraph.
25
25
 
26
- ## Writer MUST NOT
26
+ ## Compose-Reporter MUST NOT
27
27
 
28
28
  - Make inferences about what the user "probably means."
29
29
  - Make decisions (e.g., which approach is better).
@@ -31,7 +31,7 @@ or invent content.
31
31
  - Fill missing information with assumptions — mark as `TBD` instead.
32
32
  - Run bash commands — you have no `bash` permission.
33
33
 
34
- ## Writer MAY ONLY
34
+ ## Compose-Reporter MAY ONLY
35
35
 
36
36
  - Write and edit `.md` and `.txt` files as instructed.
37
37
  - Reformat / restructure given input into the target document type.