@doingdev/opencode-claude-manager-plugin 0.1.49 → 0.1.51

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 (60) hide show
  1. package/dist/claude/claude-agent-sdk-adapter.d.ts +2 -3
  2. package/dist/claude/claude-agent-sdk-adapter.js +38 -48
  3. package/dist/claude/claude-session.service.d.ts +1 -2
  4. package/dist/claude/claude-session.service.js +0 -3
  5. package/dist/claude/tool-approval-manager.d.ts +9 -6
  6. package/dist/claude/tool-approval-manager.js +43 -6
  7. package/dist/index.d.ts +1 -2
  8. package/dist/index.js +0 -1
  9. package/dist/manager/context-tracker.d.ts +0 -1
  10. package/dist/manager/context-tracker.js +0 -3
  11. package/dist/manager/git-operations.d.ts +1 -4
  12. package/dist/manager/git-operations.js +7 -12
  13. package/dist/manager/persistent-manager.d.ts +3 -53
  14. package/dist/manager/persistent-manager.js +3 -135
  15. package/dist/manager/team-orchestrator.d.ts +9 -4
  16. package/dist/manager/team-orchestrator.js +84 -31
  17. package/dist/plugin/agent-hierarchy.d.ts +0 -1
  18. package/dist/plugin/agent-hierarchy.js +4 -2
  19. package/dist/plugin/claude-manager.plugin.js +170 -24
  20. package/dist/plugin/service-factory.d.ts +5 -6
  21. package/dist/plugin/service-factory.js +9 -17
  22. package/dist/prompts/registry.js +58 -39
  23. package/dist/src/claude/claude-agent-sdk-adapter.d.ts +2 -3
  24. package/dist/src/claude/claude-agent-sdk-adapter.js +38 -48
  25. package/dist/src/claude/claude-session.service.d.ts +1 -2
  26. package/dist/src/claude/claude-session.service.js +0 -3
  27. package/dist/src/claude/tool-approval-manager.d.ts +9 -6
  28. package/dist/src/claude/tool-approval-manager.js +43 -6
  29. package/dist/src/index.d.ts +1 -2
  30. package/dist/src/index.js +0 -1
  31. package/dist/src/manager/context-tracker.d.ts +0 -1
  32. package/dist/src/manager/context-tracker.js +0 -3
  33. package/dist/src/manager/git-operations.d.ts +1 -4
  34. package/dist/src/manager/git-operations.js +7 -12
  35. package/dist/src/manager/persistent-manager.d.ts +3 -53
  36. package/dist/src/manager/persistent-manager.js +3 -135
  37. package/dist/src/manager/team-orchestrator.d.ts +9 -4
  38. package/dist/src/manager/team-orchestrator.js +84 -31
  39. package/dist/src/plugin/agent-hierarchy.d.ts +0 -1
  40. package/dist/src/plugin/agent-hierarchy.js +4 -2
  41. package/dist/src/plugin/claude-manager.plugin.js +170 -24
  42. package/dist/src/plugin/service-factory.d.ts +5 -6
  43. package/dist/src/plugin/service-factory.js +9 -17
  44. package/dist/src/prompts/registry.js +58 -39
  45. package/dist/src/state/team-state-store.js +4 -1
  46. package/dist/src/team/roster.js +1 -0
  47. package/dist/src/types/contracts.d.ts +18 -57
  48. package/dist/state/team-state-store.js +4 -1
  49. package/dist/team/roster.js +1 -0
  50. package/dist/test/claude-agent-sdk-adapter.test.js +103 -11
  51. package/dist/test/claude-manager.plugin.test.js +6 -1
  52. package/dist/test/context-tracker.test.js +0 -8
  53. package/dist/test/git-operations.test.js +0 -21
  54. package/dist/test/persistent-manager.test.js +4 -164
  55. package/dist/test/prompt-registry.test.js +4 -9
  56. package/dist/test/report-claude-event.test.js +4 -4
  57. package/dist/test/team-orchestrator.test.js +7 -5
  58. package/dist/test/tool-approval-manager.test.js +17 -17
  59. package/dist/types/contracts.d.ts +18 -57
  60. package/package.json +1 -1
@@ -1,37 +1,10 @@
1
- import { randomUUID } from 'node:crypto';
2
1
  export class PersistentManager {
3
- sessionController;
4
2
  gitOps;
5
- stateStore;
6
- contextTracker;
7
3
  transcriptStore;
8
- constructor(sessionController, gitOps, stateStore, contextTracker, transcriptStore) {
9
- this.sessionController = sessionController;
4
+ constructor(gitOps, transcriptStore) {
10
5
  this.gitOps = gitOps;
11
- this.stateStore = stateStore;
12
- this.contextTracker = contextTracker;
13
6
  this.transcriptStore = transcriptStore;
14
7
  }
15
- /**
16
- * Send a message to the persistent Claude Code session.
17
- * Creates a new session if none exists.
18
- */
19
- async sendMessage(cwd, message, options, onEvent) {
20
- const result = await this.sessionController.sendMessage(message, options, onEvent);
21
- if (result.sessionId && result.events.length > 0) {
22
- await this.transcriptStore.appendEvents(cwd, result.sessionId, result.events);
23
- }
24
- return {
25
- sessionId: result.sessionId,
26
- finalText: result.finalText,
27
- turns: result.turns,
28
- totalCostUsd: result.totalCostUsd,
29
- inputTokens: result.inputTokens,
30
- outputTokens: result.outputTokens,
31
- contextWindowSize: result.contextWindowSize,
32
- context: this.sessionController.getContextSnapshot(),
33
- };
34
- }
35
8
  /**
36
9
  * Get the current git diff.
37
10
  */
@@ -41,8 +14,8 @@ export class PersistentManager {
41
14
  /**
42
15
  * Commit all current changes.
43
16
  */
44
- async gitCommit(message) {
45
- return this.gitOps.commit(message);
17
+ async gitCommit(message, paths) {
18
+ return this.gitOps.commit(message, paths);
46
19
  }
47
20
  /**
48
21
  * Get git status summary.
@@ -62,115 +35,10 @@ export class PersistentManager {
62
35
  async gitReset() {
63
36
  return this.gitOps.resetHard();
64
37
  }
65
- /**
66
- * Get current session status and context health.
67
- */
68
- getStatus() {
69
- return this.sessionController.getContextSnapshot();
70
- }
71
- /**
72
- * Clear the active session. Next send creates a fresh one.
73
- */
74
- async clearSession() {
75
- return this.sessionController.clearSession();
76
- }
77
- /**
78
- * Compact the current session to free context.
79
- */
80
- async compactSession(cwd, onEvent) {
81
- const result = await this.sessionController.compactSession(onEvent);
82
- if (result.sessionId && result.events.length > 0) {
83
- await this.transcriptStore.appendEvents(cwd, result.sessionId, result.events);
84
- }
85
- return result;
86
- }
87
38
  /**
88
39
  * Read persisted transcript events for a session.
89
40
  */
90
41
  getTranscriptEvents(cwd, sessionId) {
91
42
  return this.transcriptStore.readEvents(cwd, sessionId);
92
43
  }
93
- /**
94
- * Execute a full task with run tracking.
95
- * Creates a run record, sends the message, and persists the result.
96
- */
97
- async executeTask(cwd, task, options, onProgress) {
98
- const runId = randomUUID();
99
- const createdAt = new Date().toISOString();
100
- const runRecord = {
101
- id: runId,
102
- cwd,
103
- task,
104
- status: 'running',
105
- createdAt,
106
- updatedAt: createdAt,
107
- sessionId: this.sessionController.sessionId,
108
- sessionHistory: [],
109
- messages: [
110
- {
111
- timestamp: createdAt,
112
- direction: 'sent',
113
- text: task,
114
- },
115
- ],
116
- actions: [],
117
- commits: [],
118
- context: this.sessionController.getContextSnapshot(),
119
- };
120
- await this.stateStore.saveRun(runRecord);
121
- await onProgress?.(runRecord);
122
- try {
123
- const result = await this.sessionController.sendMessage(task, options, async (event) => {
124
- // Update run record with progress events
125
- const currentRun = await this.stateStore.getRun(cwd, runId);
126
- if (currentRun) {
127
- const updated = {
128
- ...currentRun,
129
- updatedAt: new Date().toISOString(),
130
- sessionId: event.sessionId ?? currentRun.sessionId,
131
- context: this.sessionController.getContextSnapshot(),
132
- };
133
- await this.stateStore.saveRun(updated);
134
- await onProgress?.(updated);
135
- }
136
- });
137
- const completedRun = await this.stateStore.updateRun(cwd, runId, (run) => ({
138
- ...run,
139
- status: 'completed',
140
- updatedAt: new Date().toISOString(),
141
- sessionId: result.sessionId ?? run.sessionId,
142
- messages: [
143
- ...run.messages,
144
- {
145
- timestamp: new Date().toISOString(),
146
- direction: 'received',
147
- text: result.finalText,
148
- turns: result.turns,
149
- totalCostUsd: result.totalCostUsd,
150
- inputTokens: result.inputTokens,
151
- outputTokens: result.outputTokens,
152
- },
153
- ],
154
- context: this.sessionController.getContextSnapshot(),
155
- finalSummary: result.finalText,
156
- }));
157
- return { run: completedRun };
158
- }
159
- catch (error) {
160
- const failedRun = await this.stateStore.updateRun(cwd, runId, (run) => ({
161
- ...run,
162
- status: 'failed',
163
- updatedAt: new Date().toISOString(),
164
- context: this.sessionController.getContextSnapshot(),
165
- finalSummary: error instanceof Error ? error.message : String(error),
166
- }));
167
- return { run: failedRun };
168
- }
169
- }
170
- listRuns(cwd) {
171
- return this.stateStore.listRuns(cwd);
172
- }
173
- getRun(cwd, runId) {
174
- return this.stateStore.getRun(cwd, runId);
175
- }
176
44
  }
@@ -2,7 +2,7 @@ import type { ClaudeSessionEventHandler } from '../claude/claude-agent-sdk-adapt
2
2
  import type { ClaudeSessionService } from '../claude/claude-session.service.js';
3
3
  import type { TeamStateStore } from '../state/team-state-store.js';
4
4
  import type { TranscriptStore } from '../state/transcript-store.js';
5
- import type { DiscoveredClaudeFile, EngineerName, EngineerTaskResult, EngineerWorkMode, SynthesizedPlanResult, TeamRecord } from '../types/contracts.js';
5
+ import type { EngineerFailureResult, EngineerName, EngineerTaskResult, EngineerWorkMode, SynthesizedPlanResult, TeamRecord } from '../types/contracts.js';
6
6
  interface DispatchEngineerInput {
7
7
  teamId: string;
8
8
  cwd: string;
@@ -18,8 +18,7 @@ export declare class TeamOrchestrator {
18
18
  private readonly teamStore;
19
19
  private readonly transcriptStore;
20
20
  private readonly engineerSessionPrompt;
21
- private readonly projectClaudeFiles;
22
- constructor(sessions: ClaudeSessionService, teamStore: TeamStateStore, transcriptStore: TranscriptStore, engineerSessionPrompt: string, projectClaudeFiles: DiscoveredClaudeFile[]);
21
+ constructor(sessions: ClaudeSessionService, teamStore: TeamStateStore, transcriptStore: TranscriptStore, engineerSessionPrompt: string);
23
22
  getOrCreateTeam(cwd: string, teamId: string): Promise<TeamRecord>;
24
23
  listTeams(cwd: string): Promise<TeamRecord[]>;
25
24
  recordWrapperSession(cwd: string, teamId: string, engineer: EngineerName, wrapperSessionId: string): Promise<void>;
@@ -29,7 +28,14 @@ export declare class TeamOrchestrator {
29
28
  teamId: string;
30
29
  engineer: EngineerName;
31
30
  } | null>;
31
+ resetEngineer(cwd: string, teamId: string, engineer: EngineerName, options?: {
32
+ clearSession?: boolean;
33
+ clearHistory?: boolean;
34
+ }): Promise<void>;
32
35
  dispatchEngineer(input: DispatchEngineerInput): Promise<EngineerTaskResult>;
36
+ static classifyError(error: unknown): EngineerFailureResult & {
37
+ cause: unknown;
38
+ };
33
39
  planWithTeam(input: {
34
40
  teamId: string;
35
41
  cwd: string;
@@ -45,6 +51,5 @@ export declare class TeamOrchestrator {
45
51
  private normalizeTeamRecord;
46
52
  private buildSessionSystemPrompt;
47
53
  private buildEngineerPrompt;
48
- private mapWorkModeToSessionMode;
49
54
  }
50
55
  export {};
@@ -1,17 +1,16 @@
1
1
  import { createEmptyEngineerRecord, createEmptyTeamRecord } from '../team/roster.js';
2
2
  import { ContextTracker } from './context-tracker.js';
3
+ const BUSY_LEASE_MS = 15 * 60 * 1000;
3
4
  export class TeamOrchestrator {
4
5
  sessions;
5
6
  teamStore;
6
7
  transcriptStore;
7
8
  engineerSessionPrompt;
8
- projectClaudeFiles;
9
- constructor(sessions, teamStore, transcriptStore, engineerSessionPrompt, projectClaudeFiles) {
9
+ constructor(sessions, teamStore, transcriptStore, engineerSessionPrompt) {
10
10
  this.sessions = sessions;
11
11
  this.teamStore = teamStore;
12
12
  this.transcriptStore = transcriptStore;
13
13
  this.engineerSessionPrompt = engineerSessionPrompt;
14
- this.projectClaudeFiles = projectClaudeFiles;
15
14
  }
16
15
  async getOrCreateTeam(cwd, teamId) {
17
16
  const existing = await this.teamStore.getTeam(cwd, teamId);
@@ -77,6 +76,16 @@ export class TeamOrchestrator {
77
76
  }
78
77
  return null;
79
78
  }
79
+ async resetEngineer(cwd, teamId, engineer, options) {
80
+ await this.updateEngineer(cwd, teamId, engineer, (entry) => ({
81
+ ...entry,
82
+ busy: false,
83
+ busySince: null,
84
+ claudeSessionId: options?.clearSession ? null : entry.claudeSessionId,
85
+ wrapperHistory: options?.clearHistory ? [] : entry.wrapperHistory,
86
+ context: options?.clearSession ? createEmptyEngineerRecord(engineer).context : entry.context,
87
+ }));
88
+ }
80
89
  async dispatchEngineer(input) {
81
90
  const team = await this.getOrCreateTeam(input.cwd, input.teamId);
82
91
  const engineerState = this.getEngineerState(team, input.engineer);
@@ -102,10 +111,11 @@ export class TeamOrchestrator {
102
111
  resumeSessionId: engineerState.claudeSessionId ?? undefined,
103
112
  persistSession: true,
104
113
  includePartialMessages: true,
105
- permissionMode: this.mapWorkModeToSessionMode(input.mode) === 'plan' ? 'plan' : 'acceptEdits',
114
+ permissionMode: 'acceptEdits',
115
+ restrictWriteTools: input.mode === 'explore',
106
116
  model: input.model,
107
117
  effort: input.mode === 'implement' ? 'high' : 'medium',
108
- settingSources: ['user'],
118
+ settingSources: ['user', 'project', 'local'],
109
119
  abortSignal: input.abortSignal,
110
120
  }, input.onEvent);
111
121
  tracker.recordResult({
@@ -124,6 +134,7 @@ export class TeamOrchestrator {
124
134
  ...entry,
125
135
  claudeSessionId: result.sessionId ?? engineerState.claudeSessionId,
126
136
  busy: false,
137
+ busySince: null,
127
138
  lastMode: input.mode,
128
139
  lastTaskSummary: summarizeMessage(input.message),
129
140
  lastUsedAt: new Date().toISOString(),
@@ -147,10 +158,38 @@ export class TeamOrchestrator {
147
158
  await this.updateEngineer(input.cwd, input.teamId, input.engineer, (engineer) => ({
148
159
  ...engineer,
149
160
  busy: false,
161
+ busySince: null,
150
162
  }));
151
163
  throw error;
152
164
  }
153
165
  }
166
+ static classifyError(error) {
167
+ const message = error instanceof Error ? error.message : String(error);
168
+ let failureKind = 'unknown';
169
+ if (message.includes('already working on another assignment')) {
170
+ failureKind = 'engineerBusy';
171
+ }
172
+ else if (message.includes('context') || message.includes('token limit')) {
173
+ failureKind = 'contextExhausted';
174
+ }
175
+ else if (message.includes('denied') || message.includes('not allowed')) {
176
+ failureKind = 'toolDenied';
177
+ }
178
+ else if (message.includes('abort') || message.includes('cancel')) {
179
+ failureKind = 'aborted';
180
+ }
181
+ else {
182
+ failureKind = 'sdkError';
183
+ }
184
+ return {
185
+ teamId: '',
186
+ engineer: 'Tom',
187
+ mode: 'explore',
188
+ failureKind,
189
+ message,
190
+ cause: error,
191
+ };
192
+ }
154
193
  async planWithTeam(input) {
155
194
  if (input.leadEngineer === input.challengerEngineer) {
156
195
  throw new Error('Choose two different engineers for plan synthesis.');
@@ -185,10 +224,11 @@ export class TeamOrchestrator {
185
224
  systemPrompt: buildSynthesisSystemPrompt(),
186
225
  persistSession: false,
187
226
  includePartialMessages: false,
188
- permissionMode: 'plan',
227
+ permissionMode: 'acceptEdits',
228
+ restrictWriteTools: true,
189
229
  model: input.model,
190
230
  effort: 'high',
191
- settingSources: ['user'],
231
+ settingSources: ['user', 'project', 'local'],
192
232
  abortSignal: input.abortSignal,
193
233
  });
194
234
  const parsedSynthesis = parseSynthesisResult(synthesisResult.finalText);
@@ -221,15 +261,21 @@ export class TeamOrchestrator {
221
261
  const normalized = this.normalizeTeamRecord(team);
222
262
  const engineer = this.getEngineerState(normalized, engineerName);
223
263
  if (engineer.busy) {
224
- throw new Error(`${engineerName} is already working on another assignment.`);
264
+ const leaseExpired = engineer.busySince !== null &&
265
+ Date.now() - new Date(engineer.busySince).getTime() > BUSY_LEASE_MS;
266
+ if (!leaseExpired) {
267
+ throw new Error(`${engineerName} is already working on another assignment.`);
268
+ }
225
269
  }
270
+ const now = new Date().toISOString();
226
271
  return {
227
272
  ...normalized,
228
- updatedAt: new Date().toISOString(),
273
+ updatedAt: now,
229
274
  engineers: normalized.engineers.map((entry) => entry.name === engineerName
230
275
  ? {
231
276
  ...entry,
232
277
  busy: true,
278
+ busySince: now,
233
279
  }
234
280
  : entry),
235
281
  };
@@ -247,17 +293,11 @@ export class TeamOrchestrator {
247
293
  };
248
294
  }
249
295
  buildSessionSystemPrompt(engineer, mode) {
250
- const claudeFileSection = this.projectClaudeFiles.length
251
- ? `\n\nProject Claude Files:\n${this.projectClaudeFiles
252
- .map((file) => `## ${file.relativePath}\n${file.content}`)
253
- .join('\n\n')}`
254
- : '';
255
296
  return [
256
297
  this.engineerSessionPrompt,
257
298
  '',
258
299
  `Assigned engineer: ${engineer}.`,
259
300
  `Current work mode: ${mode}.`,
260
- claudeFileSection,
261
301
  ]
262
302
  .join('\n')
263
303
  .trim();
@@ -265,18 +305,30 @@ export class TeamOrchestrator {
265
305
  buildEngineerPrompt(mode, message) {
266
306
  return `${buildModeInstruction(mode)}\n\n${message}`;
267
307
  }
268
- mapWorkModeToSessionMode(mode) {
269
- return mode === 'explore' ? 'plan' : 'free';
270
- }
271
308
  }
272
309
  function buildModeInstruction(mode) {
273
310
  switch (mode) {
274
311
  case 'explore':
275
- return 'Work in planning mode. Investigate, reason, and write the plan inline. Do not make file edits.';
312
+ return [
313
+ 'Investigation mode.',
314
+ 'Read, search, and reason about the codebase.',
315
+ 'Produce a concrete plan with specific file paths and an approach.',
316
+ 'Do not create or edit files.',
317
+ ].join(' ');
276
318
  case 'implement':
277
- return 'Work in implementation mode. Make the changes, verify them, and report clearly.';
319
+ return [
320
+ 'Implementation mode.',
321
+ 'Make the changes, run the most relevant verification (tests, lint, typecheck), and report what changed and what you verified.',
322
+ 'Before reporting done, review your own diff for issues that pass tests but break in production.',
323
+ ].join(' ');
278
324
  case 'verify':
279
- return 'Work in verification mode. Run the narrowest useful checks first, then broaden only if needed.';
325
+ return [
326
+ 'Verification mode.',
327
+ 'Run targeted checks in order of relevance: tests, lint, typecheck, build.',
328
+ 'Check that changed code paths have test coverage.',
329
+ 'Report pass/fail with evidence.',
330
+ 'Escalate failures with exact output.',
331
+ ].join(' ');
280
332
  }
281
333
  }
282
334
  function summarizeMessage(message) {
@@ -292,28 +344,29 @@ function appendWrapperHistoryEntries(existing, nextEntries) {
292
344
  }
293
345
  function buildPlanDraftRequest(perspective, request) {
294
346
  const posture = perspective === 'lead'
295
- ? 'Propose the most direct workable plan.'
296
- : 'Challenge weak assumptions, find missing decisions, and propose a stronger alternative if needed.';
347
+ ? 'You are the lead planner. Propose the most direct workable plan with concrete file paths and clear next steps. Think about failure modes and edge cases — what can break at each boundary?'
348
+ : 'You are the challenger. Stress-test assumptions, surface missing decisions, and propose a stronger alternative when the lead plan is weak. Think about failure modes and edge cases — what can break at each boundary?';
297
349
  return [
298
350
  posture,
299
351
  '',
300
352
  'Return exactly these sections:',
301
353
  '1. Objective',
302
- '2. Proposed approach',
354
+ '2. Proposed approach (include system boundaries and data flow)',
303
355
  '3. Files or systems likely involved',
304
- '4. Risks and open questions',
305
- '5. Verification',
306
- '6. Step-by-step plan',
356
+ '4. Failure modes and edge cases (what happens when things go wrong?)',
357
+ '5. Risks and open questions',
358
+ '6. Verification (how to prove it works, including what tests to add)',
359
+ '7. Step-by-step plan',
307
360
  '',
308
361
  `User request: ${request}`,
309
362
  ].join('\n');
310
363
  }
311
364
  function buildSynthesisSystemPrompt() {
312
365
  return [
313
- 'You are the CTO synthesis engine.',
314
- 'Combine two independent engineering plans into one better plan.',
315
- 'Prefer the clearest, simplest, highest-leverage path.',
316
- 'If one user decision is still required, surface exactly one recommended question and one recommended answer.',
366
+ 'You are synthesizing two independent engineering plans into one stronger plan.',
367
+ 'Compare them on clarity, feasibility, risk, and fit to the user request.',
368
+ 'Prefer the simplest path that fully addresses the goal.',
369
+ 'If the plans disagree on something only the user can decide, surface exactly one recommended question and one recommended answer.',
317
370
  'Use this output format exactly:',
318
371
  '## Synthesis',
319
372
  '<combined plan>',
@@ -8,7 +8,6 @@ export declare const ENGINEER_AGENT_IDS: {
8
8
  readonly Alex: "alex";
9
9
  };
10
10
  export declare const ENGINEER_AGENT_NAMES: readonly ["Tom", "John", "Maya", "Sara", "Alex"];
11
- export declare const ALL_RESTRICTED_TOOL_IDS: readonly ["team_status", "list_transcripts", "list_history", "git_diff", "git_commit", "git_reset", "git_status", "git_log", "approval_policy", "approval_decisions", "approval_update", "claude"];
12
11
  type ToolPermission = 'allow' | 'ask' | 'deny';
13
12
  type AgentPermission = {
14
13
  '*'?: ToolPermission;
@@ -10,6 +10,8 @@ export const ENGINEER_AGENT_IDS = {
10
10
  export const ENGINEER_AGENT_NAMES = TEAM_ENGINEERS;
11
11
  const CTO_ONLY_TOOL_IDS = [
12
12
  'team_status',
13
+ 'plan_with_team',
14
+ 'reset_engineer',
13
15
  'list_transcripts',
14
16
  'list_history',
15
17
  'git_diff',
@@ -22,7 +24,7 @@ const CTO_ONLY_TOOL_IDS = [
22
24
  'approval_update',
23
25
  ];
24
26
  const ENGINEER_TOOL_IDS = ['claude'];
25
- export const ALL_RESTRICTED_TOOL_IDS = [...CTO_ONLY_TOOL_IDS, ...ENGINEER_TOOL_IDS];
27
+ const ALL_RESTRICTED_TOOL_IDS = [...CTO_ONLY_TOOL_IDS, ...ENGINEER_TOOL_IDS];
26
28
  const CTO_READONLY_TOOLS = {
27
29
  read: 'allow',
28
30
  grep: 'allow',
@@ -70,7 +72,7 @@ function buildEngineerPermissions() {
70
72
  }
71
73
  export function buildCtoAgentConfig(prompts) {
72
74
  return {
73
- description: 'Technical orchestrator. Finds missing requirements, explicitly assigns engineers, compares plans, reviews diffs, and owns the final outcome.',
75
+ description: 'Principal engineer who orchestrates AI-powered engineers. Decomposes work, asks clarifying questions, delegates precisely, reviews diffs, and owns the outcome.',
74
76
  mode: 'primary',
75
77
  color: '#D97757',
76
78
  permission: buildCtoPermissions(),