@doingdev/opencode-claude-manager-plugin 0.1.44 → 0.1.47

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 (53) hide show
  1. package/README.md +29 -31
  2. package/dist/index.d.ts +1 -1
  3. package/dist/manager/persistent-manager.d.ts +3 -23
  4. package/dist/manager/persistent-manager.js +2 -95
  5. package/dist/manager/team-orchestrator.d.ts +50 -0
  6. package/dist/manager/team-orchestrator.js +360 -0
  7. package/dist/plugin/agent-hierarchy.d.ts +12 -34
  8. package/dist/plugin/agent-hierarchy.js +36 -129
  9. package/dist/plugin/claude-manager.plugin.js +190 -445
  10. package/dist/plugin/service-factory.d.ts +18 -3
  11. package/dist/plugin/service-factory.js +32 -1
  12. package/dist/prompts/registry.d.ts +1 -10
  13. package/dist/prompts/registry.js +42 -270
  14. package/dist/src/claude/claude-agent-sdk-adapter.js +2 -1
  15. package/dist/src/claude/session-live-tailer.js +2 -2
  16. package/dist/src/index.d.ts +1 -1
  17. package/dist/src/manager/git-operations.d.ts +10 -1
  18. package/dist/src/manager/git-operations.js +18 -3
  19. package/dist/src/manager/persistent-manager.d.ts +18 -6
  20. package/dist/src/manager/persistent-manager.js +19 -13
  21. package/dist/src/manager/session-controller.d.ts +7 -10
  22. package/dist/src/manager/session-controller.js +12 -62
  23. package/dist/src/manager/team-orchestrator.d.ts +50 -0
  24. package/dist/src/manager/team-orchestrator.js +360 -0
  25. package/dist/src/plugin/agent-hierarchy.d.ts +12 -26
  26. package/dist/src/plugin/agent-hierarchy.js +36 -99
  27. package/dist/src/plugin/claude-manager.plugin.js +214 -393
  28. package/dist/src/plugin/service-factory.d.ts +18 -3
  29. package/dist/src/plugin/service-factory.js +33 -9
  30. package/dist/src/prompts/registry.d.ts +1 -10
  31. package/dist/src/prompts/registry.js +41 -246
  32. package/dist/src/state/team-state-store.d.ts +14 -0
  33. package/dist/src/state/team-state-store.js +85 -0
  34. package/dist/src/team/roster.d.ts +5 -0
  35. package/dist/src/team/roster.js +38 -0
  36. package/dist/src/types/contracts.d.ts +55 -13
  37. package/dist/src/types/contracts.js +1 -1
  38. package/dist/state/team-state-store.d.ts +14 -0
  39. package/dist/state/team-state-store.js +85 -0
  40. package/dist/team/roster.d.ts +5 -0
  41. package/dist/team/roster.js +38 -0
  42. package/dist/test/claude-manager.plugin.test.js +55 -280
  43. package/dist/test/git-operations.test.js +65 -1
  44. package/dist/test/persistent-manager.test.js +3 -3
  45. package/dist/test/prompt-registry.test.js +32 -252
  46. package/dist/test/session-controller.test.js +27 -27
  47. package/dist/test/team-orchestrator.test.d.ts +1 -0
  48. package/dist/test/team-orchestrator.test.js +146 -0
  49. package/dist/test/team-state-store.test.d.ts +1 -0
  50. package/dist/test/team-state-store.test.js +54 -0
  51. package/dist/types/contracts.d.ts +50 -23
  52. package/dist/types/contracts.js +1 -1
  53. package/package.json +1 -1
package/README.md CHANGED
@@ -4,17 +4,20 @@ This package provides an OpenCode plugin that lets an OpenCode-side agent hierar
4
4
 
5
5
  ## Overview
6
6
 
7
- Use this when you want OpenCode to act as a manager over Claude Code instead of talking to Claude directly. The plugin gives OpenCode a stable tool surface for delegating work to Claude Code sessions, managing session lifecycle (compact, clear, fresh start), reviewing changes via git, and inspecting session history.
7
+ Use this when you want OpenCode to act like a real technical lead over Claude Code instead of being a thin relay. The plugin gives you a CTO agent that can ask better questions, explicitly assign named engineers, reuse each engineer's Claude session for continuity, preserve wrapper-level engineer memory, compare multiple plans, and keep git/review work at the manager layer.
8
8
 
9
9
  ## Features
10
10
 
11
11
  - Runs Claude Code tasks from OpenCode through `@anthropic-ai/claude-agent-sdk`.
12
- - Persistent sessions with `freshSession`, model, and effort controls for safe task isolation.
13
- - Context lifecycle: compact (preserve state) or clear (start fresh).
12
+ - Creates a persistent named team: `Tom`, `John`, `Maya`, `Sara`, and `Alex`.
13
+ - Reuses one Claude Code session per engineer within the active CTO team.
14
+ - Reloads prior engineer wrapper context so each named subagent can prompt Claude better over time.
15
+ - Uses named engineer subagents for live delegated work while keeping both wrapper memory and Claude session continuity underneath.
16
+ - Keeps session babysitting out of the normal user flow — no public reset/fresh-session controls.
14
17
  - Discovers repo-local Claude metadata from `.claude/skills`, `.claude/commands`, `CLAUDE.md`, and settings hooks.
15
18
  - Git integration: diff, commit, and reset from the manager layer.
16
19
  - Tool approval policy for governing which Claude Code tools are allowed.
17
- - Optionally persists manager run records under `.claude-manager/runs` for post-hoc inspection (populated when tasks are executed through the run-tracking path).
20
+ - Persists local team state and transcripts under `.claude-manager/` for continuity and inspection.
18
21
 
19
22
  ## Requirements
20
23
 
@@ -51,17 +54,17 @@ If you are testing locally, point OpenCode at the local package or plugin file u
51
54
 
52
55
  ## OpenCode tools
53
56
 
54
- ### Engineer session
57
+ ### CTO orchestration
55
58
 
56
- - `explore` investigate and analyze code without making edits. Read-only exploration of the codebase. Preferred first step before implementation.
57
- - `message` (required) — the instruction to send.
58
- - `freshSession` — set to `true` to clear the active session before sending. Use when switching to an unrelated task or when context is contaminated.
59
- - `model` — `"claude-opus-4-6"` (default, recommended for most coding work), `"claude-sonnet-4-6"`, or `"claude-sonnet-4-5"` (faster/lighter tasks).
60
- - `effort` — `"high"` (default), `"medium"` (lighter tasks), `"low"`, or `"max"` (especially hard problems).
61
- - `implement` — implement code changes; can read, edit, and create files. Use after exploration to make changes. Same args as `explore`.
62
- - `compact_context` — compress session history to reclaim context window space. Preserves state while reducing token usage.
63
- - `clear_session` — clear the active session to start fresh. Use when context is full or starting a new task.
64
- - `session_health` — check session health metrics: context usage %, turn count, cost, and session ID.
59
+ - Use the built-in OpenCode `task` tool to delegate to named engineers: `tom`, `john`, `maya`, `sara`, `alex`.
60
+ - `team_status` — inspect the current CTO team's engineer bindings, Claude session IDs, busy flags, and context snapshots.
61
+
62
+ ### Engineer bridge
63
+
64
+ - `claude` — available only inside named engineer subagents. Sends work through that engineer's persistent Claude Code session.
65
+ - `mode` (required) `explore`, `implement`, or `verify`.
66
+ - `message` (required) — the work to do.
67
+ - `model` (optional) `claude-opus-4-6` or `claude-sonnet-4-6`.
65
68
 
66
69
  ### Git operations
67
70
 
@@ -72,7 +75,7 @@ If you are testing locally, point OpenCode at the local package or plugin file u
72
75
  ### Inspection
73
76
 
74
77
  - `list_transcripts` — list available session transcripts or inspect a specific transcript by ID.
75
- - `list_history` — list persistent run records from the manager or inspect a specific run.
78
+ - `list_history` — list saved CTO teams for the worktree or inspect one team by ID.
76
79
 
77
80
  ### Tool approval
78
81
 
@@ -82,11 +85,11 @@ If you are testing locally, point OpenCode at the local package or plugin file u
82
85
 
83
86
  ## Agent hierarchy
84
87
 
85
- The plugin registers a CTO Manager Engineer hierarchy through the OpenCode plugin `config` hook:
88
+ The plugin registers a CTO + named engineer team through the OpenCode plugin `config` hook:
86
89
 
87
- - **`cto`** (primary agent) — sets direction and orchestrates work by spawning `manager` subagents. Has read/search/web tools but does NOT operate Claude Code directly.
88
- - **`manager`** (subagent) — operates a Claude Code engineer through a persistent session. Has the full tool surface (`explore`, `implement`, `compact_context`, `clear_session`, `session_health`, `list_transcripts`, `list_history`, `git_*`, `approval_*`) plus read/search/web tools for investigation.
89
- - **Engineer** — the Claude Code persistent session itself (not an OpenCode agent). Receives instructions from the manager, executes code changes, and reports results.
90
+ - **`cto`** (primary agent) — owns the outcome, finds missing requirements, spawns named engineers with the Task tool, compares plans, reviews diffs, and manages git.
91
+ - **`tom`**, **`john`**, **`maya`**, **`sara`**, **`alex`** (subagents) — thin named engineer wrappers. Each uses the `claude` tool and keeps one persistent Claude Code session.
92
+ - **Claude Code sessions** the underlying execution layer. One session per engineer inside the active team.
90
93
 
91
94
  These are added to OpenCode config at runtime by the plugin, so they do not require separate manual `opencode.json` entries.
92
95
 
@@ -94,27 +97,21 @@ These are added to OpenCode config at runtime by the plugin, so they do not requ
94
97
 
95
98
  Typical flow inside OpenCode:
96
99
 
97
- 1. Explore the codebase with `explore`.
98
- 2. Implement changes with `implement`.
100
+ 1. Ask the `cto` agent for the work.
101
+ 2. Let `cto` spawn one or more named engineers with the Task tool.
99
102
  3. Review changes with `git_diff`, then commit or reset.
100
- 4. Inspect saved Claude history with `list_transcripts` or prior orchestration records with `list_history`.
103
+ 4. Inspect saved Claude history with `list_transcripts` or saved team state with `list_history` / `team_status`.
101
104
 
102
105
  Example tasks:
103
106
 
104
107
  ```text
105
- Use implement to add the new validation logic in src/auth.ts, then review with git_diff.
106
- ```
107
-
108
- Start a fresh session for an unrelated task:
109
-
110
- ```text
111
- Use explore with freshSession:true to investigate the failing CI test in test/api.test.ts.
108
+ Ask CTO to send Tom to implement the new validation logic in src/auth.ts, then review with git_diff.
112
109
  ```
113
110
 
114
- Reclaim context mid-session:
111
+ For a larger feature where you want two independent plans first:
115
112
 
116
113
  ```text
117
- Use compact_context to free up context, then continue with the next implementation step.
114
+ Ask CTO to spawn Maya and Alex in parallel to create two plans for the new billing feature, then synthesize the best combined plan.
118
115
  ```
119
116
 
120
117
  ## Local Development
@@ -176,6 +173,7 @@ After trusted publishing is working, you can tighten npm package security by dis
176
173
 
177
174
  - Claude slash commands and skills come primarily from filesystem discovery; SDK probing is available but optional.
178
175
  - Session state is local to the repo under `.claude-manager/` and is ignored by git.
176
+ - The strongest team continuity comes when engineers are spawned from the active `cto` session; the plugin maps named engineers back to that active team automatically.
179
177
  - Context tracking is heuristic-based; actual SDK context usage may differ slightly.
180
178
 
181
179
  ## Scripts
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { Plugin } from '@opencode-ai/plugin';
2
2
  import { ClaudeManagerPlugin } from './plugin/claude-manager.plugin.js';
3
- export type { ClaudeCapabilitySnapshot, ClaudeSessionRunResult, ClaudeSessionSummary, ClaudeSessionTranscriptMessage, ManagerPromptRegistry, RunClaudeSessionInput, SessionContextSnapshot, GitDiffResult, GitOperationResult, PersistentRunRecord, PersistentRunResult, AgentSlotType, RunningTask, SlotStatus, ManagerStatus, ContextWarningLevel, SessionMode, LiveTailEvent, ToolOutputPreview, ToolApprovalRule, ToolApprovalPolicy, ToolApprovalDecision, } from './types/contracts.js';
3
+ export type { ClaudeCapabilitySnapshot, ClaudeSessionRunResult, ClaudeSessionSummary, ClaudeSessionTranscriptMessage, ManagerPromptRegistry, RunClaudeSessionInput, SessionContextSnapshot, GitDiffResult, GitOperationResult, PersistentRunRecord, PersistentRunResult, ContextWarningLevel, SessionMode, EngineerName, EngineerWorkMode, WrapperHistoryEntry, TeamEngineerRecord, TeamRecord, EngineerTaskResult, PlanDraft, SynthesizedPlanResult, LiveTailEvent, ToolOutputPreview, ToolApprovalRule, ToolApprovalPolicy, ToolApprovalDecision, } from './types/contracts.js';
4
4
  export { SessionLiveTailer } from './claude/session-live-tailer.js';
5
5
  export { ClaudeManagerPlugin };
6
6
  export declare const plugin: Plugin;
@@ -1,4 +1,4 @@
1
- import type { AgentSlotType, ClaudeSessionEvent, ClaudeSessionRunResult, GitDiffResult, GitOperationResult, ManagerStatus, PersistentRunRecord, PersistentRunResult, RunningTask, SessionContextSnapshot, SlotStatus } from '../types/contracts.js';
1
+ import type { ClaudeSessionEvent, ClaudeSessionRunResult, PersistentRunRecord, PersistentRunResult, SessionContextSnapshot, GitDiffResult, GitOperationResult } from '../types/contracts.js';
2
2
  import type { ClaudeSessionEventHandler } from '../claude/claude-agent-sdk-adapter.js';
3
3
  import type { FileRunStateStore } from '../state/file-run-state-store.js';
4
4
  import type { TranscriptStore } from '../state/transcript-store.js';
@@ -12,10 +12,6 @@ export declare class PersistentManager {
12
12
  private readonly stateStore;
13
13
  private readonly contextTracker;
14
14
  private readonly transcriptStore;
15
- private static readonly slotCounts;
16
- private static readonly slotWaiters;
17
- private static readonly runningTaskList;
18
- private static readonly SLOT_MAXIMA;
19
15
  constructor(sessionController: SessionController, gitOps: GitOperations, stateStore: FileRunStateStore, contextTracker: ContextTracker, transcriptStore: TranscriptStore);
20
16
  /**
21
17
  * Send a message to the persistent Claude Code session.
@@ -65,25 +61,9 @@ export declare class PersistentManager {
65
61
  */
66
62
  gitReset(): Promise<GitOperationResult>;
67
63
  /**
68
- * Get current session status, context health, and slot usage.
64
+ * Get current session status and context health.
69
65
  */
70
- getStatus(): ManagerStatus;
71
- /**
72
- * Acquire a concurrency slot. Blocks if at capacity (implement=1, verify=5, explore=unlimited).
73
- */
74
- acquireSlot(slotType: AgentSlotType, taskDescription?: string): Promise<void>;
75
- /**
76
- * Release a concurrency slot. Wakes the next waiter if any.
77
- */
78
- releaseSlot(slotType: AgentSlotType, taskDescription?: string): void;
79
- /**
80
- * Get current slot usage across all agent types.
81
- */
82
- getSlotStatus(): SlotStatus;
83
- /**
84
- * List all currently running tasks with their slot types.
85
- */
86
- listRunningTasks(): RunningTask[];
66
+ getStatus(): SessionContextSnapshot;
87
67
  /**
88
68
  * Clear the active session. Next send creates a fresh one.
89
69
  */
@@ -5,19 +5,6 @@ export class PersistentManager {
5
5
  stateStore;
6
6
  contextTracker;
7
7
  transcriptStore;
8
- // --- Slot tracking (static — shared across all instances) ---
9
- static slotCounts = new Map([
10
- ['implement', 0],
11
- ['verify', 0],
12
- ['explore', 0],
13
- ]);
14
- static slotWaiters = new Map();
15
- static runningTaskList = [];
16
- static SLOT_MAXIMA = {
17
- implement: 1,
18
- verify: 5,
19
- explore: Infinity,
20
- };
21
8
  constructor(sessionController, gitOps, stateStore, contextTracker, transcriptStore) {
22
9
  this.sessionController = sessionController;
23
10
  this.gitOps = gitOps;
@@ -76,90 +63,10 @@ export class PersistentManager {
76
63
  return this.gitOps.resetHard();
77
64
  }
78
65
  /**
79
- * Get current session status, context health, and slot usage.
66
+ * Get current session status and context health.
80
67
  */
81
68
  getStatus() {
82
- return {
83
- context: this.sessionController.getContextSnapshot(),
84
- slotStatus: this.getSlotStatus(),
85
- runningTasks: this.listRunningTasks(),
86
- };
87
- }
88
- /**
89
- * Acquire a concurrency slot. Blocks if at capacity (implement=1, verify=5, explore=unlimited).
90
- */
91
- async acquireSlot(slotType, taskDescription) {
92
- const max = PersistentManager.SLOT_MAXIMA[slotType];
93
- const current = PersistentManager.slotCounts.get(slotType) ?? 0;
94
- if (current < max) {
95
- PersistentManager.slotCounts.set(slotType, current + 1);
96
- }
97
- else {
98
- // At capacity — wait for a slot to be transferred by releaseSlot
99
- await new Promise((resolve) => {
100
- let waiters = PersistentManager.slotWaiters.get(slotType);
101
- if (!waiters) {
102
- waiters = [];
103
- PersistentManager.slotWaiters.set(slotType, waiters);
104
- }
105
- waiters.push(resolve);
106
- });
107
- // Slot was transferred — count stays the same
108
- }
109
- if (taskDescription) {
110
- PersistentManager.runningTaskList.push({
111
- slotType,
112
- task: taskDescription,
113
- startedAt: new Date().toISOString(),
114
- });
115
- }
116
- }
117
- /**
118
- * Release a concurrency slot. Wakes the next waiter if any.
119
- */
120
- releaseSlot(slotType, taskDescription) {
121
- if (taskDescription) {
122
- const idx = PersistentManager.runningTaskList.findIndex((t) => t.slotType === slotType && t.task === taskDescription);
123
- if (idx !== -1) {
124
- PersistentManager.runningTaskList.splice(idx, 1);
125
- }
126
- }
127
- const waiters = PersistentManager.slotWaiters.get(slotType);
128
- if (waiters && waiters.length > 0) {
129
- // Transfer the slot directly to the next waiter (don't decrement count)
130
- const next = waiters.shift();
131
- next();
132
- }
133
- else {
134
- const current = PersistentManager.slotCounts.get(slotType) ?? 0;
135
- if (current > 0) {
136
- PersistentManager.slotCounts.set(slotType, current - 1);
137
- }
138
- }
139
- }
140
- /**
141
- * Get current slot usage across all agent types.
142
- */
143
- getSlotStatus() {
144
- return {
145
- implement: {
146
- used: PersistentManager.slotCounts.get('implement') ?? 0,
147
- max: PersistentManager.SLOT_MAXIMA.implement,
148
- },
149
- verify: {
150
- used: PersistentManager.slotCounts.get('verify') ?? 0,
151
- max: PersistentManager.SLOT_MAXIMA.verify,
152
- },
153
- explore: {
154
- used: PersistentManager.slotCounts.get('explore') ?? 0,
155
- },
156
- };
157
- }
158
- /**
159
- * List all currently running tasks with their slot types.
160
- */
161
- listRunningTasks() {
162
- return [...PersistentManager.runningTaskList];
69
+ return this.sessionController.getContextSnapshot();
163
70
  }
164
71
  /**
165
72
  * Clear the active session. Next send creates a fresh one.
@@ -0,0 +1,50 @@
1
+ import type { ClaudeSessionEventHandler } from '../claude/claude-agent-sdk-adapter.js';
2
+ import type { ClaudeSessionService } from '../claude/claude-session.service.js';
3
+ import type { TeamStateStore } from '../state/team-state-store.js';
4
+ import type { TranscriptStore } from '../state/transcript-store.js';
5
+ import type { DiscoveredClaudeFile, EngineerName, EngineerTaskResult, EngineerWorkMode, SynthesizedPlanResult, TeamRecord } from '../types/contracts.js';
6
+ interface DispatchEngineerInput {
7
+ teamId: string;
8
+ cwd: string;
9
+ engineer: EngineerName;
10
+ mode: EngineerWorkMode;
11
+ message: string;
12
+ model?: string;
13
+ abortSignal?: AbortSignal;
14
+ onEvent?: ClaudeSessionEventHandler;
15
+ }
16
+ export declare class TeamOrchestrator {
17
+ private readonly sessions;
18
+ private readonly teamStore;
19
+ private readonly transcriptStore;
20
+ private readonly engineerSessionPrompt;
21
+ private readonly projectClaudeFiles;
22
+ constructor(sessions: ClaudeSessionService, teamStore: TeamStateStore, transcriptStore: TranscriptStore, engineerSessionPrompt: string, projectClaudeFiles: DiscoveredClaudeFile[]);
23
+ getOrCreateTeam(cwd: string, teamId: string): Promise<TeamRecord>;
24
+ listTeams(cwd: string): Promise<TeamRecord[]>;
25
+ recordWrapperSession(cwd: string, teamId: string, engineer: EngineerName, wrapperSessionId: string): Promise<void>;
26
+ recordWrapperExchange(cwd: string, teamId: string, engineer: EngineerName, wrapperSessionId: string, mode: EngineerWorkMode, assignment: string, result: string): Promise<void>;
27
+ getWrapperSystemContext(cwd: string, teamId: string, engineer: EngineerName): Promise<string | null>;
28
+ findTeamByWrapperSession(cwd: string, wrapperSessionId: string): Promise<{
29
+ teamId: string;
30
+ engineer: EngineerName;
31
+ } | null>;
32
+ dispatchEngineer(input: DispatchEngineerInput): Promise<EngineerTaskResult>;
33
+ planWithTeam(input: {
34
+ teamId: string;
35
+ cwd: string;
36
+ request: string;
37
+ leadEngineer: EngineerName;
38
+ challengerEngineer: EngineerName;
39
+ model?: string;
40
+ abortSignal?: AbortSignal;
41
+ }): Promise<SynthesizedPlanResult>;
42
+ private updateEngineer;
43
+ private reserveEngineer;
44
+ private getEngineerState;
45
+ private normalizeTeamRecord;
46
+ private buildSessionSystemPrompt;
47
+ private buildEngineerPrompt;
48
+ private mapWorkModeToSessionMode;
49
+ }
50
+ export {};