@inixiative/hivemind 0.1.0
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/LICENSE +21 -0
- package/README.md +115 -0
- package/dist/agents/agents.test.d.ts +1 -0
- package/dist/agents/agents.test.js +167 -0
- package/dist/agents/getActiveAgents.d.ts +6 -0
- package/dist/agents/getActiveAgents.js +11 -0
- package/dist/agents/getAgent.d.ts +6 -0
- package/dist/agents/getAgent.js +7 -0
- package/dist/agents/getAgentBySessionId.d.ts +10 -0
- package/dist/agents/getAgentBySessionId.js +17 -0
- package/dist/agents/index.d.ts +10 -0
- package/dist/agents/index.js +12 -0
- package/dist/agents/markAgentDead.d.ts +6 -0
- package/dist/agents/markAgentDead.js +26 -0
- package/dist/agents/markAgentIdle.d.ts +5 -0
- package/dist/agents/markAgentIdle.js +12 -0
- package/dist/agents/registerAgent.d.ts +6 -0
- package/dist/agents/registerAgent.js +29 -0
- package/dist/agents/types.d.ts +30 -0
- package/dist/agents/types.js +1 -0
- package/dist/agents/unregisterAgent.d.ts +5 -0
- package/dist/agents/unregisterAgent.js +8 -0
- package/dist/agents/updateAgentContext.d.ts +5 -0
- package/dist/agents/updateAgentContext.js +12 -0
- package/dist/agents/updateAgentTask.d.ts +5 -0
- package/dist/agents/updateAgentTask.js +12 -0
- package/dist/agents/updateAgentWorktree.d.ts +5 -0
- package/dist/agents/updateAgentWorktree.js +12 -0
- package/dist/cli/index.d.ts +8 -0
- package/dist/cli/index.js +8 -0
- package/dist/cli/init.d.ts +14 -0
- package/dist/cli/init.js +71 -0
- package/dist/cli/install.d.ts +8 -0
- package/dist/cli/install.js +47 -0
- package/dist/cli/join.d.ts +9 -0
- package/dist/cli/join.js +38 -0
- package/dist/cli/registerMcp.d.ts +28 -0
- package/dist/cli/registerMcp.js +138 -0
- package/dist/cli/status.d.ts +8 -0
- package/dist/cli/status.js +82 -0
- package/dist/cli/watch.d.ts +6 -0
- package/dist/cli/watch.js +68 -0
- package/dist/cli.d.ts +12 -0
- package/dist/cli.js +49 -0
- package/dist/coordinator/coordinator.test.d.ts +1 -0
- package/dist/coordinator/coordinator.test.js +171 -0
- package/dist/coordinator/index.d.ts +16 -0
- package/dist/coordinator/index.js +166 -0
- package/dist/coordinator/spawn.d.ts +22 -0
- package/dist/coordinator/spawn.js +66 -0
- package/dist/datetime/datetime.test.d.ts +1 -0
- package/dist/datetime/datetime.test.js +63 -0
- package/dist/datetime/formatDate.d.ts +6 -0
- package/dist/datetime/formatDate.js +11 -0
- package/dist/datetime/formatDatetime.d.ts +6 -0
- package/dist/datetime/formatDatetime.js +12 -0
- package/dist/datetime/formatTime.d.ts +6 -0
- package/dist/datetime/formatTime.js +11 -0
- package/dist/datetime/index.d.ts +4 -0
- package/dist/datetime/index.js +7 -0
- package/dist/datetime/isStale.d.ts +10 -0
- package/dist/datetime/isStale.js +18 -0
- package/dist/datetime/now.d.ts +6 -0
- package/dist/datetime/now.js +9 -0
- package/dist/datetime/parseDatetime.d.ts +7 -0
- package/dist/datetime/parseDatetime.js +28 -0
- package/dist/db/constants.d.ts +4 -0
- package/dist/db/constants.js +6 -0
- package/dist/db/db.test.d.ts +1 -0
- package/dist/db/db.test.js +141 -0
- package/dist/db/ensureProjectDirs.d.ts +4 -0
- package/dist/db/ensureProjectDirs.js +12 -0
- package/dist/db/getConnection.d.ts +19 -0
- package/dist/db/getConnection.js +51 -0
- package/dist/db/getCurrentProject.d.ts +8 -0
- package/dist/db/getCurrentProject.js +14 -0
- package/dist/db/getProjectPaths.d.ts +21 -0
- package/dist/db/getProjectPaths.js +26 -0
- package/dist/db/index.d.ts +10 -0
- package/dist/db/index.js +13 -0
- package/dist/db/initializeDb.d.ts +7 -0
- package/dist/db/initializeDb.js +23 -0
- package/dist/db/nextEventSeq.d.ts +5 -0
- package/dist/db/nextEventSeq.js +13 -0
- package/dist/db/nextSubtaskSeq.d.ts +5 -0
- package/dist/db/nextSubtaskSeq.js +12 -0
- package/dist/db/nextTaskSeq.d.ts +5 -0
- package/dist/db/nextTaskSeq.js +13 -0
- package/dist/db/resetDb.d.ts +10 -0
- package/dist/db/resetDb.js +36 -0
- package/dist/events/emit.d.ts +6 -0
- package/dist/events/emit.js +31 -0
- package/dist/events/events.test.d.ts +1 -0
- package/dist/events/events.test.js +145 -0
- package/dist/events/getEventsByAgent.d.ts +6 -0
- package/dist/events/getEventsByAgent.js +14 -0
- package/dist/events/getEventsByBranch.d.ts +6 -0
- package/dist/events/getEventsByBranch.js +12 -0
- package/dist/events/getEventsByPlan.d.ts +6 -0
- package/dist/events/getEventsByPlan.js +14 -0
- package/dist/events/getEventsByWorktree.d.ts +6 -0
- package/dist/events/getEventsByWorktree.js +12 -0
- package/dist/events/getEventsSince.d.ts +12 -0
- package/dist/events/getEventsSince.js +47 -0
- package/dist/events/getRecentEvents.d.ts +6 -0
- package/dist/events/getRecentEvents.js +12 -0
- package/dist/events/index.d.ts +8 -0
- package/dist/events/index.js +9 -0
- package/dist/events/types.d.ts +34 -0
- package/dist/events/types.js +1 -0
- package/dist/git/getBranch.d.ts +4 -0
- package/dist/git/getBranch.js +14 -0
- package/dist/git/getCurrentWorktree.d.ts +5 -0
- package/dist/git/getCurrentWorktree.js +15 -0
- package/dist/git/getGitInfo.d.ts +10 -0
- package/dist/git/getGitInfo.js +23 -0
- package/dist/git/getRepoName.d.ts +4 -0
- package/dist/git/getRepoName.js +32 -0
- package/dist/git/getRepoRoot.d.ts +4 -0
- package/dist/git/getRepoRoot.js +14 -0
- package/dist/git/getWorktrees.d.ts +10 -0
- package/dist/git/getWorktrees.js +39 -0
- package/dist/git/index.d.ts +9 -0
- package/dist/git/index.js +7 -0
- package/dist/git/isGitRepo.d.ts +4 -0
- package/dist/git/isGitRepo.js +13 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/sessionStart.d.ts +21 -0
- package/dist/hooks/sessionStart.js +93 -0
- package/dist/ids/generateHex.d.ts +4 -0
- package/dist/ids/generateHex.js +7 -0
- package/dist/ids/getParentTaskId.d.ts +7 -0
- package/dist/ids/getParentTaskId.js +15 -0
- package/dist/ids/getPlanHexFromTaskId.d.ts +6 -0
- package/dist/ids/getPlanHexFromTaskId.js +9 -0
- package/dist/ids/ids.test.d.ts +1 -0
- package/dist/ids/ids.test.js +215 -0
- package/dist/ids/index.d.ts +16 -0
- package/dist/ids/index.js +17 -0
- package/dist/ids/isSubtask.d.ts +7 -0
- package/dist/ids/isSubtask.js +11 -0
- package/dist/ids/isValidId.d.ts +9 -0
- package/dist/ids/isValidId.js +22 -0
- package/dist/ids/makeAgentId.d.ts +8 -0
- package/dist/ids/makeAgentId.js +15 -0
- package/dist/ids/makeEventId.d.ts +11 -0
- package/dist/ids/makeEventId.js +12 -0
- package/dist/ids/makePlanId.d.ts +11 -0
- package/dist/ids/makePlanId.js +15 -0
- package/dist/ids/makeSubtaskId.d.ts +8 -0
- package/dist/ids/makeSubtaskId.js +15 -0
- package/dist/ids/makeTaskId.d.ts +8 -0
- package/dist/ids/makeTaskId.js +14 -0
- package/dist/ids/makeWorktreeId.d.ts +5 -0
- package/dist/ids/makeWorktreeId.js +12 -0
- package/dist/ids/parseId.d.ts +11 -0
- package/dist/ids/parseId.js +26 -0
- package/dist/ids/sanitizeLabel.d.ts +7 -0
- package/dist/ids/sanitizeLabel.js +12 -0
- package/dist/ids/typedIds.d.ts +34 -0
- package/dist/ids/typedIds.js +22 -0
- package/dist/ids/types.d.ts +14 -0
- package/dist/ids/types.js +1 -0
- package/dist/init/claudeConfig.d.ts +39 -0
- package/dist/init/claudeConfig.js +161 -0
- package/dist/llm/extractTasks.d.ts +28 -0
- package/dist/llm/extractTasks.js +108 -0
- package/dist/llm/index.d.ts +2 -0
- package/dist/llm/index.js +2 -0
- package/dist/llm/reconcileTasks.d.ts +21 -0
- package/dist/llm/reconcileTasks.js +82 -0
- package/dist/mcp/server.d.ts +2 -0
- package/dist/mcp/server.js +100 -0
- package/dist/mcp/tools/emitEvent.d.ts +62 -0
- package/dist/mcp/tools/emitEvent.js +84 -0
- package/dist/mcp/tools/events.d.ts +55 -0
- package/dist/mcp/tools/events.js +56 -0
- package/dist/mcp/tools/index.d.ts +18 -0
- package/dist/mcp/tools/index.js +13 -0
- package/dist/mcp/tools/query.d.ts +54 -0
- package/dist/mcp/tools/query.js +70 -0
- package/dist/mcp/tools/register.d.ts +47 -0
- package/dist/mcp/tools/register.js +79 -0
- package/dist/mcp/tools/reset.d.ts +38 -0
- package/dist/mcp/tools/reset.js +56 -0
- package/dist/mcp/tools/setup.d.ts +42 -0
- package/dist/mcp/tools/setup.js +75 -0
- package/dist/mcp/tools/status.d.ts +44 -0
- package/dist/mcp/tools/status.js +74 -0
- package/dist/mcp/tools/tasks.d.ts +116 -0
- package/dist/mcp/tools/tasks.js +143 -0
- package/dist/mcp/tools/worktreeCleanup.d.ts +38 -0
- package/dist/mcp/tools/worktreeCleanup.js +67 -0
- package/dist/plans/createPlan.d.ts +6 -0
- package/dist/plans/createPlan.js +29 -0
- package/dist/plans/getActivePlans.d.ts +6 -0
- package/dist/plans/getActivePlans.js +11 -0
- package/dist/plans/getPlan.d.ts +6 -0
- package/dist/plans/getPlan.js +7 -0
- package/dist/plans/index.d.ts +5 -0
- package/dist/plans/index.js +5 -0
- package/dist/plans/plans.test.d.ts +1 -0
- package/dist/plans/plans.test.js +107 -0
- package/dist/plans/types.d.ts +32 -0
- package/dist/plans/types.js +1 -0
- package/dist/plans/updatePlanStatus.d.ts +6 -0
- package/dist/plans/updatePlanStatus.js +8 -0
- package/dist/tasks/assignTask.d.ts +7 -0
- package/dist/tasks/assignTask.js +20 -0
- package/dist/tasks/blockTask.d.ts +5 -0
- package/dist/tasks/blockTask.js +12 -0
- package/dist/tasks/claimTask.d.ts +7 -0
- package/dist/tasks/claimTask.js +21 -0
- package/dist/tasks/completeTask.d.ts +6 -0
- package/dist/tasks/completeTask.js +18 -0
- package/dist/tasks/createTask.d.ts +6 -0
- package/dist/tasks/createTask.js +36 -0
- package/dist/tasks/getPendingTasks.d.ts +6 -0
- package/dist/tasks/getPendingTasks.js +19 -0
- package/dist/tasks/getTask.d.ts +6 -0
- package/dist/tasks/getTask.js +7 -0
- package/dist/tasks/getTasksByPlan.d.ts +6 -0
- package/dist/tasks/getTasksByPlan.js +11 -0
- package/dist/tasks/index.d.ts +11 -0
- package/dist/tasks/index.js +12 -0
- package/dist/tasks/startTask.d.ts +5 -0
- package/dist/tasks/startTask.js +12 -0
- package/dist/tasks/tasks.test.d.ts +1 -0
- package/dist/tasks/tasks.test.js +209 -0
- package/dist/tasks/types.d.ts +36 -0
- package/dist/tasks/types.js +1 -0
- package/dist/tasks/unclaimTask.d.ts +5 -0
- package/dist/tasks/unclaimTask.js +12 -0
- package/dist/test/factories/agentFactory.d.ts +13 -0
- package/dist/test/factories/agentFactory.js +5 -0
- package/dist/test/factories/eventFactory.d.ts +20 -0
- package/dist/test/factories/eventFactory.js +16 -0
- package/dist/test/factories/factories.test.d.ts +1 -0
- package/dist/test/factories/factories.test.js +101 -0
- package/dist/test/factories/index.d.ts +4 -0
- package/dist/test/factories/index.js +4 -0
- package/dist/test/factories/planFactory.d.ts +15 -0
- package/dist/test/factories/planFactory.js +14 -0
- package/dist/test/factories/taskFactory.d.ts +22 -0
- package/dist/test/factories/taskFactory.js +44 -0
- package/dist/test/multiAgentDemo.d.ts +16 -0
- package/dist/test/multiAgentDemo.js +20 -0
- package/dist/test/multiAgentDemo.test.d.ts +1 -0
- package/dist/test/multiAgentDemo.test.js +14 -0
- package/dist/test/setup.d.ts +9 -0
- package/dist/test/setup.js +50 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.js +5 -0
- package/dist/utils/math.d.ts +6 -0
- package/dist/utils/math.js +10 -0
- package/dist/utils/math.test.d.ts +1 -0
- package/dist/utils/math.test.js +26 -0
- package/dist/utils/string.d.ts +11 -0
- package/dist/utils/string.js +17 -0
- package/dist/utils/string.test.d.ts +1 -0
- package/dist/utils/string.test.js +30 -0
- package/dist/watcher/index.d.ts +1 -0
- package/dist/watcher/index.js +1 -0
- package/dist/watcher/planWatcher.d.ts +31 -0
- package/dist/watcher/planWatcher.js +171 -0
- package/dist/worktrees/getAllWorktrees.d.ts +6 -0
- package/dist/worktrees/getAllWorktrees.js +10 -0
- package/dist/worktrees/getWorktreeById.d.ts +6 -0
- package/dist/worktrees/getWorktreeById.js +7 -0
- package/dist/worktrees/getWorktreeByPath.d.ts +6 -0
- package/dist/worktrees/getWorktreeByPath.js +7 -0
- package/dist/worktrees/index.d.ts +9 -0
- package/dist/worktrees/index.js +13 -0
- package/dist/worktrees/registerWorktree.d.ts +6 -0
- package/dist/worktrees/registerWorktree.js +28 -0
- package/dist/worktrees/removeWorktree.d.ts +6 -0
- package/dist/worktrees/removeWorktree.js +9 -0
- package/dist/worktrees/syncWorktreesFromGit.d.ts +7 -0
- package/dist/worktrees/syncWorktreesFromGit.js +51 -0
- package/dist/worktrees/types.d.ts +29 -0
- package/dist/worktrees/types.js +1 -0
- package/dist/worktrees/updateWorktreeCommit.d.ts +5 -0
- package/dist/worktrees/updateWorktreeCommit.js +13 -0
- package/dist/worktrees/updateWorktreeSeen.d.ts +5 -0
- package/dist/worktrees/updateWorktreeSeen.js +13 -0
- package/package.json +58 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, afterAll } from 'bun:test';
|
|
2
|
+
import { createTestDb, cleanupAllTestDbs } from '../test/setup';
|
|
3
|
+
import { buildAgent, buildPlan, buildTask } from '../test/factories';
|
|
4
|
+
import { emit } from './emit';
|
|
5
|
+
import { getEventsSince } from './getEventsSince';
|
|
6
|
+
import { getEventsByPlan } from './getEventsByPlan';
|
|
7
|
+
import { getEventsByAgent } from './getEventsByAgent';
|
|
8
|
+
import { getRecentEvents } from './getRecentEvents';
|
|
9
|
+
describe('Events Module', () => {
|
|
10
|
+
let testDb;
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
testDb = createTestDb();
|
|
13
|
+
});
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
testDb.cleanup();
|
|
16
|
+
});
|
|
17
|
+
afterAll(() => {
|
|
18
|
+
cleanupAllTestDbs();
|
|
19
|
+
});
|
|
20
|
+
describe('emit', () => {
|
|
21
|
+
it('creates a new event', () => {
|
|
22
|
+
const { agent } = buildAgent(testDb.db, { label: 'test' });
|
|
23
|
+
const event = emit(testDb.db, {
|
|
24
|
+
agent_id: agent.id,
|
|
25
|
+
type: 'note',
|
|
26
|
+
content: 'Test note',
|
|
27
|
+
});
|
|
28
|
+
expect(event.id).toMatch(/^evt_[a-f0-9]{6}_\d+$/);
|
|
29
|
+
expect(event.agent_id).toBe(agent.id);
|
|
30
|
+
expect(event.event_type).toBe('note');
|
|
31
|
+
expect(event.content).toBe('Test note');
|
|
32
|
+
expect(event.timestamp).toBeDefined();
|
|
33
|
+
});
|
|
34
|
+
it('creates event with plan and task', () => {
|
|
35
|
+
const { task, plan, agent } = buildTask(testDb.db);
|
|
36
|
+
const event = emit(testDb.db, {
|
|
37
|
+
agent_id: agent.id,
|
|
38
|
+
plan_id: plan.id,
|
|
39
|
+
task_id: task.id,
|
|
40
|
+
type: 'task:start',
|
|
41
|
+
content: 'Starting task',
|
|
42
|
+
});
|
|
43
|
+
expect(event.plan_id).toBe(plan.id);
|
|
44
|
+
expect(event.task_id).toBe(task.id);
|
|
45
|
+
});
|
|
46
|
+
it('creates event with branch and worktree', () => {
|
|
47
|
+
const { agent } = buildAgent(testDb.db);
|
|
48
|
+
const event = emit(testDb.db, {
|
|
49
|
+
agent_id: agent.id,
|
|
50
|
+
branch: 'feature/auth',
|
|
51
|
+
type: 'context',
|
|
52
|
+
content: 'Working on auth',
|
|
53
|
+
});
|
|
54
|
+
expect(event.branch).toBe('feature/auth');
|
|
55
|
+
});
|
|
56
|
+
it('stores metadata as JSON', () => {
|
|
57
|
+
const { agent } = buildAgent(testDb.db);
|
|
58
|
+
const event = emit(testDb.db, {
|
|
59
|
+
agent_id: agent.id,
|
|
60
|
+
type: 'decision',
|
|
61
|
+
content: 'Chose option A',
|
|
62
|
+
metadata: { reason: 'simpler', alternatives: ['B', 'C'] },
|
|
63
|
+
});
|
|
64
|
+
expect(event.metadata).toBe('{"reason":"simpler","alternatives":["B","C"]}');
|
|
65
|
+
expect(JSON.parse(event.metadata)).toEqual({ reason: 'simpler', alternatives: ['B', 'C'] });
|
|
66
|
+
});
|
|
67
|
+
it('increments sequence for same hex', () => {
|
|
68
|
+
const { agent } = buildAgent(testDb.db);
|
|
69
|
+
const event1 = emit(testDb.db, { agent_id: agent.id, type: 'note', content: 'First' });
|
|
70
|
+
const event2 = emit(testDb.db, { agent_id: agent.id, type: 'note', content: 'Second' });
|
|
71
|
+
const seq1 = parseInt(event1.id.split('_')[2]);
|
|
72
|
+
const seq2 = parseInt(event2.id.split('_')[2]);
|
|
73
|
+
expect(seq2).toBe(seq1 + 1);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
describe('getEventsSince', () => {
|
|
77
|
+
it('returns events after sequence number', () => {
|
|
78
|
+
const { agent } = buildAgent(testDb.db);
|
|
79
|
+
const event1 = emit(testDb.db, { agent_id: agent.id, type: 'note', content: 'First' });
|
|
80
|
+
emit(testDb.db, { agent_id: agent.id, type: 'note', content: 'Second' });
|
|
81
|
+
const events = getEventsSince(testDb.db, event1.seq);
|
|
82
|
+
expect(events).toHaveLength(1);
|
|
83
|
+
expect(events[0].content).toBe('Second');
|
|
84
|
+
});
|
|
85
|
+
it('returns events after timestamp', () => {
|
|
86
|
+
const { agent } = buildAgent(testDb.db);
|
|
87
|
+
emit(testDb.db, { agent_id: agent.id, type: 'note', content: 'First' });
|
|
88
|
+
emit(testDb.db, { agent_id: agent.id, type: 'note', content: 'Second' });
|
|
89
|
+
const events = getEventsSince(testDb.db, '2020/01/01 00:00:00 UTC');
|
|
90
|
+
expect(events.length).toBeGreaterThanOrEqual(2);
|
|
91
|
+
});
|
|
92
|
+
it('returns empty array when no events after sequence', () => {
|
|
93
|
+
const { agent } = buildAgent(testDb.db);
|
|
94
|
+
const event = emit(testDb.db, { agent_id: agent.id, type: 'note', content: 'Only event' });
|
|
95
|
+
const events = getEventsSince(testDb.db, event.seq);
|
|
96
|
+
expect(events).toHaveLength(0);
|
|
97
|
+
});
|
|
98
|
+
it('returns empty array when no events after timestamp', () => {
|
|
99
|
+
const { agent } = buildAgent(testDb.db);
|
|
100
|
+
emit(testDb.db, { agent_id: agent.id, type: 'note', content: 'Old event' });
|
|
101
|
+
const events = getEventsSince(testDb.db, '2099/01/01 00:00:00 UTC');
|
|
102
|
+
expect(events).toHaveLength(0);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
describe('getEventsByPlan', () => {
|
|
106
|
+
it('returns events for specific plan', () => {
|
|
107
|
+
const { plan, agent } = buildPlan(testDb.db);
|
|
108
|
+
emit(testDb.db, { agent_id: agent.id, plan_id: plan.id, type: 'plan:create', content: 'Created' });
|
|
109
|
+
emit(testDb.db, { agent_id: agent.id, type: 'note', content: 'Unrelated' });
|
|
110
|
+
const events = getEventsByPlan(testDb.db, plan.id);
|
|
111
|
+
expect(events).toHaveLength(1);
|
|
112
|
+
expect(events[0].plan_id).toBe(plan.id);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
describe('getEventsByAgent', () => {
|
|
116
|
+
it('returns events for specific agent', () => {
|
|
117
|
+
const { agent: agent1 } = buildAgent(testDb.db, { label: 'one' });
|
|
118
|
+
const { agent: agent2 } = buildAgent(testDb.db, { label: 'two' });
|
|
119
|
+
emit(testDb.db, { agent_id: agent1.id, type: 'note', content: 'Agent 1 event' });
|
|
120
|
+
emit(testDb.db, { agent_id: agent2.id, type: 'note', content: 'Agent 2 event' });
|
|
121
|
+
const events = getEventsByAgent(testDb.db, agent1.id);
|
|
122
|
+
expect(events).toHaveLength(1);
|
|
123
|
+
expect(events[0].agent_id).toBe(agent1.id);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
describe('getRecentEvents', () => {
|
|
127
|
+
it('returns limited recent events', () => {
|
|
128
|
+
const { agent } = buildAgent(testDb.db);
|
|
129
|
+
for (let i = 0; i < 10; i++) {
|
|
130
|
+
emit(testDb.db, { agent_id: agent.id, type: 'note', content: `Event ${i}` });
|
|
131
|
+
}
|
|
132
|
+
const events = getRecentEvents(testDb.db, 5);
|
|
133
|
+
expect(events).toHaveLength(5);
|
|
134
|
+
});
|
|
135
|
+
it('returns events in chronological order (oldest first)', () => {
|
|
136
|
+
const { agent } = buildAgent(testDb.db);
|
|
137
|
+
emit(testDb.db, { agent_id: agent.id, type: 'note', content: 'First' });
|
|
138
|
+
emit(testDb.db, { agent_id: agent.id, type: 'note', content: 'Second' });
|
|
139
|
+
emit(testDb.db, { agent_id: agent.id, type: 'note', content: 'Third' });
|
|
140
|
+
const events = getRecentEvents(testDb.db, 3);
|
|
141
|
+
expect(events[0].content).toBe('First');
|
|
142
|
+
expect(events[2].content).toBe('Third');
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get all events for a specific agent
|
|
3
|
+
*/
|
|
4
|
+
export function getEventsByAgent(db, agentId, limit) {
|
|
5
|
+
const sql = limit
|
|
6
|
+
? `SELECT * FROM events WHERE agent_id = ? ORDER BY seq DESC LIMIT ?`
|
|
7
|
+
: `SELECT * FROM events WHERE agent_id = ? ORDER BY seq ASC`;
|
|
8
|
+
const stmt = db.prepare(sql);
|
|
9
|
+
if (limit) {
|
|
10
|
+
const results = stmt.all(agentId, limit);
|
|
11
|
+
return results.reverse();
|
|
12
|
+
}
|
|
13
|
+
return stmt.all(agentId);
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get all events for a specific plan
|
|
3
|
+
*/
|
|
4
|
+
export function getEventsByPlan(db, planId, limit) {
|
|
5
|
+
const sql = limit
|
|
6
|
+
? `SELECT * FROM events WHERE plan_id = ? ORDER BY seq DESC LIMIT ?`
|
|
7
|
+
: `SELECT * FROM events WHERE plan_id = ? ORDER BY seq ASC`;
|
|
8
|
+
const stmt = db.prepare(sql);
|
|
9
|
+
if (limit) {
|
|
10
|
+
const results = stmt.all(planId, limit);
|
|
11
|
+
return results.reverse(); // Return in chronological order
|
|
12
|
+
}
|
|
13
|
+
return stmt.all(planId);
|
|
14
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get events for a specific worktree
|
|
3
|
+
*/
|
|
4
|
+
export function getEventsByWorktree(db, worktreeId, limit = 100) {
|
|
5
|
+
const stmt = db.prepare(`
|
|
6
|
+
SELECT * FROM events
|
|
7
|
+
WHERE worktree_id = ?
|
|
8
|
+
ORDER BY seq DESC
|
|
9
|
+
LIMIT ?
|
|
10
|
+
`);
|
|
11
|
+
return stmt.all(worktreeId, limit);
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Database } from 'bun:sqlite';
|
|
2
|
+
import type { Event } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Get all events since a given point
|
|
5
|
+
*
|
|
6
|
+
* @param since - Either a sequence number (number) or timestamp (string)
|
|
7
|
+
* - number: Get events with seq > since (exclusive, efficient for polling)
|
|
8
|
+
* - string: Get events with timestamp > since (for time-based queries)
|
|
9
|
+
*
|
|
10
|
+
* Used for polling: "What happened since I last checked?"
|
|
11
|
+
*/
|
|
12
|
+
export declare function getEventsSince(db: Database, since: number | string, limit?: number): Event[];
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get all events since a given point
|
|
3
|
+
*
|
|
4
|
+
* @param since - Either a sequence number (number) or timestamp (string)
|
|
5
|
+
* - number: Get events with seq > since (exclusive, efficient for polling)
|
|
6
|
+
* - string: Get events with timestamp > since (for time-based queries)
|
|
7
|
+
*
|
|
8
|
+
* Used for polling: "What happened since I last checked?"
|
|
9
|
+
*/
|
|
10
|
+
export function getEventsSince(db, since, limit) {
|
|
11
|
+
if (typeof since === 'number') {
|
|
12
|
+
// Sequence-based query (most efficient for polling)
|
|
13
|
+
if (limit) {
|
|
14
|
+
const stmt = db.prepare(`
|
|
15
|
+
SELECT * FROM events
|
|
16
|
+
WHERE seq > ?
|
|
17
|
+
ORDER BY seq ASC
|
|
18
|
+
LIMIT ?
|
|
19
|
+
`);
|
|
20
|
+
return stmt.all(since, limit);
|
|
21
|
+
}
|
|
22
|
+
const stmt = db.prepare(`
|
|
23
|
+
SELECT * FROM events
|
|
24
|
+
WHERE seq > ?
|
|
25
|
+
ORDER BY seq ASC
|
|
26
|
+
`);
|
|
27
|
+
return stmt.all(since);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
// Timestamp-based query
|
|
31
|
+
if (limit) {
|
|
32
|
+
const stmt = db.prepare(`
|
|
33
|
+
SELECT * FROM events
|
|
34
|
+
WHERE timestamp > ?
|
|
35
|
+
ORDER BY seq ASC
|
|
36
|
+
LIMIT ?
|
|
37
|
+
`);
|
|
38
|
+
return stmt.all(since, limit);
|
|
39
|
+
}
|
|
40
|
+
const stmt = db.prepare(`
|
|
41
|
+
SELECT * FROM events
|
|
42
|
+
WHERE timestamp > ?
|
|
43
|
+
ORDER BY seq ASC
|
|
44
|
+
`);
|
|
45
|
+
return stmt.all(since);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get the most recent events
|
|
3
|
+
*/
|
|
4
|
+
export function getRecentEvents(db, limit = 50) {
|
|
5
|
+
const stmt = db.prepare(`
|
|
6
|
+
SELECT * FROM events
|
|
7
|
+
ORDER BY seq DESC
|
|
8
|
+
LIMIT ?
|
|
9
|
+
`);
|
|
10
|
+
const results = stmt.all(limit);
|
|
11
|
+
return results.reverse(); // Chronological order
|
|
12
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type { EventType, Event, EventInput } from './types';
|
|
2
|
+
export { emit } from './emit';
|
|
3
|
+
export { getEventsSince } from './getEventsSince';
|
|
4
|
+
export { getEventsByPlan } from './getEventsByPlan';
|
|
5
|
+
export { getEventsByAgent } from './getEventsByAgent';
|
|
6
|
+
export { getEventsByWorktree } from './getEventsByWorktree';
|
|
7
|
+
export { getEventsByBranch } from './getEventsByBranch';
|
|
8
|
+
export { getRecentEvents } from './getRecentEvents';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Emit
|
|
2
|
+
export { emit } from './emit';
|
|
3
|
+
// Query
|
|
4
|
+
export { getEventsSince } from './getEventsSince';
|
|
5
|
+
export { getEventsByPlan } from './getEventsByPlan';
|
|
6
|
+
export { getEventsByAgent } from './getEventsByAgent';
|
|
7
|
+
export { getEventsByWorktree } from './getEventsByWorktree';
|
|
8
|
+
export { getEventsByBranch } from './getEventsByBranch';
|
|
9
|
+
export { getRecentEvents } from './getRecentEvents';
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event types in the hivemind system
|
|
3
|
+
*/
|
|
4
|
+
export type EventType = 'agent:register' | 'agent:unregister' | 'agent:dead' | 'worktree:register' | 'worktree:switch' | 'worktree:stale' | 'plan:create' | 'plan:sync' | 'plan:join' | 'plan:leave' | 'plan:complete' | 'task:create' | 'task:claim' | 'task:unclaim' | 'task:start' | 'task:block' | 'task:unblock' | 'task:complete' | 'decision' | 'question' | 'answer' | 'context' | 'note';
|
|
5
|
+
/**
|
|
6
|
+
* Event record as stored in DB
|
|
7
|
+
*/
|
|
8
|
+
export type Event = {
|
|
9
|
+
id: string;
|
|
10
|
+
hex: string;
|
|
11
|
+
seq: number;
|
|
12
|
+
timestamp: string;
|
|
13
|
+
agent_id: string;
|
|
14
|
+
plan_id: string | null;
|
|
15
|
+
task_id: string | null;
|
|
16
|
+
worktree_id: string | null;
|
|
17
|
+
branch: string | null;
|
|
18
|
+
event_type: EventType;
|
|
19
|
+
content: string | null;
|
|
20
|
+
metadata: string | null;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Input for creating an event
|
|
24
|
+
*/
|
|
25
|
+
export type EventInput = {
|
|
26
|
+
agent_id?: string;
|
|
27
|
+
plan_id?: string;
|
|
28
|
+
task_id?: string;
|
|
29
|
+
worktree_id?: string;
|
|
30
|
+
branch?: string;
|
|
31
|
+
type: EventType;
|
|
32
|
+
content?: string;
|
|
33
|
+
metadata?: Record<string, unknown>;
|
|
34
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
/**
|
|
3
|
+
* Get current git branch name
|
|
4
|
+
*/
|
|
5
|
+
export function getBranch() {
|
|
6
|
+
try {
|
|
7
|
+
return execSync('git branch --show-current', { stdio: 'pipe' })
|
|
8
|
+
.toString()
|
|
9
|
+
.trim() || null;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { getWorktrees } from './getWorktrees';
|
|
2
|
+
/**
|
|
3
|
+
* Get the worktree for current directory
|
|
4
|
+
*/
|
|
5
|
+
export function getCurrentWorktree() {
|
|
6
|
+
const cwd = process.cwd();
|
|
7
|
+
const worktrees = getWorktrees();
|
|
8
|
+
// Find worktree that contains current directory
|
|
9
|
+
for (const wt of worktrees) {
|
|
10
|
+
if (cwd.startsWith(wt.path)) {
|
|
11
|
+
return wt;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { isGitRepo } from './isGitRepo';
|
|
2
|
+
import { getRepoName } from './getRepoName';
|
|
3
|
+
import { getBranch } from './getBranch';
|
|
4
|
+
import { getRepoRoot } from './getRepoRoot';
|
|
5
|
+
/**
|
|
6
|
+
* Get all git info for current directory
|
|
7
|
+
*/
|
|
8
|
+
export function getGitInfo() {
|
|
9
|
+
if (!isGitRepo()) {
|
|
10
|
+
return {
|
|
11
|
+
isRepo: false,
|
|
12
|
+
repoName: null,
|
|
13
|
+
branch: null,
|
|
14
|
+
root: null,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
isRepo: true,
|
|
19
|
+
repoName: getRepoName(),
|
|
20
|
+
branch: getBranch(),
|
|
21
|
+
root: getRepoRoot(),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
/**
|
|
3
|
+
* Get repository name from git remote or directory
|
|
4
|
+
*/
|
|
5
|
+
export function getRepoName() {
|
|
6
|
+
try {
|
|
7
|
+
// Try to get from remote origin
|
|
8
|
+
const remote = execSync('git remote get-url origin', { stdio: 'pipe' })
|
|
9
|
+
.toString()
|
|
10
|
+
.trim();
|
|
11
|
+
// Extract repo name from URL
|
|
12
|
+
// git@github.com:user/repo.git -> repo
|
|
13
|
+
// https://github.com/user/repo.git -> repo
|
|
14
|
+
const match = remote.match(/\/([^/]+?)(\.git)?$/);
|
|
15
|
+
if (match) {
|
|
16
|
+
return match[1];
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
// No remote, try repo root directory name
|
|
21
|
+
try {
|
|
22
|
+
const root = execSync('git rev-parse --show-toplevel', { stdio: 'pipe' })
|
|
23
|
+
.toString()
|
|
24
|
+
.trim();
|
|
25
|
+
return root.split('/').pop() || null;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
/**
|
|
3
|
+
* Get git repository root path
|
|
4
|
+
*/
|
|
5
|
+
export function getRepoRoot() {
|
|
6
|
+
try {
|
|
7
|
+
return execSync('git rev-parse --show-toplevel', { stdio: 'pipe' })
|
|
8
|
+
.toString()
|
|
9
|
+
.trim() || null;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
/**
|
|
3
|
+
* Get all git worktrees for the repository
|
|
4
|
+
*/
|
|
5
|
+
export function getWorktrees() {
|
|
6
|
+
try {
|
|
7
|
+
const output = execSync('git worktree list --porcelain', { stdio: 'pipe' })
|
|
8
|
+
.toString()
|
|
9
|
+
.trim();
|
|
10
|
+
if (!output)
|
|
11
|
+
return [];
|
|
12
|
+
const worktrees = [];
|
|
13
|
+
let current = {};
|
|
14
|
+
for (const line of output.split('\n')) {
|
|
15
|
+
if (line.startsWith('worktree ')) {
|
|
16
|
+
if (current.path) {
|
|
17
|
+
worktrees.push(current);
|
|
18
|
+
}
|
|
19
|
+
current = { path: line.slice(9), isMain: worktrees.length === 0 };
|
|
20
|
+
}
|
|
21
|
+
else if (line.startsWith('HEAD ')) {
|
|
22
|
+
current.commit = line.slice(5);
|
|
23
|
+
}
|
|
24
|
+
else if (line.startsWith('branch ')) {
|
|
25
|
+
current.branch = line.slice(7).replace('refs/heads/', '');
|
|
26
|
+
}
|
|
27
|
+
else if (line === 'detached') {
|
|
28
|
+
current.branch = null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (current.path) {
|
|
32
|
+
worktrees.push(current);
|
|
33
|
+
}
|
|
34
|
+
return worktrees;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { isGitRepo } from './isGitRepo';
|
|
2
|
+
export { getRepoName } from './getRepoName';
|
|
3
|
+
export { getBranch } from './getBranch';
|
|
4
|
+
export { getRepoRoot } from './getRepoRoot';
|
|
5
|
+
export { getGitInfo } from './getGitInfo';
|
|
6
|
+
export type { GitInfo } from './getGitInfo';
|
|
7
|
+
export { getWorktrees } from './getWorktrees';
|
|
8
|
+
export type { Worktree } from './getWorktrees';
|
|
9
|
+
export { getCurrentWorktree } from './getCurrentWorktree';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { isGitRepo } from './isGitRepo';
|
|
2
|
+
export { getRepoName } from './getRepoName';
|
|
3
|
+
export { getBranch } from './getBranch';
|
|
4
|
+
export { getRepoRoot } from './getRepoRoot';
|
|
5
|
+
export { getGitInfo } from './getGitInfo';
|
|
6
|
+
export { getWorktrees } from './getWorktrees';
|
|
7
|
+
export { getCurrentWorktree } from './getCurrentWorktree';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
/**
|
|
3
|
+
* Check if current directory is inside a git repo
|
|
4
|
+
*/
|
|
5
|
+
export function isGitRepo() {
|
|
6
|
+
try {
|
|
7
|
+
execSync('git rev-parse --is-inside-work-tree', { stdio: 'pipe' });
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { runSessionStartHook } from './sessionStart';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { runSessionStartHook } from './sessionStart';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session start hook - auto-join hivemind when starting a Claude session
|
|
3
|
+
*
|
|
4
|
+
* Installation: Add to .claude/settings.json:
|
|
5
|
+
* "hooks": {
|
|
6
|
+
* "SessionStart": [{
|
|
7
|
+
* "matcher": "startup",
|
|
8
|
+
* "hooks": [{ "type": "command", "command": "bun run /path/to/hivemind/src/hooks/sessionStart.ts" }]
|
|
9
|
+
* }]
|
|
10
|
+
* }
|
|
11
|
+
*
|
|
12
|
+
* Claude Code passes JSON via stdin with session_id, transcript_path, cwd, etc.
|
|
13
|
+
*/
|
|
14
|
+
type HookInput = {
|
|
15
|
+
session_id?: string;
|
|
16
|
+
transcript_path?: string;
|
|
17
|
+
cwd?: string;
|
|
18
|
+
permission_mode?: string;
|
|
19
|
+
};
|
|
20
|
+
export declare function runSessionStartHook(input?: HookInput): void;
|
|
21
|
+
export {};
|