@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.
Files changed (73) hide show
  1. package/README.md +67 -6
  2. package/dist/agents/agents.test.js +79 -0
  3. package/dist/agents/getAgentByPid.d.ts +7 -0
  4. package/dist/agents/getAgentByPid.js +13 -0
  5. package/dist/agents/index.d.ts +4 -0
  6. package/dist/agents/index.js +4 -0
  7. package/dist/agents/livenessMode.d.ts +2 -0
  8. package/dist/agents/livenessMode.js +13 -0
  9. package/dist/agents/registerAgent.js +4 -3
  10. package/dist/agents/touchAgent.d.ts +5 -0
  11. package/dist/agents/touchAgent.js +13 -0
  12. package/dist/agents/types.d.ts +1 -0
  13. package/dist/agents/updateAgentSession.d.ts +6 -0
  14. package/dist/agents/updateAgentSession.js +12 -0
  15. package/dist/cli/init.js +13 -5
  16. package/dist/cli/install.d.ts +1 -0
  17. package/dist/cli/install.js +9 -5
  18. package/dist/cli/registerMcp.js +4 -3
  19. package/dist/coordinator/index.js +32 -0
  20. package/dist/db/db.test.js +4 -3
  21. package/dist/db/getConnection.js +2 -0
  22. package/dist/db/initializeDb.js +2 -0
  23. package/dist/db/migrateDb.d.ts +6 -0
  24. package/dist/db/migrateDb.js +14 -0
  25. package/dist/db/schema.sql +134 -0
  26. package/dist/git/getBranch.d.ts +1 -1
  27. package/dist/git/getBranch.js +2 -2
  28. package/dist/git/getCurrentWorktree.d.ts +2 -2
  29. package/dist/git/getCurrentWorktree.js +6 -6
  30. package/dist/git/getGitInfo.d.ts +2 -2
  31. package/dist/git/getGitInfo.js +6 -6
  32. package/dist/git/getRepoName.d.ts +1 -1
  33. package/dist/git/getRepoName.js +3 -3
  34. package/dist/git/getRepoRoot.d.ts +1 -1
  35. package/dist/git/getRepoRoot.js +2 -2
  36. package/dist/git/getWorktrees.d.ts +1 -1
  37. package/dist/git/getWorktrees.js +2 -2
  38. package/dist/git/isGitRepo.d.ts +2 -2
  39. package/dist/git/isGitRepo.js +3 -3
  40. package/dist/hooks/sessionStart.d.ts +1 -1
  41. package/dist/hooks/sessionStart.js +77 -13
  42. package/dist/init/claudeConfig.d.ts +13 -3
  43. package/dist/init/claudeConfig.js +71 -29
  44. package/dist/mcp/core.d.ts +157 -0
  45. package/dist/mcp/core.js +78 -0
  46. package/dist/mcp/httpServer.d.ts +5 -0
  47. package/dist/mcp/httpServer.js +142 -0
  48. package/dist/mcp/httpServer.test.d.ts +1 -0
  49. package/dist/mcp/httpServer.test.js +164 -0
  50. package/dist/mcp/server.js +2 -93
  51. package/dist/mcp/tools/emitEvent.d.ts +1 -0
  52. package/dist/mcp/tools/emitEvent.js +20 -5
  53. package/dist/mcp/tools/events.d.ts +10 -0
  54. package/dist/mcp/tools/events.js +13 -0
  55. package/dist/mcp/tools/query.d.ts +5 -0
  56. package/dist/mcp/tools/query.js +15 -0
  57. package/dist/mcp/tools/register.d.ts +1 -0
  58. package/dist/mcp/tools/register.js +46 -24
  59. package/dist/mcp/tools/status.d.ts +35 -4
  60. package/dist/mcp/tools/status.js +103 -13
  61. package/dist/mcp/tools/status.test.d.ts +1 -0
  62. package/dist/mcp/tools/status.test.js +93 -0
  63. package/dist/mcp/tools/tasks.js +4 -0
  64. package/dist/test/factories/index.d.ts +1 -0
  65. package/dist/test/factories/index.js +1 -0
  66. package/dist/test/factories/worktreeFactory.d.ts +11 -0
  67. package/dist/test/factories/worktreeFactory.js +15 -0
  68. package/dist/test/setup.d.ts +1 -1
  69. package/dist/test/setup.js +30 -28
  70. package/dist/watcher/planWatcher.js +18 -0
  71. package/dist/worktrees/syncWorktreesFromGit.d.ts +1 -1
  72. package/dist/worktrees/syncWorktreesFromGit.js +2 -2
  73. 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);
@@ -1,4 +1,4 @@
1
1
  /**
2
2
  * Get current git branch name
3
3
  */
4
- export declare function getBranch(): string | null;
4
+ export declare function getBranch(cwd?: string): string | null;
@@ -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 current directory
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 current directory
3
+ * Get the worktree for a directory (defaults to process.cwd())
4
4
  */
5
- export function getCurrentWorktree() {
6
- const cwd = process.cwd();
7
- const worktrees = getWorktrees();
8
- // Find worktree that contains current directory
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 (cwd.startsWith(wt.path)) {
10
+ if (targetDir.startsWith(wt.path)) {
11
11
  return wt;
12
12
  }
13
13
  }
@@ -5,6 +5,6 @@ export type GitInfo = {
5
5
  root: string | null;
6
6
  };
7
7
  /**
8
- * Get all git info for current directory
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;
@@ -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 current directory
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
  }
@@ -1,4 +1,4 @@
1
1
  /**
2
2
  * Get repository name from git remote or directory
3
3
  */
4
- export declare function getRepoName(): string | null;
4
+ export declare function getRepoName(cwd?: string): string | null;
@@ -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;
@@ -1,4 +1,4 @@
1
1
  /**
2
2
  * Get git repository root path
3
3
  */
4
- export declare function getRepoRoot(): string | null;
4
+ export declare function getRepoRoot(cwd?: string): string | null;
@@ -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
  }
@@ -7,4 +7,4 @@ export type Worktree = {
7
7
  /**
8
8
  * Get all git worktrees for the repository
9
9
  */
10
- export declare function getWorktrees(): Worktree[];
10
+ export declare function getWorktrees(cwd?: string): Worktree[];
@@ -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)
@@ -1,4 +1,4 @@
1
1
  /**
2
- * Check if current directory is inside a git repo
2
+ * Check if directory is inside a git repo
3
3
  */
4
- export declare function isGitRepo(): boolean;
4
+ export declare function isGitRepo(cwd?: string): boolean;
@@ -1,10 +1,10 @@
1
1
  import { execSync } from 'child_process';
2
2
  /**
3
- * Check if current directory is inside a git repo
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 {
@@ -17,5 +17,5 @@ type HookInput = {
17
17
  cwd?: string;
18
18
  permission_mode?: string;
19
19
  };
20
- export declare function runSessionStartHook(input?: HookInput): void;
20
+ export declare function runSessionStartHook(input?: HookInput): Promise<void>;
21
21
  export {};
@@ -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
- const gitInfo = getGitInfo();
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
- // process.ppid is Claude's process (or close to it in the process tree)
49
- const pid = process.ppid;
50
- const result = executeRegister({
51
- project: gitInfo.repoName,
52
- label,
53
- sessionId,
54
- pid,
55
- });
56
- // Get status to show other agents
57
- const status = executeStatus({ project: gitInfo.repoName });
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, optionally with custom events section
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, _project: string, hivemindRoot: 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 = `## Hivemind Events
16
+ const DEFAULT_EVENTS_SECTION = `## Events
17
17
 
18
- Emit concise events to coordinate. **Under 80 chars.** Map, not territory.
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=decision content="Redis for cache, Postgres for persistence"
22
- hivemind_emit type=context content="API rate limit: 100/min per key"
23
- hivemind_emit type=note content="003 done, starting 004"
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
- **Emit for:** architectural decisions, discoveries others need, blockers, questions.
27
+ Not one combined note. Other agents need granular visibility.
27
28
 
28
- **Skip:** routine progress, obvious steps, anything verbose.`;
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, optionally with custom events section
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
- ## Automatic Behaviors
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
- - \`hivemind_status\` - View active agents, plans, and recent activity
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. **Before starting work**: Check \`hivemind_status\` to see what others are doing
79
- 2. **When making decisions**: Use \`hivemind_emit\` with type "decision" to share
80
- 3. **When blocked**: Use \`hivemind_emit\` with type "question" to ask for help
81
- 4. **Just work**: Your todos sync automatically - focus on the task!
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
- hooks: [
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, _project, hivemindRoot) {
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);