@doingdev/opencode-claude-manager-plugin 0.1.31 → 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.
@@ -495,13 +495,21 @@ function extractUsageFromResult(message) {
495
495
  result.outputTokens = usage.output_tokens;
496
496
  }
497
497
  }
498
- // Extract contextWindow from modelUsage
499
- const modelUsage = message.model_usage;
498
+ // Extract contextWindow from modelUsage (camelCase) or model_usage (snake_case)
499
+ const modelUsage = message.modelUsage ??
500
+ message.model_usage;
500
501
  if (isRecord(modelUsage)) {
501
502
  for (const model of Object.values(modelUsage)) {
502
- if (isRecord(model) && typeof model.context_window === 'number') {
503
- result.contextWindowSize = model.context_window;
504
- break;
503
+ if (isRecord(model)) {
504
+ const cw = typeof model.contextWindow === 'number'
505
+ ? model.contextWindow
506
+ : typeof model.context_window === 'number'
507
+ ? model.context_window
508
+ : undefined;
509
+ if (cw !== undefined) {
510
+ result.contextWindowSize = cw;
511
+ break;
512
+ }
505
513
  }
506
514
  }
507
515
  }
@@ -27,6 +27,9 @@ export declare class PersistentManager {
27
27
  finalText: string;
28
28
  turns?: number;
29
29
  totalCostUsd?: number;
30
+ inputTokens?: number;
31
+ outputTokens?: number;
32
+ contextWindowSize?: number;
30
33
  context: SessionContextSnapshot;
31
34
  }>;
32
35
  /**
@@ -26,6 +26,9 @@ export class PersistentManager {
26
26
  finalText: result.finalText,
27
27
  turns: result.turns,
28
28
  totalCostUsd: result.totalCostUsd,
29
+ inputTokens: result.inputTokens,
30
+ outputTokens: result.outputTokens,
31
+ contextWindowSize: result.contextWindowSize,
29
32
  context: this.sessionController.getContextSnapshot(),
30
33
  };
31
34
  }
@@ -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
  };
@@ -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 }) => {
@@ -18,8 +19,8 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
18
19
  prompt: promptPreview,
19
20
  },
20
21
  });
21
- let turnsSoFar = 0;
22
- let costSoFar = 0;
22
+ let turnsSoFar;
23
+ let costSoFar;
23
24
  const result = await services.manager.sendMessage(cwd, args.message, {
24
25
  model: args.model,
25
26
  effort: args.effort,
@@ -32,7 +33,7 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
32
33
  if (event.totalCostUsd !== undefined) {
33
34
  costSoFar = event.totalCostUsd;
34
35
  }
35
- const costLabel = `$${costSoFar.toFixed(4)}`;
36
+ const usageSuffix = formatLiveUsage(turnsSoFar, costSoFar);
36
37
  if (event.type === 'tool_call') {
37
38
  let toolName = 'tool';
38
39
  let inputPreview = '';
@@ -48,7 +49,7 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
48
49
  // ignore parse errors
49
50
  }
50
51
  context.metadata({
51
- title: `Claude Code: Running ${toolName}... (${turnsSoFar} turns, ${costLabel})`,
52
+ title: `Claude Code: Running ${toolName}...${usageSuffix}`,
52
53
  metadata: {
53
54
  sessionId: event.sessionId,
54
55
  type: event.type,
@@ -60,7 +61,7 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
60
61
  else if (event.type === 'assistant') {
61
62
  const thinkingPreview = event.text.length > 150 ? event.text.slice(0, 150) + '...' : event.text;
62
63
  context.metadata({
63
- title: `Claude Code: Thinking... (${turnsSoFar} turns, ${costLabel})`,
64
+ title: `Claude Code: Thinking...${usageSuffix}`,
64
65
  metadata: {
65
66
  sessionId: event.sessionId,
66
67
  type: event.type,
@@ -80,7 +81,7 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
80
81
  else if (event.type === 'user') {
81
82
  const preview = event.text.length > 200 ? event.text.slice(0, 200) + '...' : event.text;
82
83
  context.metadata({
83
- title: `Claude Code: Tool result (${turnsSoFar} turns, ${costLabel})`,
84
+ title: `Claude Code: Tool result${usageSuffix}`,
84
85
  metadata: {
85
86
  sessionId: event.sessionId,
86
87
  type: event.type,
@@ -100,7 +101,7 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
100
101
  // ignore
101
102
  }
102
103
  context.metadata({
103
- title: `Claude Code: ${toolName} running ${elapsed > 0 ? `(${elapsed.toFixed(0)}s)` : ''}... (${turnsSoFar} turns, ${costLabel})`,
104
+ title: `Claude Code: ${toolName} running ${elapsed > 0 ? `(${elapsed.toFixed(0)}s)` : ''}...${usageSuffix}`,
104
105
  metadata: {
105
106
  sessionId: event.sessionId,
106
107
  type: event.type,
@@ -112,7 +113,7 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
112
113
  else if (event.type === 'tool_summary') {
113
114
  const summary = event.text.length > 200 ? event.text.slice(0, 200) + '...' : event.text;
114
115
  context.metadata({
115
- title: `Claude Code: Tool done (${turnsSoFar} turns, ${costLabel})`,
116
+ title: `Claude Code: Tool done${usageSuffix}`,
116
117
  metadata: {
117
118
  sessionId: event.sessionId,
118
119
  type: event.type,
@@ -123,7 +124,7 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
123
124
  else if (event.type === 'partial') {
124
125
  const delta = event.text.length > 200 ? event.text.slice(0, 200) + '...' : event.text;
125
126
  context.metadata({
126
- title: `Claude Code: Writing... (${turnsSoFar} turns, ${costLabel})`,
127
+ title: `Claude Code: Writing...${usageSuffix}`,
127
128
  metadata: {
128
129
  sessionId: event.sessionId,
129
130
  type: event.type,
@@ -170,6 +171,9 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
170
171
  finalText: result.finalText,
171
172
  turns: result.turns,
172
173
  totalCostUsd: result.totalCostUsd,
174
+ inputTokens: result.inputTokens,
175
+ outputTokens: result.outputTokens,
176
+ contextWindowSize: result.contextWindowSize,
173
177
  context: result.context,
174
178
  contextWarning,
175
179
  toolOutputs: toolOutputs.length > 0 ? toolOutputs : undefined,
@@ -180,9 +184,16 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
180
184
  config.agent ??= {};
181
185
  config.permission ??= {};
182
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
+ };
183
194
  config.agent[AGENT_CTO] ??= buildCtoAgentConfig(managerPromptRegistry);
184
- config.agent[AGENT_ENGINEER_PLAN] ??= buildEngineerPlanAgentConfig(managerPromptRegistry);
185
- config.agent[AGENT_ENGINEER_BUILD] ??= buildEngineerBuildAgentConfig(managerPromptRegistry);
195
+ config.agent[AGENT_ENGINEER_PLAN] ??= buildEngineerPlanAgentConfig(derivedPrompts);
196
+ config.agent[AGENT_ENGINEER_BUILD] ??= buildEngineerBuildAgentConfig(derivedPrompts);
186
197
  },
187
198
  tool: {
188
199
  engineer_send: tool({
@@ -472,6 +483,19 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
472
483
  function annotateToolRun(context, title, metadata) {
473
484
  context.metadata({ title, metadata });
474
485
  }
486
+ function formatLiveUsage(turns, cost) {
487
+ if (turns === undefined && cost === undefined) {
488
+ return '';
489
+ }
490
+ const parts = [];
491
+ if (turns !== undefined) {
492
+ parts.push(`${turns} turns`);
493
+ }
494
+ if (cost !== undefined) {
495
+ parts.push(`$${cost.toFixed(4)}`);
496
+ }
497
+ return ` (${parts.join(', ')})`;
498
+ }
475
499
  function formatContextWarning(context) {
476
500
  const { warningLevel, estimatedContextPercent, totalTurns, totalCostUsd } = context;
477
501
  if (warningLevel === 'ok' || estimatedContextPercent === null) {
@@ -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,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 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: 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, small feature, refactor one module):',
19
- ' Read the relevant code yourself. Understand the current state.',
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, architecture change, large refactor):',
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 specific question.',
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 manage a Claude Code engineer for read-only investigation.',
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 manage a Claude Code engineer for implementation.',
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doingdev/opencode-claude-manager-plugin",
3
- "version": "0.1.31",
3
+ "version": "0.1.33",
4
4
  "description": "OpenCode plugin that orchestrates Claude Code sessions.",
5
5
  "keywords": [
6
6
  "opencode",