@doingdev/opencode-claude-manager-plugin 0.1.65 → 0.1.66
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/dist/index.d.ts +1 -1
- package/dist/manager/team-orchestrator.js +1 -1
- package/dist/plugin/agents/common.d.ts +2 -2
- package/dist/plugin/agents/common.js +5 -0
- package/dist/plugin/claude-manager.plugin.js +104 -0
- package/dist/plugin/inbox-ops.d.ts +50 -0
- package/dist/plugin/inbox-ops.js +166 -0
- package/dist/types/contracts.d.ts +18 -0
- package/package.json +1 -1
- package/dist/claude/session-live-tailer.d.ts +0 -51
- package/dist/claude/session-live-tailer.js +0 -269
- package/dist/manager/session-controller.d.ts +0 -41
- package/dist/manager/session-controller.js +0 -97
- package/dist/metadata/claude-metadata.service.d.ts +0 -12
- package/dist/metadata/claude-metadata.service.js +0 -38
- package/dist/metadata/repo-claude-config-reader.d.ts +0 -7
- package/dist/metadata/repo-claude-config-reader.js +0 -154
- package/dist/plugin/orchestrator.plugin.d.ts +0 -2
- package/dist/plugin/orchestrator.plugin.js +0 -116
- package/dist/providers/claude-code-wrapper.d.ts +0 -13
- package/dist/providers/claude-code-wrapper.js +0 -13
- package/dist/safety/bash-safety.d.ts +0 -21
- package/dist/safety/bash-safety.js +0 -62
- package/dist/src/claude/claude-agent-sdk-adapter.d.ts +0 -28
- package/dist/src/claude/claude-agent-sdk-adapter.js +0 -559
- package/dist/src/claude/claude-session.service.d.ts +0 -9
- package/dist/src/claude/claude-session.service.js +0 -15
- package/dist/src/claude/session-live-tailer.d.ts +0 -51
- package/dist/src/claude/session-live-tailer.js +0 -269
- package/dist/src/claude/tool-approval-manager.d.ts +0 -30
- package/dist/src/claude/tool-approval-manager.js +0 -279
- package/dist/src/index.d.ts +0 -5
- package/dist/src/index.js +0 -3
- package/dist/src/manager/context-tracker.d.ts +0 -32
- package/dist/src/manager/context-tracker.js +0 -103
- package/dist/src/manager/git-operations.d.ts +0 -18
- package/dist/src/manager/git-operations.js +0 -86
- package/dist/src/manager/persistent-manager.d.ts +0 -39
- package/dist/src/manager/persistent-manager.js +0 -44
- package/dist/src/manager/session-controller.d.ts +0 -41
- package/dist/src/manager/session-controller.js +0 -97
- package/dist/src/manager/team-orchestrator.d.ts +0 -81
- package/dist/src/manager/team-orchestrator.js +0 -612
- package/dist/src/plugin/agent-hierarchy.d.ts +0 -1
- package/dist/src/plugin/agent-hierarchy.js +0 -2
- package/dist/src/plugin/agents/browser-qa.d.ts +0 -14
- package/dist/src/plugin/agents/browser-qa.js +0 -31
- package/dist/src/plugin/agents/common.d.ts +0 -36
- package/dist/src/plugin/agents/common.js +0 -59
- package/dist/src/plugin/agents/cto.d.ts +0 -9
- package/dist/src/plugin/agents/cto.js +0 -39
- package/dist/src/plugin/agents/engineers.d.ts +0 -9
- package/dist/src/plugin/agents/engineers.js +0 -11
- package/dist/src/plugin/agents/index.d.ts +0 -5
- package/dist/src/plugin/agents/index.js +0 -5
- package/dist/src/plugin/agents/team-planner.d.ts +0 -10
- package/dist/src/plugin/agents/team-planner.js +0 -23
- package/dist/src/plugin/claude-manager.plugin.d.ts +0 -10
- package/dist/src/plugin/claude-manager.plugin.js +0 -950
- package/dist/src/plugin/service-factory.d.ts +0 -38
- package/dist/src/plugin/service-factory.js +0 -101
- package/dist/src/prompts/registry.d.ts +0 -2
- package/dist/src/prompts/registry.js +0 -210
- package/dist/src/state/file-run-state-store.d.ts +0 -14
- package/dist/src/state/file-run-state-store.js +0 -85
- package/dist/src/state/team-state-store.d.ts +0 -14
- package/dist/src/state/team-state-store.js +0 -88
- package/dist/src/state/transcript-store.d.ts +0 -15
- package/dist/src/state/transcript-store.js +0 -44
- package/dist/src/team/roster.d.ts +0 -5
- package/dist/src/team/roster.js +0 -40
- package/dist/src/types/contracts.d.ts +0 -261
- package/dist/src/types/contracts.js +0 -2
- package/dist/src/util/fs-helpers.d.ts +0 -8
- package/dist/src/util/fs-helpers.js +0 -21
- package/dist/src/util/project-context.d.ts +0 -10
- package/dist/src/util/project-context.js +0 -105
- package/dist/src/util/transcript-append.d.ts +0 -7
- package/dist/src/util/transcript-append.js +0 -29
- package/dist/state/file-run-state-store.d.ts +0 -14
- package/dist/state/file-run-state-store.js +0 -85
- package/dist/test/claude-agent-sdk-adapter.test.d.ts +0 -1
- package/dist/test/claude-agent-sdk-adapter.test.js +0 -707
- package/dist/test/claude-manager.plugin.test.d.ts +0 -1
- package/dist/test/claude-manager.plugin.test.js +0 -316
- package/dist/test/context-tracker.test.d.ts +0 -1
- package/dist/test/context-tracker.test.js +0 -130
- package/dist/test/cto-active-team.test.d.ts +0 -1
- package/dist/test/cto-active-team.test.js +0 -199
- package/dist/test/file-run-state-store.test.d.ts +0 -1
- package/dist/test/file-run-state-store.test.js +0 -82
- package/dist/test/fs-helpers.test.d.ts +0 -1
- package/dist/test/fs-helpers.test.js +0 -56
- package/dist/test/git-operations.test.d.ts +0 -1
- package/dist/test/git-operations.test.js +0 -133
- package/dist/test/persistent-manager.test.d.ts +0 -1
- package/dist/test/persistent-manager.test.js +0 -48
- package/dist/test/project-context.test.d.ts +0 -1
- package/dist/test/project-context.test.js +0 -92
- package/dist/test/prompt-registry.test.d.ts +0 -1
- package/dist/test/prompt-registry.test.js +0 -117
- package/dist/test/report-claude-event.test.d.ts +0 -1
- package/dist/test/report-claude-event.test.js +0 -304
- package/dist/test/session-controller.test.d.ts +0 -1
- package/dist/test/session-controller.test.js +0 -149
- package/dist/test/session-live-tailer.test.d.ts +0 -1
- package/dist/test/session-live-tailer.test.js +0 -313
- package/dist/test/team-orchestrator.test.d.ts +0 -1
- package/dist/test/team-orchestrator.test.js +0 -583
- package/dist/test/team-state-store.test.d.ts +0 -1
- package/dist/test/team-state-store.test.js +0 -54
- package/dist/test/tool-approval-manager.test.d.ts +0 -1
- package/dist/test/tool-approval-manager.test.js +0 -260
- package/dist/test/transcript-append.test.d.ts +0 -1
- package/dist/test/transcript-append.test.js +0 -37
- package/dist/test/transcript-store.test.d.ts +0 -1
- package/dist/test/transcript-store.test.js +0 -50
- package/dist/test/undo-propagation.test.d.ts +0 -1
- package/dist/test/undo-propagation.test.js +0 -837
- package/dist/util/project-context.d.ts +0 -10
- package/dist/util/project-context.js +0 -105
- package/dist/vitest.config.d.ts +0 -2
- package/dist/vitest.config.js +0 -11
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { mkdtemp, rm } from 'node:fs/promises';
|
|
2
|
-
import os from 'node:os';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import { afterEach, describe, expect, it } from 'vitest';
|
|
5
|
-
import { FileRunStateStore } from '../src/state/file-run-state-store.js';
|
|
6
|
-
const tempDirectories = [];
|
|
7
|
-
function createRun(cwd, id) {
|
|
8
|
-
return {
|
|
9
|
-
id,
|
|
10
|
-
cwd,
|
|
11
|
-
task: 'Implement feature',
|
|
12
|
-
status: 'completed',
|
|
13
|
-
createdAt: new Date().toISOString(),
|
|
14
|
-
updatedAt: new Date().toISOString(),
|
|
15
|
-
sessionId: null,
|
|
16
|
-
sessionHistory: [],
|
|
17
|
-
messages: [],
|
|
18
|
-
actions: [],
|
|
19
|
-
commits: [],
|
|
20
|
-
context: {
|
|
21
|
-
sessionId: null,
|
|
22
|
-
totalTurns: 0,
|
|
23
|
-
totalCostUsd: 0,
|
|
24
|
-
latestInputTokens: null,
|
|
25
|
-
latestOutputTokens: null,
|
|
26
|
-
contextWindowSize: null,
|
|
27
|
-
estimatedContextPercent: null,
|
|
28
|
-
warningLevel: 'ok',
|
|
29
|
-
compactionCount: 0,
|
|
30
|
-
},
|
|
31
|
-
finalSummary: 'Done.',
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
describe('FileRunStateStore', () => {
|
|
35
|
-
afterEach(async () => {
|
|
36
|
-
await Promise.all(tempDirectories.splice(0).map((directory) => rm(directory, { recursive: true, force: true })));
|
|
37
|
-
});
|
|
38
|
-
it('persists and reloads run records', async () => {
|
|
39
|
-
const cwd = await mkdtemp(path.join(os.tmpdir(), 'file-run-state-store-'));
|
|
40
|
-
tempDirectories.push(cwd);
|
|
41
|
-
const store = new FileRunStateStore();
|
|
42
|
-
const run = createRun(cwd, 'run_1');
|
|
43
|
-
await store.saveRun(run);
|
|
44
|
-
const loadedRun = await store.getRun(cwd, 'run_1');
|
|
45
|
-
expect(loadedRun).toMatchObject({ id: 'run_1', finalSummary: 'Done.' });
|
|
46
|
-
expect(await store.listRuns(cwd)).toHaveLength(1);
|
|
47
|
-
});
|
|
48
|
-
it('serializes concurrent updates for the same run', async () => {
|
|
49
|
-
const cwd = await mkdtemp(path.join(os.tmpdir(), 'file-run-state-store-'));
|
|
50
|
-
tempDirectories.push(cwd);
|
|
51
|
-
const store = new FileRunStateStore();
|
|
52
|
-
const run = createRun(cwd, 'run_2');
|
|
53
|
-
run.status = 'running';
|
|
54
|
-
await store.saveRun(run);
|
|
55
|
-
await Promise.all([
|
|
56
|
-
store.updateRun(cwd, 'run_2', (currentRun) => ({
|
|
57
|
-
...currentRun,
|
|
58
|
-
messages: [
|
|
59
|
-
...currentRun.messages,
|
|
60
|
-
{
|
|
61
|
-
timestamp: new Date().toISOString(),
|
|
62
|
-
direction: 'sent',
|
|
63
|
-
text: 'Task A',
|
|
64
|
-
},
|
|
65
|
-
],
|
|
66
|
-
})),
|
|
67
|
-
store.updateRun(cwd, 'run_2', (currentRun) => ({
|
|
68
|
-
...currentRun,
|
|
69
|
-
messages: [
|
|
70
|
-
...currentRun.messages,
|
|
71
|
-
{
|
|
72
|
-
timestamp: new Date().toISOString(),
|
|
73
|
-
direction: 'sent',
|
|
74
|
-
text: 'Task B',
|
|
75
|
-
},
|
|
76
|
-
],
|
|
77
|
-
})),
|
|
78
|
-
]);
|
|
79
|
-
const updatedRun = await store.getRun(cwd, 'run_2');
|
|
80
|
-
expect(updatedRun?.messages.map((m) => m.text).sort()).toEqual(['Task A', 'Task B']);
|
|
81
|
-
});
|
|
82
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { afterEach, describe, expect, it } from 'vitest';
|
|
2
|
-
import { mkdtemp, readFile, rm } from 'node:fs/promises';
|
|
3
|
-
import { join } from 'node:path';
|
|
4
|
-
import { tmpdir } from 'node:os';
|
|
5
|
-
import { appendDebugLog } from '../src/util/fs-helpers.js';
|
|
6
|
-
describe('appendDebugLog', () => {
|
|
7
|
-
let tmpDir;
|
|
8
|
-
afterEach(async () => {
|
|
9
|
-
if (tmpDir) {
|
|
10
|
-
await rm(tmpDir, { recursive: true, force: true });
|
|
11
|
-
}
|
|
12
|
-
});
|
|
13
|
-
it('creates the log file and parent directories if they do not exist', async () => {
|
|
14
|
-
tmpDir = await mkdtemp(join(tmpdir(), 'fs-helpers-'));
|
|
15
|
-
const logPath = join(tmpDir, 'nested', 'dir', 'debug.log');
|
|
16
|
-
await appendDebugLog(logPath, { type: 'test', value: 42 });
|
|
17
|
-
const content = await readFile(logPath, 'utf8');
|
|
18
|
-
expect(content).toBeTruthy();
|
|
19
|
-
});
|
|
20
|
-
it('writes a valid JSON object with a ts field on each line', async () => {
|
|
21
|
-
tmpDir = await mkdtemp(join(tmpdir(), 'fs-helpers-'));
|
|
22
|
-
const logPath = join(tmpDir, 'debug.log');
|
|
23
|
-
await appendDebugLog(logPath, { type: 'tool_denied', toolName: 'Edit' });
|
|
24
|
-
const content = await readFile(logPath, 'utf8');
|
|
25
|
-
const line = content.trim();
|
|
26
|
-
const entry = JSON.parse(line);
|
|
27
|
-
expect(entry.type).toBe('tool_denied');
|
|
28
|
-
expect(entry.toolName).toBe('Edit');
|
|
29
|
-
expect(typeof entry.ts).toBe('string');
|
|
30
|
-
// ts should be a valid ISO date string
|
|
31
|
-
expect(() => new Date(entry.ts).toISOString()).not.toThrow();
|
|
32
|
-
});
|
|
33
|
-
it('appends multiple entries as separate NDJSON lines', async () => {
|
|
34
|
-
tmpDir = await mkdtemp(join(tmpdir(), 'fs-helpers-'));
|
|
35
|
-
const logPath = join(tmpDir, 'debug.log');
|
|
36
|
-
await appendDebugLog(logPath, { type: 'a' });
|
|
37
|
-
await appendDebugLog(logPath, { type: 'b' });
|
|
38
|
-
await appendDebugLog(logPath, { type: 'c' });
|
|
39
|
-
const content = await readFile(logPath, 'utf8');
|
|
40
|
-
const lines = content.trim().split('\n');
|
|
41
|
-
expect(lines).toHaveLength(3);
|
|
42
|
-
const types = lines.map((l) => JSON.parse(l).type);
|
|
43
|
-
expect(types).toEqual(['a', 'b', 'c']);
|
|
44
|
-
});
|
|
45
|
-
it('injected ts field overrides a ts in the entry', async () => {
|
|
46
|
-
tmpDir = await mkdtemp(join(tmpdir(), 'fs-helpers-'));
|
|
47
|
-
const logPath = join(tmpDir, 'debug.log');
|
|
48
|
-
// The spread order means our ts wins over any ts in entry
|
|
49
|
-
await appendDebugLog(logPath, { ts: 'caller-value', type: 'x' });
|
|
50
|
-
const content = await readFile(logPath, 'utf8');
|
|
51
|
-
const entry = JSON.parse(content.trim());
|
|
52
|
-
// ts should be a real ISO date, not 'caller-value'
|
|
53
|
-
expect(entry.ts).not.toBe('caller-value');
|
|
54
|
-
expect(() => new Date(entry.ts).toISOString()).not.toThrow();
|
|
55
|
-
});
|
|
56
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { execFile } from 'node:child_process';
|
|
3
|
-
import { promisify } from 'node:util';
|
|
4
|
-
import { mkdir, mkdtemp, writeFile } from 'node:fs/promises';
|
|
5
|
-
import { join } from 'node:path';
|
|
6
|
-
import { tmpdir } from 'node:os';
|
|
7
|
-
import { GitOperations } from '../src/manager/git-operations.js';
|
|
8
|
-
const execFileAsync = promisify(execFile);
|
|
9
|
-
async function createTestRepo() {
|
|
10
|
-
const dir = await mkdtemp(join(tmpdir(), 'git-ops-test-'));
|
|
11
|
-
await execFileAsync('git', ['init'], { cwd: dir });
|
|
12
|
-
await execFileAsync('git', ['config', 'user.email', 'test@test.com'], {
|
|
13
|
-
cwd: dir,
|
|
14
|
-
});
|
|
15
|
-
await execFileAsync('git', ['config', 'user.name', 'Test'], { cwd: dir });
|
|
16
|
-
// Create initial commit
|
|
17
|
-
await writeFile(join(dir, 'README.md'), '# Test\n');
|
|
18
|
-
await execFileAsync('git', ['add', '-A'], { cwd: dir });
|
|
19
|
-
await execFileAsync('git', ['commit', '-m', 'initial'], { cwd: dir });
|
|
20
|
-
return dir;
|
|
21
|
-
}
|
|
22
|
-
describe('GitOperations', () => {
|
|
23
|
-
it('reports no diff on clean repo', async () => {
|
|
24
|
-
const dir = await createTestRepo();
|
|
25
|
-
const git = new GitOperations(dir);
|
|
26
|
-
const result = await git.diff();
|
|
27
|
-
expect(result.hasDiff).toBe(false);
|
|
28
|
-
expect(result.diffText.trim()).toBe('');
|
|
29
|
-
expect(result.stats.filesChanged).toBe(0);
|
|
30
|
-
});
|
|
31
|
-
it('reports diff when files are modified', async () => {
|
|
32
|
-
const dir = await createTestRepo();
|
|
33
|
-
await writeFile(join(dir, 'README.md'), '# Changed\n');
|
|
34
|
-
const git = new GitOperations(dir);
|
|
35
|
-
const result = await git.diff();
|
|
36
|
-
expect(result.hasDiff).toBe(true);
|
|
37
|
-
expect(result.diffText).toContain('Changed');
|
|
38
|
-
expect(result.stats.filesChanged).toBe(1);
|
|
39
|
-
expect(result.stats.insertions).toBeGreaterThan(0);
|
|
40
|
-
});
|
|
41
|
-
it('commits all changes', async () => {
|
|
42
|
-
const dir = await createTestRepo();
|
|
43
|
-
await writeFile(join(dir, 'new-file.ts'), 'export const x = 1;\n');
|
|
44
|
-
const git = new GitOperations(dir);
|
|
45
|
-
const result = await git.commit('add new file');
|
|
46
|
-
expect(result.success).toBe(true);
|
|
47
|
-
expect(result.output).toContain('add new file');
|
|
48
|
-
// Verify clean after commit
|
|
49
|
-
const diff = await git.diff();
|
|
50
|
-
expect(diff.hasDiff).toBe(false);
|
|
51
|
-
});
|
|
52
|
-
it('resets hard to discard changes', async () => {
|
|
53
|
-
const dir = await createTestRepo();
|
|
54
|
-
await writeFile(join(dir, 'README.md'), '# Changed\n');
|
|
55
|
-
await writeFile(join(dir, 'untracked.ts'), 'junk');
|
|
56
|
-
const git = new GitOperations(dir);
|
|
57
|
-
const result = await git.resetHard();
|
|
58
|
-
expect(result.success).toBe(true);
|
|
59
|
-
const diff = await git.diff();
|
|
60
|
-
expect(diff.hasDiff).toBe(false);
|
|
61
|
-
});
|
|
62
|
-
it('handles commit failure on clean repo', async () => {
|
|
63
|
-
const dir = await createTestRepo();
|
|
64
|
-
const git = new GitOperations(dir);
|
|
65
|
-
const result = await git.commit('nothing to commit');
|
|
66
|
-
expect(result.success).toBe(false);
|
|
67
|
-
expect(result.error).toBeDefined();
|
|
68
|
-
});
|
|
69
|
-
it('filters diff by paths', async () => {
|
|
70
|
-
const dir = await createTestRepo();
|
|
71
|
-
await mkdir(join(dir, 'src'), { recursive: true });
|
|
72
|
-
await writeFile(join(dir, 'src/index.ts'), 'export const x = 1;\n');
|
|
73
|
-
await writeFile(join(dir, 'README.md'), '# Changed\n');
|
|
74
|
-
const git = new GitOperations(dir);
|
|
75
|
-
// Stage the new file so git diff can see it
|
|
76
|
-
await execFileAsync('git', ['add', 'src/index.ts'], { cwd: dir });
|
|
77
|
-
const result = await git.diff({ paths: ['src'] });
|
|
78
|
-
expect(result.hasDiff).toBe(true);
|
|
79
|
-
expect(result.diffText).toContain('src/index.ts');
|
|
80
|
-
expect(result.diffText).not.toContain('README.md');
|
|
81
|
-
});
|
|
82
|
-
it('shows staged diff when staged=true', async () => {
|
|
83
|
-
const dir = await createTestRepo();
|
|
84
|
-
await writeFile(join(dir, 'README.md'), '# Changed\n');
|
|
85
|
-
const git = new GitOperations(dir);
|
|
86
|
-
// First commit the current state
|
|
87
|
-
await execFileAsync('git', ['add', 'README.md'], { cwd: dir });
|
|
88
|
-
// Now make another change
|
|
89
|
-
await writeFile(join(dir, 'README.md'), '# Changed more\n');
|
|
90
|
-
await execFileAsync('git', ['add', 'README.md'], { cwd: dir });
|
|
91
|
-
const result = await git.diff({ staged: true });
|
|
92
|
-
expect(result.hasDiff).toBe(true);
|
|
93
|
-
expect(result.diffText).toContain('Changed more');
|
|
94
|
-
});
|
|
95
|
-
it('compares against arbitrary ref', async () => {
|
|
96
|
-
const dir = await createTestRepo();
|
|
97
|
-
await writeFile(join(dir, 'README.md'), '# Changed\n');
|
|
98
|
-
const git = new GitOperations(dir);
|
|
99
|
-
const result = await git.diff({ ref: 'HEAD' });
|
|
100
|
-
expect(result.hasDiff).toBe(true);
|
|
101
|
-
expect(result.diffText).toContain('Changed');
|
|
102
|
-
});
|
|
103
|
-
it('returns status with isClean false when changes exist', async () => {
|
|
104
|
-
const dir = await createTestRepo();
|
|
105
|
-
await writeFile(join(dir, 'README.md'), '# Changed\n');
|
|
106
|
-
const git = new GitOperations(dir);
|
|
107
|
-
const result = await git.status();
|
|
108
|
-
expect(result.isClean).toBe(false);
|
|
109
|
-
expect(result.output).toContain('README.md');
|
|
110
|
-
});
|
|
111
|
-
it('returns status with isClean true on clean repo', async () => {
|
|
112
|
-
const dir = await createTestRepo();
|
|
113
|
-
const git = new GitOperations(dir);
|
|
114
|
-
const result = await git.status();
|
|
115
|
-
expect(result.isClean).toBe(true);
|
|
116
|
-
expect(result.output).toBe('');
|
|
117
|
-
});
|
|
118
|
-
it('returns log with custom count', async () => {
|
|
119
|
-
const dir = await createTestRepo();
|
|
120
|
-
const git = new GitOperations(dir);
|
|
121
|
-
// Add more commits
|
|
122
|
-
await writeFile(join(dir, 'file1.txt'), '1');
|
|
123
|
-
await execFileAsync('git', ['add', '-A'], { cwd: dir });
|
|
124
|
-
await execFileAsync('git', ['commit', '-m', 'commit 1'], { cwd: dir });
|
|
125
|
-
await writeFile(join(dir, 'file2.txt'), '2');
|
|
126
|
-
await execFileAsync('git', ['add', '-A'], { cwd: dir });
|
|
127
|
-
await execFileAsync('git', ['commit', '-m', 'commit 2'], { cwd: dir });
|
|
128
|
-
const result = await git.log(3);
|
|
129
|
-
expect(result).toContain('commit 2');
|
|
130
|
-
expect(result).toContain('commit 1');
|
|
131
|
-
expect(result).toContain('initial');
|
|
132
|
-
});
|
|
133
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { PersistentManager } from '../src/manager/persistent-manager.js';
|
|
3
|
-
function createMockGitOps() {
|
|
4
|
-
return {
|
|
5
|
-
diff: vi.fn(async () => ({
|
|
6
|
-
hasDiff: true,
|
|
7
|
-
diffText: 'diff --git a/file.ts\n+new line',
|
|
8
|
-
stats: { filesChanged: 1, insertions: 1, deletions: 0 },
|
|
9
|
-
})),
|
|
10
|
-
commit: vi.fn(async () => ({
|
|
11
|
-
success: true,
|
|
12
|
-
output: '[main abc1234] test commit',
|
|
13
|
-
})),
|
|
14
|
-
resetHard: vi.fn(async () => ({
|
|
15
|
-
success: true,
|
|
16
|
-
output: 'HEAD is now at abc1234',
|
|
17
|
-
})),
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
function createMockTranscriptStore() {
|
|
21
|
-
return {
|
|
22
|
-
appendEvents: vi.fn(async () => { }),
|
|
23
|
-
readEvents: vi.fn(async () => []),
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
describe('PersistentManager', () => {
|
|
27
|
-
it('delegates git diff to GitOperations', async () => {
|
|
28
|
-
const gitOps = createMockGitOps();
|
|
29
|
-
const manager = new PersistentManager(gitOps, createMockTranscriptStore());
|
|
30
|
-
const diff = await manager.gitDiff();
|
|
31
|
-
expect(diff.hasDiff).toBe(true);
|
|
32
|
-
expect(diff.stats.filesChanged).toBe(1);
|
|
33
|
-
});
|
|
34
|
-
it('delegates git commit to GitOperations', async () => {
|
|
35
|
-
const gitOps = createMockGitOps();
|
|
36
|
-
const manager = new PersistentManager(gitOps, createMockTranscriptStore());
|
|
37
|
-
const result = await manager.gitCommit('feat: add X');
|
|
38
|
-
expect(gitOps.commit).toHaveBeenCalledWith('feat: add X', undefined);
|
|
39
|
-
expect(result.success).toBe(true);
|
|
40
|
-
});
|
|
41
|
-
it('delegates git reset to GitOperations', async () => {
|
|
42
|
-
const gitOps = createMockGitOps();
|
|
43
|
-
const manager = new PersistentManager(gitOps, createMockTranscriptStore());
|
|
44
|
-
const result = await manager.gitReset();
|
|
45
|
-
expect(gitOps.resetHard).toHaveBeenCalled();
|
|
46
|
-
expect(result.success).toBe(true);
|
|
47
|
-
});
|
|
48
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import { afterEach, describe, expect, it } from 'vitest';
|
|
2
|
-
import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises';
|
|
3
|
-
import { join } from 'node:path';
|
|
4
|
-
import { tmpdir } from 'node:os';
|
|
5
|
-
import { discoverProjectClaudeFiles } from '../src/util/project-context.js';
|
|
6
|
-
describe('discoverProjectClaudeFiles', () => {
|
|
7
|
-
const dirs = [];
|
|
8
|
-
async function makeTmp() {
|
|
9
|
-
const d = await mkdtemp(join(tmpdir(), 'ctx-test-'));
|
|
10
|
-
dirs.push(d);
|
|
11
|
-
return d;
|
|
12
|
-
}
|
|
13
|
-
afterEach(async () => {
|
|
14
|
-
for (const d of dirs) {
|
|
15
|
-
await rm(d, { recursive: true, force: true });
|
|
16
|
-
}
|
|
17
|
-
dirs.length = 0;
|
|
18
|
-
});
|
|
19
|
-
it('returns empty array when no Claude files exist', async () => {
|
|
20
|
-
const cwd = await makeTmp();
|
|
21
|
-
expect(await discoverProjectClaudeFiles(cwd)).toEqual([]);
|
|
22
|
-
});
|
|
23
|
-
it('discovers root CLAUDE.md', async () => {
|
|
24
|
-
const cwd = await makeTmp();
|
|
25
|
-
await writeFile(join(cwd, 'CLAUDE.md'), 'project rules\n');
|
|
26
|
-
const files = await discoverProjectClaudeFiles(cwd);
|
|
27
|
-
expect(files).toEqual([{ relativePath: 'CLAUDE.md', content: 'project rules' }]);
|
|
28
|
-
});
|
|
29
|
-
it('discovers nested CLAUDE.md files recursively', async () => {
|
|
30
|
-
const cwd = await makeTmp();
|
|
31
|
-
await writeFile(join(cwd, 'CLAUDE.md'), 'root rules');
|
|
32
|
-
await mkdir(join(cwd, 'packages/core'), { recursive: true });
|
|
33
|
-
await writeFile(join(cwd, 'packages/core/CLAUDE.md'), 'core rules');
|
|
34
|
-
const files = await discoverProjectClaudeFiles(cwd);
|
|
35
|
-
expect(files).toHaveLength(2);
|
|
36
|
-
expect(files[0].relativePath).toBe('CLAUDE.md');
|
|
37
|
-
expect(files[1].relativePath).toBe('packages/core/CLAUDE.md');
|
|
38
|
-
});
|
|
39
|
-
it('discovers files under .claude/ recursively', async () => {
|
|
40
|
-
const cwd = await makeTmp();
|
|
41
|
-
await mkdir(join(cwd, '.claude/settings'), { recursive: true });
|
|
42
|
-
await writeFile(join(cwd, '.claude/CLAUDE.md'), 'claude dir md');
|
|
43
|
-
await writeFile(join(cwd, '.claude/settings/config.txt'), 'some config');
|
|
44
|
-
const files = await discoverProjectClaudeFiles(cwd);
|
|
45
|
-
expect(files).toHaveLength(2);
|
|
46
|
-
expect(files.map((f) => f.relativePath)).toEqual([
|
|
47
|
-
'.claude/CLAUDE.md',
|
|
48
|
-
'.claude/settings/config.txt',
|
|
49
|
-
]);
|
|
50
|
-
});
|
|
51
|
-
it('deduplicates when a file matches both CLAUDE.md walk and .claude/ walk', async () => {
|
|
52
|
-
const cwd = await makeTmp();
|
|
53
|
-
await mkdir(join(cwd, '.claude'), { recursive: true });
|
|
54
|
-
await writeFile(join(cwd, '.claude/CLAUDE.md'), 'shared content');
|
|
55
|
-
const files = await discoverProjectClaudeFiles(cwd);
|
|
56
|
-
expect(files).toHaveLength(1);
|
|
57
|
-
expect(files[0].relativePath).toBe('.claude/CLAUDE.md');
|
|
58
|
-
expect(files[0].content).toBe('shared content');
|
|
59
|
-
});
|
|
60
|
-
it('skips blank/whitespace-only files', async () => {
|
|
61
|
-
const cwd = await makeTmp();
|
|
62
|
-
await writeFile(join(cwd, 'CLAUDE.md'), ' \n \n');
|
|
63
|
-
expect(await discoverProjectClaudeFiles(cwd)).toEqual([]);
|
|
64
|
-
});
|
|
65
|
-
it('trims content whitespace', async () => {
|
|
66
|
-
const cwd = await makeTmp();
|
|
67
|
-
await writeFile(join(cwd, 'CLAUDE.md'), '\n hello world \n\n');
|
|
68
|
-
const files = await discoverProjectClaudeFiles(cwd);
|
|
69
|
-
expect(files[0].content).toBe('hello world');
|
|
70
|
-
});
|
|
71
|
-
it('returns deterministic sorted order', async () => {
|
|
72
|
-
const cwd = await makeTmp();
|
|
73
|
-
await mkdir(join(cwd, 'b'), { recursive: true });
|
|
74
|
-
await mkdir(join(cwd, 'a'), { recursive: true });
|
|
75
|
-
await writeFile(join(cwd, 'b/CLAUDE.md'), 'b rules');
|
|
76
|
-
await writeFile(join(cwd, 'a/CLAUDE.md'), 'a rules');
|
|
77
|
-
await writeFile(join(cwd, 'CLAUDE.md'), 'root rules');
|
|
78
|
-
const files = await discoverProjectClaudeFiles(cwd);
|
|
79
|
-
expect(files.map((f) => f.relativePath)).toEqual(['CLAUDE.md', 'a/CLAUDE.md', 'b/CLAUDE.md']);
|
|
80
|
-
});
|
|
81
|
-
it('skips node_modules and .git directories', async () => {
|
|
82
|
-
const cwd = await makeTmp();
|
|
83
|
-
await mkdir(join(cwd, 'node_modules/pkg'), { recursive: true });
|
|
84
|
-
await writeFile(join(cwd, 'node_modules/pkg/CLAUDE.md'), 'should not appear');
|
|
85
|
-
await mkdir(join(cwd, '.git/hooks'), { recursive: true });
|
|
86
|
-
await writeFile(join(cwd, '.git/hooks/CLAUDE.md'), 'should not appear');
|
|
87
|
-
await writeFile(join(cwd, 'CLAUDE.md'), 'root');
|
|
88
|
-
const files = await discoverProjectClaudeFiles(cwd);
|
|
89
|
-
expect(files).toHaveLength(1);
|
|
90
|
-
expect(files[0].relativePath).toBe('CLAUDE.md');
|
|
91
|
-
});
|
|
92
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { managerPromptRegistry } from '../src/prompts/registry.js';
|
|
3
|
-
describe('managerPromptRegistry', () => {
|
|
4
|
-
it('gives the CTO investigation-first orchestration guidance', () => {
|
|
5
|
-
expect(managerPromptRegistry.ctoSystemPrompt).toContain('You are a principal engineer orchestrating a team of AI-powered engineers');
|
|
6
|
-
expect(managerPromptRegistry.ctoSystemPrompt).toContain('Operating Loop');
|
|
7
|
-
expect(managerPromptRegistry.ctoSystemPrompt).toContain('Orient → Investigate → Decide → Delegate');
|
|
8
|
-
expect(managerPromptRegistry.ctoSystemPrompt).toContain('single-engineer');
|
|
9
|
-
expect(managerPromptRegistry.ctoSystemPrompt).toContain('team-planner');
|
|
10
|
-
expect(managerPromptRegistry.ctoSystemPrompt).toContain('question');
|
|
11
|
-
expect(managerPromptRegistry.ctoSystemPrompt).toContain('Review: Inspect diffs for production safety');
|
|
12
|
-
expect(managerPromptRegistry.ctoSystemPrompt).toContain('race condition');
|
|
13
|
-
expect(managerPromptRegistry.ctoSystemPrompt).toContain('contextExhausted');
|
|
14
|
-
expect(managerPromptRegistry.ctoSystemPrompt).toContain('adaptive, not rigid');
|
|
15
|
-
expect(managerPromptRegistry.ctoSystemPrompt).toContain('revisit earlier steps');
|
|
16
|
-
expect(managerPromptRegistry.ctoSystemPrompt).not.toContain('Orient → Classify → Plan → Confirm → Delegate');
|
|
17
|
-
expect(managerPromptRegistry.ctoSystemPrompt).not.toContain('clear_session');
|
|
18
|
-
expect(managerPromptRegistry.ctoSystemPrompt).not.toContain('freshSession');
|
|
19
|
-
});
|
|
20
|
-
it('keeps the engineer wrapper prompt short and tool-focused', () => {
|
|
21
|
-
expect(managerPromptRegistry.engineerAgentPrompt).toContain('named engineer');
|
|
22
|
-
expect(managerPromptRegistry.engineerAgentPrompt).toContain('claude');
|
|
23
|
-
expect(managerPromptRegistry.engineerAgentPrompt).toContain('remembers your prior turns');
|
|
24
|
-
expect(managerPromptRegistry.engineerAgentPrompt).toContain('wrapper context');
|
|
25
|
-
expect(managerPromptRegistry.engineerAgentPrompt).toContain('caller-directed');
|
|
26
|
-
expect(managerPromptRegistry.engineerAgentPrompt).not.toContain('read/grep/glob');
|
|
27
|
-
});
|
|
28
|
-
it('keeps the engineer session prompt direct and repo-aware', () => {
|
|
29
|
-
expect(managerPromptRegistry.engineerSessionPrompt).toContain('expert software engineer');
|
|
30
|
-
expect(managerPromptRegistry.engineerSessionPrompt).toContain('Start with the smallest investigation that resolves the key uncertainty');
|
|
31
|
-
expect(managerPromptRegistry.engineerSessionPrompt).toContain('Do not default to a plan unless the caller explicitly asks for one');
|
|
32
|
-
expect(managerPromptRegistry.engineerSessionPrompt).toContain('Verify your work before reporting done');
|
|
33
|
-
expect(managerPromptRegistry.engineerSessionPrompt).toContain('Do not run git commit');
|
|
34
|
-
expect(managerPromptRegistry.engineerSessionPrompt).toContain('rollout');
|
|
35
|
-
expect(managerPromptRegistry.engineerSessionPrompt).toContain('backwards compatibility');
|
|
36
|
-
});
|
|
37
|
-
it('keeps context warnings available for engineer sessions', () => {
|
|
38
|
-
expect(managerPromptRegistry.contextWarnings.moderate).toContain('{percent}');
|
|
39
|
-
expect(managerPromptRegistry.contextWarnings.high).toContain('{turns}');
|
|
40
|
-
expect(managerPromptRegistry.contextWarnings.critical).toContain('near capacity');
|
|
41
|
-
});
|
|
42
|
-
it('planSynthesisPrompt contains synthesis guidance and complete output format', () => {
|
|
43
|
-
expect(managerPromptRegistry.planSynthesisPrompt).toContain('synthesiz');
|
|
44
|
-
expect(managerPromptRegistry.planSynthesisPrompt).toContain('two independent');
|
|
45
|
-
expect(managerPromptRegistry.planSynthesisPrompt).toContain('## Synthesis');
|
|
46
|
-
expect(managerPromptRegistry.planSynthesisPrompt).toContain('## Recommended Question');
|
|
47
|
-
expect(managerPromptRegistry.planSynthesisPrompt).toContain('## Recommended Answer');
|
|
48
|
-
});
|
|
49
|
-
it('teamPlannerPrompt keeps the wrapper thin and calls plan_with_team', () => {
|
|
50
|
-
expect(managerPromptRegistry.teamPlannerPrompt).toContain('plan_with_team');
|
|
51
|
-
expect(managerPromptRegistry.teamPlannerPrompt).toContain('live activity in the UI');
|
|
52
|
-
expect(managerPromptRegistry.teamPlannerPrompt).toContain('Keep the wrapper thin');
|
|
53
|
-
expect(managerPromptRegistry.teamPlannerPrompt).toContain('auto-select');
|
|
54
|
-
expect(managerPromptRegistry.teamPlannerPrompt).toContain('pass the full result back to the CTO unchanged');
|
|
55
|
-
});
|
|
56
|
-
it('ctoSystemPrompt delegates directly when scope is clear and asks for explicit explore outputs', () => {
|
|
57
|
-
expect(managerPromptRegistry.ctoSystemPrompt).toContain('For trivial or simple work with clear scope: delegate directly to one engineer');
|
|
58
|
-
expect(managerPromptRegistry.ctoSystemPrompt).toContain('expected output shape');
|
|
59
|
-
expect(managerPromptRegistry.ctoSystemPrompt).toContain('root cause');
|
|
60
|
-
expect(managerPromptRegistry.ctoSystemPrompt).toContain('implementation plan');
|
|
61
|
-
});
|
|
62
|
-
it('engineerAgentPrompt instructs engineers to surface plan deviations', () => {
|
|
63
|
-
expect(managerPromptRegistry.engineerAgentPrompt).toContain('deviation');
|
|
64
|
-
expect(managerPromptRegistry.engineerAgentPrompt).toContain('surface');
|
|
65
|
-
});
|
|
66
|
-
it('browserQaAgentPrompt instructs browser-qa to report scope mismatches', () => {
|
|
67
|
-
expect(managerPromptRegistry.browserQaAgentPrompt).toContain('scope mismatch');
|
|
68
|
-
});
|
|
69
|
-
it('ctoSystemPrompt delegates single work to named engineers and complex planning to team-planner', () => {
|
|
70
|
-
expect(managerPromptRegistry.ctoSystemPrompt).toContain('task(subagent_type:');
|
|
71
|
-
expect(managerPromptRegistry.ctoSystemPrompt).toContain('single-engineer');
|
|
72
|
-
expect(managerPromptRegistry.ctoSystemPrompt).toContain('team-planner');
|
|
73
|
-
expect(managerPromptRegistry.ctoSystemPrompt).toContain('live UI activity');
|
|
74
|
-
});
|
|
75
|
-
it('ctoSystemPrompt mentions browser-qa for delegation', () => {
|
|
76
|
-
expect(managerPromptRegistry.ctoSystemPrompt).toContain('browser-qa');
|
|
77
|
-
});
|
|
78
|
-
it('browserQaAgentPrompt mentions Playwright and PLAYWRIGHT_UNAVAILABLE sentinel', () => {
|
|
79
|
-
expect(managerPromptRegistry.browserQaAgentPrompt).toContain('Playwright');
|
|
80
|
-
expect(managerPromptRegistry.browserQaAgentPrompt).toContain('PLAYWRIGHT_UNAVAILABLE');
|
|
81
|
-
expect(managerPromptRegistry.browserQaAgentPrompt).toContain('browser QA');
|
|
82
|
-
});
|
|
83
|
-
it('browserQaSessionPrompt mentions Playwright and restricts write tools', () => {
|
|
84
|
-
expect(managerPromptRegistry.browserQaSessionPrompt).toContain('Playwright');
|
|
85
|
-
expect(managerPromptRegistry.browserQaSessionPrompt).toContain('Allowed tools');
|
|
86
|
-
expect(managerPromptRegistry.browserQaSessionPrompt).toContain('browser QA');
|
|
87
|
-
expect(managerPromptRegistry.browserQaSessionPrompt).not.toContain('implement');
|
|
88
|
-
expect(managerPromptRegistry.browserQaSessionPrompt).not.toContain('write code');
|
|
89
|
-
});
|
|
90
|
-
it('ctoSystemPrompt encodes task size classification (trivial/simple/large)', () => {
|
|
91
|
-
expect(managerPromptRegistry.ctoSystemPrompt).toContain('trivial');
|
|
92
|
-
expect(managerPromptRegistry.ctoSystemPrompt).toContain('simple');
|
|
93
|
-
expect(managerPromptRegistry.ctoSystemPrompt).toContain('large');
|
|
94
|
-
expect(managerPromptRegistry.ctoSystemPrompt).toContain('Task size');
|
|
95
|
-
});
|
|
96
|
-
it('ctoSystemPrompt no longer mentions explicit plan-tracking tools', () => {
|
|
97
|
-
expect(managerPromptRegistry.ctoSystemPrompt).not.toContain('confirm_plan');
|
|
98
|
-
expect(managerPromptRegistry.ctoSystemPrompt).not.toContain('advance_slice');
|
|
99
|
-
});
|
|
100
|
-
it('ctoSystemPrompt encodes warn-only context policy', () => {
|
|
101
|
-
expect(managerPromptRegistry.ctoSystemPrompt).toContain('Context warnings');
|
|
102
|
-
expect(managerPromptRegistry.ctoSystemPrompt).toContain('advisory');
|
|
103
|
-
expect(managerPromptRegistry.ctoSystemPrompt).toContain('contextExhausted');
|
|
104
|
-
});
|
|
105
|
-
it('contextWarnings reflect warn-only policy for critical level', () => {
|
|
106
|
-
expect(managerPromptRegistry.contextWarnings.critical).toContain('near capacity');
|
|
107
|
-
expect(managerPromptRegistry.contextWarnings.critical).toContain('Warn only');
|
|
108
|
-
});
|
|
109
|
-
it('ctoSystemPrompt uses genuinely vertical slice examples, not horizontal layers', () => {
|
|
110
|
-
expect(managerPromptRegistry.ctoSystemPrompt).not.toContain('"types + contracts"');
|
|
111
|
-
expect(managerPromptRegistry.ctoSystemPrompt).not.toContain('"core logic"');
|
|
112
|
-
expect(managerPromptRegistry.ctoSystemPrompt).not.toContain('"plugin tools"');
|
|
113
|
-
expect(managerPromptRegistry.ctoSystemPrompt).toContain('end-to-end');
|
|
114
|
-
expect(managerPromptRegistry.ctoSystemPrompt).toContain('user-testable');
|
|
115
|
-
expect(managerPromptRegistry.ctoSystemPrompt).toContain('Horizontal layers');
|
|
116
|
-
});
|
|
117
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|