@inixiative/hivemind 0.1.5 → 0.1.8
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 +67 -6
- package/dist/agents/agents.test.js +79 -0
- package/dist/agents/getAgentByPid.d.ts +7 -0
- package/dist/agents/getAgentByPid.js +13 -0
- package/dist/agents/index.d.ts +4 -0
- package/dist/agents/index.js +4 -0
- package/dist/agents/livenessMode.d.ts +2 -0
- package/dist/agents/livenessMode.js +13 -0
- package/dist/agents/registerAgent.js +4 -3
- package/dist/agents/touchAgent.d.ts +5 -0
- package/dist/agents/touchAgent.js +13 -0
- package/dist/agents/types.d.ts +1 -0
- package/dist/agents/updateAgentSession.d.ts +6 -0
- package/dist/agents/updateAgentSession.js +12 -0
- package/dist/cli/init.js +13 -5
- package/dist/cli/install.d.ts +1 -0
- package/dist/cli/install.js +9 -5
- package/dist/cli/registerMcp.js +4 -3
- package/dist/coordinator/index.js +32 -0
- package/dist/db/db.test.js +4 -3
- package/dist/db/getConnection.js +2 -0
- package/dist/db/initializeDb.js +2 -0
- package/dist/db/migrateDb.d.ts +6 -0
- package/dist/db/migrateDb.js +14 -0
- package/dist/db/schema.sql +134 -0
- package/dist/git/getBranch.d.ts +1 -1
- package/dist/git/getBranch.js +2 -2
- package/dist/git/getCurrentWorktree.d.ts +2 -2
- package/dist/git/getCurrentWorktree.js +6 -6
- package/dist/git/getGitInfo.d.ts +2 -2
- package/dist/git/getGitInfo.js +6 -6
- package/dist/git/getRepoName.d.ts +1 -1
- package/dist/git/getRepoName.js +3 -3
- package/dist/git/getRepoRoot.d.ts +1 -1
- package/dist/git/getRepoRoot.js +2 -2
- package/dist/git/getWorktrees.d.ts +1 -1
- package/dist/git/getWorktrees.js +2 -2
- package/dist/git/isGitRepo.d.ts +2 -2
- package/dist/git/isGitRepo.js +3 -3
- package/dist/hooks/sessionStart.d.ts +1 -1
- package/dist/hooks/sessionStart.js +77 -13
- package/dist/init/claudeConfig.d.ts +13 -3
- package/dist/init/claudeConfig.js +71 -29
- package/dist/mcp/core.d.ts +157 -0
- package/dist/mcp/core.js +78 -0
- package/dist/mcp/httpServer.d.ts +5 -0
- package/dist/mcp/httpServer.js +142 -0
- package/dist/mcp/httpServer.test.d.ts +1 -0
- package/dist/mcp/httpServer.test.js +164 -0
- package/dist/mcp/server.js +2 -93
- package/dist/mcp/tools/emitEvent.d.ts +1 -0
- package/dist/mcp/tools/emitEvent.js +20 -5
- package/dist/mcp/tools/events.d.ts +10 -0
- package/dist/mcp/tools/events.js +13 -0
- package/dist/mcp/tools/query.d.ts +5 -0
- package/dist/mcp/tools/query.js +15 -0
- package/dist/mcp/tools/register.d.ts +1 -0
- package/dist/mcp/tools/register.js +46 -24
- package/dist/mcp/tools/status.d.ts +35 -4
- package/dist/mcp/tools/status.js +103 -13
- package/dist/mcp/tools/status.test.d.ts +1 -0
- package/dist/mcp/tools/status.test.js +93 -0
- package/dist/mcp/tools/tasks.js +4 -0
- package/dist/test/factories/index.d.ts +1 -0
- package/dist/test/factories/index.js +1 -0
- package/dist/test/factories/worktreeFactory.d.ts +11 -0
- package/dist/test/factories/worktreeFactory.js +15 -0
- package/dist/test/setup.d.ts +1 -1
- package/dist/test/setup.js +30 -28
- package/dist/watcher/planWatcher.js +18 -0
- package/dist/worktrees/syncWorktreesFromGit.d.ts +1 -1
- package/dist/worktrees/syncWorktreesFromGit.js +2 -2
- package/package.json +2 -1
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
-- Hivemind Schema
|
|
2
|
+
-- Timestamps: yyyy/mm/dd hh:mm:ss TZ
|
|
3
|
+
|
|
4
|
+
-- Agents: active Claude instances
|
|
5
|
+
CREATE TABLE IF NOT EXISTS agents (
|
|
6
|
+
id TEXT PRIMARY KEY, -- agt_7a3f2b or agt_7a3f2b_alice
|
|
7
|
+
hex TEXT NOT NULL, -- 7a3f2b
|
|
8
|
+
label TEXT, -- alice (optional)
|
|
9
|
+
status TEXT DEFAULT 'active', -- active/idle/dead
|
|
10
|
+
pid INTEGER, -- listener process ID
|
|
11
|
+
session_id TEXT, -- for claude --resume
|
|
12
|
+
last_seen_at TEXT, -- heartbeat/lease timestamp
|
|
13
|
+
current_plan_id TEXT, -- pln_e9d2c1_auth
|
|
14
|
+
current_task_id TEXT, -- tsk_e9d2c1_001
|
|
15
|
+
worktree_id TEXT, -- wkt_a1b2c3
|
|
16
|
+
context_summary TEXT, -- what this agent knows
|
|
17
|
+
created_at TEXT NOT NULL,
|
|
18
|
+
|
|
19
|
+
FOREIGN KEY (current_plan_id) REFERENCES plans(id),
|
|
20
|
+
FOREIGN KEY (current_task_id) REFERENCES tasks(id),
|
|
21
|
+
FOREIGN KEY (worktree_id) REFERENCES worktrees(id)
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
-- Worktrees: git worktrees being used
|
|
25
|
+
CREATE TABLE IF NOT EXISTS worktrees (
|
|
26
|
+
id TEXT PRIMARY KEY, -- wkt_a1b2c3
|
|
27
|
+
hex TEXT NOT NULL, -- a1b2c3
|
|
28
|
+
label TEXT, -- feature-auth (optional)
|
|
29
|
+
path TEXT NOT NULL UNIQUE, -- /path/to/worktree
|
|
30
|
+
branch TEXT, -- feature/auth
|
|
31
|
+
commit_hash TEXT, -- current commit hash
|
|
32
|
+
is_main INTEGER DEFAULT 0, -- 1 if main worktree
|
|
33
|
+
status TEXT DEFAULT 'active', -- active/stale
|
|
34
|
+
created_at TEXT NOT NULL,
|
|
35
|
+
last_seen TEXT
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
-- Plans: coordinated work streams
|
|
39
|
+
CREATE TABLE IF NOT EXISTS plans (
|
|
40
|
+
id TEXT PRIMARY KEY, -- pln_e9d2c1_auth
|
|
41
|
+
hex TEXT NOT NULL, -- e9d2c1
|
|
42
|
+
label TEXT, -- auth (optional)
|
|
43
|
+
title TEXT NOT NULL,
|
|
44
|
+
description TEXT,
|
|
45
|
+
status TEXT DEFAULT 'active', -- active/paused/complete
|
|
46
|
+
branch TEXT, -- associated git branch
|
|
47
|
+
worktree_id TEXT, -- associated worktree
|
|
48
|
+
claude_session_id TEXT, -- claude --resume session ID
|
|
49
|
+
created_at TEXT NOT NULL,
|
|
50
|
+
created_by TEXT, -- agt_7a3f2b
|
|
51
|
+
|
|
52
|
+
FOREIGN KEY (created_by) REFERENCES agents(id),
|
|
53
|
+
FOREIGN KEY (worktree_id) REFERENCES worktrees(id)
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
-- Tasks: atomic units of work within plans
|
|
57
|
+
CREATE TABLE IF NOT EXISTS tasks (
|
|
58
|
+
id TEXT PRIMARY KEY, -- tsk_e9d2c1_001_jwt
|
|
59
|
+
plan_hex TEXT NOT NULL, -- e9d2c1 (inherited from plan)
|
|
60
|
+
seq TEXT NOT NULL, -- 001 or 001.1
|
|
61
|
+
label TEXT, -- jwt (optional)
|
|
62
|
+
plan_id TEXT NOT NULL, -- pln_e9d2c1_auth
|
|
63
|
+
title TEXT NOT NULL,
|
|
64
|
+
description TEXT,
|
|
65
|
+
status TEXT DEFAULT 'pending', -- pending/claimed/in_progress/blocked/done
|
|
66
|
+
branch TEXT, -- feature/auth (can differ from plan branch)
|
|
67
|
+
worktree_id TEXT, -- wkt_a1b2c3 (where task is being worked)
|
|
68
|
+
claimed_by TEXT, -- agt_7a3f2b
|
|
69
|
+
claimed_at TEXT,
|
|
70
|
+
completed_at TEXT,
|
|
71
|
+
outcome TEXT,
|
|
72
|
+
parent_task_id TEXT, -- for subtasks
|
|
73
|
+
|
|
74
|
+
FOREIGN KEY (plan_id) REFERENCES plans(id),
|
|
75
|
+
FOREIGN KEY (worktree_id) REFERENCES worktrees(id),
|
|
76
|
+
FOREIGN KEY (claimed_by) REFERENCES agents(id),
|
|
77
|
+
FOREIGN KEY (parent_task_id) REFERENCES tasks(id)
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
-- Events: append-only log of all activity
|
|
81
|
+
CREATE TABLE IF NOT EXISTS events (
|
|
82
|
+
id TEXT PRIMARY KEY, -- evt_f1a2b3_00001
|
|
83
|
+
hex TEXT NOT NULL, -- f1a2b3
|
|
84
|
+
seq INTEGER NOT NULL, -- 1, 2, 3...
|
|
85
|
+
timestamp TEXT NOT NULL,
|
|
86
|
+
agent_id TEXT NOT NULL, -- agt_7a3f2b
|
|
87
|
+
plan_id TEXT, -- pln_e9d2c1_auth (optional)
|
|
88
|
+
task_id TEXT, -- tsk_e9d2c1_001 (optional)
|
|
89
|
+
worktree_id TEXT, -- wkt_a1b2c3 (optional)
|
|
90
|
+
branch TEXT, -- feature/auth (optional, git context)
|
|
91
|
+
event_type TEXT NOT NULL, -- decision/claim/complete/etc
|
|
92
|
+
content TEXT, -- event-specific content
|
|
93
|
+
metadata TEXT, -- JSON blob
|
|
94
|
+
|
|
95
|
+
FOREIGN KEY (agent_id) REFERENCES agents(id),
|
|
96
|
+
FOREIGN KEY (plan_id) REFERENCES plans(id),
|
|
97
|
+
FOREIGN KEY (task_id) REFERENCES tasks(id),
|
|
98
|
+
FOREIGN KEY (worktree_id) REFERENCES worktrees(id)
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
-- Indexes
|
|
102
|
+
CREATE INDEX IF NOT EXISTS idx_agents_status ON agents(status);
|
|
103
|
+
CREATE INDEX IF NOT EXISTS idx_agents_last_seen ON agents(last_seen_at);
|
|
104
|
+
CREATE INDEX IF NOT EXISTS idx_agents_plan ON agents(current_plan_id);
|
|
105
|
+
CREATE INDEX IF NOT EXISTS idx_agents_worktree ON agents(worktree_id);
|
|
106
|
+
|
|
107
|
+
CREATE INDEX IF NOT EXISTS idx_worktrees_path ON worktrees(path);
|
|
108
|
+
CREATE INDEX IF NOT EXISTS idx_worktrees_branch ON worktrees(branch);
|
|
109
|
+
CREATE INDEX IF NOT EXISTS idx_worktrees_status ON worktrees(status);
|
|
110
|
+
|
|
111
|
+
CREATE INDEX IF NOT EXISTS idx_plans_status ON plans(status);
|
|
112
|
+
CREATE INDEX IF NOT EXISTS idx_plans_created_by ON plans(created_by);
|
|
113
|
+
CREATE INDEX IF NOT EXISTS idx_plans_worktree ON plans(worktree_id);
|
|
114
|
+
|
|
115
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_plan ON tasks(plan_id);
|
|
116
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
|
|
117
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_claimed_by ON tasks(claimed_by);
|
|
118
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_branch ON tasks(branch);
|
|
119
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_worktree ON tasks(worktree_id);
|
|
120
|
+
|
|
121
|
+
CREATE INDEX IF NOT EXISTS idx_events_timestamp ON events(timestamp);
|
|
122
|
+
CREATE INDEX IF NOT EXISTS idx_events_agent ON events(agent_id);
|
|
123
|
+
CREATE INDEX IF NOT EXISTS idx_events_plan ON events(plan_id);
|
|
124
|
+
CREATE INDEX IF NOT EXISTS idx_events_type ON events(event_type);
|
|
125
|
+
CREATE INDEX IF NOT EXISTS idx_events_worktree ON events(worktree_id);
|
|
126
|
+
CREATE INDEX IF NOT EXISTS idx_events_branch ON events(branch);
|
|
127
|
+
|
|
128
|
+
-- Sequence counter for events
|
|
129
|
+
CREATE TABLE IF NOT EXISTS sequences (
|
|
130
|
+
name TEXT PRIMARY KEY,
|
|
131
|
+
value INTEGER DEFAULT 0
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
INSERT OR IGNORE INTO sequences (name, value) VALUES ('events', 0);
|
package/dist/git/getBranch.d.ts
CHANGED
package/dist/git/getBranch.js
CHANGED
|
@@ -2,9 +2,9 @@ import { execSync } from 'child_process';
|
|
|
2
2
|
/**
|
|
3
3
|
* Get current git branch name
|
|
4
4
|
*/
|
|
5
|
-
export function getBranch() {
|
|
5
|
+
export function getBranch(cwd) {
|
|
6
6
|
try {
|
|
7
|
-
return execSync('git branch --show-current', { stdio: 'pipe' })
|
|
7
|
+
return execSync('git branch --show-current', { stdio: 'pipe', cwd })
|
|
8
8
|
.toString()
|
|
9
9
|
.trim() || null;
|
|
10
10
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Worktree } from './getWorktrees';
|
|
2
2
|
/**
|
|
3
|
-
* Get the worktree for
|
|
3
|
+
* Get the worktree for a directory (defaults to process.cwd())
|
|
4
4
|
*/
|
|
5
|
-
export declare function getCurrentWorktree(): Worktree | null;
|
|
5
|
+
export declare function getCurrentWorktree(cwd?: string): Worktree | null;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { getWorktrees } from './getWorktrees';
|
|
2
2
|
/**
|
|
3
|
-
* Get the worktree for
|
|
3
|
+
* Get the worktree for a directory (defaults to process.cwd())
|
|
4
4
|
*/
|
|
5
|
-
export function getCurrentWorktree() {
|
|
6
|
-
const
|
|
7
|
-
const worktrees = getWorktrees();
|
|
8
|
-
// Find worktree that contains
|
|
5
|
+
export function getCurrentWorktree(cwd) {
|
|
6
|
+
const targetDir = cwd ?? process.cwd();
|
|
7
|
+
const worktrees = getWorktrees(cwd);
|
|
8
|
+
// Find worktree that contains target directory
|
|
9
9
|
for (const wt of worktrees) {
|
|
10
|
-
if (
|
|
10
|
+
if (targetDir.startsWith(wt.path)) {
|
|
11
11
|
return wt;
|
|
12
12
|
}
|
|
13
13
|
}
|
package/dist/git/getGitInfo.d.ts
CHANGED
|
@@ -5,6 +5,6 @@ export type GitInfo = {
|
|
|
5
5
|
root: string | null;
|
|
6
6
|
};
|
|
7
7
|
/**
|
|
8
|
-
* Get all git info for
|
|
8
|
+
* Get all git info for a directory (defaults to process.cwd())
|
|
9
9
|
*/
|
|
10
|
-
export declare function getGitInfo(): GitInfo;
|
|
10
|
+
export declare function getGitInfo(cwd?: string): GitInfo;
|
package/dist/git/getGitInfo.js
CHANGED
|
@@ -3,10 +3,10 @@ import { getRepoName } from './getRepoName';
|
|
|
3
3
|
import { getBranch } from './getBranch';
|
|
4
4
|
import { getRepoRoot } from './getRepoRoot';
|
|
5
5
|
/**
|
|
6
|
-
* Get all git info for
|
|
6
|
+
* Get all git info for a directory (defaults to process.cwd())
|
|
7
7
|
*/
|
|
8
|
-
export function getGitInfo() {
|
|
9
|
-
if (!isGitRepo()) {
|
|
8
|
+
export function getGitInfo(cwd) {
|
|
9
|
+
if (!isGitRepo(cwd)) {
|
|
10
10
|
return {
|
|
11
11
|
isRepo: false,
|
|
12
12
|
repoName: null,
|
|
@@ -16,8 +16,8 @@ export function getGitInfo() {
|
|
|
16
16
|
}
|
|
17
17
|
return {
|
|
18
18
|
isRepo: true,
|
|
19
|
-
repoName: getRepoName(),
|
|
20
|
-
branch: getBranch(),
|
|
21
|
-
root: getRepoRoot(),
|
|
19
|
+
repoName: getRepoName(cwd),
|
|
20
|
+
branch: getBranch(cwd),
|
|
21
|
+
root: getRepoRoot(cwd),
|
|
22
22
|
};
|
|
23
23
|
}
|
package/dist/git/getRepoName.js
CHANGED
|
@@ -2,10 +2,10 @@ import { execSync } from 'child_process';
|
|
|
2
2
|
/**
|
|
3
3
|
* Get repository name from git remote or directory
|
|
4
4
|
*/
|
|
5
|
-
export function getRepoName() {
|
|
5
|
+
export function getRepoName(cwd) {
|
|
6
6
|
try {
|
|
7
7
|
// Try to get from remote origin
|
|
8
|
-
const remote = execSync('git remote get-url origin', { stdio: 'pipe' })
|
|
8
|
+
const remote = execSync('git remote get-url origin', { stdio: 'pipe', cwd })
|
|
9
9
|
.toString()
|
|
10
10
|
.trim();
|
|
11
11
|
// Extract repo name from URL
|
|
@@ -19,7 +19,7 @@ export function getRepoName() {
|
|
|
19
19
|
catch {
|
|
20
20
|
// No remote, try repo root directory name
|
|
21
21
|
try {
|
|
22
|
-
const root = execSync('git rev-parse --show-toplevel', { stdio: 'pipe' })
|
|
22
|
+
const root = execSync('git rev-parse --show-toplevel', { stdio: 'pipe', cwd })
|
|
23
23
|
.toString()
|
|
24
24
|
.trim();
|
|
25
25
|
return root.split('/').pop() || null;
|
package/dist/git/getRepoRoot.js
CHANGED
|
@@ -2,9 +2,9 @@ import { execSync } from 'child_process';
|
|
|
2
2
|
/**
|
|
3
3
|
* Get git repository root path
|
|
4
4
|
*/
|
|
5
|
-
export function getRepoRoot() {
|
|
5
|
+
export function getRepoRoot(cwd) {
|
|
6
6
|
try {
|
|
7
|
-
return execSync('git rev-parse --show-toplevel', { stdio: 'pipe' })
|
|
7
|
+
return execSync('git rev-parse --show-toplevel', { stdio: 'pipe', cwd })
|
|
8
8
|
.toString()
|
|
9
9
|
.trim() || null;
|
|
10
10
|
}
|
package/dist/git/getWorktrees.js
CHANGED
|
@@ -2,9 +2,9 @@ import { execSync } from 'child_process';
|
|
|
2
2
|
/**
|
|
3
3
|
* Get all git worktrees for the repository
|
|
4
4
|
*/
|
|
5
|
-
export function getWorktrees() {
|
|
5
|
+
export function getWorktrees(cwd) {
|
|
6
6
|
try {
|
|
7
|
-
const output = execSync('git worktree list --porcelain', { stdio: 'pipe' })
|
|
7
|
+
const output = execSync('git worktree list --porcelain', { stdio: 'pipe', cwd })
|
|
8
8
|
.toString()
|
|
9
9
|
.trim();
|
|
10
10
|
if (!output)
|
package/dist/git/isGitRepo.d.ts
CHANGED
package/dist/git/isGitRepo.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { execSync } from 'child_process';
|
|
2
2
|
/**
|
|
3
|
-
* Check if
|
|
3
|
+
* Check if directory is inside a git repo
|
|
4
4
|
*/
|
|
5
|
-
export function isGitRepo() {
|
|
5
|
+
export function isGitRepo(cwd) {
|
|
6
6
|
try {
|
|
7
|
-
execSync('git rev-parse --is-inside-work-tree', { stdio: 'pipe' });
|
|
7
|
+
execSync('git rev-parse --is-inside-work-tree', { stdio: 'pipe', cwd });
|
|
8
8
|
return true;
|
|
9
9
|
}
|
|
10
10
|
catch {
|
|
@@ -11,9 +11,22 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Claude Code passes JSON via stdin with session_id, transcript_path, cwd, etc.
|
|
13
13
|
*/
|
|
14
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
15
|
+
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
14
16
|
import { executeRegister } from '../mcp/tools/register';
|
|
15
17
|
import { executeStatus } from '../mcp/tools/status';
|
|
16
18
|
import { getGitInfo } from '../git/getGitInfo';
|
|
19
|
+
function getTextContent(result, errorPrefix) {
|
|
20
|
+
if (!result || typeof result !== 'object') {
|
|
21
|
+
throw new Error(`${errorPrefix} returned invalid payload`);
|
|
22
|
+
}
|
|
23
|
+
const content = result.content;
|
|
24
|
+
const text = content?.find((item) => item?.type === 'text')?.text;
|
|
25
|
+
if (!text) {
|
|
26
|
+
throw new Error(`${errorPrefix} returned no text content`);
|
|
27
|
+
}
|
|
28
|
+
return text;
|
|
29
|
+
}
|
|
17
30
|
function readStdinSync() {
|
|
18
31
|
try {
|
|
19
32
|
// Bun supports reading stdin synchronously
|
|
@@ -37,24 +50,75 @@ function readStdinSync() {
|
|
|
37
50
|
return '';
|
|
38
51
|
}
|
|
39
52
|
}
|
|
40
|
-
export function runSessionStartHook(input) {
|
|
41
|
-
|
|
53
|
+
export async function runSessionStartHook(input) {
|
|
54
|
+
// Use cwd from hook input (Claude's actual working directory), not process.cwd()
|
|
55
|
+
const gitInfo = getGitInfo(input?.cwd);
|
|
42
56
|
if (!gitInfo.isRepo || !gitInfo.repoName) {
|
|
43
57
|
return;
|
|
44
58
|
}
|
|
45
59
|
const sessionId = input?.session_id;
|
|
46
60
|
const label = process.env.CLAUDE_AGENT_LABEL;
|
|
47
61
|
try {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
62
|
+
const remoteUrl = process.env.HIVEMIND_REMOTE_URL?.trim();
|
|
63
|
+
let result;
|
|
64
|
+
let status;
|
|
65
|
+
if (remoteUrl) {
|
|
66
|
+
const token = process.env.HIVEMIND_API_TOKEN?.trim();
|
|
67
|
+
if (!token) {
|
|
68
|
+
throw new Error('HIVEMIND_API_TOKEN is required when HIVEMIND_REMOTE_URL is set');
|
|
69
|
+
}
|
|
70
|
+
const client = new Client({
|
|
71
|
+
name: 'hivemind-session-start',
|
|
72
|
+
version: '0.1.0',
|
|
73
|
+
}, { capabilities: {} });
|
|
74
|
+
const transport = new StreamableHTTPClientTransport(new URL(remoteUrl), {
|
|
75
|
+
requestInit: {
|
|
76
|
+
headers: {
|
|
77
|
+
Authorization: `Bearer ${token}`,
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
await client.connect(transport);
|
|
82
|
+
try {
|
|
83
|
+
const registerCall = await client.callTool({
|
|
84
|
+
name: 'hivemind_register',
|
|
85
|
+
arguments: {
|
|
86
|
+
project: gitInfo.repoName,
|
|
87
|
+
label,
|
|
88
|
+
sessionId,
|
|
89
|
+
cwd: input?.cwd,
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
const registerText = getTextContent(registerCall, 'Remote hivemind_register');
|
|
93
|
+
result = JSON.parse(registerText);
|
|
94
|
+
const statusCall = await client.callTool({
|
|
95
|
+
name: 'hivemind_status',
|
|
96
|
+
arguments: {
|
|
97
|
+
project: gitInfo.repoName,
|
|
98
|
+
agentId: result.agentId,
|
|
99
|
+
sessionId,
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
const statusText = getTextContent(statusCall, 'Remote hivemind_status');
|
|
103
|
+
status = JSON.parse(statusText);
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
await client.close();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
// process.ppid is Claude's process (or close to it in the process tree)
|
|
111
|
+
const pid = process.ppid;
|
|
112
|
+
result = executeRegister({
|
|
113
|
+
project: gitInfo.repoName,
|
|
114
|
+
label,
|
|
115
|
+
sessionId,
|
|
116
|
+
pid,
|
|
117
|
+
cwd: input?.cwd,
|
|
118
|
+
});
|
|
119
|
+
// Get status to show other agents
|
|
120
|
+
status = executeStatus({ project: gitInfo.repoName });
|
|
121
|
+
}
|
|
58
122
|
const otherAgents = status.activeAgents?.filter((a) => a.id !== result.agentId) || [];
|
|
59
123
|
// Output agent info for Claude's context
|
|
60
124
|
const lines = [
|
|
@@ -89,5 +153,5 @@ if (import.meta.main) {
|
|
|
89
153
|
// Not valid JSON, ignore
|
|
90
154
|
}
|
|
91
155
|
}
|
|
92
|
-
runSessionStartHook(input);
|
|
156
|
+
await runSessionStartHook(input);
|
|
93
157
|
}
|
|
@@ -14,15 +14,25 @@
|
|
|
14
14
|
*/
|
|
15
15
|
export declare function extractMarkdownSection(content: string, heading: string): string | null;
|
|
16
16
|
/**
|
|
17
|
-
* Build the CLAUDE.md content
|
|
17
|
+
* Build the CLAUDE.md content with project name and optional custom events section
|
|
18
18
|
*/
|
|
19
|
-
export declare function buildHivemindClaudeMd(customEventsSection?: string): string;
|
|
19
|
+
export declare function buildHivemindClaudeMd(projectName: string, customEventsSection?: string): string;
|
|
20
20
|
/**
|
|
21
21
|
* Settings.json hooks for auto-registration
|
|
22
|
+
*
|
|
23
|
+
* SessionStart matchers:
|
|
24
|
+
* - startup: New session
|
|
25
|
+
* - resume: --resume, --continue, /resume
|
|
26
|
+
* - clear: /clear command
|
|
27
|
+
* - compact: Auto or manual compact (new session ID assigned)
|
|
28
|
+
*
|
|
29
|
+
* We register on all events to handle compaction gracefully.
|
|
30
|
+
* The hook checks PID to reuse existing agent when session ID changes.
|
|
22
31
|
*/
|
|
23
32
|
export declare function getHivemindHooks(hivemindRoot: string): {
|
|
24
33
|
hooks: {
|
|
25
34
|
SessionStart: {
|
|
35
|
+
matcher: string;
|
|
26
36
|
hooks: {
|
|
27
37
|
type: string;
|
|
28
38
|
command: string;
|
|
@@ -33,7 +43,7 @@ export declare function getHivemindHooks(hivemindRoot: string): {
|
|
|
33
43
|
/**
|
|
34
44
|
* Initialize Claude Code configuration for hivemind
|
|
35
45
|
*/
|
|
36
|
-
export declare function initClaudeConfig(projectRoot: string,
|
|
46
|
+
export declare function initClaudeConfig(projectRoot: string, project: string, hivemindRoot: string): {
|
|
37
47
|
created: string[];
|
|
38
48
|
updated: string[];
|
|
39
49
|
};
|
|
@@ -13,19 +13,26 @@ import { join } from 'path';
|
|
|
13
13
|
/**
|
|
14
14
|
* Default events instructions (used if project doesn't define custom ones)
|
|
15
15
|
*/
|
|
16
|
-
const DEFAULT_EVENTS_SECTION = `##
|
|
16
|
+
const DEFAULT_EVENTS_SECTION = `## Events
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
Keep events **under 80 chars**. Map, not territory.
|
|
19
19
|
|
|
20
|
+
**One event per accomplishment.** If you complete 3 tasks, emit 3 notes:
|
|
20
21
|
\`\`\`
|
|
21
|
-
hivemind_emit type=
|
|
22
|
-
hivemind_emit type=
|
|
23
|
-
hivemind_emit type=note content="
|
|
22
|
+
hivemind_emit type=note content="Added Redis caching layer"
|
|
23
|
+
hivemind_emit type=note content="Updated API rate limiting to 100/min"
|
|
24
|
+
hivemind_emit type=note content="Fixed auth token refresh bug"
|
|
24
25
|
\`\`\`
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
Not one combined note. Other agents need granular visibility.
|
|
27
28
|
|
|
28
|
-
**
|
|
29
|
+
**Event types:**
|
|
30
|
+
- \`decision\` - Architectural choices others should know
|
|
31
|
+
- \`context\` - Discovered constraints, gotchas
|
|
32
|
+
- \`note\` - Task completions, progress updates
|
|
33
|
+
- \`question\` - When blocked and need input
|
|
34
|
+
|
|
35
|
+
**Skip:** routine progress on obvious steps, verbose explanations.`;
|
|
29
36
|
/**
|
|
30
37
|
* Extract a markdown section by heading from content
|
|
31
38
|
* Returns the section content (including heading) or null if not found
|
|
@@ -51,50 +58,85 @@ export function extractMarkdownSection(content, heading) {
|
|
|
51
58
|
return lines.slice(startIdx, endIdx).join('\n').trim();
|
|
52
59
|
}
|
|
53
60
|
/**
|
|
54
|
-
* Build the CLAUDE.md content
|
|
61
|
+
* Build the CLAUDE.md content with project name and optional custom events section
|
|
55
62
|
*/
|
|
56
|
-
export function buildHivemindClaudeMd(customEventsSection) {
|
|
63
|
+
export function buildHivemindClaudeMd(projectName, customEventsSection) {
|
|
57
64
|
const eventsSection = customEventsSection || DEFAULT_EVENTS_SECTION;
|
|
58
65
|
return `# Hivemind Agent Instructions
|
|
59
66
|
|
|
60
67
|
You are part of a **hivemind** - a coordinated group of Claude agents working together.
|
|
61
68
|
|
|
62
|
-
##
|
|
63
|
-
|
|
64
|
-
**Your todos are automatically synced to hivemind.** When you use TodoWrite, the hivemind watcher detects changes and syncs them to a shared plan. Other agents can see your progress in real-time.
|
|
69
|
+
## Lifecycle
|
|
65
70
|
|
|
66
71
|
Your lifecycle is tracked by PID - no heartbeats needed. When you exit, hivemind automatically marks you as inactive.
|
|
67
72
|
|
|
68
|
-
## MCP Tools
|
|
73
|
+
## MCP Tools - USE THESE
|
|
74
|
+
|
|
75
|
+
**\`hivemind_status\`** - CALL THIS FIRST when you start a session. Shows active agents, what they're working on, and recent events. Prevents duplicate work.
|
|
76
|
+
|
|
77
|
+
**\`hivemind_emit\`** - Share important context with other agents. Use for:
|
|
78
|
+
- \`type=decision\` - Architectural choices others should know
|
|
79
|
+
- \`type=context\` - Discovered constraints (rate limits, gotchas)
|
|
80
|
+
- \`type=question\` - When you're blocked and need input
|
|
69
81
|
|
|
70
|
-
|
|
71
|
-
- \`hivemind_query\` - See what other agents are doing
|
|
72
|
-
- \`hivemind_emit\` - Share notes, decisions, questions with other agents
|
|
82
|
+
**\`hivemind_query\`** - Ask questions about the hivemind state.
|
|
73
83
|
|
|
74
84
|
${eventsSection}
|
|
75
85
|
|
|
76
86
|
## Coordination Protocol
|
|
77
87
|
|
|
78
|
-
1. **
|
|
79
|
-
2. **
|
|
80
|
-
3. **
|
|
81
|
-
4. **
|
|
88
|
+
1. **Session start**: Run \`hivemind_status\` to see who's online and what's happening
|
|
89
|
+
2. **Architectural decisions**: Emit with \`type=decision\` so others know
|
|
90
|
+
3. **Blockers**: Emit with \`type=question\` - another agent may have context
|
|
91
|
+
4. **Focus on work**: The system tracks your lifecycle automatically
|
|
92
|
+
|
|
93
|
+
## Plan Mode
|
|
94
|
+
|
|
95
|
+
When creating plan files in ~/.claude/plans/, **always start with frontmatter**:
|
|
96
|
+
|
|
97
|
+
\`\`\`yaml
|
|
98
|
+
---
|
|
99
|
+
project: ${projectName}
|
|
100
|
+
---
|
|
101
|
+
\`\`\`
|
|
102
|
+
|
|
103
|
+
This ensures plans are synced to the correct hivemind project database.
|
|
82
104
|
`;
|
|
83
105
|
}
|
|
84
106
|
/**
|
|
85
107
|
* Settings.json hooks for auto-registration
|
|
108
|
+
*
|
|
109
|
+
* SessionStart matchers:
|
|
110
|
+
* - startup: New session
|
|
111
|
+
* - resume: --resume, --continue, /resume
|
|
112
|
+
* - clear: /clear command
|
|
113
|
+
* - compact: Auto or manual compact (new session ID assigned)
|
|
114
|
+
*
|
|
115
|
+
* We register on all events to handle compaction gracefully.
|
|
116
|
+
* The hook checks PID to reuse existing agent when session ID changes.
|
|
86
117
|
*/
|
|
87
118
|
export function getHivemindHooks(hivemindRoot) {
|
|
119
|
+
const sessionStartCommand = {
|
|
120
|
+
type: 'command',
|
|
121
|
+
command: `bun run ${hivemindRoot}/src/hooks/sessionStart.ts`,
|
|
122
|
+
};
|
|
88
123
|
return {
|
|
89
124
|
hooks: {
|
|
90
125
|
SessionStart: [
|
|
126
|
+
// Handle new sessions
|
|
127
|
+
{
|
|
128
|
+
matcher: 'startup',
|
|
129
|
+
hooks: [sessionStartCommand],
|
|
130
|
+
},
|
|
131
|
+
// Handle compaction (session ID changes, but PID stays same)
|
|
132
|
+
{
|
|
133
|
+
matcher: 'compact',
|
|
134
|
+
hooks: [sessionStartCommand],
|
|
135
|
+
},
|
|
136
|
+
// Handle resume scenarios
|
|
91
137
|
{
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
type: 'command',
|
|
95
|
-
command: `bun run ${hivemindRoot}/src/hooks/sessionStart.ts`,
|
|
96
|
-
},
|
|
97
|
-
],
|
|
138
|
+
matcher: 'resume',
|
|
139
|
+
hooks: [sessionStartCommand],
|
|
98
140
|
},
|
|
99
141
|
],
|
|
100
142
|
},
|
|
@@ -103,7 +145,7 @@ export function getHivemindHooks(hivemindRoot) {
|
|
|
103
145
|
/**
|
|
104
146
|
* Initialize Claude Code configuration for hivemind
|
|
105
147
|
*/
|
|
106
|
-
export function initClaudeConfig(projectRoot,
|
|
148
|
+
export function initClaudeConfig(projectRoot, project, hivemindRoot) {
|
|
107
149
|
const claudeDir = join(projectRoot, '.claude');
|
|
108
150
|
const settingsPath = join(claudeDir, 'settings.json');
|
|
109
151
|
const claudeMdPath = join(claudeDir, 'CLAUDE.md');
|
|
@@ -141,8 +183,8 @@ export function initClaudeConfig(projectRoot, _project, hivemindRoot) {
|
|
|
141
183
|
customEventsSection = extracted;
|
|
142
184
|
}
|
|
143
185
|
}
|
|
144
|
-
// Build the hivemind instructions (with custom or default events section)
|
|
145
|
-
const hivemindClaudeMd = buildHivemindClaudeMd(customEventsSection);
|
|
186
|
+
// Build the hivemind instructions (with project name and custom or default events section)
|
|
187
|
+
const hivemindClaudeMd = buildHivemindClaudeMd(project, customEventsSection);
|
|
146
188
|
// Create or update .claude/CLAUDE.md
|
|
147
189
|
if (!existsSync(claudeMdPath)) {
|
|
148
190
|
writeFileSync(claudeMdPath, hivemindClaudeMd);
|