@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.
Files changed (287) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +115 -0
  3. package/dist/agents/agents.test.d.ts +1 -0
  4. package/dist/agents/agents.test.js +167 -0
  5. package/dist/agents/getActiveAgents.d.ts +6 -0
  6. package/dist/agents/getActiveAgents.js +11 -0
  7. package/dist/agents/getAgent.d.ts +6 -0
  8. package/dist/agents/getAgent.js +7 -0
  9. package/dist/agents/getAgentBySessionId.d.ts +10 -0
  10. package/dist/agents/getAgentBySessionId.js +17 -0
  11. package/dist/agents/index.d.ts +10 -0
  12. package/dist/agents/index.js +12 -0
  13. package/dist/agents/markAgentDead.d.ts +6 -0
  14. package/dist/agents/markAgentDead.js +26 -0
  15. package/dist/agents/markAgentIdle.d.ts +5 -0
  16. package/dist/agents/markAgentIdle.js +12 -0
  17. package/dist/agents/registerAgent.d.ts +6 -0
  18. package/dist/agents/registerAgent.js +29 -0
  19. package/dist/agents/types.d.ts +30 -0
  20. package/dist/agents/types.js +1 -0
  21. package/dist/agents/unregisterAgent.d.ts +5 -0
  22. package/dist/agents/unregisterAgent.js +8 -0
  23. package/dist/agents/updateAgentContext.d.ts +5 -0
  24. package/dist/agents/updateAgentContext.js +12 -0
  25. package/dist/agents/updateAgentTask.d.ts +5 -0
  26. package/dist/agents/updateAgentTask.js +12 -0
  27. package/dist/agents/updateAgentWorktree.d.ts +5 -0
  28. package/dist/agents/updateAgentWorktree.js +12 -0
  29. package/dist/cli/index.d.ts +8 -0
  30. package/dist/cli/index.js +8 -0
  31. package/dist/cli/init.d.ts +14 -0
  32. package/dist/cli/init.js +71 -0
  33. package/dist/cli/install.d.ts +8 -0
  34. package/dist/cli/install.js +47 -0
  35. package/dist/cli/join.d.ts +9 -0
  36. package/dist/cli/join.js +38 -0
  37. package/dist/cli/registerMcp.d.ts +28 -0
  38. package/dist/cli/registerMcp.js +138 -0
  39. package/dist/cli/status.d.ts +8 -0
  40. package/dist/cli/status.js +82 -0
  41. package/dist/cli/watch.d.ts +6 -0
  42. package/dist/cli/watch.js +68 -0
  43. package/dist/cli.d.ts +12 -0
  44. package/dist/cli.js +49 -0
  45. package/dist/coordinator/coordinator.test.d.ts +1 -0
  46. package/dist/coordinator/coordinator.test.js +171 -0
  47. package/dist/coordinator/index.d.ts +16 -0
  48. package/dist/coordinator/index.js +166 -0
  49. package/dist/coordinator/spawn.d.ts +22 -0
  50. package/dist/coordinator/spawn.js +66 -0
  51. package/dist/datetime/datetime.test.d.ts +1 -0
  52. package/dist/datetime/datetime.test.js +63 -0
  53. package/dist/datetime/formatDate.d.ts +6 -0
  54. package/dist/datetime/formatDate.js +11 -0
  55. package/dist/datetime/formatDatetime.d.ts +6 -0
  56. package/dist/datetime/formatDatetime.js +12 -0
  57. package/dist/datetime/formatTime.d.ts +6 -0
  58. package/dist/datetime/formatTime.js +11 -0
  59. package/dist/datetime/index.d.ts +4 -0
  60. package/dist/datetime/index.js +7 -0
  61. package/dist/datetime/isStale.d.ts +10 -0
  62. package/dist/datetime/isStale.js +18 -0
  63. package/dist/datetime/now.d.ts +6 -0
  64. package/dist/datetime/now.js +9 -0
  65. package/dist/datetime/parseDatetime.d.ts +7 -0
  66. package/dist/datetime/parseDatetime.js +28 -0
  67. package/dist/db/constants.d.ts +4 -0
  68. package/dist/db/constants.js +6 -0
  69. package/dist/db/db.test.d.ts +1 -0
  70. package/dist/db/db.test.js +141 -0
  71. package/dist/db/ensureProjectDirs.d.ts +4 -0
  72. package/dist/db/ensureProjectDirs.js +12 -0
  73. package/dist/db/getConnection.d.ts +19 -0
  74. package/dist/db/getConnection.js +51 -0
  75. package/dist/db/getCurrentProject.d.ts +8 -0
  76. package/dist/db/getCurrentProject.js +14 -0
  77. package/dist/db/getProjectPaths.d.ts +21 -0
  78. package/dist/db/getProjectPaths.js +26 -0
  79. package/dist/db/index.d.ts +10 -0
  80. package/dist/db/index.js +13 -0
  81. package/dist/db/initializeDb.d.ts +7 -0
  82. package/dist/db/initializeDb.js +23 -0
  83. package/dist/db/nextEventSeq.d.ts +5 -0
  84. package/dist/db/nextEventSeq.js +13 -0
  85. package/dist/db/nextSubtaskSeq.d.ts +5 -0
  86. package/dist/db/nextSubtaskSeq.js +12 -0
  87. package/dist/db/nextTaskSeq.d.ts +5 -0
  88. package/dist/db/nextTaskSeq.js +13 -0
  89. package/dist/db/resetDb.d.ts +10 -0
  90. package/dist/db/resetDb.js +36 -0
  91. package/dist/events/emit.d.ts +6 -0
  92. package/dist/events/emit.js +31 -0
  93. package/dist/events/events.test.d.ts +1 -0
  94. package/dist/events/events.test.js +145 -0
  95. package/dist/events/getEventsByAgent.d.ts +6 -0
  96. package/dist/events/getEventsByAgent.js +14 -0
  97. package/dist/events/getEventsByBranch.d.ts +6 -0
  98. package/dist/events/getEventsByBranch.js +12 -0
  99. package/dist/events/getEventsByPlan.d.ts +6 -0
  100. package/dist/events/getEventsByPlan.js +14 -0
  101. package/dist/events/getEventsByWorktree.d.ts +6 -0
  102. package/dist/events/getEventsByWorktree.js +12 -0
  103. package/dist/events/getEventsSince.d.ts +12 -0
  104. package/dist/events/getEventsSince.js +47 -0
  105. package/dist/events/getRecentEvents.d.ts +6 -0
  106. package/dist/events/getRecentEvents.js +12 -0
  107. package/dist/events/index.d.ts +8 -0
  108. package/dist/events/index.js +9 -0
  109. package/dist/events/types.d.ts +34 -0
  110. package/dist/events/types.js +1 -0
  111. package/dist/git/getBranch.d.ts +4 -0
  112. package/dist/git/getBranch.js +14 -0
  113. package/dist/git/getCurrentWorktree.d.ts +5 -0
  114. package/dist/git/getCurrentWorktree.js +15 -0
  115. package/dist/git/getGitInfo.d.ts +10 -0
  116. package/dist/git/getGitInfo.js +23 -0
  117. package/dist/git/getRepoName.d.ts +4 -0
  118. package/dist/git/getRepoName.js +32 -0
  119. package/dist/git/getRepoRoot.d.ts +4 -0
  120. package/dist/git/getRepoRoot.js +14 -0
  121. package/dist/git/getWorktrees.d.ts +10 -0
  122. package/dist/git/getWorktrees.js +39 -0
  123. package/dist/git/index.d.ts +9 -0
  124. package/dist/git/index.js +7 -0
  125. package/dist/git/isGitRepo.d.ts +4 -0
  126. package/dist/git/isGitRepo.js +13 -0
  127. package/dist/hooks/index.d.ts +1 -0
  128. package/dist/hooks/index.js +1 -0
  129. package/dist/hooks/sessionStart.d.ts +21 -0
  130. package/dist/hooks/sessionStart.js +93 -0
  131. package/dist/ids/generateHex.d.ts +4 -0
  132. package/dist/ids/generateHex.js +7 -0
  133. package/dist/ids/getParentTaskId.d.ts +7 -0
  134. package/dist/ids/getParentTaskId.js +15 -0
  135. package/dist/ids/getPlanHexFromTaskId.d.ts +6 -0
  136. package/dist/ids/getPlanHexFromTaskId.js +9 -0
  137. package/dist/ids/ids.test.d.ts +1 -0
  138. package/dist/ids/ids.test.js +215 -0
  139. package/dist/ids/index.d.ts +16 -0
  140. package/dist/ids/index.js +17 -0
  141. package/dist/ids/isSubtask.d.ts +7 -0
  142. package/dist/ids/isSubtask.js +11 -0
  143. package/dist/ids/isValidId.d.ts +9 -0
  144. package/dist/ids/isValidId.js +22 -0
  145. package/dist/ids/makeAgentId.d.ts +8 -0
  146. package/dist/ids/makeAgentId.js +15 -0
  147. package/dist/ids/makeEventId.d.ts +11 -0
  148. package/dist/ids/makeEventId.js +12 -0
  149. package/dist/ids/makePlanId.d.ts +11 -0
  150. package/dist/ids/makePlanId.js +15 -0
  151. package/dist/ids/makeSubtaskId.d.ts +8 -0
  152. package/dist/ids/makeSubtaskId.js +15 -0
  153. package/dist/ids/makeTaskId.d.ts +8 -0
  154. package/dist/ids/makeTaskId.js +14 -0
  155. package/dist/ids/makeWorktreeId.d.ts +5 -0
  156. package/dist/ids/makeWorktreeId.js +12 -0
  157. package/dist/ids/parseId.d.ts +11 -0
  158. package/dist/ids/parseId.js +26 -0
  159. package/dist/ids/sanitizeLabel.d.ts +7 -0
  160. package/dist/ids/sanitizeLabel.js +12 -0
  161. package/dist/ids/typedIds.d.ts +34 -0
  162. package/dist/ids/typedIds.js +22 -0
  163. package/dist/ids/types.d.ts +14 -0
  164. package/dist/ids/types.js +1 -0
  165. package/dist/init/claudeConfig.d.ts +39 -0
  166. package/dist/init/claudeConfig.js +161 -0
  167. package/dist/llm/extractTasks.d.ts +28 -0
  168. package/dist/llm/extractTasks.js +108 -0
  169. package/dist/llm/index.d.ts +2 -0
  170. package/dist/llm/index.js +2 -0
  171. package/dist/llm/reconcileTasks.d.ts +21 -0
  172. package/dist/llm/reconcileTasks.js +82 -0
  173. package/dist/mcp/server.d.ts +2 -0
  174. package/dist/mcp/server.js +100 -0
  175. package/dist/mcp/tools/emitEvent.d.ts +62 -0
  176. package/dist/mcp/tools/emitEvent.js +84 -0
  177. package/dist/mcp/tools/events.d.ts +55 -0
  178. package/dist/mcp/tools/events.js +56 -0
  179. package/dist/mcp/tools/index.d.ts +18 -0
  180. package/dist/mcp/tools/index.js +13 -0
  181. package/dist/mcp/tools/query.d.ts +54 -0
  182. package/dist/mcp/tools/query.js +70 -0
  183. package/dist/mcp/tools/register.d.ts +47 -0
  184. package/dist/mcp/tools/register.js +79 -0
  185. package/dist/mcp/tools/reset.d.ts +38 -0
  186. package/dist/mcp/tools/reset.js +56 -0
  187. package/dist/mcp/tools/setup.d.ts +42 -0
  188. package/dist/mcp/tools/setup.js +75 -0
  189. package/dist/mcp/tools/status.d.ts +44 -0
  190. package/dist/mcp/tools/status.js +74 -0
  191. package/dist/mcp/tools/tasks.d.ts +116 -0
  192. package/dist/mcp/tools/tasks.js +143 -0
  193. package/dist/mcp/tools/worktreeCleanup.d.ts +38 -0
  194. package/dist/mcp/tools/worktreeCleanup.js +67 -0
  195. package/dist/plans/createPlan.d.ts +6 -0
  196. package/dist/plans/createPlan.js +29 -0
  197. package/dist/plans/getActivePlans.d.ts +6 -0
  198. package/dist/plans/getActivePlans.js +11 -0
  199. package/dist/plans/getPlan.d.ts +6 -0
  200. package/dist/plans/getPlan.js +7 -0
  201. package/dist/plans/index.d.ts +5 -0
  202. package/dist/plans/index.js +5 -0
  203. package/dist/plans/plans.test.d.ts +1 -0
  204. package/dist/plans/plans.test.js +107 -0
  205. package/dist/plans/types.d.ts +32 -0
  206. package/dist/plans/types.js +1 -0
  207. package/dist/plans/updatePlanStatus.d.ts +6 -0
  208. package/dist/plans/updatePlanStatus.js +8 -0
  209. package/dist/tasks/assignTask.d.ts +7 -0
  210. package/dist/tasks/assignTask.js +20 -0
  211. package/dist/tasks/blockTask.d.ts +5 -0
  212. package/dist/tasks/blockTask.js +12 -0
  213. package/dist/tasks/claimTask.d.ts +7 -0
  214. package/dist/tasks/claimTask.js +21 -0
  215. package/dist/tasks/completeTask.d.ts +6 -0
  216. package/dist/tasks/completeTask.js +18 -0
  217. package/dist/tasks/createTask.d.ts +6 -0
  218. package/dist/tasks/createTask.js +36 -0
  219. package/dist/tasks/getPendingTasks.d.ts +6 -0
  220. package/dist/tasks/getPendingTasks.js +19 -0
  221. package/dist/tasks/getTask.d.ts +6 -0
  222. package/dist/tasks/getTask.js +7 -0
  223. package/dist/tasks/getTasksByPlan.d.ts +6 -0
  224. package/dist/tasks/getTasksByPlan.js +11 -0
  225. package/dist/tasks/index.d.ts +11 -0
  226. package/dist/tasks/index.js +12 -0
  227. package/dist/tasks/startTask.d.ts +5 -0
  228. package/dist/tasks/startTask.js +12 -0
  229. package/dist/tasks/tasks.test.d.ts +1 -0
  230. package/dist/tasks/tasks.test.js +209 -0
  231. package/dist/tasks/types.d.ts +36 -0
  232. package/dist/tasks/types.js +1 -0
  233. package/dist/tasks/unclaimTask.d.ts +5 -0
  234. package/dist/tasks/unclaimTask.js +12 -0
  235. package/dist/test/factories/agentFactory.d.ts +13 -0
  236. package/dist/test/factories/agentFactory.js +5 -0
  237. package/dist/test/factories/eventFactory.d.ts +20 -0
  238. package/dist/test/factories/eventFactory.js +16 -0
  239. package/dist/test/factories/factories.test.d.ts +1 -0
  240. package/dist/test/factories/factories.test.js +101 -0
  241. package/dist/test/factories/index.d.ts +4 -0
  242. package/dist/test/factories/index.js +4 -0
  243. package/dist/test/factories/planFactory.d.ts +15 -0
  244. package/dist/test/factories/planFactory.js +14 -0
  245. package/dist/test/factories/taskFactory.d.ts +22 -0
  246. package/dist/test/factories/taskFactory.js +44 -0
  247. package/dist/test/multiAgentDemo.d.ts +16 -0
  248. package/dist/test/multiAgentDemo.js +20 -0
  249. package/dist/test/multiAgentDemo.test.d.ts +1 -0
  250. package/dist/test/multiAgentDemo.test.js +14 -0
  251. package/dist/test/setup.d.ts +9 -0
  252. package/dist/test/setup.js +50 -0
  253. package/dist/utils/index.d.ts +5 -0
  254. package/dist/utils/index.js +5 -0
  255. package/dist/utils/math.d.ts +6 -0
  256. package/dist/utils/math.js +10 -0
  257. package/dist/utils/math.test.d.ts +1 -0
  258. package/dist/utils/math.test.js +26 -0
  259. package/dist/utils/string.d.ts +11 -0
  260. package/dist/utils/string.js +17 -0
  261. package/dist/utils/string.test.d.ts +1 -0
  262. package/dist/utils/string.test.js +30 -0
  263. package/dist/watcher/index.d.ts +1 -0
  264. package/dist/watcher/index.js +1 -0
  265. package/dist/watcher/planWatcher.d.ts +31 -0
  266. package/dist/watcher/planWatcher.js +171 -0
  267. package/dist/worktrees/getAllWorktrees.d.ts +6 -0
  268. package/dist/worktrees/getAllWorktrees.js +10 -0
  269. package/dist/worktrees/getWorktreeById.d.ts +6 -0
  270. package/dist/worktrees/getWorktreeById.js +7 -0
  271. package/dist/worktrees/getWorktreeByPath.d.ts +6 -0
  272. package/dist/worktrees/getWorktreeByPath.js +7 -0
  273. package/dist/worktrees/index.d.ts +9 -0
  274. package/dist/worktrees/index.js +13 -0
  275. package/dist/worktrees/registerWorktree.d.ts +6 -0
  276. package/dist/worktrees/registerWorktree.js +28 -0
  277. package/dist/worktrees/removeWorktree.d.ts +6 -0
  278. package/dist/worktrees/removeWorktree.js +9 -0
  279. package/dist/worktrees/syncWorktreesFromGit.d.ts +7 -0
  280. package/dist/worktrees/syncWorktreesFromGit.js +51 -0
  281. package/dist/worktrees/types.d.ts +29 -0
  282. package/dist/worktrees/types.js +1 -0
  283. package/dist/worktrees/updateWorktreeCommit.d.ts +5 -0
  284. package/dist/worktrees/updateWorktreeCommit.js +13 -0
  285. package/dist/worktrees/updateWorktreeSeen.d.ts +5 -0
  286. package/dist/worktrees/updateWorktreeSeen.js +13 -0
  287. package/package.json +58 -0
@@ -0,0 +1,93 @@
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
+ import { executeRegister } from '../mcp/tools/register';
15
+ import { executeStatus } from '../mcp/tools/status';
16
+ import { getGitInfo } from '../git/getGitInfo';
17
+ function readStdinSync() {
18
+ try {
19
+ // Bun supports reading stdin synchronously
20
+ const chunks = [];
21
+ const fd = 0; // stdin
22
+ const buf = Buffer.alloc(1024);
23
+ let bytesRead;
24
+ // Non-blocking check for stdin data
25
+ const fs = require('fs');
26
+ try {
27
+ while ((bytesRead = fs.readSync(fd, buf, 0, buf.length, null)) > 0) {
28
+ chunks.push(buf.subarray(0, bytesRead));
29
+ }
30
+ }
31
+ catch {
32
+ // No more data or stdin not ready
33
+ }
34
+ return Buffer.concat(chunks).toString('utf-8');
35
+ }
36
+ catch {
37
+ return '';
38
+ }
39
+ }
40
+ export function runSessionStartHook(input) {
41
+ const gitInfo = getGitInfo();
42
+ if (!gitInfo.isRepo || !gitInfo.repoName) {
43
+ return;
44
+ }
45
+ const sessionId = input?.session_id;
46
+ const label = process.env.CLAUDE_AGENT_LABEL;
47
+ 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 });
58
+ const otherAgents = status.activeAgents?.filter((a) => a.id !== result.agentId) || [];
59
+ // Output agent info for Claude's context
60
+ const lines = [
61
+ `hivemind: ${result.agentId} joined ${gitInfo.repoName}`,
62
+ ];
63
+ if (result.branch) {
64
+ lines[0] += ` (${result.branch})`;
65
+ }
66
+ // Include session_id so Claude can use it for MCP calls
67
+ if (sessionId) {
68
+ lines.push(` session: ${sessionId}`);
69
+ }
70
+ if (otherAgents.length > 0) {
71
+ lines.push(` active: ${otherAgents.map((a) => a.id).join(', ')}`);
72
+ }
73
+ console.log(lines.join('\n'));
74
+ }
75
+ catch (error) {
76
+ console.error(`hivemind error: ${error instanceof Error ? error.message : String(error)}`);
77
+ }
78
+ }
79
+ // CLI entry point
80
+ if (import.meta.main) {
81
+ // Read hook input from stdin (Claude Code passes JSON)
82
+ const stdin = readStdinSync();
83
+ let input;
84
+ if (stdin.trim()) {
85
+ try {
86
+ input = JSON.parse(stdin);
87
+ }
88
+ catch {
89
+ // Not valid JSON, ignore
90
+ }
91
+ }
92
+ runSessionStartHook(input);
93
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Generate a 6-character hex string for use in IDs
3
+ */
4
+ export declare function generateHex(): string;
@@ -0,0 +1,7 @@
1
+ import { randomBytes } from 'crypto';
2
+ /**
3
+ * Generate a 6-character hex string for use in IDs
4
+ */
5
+ export function generateHex() {
6
+ return randomBytes(3).toString('hex');
7
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Get parent task ID from a subtask ID
3
+ *
4
+ * Example: tsk_e9d2c1_001.1 → tsk_e9d2c1_001
5
+ * tsk_e9d2c1_001 → null (not a subtask)
6
+ */
7
+ export declare function getParentTaskId(subtaskId: string): string | null;
@@ -0,0 +1,15 @@
1
+ import { parseId } from './parseId';
2
+ /**
3
+ * Get parent task ID from a subtask ID
4
+ *
5
+ * Example: tsk_e9d2c1_001.1 → tsk_e9d2c1_001
6
+ * tsk_e9d2c1_001 → null (not a subtask)
7
+ */
8
+ export function getParentTaskId(subtaskId) {
9
+ const parsed = parseId(subtaskId);
10
+ if (!parsed.seq?.includes('.')) {
11
+ return null;
12
+ }
13
+ const parentSeq = parsed.seq.split('.')[0];
14
+ return `tsk_${parsed.hex}_${parentSeq}`;
15
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Extract plan hex from a task ID
3
+ *
4
+ * Example: tsk_e9d2c1_001 → 'e9d2c1'
5
+ */
6
+ export declare function getPlanHexFromTaskId(taskId: string): string;
@@ -0,0 +1,9 @@
1
+ import { parseId } from './parseId';
2
+ /**
3
+ * Extract plan hex from a task ID
4
+ *
5
+ * Example: tsk_e9d2c1_001 → 'e9d2c1'
6
+ */
7
+ export function getPlanHexFromTaskId(taskId) {
8
+ return parseId(taskId).hex;
9
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,215 @@
1
+ import { describe, it, expect } from 'bun:test';
2
+ import { generateHex } from './generateHex';
3
+ import { makeAgentId } from './makeAgentId';
4
+ import { makePlanId } from './makePlanId';
5
+ import { makeTaskId } from './makeTaskId';
6
+ import { makeEventId } from './makeEventId';
7
+ import { makeWorktreeId } from './makeWorktreeId';
8
+ import { makeSubtaskId } from './makeSubtaskId';
9
+ import { parseId } from './parseId';
10
+ import { isValidId } from './isValidId';
11
+ import { sanitizeLabel } from './sanitizeLabel';
12
+ import { getPlanHexFromTaskId } from './getPlanHexFromTaskId';
13
+ import { isSubtask } from './isSubtask';
14
+ import { getParentTaskId } from './getParentTaskId';
15
+ import { isAgentId, isPlanId, isTaskId, isEventId, isWorktreeId, taskId } from './typedIds';
16
+ describe('generateHex', () => {
17
+ it('generates 6 character hex strings', () => {
18
+ const hex = generateHex();
19
+ expect(hex).toHaveLength(6);
20
+ expect(hex).toMatch(/^[a-f0-9]{6}$/);
21
+ });
22
+ it('generates unique values', () => {
23
+ const hexes = new Set();
24
+ for (let i = 0; i < 100; i++) {
25
+ hexes.add(generateHex());
26
+ }
27
+ expect(hexes.size).toBe(100);
28
+ });
29
+ });
30
+ describe('makeAgentId', () => {
31
+ it('creates agent id without label', () => {
32
+ const id = makeAgentId();
33
+ expect(id).toMatch(/^agt_[a-f0-9]{6}$/);
34
+ expect(isAgentId(id)).toBe(true);
35
+ });
36
+ it('creates agent id with label', () => {
37
+ const id = makeAgentId('main');
38
+ expect(id).toMatch(/^agt_[a-f0-9]{6}_main$/);
39
+ });
40
+ it('sanitizes label (removes non-alphanumeric)', () => {
41
+ const id = makeAgentId('My Agent!');
42
+ expect(id).toMatch(/^agt_[a-f0-9]{6}_myagent$/);
43
+ });
44
+ });
45
+ describe('makePlanId', () => {
46
+ it('creates plan id and returns hex', () => {
47
+ const { id, hex } = makePlanId();
48
+ expect(id).toMatch(/^pln_[a-f0-9]{6}$/);
49
+ expect(hex).toMatch(/^[a-f0-9]{6}$/);
50
+ expect(isPlanId(id)).toBe(true);
51
+ });
52
+ it('creates plan id with label', () => {
53
+ const { id } = makePlanId('auth-feature');
54
+ expect(id).toMatch(/^pln_[a-f0-9]{6}_authfeature$/);
55
+ });
56
+ });
57
+ describe('makeTaskId', () => {
58
+ it('creates task id with plan hex and sequence (3-digit padded)', () => {
59
+ const id = makeTaskId('abc123', 1);
60
+ expect(id).toBe(taskId('tsk_abc123_001'));
61
+ expect(isTaskId(id)).toBe(true);
62
+ });
63
+ it('creates task id with label', () => {
64
+ const id = makeTaskId('abc123', 1, 'setup');
65
+ expect(id).toBe(taskId('tsk_abc123_001_setup'));
66
+ });
67
+ it('pads sequence correctly', () => {
68
+ expect(makeTaskId('abc123', 1)).toBe(taskId('tsk_abc123_001'));
69
+ expect(makeTaskId('abc123', 10)).toBe(taskId('tsk_abc123_010'));
70
+ expect(makeTaskId('abc123', 100)).toBe(taskId('tsk_abc123_100'));
71
+ });
72
+ });
73
+ describe('makeSubtaskId', () => {
74
+ it('creates subtask id from parent task', () => {
75
+ const parentId = taskId('tsk_abc123_001');
76
+ const id = makeSubtaskId(parentId, 1);
77
+ expect(id).toBe(taskId('tsk_abc123_001.1'));
78
+ });
79
+ it('creates subtask id with label', () => {
80
+ const parentId = taskId('tsk_abc123_001');
81
+ const id = makeSubtaskId(parentId, 2, 'validate');
82
+ expect(id).toBe(taskId('tsk_abc123_001.2_validate'));
83
+ });
84
+ });
85
+ describe('makeEventId', () => {
86
+ it('creates event id with hex and sequence (5-digit padded)', () => {
87
+ const { id, hex } = makeEventId(1);
88
+ expect(id).toMatch(/^evt_[a-f0-9]{6}_00001$/);
89
+ expect(hex).toMatch(/^[a-f0-9]{6}$/);
90
+ expect(isEventId(id)).toBe(true);
91
+ });
92
+ });
93
+ describe('makeWorktreeId', () => {
94
+ it('creates worktree id without label', () => {
95
+ const id = makeWorktreeId();
96
+ expect(id).toMatch(/^wkt_[a-f0-9]{6}$/);
97
+ expect(isWorktreeId(id)).toBe(true);
98
+ });
99
+ it('creates worktree id with label', () => {
100
+ const id = makeWorktreeId('feature-auth');
101
+ expect(id).toMatch(/^wkt_[a-f0-9]{6}_featureauth$/);
102
+ });
103
+ });
104
+ describe('parseId', () => {
105
+ it('parses agent id without label', () => {
106
+ const parsed = parseId('agt_abc123');
107
+ expect(parsed.type).toBe('agt');
108
+ expect(parsed.hex).toBe('abc123');
109
+ expect(parsed.label).toBeUndefined();
110
+ });
111
+ it('parses agent id with label', () => {
112
+ const parsed = parseId('agt_abc123_main');
113
+ expect(parsed.type).toBe('agt');
114
+ expect(parsed.hex).toBe('abc123');
115
+ expect(parsed.label).toBe('main');
116
+ });
117
+ it('parses task id', () => {
118
+ const parsed = parseId('tsk_abc123_001');
119
+ expect(parsed.type).toBe('tsk');
120
+ expect(parsed.hex).toBe('abc123');
121
+ expect(parsed.seq).toBe('001');
122
+ });
123
+ it('parses task id with label', () => {
124
+ const parsed = parseId('tsk_abc123_001_setup');
125
+ expect(parsed.type).toBe('tsk');
126
+ expect(parsed.hex).toBe('abc123');
127
+ expect(parsed.seq).toBe('001');
128
+ expect(parsed.label).toBe('setup');
129
+ });
130
+ it('parses subtask id (seq includes dot notation)', () => {
131
+ const parsed = parseId('tsk_abc123_001.2');
132
+ expect(parsed.type).toBe('tsk');
133
+ expect(parsed.hex).toBe('abc123');
134
+ expect(parsed.seq).toBe('001.2');
135
+ });
136
+ it('parses event id', () => {
137
+ const parsed = parseId('evt_abc123_00005');
138
+ expect(parsed.type).toBe('evt');
139
+ expect(parsed.hex).toBe('abc123');
140
+ expect(parsed.seq).toBe('00005');
141
+ });
142
+ });
143
+ describe('isValidId', () => {
144
+ it('validates correct ids', () => {
145
+ expect(isValidId('agt_abc123')).toBe(true);
146
+ expect(isValidId('agt_abc123_main')).toBe(true);
147
+ expect(isValidId('pln_abc123')).toBe(true);
148
+ expect(isValidId('tsk_abc123_001')).toBe(true);
149
+ expect(isValidId('evt_abc123_00001')).toBe(true);
150
+ expect(isValidId('wkt_abc123')).toBe(true);
151
+ });
152
+ it('rejects invalid ids', () => {
153
+ expect(isValidId('invalid')).toBe(false);
154
+ expect(isValidId('agt_')).toBe(false);
155
+ expect(isValidId('xxx_abc123')).toBe(false);
156
+ expect(isValidId('')).toBe(false);
157
+ expect(isValidId('agt_ABC123')).toBe(false); // uppercase hex invalid
158
+ });
159
+ });
160
+ describe('sanitizeLabel', () => {
161
+ it('lowercases', () => {
162
+ expect(sanitizeLabel('MyLabel')).toBe('mylabel');
163
+ });
164
+ it('removes non-alphanumeric characters', () => {
165
+ expect(sanitizeLabel('test-label')).toBe('testlabel');
166
+ expect(sanitizeLabel('Test Label!')).toBe('testlabel');
167
+ expect(sanitizeLabel('test_123')).toBe('test123');
168
+ });
169
+ it('truncates long labels to 20 chars', () => {
170
+ const long = 'a'.repeat(30);
171
+ expect(sanitizeLabel(long)).toHaveLength(20);
172
+ });
173
+ it('handles empty string', () => {
174
+ expect(sanitizeLabel('')).toBe('');
175
+ });
176
+ });
177
+ describe('getPlanHexFromTaskId', () => {
178
+ it('extracts plan hex from task id', () => {
179
+ expect(getPlanHexFromTaskId('tsk_abc123_001')).toBe('abc123');
180
+ expect(getPlanHexFromTaskId('tsk_abc123_001_label')).toBe('abc123');
181
+ });
182
+ });
183
+ describe('isSubtask', () => {
184
+ it('identifies subtasks (contains dot in seq)', () => {
185
+ expect(isSubtask('tsk_abc123_001.1')).toBe(true);
186
+ expect(isSubtask('tsk_abc123_001.2_label')).toBe(true);
187
+ });
188
+ it('identifies non-subtasks', () => {
189
+ expect(isSubtask('tsk_abc123_001')).toBe(false);
190
+ expect(isSubtask('tsk_abc123_001_label')).toBe(false);
191
+ });
192
+ });
193
+ describe('getParentTaskId', () => {
194
+ it('gets parent task id from subtask', () => {
195
+ expect(getParentTaskId('tsk_abc123_001.2')).toBe('tsk_abc123_001');
196
+ expect(getParentTaskId('tsk_abc123_001.2_label')).toBe('tsk_abc123_001');
197
+ });
198
+ it('returns null for non-subtask', () => {
199
+ expect(getParentTaskId('tsk_abc123_001')).toBeNull();
200
+ });
201
+ });
202
+ describe('typed id guards', () => {
203
+ it('correctly identifies id types', () => {
204
+ expect(isAgentId('agt_abc123')).toBe(true);
205
+ expect(isAgentId('pln_abc123')).toBe(false);
206
+ expect(isPlanId('pln_abc123')).toBe(true);
207
+ expect(isPlanId('agt_abc123')).toBe(false);
208
+ expect(isTaskId('tsk_abc123_001')).toBe(true);
209
+ expect(isTaskId('pln_abc123')).toBe(false);
210
+ expect(isEventId('evt_abc123_00001')).toBe(true);
211
+ expect(isEventId('tsk_abc123_001')).toBe(false);
212
+ expect(isWorktreeId('wkt_abc123')).toBe(true);
213
+ expect(isWorktreeId('agt_abc123')).toBe(false);
214
+ });
215
+ });
@@ -0,0 +1,16 @@
1
+ export type { IdType, ParsedId } from './types';
2
+ export type { AgentId, PlanId, TaskId, EventId, WorktreeId, HivemindIdType, } from './typedIds';
3
+ export { agentId, planId, taskId, eventId, worktreeId, isAgentId, isPlanId, isTaskId, isEventId, isWorktreeId, } from './typedIds';
4
+ export { generateHex } from './generateHex';
5
+ export { makeAgentId } from './makeAgentId';
6
+ export { makePlanId } from './makePlanId';
7
+ export { makeTaskId } from './makeTaskId';
8
+ export { makeSubtaskId } from './makeSubtaskId';
9
+ export { makeEventId } from './makeEventId';
10
+ export { makeWorktreeId } from './makeWorktreeId';
11
+ export { parseId } from './parseId';
12
+ export { sanitizeLabel } from './sanitizeLabel';
13
+ export { getPlanHexFromTaskId } from './getPlanHexFromTaskId';
14
+ export { getParentTaskId } from './getParentTaskId';
15
+ export { isSubtask } from './isSubtask';
16
+ export { isValidId } from './isValidId';
@@ -0,0 +1,17 @@
1
+ export { agentId, planId, taskId, eventId, worktreeId, isAgentId, isPlanId, isTaskId, isEventId, isWorktreeId, } from './typedIds';
2
+ // Generators
3
+ export { generateHex } from './generateHex';
4
+ export { makeAgentId } from './makeAgentId';
5
+ export { makePlanId } from './makePlanId';
6
+ export { makeTaskId } from './makeTaskId';
7
+ export { makeSubtaskId } from './makeSubtaskId';
8
+ export { makeEventId } from './makeEventId';
9
+ export { makeWorktreeId } from './makeWorktreeId';
10
+ // Parsing
11
+ export { parseId } from './parseId';
12
+ export { sanitizeLabel } from './sanitizeLabel';
13
+ // Helpers
14
+ export { getPlanHexFromTaskId } from './getPlanHexFromTaskId';
15
+ export { getParentTaskId } from './getParentTaskId';
16
+ export { isSubtask } from './isSubtask';
17
+ export { isValidId } from './isValidId';
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Check if a task ID is a subtask (contains a dot in seq)
3
+ *
4
+ * Example: tsk_e9d2c1_001.1 → true
5
+ * tsk_e9d2c1_001 → false
6
+ */
7
+ export declare function isSubtask(taskId: string): boolean;
@@ -0,0 +1,11 @@
1
+ import { parseId } from './parseId';
2
+ /**
3
+ * Check if a task ID is a subtask (contains a dot in seq)
4
+ *
5
+ * Example: tsk_e9d2c1_001.1 → true
6
+ * tsk_e9d2c1_001 → false
7
+ */
8
+ export function isSubtask(taskId) {
9
+ const parsed = parseId(taskId);
10
+ return parsed.seq?.includes('.') ?? false;
11
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Validate an ID format
3
+ *
4
+ * Checks:
5
+ * - Has at least 2 parts (type_hex)
6
+ * - Type is valid (agt, pln, tsk, evt, wkt)
7
+ * - Hex is 6 lowercase hex chars
8
+ */
9
+ export declare function isValidId(id: string): boolean;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Validate an ID format
3
+ *
4
+ * Checks:
5
+ * - Has at least 2 parts (type_hex)
6
+ * - Type is valid (agt, pln, tsk, evt, wkt)
7
+ * - Hex is 6 lowercase hex chars
8
+ */
9
+ export function isValidId(id) {
10
+ const validTypes = ['agt', 'pln', 'tsk', 'evt', 'wkt'];
11
+ const parts = id.split('_');
12
+ if (parts.length < 2) {
13
+ return false;
14
+ }
15
+ if (!validTypes.includes(parts[0])) {
16
+ return false;
17
+ }
18
+ if (!/^[a-f0-9]{6}$/.test(parts[1])) {
19
+ return false;
20
+ }
21
+ return true;
22
+ }
@@ -0,0 +1,8 @@
1
+ import type { AgentId } from './typedIds';
2
+ /**
3
+ * Create an agent ID
4
+ *
5
+ * Format: agt_{6hex}[_{label}]
6
+ * Examples: agt_7a3f2b, agt_7a3f2b_alice
7
+ */
8
+ export declare function makeAgentId(label?: string): AgentId;
@@ -0,0 +1,15 @@
1
+ import { generateHex } from './generateHex';
2
+ import { sanitizeLabel } from './sanitizeLabel';
3
+ /**
4
+ * Create an agent ID
5
+ *
6
+ * Format: agt_{6hex}[_{label}]
7
+ * Examples: agt_7a3f2b, agt_7a3f2b_alice
8
+ */
9
+ export function makeAgentId(label) {
10
+ const hex = generateHex();
11
+ if (label) {
12
+ return `agt_${hex}_${sanitizeLabel(label)}`;
13
+ }
14
+ return `agt_${hex}`;
15
+ }
@@ -0,0 +1,11 @@
1
+ import type { EventId } from './typedIds';
2
+ /**
3
+ * Create an event ID
4
+ *
5
+ * Format: evt_{6hex}_{seq}
6
+ * Example: evt_f1a2b3_00001
7
+ */
8
+ export declare function makeEventId(seq: number): {
9
+ id: EventId;
10
+ hex: string;
11
+ };
@@ -0,0 +1,12 @@
1
+ import { generateHex } from './generateHex';
2
+ /**
3
+ * Create an event ID
4
+ *
5
+ * Format: evt_{6hex}_{seq}
6
+ * Example: evt_f1a2b3_00001
7
+ */
8
+ export function makeEventId(seq) {
9
+ const hex = generateHex();
10
+ const seqStr = String(seq).padStart(5, '0');
11
+ return { id: `evt_${hex}_${seqStr}`, hex };
12
+ }
@@ -0,0 +1,11 @@
1
+ import type { PlanId } from './typedIds';
2
+ /**
3
+ * Create a plan ID
4
+ *
5
+ * Format: pln_{6hex}[_{label}]
6
+ * Examples: pln_e9d2c1, pln_e9d2c1_auth
7
+ */
8
+ export declare function makePlanId(label?: string): {
9
+ id: PlanId;
10
+ hex: string;
11
+ };
@@ -0,0 +1,15 @@
1
+ import { generateHex } from './generateHex';
2
+ import { sanitizeLabel } from './sanitizeLabel';
3
+ /**
4
+ * Create a plan ID
5
+ *
6
+ * Format: pln_{6hex}[_{label}]
7
+ * Examples: pln_e9d2c1, pln_e9d2c1_auth
8
+ */
9
+ export function makePlanId(label) {
10
+ const hex = generateHex();
11
+ if (label) {
12
+ return { id: `pln_${hex}_${sanitizeLabel(label)}`, hex };
13
+ }
14
+ return { id: `pln_${hex}`, hex };
15
+ }
@@ -0,0 +1,8 @@
1
+ import type { TaskId } from './typedIds';
2
+ /**
3
+ * Create a subtask ID from a parent task ID
4
+ *
5
+ * Format: tsk_{planHex}_{parentSeq}.{subSeq}[_{label}]
6
+ * Example: tsk_e9d2c1_001 + subSeq 1 → tsk_e9d2c1_001.1
7
+ */
8
+ export declare function makeSubtaskId(parentTaskId: TaskId, subSeq: number, label?: string): TaskId;
@@ -0,0 +1,15 @@
1
+ import { sanitizeLabel } from './sanitizeLabel';
2
+ /**
3
+ * Create a subtask ID from a parent task ID
4
+ *
5
+ * Format: tsk_{planHex}_{parentSeq}.{subSeq}[_{label}]
6
+ * Example: tsk_e9d2c1_001 + subSeq 1 → tsk_e9d2c1_001.1
7
+ */
8
+ export function makeSubtaskId(parentTaskId, subSeq, label) {
9
+ const parts = parentTaskId.split('_');
10
+ const base = parts.slice(0, 3).join('_'); // tsk_e9d2c1_001
11
+ if (label) {
12
+ return `${base}.${subSeq}_${sanitizeLabel(label)}`;
13
+ }
14
+ return `${base}.${subSeq}`;
15
+ }
@@ -0,0 +1,8 @@
1
+ import type { TaskId } from './typedIds';
2
+ /**
3
+ * Create a task ID (inherits plan's hex)
4
+ *
5
+ * Format: tsk_{planHex}_{seq}[_{label}]
6
+ * Examples: tsk_e9d2c1_001, tsk_e9d2c1_002_jwt
7
+ */
8
+ export declare function makeTaskId(planHex: string, seq: number, label?: string): TaskId;
@@ -0,0 +1,14 @@
1
+ import { sanitizeLabel } from './sanitizeLabel';
2
+ /**
3
+ * Create a task ID (inherits plan's hex)
4
+ *
5
+ * Format: tsk_{planHex}_{seq}[_{label}]
6
+ * Examples: tsk_e9d2c1_001, tsk_e9d2c1_002_jwt
7
+ */
8
+ export function makeTaskId(planHex, seq, label) {
9
+ const seqStr = String(seq).padStart(3, '0');
10
+ if (label) {
11
+ return `tsk_${planHex}_${seqStr}_${sanitizeLabel(label)}`;
12
+ }
13
+ return `tsk_${planHex}_${seqStr}`;
14
+ }
@@ -0,0 +1,5 @@
1
+ import type { WorktreeId } from './typedIds';
2
+ /**
3
+ * Create a worktree ID: wkt_{hex}[_{label}]
4
+ */
5
+ export declare function makeWorktreeId(label?: string): WorktreeId;
@@ -0,0 +1,12 @@
1
+ import { generateHex } from './generateHex';
2
+ import { sanitizeLabel } from './sanitizeLabel';
3
+ /**
4
+ * Create a worktree ID: wkt_{hex}[_{label}]
5
+ */
6
+ export function makeWorktreeId(label) {
7
+ const hex = generateHex();
8
+ if (label) {
9
+ return `wkt_${hex}_${sanitizeLabel(label)}`;
10
+ }
11
+ return `wkt_${hex}`;
12
+ }
@@ -0,0 +1,11 @@
1
+ import type { ParsedId } from './types';
2
+ /**
3
+ * Parse any hivemind ID into its components
4
+ *
5
+ * Examples:
6
+ * parseId('agt_7a3f2b') → { type: 'agt', hex: '7a3f2b' }
7
+ * parseId('agt_7a3f2b_alice') → { type: 'agt', hex: '7a3f2b', label: 'alice' }
8
+ * parseId('tsk_e9d2c1_001') → { type: 'tsk', hex: 'e9d2c1', seq: '001' }
9
+ * parseId('tsk_e9d2c1_001.1') → { type: 'tsk', hex: 'e9d2c1', seq: '001.1' }
10
+ */
11
+ export declare function parseId(id: string): ParsedId;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Parse any hivemind ID into its components
3
+ *
4
+ * Examples:
5
+ * parseId('agt_7a3f2b') → { type: 'agt', hex: '7a3f2b' }
6
+ * parseId('agt_7a3f2b_alice') → { type: 'agt', hex: '7a3f2b', label: 'alice' }
7
+ * parseId('tsk_e9d2c1_001') → { type: 'tsk', hex: 'e9d2c1', seq: '001' }
8
+ * parseId('tsk_e9d2c1_001.1') → { type: 'tsk', hex: 'e9d2c1', seq: '001.1' }
9
+ */
10
+ export function parseId(id) {
11
+ const parts = id.split('_');
12
+ const type = parts[0];
13
+ const hex = parts[1];
14
+ if (type === 'tsk') {
15
+ const seq = parts[2];
16
+ const label = parts[3];
17
+ return { type, hex, seq, label, raw: id };
18
+ }
19
+ if (type === 'evt') {
20
+ const seq = parts[2];
21
+ return { type, hex, seq, raw: id };
22
+ }
23
+ // agt or pln: type_hex or type_hex_label
24
+ const label = parts[2];
25
+ return { type, hex, label, raw: id };
26
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Sanitize a label for use in IDs
3
+ * - lowercase
4
+ * - remove special chars (keep alphanumeric only)
5
+ * - max 20 chars
6
+ */
7
+ export declare function sanitizeLabel(label: string): string;