@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.
- package/README.md +29 -31
- package/dist/index.d.ts +1 -1
- package/dist/manager/team-orchestrator.d.ts +50 -0
- package/dist/manager/team-orchestrator.js +360 -0
- package/dist/plugin/agent-hierarchy.d.ts +12 -34
- package/dist/plugin/agent-hierarchy.js +36 -129
- package/dist/plugin/claude-manager.plugin.js +233 -421
- package/dist/plugin/service-factory.d.ts +20 -3
- package/dist/plugin/service-factory.js +46 -1
- package/dist/prompts/registry.d.ts +1 -10
- package/dist/prompts/registry.js +42 -261
- package/dist/src/claude/claude-agent-sdk-adapter.js +2 -1
- package/dist/src/claude/session-live-tailer.js +2 -2
- package/dist/src/index.d.ts +1 -1
- package/dist/src/manager/git-operations.d.ts +10 -1
- package/dist/src/manager/git-operations.js +18 -3
- package/dist/src/manager/persistent-manager.d.ts +18 -6
- package/dist/src/manager/persistent-manager.js +19 -13
- package/dist/src/manager/session-controller.d.ts +7 -10
- package/dist/src/manager/session-controller.js +12 -62
- package/dist/src/manager/team-orchestrator.d.ts +50 -0
- package/dist/src/manager/team-orchestrator.js +360 -0
- package/dist/src/plugin/agent-hierarchy.d.ts +12 -26
- package/dist/src/plugin/agent-hierarchy.js +36 -99
- package/dist/src/plugin/claude-manager.plugin.js +257 -391
- package/dist/src/plugin/service-factory.d.ts +20 -3
- package/dist/src/plugin/service-factory.js +47 -9
- package/dist/src/prompts/registry.d.ts +1 -10
- package/dist/src/prompts/registry.js +41 -246
- package/dist/src/state/team-state-store.d.ts +17 -0
- package/dist/src/state/team-state-store.js +107 -0
- package/dist/src/team/roster.d.ts +5 -0
- package/dist/src/team/roster.js +38 -0
- package/dist/src/types/contracts.d.ts +55 -13
- package/dist/src/types/contracts.js +1 -1
- package/dist/state/team-state-store.d.ts +17 -0
- package/dist/state/team-state-store.js +107 -0
- package/dist/team/roster.d.ts +5 -0
- package/dist/team/roster.js +38 -0
- package/dist/test/claude-manager.plugin.test.js +55 -280
- package/dist/test/cto-active-team.test.d.ts +1 -0
- package/dist/test/cto-active-team.test.js +52 -0
- package/dist/test/git-operations.test.js +65 -1
- package/dist/test/persistent-manager.test.js +3 -3
- package/dist/test/prompt-registry.test.js +32 -252
- package/dist/test/report-claude-event.test.d.ts +1 -0
- package/dist/test/report-claude-event.test.js +246 -0
- package/dist/test/session-controller.test.js +27 -27
- package/dist/test/team-orchestrator.test.d.ts +1 -0
- package/dist/test/team-orchestrator.test.js +146 -0
- package/dist/test/team-state-store.test.d.ts +1 -0
- package/dist/test/team-state-store.test.js +72 -0
- package/dist/types/contracts.d.ts +54 -3
- package/dist/types/contracts.js +1 -1
- 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('
|
|
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(
|
|
195
|
+
const cleared = await manager.clearSession();
|
|
196
196
|
expect(cleared).toBe('ses_mock');
|
|
197
|
-
expect(sessionCtrl.clearSession).
|
|
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 {
|
|
2
|
+
import { managerPromptRegistry } from '../src/prompts/registry.js';
|
|
3
3
|
describe('managerPromptRegistry', () => {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
|
|
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 {};
|