@doingdev/opencode-claude-manager-plugin 0.1.32 → 0.1.34

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.
@@ -21,7 +21,6 @@ export declare class SessionController {
21
21
  model?: string;
22
22
  effort?: 'low' | 'medium' | 'high' | 'max';
23
23
  mode?: SessionMode;
24
- settingSources?: Array<'user' | 'project' | 'local'>;
25
24
  abortSignal?: AbortSignal;
26
25
  }, onEvent?: ClaudeSessionEventHandler): Promise<ClaudeSessionRunResult>;
27
26
  /**
@@ -38,7 +38,7 @@ export class SessionController {
38
38
  includePartialMessages: true,
39
39
  model: options?.model,
40
40
  effort: options?.effort,
41
- settingSources: options?.settingSources ?? ['user', 'project', 'local'],
41
+ settingSources: ['user'],
42
42
  abortSignal: options?.abortSignal,
43
43
  };
44
44
  if (this.activeSessionId) {
@@ -46,7 +46,7 @@ export class SessionController {
46
46
  input.resumeSessionId = this.activeSessionId;
47
47
  }
48
48
  else {
49
- // New session — apply the expert operator system prompt and defaults
49
+ // New session — use the base session prompt (project context is in the wrapper)
50
50
  input.systemPrompt = this.sessionPrompt;
51
51
  input.model ??= 'claude-opus-4-6';
52
52
  input.effort ??= 'high';
@@ -87,7 +87,7 @@ function buildCtoPermissions() {
87
87
  },
88
88
  };
89
89
  }
90
- /** Engineer plan wrapper: engineer_send_plan + shared session tools. */
90
+ /** Engineer plan wrapper: read-only investigation + engineer_send_plan + shared session tools. */
91
91
  function buildEngineerPlanPermissions() {
92
92
  const denied = {};
93
93
  for (const toolId of ALL_RESTRICTED_TOOL_IDS) {
@@ -99,11 +99,12 @@ function buildEngineerPlanPermissions() {
99
99
  }
100
100
  return {
101
101
  '*': 'deny',
102
+ ...READONLY_TOOLS,
102
103
  ...denied,
103
104
  ...allowed,
104
105
  };
105
106
  }
106
- /** Engineer build wrapper: engineer_send_build + shared session tools. */
107
+ /** Engineer build wrapper: read-only investigation + engineer_send_build + shared session tools. */
107
108
  function buildEngineerBuildPermissions() {
108
109
  const denied = {};
109
110
  for (const toolId of ALL_RESTRICTED_TOOL_IDS) {
@@ -115,6 +116,7 @@ function buildEngineerBuildPermissions() {
115
116
  }
116
117
  return {
117
118
  '*': 'deny',
119
+ ...READONLY_TOOLS,
118
120
  ...denied,
119
121
  ...allowed,
120
122
  };
@@ -124,7 +126,7 @@ function buildEngineerBuildPermissions() {
124
126
  // ---------------------------------------------------------------------------
125
127
  export function buildCtoAgentConfig(prompts) {
126
128
  return {
127
- description: 'Pure orchestrator that investigates, spawns engineers for planning and building, reviews diffs, and commits.',
129
+ description: 'Delegates by default with minimal spot-checks, spawns engineers for exploration and implementation, reviews diffs, and commits.',
128
130
  mode: 'primary',
129
131
  color: '#D97757',
130
132
  permission: buildCtoPermissions(),
@@ -133,7 +135,7 @@ export function buildCtoAgentConfig(prompts) {
133
135
  }
134
136
  export function buildEngineerPlanAgentConfig(prompts) {
135
137
  return {
136
- description: 'Engineer that manages a Claude Code session in plan mode for read-only investigation and analysis.',
138
+ description: 'Thin high-judgment wrapper that frames work quickly and dispatches to Claude Code in plan mode for read-only investigation.',
137
139
  mode: 'subagent',
138
140
  color: '#D97757',
139
141
  permission: buildEngineerPlanPermissions(),
@@ -142,7 +144,7 @@ export function buildEngineerPlanAgentConfig(prompts) {
142
144
  }
143
145
  export function buildEngineerBuildAgentConfig(prompts) {
144
146
  return {
145
- description: 'Engineer that manages a Claude Code session in free mode for implementation and execution.',
147
+ description: 'Thin high-judgment wrapper that frames work quickly and dispatches to Claude Code in free mode for implementation.',
146
148
  mode: 'subagent',
147
149
  color: '#D97757',
148
150
  permission: buildEngineerBuildPermissions(),
@@ -1,5 +1,6 @@
1
1
  import { tool } from '@opencode-ai/plugin';
2
- import { managerPromptRegistry } from '../prompts/registry.js';
2
+ import { composeWrapperPrompt, managerPromptRegistry } from '../prompts/registry.js';
3
+ import { discoverProjectClaudeFiles } from '../util/project-context.js';
3
4
  import { AGENT_CTO, AGENT_ENGINEER_BUILD, AGENT_ENGINEER_PLAN, buildCtoAgentConfig, buildEngineerBuildAgentConfig, buildEngineerPlanAgentConfig, denyRestrictedToolsGlobally, } from './agent-hierarchy.js';
4
5
  import { getOrCreatePluginServices } from './service-factory.js';
5
6
  export const ClaudeManagerPlugin = async ({ worktree }) => {
@@ -183,9 +184,16 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
183
184
  config.agent ??= {};
184
185
  config.permission ??= {};
185
186
  denyRestrictedToolsGlobally(config.permission);
187
+ // Discover project Claude files and build derived wrapper prompts.
188
+ const claudeFiles = await discoverProjectClaudeFiles(worktree);
189
+ const derivedPrompts = {
190
+ ...managerPromptRegistry,
191
+ engineerPlanPrompt: composeWrapperPrompt(managerPromptRegistry.engineerPlanPrompt, claudeFiles),
192
+ engineerBuildPrompt: composeWrapperPrompt(managerPromptRegistry.engineerBuildPrompt, claudeFiles),
193
+ };
186
194
  config.agent[AGENT_CTO] ??= buildCtoAgentConfig(managerPromptRegistry);
187
- config.agent[AGENT_ENGINEER_PLAN] ??= buildEngineerPlanAgentConfig(managerPromptRegistry);
188
- config.agent[AGENT_ENGINEER_BUILD] ??= buildEngineerBuildAgentConfig(managerPromptRegistry);
195
+ config.agent[AGENT_ENGINEER_PLAN] ??= buildEngineerPlanAgentConfig(derivedPrompts);
196
+ config.agent[AGENT_ENGINEER_BUILD] ??= buildEngineerBuildAgentConfig(derivedPrompts);
189
197
  },
190
198
  tool: {
191
199
  engineer_send: tool({
@@ -206,7 +214,8 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
206
214
  },
207
215
  }),
208
216
  engineer_send_plan: tool({
209
- description: 'Send a read-only investigation message to the Claude Code session in plan mode. ' +
217
+ description: 'Preferred path for repo exploration and investigation. ' +
218
+ 'Send a read-only investigation message to the Claude Code session in plan mode. ' +
210
219
  'The engineer will analyze code without making edits. ' +
211
220
  'Auto-creates a session on first call. Resumes the existing session on subsequent calls. ' +
212
221
  'Returns the assistant response and current context health snapshot.',
@@ -224,7 +233,8 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
224
233
  },
225
234
  }),
226
235
  engineer_send_build: tool({
227
- description: 'Send an implementation message to the Claude Code session in free mode. ' +
236
+ description: 'Preferred path for implementation after framing. ' +
237
+ 'Send an implementation message to the Claude Code session in free mode. ' +
228
238
  'The engineer can read, edit, and create files. ' +
229
239
  'Auto-creates a session on first call. Resumes the existing session on subsequent calls. ' +
230
240
  'Returns the assistant response and current context health snapshot. ' +
@@ -1,2 +1,11 @@
1
- import type { ManagerPromptRegistry } from '../types/contracts.js';
1
+ import type { DiscoveredClaudeFile, ManagerPromptRegistry } from '../types/contracts.js';
2
+ /**
3
+ * Compose a wrapper agent prompt from the base prompt plus discovered
4
+ * project Claude files. Returns the base prompt unchanged when no
5
+ * files are provided.
6
+ *
7
+ * Each file is rendered under a clear path-labeled section so the
8
+ * wrapper agent knows exactly where each instruction came from.
9
+ */
10
+ export declare function composeWrapperPrompt(basePrompt: string, claudeFiles: DiscoveredClaudeFile[]): string;
2
11
  export declare const managerPromptRegistry: ManagerPromptRegistry;
@@ -1,7 +1,37 @@
1
+ /**
2
+ * Compose a wrapper agent prompt from the base prompt plus discovered
3
+ * project Claude files. Returns the base prompt unchanged when no
4
+ * files are provided.
5
+ *
6
+ * Each file is rendered under a clear path-labeled section so the
7
+ * wrapper agent knows exactly where each instruction came from.
8
+ */
9
+ export function composeWrapperPrompt(basePrompt, claudeFiles) {
10
+ if (claudeFiles.length === 0) {
11
+ return basePrompt;
12
+ }
13
+ const sections = claudeFiles.map((f) => `### ${f.relativePath}\n${f.content}`).join('\n\n');
14
+ return `${basePrompt}\n\n## Project Claude Files\nThe following project-level instructions were discovered from the repository.\n\n${sections}`;
15
+ }
1
16
  export const managerPromptRegistry = {
2
17
  ctoSystemPrompt: [
3
- 'You are a cracked AI-native engineer who uses Claude Code better than anyone.',
4
- 'You build the right thing, with the right quality, on the first try.',
18
+ 'You are a staff+ technical owner who uses Claude Code better than anyone.',
19
+ 'You own the outcome discover the right problem before solving it.',
20
+ '',
21
+ '## Core principle: delegation-first',
22
+ 'Your default action is to delegate. Do not do broad repo exploration yourself',
23
+ 'when an engineer can do it. Direct read/grep/glob is allowed only for:',
24
+ '- Spot-checks to sharpen a delegation.',
25
+ '- Verifying a result after an engineer returns.',
26
+ '- Resolving one high-leverage ambiguity before dispatching.',
27
+ 'If you need more than 2 direct read/grep/glob lookups, stop and delegate',
28
+ 'the investigation to `engineer_plan` instead.',
29
+ '',
30
+ '## Core principle: technical ownership',
31
+ 'You are not a ticket-taker. Before acting, look for:',
32
+ '- Hidden assumptions and missing constraints.',
33
+ '- Ownership issues, abstraction leaks, and better boundaries.',
34
+ '- Root causes — prefer fixing root causes over patching symptoms.',
5
35
  '',
6
36
  '## Core principle: verification-first',
7
37
  'Every delegation MUST include how to verify success.',
@@ -15,17 +45,31 @@ export const managerPromptRegistry = {
15
45
  '**Simple tasks** (typo, rename, add a log line, one-file fix):',
16
46
  ' Skip investigation. Delegate directly with specific context.',
17
47
  '',
18
- '**Medium tasks** (bug fix, small feature, refactor one module):',
19
- ' Read the relevant code yourself. Understand the current state.',
48
+ '**Medium+ tasks** (bug fix, feature, refactor, architecture change):',
49
+ ' Before delegating, determine:',
50
+ ' 1. What the user asked vs. what is actually needed.',
51
+ ' 2. What is underspecified or conflicting.',
52
+ ' 3. What the cleanest architecture is.',
53
+ ' 4. What should be clarified before proceeding.',
54
+ ' Prefer spawning `engineer_plan` for repo exploration rather than reading',
55
+ ' code yourself. Use at most 1-2 spot-check reads to sharpen the delegation.',
20
56
  ' Then delegate with file paths, line numbers, patterns, and verification.',
21
57
  '',
22
- '**Complex tasks** (multi-file feature, architecture change, large refactor):',
23
- ' 1. Investigate: read code, grep for patterns, spawn `engineer_plan` to explore.',
24
- ' 2. If requirements are unclear, ask the user ONE specific question.',
58
+ '**Complex tasks** (multi-file feature, large refactor):',
59
+ ' 1. Spawn `engineer_plan` to explore the repo, map dependencies, and analyze impact.',
60
+ ' 2. If requirements are unclear, ask the user ONE high-leverage question',
61
+ ' only when it materially changes architecture, ownership, or destructive behavior.',
25
62
  ' Prefer the question tool when discrete options exist.',
26
63
  ' 3. Write a plan to the todo list (todowrite). Share it with the user.',
27
64
  ' 4. Execute steps sequentially, committing after each.',
28
65
  '',
66
+ '## Missed-opportunity check',
67
+ 'Before finalizing any medium+ delegation, check:',
68
+ '- One cleaner alternative you considered.',
69
+ '- One risk the current approach carries.',
70
+ '- One thing the requester likely missed.',
71
+ 'If any of these materially improve the outcome, surface them.',
72
+ '',
29
73
  '## How to delegate effectively',
30
74
  'The engineer does not have your context. Every instruction must be self-contained.',
31
75
  'Include:',
@@ -87,7 +131,21 @@ export const managerPromptRegistry = {
87
131
  'State the blocker, what you need, and a concrete suggestion to unblock.',
88
132
  ].join('\n'),
89
133
  engineerPlanPrompt: [
90
- 'You manage a Claude Code engineer for read-only investigation.',
134
+ 'You are a staff engineer managing a Claude Code session for read-only investigation.',
135
+ 'You are not a forwarding layer — interpret the task in repo context before delegating.',
136
+ '',
137
+ '## Staff-level framing',
138
+ '- Identify the real problem, not just the stated request.',
139
+ '- Look for missing architecture, ownership, or precedence issues before delegating.',
140
+ '- Rewrite weak or underspecified requests into precise prompts for the engineer.',
141
+ '- For medium+ tasks, determine: the actual problem, the cleanest architecture,',
142
+ ' and what needs clarification before work begins.',
143
+ '- Ask ONE clarification first if it materially improves architecture.',
144
+ '',
145
+ '## Repo-context investigation',
146
+ '- Use read/grep/glob sparingly — only for spot-checks to sharpen a delegation.',
147
+ '- If more than 2 lookups are needed, send the investigation to the engineer.',
148
+ '- Do NOT implement changes yourself — investigation only.',
91
149
  '',
92
150
  '## Behavior',
93
151
  '- Send the objective to the engineer using engineer_send_plan.',
@@ -103,13 +161,34 @@ export const managerPromptRegistry = {
103
161
  '- claude-sonnet-4-6: lighter analysis.',
104
162
  '- effort "medium": simple lookups.',
105
163
  '',
164
+ '## Using appended Project Claude Files',
165
+ '- Treat appended Project Claude Files as project guidance for the engineer.',
166
+ '- Extract only the rules relevant to the current task when delegating.',
167
+ '- Prefer guidance from more specific/nested paths over root-level guidance on conflict.',
168
+ '- Direct user instructions override Claude-file guidance.',
169
+ '- Keep delegated instructions tight; do not dump the full file corpus unless needed.',
170
+ '- Cite file paths (e.g. "per packages/core/CLAUDE.md") when the source matters.',
171
+ '',
106
172
  '## What you must NOT do',
107
- '- Do NOT investigate on your own — no read, grep, glob, or web tools.',
108
173
  '- Do NOT call git_*, approval_*, or any non-engineer tools.',
109
174
  '- Do NOT add commentary to the engineer response.',
110
175
  ].join('\n'),
111
176
  engineerBuildPrompt: [
112
- 'You manage a Claude Code engineer for implementation.',
177
+ 'You are a staff engineer managing a Claude Code session for implementation.',
178
+ 'You are not a forwarding layer — interpret the task in repo context before delegating.',
179
+ '',
180
+ '## Staff-level framing',
181
+ '- Identify the real problem, not just the stated request.',
182
+ '- Look for missing architecture, ownership, or precedence issues before delegating.',
183
+ '- Rewrite weak or underspecified requests into precise prompts for the engineer.',
184
+ '- For medium+ tasks, determine: the actual problem, the cleanest architecture,',
185
+ ' and what needs clarification before work begins.',
186
+ '- Ask ONE clarification first if it materially improves architecture.',
187
+ '',
188
+ '## Repo-context investigation',
189
+ '- Use read/grep/glob sparingly — only for spot-checks to sharpen a delegation.',
190
+ '- If more than 2 lookups are needed, send the investigation to the engineer.',
191
+ '- Do NOT implement changes yourself — investigation only.',
113
192
  '',
114
193
  '## Behavior',
115
194
  '- Send the objective to the engineer using engineer_send_build.',
@@ -125,8 +204,15 @@ export const managerPromptRegistry = {
125
204
  '- claude-sonnet-4-6: simple renames, formatting, scaffolding.',
126
205
  '- effort "max": complex refactors, subtle bugs, cross-cutting changes.',
127
206
  '',
207
+ '## Using appended Project Claude Files',
208
+ '- Treat appended Project Claude Files as project guidance for the engineer.',
209
+ '- Extract only the rules relevant to the current task when delegating.',
210
+ '- Prefer guidance from more specific/nested paths over root-level guidance on conflict.',
211
+ '- Direct user instructions override Claude-file guidance.',
212
+ '- Keep delegated instructions tight; do not dump the full file corpus unless needed.',
213
+ '- Cite file paths (e.g. "per packages/core/CLAUDE.md") when the source matters.',
214
+ '',
128
215
  '## What you must NOT do',
129
- '- Do NOT investigate on your own — no read, grep, glob, or web tools.',
130
216
  '- Do NOT call git_*, approval_*, or any non-engineer tools.',
131
217
  '- Do NOT add commentary to the engineer response.',
132
218
  ].join('\n'),
@@ -166,6 +166,12 @@ export interface ToolOutputPreview {
166
166
  content: string;
167
167
  isError: boolean;
168
168
  }
169
+ export interface DiscoveredClaudeFile {
170
+ /** Relative path from the project root (forward slashes, deterministic order). */
171
+ relativePath: string;
172
+ /** Trimmed UTF-8 text content. */
173
+ content: string;
174
+ }
169
175
  export interface ToolApprovalRule {
170
176
  id: string;
171
177
  description?: string;
@@ -0,0 +1,10 @@
1
+ import type { DiscoveredClaudeFile } from '../types/contracts.js';
2
+ /**
3
+ * Recursively discover all project-level Claude files:
4
+ * - every `CLAUDE.md` found anywhere in the repo tree
5
+ * - every file under `.claude/` recursively
6
+ *
7
+ * Returns a de-duplicated, deterministically sorted array of
8
+ * { relativePath, content } entries. Empty/blank files are skipped.
9
+ */
10
+ export declare function discoverProjectClaudeFiles(cwd: string): Promise<DiscoveredClaudeFile[]>;
@@ -0,0 +1,105 @@
1
+ import { readFile, readdir } from 'node:fs/promises';
2
+ import { join, relative } from 'node:path';
3
+ import { isFileNotFoundError } from './fs-helpers.js';
4
+ /**
5
+ * Recursively discover all project-level Claude files:
6
+ * - every `CLAUDE.md` found anywhere in the repo tree
7
+ * - every file under `.claude/` recursively
8
+ *
9
+ * Returns a de-duplicated, deterministically sorted array of
10
+ * { relativePath, content } entries. Empty/blank files are skipped.
11
+ */
12
+ export async function discoverProjectClaudeFiles(cwd) {
13
+ const seen = new Set();
14
+ const results = [];
15
+ // 1. Walk the entire tree for CLAUDE.md files.
16
+ await walkForClaudeMd(cwd, cwd, seen, results);
17
+ // 2. Walk .claude/ recursively for any file.
18
+ await walkDotClaudeDir(cwd, seen, results);
19
+ // Deterministic: sort by relativePath (lexicographic).
20
+ results.sort((a, b) => a.relativePath < b.relativePath ? -1 : a.relativePath > b.relativePath ? 1 : 0);
21
+ return results;
22
+ }
23
+ // ---------------------------------------------------------------------------
24
+ // Internal helpers
25
+ // ---------------------------------------------------------------------------
26
+ /** Recursively walk `dir` looking for files named `CLAUDE.md`. */
27
+ async function walkForClaudeMd(root, dir, seen, out) {
28
+ let entries;
29
+ try {
30
+ entries = await readdir(dir, { withFileTypes: true });
31
+ }
32
+ catch (err) {
33
+ if (isFileNotFoundError(err))
34
+ return;
35
+ throw err;
36
+ }
37
+ for (const entry of entries) {
38
+ const fullPath = join(dir, entry.name);
39
+ if (entry.isDirectory()) {
40
+ // Skip node_modules, .git, and hidden dirs other than .claude
41
+ if (entry.name === 'node_modules' || entry.name === '.git')
42
+ continue;
43
+ await walkForClaudeMd(root, fullPath, seen, out);
44
+ }
45
+ else if (entry.name === 'CLAUDE.md') {
46
+ const rel = toForwardSlash(relative(root, fullPath));
47
+ if (seen.has(rel))
48
+ continue;
49
+ const content = await tryReadText(fullPath);
50
+ if (content !== null) {
51
+ seen.add(rel);
52
+ out.push({ relativePath: rel, content });
53
+ }
54
+ }
55
+ }
56
+ }
57
+ /** Recursively walk `.claude/` and collect every file. */
58
+ async function walkDotClaudeDir(root, seen, out) {
59
+ const dotClaudeDir = join(root, '.claude');
60
+ await walkAllFiles(root, dotClaudeDir, seen, out);
61
+ }
62
+ /** Recursively collect all files under `dir`. */
63
+ async function walkAllFiles(root, dir, seen, out) {
64
+ let entries;
65
+ try {
66
+ entries = await readdir(dir, { withFileTypes: true });
67
+ }
68
+ catch (err) {
69
+ if (isFileNotFoundError(err))
70
+ return;
71
+ throw err;
72
+ }
73
+ for (const entry of entries) {
74
+ const fullPath = join(dir, entry.name);
75
+ if (entry.isDirectory()) {
76
+ await walkAllFiles(root, fullPath, seen, out);
77
+ }
78
+ else if (entry.isFile()) {
79
+ const rel = toForwardSlash(relative(root, fullPath));
80
+ if (seen.has(rel))
81
+ continue;
82
+ const content = await tryReadText(fullPath);
83
+ if (content !== null) {
84
+ seen.add(rel);
85
+ out.push({ relativePath: rel, content });
86
+ }
87
+ }
88
+ }
89
+ }
90
+ /** Read a file as UTF-8, returning trimmed content or null if empty/missing. */
91
+ async function tryReadText(filePath) {
92
+ try {
93
+ const raw = await readFile(filePath, 'utf-8');
94
+ const trimmed = raw.trim();
95
+ return trimmed.length > 0 ? trimmed : null;
96
+ }
97
+ catch (err) {
98
+ if (isFileNotFoundError(err))
99
+ return null;
100
+ throw err;
101
+ }
102
+ }
103
+ function toForwardSlash(p) {
104
+ return p.replace(/\\/g, '/');
105
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doingdev/opencode-claude-manager-plugin",
3
- "version": "0.1.32",
3
+ "version": "0.1.34",
4
4
  "description": "OpenCode plugin that orchestrates Claude Code sessions.",
5
5
  "keywords": [
6
6
  "opencode",