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

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 (55) hide show
  1. package/README.md +29 -31
  2. package/dist/index.d.ts +1 -1
  3. package/dist/manager/team-orchestrator.d.ts +50 -0
  4. package/dist/manager/team-orchestrator.js +360 -0
  5. package/dist/plugin/agent-hierarchy.d.ts +12 -34
  6. package/dist/plugin/agent-hierarchy.js +36 -129
  7. package/dist/plugin/claude-manager.plugin.js +233 -421
  8. package/dist/plugin/service-factory.d.ts +20 -3
  9. package/dist/plugin/service-factory.js +46 -1
  10. package/dist/prompts/registry.d.ts +1 -10
  11. package/dist/prompts/registry.js +42 -261
  12. package/dist/src/claude/claude-agent-sdk-adapter.js +2 -1
  13. package/dist/src/claude/session-live-tailer.js +2 -2
  14. package/dist/src/index.d.ts +1 -1
  15. package/dist/src/manager/git-operations.d.ts +10 -1
  16. package/dist/src/manager/git-operations.js +18 -3
  17. package/dist/src/manager/persistent-manager.d.ts +18 -6
  18. package/dist/src/manager/persistent-manager.js +19 -13
  19. package/dist/src/manager/session-controller.d.ts +7 -10
  20. package/dist/src/manager/session-controller.js +12 -62
  21. package/dist/src/manager/team-orchestrator.d.ts +50 -0
  22. package/dist/src/manager/team-orchestrator.js +360 -0
  23. package/dist/src/plugin/agent-hierarchy.d.ts +12 -26
  24. package/dist/src/plugin/agent-hierarchy.js +36 -99
  25. package/dist/src/plugin/claude-manager.plugin.js +257 -391
  26. package/dist/src/plugin/service-factory.d.ts +20 -3
  27. package/dist/src/plugin/service-factory.js +47 -9
  28. package/dist/src/prompts/registry.d.ts +1 -10
  29. package/dist/src/prompts/registry.js +41 -246
  30. package/dist/src/state/team-state-store.d.ts +17 -0
  31. package/dist/src/state/team-state-store.js +107 -0
  32. package/dist/src/team/roster.d.ts +5 -0
  33. package/dist/src/team/roster.js +38 -0
  34. package/dist/src/types/contracts.d.ts +55 -13
  35. package/dist/src/types/contracts.js +1 -1
  36. package/dist/state/team-state-store.d.ts +17 -0
  37. package/dist/state/team-state-store.js +107 -0
  38. package/dist/team/roster.d.ts +5 -0
  39. package/dist/team/roster.js +38 -0
  40. package/dist/test/claude-manager.plugin.test.js +55 -280
  41. package/dist/test/cto-active-team.test.d.ts +1 -0
  42. package/dist/test/cto-active-team.test.js +52 -0
  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/report-claude-event.test.d.ts +1 -0
  47. package/dist/test/report-claude-event.test.js +246 -0
  48. package/dist/test/session-controller.test.js +27 -27
  49. package/dist/test/team-orchestrator.test.d.ts +1 -0
  50. package/dist/test/team-orchestrator.test.js +146 -0
  51. package/dist/test/team-state-store.test.d.ts +1 -0
  52. package/dist/test/team-state-store.test.js +72 -0
  53. package/dist/types/contracts.d.ts +54 -3
  54. package/dist/types/contracts.js +1 -1
  55. package/package.json +1 -1
@@ -0,0 +1,52 @@
1
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
2
+ import { mkdtemp, rm } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { tmpdir } from 'node:os';
5
+ import { ClaudeManagerPlugin } from '../src/plugin/claude-manager.plugin.js';
6
+ import { clearPluginServices, getActiveTeamSession } from '../src/plugin/service-factory.js';
7
+ import { AGENT_CTO } from '../src/plugin/agent-hierarchy.js';
8
+ import { TeamStateStore } from '../src/state/team-state-store.js';
9
+ describe('CTO chat.message — persisted active team', () => {
10
+ let tempRoot;
11
+ beforeEach(async () => {
12
+ tempRoot = await mkdtemp(join(tmpdir(), 'cto-team-'));
13
+ clearPluginServices();
14
+ });
15
+ afterEach(async () => {
16
+ clearPluginServices();
17
+ if (tempRoot) {
18
+ await rm(tempRoot, { recursive: true, force: true });
19
+ }
20
+ });
21
+ it('persists the active team on the first CTO message', async () => {
22
+ const plugin = await ClaudeManagerPlugin({ worktree: tempRoot });
23
+ const chatMessage = plugin['chat.message'];
24
+ await chatMessage({ agent: AGENT_CTO, sessionID: 'session-cto-1' });
25
+ const store = new TeamStateStore('.claude-manager');
26
+ await expect(store.getActiveTeam(tempRoot)).resolves.toBe('session-cto-1');
27
+ expect(getActiveTeamSession(tempRoot)).toBe('session-cto-1');
28
+ });
29
+ it('a new CTO session adopts the already-persisted active team instead of overwriting it', async () => {
30
+ const store = new TeamStateStore('.claude-manager');
31
+ // Simulate a pre-existing persisted active team (e.g., from a previous process run).
32
+ await store.setActiveTeam(tempRoot, 'old-team-id');
33
+ const plugin = await ClaudeManagerPlugin({ worktree: tempRoot });
34
+ const chatMessage = plugin['chat.message'];
35
+ // New CTO session with a different session ID.
36
+ await chatMessage({ agent: AGENT_CTO, sessionID: 'brand-new-cto-session' });
37
+ // The persisted active team must NOT be overwritten.
38
+ await expect(store.getActiveTeam(tempRoot)).resolves.toBe('old-team-id');
39
+ // The in-memory registry must point to the persisted team, NOT the new session.
40
+ expect(getActiveTeamSession(tempRoot)).toBe('old-team-id');
41
+ });
42
+ it('does not overwrite the persisted team across two CTO messages in the same session', async () => {
43
+ const store = new TeamStateStore('.claude-manager');
44
+ const plugin = await ClaudeManagerPlugin({ worktree: tempRoot });
45
+ const chatMessage = plugin['chat.message'];
46
+ await chatMessage({ agent: AGENT_CTO, sessionID: 'session-cto-1' });
47
+ await chatMessage({ agent: AGENT_CTO, sessionID: 'session-cto-1' });
48
+ // Still the original session — persisted.
49
+ await expect(store.getActiveTeam(tempRoot)).resolves.toBe('session-cto-1');
50
+ expect(getActiveTeamSession(tempRoot)).toBe('session-cto-1');
51
+ });
52
+ });
@@ -1,7 +1,7 @@
1
1
  import { describe, expect, it } from 'vitest';
2
2
  import { execFile } from 'node:child_process';
3
3
  import { promisify } from 'node:util';
4
- import { mkdtemp, writeFile } from 'node:fs/promises';
4
+ import { mkdir, mkdtemp, writeFile } from 'node:fs/promises';
5
5
  import { join } from 'node:path';
6
6
  import { tmpdir } from 'node:os';
7
7
  import { GitOperations } from '../src/manager/git-operations.js';
@@ -87,4 +87,68 @@ describe('GitOperations', () => {
87
87
  expect(result.success).toBe(false);
88
88
  expect(result.error).toBeDefined();
89
89
  });
90
+ it('filters diff by paths', async () => {
91
+ const dir = await createTestRepo();
92
+ await mkdir(join(dir, 'src'), { recursive: true });
93
+ await writeFile(join(dir, 'src/index.ts'), 'export const x = 1;\n');
94
+ await writeFile(join(dir, 'README.md'), '# Changed\n');
95
+ const git = new GitOperations(dir);
96
+ // Stage the new file so git diff can see it
97
+ await execFileAsync('git', ['add', 'src/index.ts'], { cwd: dir });
98
+ const result = await git.diff({ paths: ['src'] });
99
+ expect(result.hasDiff).toBe(true);
100
+ expect(result.diffText).toContain('src/index.ts');
101
+ expect(result.diffText).not.toContain('README.md');
102
+ });
103
+ it('shows staged diff when staged=true', async () => {
104
+ const dir = await createTestRepo();
105
+ await writeFile(join(dir, 'README.md'), '# Changed\n');
106
+ const git = new GitOperations(dir);
107
+ // First commit the current state
108
+ await execFileAsync('git', ['add', 'README.md'], { cwd: dir });
109
+ // Now make another change
110
+ await writeFile(join(dir, 'README.md'), '# Changed more\n');
111
+ await execFileAsync('git', ['add', 'README.md'], { cwd: dir });
112
+ const result = await git.diff({ staged: true });
113
+ expect(result.hasDiff).toBe(true);
114
+ expect(result.diffText).toContain('Changed more');
115
+ });
116
+ it('compares against arbitrary ref', async () => {
117
+ const dir = await createTestRepo();
118
+ await writeFile(join(dir, 'README.md'), '# Changed\n');
119
+ const git = new GitOperations(dir);
120
+ const result = await git.diff({ ref: 'HEAD' });
121
+ expect(result.hasDiff).toBe(true);
122
+ expect(result.diffText).toContain('Changed');
123
+ });
124
+ it('returns status with isClean false when changes exist', async () => {
125
+ const dir = await createTestRepo();
126
+ await writeFile(join(dir, 'README.md'), '# Changed\n');
127
+ const git = new GitOperations(dir);
128
+ const result = await git.status();
129
+ expect(result.isClean).toBe(false);
130
+ expect(result.output).toContain('README.md');
131
+ });
132
+ it('returns status with isClean true on clean repo', async () => {
133
+ const dir = await createTestRepo();
134
+ const git = new GitOperations(dir);
135
+ const result = await git.status();
136
+ expect(result.isClean).toBe(true);
137
+ expect(result.output).toBe('');
138
+ });
139
+ it('returns log with custom count', async () => {
140
+ const dir = await createTestRepo();
141
+ const git = new GitOperations(dir);
142
+ // Add more commits
143
+ await writeFile(join(dir, 'file1.txt'), '1');
144
+ await execFileAsync('git', ['add', '-A'], { cwd: dir });
145
+ await execFileAsync('git', ['commit', '-m', 'commit 1'], { cwd: dir });
146
+ await writeFile(join(dir, 'file2.txt'), '2');
147
+ await execFileAsync('git', ['add', '-A'], { cwd: dir });
148
+ await execFileAsync('git', ['commit', '-m', 'commit 2'], { cwd: dir });
149
+ const result = await git.log(3);
150
+ expect(result).toContain('commit 2');
151
+ expect(result).toContain('commit 1');
152
+ expect(result).toContain('initial');
153
+ });
90
154
  });
@@ -93,7 +93,7 @@ describe('PersistentManager', () => {
93
93
  const tracker = new ContextTracker();
94
94
  const manager = new PersistentManager(sessionCtrl, gitOps, store, tracker, createMockTranscriptStore());
95
95
  const result = await manager.sendMessage('/tmp', 'implement feature X');
96
- expect(sessionCtrl.sendMessage).toHaveBeenCalledWith('/tmp', 'implement feature X', undefined, undefined);
96
+ expect(sessionCtrl.sendMessage).toHaveBeenCalledWith('implement feature X', undefined, undefined);
97
97
  expect(result.finalText).toBe('Task completed.');
98
98
  expect(result.inputTokens).toBe(20_000);
99
99
  expect(result.outputTokens).toBe(1_000);
@@ -192,9 +192,9 @@ describe('PersistentManager', () => {
192
192
  const store = createMockStateStore();
193
193
  const tracker = new ContextTracker();
194
194
  const manager = new PersistentManager(sessionCtrl, gitOps, store, tracker, createMockTranscriptStore());
195
- const cleared = await manager.clearSession('/tmp');
195
+ const cleared = await manager.clearSession();
196
196
  expect(cleared).toBe('ses_mock');
197
- expect(sessionCtrl.clearSession).toHaveBeenCalledWith('/tmp');
197
+ expect(sessionCtrl.clearSession).toHaveBeenCalled();
198
198
  });
199
199
  it('returns status from session controller', () => {
200
200
  const sessionCtrl = createMockSessionController();
@@ -1,256 +1,36 @@
1
1
  import { describe, expect, it } from 'vitest';
2
- import { composeWrapperPrompt, managerPromptRegistry } from '../src/prompts/registry.js';
2
+ import { managerPromptRegistry } from '../src/prompts/registry.js';
3
3
  describe('managerPromptRegistry', () => {
4
- describe('ctoSystemPrompt', () => {
5
- it('establishes technical owner identity', () => {
6
- expect(managerPromptRegistry.ctoSystemPrompt).toContain('technical owner');
7
- expect(managerPromptRegistry.ctoSystemPrompt).toContain('not a ticket-taker');
8
- });
9
- it('instructs to discover the right problem before solving', () => {
10
- expect(managerPromptRegistry.ctoSystemPrompt).toContain('discover the right problem before solving it');
11
- });
12
- it('checks for hidden assumptions and root causes', () => {
13
- expect(managerPromptRegistry.ctoSystemPrompt).toContain('Hidden assumptions');
14
- expect(managerPromptRegistry.ctoSystemPrompt).toContain('root causes');
15
- expect(managerPromptRegistry.ctoSystemPrompt).toContain('abstraction leaks');
16
- });
17
- it('includes missed-opportunity check', () => {
18
- expect(managerPromptRegistry.ctoSystemPrompt).toContain('Missed-opportunity check');
19
- expect(managerPromptRegistry.ctoSystemPrompt).toContain('cleaner alternative');
20
- expect(managerPromptRegistry.ctoSystemPrompt).toContain('requester likely missed');
21
- });
22
- it('puts verification-first as core principle', () => {
23
- expect(managerPromptRegistry.ctoSystemPrompt).toContain('verification-first');
24
- expect(managerPromptRegistry.ctoSystemPrompt).toContain('Every delegation MUST include how to verify success');
25
- expect(managerPromptRegistry.ctoSystemPrompt).toContain('single highest-leverage');
26
- });
27
- it('right-sizes approach by complexity', () => {
28
- expect(managerPromptRegistry.ctoSystemPrompt).toContain('Simple tasks');
29
- expect(managerPromptRegistry.ctoSystemPrompt).toContain('Medium+ tasks');
30
- expect(managerPromptRegistry.ctoSystemPrompt).toContain('Complex tasks');
31
- expect(managerPromptRegistry.ctoSystemPrompt).toContain('Right-size your approach');
32
- });
33
- it('requires self-contained delegation with context', () => {
34
- expect(managerPromptRegistry.ctoSystemPrompt).toContain('does not have your context');
35
- expect(managerPromptRegistry.ctoSystemPrompt).toContain('self-contained');
36
- });
37
- it('requires review after every delegation', () => {
38
- expect(managerPromptRegistry.ctoSystemPrompt).toContain('Review every change');
39
- expect(managerPromptRegistry.ctoSystemPrompt).toContain('read the FULL diff');
40
- });
41
- it('forbids delegating without verification criteria', () => {
42
- expect(managerPromptRegistry.ctoSystemPrompt).toContain('Do NOT delegate without verification criteria');
43
- });
44
- it('establishes delegation-first as core principle', () => {
45
- expect(managerPromptRegistry.ctoSystemPrompt).toContain('delegation-first');
46
- expect(managerPromptRegistry.ctoSystemPrompt).toContain('default action is to delegate');
47
- });
48
- it('limits direct lookups before delegating', () => {
49
- expect(managerPromptRegistry.ctoSystemPrompt).toContain('more than 2 direct read/grep/glob lookups');
50
- expect(managerPromptRegistry.ctoSystemPrompt).toContain('stop and delegate');
51
- });
52
- it('prefers engineer_plan for repo exploration', () => {
53
- expect(managerPromptRegistry.ctoSystemPrompt).toContain('Prefer spawning `engineer_plan` for repo exploration');
54
- });
55
- it('instructs using the Task tool to invoke engineers', () => {
56
- expect(managerPromptRegistry.ctoSystemPrompt).toContain('Task tool');
57
- expect(managerPromptRegistry.ctoSystemPrompt).toContain('engineer_plan');
58
- expect(managerPromptRegistry.ctoSystemPrompt).toContain('engineer_build');
59
- });
60
- it('forbids direct engineer_* tool calls', () => {
61
- expect(managerPromptRegistry.ctoSystemPrompt).toContain('Do NOT call any engineer_* tools directly');
62
- });
63
- it('includes context efficiency guidance', () => {
64
- expect(managerPromptRegistry.ctoSystemPrompt).toContain('Context efficiency');
65
- expect(managerPromptRegistry.ctoSystemPrompt).toContain('freshSession');
66
- });
67
- it('references CTO-owned tools', () => {
68
- expect(managerPromptRegistry.ctoSystemPrompt).toContain('git_diff');
69
- expect(managerPromptRegistry.ctoSystemPrompt).toContain('git_commit');
70
- expect(managerPromptRegistry.ctoSystemPrompt).toContain('git_reset');
71
- expect(managerPromptRegistry.ctoSystemPrompt).toContain('todowrite');
72
- });
73
- it('does not reference Claude Code session internals', () => {
74
- expect(managerPromptRegistry.ctoSystemPrompt).not.toContain('compact_context');
75
- expect(managerPromptRegistry.ctoSystemPrompt).not.toContain('session_health');
76
- expect(managerPromptRegistry.ctoSystemPrompt).not.toContain('clear_session');
77
- });
78
- it('includes autonomy blockers', () => {
79
- expect(managerPromptRegistry.ctoSystemPrompt).toContain('Autonomy blockers');
80
- });
81
- });
82
- describe('engineerPlanPrompt', () => {
83
- it('establishes staff engineer identity, not a relay', () => {
84
- expect(managerPromptRegistry.engineerPlanPrompt).toContain('staff engineer');
85
- expect(managerPromptRegistry.engineerPlanPrompt).toContain('not a forwarding layer');
86
- });
87
- it('includes repo-context investigation guidance', () => {
88
- expect(managerPromptRegistry.engineerPlanPrompt).toContain('Repo-context investigation');
89
- });
90
- it('uses read/grep/glob sparingly', () => {
91
- expect(managerPromptRegistry.engineerPlanPrompt).toContain('read/grep/glob sparingly');
92
- });
93
- it('delegates after more than 2 lookups', () => {
94
- expect(managerPromptRegistry.engineerPlanPrompt).toContain('more than 2 lookups are needed, send the investigation to the engineer');
95
- });
96
- it('includes architecture framing guidance', () => {
97
- expect(managerPromptRegistry.engineerPlanPrompt).toContain('real problem');
98
- expect(managerPromptRegistry.engineerPlanPrompt).toContain('cleanest architecture');
99
- });
100
- it('allows one high-leverage clarification', () => {
101
- expect(managerPromptRegistry.engineerPlanPrompt).toContain('ONE clarification first if it materially improves architecture');
102
- });
103
- it('forbids implementation by the wrapper itself', () => {
104
- expect(managerPromptRegistry.engineerPlanPrompt).toContain('Do NOT implement changes yourself');
105
- });
106
- it('does not prohibit self-investigation', () => {
107
- expect(managerPromptRegistry.engineerPlanPrompt).not.toContain('Do NOT investigate on your own');
108
- });
109
- it('instructs using the plan-mode send tool', () => {
110
- expect(managerPromptRegistry.engineerPlanPrompt).toContain('explore');
111
- });
112
- it('includes context management', () => {
113
- expect(managerPromptRegistry.engineerPlanPrompt).toContain('session_health');
114
- expect(managerPromptRegistry.engineerPlanPrompt).toContain('compact_context');
115
- expect(managerPromptRegistry.engineerPlanPrompt).toContain('clear_session');
116
- });
117
- it('includes freshSession guidance', () => {
118
- expect(managerPromptRegistry.engineerPlanPrompt).toContain('freshSession:true');
119
- });
120
- it('requires verbatim response', () => {
121
- expect(managerPromptRegistry.engineerPlanPrompt).toContain('verbatim');
122
- });
123
- it('includes Project Claude Files guidance', () => {
124
- expect(managerPromptRegistry.engineerPlanPrompt).toContain('Using appended Project Claude Files');
125
- expect(managerPromptRegistry.engineerPlanPrompt).toContain('Extract only the rules relevant to the current task');
126
- expect(managerPromptRegistry.engineerPlanPrompt).toContain('specific/nested paths over root-level guidance');
127
- expect(managerPromptRegistry.engineerPlanPrompt).toContain('Direct user instructions override Claude-file guidance');
128
- });
129
- });
130
- describe('engineerBuildPrompt', () => {
131
- it('establishes staff engineer identity, not a relay', () => {
132
- expect(managerPromptRegistry.engineerBuildPrompt).toContain('staff engineer');
133
- expect(managerPromptRegistry.engineerBuildPrompt).toContain('not a forwarding layer');
134
- });
135
- it('includes repo-context investigation guidance', () => {
136
- expect(managerPromptRegistry.engineerBuildPrompt).toContain('Repo-context investigation');
137
- });
138
- it('uses read/grep/glob sparingly', () => {
139
- expect(managerPromptRegistry.engineerBuildPrompt).toContain('read/grep/glob sparingly');
140
- });
141
- it('delegates after more than 2 lookups', () => {
142
- expect(managerPromptRegistry.engineerBuildPrompt).toContain('more than 2 lookups are needed, send the investigation to the engineer');
143
- });
144
- it('includes architecture framing guidance', () => {
145
- expect(managerPromptRegistry.engineerBuildPrompt).toContain('real problem');
146
- expect(managerPromptRegistry.engineerBuildPrompt).toContain('cleanest architecture');
147
- });
148
- it('allows one high-leverage clarification', () => {
149
- expect(managerPromptRegistry.engineerBuildPrompt).toContain('ONE clarification first if it materially improves architecture');
150
- });
151
- it('forbids implementation by the wrapper itself', () => {
152
- expect(managerPromptRegistry.engineerBuildPrompt).toContain('Do NOT implement changes yourself');
153
- });
154
- it('does not prohibit self-investigation', () => {
155
- expect(managerPromptRegistry.engineerBuildPrompt).not.toContain('Do NOT investigate on your own');
156
- });
157
- it('instructs using the build-mode send tool', () => {
158
- expect(managerPromptRegistry.engineerBuildPrompt).toContain('implement');
159
- });
160
- it('includes context management', () => {
161
- expect(managerPromptRegistry.engineerBuildPrompt).toContain('session_health');
162
- expect(managerPromptRegistry.engineerBuildPrompt).toContain('compact_context');
163
- });
164
- it('includes freshSession guidance', () => {
165
- expect(managerPromptRegistry.engineerBuildPrompt).toContain('freshSession:true');
166
- });
167
- it('includes effort max for hard problems', () => {
168
- expect(managerPromptRegistry.engineerBuildPrompt).toContain('effort "max"');
169
- });
170
- it('requires verbatim response', () => {
171
- expect(managerPromptRegistry.engineerBuildPrompt).toContain('verbatim');
172
- });
173
- it('includes Project Claude Files guidance', () => {
174
- expect(managerPromptRegistry.engineerBuildPrompt).toContain('Using appended Project Claude Files');
175
- expect(managerPromptRegistry.engineerBuildPrompt).toContain('Extract only the rules relevant to the current task');
176
- expect(managerPromptRegistry.engineerBuildPrompt).toContain('specific/nested paths over root-level guidance');
177
- expect(managerPromptRegistry.engineerBuildPrompt).toContain('Direct user instructions override Claude-file guidance');
178
- });
179
- });
180
- describe('engineerSessionPrompt', () => {
181
- it('establishes expert engineer identity', () => {
182
- expect(managerPromptRegistry.engineerSessionPrompt).toContain('expert engineer');
183
- });
184
- it('requires self-verification', () => {
185
- expect(managerPromptRegistry.engineerSessionPrompt).toContain('verify your own work before reporting done');
186
- });
187
- it('runs tests even if not explicitly asked', () => {
188
- expect(managerPromptRegistry.engineerSessionPrompt).toContain('still run relevant tests if they exist');
189
- });
190
- it('forbids git operations', () => {
191
- expect(managerPromptRegistry.engineerSessionPrompt).toContain('git commit');
192
- expect(managerPromptRegistry.engineerSessionPrompt).toContain('do NOT run');
193
- });
194
- it('requires structured reporting', () => {
195
- expect(managerPromptRegistry.engineerSessionPrompt).toContain('what was done, what was verified, what passed/failed');
196
- });
197
- });
198
- describe('modePrefixes', () => {
199
- it('has both plan and free keys', () => {
200
- expect(managerPromptRegistry.modePrefixes).toHaveProperty('plan');
201
- expect(managerPromptRegistry.modePrefixes).toHaveProperty('free');
202
- });
203
- it('plan prefix enforces read-only', () => {
204
- expect(managerPromptRegistry.modePrefixes.plan).toContain('PLAN MODE');
205
- expect(managerPromptRegistry.modePrefixes.plan).toContain('Do NOT create or edit');
206
- });
207
- it('free prefix is empty', () => {
208
- expect(managerPromptRegistry.modePrefixes.free).toBe('');
209
- });
210
- });
211
- describe('contextWarnings', () => {
212
- it('ships templates with placeholders', () => {
213
- expect(managerPromptRegistry.contextWarnings.moderate).toContain('{percent}');
214
- expect(managerPromptRegistry.contextWarnings.high).toContain('{turns}');
215
- expect(managerPromptRegistry.contextWarnings.critical).toContain('Clear the session');
216
- });
217
- });
218
- });
219
- describe('composeWrapperPrompt', () => {
220
- const base = 'You manage a Claude Code engineer.';
221
- it('returns base prompt unchanged when no Claude files provided', () => {
222
- expect(composeWrapperPrompt(base, [])).toBe(base);
223
- });
224
- it('appends a single file with path-labeled section', () => {
225
- const files = [
226
- { relativePath: 'CLAUDE.md', content: 'Use pnpm. No default exports.' },
227
- ];
228
- const result = composeWrapperPrompt(base, files);
229
- expect(result).toContain('## Project Claude Files');
230
- expect(result).toContain('### CLAUDE.md');
231
- expect(result).toContain('Use pnpm. No default exports.');
232
- });
233
- it('appends multiple files with distinct path headers', () => {
234
- const files = [
235
- { relativePath: 'CLAUDE.md', content: 'root rules' },
236
- { relativePath: '.claude/settings.md', content: 'settings content' },
237
- { relativePath: 'packages/core/CLAUDE.md', content: 'core rules' },
238
- ];
239
- const result = composeWrapperPrompt(base, files);
240
- expect(result).toContain('### CLAUDE.md\nroot rules');
241
- expect(result).toContain('### .claude/settings.md\nsettings content');
242
- expect(result).toContain('### packages/core/CLAUDE.md\ncore rules');
243
- });
244
- it('preserves multi-line file content', () => {
245
- const files = [
246
- { relativePath: 'CLAUDE.md', content: 'Line one\nLine two\nLine three' },
247
- ];
248
- const result = composeWrapperPrompt(base, files);
249
- expect(result).toContain('Line one\nLine two\nLine three');
250
- });
251
- it('starts with the base prompt', () => {
252
- const files = [{ relativePath: 'CLAUDE.md', content: 'rules' }];
253
- const result = composeWrapperPrompt(base, files);
254
- expect(result.startsWith(base)).toBe(true);
4
+ it('gives the CTO explicit orchestration guidance', () => {
5
+ expect(managerPromptRegistry.ctoSystemPrompt).toContain('You are the CTO');
6
+ expect(managerPromptRegistry.ctoSystemPrompt).toContain('Task tool');
7
+ expect(managerPromptRegistry.ctoSystemPrompt).toContain('spawn two engineers in parallel');
8
+ expect(managerPromptRegistry.ctoSystemPrompt).toContain('question');
9
+ expect(managerPromptRegistry.ctoSystemPrompt).toContain('Tom, John, Maya, Sara, and Alex');
10
+ expect(managerPromptRegistry.ctoSystemPrompt).not.toContain('clear_session');
11
+ expect(managerPromptRegistry.ctoSystemPrompt).not.toContain('freshSession');
12
+ });
13
+ it('keeps the engineer wrapper prompt short and tool-focused', () => {
14
+ expect(managerPromptRegistry.engineerAgentPrompt).toContain('named engineer');
15
+ expect(managerPromptRegistry.engineerAgentPrompt).toContain('claude');
16
+ expect(managerPromptRegistry.engineerAgentPrompt).toContain('remembers prior turns');
17
+ expect(managerPromptRegistry.engineerAgentPrompt).toContain('wrapper context');
18
+ expect(managerPromptRegistry.engineerAgentPrompt).not.toContain('read/grep/glob');
19
+ });
20
+ it('keeps the engineer session prompt direct and repo-aware', () => {
21
+ expect(managerPromptRegistry.engineerSessionPrompt).toContain('expert software engineer');
22
+ expect(managerPromptRegistry.engineerSessionPrompt).toContain('Execute directly');
23
+ expect(managerPromptRegistry.engineerSessionPrompt).toContain('Verify your own work');
24
+ expect(managerPromptRegistry.engineerSessionPrompt).toContain('Do not run git commit');
25
+ });
26
+ it('still exposes plan and free mode prefixes', () => {
27
+ expect(managerPromptRegistry.modePrefixes.plan).toContain('PLAN MODE');
28
+ expect(managerPromptRegistry.modePrefixes.plan).toContain('Read-only');
29
+ expect(managerPromptRegistry.modePrefixes.free).toBe('');
30
+ });
31
+ it('keeps context warnings available for engineer sessions', () => {
32
+ expect(managerPromptRegistry.contextWarnings.moderate).toContain('{percent}');
33
+ expect(managerPromptRegistry.contextWarnings.high).toContain('{turns}');
34
+ expect(managerPromptRegistry.contextWarnings.critical).toContain('near capacity');
255
35
  });
256
36
  });
@@ -0,0 +1 @@
1
+ export {};