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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +29 -31
  2. package/dist/index.d.ts +1 -1
  3. package/dist/manager/persistent-manager.d.ts +3 -23
  4. package/dist/manager/persistent-manager.js +2 -95
  5. package/dist/manager/team-orchestrator.d.ts +50 -0
  6. package/dist/manager/team-orchestrator.js +360 -0
  7. package/dist/plugin/agent-hierarchy.d.ts +12 -34
  8. package/dist/plugin/agent-hierarchy.js +36 -129
  9. package/dist/plugin/claude-manager.plugin.js +190 -445
  10. package/dist/plugin/service-factory.d.ts +18 -3
  11. package/dist/plugin/service-factory.js +32 -1
  12. package/dist/prompts/registry.d.ts +1 -10
  13. package/dist/prompts/registry.js +42 -270
  14. package/dist/src/claude/claude-agent-sdk-adapter.js +2 -1
  15. package/dist/src/claude/session-live-tailer.js +2 -2
  16. package/dist/src/index.d.ts +1 -1
  17. package/dist/src/manager/git-operations.d.ts +10 -1
  18. package/dist/src/manager/git-operations.js +18 -3
  19. package/dist/src/manager/persistent-manager.d.ts +18 -6
  20. package/dist/src/manager/persistent-manager.js +19 -13
  21. package/dist/src/manager/session-controller.d.ts +7 -10
  22. package/dist/src/manager/session-controller.js +12 -62
  23. package/dist/src/manager/team-orchestrator.d.ts +50 -0
  24. package/dist/src/manager/team-orchestrator.js +360 -0
  25. package/dist/src/plugin/agent-hierarchy.d.ts +12 -26
  26. package/dist/src/plugin/agent-hierarchy.js +36 -99
  27. package/dist/src/plugin/claude-manager.plugin.js +214 -393
  28. package/dist/src/plugin/service-factory.d.ts +18 -3
  29. package/dist/src/plugin/service-factory.js +33 -9
  30. package/dist/src/prompts/registry.d.ts +1 -10
  31. package/dist/src/prompts/registry.js +41 -246
  32. package/dist/src/state/team-state-store.d.ts +14 -0
  33. package/dist/src/state/team-state-store.js +85 -0
  34. package/dist/src/team/roster.d.ts +5 -0
  35. package/dist/src/team/roster.js +38 -0
  36. package/dist/src/types/contracts.d.ts +55 -13
  37. package/dist/src/types/contracts.js +1 -1
  38. package/dist/state/team-state-store.d.ts +14 -0
  39. package/dist/state/team-state-store.js +85 -0
  40. package/dist/team/roster.d.ts +5 -0
  41. package/dist/team/roster.js +38 -0
  42. package/dist/test/claude-manager.plugin.test.js +55 -280
  43. package/dist/test/git-operations.test.js +65 -1
  44. package/dist/test/persistent-manager.test.js +3 -3
  45. package/dist/test/prompt-registry.test.js +32 -252
  46. package/dist/test/session-controller.test.js +27 -27
  47. package/dist/test/team-orchestrator.test.d.ts +1 -0
  48. package/dist/test/team-orchestrator.test.js +146 -0
  49. package/dist/test/team-state-store.test.d.ts +1 -0
  50. package/dist/test/team-state-store.test.js +54 -0
  51. package/dist/types/contracts.d.ts +50 -23
  52. package/dist/types/contracts.js +1 -1
  53. package/package.json +1 -1
@@ -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
  });
@@ -25,9 +25,9 @@ describe('SessionController', () => {
25
25
  it('creates a new session on first sendMessage', async () => {
26
26
  const adapter = createMockAdapter();
27
27
  const tracker = new ContextTracker();
28
- const controller = new SessionController(adapter, tracker, 'test prompt');
28
+ const controller = new SessionController(adapter, tracker, 'test prompt', 'test', '/tmp');
29
29
  expect(controller.isActive).toBe(false);
30
- const result = await controller.sendMessage('/tmp', 'hello');
30
+ const result = await controller.sendMessage('hello');
31
31
  expect(controller.isActive).toBe(true);
32
32
  expect(controller.sessionId).toBe('ses_test');
33
33
  expect(result.finalText).toBe('Done.');
@@ -39,17 +39,17 @@ describe('SessionController', () => {
39
39
  it('sends settingSources as user-only', async () => {
40
40
  const adapter = createMockAdapter();
41
41
  const tracker = new ContextTracker();
42
- const controller = new SessionController(adapter, tracker, 'test');
43
- await controller.sendMessage('/tmp', 'hello');
42
+ const controller = new SessionController(adapter, tracker, 'test', 'test', '/tmp');
43
+ await controller.sendMessage('hello');
44
44
  const call = adapter.runSession.mock.calls[0][0];
45
45
  expect(call.settingSources).toEqual(['user']);
46
46
  });
47
47
  it('resumes session on subsequent sends', async () => {
48
48
  const adapter = createMockAdapter([{}, {}]);
49
49
  const tracker = new ContextTracker();
50
- const controller = new SessionController(adapter, tracker, 'test prompt');
51
- await controller.sendMessage('/tmp', 'first');
52
- await controller.sendMessage('/tmp', 'second');
50
+ const controller = new SessionController(adapter, tracker, 'test prompt', 'test', '/tmp');
51
+ await controller.sendMessage('first');
52
+ await controller.sendMessage('second');
53
53
  const secondCall = adapter.runSession.mock.calls[1][0];
54
54
  expect(secondCall.resumeSessionId).toBe('ses_test');
55
55
  expect(secondCall.systemPrompt).toBeUndefined();
@@ -57,8 +57,8 @@ describe('SessionController', () => {
57
57
  it('updates context tracker on each message', async () => {
58
58
  const adapter = createMockAdapter([{ turns: 3, totalCostUsd: 0.05, inputTokens: 50_000 }]);
59
59
  const tracker = new ContextTracker();
60
- const controller = new SessionController(adapter, tracker, 'test');
61
- await controller.sendMessage('/tmp', 'task');
60
+ const controller = new SessionController(adapter, tracker, 'test', 'test', '/tmp');
61
+ await controller.sendMessage('task');
62
62
  const snap = controller.getContextSnapshot();
63
63
  expect(snap.totalTurns).toBe(3);
64
64
  expect(snap.totalCostUsd).toBe(0.05);
@@ -68,10 +68,10 @@ describe('SessionController', () => {
68
68
  it('clears session and resets context', async () => {
69
69
  const adapter = createMockAdapter();
70
70
  const tracker = new ContextTracker();
71
- const controller = new SessionController(adapter, tracker, 'test');
72
- await controller.sendMessage('/tmp', 'task');
71
+ const controller = new SessionController(adapter, tracker, 'test', 'test', '/tmp');
72
+ await controller.sendMessage('task');
73
73
  expect(controller.isActive).toBe(true);
74
- const clearedId = await controller.clearSession('/tmp');
74
+ const clearedId = await controller.clearSession();
75
75
  expect(clearedId).toBe('ses_test');
76
76
  expect(controller.isActive).toBe(false);
77
77
  expect(controller.sessionId).toBeNull();
@@ -82,9 +82,9 @@ describe('SessionController', () => {
82
82
  it('sends /compact to current session', async () => {
83
83
  const adapter = createMockAdapter([{}, {}]);
84
84
  const tracker = new ContextTracker();
85
- const controller = new SessionController(adapter, tracker, 'test');
86
- await controller.sendMessage('/tmp', 'task');
87
- await controller.compactSession('/tmp');
85
+ const controller = new SessionController(adapter, tracker, 'test', 'test', '/tmp');
86
+ await controller.sendMessage('task');
87
+ await controller.compactSession();
88
88
  const compactCall = adapter.runSession.mock.calls[1][0];
89
89
  expect(compactCall.prompt).toBe('/compact');
90
90
  expect(compactCall.resumeSessionId).toBe('ses_test');
@@ -92,14 +92,14 @@ describe('SessionController', () => {
92
92
  it('throws when compacting without active session', async () => {
93
93
  const adapter = createMockAdapter();
94
94
  const tracker = new ContextTracker();
95
- const controller = new SessionController(adapter, tracker, 'test');
96
- await expect(controller.compactSession('/tmp')).rejects.toThrow('No active session to compact');
95
+ const controller = new SessionController(adapter, tracker, 'test', 'test', '/tmp');
96
+ await expect(controller.compactSession()).rejects.toThrow('No active session to compact');
97
97
  });
98
98
  it('threads effort through to the SDK input', async () => {
99
99
  const adapter = createMockAdapter();
100
100
  const tracker = new ContextTracker();
101
- const controller = new SessionController(adapter, tracker, 'test');
102
- await controller.sendMessage('/tmp', 'hard task', { effort: 'max' });
101
+ const controller = new SessionController(adapter, tracker, 'test', 'test', '/tmp');
102
+ await controller.sendMessage('hard task', { effort: 'max' });
103
103
  const call = adapter.runSession.mock.calls[0][0];
104
104
  expect(call.effort).toBe('max');
105
105
  });
@@ -111,8 +111,8 @@ describe('SessionController', () => {
111
111
  it('defaults to free mode with acceptEdits permissionMode', async () => {
112
112
  const adapter = createMockAdapter();
113
113
  const tracker = new ContextTracker();
114
- const controller = new SessionController(adapter, tracker, 'test', modePrefixes);
115
- await controller.sendMessage('/tmp', 'do something');
114
+ const controller = new SessionController(adapter, tracker, 'test', 'test', '/tmp', modePrefixes);
115
+ await controller.sendMessage('do something');
116
116
  const call = adapter.runSession.mock.calls[0][0];
117
117
  expect(call.permissionMode).toBe('acceptEdits');
118
118
  expect(call.prompt).toBe('do something');
@@ -120,8 +120,8 @@ describe('SessionController', () => {
120
120
  it('sets permissionMode to plan and prepends prefix in plan mode', async () => {
121
121
  const adapter = createMockAdapter();
122
122
  const tracker = new ContextTracker();
123
- const controller = new SessionController(adapter, tracker, 'test', modePrefixes);
124
- await controller.sendMessage('/tmp', 'analyze this', { mode: 'plan' });
123
+ const controller = new SessionController(adapter, tracker, 'test', 'test', '/tmp', modePrefixes);
124
+ await controller.sendMessage('analyze this', { mode: 'plan' });
125
125
  const call = adapter.runSession.mock.calls[0][0];
126
126
  expect(call.permissionMode).toBe('plan');
127
127
  expect(call.prompt).toBe('[PLAN MODE] Read-only planning.\n\nanalyze this');
@@ -129,8 +129,8 @@ describe('SessionController', () => {
129
129
  it('explicit free mode uses acceptEdits and no prefix', async () => {
130
130
  const adapter = createMockAdapter();
131
131
  const tracker = new ContextTracker();
132
- const controller = new SessionController(adapter, tracker, 'test', modePrefixes);
133
- await controller.sendMessage('/tmp', 'build it', { mode: 'free' });
132
+ const controller = new SessionController(adapter, tracker, 'test', 'test', '/tmp', modePrefixes);
133
+ await controller.sendMessage('build it', { mode: 'free' });
134
134
  const call = adapter.runSession.mock.calls[0][0];
135
135
  expect(call.permissionMode).toBe('acceptEdits');
136
136
  expect(call.prompt).toBe('build it');
@@ -138,8 +138,8 @@ describe('SessionController', () => {
138
138
  it('works without modePrefixes constructor arg (backward compat)', async () => {
139
139
  const adapter = createMockAdapter();
140
140
  const tracker = new ContextTracker();
141
- const controller = new SessionController(adapter, tracker, 'test');
142
- await controller.sendMessage('/tmp', 'hello', { mode: 'plan' });
141
+ const controller = new SessionController(adapter, tracker, 'test', 'test', '/tmp');
142
+ await controller.sendMessage('hello', { mode: 'plan' });
143
143
  const call = adapter.runSession.mock.calls[0][0];
144
144
  expect(call.permissionMode).toBe('plan');
145
145
  // Empty prefix defaults — prompt should be unchanged
@@ -0,0 +1 @@
1
+ export {};