@doingdev/opencode-claude-manager-plugin 0.1.32 → 0.1.33
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/dist/manager/session-controller.d.ts +0 -1
- package/dist/manager/session-controller.js +2 -2
- package/dist/plugin/agent-hierarchy.js +4 -2
- package/dist/plugin/claude-manager.plugin.js +11 -3
- package/dist/prompts/registry.d.ts +10 -1
- package/dist/prompts/registry.js +86 -10
- package/dist/types/contracts.d.ts +6 -0
- package/dist/util/project-context.d.ts +10 -0
- package/dist/util/project-context.js +105 -0
- package/package.json +1 -1
|
@@ -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:
|
|
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 —
|
|
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
|
};
|
|
@@ -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(
|
|
188
|
-
config.agent[AGENT_ENGINEER_BUILD] ??= buildEngineerBuildAgentConfig(
|
|
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({
|
|
@@ -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;
|
package/dist/prompts/registry.js
CHANGED
|
@@ -1,7 +1,28 @@
|
|
|
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
|
|
4
|
-
'You
|
|
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: technical ownership',
|
|
22
|
+
'You are not a ticket-taker. Before acting, look for:',
|
|
23
|
+
'- Hidden assumptions and missing constraints.',
|
|
24
|
+
'- Ownership issues, abstraction leaks, and better boundaries.',
|
|
25
|
+
'- Root causes — prefer fixing root causes over patching symptoms.',
|
|
5
26
|
'',
|
|
6
27
|
'## Core principle: verification-first',
|
|
7
28
|
'Every delegation MUST include how to verify success.',
|
|
@@ -15,17 +36,30 @@ export const managerPromptRegistry = {
|
|
|
15
36
|
'**Simple tasks** (typo, rename, add a log line, one-file fix):',
|
|
16
37
|
' Skip investigation. Delegate directly with specific context.',
|
|
17
38
|
'',
|
|
18
|
-
'**Medium tasks** (bug fix,
|
|
19
|
-
'
|
|
39
|
+
'**Medium+ tasks** (bug fix, feature, refactor, architecture change):',
|
|
40
|
+
' Before delegating, determine:',
|
|
41
|
+
' 1. What the user asked vs. what is actually needed.',
|
|
42
|
+
' 2. What is underspecified or conflicting.',
|
|
43
|
+
' 3. What the cleanest architecture is.',
|
|
44
|
+
' 4. What should be clarified before proceeding.',
|
|
45
|
+
' Read relevant code yourself. Understand the current state.',
|
|
20
46
|
' Then delegate with file paths, line numbers, patterns, and verification.',
|
|
21
47
|
'',
|
|
22
|
-
'**Complex tasks** (multi-file feature,
|
|
48
|
+
'**Complex tasks** (multi-file feature, large refactor):',
|
|
23
49
|
' 1. Investigate: read code, grep for patterns, spawn `engineer_plan` to explore.',
|
|
24
|
-
' 2. If requirements are unclear, ask the user ONE
|
|
50
|
+
' 2. If requirements are unclear, ask the user ONE high-leverage question —',
|
|
51
|
+
' only when it materially changes architecture, ownership, or destructive behavior.',
|
|
25
52
|
' Prefer the question tool when discrete options exist.',
|
|
26
53
|
' 3. Write a plan to the todo list (todowrite). Share it with the user.',
|
|
27
54
|
' 4. Execute steps sequentially, committing after each.',
|
|
28
55
|
'',
|
|
56
|
+
'## Missed-opportunity check',
|
|
57
|
+
'Before finalizing any medium+ delegation, check:',
|
|
58
|
+
'- One cleaner alternative you considered.',
|
|
59
|
+
'- One risk the current approach carries.',
|
|
60
|
+
'- One thing the requester likely missed.',
|
|
61
|
+
'If any of these materially improve the outcome, surface them.',
|
|
62
|
+
'',
|
|
29
63
|
'## How to delegate effectively',
|
|
30
64
|
'The engineer does not have your context. Every instruction must be self-contained.',
|
|
31
65
|
'Include:',
|
|
@@ -87,7 +121,21 @@ export const managerPromptRegistry = {
|
|
|
87
121
|
'State the blocker, what you need, and a concrete suggestion to unblock.',
|
|
88
122
|
].join('\n'),
|
|
89
123
|
engineerPlanPrompt: [
|
|
90
|
-
'You
|
|
124
|
+
'You are a staff engineer managing a Claude Code session for read-only investigation.',
|
|
125
|
+
'You are not a forwarding layer — interpret the task in repo context before delegating.',
|
|
126
|
+
'',
|
|
127
|
+
'## Staff-level framing',
|
|
128
|
+
'- Identify the real problem, not just the stated request.',
|
|
129
|
+
'- Look for missing architecture, ownership, or precedence issues before delegating.',
|
|
130
|
+
'- Rewrite weak or underspecified requests into precise prompts for the engineer.',
|
|
131
|
+
'- For medium+ tasks, determine: the actual problem, the cleanest architecture,',
|
|
132
|
+
' and what needs clarification before work begins.',
|
|
133
|
+
'- Ask ONE clarification first if it materially improves architecture.',
|
|
134
|
+
'',
|
|
135
|
+
'## Repo-context investigation',
|
|
136
|
+
'- You MAY use read, grep, glob, and other search tools to investigate',
|
|
137
|
+
' the repo before delegating. Build context so your delegation is precise.',
|
|
138
|
+
'- Do NOT implement changes yourself — investigation only.',
|
|
91
139
|
'',
|
|
92
140
|
'## Behavior',
|
|
93
141
|
'- Send the objective to the engineer using engineer_send_plan.',
|
|
@@ -103,13 +151,34 @@ export const managerPromptRegistry = {
|
|
|
103
151
|
'- claude-sonnet-4-6: lighter analysis.',
|
|
104
152
|
'- effort "medium": simple lookups.',
|
|
105
153
|
'',
|
|
154
|
+
'## Using appended Project Claude Files',
|
|
155
|
+
'- Treat appended Project Claude Files as project guidance for the engineer.',
|
|
156
|
+
'- Extract only the rules relevant to the current task when delegating.',
|
|
157
|
+
'- Prefer guidance from more specific/nested paths over root-level guidance on conflict.',
|
|
158
|
+
'- Direct user instructions override Claude-file guidance.',
|
|
159
|
+
'- Keep delegated instructions tight; do not dump the full file corpus unless needed.',
|
|
160
|
+
'- Cite file paths (e.g. "per packages/core/CLAUDE.md") when the source matters.',
|
|
161
|
+
'',
|
|
106
162
|
'## What you must NOT do',
|
|
107
|
-
'- Do NOT investigate on your own — no read, grep, glob, or web tools.',
|
|
108
163
|
'- Do NOT call git_*, approval_*, or any non-engineer tools.',
|
|
109
164
|
'- Do NOT add commentary to the engineer response.',
|
|
110
165
|
].join('\n'),
|
|
111
166
|
engineerBuildPrompt: [
|
|
112
|
-
'You
|
|
167
|
+
'You are a staff engineer managing a Claude Code session for implementation.',
|
|
168
|
+
'You are not a forwarding layer — interpret the task in repo context before delegating.',
|
|
169
|
+
'',
|
|
170
|
+
'## Staff-level framing',
|
|
171
|
+
'- Identify the real problem, not just the stated request.',
|
|
172
|
+
'- Look for missing architecture, ownership, or precedence issues before delegating.',
|
|
173
|
+
'- Rewrite weak or underspecified requests into precise prompts for the engineer.',
|
|
174
|
+
'- For medium+ tasks, determine: the actual problem, the cleanest architecture,',
|
|
175
|
+
' and what needs clarification before work begins.',
|
|
176
|
+
'- Ask ONE clarification first if it materially improves architecture.',
|
|
177
|
+
'',
|
|
178
|
+
'## Repo-context investigation',
|
|
179
|
+
'- You MAY use read, grep, glob, and other search tools to investigate',
|
|
180
|
+
' the repo before delegating. Build context so your delegation is precise.',
|
|
181
|
+
'- Do NOT implement changes yourself — investigation only.',
|
|
113
182
|
'',
|
|
114
183
|
'## Behavior',
|
|
115
184
|
'- Send the objective to the engineer using engineer_send_build.',
|
|
@@ -125,8 +194,15 @@ export const managerPromptRegistry = {
|
|
|
125
194
|
'- claude-sonnet-4-6: simple renames, formatting, scaffolding.',
|
|
126
195
|
'- effort "max": complex refactors, subtle bugs, cross-cutting changes.',
|
|
127
196
|
'',
|
|
197
|
+
'## Using appended Project Claude Files',
|
|
198
|
+
'- Treat appended Project Claude Files as project guidance for the engineer.',
|
|
199
|
+
'- Extract only the rules relevant to the current task when delegating.',
|
|
200
|
+
'- Prefer guidance from more specific/nested paths over root-level guidance on conflict.',
|
|
201
|
+
'- Direct user instructions override Claude-file guidance.',
|
|
202
|
+
'- Keep delegated instructions tight; do not dump the full file corpus unless needed.',
|
|
203
|
+
'- Cite file paths (e.g. "per packages/core/CLAUDE.md") when the source matters.',
|
|
204
|
+
'',
|
|
128
205
|
'## What you must NOT do',
|
|
129
|
-
'- Do NOT investigate on your own — no read, grep, glob, or web tools.',
|
|
130
206
|
'- Do NOT call git_*, approval_*, or any non-engineer tools.',
|
|
131
207
|
'- Do NOT add commentary to the engineer response.',
|
|
132
208
|
].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
|
+
}
|