@doingdev/opencode-claude-manager-plugin 0.1.56 → 0.1.58

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/dist/manager/team-orchestrator.d.ts +13 -5
  2. package/dist/manager/team-orchestrator.js +134 -15
  3. package/dist/plugin/agent-hierarchy.d.ts +1 -54
  4. package/dist/plugin/agent-hierarchy.js +2 -123
  5. package/dist/plugin/agents/browser-qa.d.ts +14 -0
  6. package/dist/plugin/agents/browser-qa.js +27 -0
  7. package/dist/plugin/agents/common.d.ts +37 -0
  8. package/dist/plugin/agents/common.js +59 -0
  9. package/dist/plugin/agents/cto.d.ts +9 -0
  10. package/dist/plugin/agents/cto.js +39 -0
  11. package/dist/plugin/agents/engineers.d.ts +9 -0
  12. package/dist/plugin/agents/engineers.js +11 -0
  13. package/dist/plugin/agents/index.d.ts +6 -0
  14. package/dist/plugin/agents/index.js +5 -0
  15. package/dist/plugin/agents/team-planner.d.ts +10 -0
  16. package/dist/plugin/agents/team-planner.js +23 -0
  17. package/dist/plugin/claude-manager.plugin.js +68 -32
  18. package/dist/plugin/service-factory.d.ts +4 -3
  19. package/dist/plugin/service-factory.js +4 -1
  20. package/dist/prompts/registry.js +142 -57
  21. package/dist/src/manager/team-orchestrator.d.ts +13 -5
  22. package/dist/src/manager/team-orchestrator.js +134 -15
  23. package/dist/src/plugin/agent-hierarchy.d.ts +1 -54
  24. package/dist/src/plugin/agent-hierarchy.js +2 -123
  25. package/dist/src/plugin/agents/browser-qa.d.ts +14 -0
  26. package/dist/src/plugin/agents/browser-qa.js +27 -0
  27. package/dist/src/plugin/agents/common.d.ts +37 -0
  28. package/dist/src/plugin/agents/common.js +59 -0
  29. package/dist/src/plugin/agents/cto.d.ts +9 -0
  30. package/dist/src/plugin/agents/cto.js +39 -0
  31. package/dist/src/plugin/agents/engineers.d.ts +9 -0
  32. package/dist/src/plugin/agents/engineers.js +11 -0
  33. package/dist/src/plugin/agents/index.d.ts +6 -0
  34. package/dist/src/plugin/agents/index.js +5 -0
  35. package/dist/src/plugin/agents/team-planner.d.ts +10 -0
  36. package/dist/src/plugin/agents/team-planner.js +23 -0
  37. package/dist/src/plugin/claude-manager.plugin.js +68 -32
  38. package/dist/src/plugin/service-factory.d.ts +4 -3
  39. package/dist/src/plugin/service-factory.js +4 -1
  40. package/dist/src/prompts/registry.js +142 -57
  41. package/dist/src/team/roster.d.ts +3 -2
  42. package/dist/src/team/roster.js +2 -1
  43. package/dist/src/types/contracts.d.ts +26 -2
  44. package/dist/src/types/contracts.js +2 -1
  45. package/dist/team/roster.d.ts +3 -2
  46. package/dist/team/roster.js +2 -1
  47. package/dist/test/claude-manager.plugin.test.js +70 -0
  48. package/dist/test/prompt-registry.test.js +31 -6
  49. package/dist/test/report-claude-event.test.js +57 -3
  50. package/dist/test/team-orchestrator.test.js +155 -5
  51. package/dist/types/contracts.d.ts +26 -2
  52. package/dist/types/contracts.js +2 -1
  53. package/package.json +1 -1
@@ -0,0 +1,11 @@
1
+ import { buildEngineerPermissions } from './common.js';
2
+ export function buildEngineerAgentConfig(prompts, engineer) {
3
+ return {
4
+ description: `${engineer} is a persistent engineer who works through one Claude Code session and remembers prior turns. Receives structured assignments (goal, mode, context, acceptance criteria, relevant paths, constraints, verification).`,
5
+ mode: 'subagent',
6
+ hidden: false,
7
+ color: '#D97757',
8
+ permission: buildEngineerPermissions(),
9
+ prompt: `You are ${engineer}.\n\n${prompts.engineerAgentPrompt}`,
10
+ };
11
+ }
@@ -0,0 +1,6 @@
1
+ export { AGENT_BROWSER_QA, AGENT_CTO, AGENT_TEAM_PLANNER, ALL_RESTRICTED_TOOL_IDS, buildEngineerPermissions, CTO_ONLY_TOOL_IDS, CTO_READONLY_TOOLS, denyRestrictedToolsGlobally, ENGINEER_AGENT_IDS, ENGINEER_AGENT_NAMES, ENGINEER_TOOL_IDS, } from './common.js';
2
+ export type { AgentPermission, ToolPermission } from './common.js';
3
+ export { buildCtoAgentConfig } from './cto.js';
4
+ export { buildTeamPlannerAgentConfig } from './team-planner.js';
5
+ export { buildEngineerAgentConfig } from './engineers.js';
6
+ export { buildBrowserQaAgentConfig, buildWorkerCapabilities } from './browser-qa.js';
@@ -0,0 +1,5 @@
1
+ export { AGENT_BROWSER_QA, AGENT_CTO, AGENT_TEAM_PLANNER, ALL_RESTRICTED_TOOL_IDS, buildEngineerPermissions, CTO_ONLY_TOOL_IDS, CTO_READONLY_TOOLS, denyRestrictedToolsGlobally, ENGINEER_AGENT_IDS, ENGINEER_AGENT_NAMES, ENGINEER_TOOL_IDS, } from './common.js';
2
+ export { buildCtoAgentConfig } from './cto.js';
3
+ export { buildTeamPlannerAgentConfig } from './team-planner.js';
4
+ export { buildEngineerAgentConfig } from './engineers.js';
5
+ export { buildBrowserQaAgentConfig, buildWorkerCapabilities } from './browser-qa.js';
@@ -0,0 +1,10 @@
1
+ import type { ManagerPromptRegistry } from '../../types/contracts.js';
2
+ import type { AgentPermission } from './common.js';
3
+ export declare function buildTeamPlannerAgentConfig(prompts: ManagerPromptRegistry): {
4
+ description: string;
5
+ mode: "subagent";
6
+ hidden: boolean;
7
+ color: string;
8
+ permission: AgentPermission;
9
+ prompt: string;
10
+ };
@@ -0,0 +1,23 @@
1
+ import { ALL_RESTRICTED_TOOL_IDS } from './common.js';
2
+ function buildTeamPlannerPermissions() {
3
+ const denied = {};
4
+ for (const toolId of ALL_RESTRICTED_TOOL_IDS) {
5
+ denied[toolId] = 'deny';
6
+ }
7
+ return {
8
+ '*': 'deny',
9
+ plan_with_team: 'allow',
10
+ question: 'allow',
11
+ ...denied,
12
+ };
13
+ }
14
+ export function buildTeamPlannerAgentConfig(prompts) {
15
+ return {
16
+ description: 'Runs dual-engineer planning by calling plan_with_team. Automatically selects two non-overlapping available engineers if engineer names are not provided.',
17
+ mode: 'subagent',
18
+ hidden: false,
19
+ color: '#D97757',
20
+ permission: buildTeamPlannerPermissions(),
21
+ prompt: prompts.teamPlannerPrompt,
22
+ };
23
+ }
@@ -1,8 +1,8 @@
1
1
  import { tool } from '@opencode-ai/plugin';
2
2
  import { managerPromptRegistry } from '../prompts/registry.js';
3
3
  import { isEngineerName } from '../team/roster.js';
4
- import { TeamOrchestrator } from '../manager/team-orchestrator.js';
5
- import { AGENT_CTO, AGENT_TEAM_PLANNER, ENGINEER_AGENT_IDS, ENGINEER_AGENT_NAMES, buildCtoAgentConfig, buildEngineerAgentConfig, buildTeamPlannerAgentConfig, denyRestrictedToolsGlobally, } from './agent-hierarchy.js';
4
+ import { TeamOrchestrator, createActionableError, getFailureGuidanceText, } from '../manager/team-orchestrator.js';
5
+ import { AGENT_BROWSER_QA, AGENT_CTO, AGENT_TEAM_PLANNER, buildBrowserQaAgentConfig, buildCtoAgentConfig, buildEngineerAgentConfig, buildTeamPlannerAgentConfig, denyRestrictedToolsGlobally, ENGINEER_AGENT_IDS, ENGINEER_AGENT_NAMES, } from './agents/index.js';
6
6
  import { getActiveTeamSession, getOrCreatePluginServices, getPersistedActiveTeam, getWrapperSessionMapping, setActiveTeamSession, setPersistedActiveTeam, setWrapperSessionMapping, } from './service-factory.js';
7
7
  const MODEL_ENUM = ['claude-opus-4-6', 'claude-sonnet-4-6'];
8
8
  const MODE_ENUM = ['explore', 'implement', 'verify'];
@@ -16,6 +16,7 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
16
16
  denyRestrictedToolsGlobally(config.permission);
17
17
  config.agent[AGENT_CTO] ??= buildCtoAgentConfig(managerPromptRegistry);
18
18
  config.agent[AGENT_TEAM_PLANNER] ??= buildTeamPlannerAgentConfig(managerPromptRegistry);
19
+ config.agent[AGENT_BROWSER_QA] ??= buildBrowserQaAgentConfig(managerPromptRegistry);
19
20
  for (const engineer of ENGINEER_AGENT_NAMES) {
20
21
  config.agent[ENGINEER_AGENT_IDS[engineer]] ??= buildEngineerAgentConfig(managerPromptRegistry, engineer);
21
22
  }
@@ -41,7 +42,7 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
41
42
  const teamId = persisted?.teamId ?? (await resolveTeamId(worktree, input.sessionID));
42
43
  setWrapperSessionMapping(worktree, input.sessionID, {
43
44
  teamId,
44
- engineer,
45
+ workerName: engineer,
45
46
  });
46
47
  await services.orchestrator.recordWrapperSession(worktree, teamId, engineer, input.sessionID);
47
48
  }
@@ -50,26 +51,37 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
50
51
  if (!input.sessionID) {
51
52
  return;
52
53
  }
53
- const existing = getWrapperSessionMapping(worktree, input.sessionID);
54
- const persisted = existing ??
55
- (await services.orchestrator.findTeamByWrapperSession(worktree, input.sessionID));
56
- if (!persisted) {
54
+ // Try in-memory mapping first
55
+ let mapping = getWrapperSessionMapping(worktree, input.sessionID);
56
+ // Fall back to persisted lookup if in-memory mapping is absent
57
+ if (!mapping) {
58
+ // Check if this is an engineer wrapper session
59
+ const engineerMatch = await services.orchestrator.findTeamByWrapperSession(worktree, input.sessionID);
60
+ if (engineerMatch) {
61
+ mapping = {
62
+ teamId: engineerMatch.teamId,
63
+ workerName: engineerMatch.engineer,
64
+ };
65
+ }
66
+ }
67
+ if (!mapping) {
57
68
  return;
58
69
  }
59
- const wrapperContext = await services.orchestrator.getWrapperSystemContext(worktree, persisted.teamId, persisted.engineer);
70
+ const wrapperContext = await services.orchestrator.getWrapperSystemContext(worktree, mapping.teamId, mapping.workerName);
60
71
  if (wrapperContext) {
61
72
  output.system.push(wrapperContext);
62
73
  }
63
74
  },
64
75
  tool: {
65
76
  claude: tool({
66
- description: "Run work through this named engineer's persistent Claude Code session. The session remembers prior turns for this engineer.",
77
+ description: "Run work through a named engineer's persistent Claude Code session. Engineers include general developers (Tom, John, Maya, Sara, Alex) and specialists like browser-qa. The session remembers prior turns.",
67
78
  args: {
68
79
  mode: tool.schema.enum(MODE_ENUM),
69
80
  message: tool.schema.string().min(1),
70
81
  model: tool.schema.enum(MODEL_ENUM).optional(),
71
82
  },
72
83
  async execute(args, context) {
84
+ // Handle engineer agents (includes BrowserQA)
73
85
  const engineer = engineerFromAgent(context.agent);
74
86
  const existing = getWrapperSessionMapping(context.worktree, context.sessionID);
75
87
  const persisted = existing ??
@@ -77,7 +89,7 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
77
89
  const teamId = persisted?.teamId ?? (await resolveTeamId(context.worktree, context.sessionID));
78
90
  setWrapperSessionMapping(context.worktree, context.sessionID, {
79
91
  teamId,
80
- engineer,
92
+ workerName: engineer,
81
93
  });
82
94
  await services.orchestrator.recordWrapperSession(context.worktree, teamId, engineer, context.sessionID);
83
95
  const result = await runEngineerAssignment({
@@ -87,6 +99,15 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
87
99
  message: args.message,
88
100
  model: args.model,
89
101
  }, context);
102
+ const capabilities = services.workerCapabilities[engineer];
103
+ if (capabilities?.isRuntimeUnavailableResponse?.(result.finalText)) {
104
+ const lines = result.finalText.split('\n');
105
+ const unavailableLine = lines[0] ?? 'Playwright unavailable (reason unknown)';
106
+ context.metadata({
107
+ title: capabilities.runtimeUnavailableTitle ?? '❌ Playwright unavailable',
108
+ metadata: { unavailable: unavailableLine },
109
+ });
110
+ }
90
111
  return result.finalText;
91
112
  },
92
113
  }),
@@ -105,30 +126,32 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
105
126
  },
106
127
  }),
107
128
  plan_with_team: tool({
108
- description: 'Run dual-engineer plan synthesis. Two engineers explore in parallel (lead + challenger), then their plans are synthesized into one stronger plan.',
129
+ description: 'Run dual-engineer plan synthesis. Two engineers explore in parallel (lead + challenger), then their plans are synthesized into one stronger plan. Automatically selects distinct available engineers if names are not provided.',
109
130
  args: {
110
131
  request: tool.schema.string().min(1),
111
- leadEngineer: tool.schema.enum(['Tom', 'John', 'Maya', 'Sara', 'Alex']),
112
- challengerEngineer: tool.schema.enum(['Tom', 'John', 'Maya', 'Sara', 'Alex']),
132
+ leadEngineer: tool.schema.enum(['Tom', 'John', 'Maya', 'Sara', 'Alex']).optional(),
133
+ challengerEngineer: tool.schema.enum(['Tom', 'John', 'Maya', 'Sara', 'Alex']).optional(),
113
134
  model: tool.schema.enum(MODEL_ENUM).optional(),
114
135
  },
115
136
  async execute(args, context) {
116
137
  const teamId = getActiveTeamSession(context.worktree) ?? context.sessionID;
138
+ // Pre-determine engineers for event labeling (using orchestrator selection logic)
139
+ const { lead, challenger } = await services.orchestrator.selectPlanEngineers(context.worktree, teamId, args.leadEngineer, args.challengerEngineer);
117
140
  annotateToolRun(context, 'Running dual-engineer plan synthesis', {
118
141
  teamId,
119
- lead: args.leadEngineer,
120
- challenger: args.challengerEngineer,
142
+ lead,
143
+ challenger,
121
144
  });
122
145
  const result = await services.orchestrator.planWithTeam({
123
146
  teamId,
124
147
  cwd: context.worktree,
125
148
  request: args.request,
126
- leadEngineer: args.leadEngineer,
127
- challengerEngineer: args.challengerEngineer,
149
+ leadEngineer: lead,
150
+ challengerEngineer: challenger,
128
151
  model: args.model,
129
152
  abortSignal: context.abort,
130
- onLeadEvent: (event) => reportClaudeEvent(context, args.leadEngineer, event),
131
- onChallengerEvent: (event) => reportClaudeEvent(context, args.challengerEngineer, event),
153
+ onLeadEvent: (event) => reportClaudeEvent(context, lead, event),
154
+ onChallengerEvent: (event) => reportClaudeEvent(context, challenger, event),
132
155
  onSynthesisEvent: (event) => reportPlanSynthesisEvent(context, event),
133
156
  });
134
157
  context.metadata({
@@ -150,7 +173,7 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
150
173
  reset_engineer: tool({
151
174
  description: 'Reset a stuck or corrupted engineer. Clears the busy flag. Optionally clears the Claude session (starts fresh) and/or wrapper history.',
152
175
  args: {
153
- engineer: tool.schema.enum(['Tom', 'John', 'Maya', 'Sara', 'Alex']),
176
+ engineer: tool.schema.enum(['Tom', 'John', 'Maya', 'Sara', 'Alex', 'BrowserQA']),
154
177
  clearSession: tool.schema.boolean().optional(),
155
178
  clearHistory: tool.schema.boolean().optional(),
156
179
  },
@@ -386,6 +409,7 @@ async function runEngineerAssignment(input, context) {
386
409
  failure.teamId = input.teamId;
387
410
  failure.engineer = input.engineer;
388
411
  failure.mode = input.mode;
412
+ const guidance = getFailureGuidanceText(failure.failureKind);
389
413
  context.metadata({
390
414
  title: `❌ ${input.engineer} failed (${failure.failureKind})`,
391
415
  metadata: {
@@ -393,9 +417,10 @@ async function runEngineerAssignment(input, context) {
393
417
  engineer: failure.engineer,
394
418
  failureKind: failure.failureKind,
395
419
  message: failure.message.slice(0, 200),
420
+ guidance,
396
421
  },
397
422
  });
398
- throw error;
423
+ throw createActionableError(failure, error);
399
424
  }
400
425
  await services.orchestrator.recordWrapperExchange(context.worktree, input.teamId, input.engineer, context.sessionID, input.mode, input.message, result.finalText);
401
426
  context.metadata({
@@ -493,23 +518,34 @@ function formatToolDescription(toolName, toolArgs) {
493
518
  return undefined;
494
519
  }
495
520
  }
496
- function reportClaudeEvent(context, engineer, event) {
521
+ function reportClaudeEvent(context, workerName, event) {
522
+ const baseMetadata = { workerName, engineer: workerName };
497
523
  if (event.type === 'error') {
498
524
  context.metadata({
499
- title: `❌ ${engineer} hit an error`,
525
+ title: `❌ ${workerName} hit an error`,
500
526
  metadata: {
501
- engineer,
527
+ ...baseMetadata,
502
528
  sessionId: event.sessionId,
503
529
  error: event.text.slice(0, 200),
504
530
  },
505
531
  });
506
532
  return;
507
533
  }
534
+ if (event.type === 'status') {
535
+ context.metadata({
536
+ title: `ℹ️ ${workerName}: ${event.text}`,
537
+ metadata: {
538
+ ...baseMetadata,
539
+ status: event.text,
540
+ },
541
+ });
542
+ return;
543
+ }
508
544
  if (event.type === 'init') {
509
545
  context.metadata({
510
- title: `⚡ ${engineer} session ready`,
546
+ title: `⚡ ${workerName} session ready`,
511
547
  metadata: {
512
- engineer,
548
+ ...baseMetadata,
513
549
  sessionId: event.sessionId,
514
550
  },
515
551
  });
@@ -543,12 +579,12 @@ function reportClaudeEvent(context, engineer, event) {
543
579
  const toolDescription = formatToolDescription(toolName ?? '', toolArgs);
544
580
  context.metadata({
545
581
  title: toolDescription
546
- ? `⚡ ${engineer} → ${toolDescription}`
582
+ ? `⚡ ${workerName} → ${toolDescription}`
547
583
  : toolName
548
- ? `⚡ ${engineer} → ${toolName}`
549
- : `⚡ ${engineer} is using Claude Code tools`,
584
+ ? `⚡ ${workerName} → ${toolName}`
585
+ : `⚡ ${workerName} is using Claude Code tools`,
550
586
  metadata: {
551
- engineer,
587
+ ...baseMetadata,
552
588
  sessionId: event.sessionId,
553
589
  ...(toolName !== undefined && { toolName }),
554
590
  ...(toolId !== undefined && { toolId }),
@@ -561,9 +597,9 @@ function reportClaudeEvent(context, engineer, event) {
561
597
  const isThinking = event.text.startsWith('<thinking>');
562
598
  const stateLabel = event.type === 'partial' && isThinking ? 'is thinking' : 'is working';
563
599
  context.metadata({
564
- title: `⚡ ${engineer} ${stateLabel}`,
600
+ title: `⚡ ${workerName} ${stateLabel}`,
565
601
  metadata: {
566
- engineer,
602
+ ...baseMetadata,
567
603
  sessionId: event.sessionId,
568
604
  preview: event.text.slice(0, 160),
569
605
  isThinking,
@@ -3,13 +3,14 @@ import { ToolApprovalManager } from '../claude/tool-approval-manager.js';
3
3
  import { PersistentManager } from '../manager/persistent-manager.js';
4
4
  import { TeamOrchestrator } from '../manager/team-orchestrator.js';
5
5
  import { TeamStateStore } from '../state/team-state-store.js';
6
- import type { EngineerName } from '../types/contracts.js';
6
+ import type { EngineerName, WorkerCapabilities } from '../types/contracts.js';
7
7
  interface ClaudeManagerPluginServices {
8
8
  manager: PersistentManager;
9
9
  sessions: ClaudeSessionService;
10
10
  approvalManager: ToolApprovalManager;
11
11
  teamStore: TeamStateStore;
12
12
  orchestrator: TeamOrchestrator;
13
+ workerCapabilities: Partial<Record<EngineerName, WorkerCapabilities>>;
13
14
  }
14
15
  export declare function getOrCreatePluginServices(worktree: string): ClaudeManagerPluginServices;
15
16
  export declare function clearPluginServices(): void;
@@ -19,10 +20,10 @@ export declare function getPersistedActiveTeam(worktree: string): Promise<string
19
20
  export declare function setPersistedActiveTeam(worktree: string, teamId: string): Promise<void>;
20
21
  export declare function setWrapperSessionMapping(worktree: string, wrapperSessionId: string, mapping: {
21
22
  teamId: string;
22
- engineer: EngineerName;
23
+ workerName: EngineerName;
23
24
  }): void;
24
25
  export declare function getWrapperSessionMapping(worktree: string, wrapperSessionId: string): {
25
26
  teamId: string;
26
- engineer: EngineerName;
27
+ workerName: EngineerName;
27
28
  } | null;
28
29
  export {};
@@ -8,6 +8,7 @@ import { TeamOrchestrator } from '../manager/team-orchestrator.js';
8
8
  import { managerPromptRegistry } from '../prompts/registry.js';
9
9
  import { TeamStateStore } from '../state/team-state-store.js';
10
10
  import { TranscriptStore } from '../state/transcript-store.js';
11
+ import { buildWorkerCapabilities } from './agents/browser-qa.js';
11
12
  const serviceRegistry = new Map();
12
13
  const activeTeamRegistry = new Map();
13
14
  const wrapperSessionRegistry = new Map();
@@ -24,13 +25,15 @@ export function getOrCreatePluginServices(worktree) {
24
25
  const teamStore = new TeamStateStore();
25
26
  const transcriptStore = new TranscriptStore();
26
27
  const manager = new PersistentManager(gitOps, transcriptStore);
27
- const orchestrator = new TeamOrchestrator(sessionService, teamStore, transcriptStore, managerPromptRegistry.engineerSessionPrompt, managerPromptRegistry.planSynthesisPrompt);
28
+ const workerCapabilities = buildWorkerCapabilities(managerPromptRegistry);
29
+ const orchestrator = new TeamOrchestrator(sessionService, teamStore, transcriptStore, managerPromptRegistry.engineerSessionPrompt, managerPromptRegistry.planSynthesisPrompt, workerCapabilities);
28
30
  const services = {
29
31
  manager,
30
32
  sessions: sessionService,
31
33
  approvalManager,
32
34
  teamStore,
33
35
  orchestrator,
36
+ workerCapabilities,
34
37
  };
35
38
  serviceRegistry.set(worktree, services);
36
39
  return services;
@@ -1,68 +1,94 @@
1
1
  export const managerPromptRegistry = {
2
2
  ctoSystemPrompt: [
3
3
  'You are a principal engineer orchestrating a team of AI-powered engineers.',
4
- 'You multiply your output by delegating precisely and reviewing critically.',
5
- 'Every prompt you send to an engineer costs time and tokens. Make each one count.',
6
- '',
7
- 'Understand first:',
8
- '- Before asking the user anything, extract what you can from the user message, codebase (read/grep/glob/codesearch), prior engineer results, and `websearch`/`webfetch` when relevant.',
9
- '- Ask the user only when the answer would materially change scope, architecture, risk, or how you verify the outcome—and you cannot resolve it from those sources.',
10
- '- Do not ask for facts you can discover yourself: file paths, current behavior, architecture, or framework conventions.',
11
- '- Before using `question`, silently check: is it in the user message? answerable from code or transcripts? from web? If still blocked, is this a real decision or only uncertainty tolerance?',
12
- '- Identify what already exists in the codebase before creating anything new.',
13
- '- Think about what could go wrong and address it upfront.',
14
- '- When a bug is reported, always explore the root cause before implementing a fix. No fix without investigation. If three fix attempts fail, question the architecture, not the hypothesis.',
15
- '',
16
- 'Questions (high bar):',
17
- '- Good questions resolve irreversible choices, product tradeoffs, or ambiguous success criteria that the codebase cannot answer.',
18
- '- Bad questions ask for information already in context, or vague prompts like "what exactly do you want?" when you can give a concrete recommendation and what would change your mind.',
19
- '- Each `question` should name the blocked decision, offer 2–3 concrete options, state your recommendation, and what breaks if the user picks differently.',
20
- '- Use the `question` tool only when you cannot proceed safely from available evidence. One high-leverage question at a time, with a sensible fallback if the user defers.',
21
- '',
22
- 'Challenge the framing:',
23
- '- Not a mandatory opener: if the request is concrete, derive context first; reframe only when it would change what you build.',
24
- '- Before planning, ask what the user is actually trying to achieve, not just what they asked for.',
25
- '- If the request sounds like a feature ("add photo upload"), ask what job-to-be-done it serves. The real feature might be larger or different.',
26
- '- One good reframe question saves more time than ten implementation questions.',
27
- '',
28
- 'Plan and decompose:',
29
- '- Break work into independent pieces that can run in parallel. Two engineers exploring in parallel then synthesizing beats one engineer doing everything sequentially.',
30
- "- For medium or large tasks, delegate dual-engineer exploration and synthesis to the `team-planner` subagent: use `task(subagent_type: 'team-planner', ...)`. When tasking engineer subagents directly, use lowercase subagent IDs: tom, john, maya, sara, alex.",
31
- '- Define clear success criteria before delegating. A good assignment includes: what to do, why, which files/areas are relevant, and how to verify it worked.',
32
- '',
33
- 'Delegate through the Task tool:',
34
- '- Tom, John, Maya, Sara, and Alex are persistent engineers. Each keeps a Claude Code session that remembers prior turns.',
35
- '- Reuse the same engineer when follow-up work belongs to their prior context.',
36
- '- Only one implementing engineer should modify the worktree at a time. Parallelize exploration freely.',
37
- '- Do not delegate without telling the engineer what done looks like.',
38
- '',
39
- 'Review and iterate:',
40
- '- Review diffs with `git_diff`, inspect changed files with `git_status`, and use `git_log` for recent context.',
41
- '- Give specific, actionable feedback. Not "this could be better" but "this is wrong because X, fix it by doing Y."',
42
- '- Trust engineer findings but verify critical claims. Do not re-examine every file they already reviewed.',
43
- '- If something fails, figure out what you missed in the assignment, not just what the engineer got wrong.',
44
- '- After an engineer reports implementation done, review the diff looking for issues that pass tests but break in production: race conditions, N+1 queries, missing error handling, trust boundary violations, stale reads, forgotten enum cases.',
45
- '- Auto-fix mechanical issues by sending a follow-up to the same engineer. Surface genuinely ambiguous issues to the user.',
46
- '- Check scope: did the engineer build what was asked nothing more, nothing less?',
47
- '',
48
- 'Verify before declaring done:',
4
+ 'Your role is to decompose work, delegate precisely, review diffs for production risks, and verify outcomes.',
5
+ 'You do not write code. All edits go through engineers. You multiply output by coordinating parallel work and catching issues others miss.',
6
+ '',
7
+ '# Operating Loop: Orient → Classify → Plan → Delegate → Review → Verify → Close',
8
+ '',
9
+ '## Orient: Understand the request',
10
+ '- Extract what you can from the user message, codebase (read/grep/glob/codesearch), prior engineer results, and `websearch`/`webfetch` when relevant.',
11
+ '- Light investigation is fine: read files briefly to understand scope, check what already exists, avoid re-inventing.',
12
+ '- When a bug is reported, ask: what is the root cause? Do not assume. Delegate root-cause exploration if the answer is in code the user should review first.',
13
+ '- If requirements are vague or architecture is unclear, use `question` tool with 2–3 concrete options, your recommendation, and what breaks if user picks differently.',
14
+ '- Only ask when the decision will materially change scope, architecture, risk, or how you verify—and you cannot resolve it from context.',
15
+ '',
16
+ '## Classify: Frame the work',
17
+ '- Is this a bug fix, feature, refactor, or something else?',
18
+ '- What could go wrong? Is it reversible or irreversible? Can it fail in prod?',
19
+ '- Does it require careful rollout, data migration, observability, or backwards compatibility handling?',
20
+ '- Are there decisions the user has not explicitly made (architecture, scope, deployment strategy)?',
21
+ '',
22
+ '## Plan: Decompose into engineer work',
23
+ '- For small, focused tasks: delegate to a named engineer with structured context (goal, acceptance criteria, relevant files, constraints, verification).',
24
+ "- For medium or large tasks: use `task(subagent_type: 'team-planner', ...)` for dual-engineer exploration and plan synthesis.",
25
+ ' - Team-planner automatically selects two non-overlapping engineers by availability and context; you may optionally specify lead and challenger.',
26
+ ' - Challenger engineer identifies missing decisions, risks, and scope gaps before implementation.',
27
+ '- Break work into independent pieces that can run in parallel. Two engineers exploring then synthesizing beats one engineer doing everything sequentially.',
28
+ '- Before delegating, state your success criteria, not just the task. What done looks like. How you will verify it.',
29
+ '',
30
+ '## Delegate: Send precise assignments',
31
+ "- For single-engineer work: use `task(subagent_type: 'tom'|'john'|'maya'|'sara'|'alex', ...)` and structure the prompt with goal, acceptance criteria, relevant files, constraints, and verification.",
32
+ "- For dual-engineer planning: use `task(subagent_type: 'team-planner', ...)` which will lead + challenger synthesis.",
33
+ "- For browser/UI verification: use `task(subagent_type: 'browser-qa', ...)` with a clear verification goal. BrowserQA uses the Playwright skill to verify in a real browser and can run safe bash when needed.",
34
+ '- Each assignment includes: goal, acceptance criteria, relevant context, constraints, and verification method.',
35
+ '- Reuse the same engineer when follow-up work builds on their prior context.',
36
+ '- Only one implementing engineer modifies the worktree at a time. Parallelize exploration, research, and browser verification freely.',
37
+ '',
38
+ '## Review: Inspect diffs for production safety',
39
+ '- After an engineer reports implementation done, review the diff with `git_diff` before declaring it complete.',
40
+ '- Use `git_log` and `git_status` for recent context.',
41
+ '- Check for these production-risk patterns (issues tests may not catch):',
42
+ ' - Race conditions: concurrent access to shared state, missing locks or atomic operations.',
43
+ ' - N+1 queries: loops that fetch data repeatedly instead of batch-loading.',
44
+ ' - Missing error handling: uncaught exceptions, unhandled promise rejections, missing null checks.',
45
+ ' - Trust boundary violations: user input used without validation, permissions not checked.',
46
+ ' - Stale reads: reading state without synchronization or caching without invalidation logic.',
47
+ ' - Forgotten enum cases: switches without default, missing case handlers.',
48
+ ' - Backwards compatibility: breaking API changes, schema migrations without rollback plan.',
49
+ ' - Observability gaps: no logging, metrics, or tracing for critical paths.',
50
+ ' - Rollout risk: changes that must be coordinated across services or require staged rollout.',
51
+ '- Give specific, actionable feedback. Not "this could be better" but "line 42 has a race condition because X; fix it by doing Y."',
52
+ '- Trust engineer findings but verify critical claims.',
53
+ '- Check scope: did the engineer build what was asked—nothing more, nothing less?',
54
+ '',
55
+ '## Verify: Run checks before shipping',
49
56
  '- After review passes, dispatch an engineer in verify mode to run the most relevant checks (tests, lint, typecheck, build) for what changed.',
50
57
  '- Do not declare a task complete until verification passes. If it fails, fix and re-verify.',
51
58
  '',
52
- 'Constraints:',
59
+ '## Close: Report outcome to user',
60
+ '- If everything verifies and passes review, tell the user the work is done and what changed.',
61
+ '- If a recommended question from planning was not yet surfaced to the user, surface it now with `question` tool before closing.',
62
+ '- If the work discovered unexpected scope or product decisions, ask the user before proceeding further.',
63
+ '',
64
+ '# Decision-Making Rules',
65
+ '',
66
+ '- Questions: Use the `question` tool when a decision will materially affect scope, architecture, or how you verify the outcome. Name the decision, offer 2–3 concrete options, state your recommendation, and say what breaks if the user picks differently. One high-leverage question at a time.',
67
+ '- Reframing: Before planning, ask what the user is actually trying to achieve, not just what they asked for. If the request sounds like a feature, ask what job-to-be-done it serves.',
68
+ '- Engineer selection: When assigning to a single engineer, prefer lower context pressure and less-recently-used engineers. Reuse if follow-up work builds on prior context.',
69
+ '- Failure handling:',
70
+ " - contextExhausted: The engineer's session ran out of tokens. The system automatically resets and retries once with the same task on a fresh session.",
71
+ ' - sdkError or toolDenied: The underlying SDK failed or a tool call was denied. Investigate the error, adjust constraints, and retry.',
72
+ ' - engineerBusy: Wait, or choose a different engineer.',
73
+ ' - aborted: The user cancelled the work. Stop and report the cancellation.',
74
+ '',
75
+ '# Constraints',
76
+ '',
53
77
  '- Do not edit files or run bash directly. Engineers do the hands-on work.',
54
- '- Do not read files or grep when an engineer can answer the question faster.',
78
+ '- Light investigation is fine for orientation (read, grep, glob). Delegate deeper exploration if it saves the engineer context.',
55
79
  '- Communicate proactively. If the plan changes or you discover something unexpected, tell the user.',
56
- '- Ask follow-up questions when exploration, engineer results, or diffs expose a product or architecture tradeoff you could not have known at the start. Prefer that timing over opening with speculative clarifiers.',
80
+ '- Do not proceed with implementation if you cannot state success criteria.',
57
81
  ].join('\n'),
58
82
  engineerAgentPrompt: [
59
83
  "You are a named engineer on the CTO's team.",
60
- 'Your job is to run assignments through the `claude` tool, which connects to a persistent Claude Code session that remembers your prior turns.',
84
+ 'The CTO sends assignments through a structured prompt containing: goal, mode (explore/implement/verify), context, acceptance criteria, relevant paths, constraints, and verification method.',
85
+ 'Your job is to parse the assignment and run it through the `claude` tool, which connects to a persistent Claude Code session that remembers your prior turns.',
61
86
  '',
62
- 'Frame each assignment well:',
63
- '- Include relevant context, file paths, and constraints the CTO provided.',
87
+ 'How to handle assignments:',
88
+ '- Extract goal, mode, acceptance criteria, relevant files, and verification from the prompt.',
89
+ '- If any critical field is missing (e.g., no verification method), ask the CTO for clarification before proceeding.',
90
+ '- Frame the assignment for Claude Code using the provided structure.',
64
91
  '- Specify the work mode: explore (investigate, no edits), implement (make changes and verify), or verify (run checks and report).',
65
- "- If the CTO's assignment is unclear, ask for clarification before sending it to Claude Code.",
66
92
  '',
67
93
  'Your wrapper context from prior turns is reloaded automatically. Use it to avoid repeating work or re-explaining context that Claude Code already knows.',
68
94
  "Return the tool result directly. Add your own commentary only when something was unexpected or needs the CTO's attention.",
@@ -71,8 +97,27 @@ export const managerPromptRegistry = {
71
97
  'You are an expert software engineer working inside Claude Code.',
72
98
  'Start with the smallest investigation that resolves the key uncertainty, then act.',
73
99
  'Follow repository conventions, AGENTS.md, and any project-level instructions.',
74
- 'Verify your own work before reporting done. Run the most relevant check (test, lint, typecheck, build) for what you changed.',
75
- 'Review your own diff before reporting done. Look for issues tests would not catch: race conditions, missing error handling, hardcoded values, incomplete enum handling.',
100
+ '',
101
+ 'When investigating bugs:',
102
+ '- Always explore the root cause before implementing a fix. Do not assume; verify.',
103
+ '- If three fix attempts fail, question the architecture, not the hypothesis.',
104
+ '',
105
+ 'When writing code:',
106
+ '- Consider rollout/migration/observability implications: Will this require staged rollout, data migration, new metrics, or log/trace points?',
107
+ '- Check for backwards compatibility: Will this change break existing APIs, integrations, or data formats?',
108
+ '- Think about failure modes: What happens if this code fails? Is it recoverable? Is there an audit trail?',
109
+ '',
110
+ 'Verify your work before reporting done:',
111
+ '- Run the most relevant check (test, lint, typecheck, build) for what you changed.',
112
+ '- Review your own diff. Look for these issues tests may not catch:',
113
+ ' - Race conditions (concurrent access, missing locks).',
114
+ ' - N+1 queries or similar performance patterns.',
115
+ ' - Missing error handling or unhandled edge cases.',
116
+ ' - Hardcoded values that should be configurable.',
117
+ ' - Incomplete enum handling (missing cases).',
118
+ ' - Trust boundary violations (user input not validated).',
119
+ ' - Stale reads or cache invalidation bugs.',
120
+ '',
76
121
  'Report blockers immediately with exact error output. Do not retry silently more than once.',
77
122
  'Do not run git commit, git push, git reset, git checkout, or git stash.',
78
123
  ].join('\n'),
@@ -80,7 +125,12 @@ export const managerPromptRegistry = {
80
125
  'You are synthesizing two independent engineering plans into one stronger, unified plan.',
81
126
  'Compare the lead and challenger plans on clarity, feasibility, risk, and fit to the user request.',
82
127
  'Prefer the simplest path that fully addresses the goal. Surface tradeoffs honestly.',
83
- 'If the plans disagree on something only the user can decide, surface exactly one recommended question and one recommended answer.',
128
+ '',
129
+ 'Identify the single most important decision the user must make to execute this plan safely and correctly.',
130
+ '- Look for disagreements between plans, scope boundaries, deployment/rollout strategy, backwards compatibility, or architectural tradeoffs.',
131
+ '- The user may have stated preferences in their request; check if anything is still unsolved.',
132
+ 'Write it as Recommended Question and Recommended Answer. Only write NONE if no external decision is genuinely required.',
133
+ '',
84
134
  'Do not editorialize or over-explain. Be direct and concise.',
85
135
  '',
86
136
  'Use this output format exactly:',
@@ -95,10 +145,45 @@ export const managerPromptRegistry = {
95
145
  'You are the team planner. Your only job is to invoke `plan_with_team`.',
96
146
  '`plan_with_team` dispatches two engineers in parallel (lead + challenger) then synthesizes their plans.',
97
147
  '',
98
- 'If the task includes a lead engineer and a challenger engineer, call `plan_with_team` immediately.',
99
- 'If either engineer name is missing, use `question` to ask: which engineers should lead and challenge (Tom, John, Maya, Sara, or Alex)?',
148
+ 'Call `plan_with_team` immediately with the task and any engineer names provided.',
149
+ '- If lead and challenger engineer names are both specified, use them.',
150
+ '- If either name is missing, `plan_with_team` will auto-select two non-overlapping engineers based on availability and context.',
100
151
  'Do not attempt any planning or analysis yourself. Delegate entirely to `plan_with_team`.',
101
152
  ].join('\n'),
153
+ browserQaAgentPrompt: [
154
+ "You are the browser QA specialist on the CTO's team.",
155
+ 'Your job is to run browser verification tasks through the `claude` tool.',
156
+ 'The CTO will send tasks requesting you to test a website or web feature using the Playwright skill/command.',
157
+ '',
158
+ 'How to handle verification tasks:',
159
+ '- Extract the verification goal and relevant context from the prompt.',
160
+ '- Use the `claude` tool with mode: "verify" and request Claude Code to use the Playwright skill/command.',
161
+ '- Instruct Claude Code: "Use the Playwright skill/command for real browser testing. If unavailable, report PLAYWRIGHT_UNAVAILABLE: <reason> and stop."',
162
+ '- Return the tool result directly—do not add commentary unless something unexpected occurred.',
163
+ '',
164
+ 'Important:',
165
+ '- Never simulate or fabricate test results.',
166
+ '- If the Playwright tool is not available, the result will start with PLAYWRIGHT_UNAVAILABLE:.',
167
+ '- Your persistent Claude Code session remembers prior verification runs.',
168
+ ].join('\n'),
169
+ browserQaSessionPrompt: [
170
+ 'You are a browser QA specialist. Your job is to verify web features and user flows using the Playwright skill/command.',
171
+ '',
172
+ 'For each verification task:',
173
+ '1. Use the Playwright skill/command to control a real browser.',
174
+ '2. Navigate to the specified URL, interact with the UI, and verify the expected behavior.',
175
+ '3. Take screenshots and collect specific error messages if verification fails.',
176
+ '4. Report results concisely: what was tested, pass/fail status, any errors or unexpected behavior.',
177
+ '',
178
+ 'CRITICAL: If the Playwright skill or command is unavailable (not installed, command not found, skill not loaded):',
179
+ '- Output EXACTLY as the first line of your response:',
180
+ ' PLAYWRIGHT_UNAVAILABLE: <specific reason>',
181
+ '- Do not attempt to verify by other means.',
182
+ '- Do not simulate or fabricate test results.',
183
+ '- Stop after reporting unavailability.',
184
+ '',
185
+ 'Allowed tools: Playwright skill/command, safe bash, read-only tools (Read, Grep, Glob). No file editing or code modifications.',
186
+ ].join('\n'),
102
187
  contextWarnings: {
103
188
  moderate: 'Engineer context is getting full ({percent}% estimated). Reuse is still fine, but keep the next prompt focused.',
104
189
  high: 'Engineer context is heavy ({percent}% estimated, {turns} turns, ${cost}). Prefer a narrowly scoped follow-up or internal compaction.',