@chuckssmith/agentloom 0.2.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/README.md ADDED
@@ -0,0 +1,100 @@
1
+ # agentloom
2
+
3
+ A workflow layer for Claude Code. Better routing, reusable roles, and multi-agent crew coordination — built natively on what Claude Code already provides.
4
+
5
+ ```bash
6
+ npm install -g agentloom
7
+ loom setup
8
+ ```
9
+
10
+ ---
11
+
12
+ ## What this is
13
+
14
+ Claude Code is the execution engine. agentloom adds:
15
+ - **`$grind`** — persistence loop that keeps working until a task is verified complete
16
+ - **`$crew`** — parallel workers that decompose and execute simultaneously
17
+ - **`$architect`** — deep analysis mode before major decisions
18
+ - **`loom crew`** — CLI to spawn a crew of workers from your terminal
19
+
20
+ It does not replace Claude Code. It wraps it.
21
+
22
+ ---
23
+
24
+ ## Quick start
25
+
26
+ ```bash
27
+ npm install -g agentloom
28
+ loom setup # installs skills, validates deps
29
+
30
+ # From your terminal:
31
+ loom crew "audit every API endpoint for security issues"
32
+ loom crew 2:explore+1:code-reviewer "review the payment flow"
33
+
34
+ # Or from inside a Claude Code session:
35
+ # $grind "port the auth module to the new interface"
36
+ # $crew "analyze all three data pipeline stages in parallel"
37
+ ```
38
+
39
+ ---
40
+
41
+ ## Skills
42
+
43
+ Install with `loom setup`. Then use inside any Claude Code session:
44
+
45
+ | Skill | What it does |
46
+ |---|---|
47
+ | `$grind` | Persistence loop with mandatory verification gate |
48
+ | `$crew` | Parallel workers — decomposes task, runs simultaneously, verifies |
49
+ | `$architect` | Deep analysis — maps system, finds real problems, recommends approach |
50
+
51
+ ---
52
+
53
+ ## CLI
54
+
55
+ ```
56
+ loom crew [N] "<task>" Spawn N general-purpose workers
57
+ loom crew 3 "<task>" Spawn 3 workers
58
+ loom crew 2:explore "<task>" Spawn 2 Explore-type workers
59
+ loom crew 2:explore+1:code-reviewer Spawn typed crew
60
+ loom status Show active session
61
+ loom setup Install skills + validate
62
+ ```
63
+
64
+ ### Worker types
65
+
66
+ Matches Claude Code's built-in subagent types:
67
+
68
+ | Type | Best for |
69
+ |---|---|
70
+ | `explore` | Read-only research, codebase mapping |
71
+ | `plan` | Architecture decisions, approach planning |
72
+ | `code-reviewer` | Audits, security reviews, quality checks |
73
+ | `frontend-developer` | UI and component work |
74
+ | `general-purpose` | General implementation (default) |
75
+
76
+ ---
77
+
78
+ ## State directory
79
+
80
+ ```
81
+ .agentloom/
82
+ tasks/ Task queue — workers claim atomically
83
+ workers/ Worker status and results
84
+ context/ Shared context snapshots
85
+ session.json Active session metadata
86
+ ```
87
+
88
+ ---
89
+
90
+ ## Requirements
91
+
92
+ - Node.js 20+
93
+ - Claude Code CLI (`claude`)
94
+ - tmux (optional — used for crew mode on Mac/Linux; falls back to background processes on WSL/Windows)
95
+
96
+ ---
97
+
98
+ ## License
99
+
100
+ MIT
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+ import { setup } from './commands/setup.js';
3
+ import { crew } from './commands/crew.js';
4
+ import { status } from './commands/status.js';
5
+ const [, , command, ...args] = process.argv;
6
+ const usage = `
7
+ agentloom (loom) — workflow layer for Claude Code
8
+
9
+ Usage:
10
+ loom setup Install skills and initialize state dir
11
+ loom crew [N] "<task>" Spawn N parallel workers on a task
12
+ loom crew 2:explore "<task>" Spawn typed workers (explore/plan/code-reviewer)
13
+ loom status Show active crew session
14
+
15
+ Modes (use $grind or $crew inside a Claude Code session):
16
+ $grind Persistence loop — keeps working until verified complete
17
+ $crew Parallel workers — decompose and execute simultaneously
18
+
19
+ Examples:
20
+ loom setup
21
+ loom crew "refactor the auth module"
22
+ loom crew 3 "audit every API endpoint for security issues"
23
+ loom crew 2:explore+1:code-reviewer "review the payment flow"
24
+ `;
25
+ switch (command) {
26
+ case 'setup':
27
+ await setup();
28
+ break;
29
+ case 'crew':
30
+ await crew(args);
31
+ break;
32
+ case 'status':
33
+ await status();
34
+ break;
35
+ default:
36
+ console.log(usage);
37
+ process.exit(command ? 1 : 0);
38
+ }
@@ -0,0 +1 @@
1
+ export declare function crew(args: string[]): Promise<void>;
@@ -0,0 +1,103 @@
1
+ import { execSync, spawn } from 'child_process';
2
+ import { writeFile, mkdir } from 'fs/promises';
3
+ import { join } from 'path';
4
+ import { parseWorkerSpec, initSession, writeContextSnapshot, decomposeTasks, } from '../team/orchestrator.js';
5
+ import { STATE_DIR } from '../state/session.js';
6
+ const hasTmux = () => {
7
+ try {
8
+ execSync('tmux -V', { stdio: 'ignore' });
9
+ return true;
10
+ }
11
+ catch {
12
+ return false;
13
+ }
14
+ };
15
+ const isWSL = () => process.platform === 'linux' && !!process.env.WSL_DISTRO_NAME;
16
+ export async function crew(args) {
17
+ if (args.length === 0) {
18
+ console.error('Usage: loom crew [N] "<task>"');
19
+ process.exit(1);
20
+ }
21
+ const { specs, task } = parseWorkerSpec(args);
22
+ const totalWorkers = specs.reduce((sum, s) => sum + s.count, 0);
23
+ const slug = task.slice(0, 30).toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, '');
24
+ console.log(`\nagentloom crew`);
25
+ console.log(`Task: ${task}`);
26
+ console.log(`Workers: ${totalWorkers}`);
27
+ console.log(`Mode: ${hasTmux() && !isWSL() ? 'tmux' : 'background processes'}\n`);
28
+ const session = await initSession(task, totalWorkers);
29
+ const contextPath = await writeContextSnapshot(slug, task);
30
+ const tasks = await decomposeTasks(task, specs);
31
+ console.log(`Session: ${session.id}`);
32
+ console.log(`Tasks: ${tasks.length} created`);
33
+ console.log(`Context: ${contextPath}\n`);
34
+ const workerPrompt = buildWorkerPrompt(task, contextPath, session.id);
35
+ if (hasTmux() && !isWSL()) {
36
+ await launchTmux(session.id, totalWorkers, specs, workerPrompt);
37
+ }
38
+ else {
39
+ await launchBackground(session.id, totalWorkers, specs, workerPrompt);
40
+ }
41
+ console.log(`\nWorkers launched. Monitor with: loom status`);
42
+ console.log(`State dir: ${STATE_DIR}/`);
43
+ }
44
+ function buildWorkerPrompt(task, contextPath, sessionId) {
45
+ return `You are a worker agent in an agentloom crew session (${sessionId}).
46
+
47
+ Your job: help complete this task: "${task}"
48
+
49
+ ## Your protocol
50
+
51
+ 1. Read the shared context at: ${contextPath}
52
+ 2. Check ${STATE_DIR}/tasks/ for unclaimed work (files ending in -pending.json)
53
+ 3. Claim a task by reading it and noting the task ID
54
+ 4. Do the work thoroughly using all tools available to you
55
+ 5. Write your result back to ${STATE_DIR}/workers/
56
+ 6. Repeat until no pending tasks remain
57
+
58
+ ## Rules
59
+ - Claim only one task at a time
60
+ - Write your findings to the context file so other workers can see them
61
+ - Do not stop until you have completed at least one task
62
+ - If all tasks are claimed, do exploratory work relevant to the main task
63
+
64
+ Begin now. Check for pending tasks and start working.`;
65
+ }
66
+ async function launchBackground(sessionId, count, specs, prompt) {
67
+ await mkdir(join(STATE_DIR, 'workers'), { recursive: true });
68
+ let workerIdx = 0;
69
+ for (const spec of specs) {
70
+ for (let i = 0; i < spec.count; i++) {
71
+ const workerId = `w${String(workerIdx).padStart(2, '0')}`;
72
+ workerIdx++;
73
+ const promptFile = join(STATE_DIR, 'workers', `${workerId}-prompt.md`);
74
+ await writeFile(promptFile, prompt);
75
+ const child = spawn('claude', ['--print', '--dangerously-skip-permissions', '-p', prompt], {
76
+ detached: true,
77
+ stdio: ['ignore', 'ignore', 'ignore'],
78
+ env: { ...process.env, AGENTLOOM_WORKER_ID: workerId, AGENTLOOM_SESSION: sessionId },
79
+ });
80
+ child.unref();
81
+ console.log(` ✓ Worker ${workerId} (${spec.agentType}) launched [pid ${child.pid}]`);
82
+ }
83
+ }
84
+ }
85
+ async function launchTmux(sessionId, count, specs, prompt) {
86
+ const tmuxSession = `loom-${sessionId}`;
87
+ execSync(`tmux new-session -d -s ${tmuxSession} -x 220 -y 50`);
88
+ let workerIdx = 0;
89
+ for (const spec of specs) {
90
+ for (let i = 0; i < spec.count; i++) {
91
+ const workerId = `w${String(workerIdx).padStart(2, '0')}`;
92
+ workerIdx++;
93
+ if (workerIdx > 1) {
94
+ execSync(`tmux split-window -h -t ${tmuxSession}`);
95
+ execSync(`tmux select-layout -t ${tmuxSession} tiled`);
96
+ }
97
+ const cmd = `AGENTLOOM_WORKER_ID=${workerId} AGENTLOOM_SESSION=${sessionId} claude --print --dangerously-skip-permissions -p '${prompt.replace(/'/g, "'\"'\"'")}'; echo '[worker done]'; read`;
98
+ execSync(`tmux send-keys -t ${tmuxSession} "${cmd}" Enter`);
99
+ console.log(` ✓ Worker ${workerId} (${spec.agentType}) launched in tmux pane`);
100
+ }
101
+ }
102
+ execSync(`tmux attach-session -t ${tmuxSession}`);
103
+ }
@@ -0,0 +1 @@
1
+ export declare function setup(): Promise<void>;
@@ -0,0 +1,40 @@
1
+ import { mkdir, copyFile, readdir } from 'fs/promises';
2
+ import { join, dirname } from 'path';
3
+ import { homedir } from 'os';
4
+ import { fileURLToPath } from 'url';
5
+ const __dirname = dirname(fileURLToPath(import.meta.url));
6
+ const SKILLS_SRC = join(__dirname, '../../skills');
7
+ const SKILLS_DEST = join(homedir(), '.claude', 'skills');
8
+ export async function setup() {
9
+ console.log('agentloom setup\n');
10
+ // 1. Validate claude CLI exists
11
+ const { execSync } = await import('child_process');
12
+ try {
13
+ execSync('claude --version', { stdio: 'ignore' });
14
+ console.log('✓ claude CLI found');
15
+ }
16
+ catch {
17
+ console.error('✗ claude CLI not found — install Claude Code first');
18
+ process.exit(1);
19
+ }
20
+ // 2. Install skills
21
+ await mkdir(SKILLS_DEST, { recursive: true });
22
+ const skills = await readdir(SKILLS_SRC);
23
+ for (const skill of skills.filter(f => f.endsWith('.md'))) {
24
+ const dest = join(SKILLS_DEST, skill);
25
+ await copyFile(join(SKILLS_SRC, skill), dest);
26
+ console.log(`✓ skill installed: ${skill}`);
27
+ }
28
+ // 3. Check tmux (optional)
29
+ try {
30
+ execSync('tmux -V', { stdio: 'ignore' });
31
+ console.log('✓ tmux found — crew mode will use split panes');
32
+ }
33
+ catch {
34
+ console.log('~ tmux not found — crew mode will use background processes');
35
+ }
36
+ console.log('\nSetup complete.');
37
+ console.log('\nGet started:');
38
+ console.log(' loom crew "your task here"');
39
+ console.log(' or use $grind / $crew inside a Claude Code session');
40
+ }
@@ -0,0 +1 @@
1
+ export declare function status(): Promise<void>;
@@ -0,0 +1,34 @@
1
+ import { readSession, readTasks, readWorkers } from '../state/session.js';
2
+ import { existsSync } from 'fs';
3
+ import { STATE_DIR } from '../state/session.js';
4
+ export async function status() {
5
+ if (!existsSync(STATE_DIR)) {
6
+ console.log('No active session. Run: loom crew "<task>"');
7
+ return;
8
+ }
9
+ const session = await readSession();
10
+ if (!session) {
11
+ console.log('No session found.');
12
+ return;
13
+ }
14
+ const tasks = await readTasks();
15
+ const workers = await readWorkers();
16
+ const pending = tasks.filter(t => t.status === 'pending').length;
17
+ const claimed = tasks.filter(t => t.status === 'claimed').length;
18
+ const done = tasks.filter(t => t.status === 'done').length;
19
+ const failed = tasks.filter(t => t.status === 'failed').length;
20
+ console.log(`\nSession: ${session.id}`);
21
+ console.log(`Status: ${session.status}`);
22
+ console.log(`Task: ${session.description}`);
23
+ console.log(`Started: ${session.createdAt}`);
24
+ console.log(`\nTasks: ${pending} pending ${claimed} active ${done} done ${failed} failed`);
25
+ console.log(`Workers: ${workers.length} (${session.workerCount} total)`);
26
+ if (workers.length > 0) {
27
+ console.log('\nWorker status:');
28
+ for (const w of workers) {
29
+ const task = w.currentTaskId ? tasks.find(t => t.id === w.currentTaskId) : null;
30
+ const desc = task ? ` → ${task.description.slice(0, 60)}` : '';
31
+ console.log(` [${w.id}] ${w.status}${desc}`);
32
+ }
33
+ }
34
+ }
@@ -0,0 +1,39 @@
1
+ export declare const STATE_DIR = ".claude-team";
2
+ export type TaskStatus = 'pending' | 'claimed' | 'done' | 'failed';
3
+ export type Task = {
4
+ id: string;
5
+ description: string;
6
+ agentType?: string;
7
+ status: TaskStatus;
8
+ workerId?: string;
9
+ result?: string;
10
+ error?: string;
11
+ createdAt: string;
12
+ claimedAt?: string;
13
+ completedAt?: string;
14
+ };
15
+ export type Worker = {
16
+ id: string;
17
+ agentType: string;
18
+ status: 'idle' | 'working' | 'done';
19
+ currentTaskId?: string;
20
+ startedAt: string;
21
+ completedAt?: string;
22
+ };
23
+ export type Session = {
24
+ id: string;
25
+ description: string;
26
+ status: 'running' | 'verifying' | 'done' | 'failed';
27
+ workerCount: number;
28
+ createdAt: string;
29
+ completedAt?: string;
30
+ verificationResult?: 'pass' | 'fail';
31
+ verificationNotes?: string;
32
+ };
33
+ export declare function ensureStateDir(): Promise<void>;
34
+ export declare function writeSession(session: Session): Promise<void>;
35
+ export declare function readSession(): Promise<Session | null>;
36
+ export declare function writeTask(task: Task): Promise<void>;
37
+ export declare function writeWorker(worker: Worker): Promise<void>;
38
+ export declare function readWorkers(): Promise<Worker[]>;
39
+ export declare function readTasks(): Promise<Task[]>;
@@ -0,0 +1,40 @@
1
+ import { readFile, writeFile, mkdir } from 'fs/promises';
2
+ import { join } from 'path';
3
+ import { existsSync } from 'fs';
4
+ export const STATE_DIR = '.claude-team';
5
+ export async function ensureStateDir() {
6
+ await mkdir(join(STATE_DIR, 'tasks'), { recursive: true });
7
+ await mkdir(join(STATE_DIR, 'workers'), { recursive: true });
8
+ await mkdir(join(STATE_DIR, 'context'), { recursive: true });
9
+ }
10
+ export async function writeSession(session) {
11
+ await writeFile(join(STATE_DIR, 'session.json'), JSON.stringify(session, null, 2));
12
+ }
13
+ export async function readSession() {
14
+ const path = join(STATE_DIR, 'session.json');
15
+ if (!existsSync(path))
16
+ return null;
17
+ return JSON.parse(await readFile(path, 'utf8'));
18
+ }
19
+ export async function writeTask(task) {
20
+ await writeFile(join(STATE_DIR, 'tasks', `${task.id}-${task.status}.json`), JSON.stringify(task, null, 2));
21
+ }
22
+ export async function writeWorker(worker) {
23
+ await writeFile(join(STATE_DIR, 'workers', `${worker.id}.json`), JSON.stringify(worker, null, 2));
24
+ }
25
+ export async function readWorkers() {
26
+ const { readdir } = await import('fs/promises');
27
+ const dir = join(STATE_DIR, 'workers');
28
+ if (!existsSync(dir))
29
+ return [];
30
+ const files = await readdir(dir);
31
+ return Promise.all(files.filter(f => f.endsWith('.json')).map(async (f) => JSON.parse(await readFile(join(dir, f), 'utf8'))));
32
+ }
33
+ export async function readTasks() {
34
+ const { readdir } = await import('fs/promises');
35
+ const dir = join(STATE_DIR, 'tasks');
36
+ if (!existsSync(dir))
37
+ return [];
38
+ const files = await readdir(dir);
39
+ return Promise.all(files.filter(f => f.endsWith('.json')).map(async (f) => JSON.parse(await readFile(join(dir, f), 'utf8'))));
40
+ }
@@ -0,0 +1,12 @@
1
+ import { type Task, type Session } from '../state/session.js';
2
+ export type WorkerSpec = {
3
+ count: number;
4
+ agentType: string;
5
+ };
6
+ export declare function parseWorkerSpec(args: string[]): {
7
+ specs: WorkerSpec[];
8
+ task: string;
9
+ };
10
+ export declare function initSession(description: string, workerCount: number): Promise<Session>;
11
+ export declare function writeContextSnapshot(slug: string, task: string): Promise<string>;
12
+ export declare function decomposeTasks(task: string, specs: WorkerSpec[]): Promise<Task[]>;
@@ -0,0 +1,101 @@
1
+ import { writeFile } from 'fs/promises';
2
+ import { join } from 'path';
3
+ import { randomUUID } from 'crypto';
4
+ import { execSync } from 'child_process';
5
+ import { STATE_DIR, ensureStateDir, writeSession, writeTask } from '../state/session.js';
6
+ export function parseWorkerSpec(args) {
7
+ // Formats:
8
+ // omc team "task description"
9
+ // omc team 3 "task description"
10
+ // omc team 2:explore "task description"
11
+ // omc team 2:explore+1:code-reviewer "task description"
12
+ const task = args[args.length - 1] ?? '';
13
+ const specArg = args.length > 1 ? args[0] ?? '' : '';
14
+ if (!specArg) {
15
+ return { specs: [{ count: 2, agentType: 'general-purpose' }], task };
16
+ }
17
+ // Plain number: "3"
18
+ if (/^\d+$/.test(specArg)) {
19
+ return { specs: [{ count: parseInt(specArg), agentType: 'general-purpose' }], task };
20
+ }
21
+ // Typed specs: "2:explore+1:code-reviewer"
22
+ const parts = specArg.split('+');
23
+ const specs = parts.map(part => {
24
+ const [countStr, agentType] = part.split(':');
25
+ return {
26
+ count: parseInt(countStr ?? '1'),
27
+ agentType: agentType ?? 'general-purpose',
28
+ };
29
+ });
30
+ return { specs, task };
31
+ }
32
+ export async function initSession(description, workerCount) {
33
+ await ensureStateDir();
34
+ const session = {
35
+ id: randomUUID().slice(0, 8),
36
+ description,
37
+ status: 'running',
38
+ workerCount,
39
+ createdAt: new Date().toISOString(),
40
+ };
41
+ await writeSession(session);
42
+ return session;
43
+ }
44
+ export async function writeContextSnapshot(slug, task) {
45
+ const path = join(STATE_DIR, 'context', `${slug}.md`);
46
+ const content = `# Task Context\n\n**Task:** ${task}\n\n**Started:** ${new Date().toISOString()}\n\n## Notes\n\n_Workers will append findings here._\n`;
47
+ await writeFile(path, content);
48
+ return path;
49
+ }
50
+ export async function decomposeTasks(task, specs) {
51
+ const totalWorkers = specs.reduce((sum, s) => sum + s.count, 0);
52
+ const subtasks = await callClaudeDecompose(task, totalWorkers);
53
+ const tasks = [];
54
+ let idx = 0;
55
+ for (const spec of specs) {
56
+ for (let i = 0; i < spec.count; i++) {
57
+ const t = {
58
+ id: randomUUID().slice(0, 8),
59
+ description: subtasks[idx] ?? task,
60
+ agentType: spec.agentType,
61
+ status: 'pending',
62
+ createdAt: new Date().toISOString(),
63
+ };
64
+ tasks.push(t);
65
+ await writeTask(t);
66
+ idx++;
67
+ }
68
+ }
69
+ return tasks;
70
+ }
71
+ function callClaudeDecompose(task, n) {
72
+ if (n <= 1)
73
+ return [task];
74
+ const prompt = `Decompose this task into exactly ${n} independent subtasks that can run in parallel. Each must be specific and actionable. Respond with a JSON array of ${n} strings — no explanation, no markdown, just the array.
75
+
76
+ Task: "${task}"`;
77
+ try {
78
+ const escaped = prompt.replace(/'/g, `'"'"'`);
79
+ const raw = execSync(`claude --print -p '${escaped}'`, {
80
+ encoding: 'utf8',
81
+ timeout: 30_000,
82
+ stdio: ['ignore', 'pipe', 'ignore'],
83
+ }).trim();
84
+ // Extract JSON array from the response (strip any surrounding prose)
85
+ const match = raw.match(/\[[\s\S]*\]/);
86
+ if (!match)
87
+ throw new Error('No JSON array in response');
88
+ const parsed = JSON.parse(match[0]);
89
+ if (!Array.isArray(parsed) || parsed.length === 0)
90
+ throw new Error('Empty array');
91
+ const subtasks = parsed.map(String);
92
+ // Pad or trim to exactly n
93
+ while (subtasks.length < n)
94
+ subtasks.push(task);
95
+ return subtasks.slice(0, n);
96
+ }
97
+ catch {
98
+ // Fallback: every worker gets the same task description
99
+ return Array(n).fill(task);
100
+ }
101
+ }
@@ -0,0 +1,6 @@
1
+ import { type Task } from '../state/session.js';
2
+ export declare function claimTask(workerId: string): Promise<Task | null>;
3
+ export declare function completeTask(task: Task, result: string): Promise<void>;
4
+ export declare function failTask(task: Task, error: string): Promise<void>;
5
+ export declare function pendingCount(): Promise<number>;
6
+ export declare function allDone(): Promise<boolean>;
@@ -0,0 +1,62 @@
1
+ import { readdir, readFile, rename, writeFile } from 'fs/promises';
2
+ import { join } from 'path';
3
+ import { existsSync } from 'fs';
4
+ import { STATE_DIR } from '../state/session.js';
5
+ const TASKS_DIR = join(STATE_DIR, 'tasks');
6
+ export async function claimTask(workerId) {
7
+ if (!existsSync(TASKS_DIR))
8
+ return null;
9
+ const files = await readdir(TASKS_DIR);
10
+ const pending = files.filter(f => f.endsWith('-pending.json'));
11
+ for (const file of pending) {
12
+ const oldPath = join(TASKS_DIR, file);
13
+ const task = JSON.parse(await readFile(oldPath, 'utf8'));
14
+ const newFile = file.replace('-pending.json', `-claimed-${workerId}.json`);
15
+ const newPath = join(TASKS_DIR, newFile);
16
+ // Atomic rename = claim. First writer wins.
17
+ try {
18
+ await rename(oldPath, newPath);
19
+ task.status = 'claimed';
20
+ task.workerId = workerId;
21
+ task.claimedAt = new Date().toISOString();
22
+ await writeFile(newPath, JSON.stringify(task, null, 2));
23
+ return task;
24
+ }
25
+ catch {
26
+ // Another worker claimed it first — try next
27
+ continue;
28
+ }
29
+ }
30
+ return null;
31
+ }
32
+ export async function completeTask(task, result) {
33
+ const claimedFile = join(TASKS_DIR, `${task.id}-claimed-${task.workerId}.json`);
34
+ const doneFile = join(TASKS_DIR, `${task.id}-done-${task.workerId}.json`);
35
+ task.status = 'done';
36
+ task.result = result;
37
+ task.completedAt = new Date().toISOString();
38
+ await writeFile(claimedFile, JSON.stringify(task, null, 2));
39
+ await rename(claimedFile, doneFile);
40
+ }
41
+ export async function failTask(task, error) {
42
+ const claimedFile = join(TASKS_DIR, `${task.id}-claimed-${task.workerId}.json`);
43
+ const failedFile = join(TASKS_DIR, `${task.id}-failed-${task.workerId}.json`);
44
+ task.status = 'failed';
45
+ task.error = error;
46
+ task.completedAt = new Date().toISOString();
47
+ await writeFile(claimedFile, JSON.stringify(task, null, 2));
48
+ await rename(claimedFile, failedFile);
49
+ }
50
+ export async function pendingCount() {
51
+ if (!existsSync(TASKS_DIR))
52
+ return 0;
53
+ const files = await readdir(TASKS_DIR);
54
+ return files.filter(f => f.includes('-pending.json')).length;
55
+ }
56
+ export async function allDone() {
57
+ if (!existsSync(TASKS_DIR))
58
+ return true;
59
+ const files = await readdir(TASKS_DIR);
60
+ const active = files.filter(f => f.includes('-pending.json') || f.includes('-claimed-'));
61
+ return active.length === 0;
62
+ }
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@chuckssmith/agentloom",
3
+ "version": "0.2.0",
4
+ "description": "A workflow layer for Claude Code — reusable roles, persistence loops, and multi-agent crew coordination",
5
+ "keywords": [
6
+ "ai",
7
+ "agent",
8
+ "workflow",
9
+ "multi-agent"
10
+ ],
11
+ "homepage": "https://github.com/cssmith615/agentloom",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/cssmith615/agentloom.git"
15
+ },
16
+ "license": "MIT",
17
+ "author": "cssmith615",
18
+ "files": [
19
+ "dist",
20
+ "skills"
21
+ ],
22
+ "type": "module",
23
+ "bin": {
24
+ "loom": "dist/cli.js"
25
+ },
26
+ "scripts": {
27
+ "build": "tsc",
28
+ "dev": "tsc --watch",
29
+ "start": "node dist/cli.js"
30
+ },
31
+ "engines": {
32
+ "node": ">=20"
33
+ },
34
+ "devDependencies": {
35
+ "@types/node": "^20.0.0",
36
+ "typescript": "^5.4.0"
37
+ }
38
+ }
@@ -0,0 +1,53 @@
1
+ ---
2
+ name: architect
3
+ description: Deep analysis and architecture review. Use before major implementation decisions or to audit existing systems. Returns a structured assessment with specific recommendations.
4
+ ---
5
+
6
+ # $architect — Deep Analysis
7
+
8
+ You are now in **architect mode**. You will analyze deeply, not implement.
9
+
10
+ ## Your job
11
+ Produce a structured architectural assessment. Be specific, cite file paths and line numbers, identify actual problems — not hypothetical ones.
12
+
13
+ ## Process
14
+
15
+ ### Step 1 — Explore (parallel)
16
+ Spawn `Explore` subagents in parallel to map the relevant systems simultaneously.
17
+ Use `run_in_background: true`.
18
+
19
+ ### Step 2 — Synthesize
20
+ From the exploration results, identify:
21
+ - What exists and how it works
22
+ - Boundaries and contracts between components
23
+ - Actual problems (with evidence)
24
+ - Risks (specific, not generic)
25
+ - Recommended approach with tradeoffs
26
+
27
+ ### Step 3 — Deliver
28
+ Return a structured report:
29
+
30
+ ```
31
+ ## System map
32
+ [What exists, how it's connected]
33
+
34
+ ## Findings
35
+ [Numbered list, each with: what, where (file:line), why it matters]
36
+
37
+ ## Recommendation
38
+ [Specific approach, not "consider refactoring"]
39
+
40
+ ## Risks
41
+ [What could go wrong with the recommendation]
42
+
43
+ ## Open questions
44
+ [What you'd need to know before proceeding]
45
+ ```
46
+
47
+ ## Rules
48
+ - Cite specific files and line numbers for every finding
49
+ - Do not recommend changes you haven't verified are needed
50
+ - "Consider refactoring" is not a recommendation — say what to refactor and why
51
+ - If the system is fine, say so
52
+
53
+ Begin analysis now.
package/skills/crew.md ADDED
@@ -0,0 +1,56 @@
1
+ ---
2
+ name: crew
3
+ description: Spawn parallel workers on a task. Decomposes the work, runs workers simultaneously, then runs a verification pass. Use when the task is clearly decomposable into independent streams.
4
+ ---
5
+
6
+ # $crew — Parallel Workers
7
+
8
+ You are now in **crew mode**. You will decompose this task, run workers in parallel, and verify the result.
9
+
10
+ ## When to use
11
+ - Task has clearly independent work streams (e.g. audit 5 modules, port 3 subsystems)
12
+ - Work would benefit from simultaneous execution
13
+ - You want one worker per concern (explore / implement / verify)
14
+
15
+ ## When NOT to use
16
+ - Task must be done sequentially (each step depends on the last) — use `$grind` instead
17
+ - Task is a quick single fix — just do it directly
18
+
19
+ ## Execution rules
20
+ - Decompose into **truly independent** subtasks only
21
+ - Default crew: 1 `Explore` + 1 `general-purpose` + 1 `code-reviewer`
22
+ - For large tasks: up to 4 workers; beyond that, coordination cost exceeds benefit
23
+ - Use `run_in_background: true` for all worker spawns
24
+ - Collect all results before running the verification pass
25
+
26
+ ## Crew structure
27
+
28
+ ### Step 1 — Decompose
29
+ Break the task into N independent subtasks. Write them to `.agentloom/tasks/`.
30
+
31
+ For each subtask, decide the right agent type:
32
+ - `Explore` — research, read files, understand structure (read-only)
33
+ - `Plan` — architecture decisions, approach planning
34
+ - `general-purpose` — implementation work
35
+ - `code-reviewer` — audit, security review, quality check
36
+ - `frontend-developer` — UI/component work
37
+
38
+ ### Step 2 — Launch crew (parallel)
39
+ Spawn all workers simultaneously with `run_in_background: true`.
40
+ Each worker gets: the subtask description + the shared context path.
41
+
42
+ ### Step 3 — Collect results
43
+ Wait for all background workers to complete.
44
+ Read each result from `.agentloom/workers/`.
45
+
46
+ ### Step 4 — Verify
47
+ Spawn a `code-reviewer` subagent across all completed work.
48
+ Return PASS or FAIL with evidence.
49
+
50
+ ### Step 5 — Report
51
+ Summarize what each worker did and the final verification result.
52
+
53
+ ## Completion contract
54
+ Same as `$grind` — only done when verification passes with evidence.
55
+
56
+ Begin now. Decompose the task, then launch the crew.
@@ -0,0 +1,53 @@
1
+ ---
2
+ name: grind
3
+ description: Persistence loop — keeps working on a task until it is fully complete and verified. Use when you need guaranteed completion, not "do your best".
4
+ ---
5
+
6
+ # $grind — Persistence Loop
7
+
8
+ You are now in **grind mode**. You will not stop until this task is genuinely complete and passes verification.
9
+
10
+ ## When to use
11
+ - Task requires guaranteed completion with verification
12
+ - Multi-step work that may span many iterations
13
+ - User says "don't stop", "keep going", "finish this", "must work"
14
+
15
+ ## Execution rules
16
+ - Fire independent sub-agent calls **simultaneously** using `run_in_background: true`
17
+ - Use the `Explore` subagent type for research/read-only work
18
+ - Use the `Plan` subagent type before major implementation decisions
19
+ - Use the `code-reviewer` subagent type for verification
20
+ - Do NOT declare done until verification passes
21
+ - Do NOT reduce scope to make the task easier
22
+ - Do NOT delete or skip tests to make them pass
23
+
24
+ ## Loop structure
25
+
26
+ ### Step 0 — Context snapshot
27
+ Before starting, write a context file to `.agentloom/context/{task-slug}.md` with:
28
+ - Task statement
29
+ - Desired outcome
30
+ - Known facts
31
+ - Constraints
32
+ - Unknowns
33
+
34
+ ### Step 1 — Plan
35
+ Spawn a `Plan` subagent to map the implementation approach. Save the plan.
36
+
37
+ ### Step 2 — Execute (parallel)
38
+ Spawn parallel subagents for independent work streams. Use `run_in_background: true`.
39
+
40
+ ### Step 3 — Verify
41
+ Spawn a `code-reviewer` subagent with this prompt:
42
+ > "Review the completed work. Run all tests. Check that the original task requirements are met. Return PASS or FAIL with specific evidence."
43
+
44
+ ### Step 4 — Iterate or complete
45
+ - **PASS** → report completion with evidence
46
+ - **FAIL** → go back to Step 2 with the reviewer's specific findings
47
+
48
+ ## Completion contract
49
+ You may only declare this task done when:
50
+ 1. A `code-reviewer` subagent has returned PASS
51
+ 2. You can cite specific evidence (test output, file paths, behavior observed)
52
+
53
+ Begin now. State the task, write the context snapshot, then start the loop.