heyio 3.0.2 → 3.0.3
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/dist/api/server.js +1 -1
- package/dist/api/server.js.map +1 -1
- package/dist/logging/logger.d.ts.map +1 -1
- package/dist/logging/logger.js +13 -1
- package/dist/logging/logger.js.map +1 -1
- package/node_modules/@io/shared/package.json +1 -1
- package/package.json +7 -2
- package/public/assets/index-2RY89H3W.js +336 -0
- package/public/assets/index-2RY89H3W.js.map +1 -0
- package/public/assets/index-D3cGfBsj.css +1 -0
- package/public/index.html +14 -0
- package/src/api/middleware/auth.ts +0 -76
- package/src/api/notifications.ts +0 -122
- package/src/api/routes/activity.ts +0 -29
- package/src/api/routes/attachments.ts +0 -93
- package/src/api/routes/config.ts +0 -115
- package/src/api/routes/conversations.ts +0 -87
- package/src/api/routes/health.ts +0 -18
- package/src/api/routes/inbox.ts +0 -98
- package/src/api/routes/schedules.ts +0 -121
- package/src/api/routes/skills.ts +0 -105
- package/src/api/routes/squads.ts +0 -145
- package/src/api/routes/usage.ts +0 -57
- package/src/api/routes/wiki.ts +0 -49
- package/src/api/server.ts +0 -186
- package/src/config.ts +0 -3
- package/src/copilot/client.ts +0 -42
- package/src/copilot/health-monitor.ts +0 -85
- package/src/copilot/orchestrator.ts +0 -222
- package/src/copilot/tools.ts +0 -707
- package/src/index.ts +0 -113
- package/src/logging/logger.ts +0 -26
- package/src/models/index.ts +0 -11
- package/src/models/pricing.ts +0 -121
- package/src/models/registry.ts +0 -131
- package/src/models/token-tracker.ts +0 -151
- package/src/scheduler/engine.ts +0 -146
- package/src/skills/index.ts +0 -13
- package/src/skills/store.ts +0 -188
- package/src/squad/agent.ts +0 -326
- package/src/squad/autonomy.ts +0 -78
- package/src/squad/event-bus.ts +0 -71
- package/src/squad/execution/index.ts +0 -17
- package/src/squad/execution/instance.ts +0 -186
- package/src/squad/execution/meeting.ts +0 -191
- package/src/squad/execution/pr.ts +0 -127
- package/src/squad/execution/runner.ts +0 -97
- package/src/squad/execution/tasks.ts +0 -111
- package/src/squad/execution/worktree.ts +0 -138
- package/src/squad/hiring.ts +0 -222
- package/src/squad/index.ts +0 -17
- package/src/squad/manager.ts +0 -337
- package/src/squad/name-generator.ts +0 -135
- package/src/squad/roles/templates.ts +0 -104
- package/src/squad/skill-parser.ts +0 -120
- package/src/squad/source-resolver.ts +0 -57
- package/src/store/activity.ts +0 -176
- package/src/store/db.ts +0 -237
- package/src/store/inbox.ts +0 -199
- package/src/store/schedules.ts +0 -199
- package/src/wiki/index.ts +0 -12
- package/src/wiki/store.ts +0 -139
- package/tsconfig.json +0 -9
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import { createChildLogger } from '../../logging/logger.js';
|
|
2
|
-
import type { Agent } from '../agent.js';
|
|
3
|
-
import { getEventBus } from '../event-bus.js';
|
|
4
|
-
import type { SquadRuntime } from '../manager.js';
|
|
5
|
-
import { type Instance, type InstanceTask, transitionInstance } from './instance.js';
|
|
6
|
-
|
|
7
|
-
const logger = () => createChildLogger('task-exec');
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Execute all tasks in an instance sequentially.
|
|
11
|
-
* Team lead reviews each completed task before moving to the next.
|
|
12
|
-
*/
|
|
13
|
-
export async function executeTasks(params: {
|
|
14
|
-
instance: Instance;
|
|
15
|
-
runtime: SquadRuntime;
|
|
16
|
-
}): Promise<void> {
|
|
17
|
-
const log = logger();
|
|
18
|
-
const { instance, runtime } = params;
|
|
19
|
-
|
|
20
|
-
await transitionInstance(instance.id, 'working');
|
|
21
|
-
|
|
22
|
-
const teamLead = runtime.members.get('team-lead');
|
|
23
|
-
if (!teamLead) throw new Error('No team lead for task execution');
|
|
24
|
-
|
|
25
|
-
for (const task of instance.tasks) {
|
|
26
|
-
if (task.status === 'done') continue;
|
|
27
|
-
|
|
28
|
-
log.info({ taskId: task.id, assignedTo: task.assignedTo }, 'Executing task');
|
|
29
|
-
task.status = 'in_progress';
|
|
30
|
-
|
|
31
|
-
const agent = runtime.members.get(task.assignedTo);
|
|
32
|
-
if (!agent) {
|
|
33
|
-
log.warn({ role: task.assignedTo }, 'No agent for assigned role, team lead will handle');
|
|
34
|
-
// Reassign to team lead for delegation decision
|
|
35
|
-
task.result = `No agent with role '${task.assignedTo}' available.`;
|
|
36
|
-
task.status = 'failed';
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
try {
|
|
41
|
-
// Agent executes the task
|
|
42
|
-
const workingDir = instance.worktree?.path ?? '';
|
|
43
|
-
const taskPrompt = buildTaskPrompt(task, workingDir, instance);
|
|
44
|
-
|
|
45
|
-
const result = await agent.send(taskPrompt);
|
|
46
|
-
task.result = result;
|
|
47
|
-
|
|
48
|
-
// Team lead reviews
|
|
49
|
-
const review = await teamLead.send(
|
|
50
|
-
`An agent (${task.assignedTo}) completed a task. Review their work:\n\nTask: ${task.description}\n\nAgent's report:\n${result.slice(0, 2000)}\n\nIs this satisfactory? Reply with:\n- "APPROVED" if the work meets requirements\n- "REDO: <feedback>" if it needs changes`,
|
|
51
|
-
);
|
|
52
|
-
|
|
53
|
-
if (review.toUpperCase().includes('APPROVED')) {
|
|
54
|
-
task.status = 'done';
|
|
55
|
-
log.info({ taskId: task.id }, 'Task approved');
|
|
56
|
-
} else if (review.toUpperCase().includes('REDO:')) {
|
|
57
|
-
// Give agent one more attempt with feedback
|
|
58
|
-
const feedback = review.replace(/^.*REDO:\s*/i, '');
|
|
59
|
-
const retry = await agent.send(
|
|
60
|
-
`Your work on "${task.description}" needs revision. Feedback from team lead:\n\n${feedback}\n\nPlease address this feedback and report your updated results.`,
|
|
61
|
-
);
|
|
62
|
-
task.result = retry;
|
|
63
|
-
task.status = 'done'; // Accept after one retry
|
|
64
|
-
log.info({ taskId: task.id }, 'Task completed after revision');
|
|
65
|
-
} else {
|
|
66
|
-
task.status = 'done';
|
|
67
|
-
}
|
|
68
|
-
} catch (err) {
|
|
69
|
-
log.error({ err, taskId: task.id }, 'Task execution failed');
|
|
70
|
-
task.status = 'failed';
|
|
71
|
-
task.result = `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Move to reviewing phase
|
|
76
|
-
await transitionInstance(instance.id, 'reviewing');
|
|
77
|
-
|
|
78
|
-
// Team lead does final review
|
|
79
|
-
const taskSummary = instance.tasks
|
|
80
|
-
.map((t) => `- [${t.status}] ${t.description} (${t.assignedTo})`)
|
|
81
|
-
.join('\n');
|
|
82
|
-
|
|
83
|
-
const finalReview = await teamLead.send(
|
|
84
|
-
`All tasks have been executed. Here's the summary:\n\n${taskSummary}\n\nProvide a final assessment. Should we proceed to create a PR, or are there critical issues? Reply with:\n- "READY_FOR_PR: <PR title>" if ready\n- "NEEDS_WORK: <what's missing>" if not ready`,
|
|
85
|
-
);
|
|
86
|
-
|
|
87
|
-
if (finalReview.toUpperCase().includes('READY_FOR_PR:')) {
|
|
88
|
-
instance.meetingLog.push(`[team-lead] Final review: ${finalReview}`);
|
|
89
|
-
} else {
|
|
90
|
-
// Mark complete anyway — we don't loop indefinitely
|
|
91
|
-
instance.meetingLog.push(`[team-lead] Final review (issues noted): ${finalReview}`);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function buildTaskPrompt(task: InstanceTask, workingDir: string, instance: Instance): string {
|
|
96
|
-
const parts = ['You have been assigned the following task:', `\nTask: ${task.description}`];
|
|
97
|
-
|
|
98
|
-
if (workingDir) {
|
|
99
|
-
parts.push(`\nWorking directory: ${workingDir}`);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (instance.issueRef) {
|
|
103
|
-
parts.push(`\nRelated issue: ${instance.issueRef}`);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
parts.push(
|
|
107
|
-
'\nComplete the task to the best of your ability. Use your available tools to read, edit, and test code as needed. Report back with a summary of what you did.',
|
|
108
|
-
);
|
|
109
|
-
|
|
110
|
-
return parts.join('');
|
|
111
|
-
}
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import { execSync } from 'node:child_process';
|
|
2
|
-
import { existsSync, rmSync } from 'node:fs';
|
|
3
|
-
import { join } from 'node:path';
|
|
4
|
-
import { createChildLogger } from '../../logging/logger.js';
|
|
5
|
-
|
|
6
|
-
const logger = () => createChildLogger('worktree');
|
|
7
|
-
|
|
8
|
-
export interface WorktreeInfo {
|
|
9
|
-
path: string;
|
|
10
|
-
branch: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Create a git worktree for an instance.
|
|
15
|
-
* Branch naming: io/{squad-name}/{short-id}
|
|
16
|
-
*/
|
|
17
|
-
export function createWorktree(params: {
|
|
18
|
-
repoPath: string;
|
|
19
|
-
squadName: string;
|
|
20
|
-
instanceId: string;
|
|
21
|
-
}): WorktreeInfo {
|
|
22
|
-
const log = logger();
|
|
23
|
-
const shortId = params.instanceId.slice(0, 8);
|
|
24
|
-
const branch = `io/${params.squadName}/${shortId}`;
|
|
25
|
-
const worktreePath = join(
|
|
26
|
-
params.repoPath,
|
|
27
|
-
'..',
|
|
28
|
-
'.io-worktrees',
|
|
29
|
-
`${params.squadName}-${shortId}`,
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
// Ensure the repo is a git repository
|
|
33
|
-
if (!existsSync(join(params.repoPath, '.git'))) {
|
|
34
|
-
throw new Error(`Not a git repository: ${params.repoPath}`);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Create the worktree with a new branch from HEAD
|
|
38
|
-
try {
|
|
39
|
-
execSync(`git worktree add -b "${branch}" "${worktreePath}"`, {
|
|
40
|
-
cwd: params.repoPath,
|
|
41
|
-
stdio: 'pipe',
|
|
42
|
-
encoding: 'utf-8',
|
|
43
|
-
});
|
|
44
|
-
} catch (err) {
|
|
45
|
-
// Branch might already exist — try without -b
|
|
46
|
-
try {
|
|
47
|
-
execSync(`git worktree add "${worktreePath}" "${branch}"`, {
|
|
48
|
-
cwd: params.repoPath,
|
|
49
|
-
stdio: 'pipe',
|
|
50
|
-
encoding: 'utf-8',
|
|
51
|
-
});
|
|
52
|
-
} catch (innerErr) {
|
|
53
|
-
throw new Error(
|
|
54
|
-
`Failed to create worktree: ${innerErr instanceof Error ? innerErr.message : String(innerErr)}`,
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
log.info({ worktreePath, branch }, 'Worktree created');
|
|
60
|
-
return { path: worktreePath, branch };
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Remove a git worktree and delete its branch.
|
|
65
|
-
*/
|
|
66
|
-
export function removeWorktree(params: {
|
|
67
|
-
repoPath: string;
|
|
68
|
-
worktreePath: string;
|
|
69
|
-
branch: string;
|
|
70
|
-
}): void {
|
|
71
|
-
const log = logger();
|
|
72
|
-
|
|
73
|
-
try {
|
|
74
|
-
execSync(`git worktree remove "${params.worktreePath}" --force`, {
|
|
75
|
-
cwd: params.repoPath,
|
|
76
|
-
stdio: 'pipe',
|
|
77
|
-
encoding: 'utf-8',
|
|
78
|
-
});
|
|
79
|
-
} catch {
|
|
80
|
-
// Worktree might already be removed; try cleaning up the directory
|
|
81
|
-
if (existsSync(params.worktreePath)) {
|
|
82
|
-
rmSync(params.worktreePath, { recursive: true, force: true });
|
|
83
|
-
}
|
|
84
|
-
// Prune worktree references
|
|
85
|
-
try {
|
|
86
|
-
execSync('git worktree prune', { cwd: params.repoPath, stdio: 'pipe' });
|
|
87
|
-
} catch {
|
|
88
|
-
// ignore
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Delete the branch
|
|
93
|
-
try {
|
|
94
|
-
execSync(`git branch -D "${params.branch}"`, {
|
|
95
|
-
cwd: params.repoPath,
|
|
96
|
-
stdio: 'pipe',
|
|
97
|
-
encoding: 'utf-8',
|
|
98
|
-
});
|
|
99
|
-
} catch {
|
|
100
|
-
// Branch might not exist or be checked out elsewhere
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
log.info({ branch: params.branch }, 'Worktree removed');
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* List all IO-managed worktrees for a repository.
|
|
108
|
-
*/
|
|
109
|
-
export function listWorktrees(repoPath: string): WorktreeInfo[] {
|
|
110
|
-
try {
|
|
111
|
-
const output = execSync('git worktree list --porcelain', {
|
|
112
|
-
cwd: repoPath,
|
|
113
|
-
stdio: 'pipe',
|
|
114
|
-
encoding: 'utf-8',
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
const worktrees: WorktreeInfo[] = [];
|
|
118
|
-
const blocks = output.split('\n\n');
|
|
119
|
-
|
|
120
|
-
for (const block of blocks) {
|
|
121
|
-
const lines = block.trim().split('\n');
|
|
122
|
-
const pathLine = lines.find((l) => l.startsWith('worktree '));
|
|
123
|
-
const branchLine = lines.find((l) => l.startsWith('branch '));
|
|
124
|
-
|
|
125
|
-
if (pathLine && branchLine) {
|
|
126
|
-
const path = pathLine.replace('worktree ', '');
|
|
127
|
-
const branch = branchLine.replace('branch refs/heads/', '');
|
|
128
|
-
if (branch.startsWith('io/')) {
|
|
129
|
-
worktrees.push({ path, branch });
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return worktrees;
|
|
135
|
-
} catch {
|
|
136
|
-
return [];
|
|
137
|
-
}
|
|
138
|
-
}
|
package/src/squad/hiring.ts
DELETED
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import { homedir } from 'node:os';
|
|
3
|
-
import { basename, join } from 'node:path';
|
|
4
|
-
import { createChildLogger } from '../logging/logger.js';
|
|
5
|
-
import { ensureSquadWiki } from '../wiki/index.js';
|
|
6
|
-
import { addMember, createSquad } from './manager.js';
|
|
7
|
-
import { generateSquadNames } from './name-generator.js';
|
|
8
|
-
import { QA_TESTER_SKILL, SCRIBE_SKILL, TEAM_LEAD_SKILL } from './roles/templates.js';
|
|
9
|
-
import { parseSkillContent } from './skill-parser.js';
|
|
10
|
-
|
|
11
|
-
const logger = () => createChildLogger('hiring');
|
|
12
|
-
|
|
13
|
-
interface ProjectAnalysis {
|
|
14
|
-
name: string;
|
|
15
|
-
languages: string[];
|
|
16
|
-
frameworks: string[];
|
|
17
|
-
hasTests: boolean;
|
|
18
|
-
hasCi: boolean;
|
|
19
|
-
suggestedSpecialists: string[];
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Analyze a project directory to determine what kind of squad it needs.
|
|
24
|
-
*/
|
|
25
|
-
export function analyzeProject(projectPath: string): ProjectAnalysis {
|
|
26
|
-
const name = basename(projectPath);
|
|
27
|
-
const languages: string[] = [];
|
|
28
|
-
const frameworks: string[] = [];
|
|
29
|
-
let hasTests = false;
|
|
30
|
-
let hasCi = false;
|
|
31
|
-
|
|
32
|
-
// Check for common project indicators
|
|
33
|
-
const files = safeReadDir(projectPath);
|
|
34
|
-
|
|
35
|
-
// Language detection
|
|
36
|
-
if (files.includes('package.json')) {
|
|
37
|
-
languages.push('TypeScript/JavaScript');
|
|
38
|
-
const pkg = safeReadJson(join(projectPath, 'package.json'));
|
|
39
|
-
if (pkg) {
|
|
40
|
-
const deps = (pkg.dependencies ?? {}) as Record<string, string>;
|
|
41
|
-
const devDeps = (pkg.devDependencies ?? {}) as Record<string, string>;
|
|
42
|
-
const allDeps = { ...deps, ...devDeps };
|
|
43
|
-
if (allDeps.react || allDeps['react-dom']) frameworks.push('React');
|
|
44
|
-
if (allDeps.vue) frameworks.push('Vue');
|
|
45
|
-
if (allDeps.svelte) frameworks.push('Svelte');
|
|
46
|
-
if (allDeps.next) frameworks.push('Next.js');
|
|
47
|
-
if (allDeps.express || allDeps.fastify || allDeps.koa) frameworks.push('Node.js Backend');
|
|
48
|
-
if (allDeps.vitest || allDeps.jest || allDeps.mocha) hasTests = true;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
if (files.includes('Cargo.toml')) languages.push('Rust');
|
|
52
|
-
if (files.includes('go.mod')) languages.push('Go');
|
|
53
|
-
if (files.includes('requirements.txt') || files.includes('pyproject.toml'))
|
|
54
|
-
languages.push('Python');
|
|
55
|
-
if (files.includes('Gemfile')) languages.push('Ruby');
|
|
56
|
-
if (files.some((f) => f.endsWith('.csproj') || f.endsWith('.sln'))) languages.push('C#/.NET');
|
|
57
|
-
|
|
58
|
-
// CI detection
|
|
59
|
-
if (files.includes('.github')) {
|
|
60
|
-
const ghDir = safeReadDir(join(projectPath, '.github'));
|
|
61
|
-
if (ghDir.includes('workflows')) hasCi = true;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Suggest specialists based on detected tech
|
|
65
|
-
const suggestedSpecialists: string[] = [];
|
|
66
|
-
if (frameworks.includes('React') || frameworks.includes('Vue') || frameworks.includes('Svelte'))
|
|
67
|
-
suggestedSpecialists.push('frontend-developer');
|
|
68
|
-
if (frameworks.includes('Node.js Backend')) suggestedSpecialists.push('backend-developer');
|
|
69
|
-
if (languages.includes('Rust')) suggestedSpecialists.push('rust-developer');
|
|
70
|
-
if (languages.includes('Go')) suggestedSpecialists.push('go-developer');
|
|
71
|
-
if (languages.includes('Python')) suggestedSpecialists.push('python-developer');
|
|
72
|
-
if (languages.includes('C#/.NET')) suggestedSpecialists.push('dotnet-developer');
|
|
73
|
-
|
|
74
|
-
// If no specialists detected, add a generic one
|
|
75
|
-
if (suggestedSpecialists.length === 0 && languages.length > 0) {
|
|
76
|
-
suggestedSpecialists.push('developer');
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return { name, languages, frameworks, hasTests, hasCi, suggestedSpecialists };
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Generate a specialist SKILL.md from a role name and detected context.
|
|
84
|
-
*/
|
|
85
|
-
function generateSpecialistSkill(role: string, analysis: ProjectAnalysis): string {
|
|
86
|
-
const langContext = analysis.languages.join(', ');
|
|
87
|
-
const frameworkContext =
|
|
88
|
-
analysis.frameworks.length > 0 ? `\nFrameworks: ${analysis.frameworks.join(', ')}` : '';
|
|
89
|
-
|
|
90
|
-
return `---
|
|
91
|
-
role: ${role}
|
|
92
|
-
tools:
|
|
93
|
-
- read_file
|
|
94
|
-
- edit_file
|
|
95
|
-
- run_command
|
|
96
|
-
- search_code
|
|
97
|
-
veto: false
|
|
98
|
-
---
|
|
99
|
-
|
|
100
|
-
# ${titleCase(role)}
|
|
101
|
-
|
|
102
|
-
## Identity
|
|
103
|
-
You are a ${titleCase(role)} specializing in ${langContext}.${frameworkContext}
|
|
104
|
-
|
|
105
|
-
## Responsibilities
|
|
106
|
-
- Implement features and fix bugs assigned by the Team Lead
|
|
107
|
-
- Write clean, well-tested code following project conventions
|
|
108
|
-
- Run tests before submitting work for review
|
|
109
|
-
- Respond to code review feedback promptly
|
|
110
|
-
|
|
111
|
-
## Boundaries
|
|
112
|
-
- Only work on tasks assigned to you by the Team Lead
|
|
113
|
-
- Do NOT modify files outside your area of expertise unless directed
|
|
114
|
-
- Do NOT merge PRs — submit work for team lead review
|
|
115
|
-
- Always run the test suite before reporting task completion
|
|
116
|
-
|
|
117
|
-
## Project Context
|
|
118
|
-
Languages: ${langContext}${frameworkContext}
|
|
119
|
-
`;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Execute the full squad hiring flow:
|
|
124
|
-
* 1. Analyze the project
|
|
125
|
-
* 2. Create the squad in DB
|
|
126
|
-
* 3. Write SKILL.md files to disk
|
|
127
|
-
* 4. Add all members
|
|
128
|
-
*/
|
|
129
|
-
export async function hireSquad(params: {
|
|
130
|
-
projectPath: string;
|
|
131
|
-
repoUrl?: string;
|
|
132
|
-
name?: string;
|
|
133
|
-
universe?: string;
|
|
134
|
-
}): Promise<{ squadId: string; analysis: ProjectAnalysis; members: string[]; universe: string }> {
|
|
135
|
-
const log = logger();
|
|
136
|
-
|
|
137
|
-
// 1. Analyze
|
|
138
|
-
const analysis = analyzeProject(params.projectPath);
|
|
139
|
-
const squadName = params.name ?? analysis.name;
|
|
140
|
-
log.info({ squadName, analysis }, 'Project analyzed');
|
|
141
|
-
|
|
142
|
-
// 2. Build skill files
|
|
143
|
-
const skillsDir = join(homedir(), '.io', 'squads', squadName);
|
|
144
|
-
mkdirSync(skillsDir, { recursive: true });
|
|
145
|
-
|
|
146
|
-
const skillFiles: { role: string; content: string; veto: boolean }[] = [
|
|
147
|
-
{ role: 'team-lead', content: TEAM_LEAD_SKILL, veto: true },
|
|
148
|
-
{ role: 'scribe', content: SCRIBE_SKILL, veto: false },
|
|
149
|
-
{ role: 'qa-tester', content: QA_TESTER_SKILL, veto: true },
|
|
150
|
-
];
|
|
151
|
-
|
|
152
|
-
for (const specialist of analysis.suggestedSpecialists) {
|
|
153
|
-
skillFiles.push({
|
|
154
|
-
role: specialist,
|
|
155
|
-
content: generateSpecialistSkill(specialist, analysis),
|
|
156
|
-
veto: false,
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// 3. Generate character names from universe via LLM
|
|
161
|
-
const allRoles = skillFiles.map((f) => f.role);
|
|
162
|
-
const generated = await generateSquadNames(allRoles, params.universe);
|
|
163
|
-
log.info({ squadName, universe: generated.universe }, 'Universe names generated');
|
|
164
|
-
|
|
165
|
-
// 4. Create squad
|
|
166
|
-
const squad = await createSquad({
|
|
167
|
-
name: squadName,
|
|
168
|
-
projectPath: params.projectPath,
|
|
169
|
-
repoUrl: params.repoUrl,
|
|
170
|
-
universe: generated.universe,
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
// 5. Create wiki folder for this squad
|
|
174
|
-
ensureSquadWiki(squadName);
|
|
175
|
-
|
|
176
|
-
// 6. Write files and add members
|
|
177
|
-
const memberRoles: string[] = [];
|
|
178
|
-
for (const { role, content, veto } of skillFiles) {
|
|
179
|
-
const filePath = join(skillsDir, `${role}.skill.md`);
|
|
180
|
-
writeFileSync(filePath, content, 'utf-8');
|
|
181
|
-
|
|
182
|
-
const skill = parseSkillContent(content, filePath);
|
|
183
|
-
const assignment = generated.assignments.find((a) => a.role === role);
|
|
184
|
-
const displayName = assignment?.displayName ?? role;
|
|
185
|
-
const persona = assignment?.persona;
|
|
186
|
-
await addMember({ squadId: squad.id, skill, displayName, persona, isVetoMember: veto });
|
|
187
|
-
memberRoles.push(`${displayName} (${role})`);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
log.info(
|
|
191
|
-
{ squadId: squad.id, members: memberRoles, universe: generated.universe },
|
|
192
|
-
'Squad hired successfully',
|
|
193
|
-
);
|
|
194
|
-
return { squadId: squad.id, analysis, members: memberRoles, universe: generated.universe };
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Helpers
|
|
198
|
-
|
|
199
|
-
function safeReadDir(dir: string): string[] {
|
|
200
|
-
try {
|
|
201
|
-
if (!existsSync(dir)) return [];
|
|
202
|
-
return readdirSync(dir);
|
|
203
|
-
} catch {
|
|
204
|
-
return [];
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function safeReadJson(path: string): Record<string, unknown> | null {
|
|
209
|
-
try {
|
|
210
|
-
if (!existsSync(path)) return null;
|
|
211
|
-
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
212
|
-
} catch {
|
|
213
|
-
return null;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function titleCase(str: string): string {
|
|
218
|
-
return str
|
|
219
|
-
.split('-')
|
|
220
|
-
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
221
|
-
.join(' ');
|
|
222
|
-
}
|
package/src/squad/index.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
export { EventBus, getEventBus } from './event-bus.js';
|
|
2
|
-
export { parseSkillFile, parseSkillContent, compileSystemPrompt } from './skill-parser.js';
|
|
3
|
-
export type { SkillDefinition } from './skill-parser.js';
|
|
4
|
-
export { Agent } from './agent.js';
|
|
5
|
-
export type { AgentConfig } from './agent.js';
|
|
6
|
-
export {
|
|
7
|
-
createSquad,
|
|
8
|
-
addMember,
|
|
9
|
-
listSquads,
|
|
10
|
-
getSquadByName,
|
|
11
|
-
getSquadMembers,
|
|
12
|
-
disbandSquad,
|
|
13
|
-
bootSquad,
|
|
14
|
-
getSquadRuntime,
|
|
15
|
-
delegateToSquad,
|
|
16
|
-
} from './manager.js';
|
|
17
|
-
export { hireSquad, analyzeProject } from './hiring.js';
|