@inixiative/hivemind 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +115 -0
- package/dist/agents/agents.test.d.ts +1 -0
- package/dist/agents/agents.test.js +167 -0
- package/dist/agents/getActiveAgents.d.ts +6 -0
- package/dist/agents/getActiveAgents.js +11 -0
- package/dist/agents/getAgent.d.ts +6 -0
- package/dist/agents/getAgent.js +7 -0
- package/dist/agents/getAgentBySessionId.d.ts +10 -0
- package/dist/agents/getAgentBySessionId.js +17 -0
- package/dist/agents/index.d.ts +10 -0
- package/dist/agents/index.js +12 -0
- package/dist/agents/markAgentDead.d.ts +6 -0
- package/dist/agents/markAgentDead.js +26 -0
- package/dist/agents/markAgentIdle.d.ts +5 -0
- package/dist/agents/markAgentIdle.js +12 -0
- package/dist/agents/registerAgent.d.ts +6 -0
- package/dist/agents/registerAgent.js +29 -0
- package/dist/agents/types.d.ts +30 -0
- package/dist/agents/types.js +1 -0
- package/dist/agents/unregisterAgent.d.ts +5 -0
- package/dist/agents/unregisterAgent.js +8 -0
- package/dist/agents/updateAgentContext.d.ts +5 -0
- package/dist/agents/updateAgentContext.js +12 -0
- package/dist/agents/updateAgentTask.d.ts +5 -0
- package/dist/agents/updateAgentTask.js +12 -0
- package/dist/agents/updateAgentWorktree.d.ts +5 -0
- package/dist/agents/updateAgentWorktree.js +12 -0
- package/dist/cli/index.d.ts +8 -0
- package/dist/cli/index.js +8 -0
- package/dist/cli/init.d.ts +14 -0
- package/dist/cli/init.js +71 -0
- package/dist/cli/install.d.ts +8 -0
- package/dist/cli/install.js +47 -0
- package/dist/cli/join.d.ts +9 -0
- package/dist/cli/join.js +38 -0
- package/dist/cli/registerMcp.d.ts +28 -0
- package/dist/cli/registerMcp.js +138 -0
- package/dist/cli/status.d.ts +8 -0
- package/dist/cli/status.js +82 -0
- package/dist/cli/watch.d.ts +6 -0
- package/dist/cli/watch.js +68 -0
- package/dist/cli.d.ts +12 -0
- package/dist/cli.js +49 -0
- package/dist/coordinator/coordinator.test.d.ts +1 -0
- package/dist/coordinator/coordinator.test.js +171 -0
- package/dist/coordinator/index.d.ts +16 -0
- package/dist/coordinator/index.js +166 -0
- package/dist/coordinator/spawn.d.ts +22 -0
- package/dist/coordinator/spawn.js +66 -0
- package/dist/datetime/datetime.test.d.ts +1 -0
- package/dist/datetime/datetime.test.js +63 -0
- package/dist/datetime/formatDate.d.ts +6 -0
- package/dist/datetime/formatDate.js +11 -0
- package/dist/datetime/formatDatetime.d.ts +6 -0
- package/dist/datetime/formatDatetime.js +12 -0
- package/dist/datetime/formatTime.d.ts +6 -0
- package/dist/datetime/formatTime.js +11 -0
- package/dist/datetime/index.d.ts +4 -0
- package/dist/datetime/index.js +7 -0
- package/dist/datetime/isStale.d.ts +10 -0
- package/dist/datetime/isStale.js +18 -0
- package/dist/datetime/now.d.ts +6 -0
- package/dist/datetime/now.js +9 -0
- package/dist/datetime/parseDatetime.d.ts +7 -0
- package/dist/datetime/parseDatetime.js +28 -0
- package/dist/db/constants.d.ts +4 -0
- package/dist/db/constants.js +6 -0
- package/dist/db/db.test.d.ts +1 -0
- package/dist/db/db.test.js +141 -0
- package/dist/db/ensureProjectDirs.d.ts +4 -0
- package/dist/db/ensureProjectDirs.js +12 -0
- package/dist/db/getConnection.d.ts +19 -0
- package/dist/db/getConnection.js +51 -0
- package/dist/db/getCurrentProject.d.ts +8 -0
- package/dist/db/getCurrentProject.js +14 -0
- package/dist/db/getProjectPaths.d.ts +21 -0
- package/dist/db/getProjectPaths.js +26 -0
- package/dist/db/index.d.ts +10 -0
- package/dist/db/index.js +13 -0
- package/dist/db/initializeDb.d.ts +7 -0
- package/dist/db/initializeDb.js +23 -0
- package/dist/db/nextEventSeq.d.ts +5 -0
- package/dist/db/nextEventSeq.js +13 -0
- package/dist/db/nextSubtaskSeq.d.ts +5 -0
- package/dist/db/nextSubtaskSeq.js +12 -0
- package/dist/db/nextTaskSeq.d.ts +5 -0
- package/dist/db/nextTaskSeq.js +13 -0
- package/dist/db/resetDb.d.ts +10 -0
- package/dist/db/resetDb.js +36 -0
- package/dist/events/emit.d.ts +6 -0
- package/dist/events/emit.js +31 -0
- package/dist/events/events.test.d.ts +1 -0
- package/dist/events/events.test.js +145 -0
- package/dist/events/getEventsByAgent.d.ts +6 -0
- package/dist/events/getEventsByAgent.js +14 -0
- package/dist/events/getEventsByBranch.d.ts +6 -0
- package/dist/events/getEventsByBranch.js +12 -0
- package/dist/events/getEventsByPlan.d.ts +6 -0
- package/dist/events/getEventsByPlan.js +14 -0
- package/dist/events/getEventsByWorktree.d.ts +6 -0
- package/dist/events/getEventsByWorktree.js +12 -0
- package/dist/events/getEventsSince.d.ts +12 -0
- package/dist/events/getEventsSince.js +47 -0
- package/dist/events/getRecentEvents.d.ts +6 -0
- package/dist/events/getRecentEvents.js +12 -0
- package/dist/events/index.d.ts +8 -0
- package/dist/events/index.js +9 -0
- package/dist/events/types.d.ts +34 -0
- package/dist/events/types.js +1 -0
- package/dist/git/getBranch.d.ts +4 -0
- package/dist/git/getBranch.js +14 -0
- package/dist/git/getCurrentWorktree.d.ts +5 -0
- package/dist/git/getCurrentWorktree.js +15 -0
- package/dist/git/getGitInfo.d.ts +10 -0
- package/dist/git/getGitInfo.js +23 -0
- package/dist/git/getRepoName.d.ts +4 -0
- package/dist/git/getRepoName.js +32 -0
- package/dist/git/getRepoRoot.d.ts +4 -0
- package/dist/git/getRepoRoot.js +14 -0
- package/dist/git/getWorktrees.d.ts +10 -0
- package/dist/git/getWorktrees.js +39 -0
- package/dist/git/index.d.ts +9 -0
- package/dist/git/index.js +7 -0
- package/dist/git/isGitRepo.d.ts +4 -0
- package/dist/git/isGitRepo.js +13 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/sessionStart.d.ts +21 -0
- package/dist/hooks/sessionStart.js +93 -0
- package/dist/ids/generateHex.d.ts +4 -0
- package/dist/ids/generateHex.js +7 -0
- package/dist/ids/getParentTaskId.d.ts +7 -0
- package/dist/ids/getParentTaskId.js +15 -0
- package/dist/ids/getPlanHexFromTaskId.d.ts +6 -0
- package/dist/ids/getPlanHexFromTaskId.js +9 -0
- package/dist/ids/ids.test.d.ts +1 -0
- package/dist/ids/ids.test.js +215 -0
- package/dist/ids/index.d.ts +16 -0
- package/dist/ids/index.js +17 -0
- package/dist/ids/isSubtask.d.ts +7 -0
- package/dist/ids/isSubtask.js +11 -0
- package/dist/ids/isValidId.d.ts +9 -0
- package/dist/ids/isValidId.js +22 -0
- package/dist/ids/makeAgentId.d.ts +8 -0
- package/dist/ids/makeAgentId.js +15 -0
- package/dist/ids/makeEventId.d.ts +11 -0
- package/dist/ids/makeEventId.js +12 -0
- package/dist/ids/makePlanId.d.ts +11 -0
- package/dist/ids/makePlanId.js +15 -0
- package/dist/ids/makeSubtaskId.d.ts +8 -0
- package/dist/ids/makeSubtaskId.js +15 -0
- package/dist/ids/makeTaskId.d.ts +8 -0
- package/dist/ids/makeTaskId.js +14 -0
- package/dist/ids/makeWorktreeId.d.ts +5 -0
- package/dist/ids/makeWorktreeId.js +12 -0
- package/dist/ids/parseId.d.ts +11 -0
- package/dist/ids/parseId.js +26 -0
- package/dist/ids/sanitizeLabel.d.ts +7 -0
- package/dist/ids/sanitizeLabel.js +12 -0
- package/dist/ids/typedIds.d.ts +34 -0
- package/dist/ids/typedIds.js +22 -0
- package/dist/ids/types.d.ts +14 -0
- package/dist/ids/types.js +1 -0
- package/dist/init/claudeConfig.d.ts +39 -0
- package/dist/init/claudeConfig.js +161 -0
- package/dist/llm/extractTasks.d.ts +28 -0
- package/dist/llm/extractTasks.js +108 -0
- package/dist/llm/index.d.ts +2 -0
- package/dist/llm/index.js +2 -0
- package/dist/llm/reconcileTasks.d.ts +21 -0
- package/dist/llm/reconcileTasks.js +82 -0
- package/dist/mcp/server.d.ts +2 -0
- package/dist/mcp/server.js +100 -0
- package/dist/mcp/tools/emitEvent.d.ts +62 -0
- package/dist/mcp/tools/emitEvent.js +84 -0
- package/dist/mcp/tools/events.d.ts +55 -0
- package/dist/mcp/tools/events.js +56 -0
- package/dist/mcp/tools/index.d.ts +18 -0
- package/dist/mcp/tools/index.js +13 -0
- package/dist/mcp/tools/query.d.ts +54 -0
- package/dist/mcp/tools/query.js +70 -0
- package/dist/mcp/tools/register.d.ts +47 -0
- package/dist/mcp/tools/register.js +79 -0
- package/dist/mcp/tools/reset.d.ts +38 -0
- package/dist/mcp/tools/reset.js +56 -0
- package/dist/mcp/tools/setup.d.ts +42 -0
- package/dist/mcp/tools/setup.js +75 -0
- package/dist/mcp/tools/status.d.ts +44 -0
- package/dist/mcp/tools/status.js +74 -0
- package/dist/mcp/tools/tasks.d.ts +116 -0
- package/dist/mcp/tools/tasks.js +143 -0
- package/dist/mcp/tools/worktreeCleanup.d.ts +38 -0
- package/dist/mcp/tools/worktreeCleanup.js +67 -0
- package/dist/plans/createPlan.d.ts +6 -0
- package/dist/plans/createPlan.js +29 -0
- package/dist/plans/getActivePlans.d.ts +6 -0
- package/dist/plans/getActivePlans.js +11 -0
- package/dist/plans/getPlan.d.ts +6 -0
- package/dist/plans/getPlan.js +7 -0
- package/dist/plans/index.d.ts +5 -0
- package/dist/plans/index.js +5 -0
- package/dist/plans/plans.test.d.ts +1 -0
- package/dist/plans/plans.test.js +107 -0
- package/dist/plans/types.d.ts +32 -0
- package/dist/plans/types.js +1 -0
- package/dist/plans/updatePlanStatus.d.ts +6 -0
- package/dist/plans/updatePlanStatus.js +8 -0
- package/dist/tasks/assignTask.d.ts +7 -0
- package/dist/tasks/assignTask.js +20 -0
- package/dist/tasks/blockTask.d.ts +5 -0
- package/dist/tasks/blockTask.js +12 -0
- package/dist/tasks/claimTask.d.ts +7 -0
- package/dist/tasks/claimTask.js +21 -0
- package/dist/tasks/completeTask.d.ts +6 -0
- package/dist/tasks/completeTask.js +18 -0
- package/dist/tasks/createTask.d.ts +6 -0
- package/dist/tasks/createTask.js +36 -0
- package/dist/tasks/getPendingTasks.d.ts +6 -0
- package/dist/tasks/getPendingTasks.js +19 -0
- package/dist/tasks/getTask.d.ts +6 -0
- package/dist/tasks/getTask.js +7 -0
- package/dist/tasks/getTasksByPlan.d.ts +6 -0
- package/dist/tasks/getTasksByPlan.js +11 -0
- package/dist/tasks/index.d.ts +11 -0
- package/dist/tasks/index.js +12 -0
- package/dist/tasks/startTask.d.ts +5 -0
- package/dist/tasks/startTask.js +12 -0
- package/dist/tasks/tasks.test.d.ts +1 -0
- package/dist/tasks/tasks.test.js +209 -0
- package/dist/tasks/types.d.ts +36 -0
- package/dist/tasks/types.js +1 -0
- package/dist/tasks/unclaimTask.d.ts +5 -0
- package/dist/tasks/unclaimTask.js +12 -0
- package/dist/test/factories/agentFactory.d.ts +13 -0
- package/dist/test/factories/agentFactory.js +5 -0
- package/dist/test/factories/eventFactory.d.ts +20 -0
- package/dist/test/factories/eventFactory.js +16 -0
- package/dist/test/factories/factories.test.d.ts +1 -0
- package/dist/test/factories/factories.test.js +101 -0
- package/dist/test/factories/index.d.ts +4 -0
- package/dist/test/factories/index.js +4 -0
- package/dist/test/factories/planFactory.d.ts +15 -0
- package/dist/test/factories/planFactory.js +14 -0
- package/dist/test/factories/taskFactory.d.ts +22 -0
- package/dist/test/factories/taskFactory.js +44 -0
- package/dist/test/multiAgentDemo.d.ts +16 -0
- package/dist/test/multiAgentDemo.js +20 -0
- package/dist/test/multiAgentDemo.test.d.ts +1 -0
- package/dist/test/multiAgentDemo.test.js +14 -0
- package/dist/test/setup.d.ts +9 -0
- package/dist/test/setup.js +50 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.js +5 -0
- package/dist/utils/math.d.ts +6 -0
- package/dist/utils/math.js +10 -0
- package/dist/utils/math.test.d.ts +1 -0
- package/dist/utils/math.test.js +26 -0
- package/dist/utils/string.d.ts +11 -0
- package/dist/utils/string.js +17 -0
- package/dist/utils/string.test.d.ts +1 -0
- package/dist/utils/string.test.js +30 -0
- package/dist/watcher/index.d.ts +1 -0
- package/dist/watcher/index.js +1 -0
- package/dist/watcher/planWatcher.d.ts +31 -0
- package/dist/watcher/planWatcher.js +171 -0
- package/dist/worktrees/getAllWorktrees.d.ts +6 -0
- package/dist/worktrees/getAllWorktrees.js +10 -0
- package/dist/worktrees/getWorktreeById.d.ts +6 -0
- package/dist/worktrees/getWorktreeById.js +7 -0
- package/dist/worktrees/getWorktreeByPath.d.ts +6 -0
- package/dist/worktrees/getWorktreeByPath.js +7 -0
- package/dist/worktrees/index.d.ts +9 -0
- package/dist/worktrees/index.js +13 -0
- package/dist/worktrees/registerWorktree.d.ts +6 -0
- package/dist/worktrees/registerWorktree.js +28 -0
- package/dist/worktrees/removeWorktree.d.ts +6 -0
- package/dist/worktrees/removeWorktree.js +9 -0
- package/dist/worktrees/syncWorktreesFromGit.d.ts +7 -0
- package/dist/worktrees/syncWorktreesFromGit.js +51 -0
- package/dist/worktrees/types.d.ts +29 -0
- package/dist/worktrees/types.js +1 -0
- package/dist/worktrees/updateWorktreeCommit.d.ts +5 -0
- package/dist/worktrees/updateWorktreeCommit.js +13 -0
- package/dist/worktrees/updateWorktreeSeen.d.ts +5 -0
- package/dist/worktrees/updateWorktreeSeen.js +13 -0
- package/package.json +58 -0
|
@@ -0,0 +1,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,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 @@
|
|
|
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,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,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,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,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,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,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
|
+
}
|