heyio 1.13.0 → 3.0.1
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 +162 -278
- package/dist/api/middleware/auth.d.ts +14 -0
- package/dist/api/middleware/auth.d.ts.map +1 -0
- package/dist/api/middleware/auth.js +66 -0
- package/dist/api/middleware/auth.js.map +1 -0
- package/dist/api/notifications.d.ts +14 -0
- package/dist/api/notifications.d.ts.map +1 -0
- package/dist/api/notifications.js +112 -0
- package/dist/api/notifications.js.map +1 -0
- package/dist/api/routes/activity.d.ts +3 -0
- package/dist/api/routes/activity.d.ts.map +1 -0
- package/dist/api/routes/activity.js +28 -0
- package/dist/api/routes/activity.js.map +1 -0
- package/dist/api/routes/attachments.d.ts +3 -0
- package/dist/api/routes/attachments.d.ts.map +1 -0
- package/dist/api/routes/attachments.js +83 -0
- package/dist/api/routes/attachments.js.map +1 -0
- package/dist/api/routes/config.d.ts +3 -0
- package/dist/api/routes/config.d.ts.map +1 -0
- package/dist/api/routes/config.js +106 -0
- package/dist/api/routes/config.js.map +1 -0
- package/dist/api/routes/conversations.d.ts +3 -0
- package/dist/api/routes/conversations.d.ts.map +1 -0
- package/dist/api/routes/conversations.js +69 -0
- package/dist/api/routes/conversations.js.map +1 -0
- package/dist/api/routes/health.d.ts +3 -0
- package/dist/api/routes/health.d.ts.map +1 -0
- package/dist/api/routes/health.js +16 -0
- package/dist/api/routes/health.js.map +1 -0
- package/dist/api/routes/inbox.d.ts +3 -0
- package/dist/api/routes/inbox.d.ts.map +1 -0
- package/dist/api/routes/inbox.js +88 -0
- package/dist/api/routes/inbox.js.map +1 -0
- package/dist/api/routes/schedules.d.ts +3 -0
- package/dist/api/routes/schedules.d.ts.map +1 -0
- package/dist/api/routes/schedules.js +96 -0
- package/dist/api/routes/schedules.js.map +1 -0
- package/dist/api/routes/skills.d.ts +2 -0
- package/dist/api/routes/skills.d.ts.map +1 -0
- package/dist/api/routes/skills.js +85 -0
- package/dist/api/routes/skills.js.map +1 -0
- package/dist/api/routes/squads.d.ts +3 -0
- package/dist/api/routes/squads.d.ts.map +1 -0
- package/dist/api/routes/squads.js +129 -0
- package/dist/api/routes/squads.js.map +1 -0
- package/dist/api/routes/usage.d.ts +3 -0
- package/dist/api/routes/usage.d.ts.map +1 -0
- package/dist/api/routes/usage.js +55 -0
- package/dist/api/routes/usage.js.map +1 -0
- package/dist/api/routes/wiki.d.ts +2 -0
- package/dist/api/routes/wiki.d.ts.map +1 -0
- package/dist/api/routes/wiki.js +43 -0
- package/dist/api/routes/wiki.js.map +1 -0
- package/dist/api/server.d.ts +7 -0
- package/dist/api/server.d.ts.map +1 -0
- package/dist/api/server.js +136 -634
- package/dist/api/server.js.map +1 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +2 -91
- package/dist/config.js.map +1 -0
- package/dist/copilot/client.d.ts +5 -0
- package/dist/copilot/client.d.ts.map +1 -0
- package/dist/copilot/client.js +19 -11
- package/dist/copilot/client.js.map +1 -0
- package/dist/copilot/health-monitor.d.ts +14 -0
- package/dist/copilot/health-monitor.d.ts.map +1 -0
- package/dist/copilot/health-monitor.js +70 -0
- package/dist/copilot/health-monitor.js.map +1 -0
- package/dist/copilot/orchestrator.d.ts +5 -0
- package/dist/copilot/orchestrator.d.ts.map +1 -0
- package/dist/copilot/orchestrator.js +127 -123
- package/dist/copilot/orchestrator.js.map +1 -0
- package/dist/copilot/tools.d.ts +49 -0
- package/dist/copilot/tools.d.ts.map +1 -0
- package/dist/copilot/tools.js +545 -321
- package/dist/copilot/tools.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +82 -26
- package/dist/index.js.map +1 -0
- package/dist/logging/logger.d.ts +6 -0
- package/dist/logging/logger.d.ts.map +1 -0
- package/dist/logging/logger.js +21 -0
- package/dist/logging/logger.js.map +1 -0
- package/dist/models/index.d.ts +6 -0
- package/dist/models/index.d.ts.map +1 -0
- package/dist/models/index.js +4 -0
- package/dist/models/index.js.map +1 -0
- package/dist/models/pricing.d.ts +25 -0
- package/dist/models/pricing.d.ts.map +1 -0
- package/dist/models/pricing.js +96 -0
- package/dist/models/pricing.js.map +1 -0
- package/dist/models/registry.d.ts +34 -0
- package/dist/models/registry.d.ts.map +1 -0
- package/dist/models/registry.js +109 -0
- package/dist/models/registry.js.map +1 -0
- package/dist/models/token-tracker.d.ts +40 -0
- package/dist/models/token-tracker.d.ts.map +1 -0
- package/dist/models/token-tracker.js +102 -0
- package/dist/models/token-tracker.js.map +1 -0
- package/dist/scheduler/engine.d.ts +9 -0
- package/dist/scheduler/engine.d.ts.map +1 -0
- package/dist/scheduler/engine.js +127 -0
- package/dist/scheduler/engine.js.map +1 -0
- package/dist/skills/index.d.ts +3 -0
- package/dist/skills/index.d.ts.map +1 -0
- package/dist/skills/index.js +2 -0
- package/dist/skills/index.js.map +1 -0
- package/dist/skills/store.d.ts +52 -0
- package/dist/skills/store.d.ts.map +1 -0
- package/dist/skills/store.js +148 -0
- package/dist/skills/store.js.map +1 -0
- package/dist/squad/agent.d.ts +46 -0
- package/dist/squad/agent.d.ts.map +1 -0
- package/dist/squad/agent.js +261 -0
- package/dist/squad/agent.js.map +1 -0
- package/dist/squad/autonomy.d.ts +16 -0
- package/dist/squad/autonomy.d.ts.map +1 -0
- package/dist/squad/autonomy.js +63 -0
- package/dist/squad/autonomy.js.map +1 -0
- package/dist/squad/event-bus.d.ts +22 -0
- package/dist/squad/event-bus.d.ts.map +1 -0
- package/dist/squad/event-bus.js +56 -0
- package/dist/squad/event-bus.js.map +1 -0
- package/dist/squad/execution/index.d.ts +12 -0
- package/dist/squad/execution/index.d.ts.map +1 -0
- package/dist/squad/execution/index.js +7 -0
- package/dist/squad/execution/index.js.map +1 -0
- package/dist/squad/execution/instance.d.ts +40 -0
- package/dist/squad/execution/instance.d.ts.map +1 -0
- package/dist/squad/execution/instance.js +138 -0
- package/dist/squad/execution/instance.js.map +1 -0
- package/dist/squad/execution/meeting.d.ts +25 -0
- package/dist/squad/execution/meeting.d.ts.map +1 -0
- package/dist/squad/execution/meeting.js +140 -0
- package/dist/squad/execution/meeting.js.map +1 -0
- package/dist/squad/execution/pr.d.ts +15 -0
- package/dist/squad/execution/pr.d.ts.map +1 -0
- package/dist/squad/execution/pr.js +93 -0
- package/dist/squad/execution/pr.js.map +1 -0
- package/dist/squad/execution/runner.d.ts +22 -0
- package/dist/squad/execution/runner.d.ts.map +1 -0
- package/dist/squad/execution/runner.js +68 -0
- package/dist/squad/execution/runner.js.map +1 -0
- package/dist/squad/execution/tasks.d.ts +11 -0
- package/dist/squad/execution/tasks.d.ts.map +1 -0
- package/dist/squad/execution/tasks.js +85 -0
- package/dist/squad/execution/tasks.js.map +1 -0
- package/dist/squad/execution/worktree.d.ts +26 -0
- package/dist/squad/execution/worktree.d.ts.map +1 -0
- package/dist/squad/execution/worktree.js +111 -0
- package/dist/squad/execution/worktree.js.map +1 -0
- package/dist/squad/hiring.d.ts +32 -0
- package/dist/squad/hiring.d.ts.map +1 -0
- package/dist/squad/hiring.js +200 -0
- package/dist/squad/hiring.js.map +1 -0
- package/dist/squad/index.d.ts +8 -0
- package/dist/squad/index.d.ts.map +1 -0
- package/dist/squad/index.js +6 -0
- package/dist/squad/index.js.map +1 -0
- package/dist/squad/manager.d.ts +48 -0
- package/dist/squad/manager.d.ts.map +1 -0
- package/dist/squad/manager.js +274 -0
- package/dist/squad/manager.js.map +1 -0
- package/dist/squad/name-generator.d.ts +16 -0
- package/dist/squad/name-generator.d.ts.map +1 -0
- package/dist/squad/name-generator.js +113 -0
- package/dist/squad/name-generator.js.map +1 -0
- package/dist/squad/roles/templates.d.ts +5 -0
- package/dist/squad/roles/templates.d.ts.map +1 -0
- package/dist/squad/roles/templates.js +102 -0
- package/dist/squad/roles/templates.js.map +1 -0
- package/dist/squad/skill-parser.d.ts +36 -0
- package/dist/squad/skill-parser.d.ts.map +1 -0
- package/dist/squad/skill-parser.js +83 -0
- package/dist/squad/skill-parser.js.map +1 -0
- package/dist/squad/source-resolver.d.ts +20 -0
- package/dist/squad/source-resolver.d.ts.map +1 -0
- package/dist/squad/source-resolver.js +52 -0
- package/dist/squad/source-resolver.js.map +1 -0
- package/dist/store/activity.d.ts +43 -0
- package/dist/store/activity.d.ts.map +1 -0
- package/dist/store/activity.js +131 -0
- package/dist/store/activity.js.map +1 -0
- package/dist/store/db.d.ts +5 -0
- package/dist/store/db.d.ts.map +1 -0
- package/dist/store/db.js +209 -248
- package/dist/store/db.js.map +1 -0
- package/dist/store/inbox.d.ts +53 -0
- package/dist/store/inbox.d.ts.map +1 -0
- package/dist/store/inbox.js +151 -0
- package/dist/store/inbox.js.map +1 -0
- package/dist/store/schedules.d.ts +53 -0
- package/dist/store/schedules.d.ts.map +1 -0
- package/dist/store/schedules.js +149 -54
- package/dist/store/schedules.js.map +1 -0
- package/dist/wiki/index.d.ts +3 -0
- package/dist/wiki/index.d.ts.map +1 -0
- package/dist/wiki/index.js +2 -0
- package/dist/wiki/index.js.map +1 -0
- package/dist/wiki/store.d.ts +49 -0
- package/dist/wiki/store.d.ts.map +1 -0
- package/dist/wiki/store.js +115 -0
- package/dist/wiki/store.js.map +1 -0
- package/node_modules/@io/shared/dist/config.d.ts +25 -0
- package/node_modules/@io/shared/dist/config.d.ts.map +1 -0
- package/node_modules/@io/shared/dist/config.js +47 -0
- package/node_modules/@io/shared/dist/config.js.map +1 -0
- package/node_modules/@io/shared/dist/constants.d.ts +13 -0
- package/node_modules/@io/shared/dist/constants.d.ts.map +1 -0
- package/node_modules/@io/shared/dist/constants.js +34 -0
- package/node_modules/@io/shared/dist/constants.js.map +1 -0
- package/node_modules/@io/shared/dist/index.d.ts +11 -0
- package/node_modules/@io/shared/dist/index.d.ts.map +1 -0
- package/node_modules/@io/shared/dist/index.js +3 -0
- package/node_modules/@io/shared/dist/index.js.map +1 -0
- package/node_modules/@io/shared/dist/types/agents.d.ts +3 -0
- package/node_modules/@io/shared/dist/types/agents.d.ts.map +1 -0
- package/node_modules/@io/shared/dist/types/agents.js +2 -0
- package/node_modules/@io/shared/dist/types/agents.js.map +1 -0
- package/node_modules/@io/shared/dist/types/api.d.ts +33 -0
- package/node_modules/@io/shared/dist/types/api.d.ts.map +1 -0
- package/node_modules/@io/shared/dist/types/api.js +2 -0
- package/node_modules/@io/shared/dist/types/api.js.map +1 -0
- package/node_modules/@io/shared/dist/types/attachments.d.ts +10 -0
- package/node_modules/@io/shared/dist/types/attachments.d.ts.map +1 -0
- package/node_modules/@io/shared/dist/types/attachments.js +2 -0
- package/node_modules/@io/shared/dist/types/attachments.js.map +1 -0
- package/node_modules/@io/shared/dist/types/events.d.ts +44 -0
- package/node_modules/@io/shared/dist/types/events.d.ts.map +1 -0
- package/node_modules/@io/shared/dist/types/events.js +2 -0
- package/node_modules/@io/shared/dist/types/events.js.map +1 -0
- package/node_modules/@io/shared/dist/types/messages.d.ts +15 -0
- package/node_modules/@io/shared/dist/types/messages.d.ts.map +1 -0
- package/node_modules/@io/shared/dist/types/messages.js +2 -0
- package/node_modules/@io/shared/dist/types/messages.js.map +1 -0
- package/node_modules/@io/shared/dist/types/squads.d.ts +43 -0
- package/node_modules/@io/shared/dist/types/squads.d.ts.map +1 -0
- package/node_modules/@io/shared/dist/types/squads.js +2 -0
- package/node_modules/@io/shared/dist/types/squads.js.map +1 -0
- package/node_modules/@io/shared/dist/types/tokens.d.ts +19 -0
- package/node_modules/@io/shared/dist/types/tokens.d.ts.map +1 -0
- package/node_modules/@io/shared/dist/types/tokens.js +2 -0
- package/node_modules/@io/shared/dist/types/tokens.js.map +1 -0
- package/node_modules/@io/shared/package.json +18 -0
- package/node_modules/@io/shared/src/config.ts +74 -0
- package/node_modules/@io/shared/src/constants.ts +36 -0
- package/node_modules/@io/shared/src/index.ts +37 -0
- package/node_modules/@io/shared/src/types/agents.ts +3 -0
- package/node_modules/@io/shared/src/types/api.ts +35 -0
- package/node_modules/@io/shared/src/types/attachments.ts +9 -0
- package/node_modules/@io/shared/src/types/events.ts +81 -0
- package/node_modules/@io/shared/src/types/messages.ts +15 -0
- package/node_modules/@io/shared/src/types/squads.ts +53 -0
- package/node_modules/@io/shared/src/types/tokens.ts +19 -0
- package/node_modules/@io/shared/tsconfig.json +9 -0
- package/node_modules/@io/shared/tsconfig.tsbuildinfo +1 -0
- package/package.json +56 -59
- package/src/api/middleware/auth.ts +76 -0
- package/src/api/notifications.ts +122 -0
- package/src/api/routes/activity.ts +29 -0
- package/src/api/routes/attachments.ts +93 -0
- package/src/api/routes/config.ts +115 -0
- package/src/api/routes/conversations.ts +87 -0
- package/src/api/routes/health.ts +18 -0
- package/src/api/routes/inbox.ts +98 -0
- package/src/api/routes/schedules.ts +121 -0
- package/src/api/routes/skills.ts +105 -0
- package/src/api/routes/squads.ts +145 -0
- package/src/api/routes/usage.ts +57 -0
- package/src/api/routes/wiki.ts +49 -0
- package/src/api/server.ts +186 -0
- package/src/config.ts +3 -0
- package/src/copilot/client.ts +42 -0
- package/src/copilot/health-monitor.ts +85 -0
- package/src/copilot/orchestrator.ts +222 -0
- package/src/copilot/tools.ts +707 -0
- package/src/index.ts +112 -0
- package/src/logging/logger.ts +26 -0
- package/src/models/index.ts +11 -0
- package/src/models/pricing.ts +121 -0
- package/src/models/registry.ts +131 -0
- package/src/models/token-tracker.ts +151 -0
- package/src/scheduler/engine.ts +146 -0
- package/src/skills/index.ts +13 -0
- package/src/skills/store.ts +188 -0
- package/src/squad/agent.ts +326 -0
- package/src/squad/autonomy.ts +78 -0
- package/src/squad/event-bus.ts +71 -0
- package/src/squad/execution/index.ts +17 -0
- package/src/squad/execution/instance.ts +186 -0
- package/src/squad/execution/meeting.ts +191 -0
- package/src/squad/execution/pr.ts +127 -0
- package/src/squad/execution/runner.ts +97 -0
- package/src/squad/execution/tasks.ts +111 -0
- package/src/squad/execution/worktree.ts +138 -0
- package/src/squad/hiring.ts +222 -0
- package/src/squad/index.ts +17 -0
- package/src/squad/manager.ts +337 -0
- package/src/squad/name-generator.ts +135 -0
- package/src/squad/roles/templates.ts +104 -0
- package/src/squad/skill-parser.ts +120 -0
- package/src/squad/source-resolver.ts +57 -0
- package/src/store/activity.ts +176 -0
- package/src/store/db.ts +237 -0
- package/src/store/inbox.ts +199 -0
- package/src/store/schedules.ts +199 -0
- package/src/wiki/index.ts +12 -0
- package/src/wiki/store.ts +139 -0
- package/tsconfig.json +9 -0
- package/LICENSE +0 -21
- package/dist/api/auth.js +0 -46
- package/dist/chat/attachments.js +0 -112
- package/dist/copilot/agents.js +0 -309
- package/dist/copilot/ceremonies.js +0 -174
- package/dist/copilot/gh-token.js +0 -64
- package/dist/copilot/io-scheduler.js +0 -79
- package/dist/copilot/model-router.js +0 -114
- package/dist/copilot/scheduler.js +0 -88
- package/dist/copilot/skills.js +0 -252
- package/dist/copilot/specialist-runner.js +0 -191
- package/dist/copilot/squad-tools.js +0 -258
- package/dist/copilot/system-message.js +0 -86
- package/dist/copilot/token-tracker.js +0 -98
- package/dist/copilot/trigger-schedule.js +0 -33
- package/dist/daemon.js +0 -67
- package/dist/logging.js +0 -27
- package/dist/mcp/config.js +0 -29
- package/dist/mcp/index.js +0 -3
- package/dist/mcp/registry.js +0 -42
- package/dist/notify.js +0 -25
- package/dist/paths.js +0 -17
- package/dist/setup.js +0 -35
- package/dist/store/agent-events.js +0 -19
- package/dist/store/audit-log.js +0 -71
- package/dist/store/conversations.js +0 -164
- package/dist/store/feed.js +0 -44
- package/dist/store/instances.js +0 -75
- package/dist/store/squad-colors.js +0 -23
- package/dist/store/squads.js +0 -60
- package/dist/store/tasks.js +0 -78
- package/dist/store/token-usage.js +0 -94
- package/dist/telegram/bot.js +0 -41
- package/dist/telegram/handlers.js +0 -42
- package/dist/watchdog.js +0 -37
- package/dist/wiki/backlinks.js +0 -51
- package/dist/wiki/fs.js +0 -108
- package/dist/wiki/search.js +0 -47
- package/web-dist/assets/AuditLogView-BzfjNXBT.js +0 -6
- package/web-dist/assets/ChatView-BdMukPKG.js +0 -1
- package/web-dist/assets/FeedView-BfPIabGr.js +0 -6
- package/web-dist/assets/HistoryView-BmEEk3Rs.js +0 -1
- package/web-dist/assets/LoginView-D7LrkeX7.js +0 -1
- package/web-dist/assets/McpView-BAP_ah3T.js +0 -1
- package/web-dist/assets/SchedulesView-CAtsUPCZ.js +0 -6
- package/web-dist/assets/SettingsView-BovjWZDa.js +0 -1
- package/web-dist/assets/SkillsView-DQSMM5LN.js +0 -16
- package/web-dist/assets/SquadDetailView-DBscu0m2.js +0 -26
- package/web-dist/assets/SquadHealthView-D686BuQo.js +0 -11
- package/web-dist/assets/SquadsView-AzMht2NJ.js +0 -6
- package/web-dist/assets/ToggleSwitch.vue_vue_type_script_setup_true_lang-DtShZAjW.js +0 -1
- package/web-dist/assets/UsageView-DiVn97aI.js +0 -16
- package/web-dist/assets/WikiView-Cn7KipkZ.js +0 -26
- package/web-dist/assets/api-D4mHJ3u0.js +0 -1
- package/web-dist/assets/arrow-left-D_qUNUWW.js +0 -6
- package/web-dist/assets/git-branch-DOM-orcl.js +0 -6
- package/web-dist/assets/index-Bo83B1LR.css +0 -1
- package/web-dist/assets/index-ELvnkQjd.js +0 -273
- package/web-dist/assets/pencil-CFsi7ufI.js +0 -6
- package/web-dist/assets/plus-BAzlGFd_.js +0 -6
- package/web-dist/assets/save-BmgCYJ1g.js +0 -6
- package/web-dist/assets/search-CS9zSIeW.js +0 -6
- package/web-dist/assets/squad-colors-B8B_Y-lz.js +0 -1
- package/web-dist/assets/trash-2-DLveUEsd.js +0 -6
- package/web-dist/assets/triangle-alert-lj4I30rL.js +0 -6
- package/web-dist/assets/x-CjXR97Fa.js +0 -6
- package/web-dist/favicon.svg +0 -10
- package/web-dist/index.html +0 -14
- package/web-dist/logo.svg +0 -10
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import { AUTONOMY_TIERS, type AutonomyConfig, type Squad, type SquadMember } from '@io/shared';
|
|
2
|
+
import type { AutonomyTier } from '@io/shared';
|
|
3
|
+
import { createChildLogger } from '../logging/logger.js';
|
|
4
|
+
import { getDatabase } from '../store/db.js';
|
|
5
|
+
import { Agent, type AgentConfig } from './agent.js';
|
|
6
|
+
import { getEventBus } from './event-bus.js';
|
|
7
|
+
import { type SkillDefinition, parseSkillContent, parseSkillFile } from './skill-parser.js';
|
|
8
|
+
|
|
9
|
+
const logger = () => createChildLogger('squad-manager');
|
|
10
|
+
|
|
11
|
+
// Active squads keyed by squad ID
|
|
12
|
+
const activeSquads = new Map<string, SquadRuntime>();
|
|
13
|
+
|
|
14
|
+
export interface SquadRuntime {
|
|
15
|
+
squad: Squad;
|
|
16
|
+
members: Map<string, Agent>; // keyed by role
|
|
17
|
+
skills: Map<string, SkillDefinition>; // keyed by role
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Create a new squad in the database and return it */
|
|
21
|
+
export async function createSquad(params: {
|
|
22
|
+
name: string;
|
|
23
|
+
projectPath: string;
|
|
24
|
+
repoUrl?: string;
|
|
25
|
+
universe?: string;
|
|
26
|
+
autonomyTier?: AutonomyTier;
|
|
27
|
+
}): Promise<Squad> {
|
|
28
|
+
const db = getDatabase();
|
|
29
|
+
const id = crypto.randomUUID();
|
|
30
|
+
const tier = params.autonomyTier ?? 'medium';
|
|
31
|
+
const autonomyConfig = AUTONOMY_TIERS[tier];
|
|
32
|
+
|
|
33
|
+
await db.execute({
|
|
34
|
+
sql: `INSERT INTO squads (id, name, project_path, repo_url, universe, autonomy_tier, autonomy_config, status)
|
|
35
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, 'active')`,
|
|
36
|
+
args: [
|
|
37
|
+
id,
|
|
38
|
+
params.name,
|
|
39
|
+
params.projectPath,
|
|
40
|
+
params.repoUrl ?? null,
|
|
41
|
+
params.universe ?? null,
|
|
42
|
+
tier,
|
|
43
|
+
JSON.stringify(autonomyConfig),
|
|
44
|
+
],
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const squad: Squad = {
|
|
48
|
+
id,
|
|
49
|
+
name: params.name,
|
|
50
|
+
projectPath: params.projectPath,
|
|
51
|
+
repoUrl: params.repoUrl,
|
|
52
|
+
universe: params.universe,
|
|
53
|
+
autonomyTier: tier,
|
|
54
|
+
autonomyConfig,
|
|
55
|
+
status: 'active',
|
|
56
|
+
createdAt: new Date(),
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
await getEventBus().emit({
|
|
60
|
+
id: crypto.randomUUID(),
|
|
61
|
+
timestamp: new Date(),
|
|
62
|
+
type: 'squad:created',
|
|
63
|
+
squadId: id,
|
|
64
|
+
squadName: params.name,
|
|
65
|
+
data: { projectPath: params.projectPath, tier },
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
logger().info({ squadId: id, name: params.name }, 'Squad created');
|
|
69
|
+
return squad;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Add a member to a squad */
|
|
73
|
+
export async function addMember(params: {
|
|
74
|
+
squadId: string;
|
|
75
|
+
skill: SkillDefinition;
|
|
76
|
+
displayName: string;
|
|
77
|
+
persona?: string;
|
|
78
|
+
isVetoMember?: boolean;
|
|
79
|
+
}): Promise<SquadMember> {
|
|
80
|
+
const db = getDatabase();
|
|
81
|
+
const id = crypto.randomUUID();
|
|
82
|
+
|
|
83
|
+
await db.execute({
|
|
84
|
+
sql: `INSERT INTO squad_members (id, squad_id, display_name, role_name, persona, skill_file_path, tools_allowed, is_veto_member, status)
|
|
85
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'active')`,
|
|
86
|
+
args: [
|
|
87
|
+
id,
|
|
88
|
+
params.squadId,
|
|
89
|
+
params.displayName,
|
|
90
|
+
params.skill.role,
|
|
91
|
+
params.persona ?? null,
|
|
92
|
+
params.skill.filePath,
|
|
93
|
+
JSON.stringify(params.skill.tools),
|
|
94
|
+
params.isVetoMember ? 1 : 0,
|
|
95
|
+
],
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const member: SquadMember = {
|
|
99
|
+
id,
|
|
100
|
+
squadId: params.squadId,
|
|
101
|
+
displayName: params.displayName,
|
|
102
|
+
roleName: params.skill.role,
|
|
103
|
+
persona: params.persona,
|
|
104
|
+
skillFilePath: params.skill.filePath,
|
|
105
|
+
toolsAllowed: params.skill.tools,
|
|
106
|
+
isVetoMember: params.isVetoMember ?? false,
|
|
107
|
+
status: 'active',
|
|
108
|
+
createdAt: new Date(),
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
return member;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** List all squads */
|
|
115
|
+
export async function listSquads(): Promise<Squad[]> {
|
|
116
|
+
const db = getDatabase();
|
|
117
|
+
const result = await db.execute("SELECT * FROM squads WHERE status = 'active'");
|
|
118
|
+
|
|
119
|
+
return result.rows.map((row) => ({
|
|
120
|
+
id: row.id as string,
|
|
121
|
+
name: row.name as string,
|
|
122
|
+
projectPath: row.project_path as string,
|
|
123
|
+
repoUrl: (row.repo_url as string) || undefined,
|
|
124
|
+
universe: (row.universe as string) || undefined,
|
|
125
|
+
autonomyTier: row.autonomy_tier as AutonomyTier,
|
|
126
|
+
autonomyConfig: JSON.parse((row.autonomy_config as string) || '{}') as AutonomyConfig,
|
|
127
|
+
status: row.status as Squad['status'],
|
|
128
|
+
createdAt: new Date(row.created_at as string),
|
|
129
|
+
}));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Get a squad by name */
|
|
133
|
+
export async function getSquadByName(name: string): Promise<Squad | null> {
|
|
134
|
+
const db = getDatabase();
|
|
135
|
+
const result = await db.execute({
|
|
136
|
+
sql: "SELECT * FROM squads WHERE name = ? AND status = 'active'",
|
|
137
|
+
args: [name],
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
if (result.rows.length === 0) return null;
|
|
141
|
+
const row = result.rows[0];
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
id: row.id as string,
|
|
145
|
+
name: row.name as string,
|
|
146
|
+
projectPath: row.project_path as string,
|
|
147
|
+
repoUrl: (row.repo_url as string) || undefined,
|
|
148
|
+
universe: (row.universe as string) || undefined,
|
|
149
|
+
autonomyTier: row.autonomy_tier as AutonomyTier,
|
|
150
|
+
autonomyConfig: JSON.parse((row.autonomy_config as string) || '{}') as AutonomyConfig,
|
|
151
|
+
status: row.status as Squad['status'],
|
|
152
|
+
createdAt: new Date(row.created_at as string),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Get squad members */
|
|
157
|
+
export async function getSquadMembers(squadId: string): Promise<SquadMember[]> {
|
|
158
|
+
const db = getDatabase();
|
|
159
|
+
const result = await db.execute({
|
|
160
|
+
sql: "SELECT * FROM squad_members WHERE squad_id = ? AND status = 'active'",
|
|
161
|
+
args: [squadId],
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
return result.rows.map((row) => ({
|
|
165
|
+
id: row.id as string,
|
|
166
|
+
squadId: row.squad_id as string,
|
|
167
|
+
displayName: (row.display_name as string) || (row.role_name as string),
|
|
168
|
+
roleName: row.role_name as string,
|
|
169
|
+
persona: (row.persona as string) || undefined,
|
|
170
|
+
skillFilePath: (row.skill_file_path as string) || undefined,
|
|
171
|
+
toolsAllowed: JSON.parse((row.tools_allowed as string) || '[]') as string[],
|
|
172
|
+
isVetoMember: Boolean(row.is_veto_member),
|
|
173
|
+
status: row.status as SquadMember['status'],
|
|
174
|
+
createdAt: new Date(row.created_at as string),
|
|
175
|
+
}));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Disband a squad */
|
|
179
|
+
export async function disbandSquad(squadId: string): Promise<void> {
|
|
180
|
+
const db = getDatabase();
|
|
181
|
+
await db.execute({
|
|
182
|
+
sql: "UPDATE squads SET status = 'disbanded' WHERE id = ?",
|
|
183
|
+
args: [squadId],
|
|
184
|
+
});
|
|
185
|
+
await db.execute({
|
|
186
|
+
sql: "UPDATE squad_members SET status = 'retired' WHERE squad_id = ?",
|
|
187
|
+
args: [squadId],
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// Destroy any running agents
|
|
191
|
+
const runtime = activeSquads.get(squadId);
|
|
192
|
+
if (runtime) {
|
|
193
|
+
for (const agent of runtime.members.values()) {
|
|
194
|
+
await agent.destroy().catch(() => {});
|
|
195
|
+
}
|
|
196
|
+
activeSquads.delete(squadId);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
await getEventBus().emit({
|
|
200
|
+
id: crypto.randomUUID(),
|
|
201
|
+
timestamp: new Date(),
|
|
202
|
+
type: 'squad:disbanded',
|
|
203
|
+
squadId,
|
|
204
|
+
squadName: '',
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
logger().info({ squadId }, 'Squad disbanded');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Retheme a squad with a new universe — updates display names and personas */
|
|
211
|
+
export async function rethemeSquad(
|
|
212
|
+
squadId: string,
|
|
213
|
+
newUniverse: string,
|
|
214
|
+
assignments: { role: string; displayName: string; persona: string }[],
|
|
215
|
+
): Promise<void> {
|
|
216
|
+
const db = getDatabase();
|
|
217
|
+
|
|
218
|
+
// Update squad universe
|
|
219
|
+
await db.execute({
|
|
220
|
+
sql: 'UPDATE squads SET universe = ? WHERE id = ?',
|
|
221
|
+
args: [newUniverse, squadId],
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// Update each member
|
|
225
|
+
for (const assignment of assignments) {
|
|
226
|
+
await db.execute({
|
|
227
|
+
sql: 'UPDATE squad_members SET display_name = ?, persona = ? WHERE squad_id = ? AND role_name = ?',
|
|
228
|
+
args: [assignment.displayName, assignment.persona, squadId, assignment.role],
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// If squad is running, destroy and reboot agents
|
|
233
|
+
const runtime = activeSquads.get(squadId);
|
|
234
|
+
if (runtime) {
|
|
235
|
+
for (const agent of runtime.members.values()) {
|
|
236
|
+
await agent.destroy().catch(() => {});
|
|
237
|
+
}
|
|
238
|
+
activeSquads.delete(squadId);
|
|
239
|
+
|
|
240
|
+
// Reload squad data and reboot
|
|
241
|
+
const squad = await getSquadById(squadId);
|
|
242
|
+
if (squad) {
|
|
243
|
+
await bootSquad(squad);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
logger().info({ squadId, universe: newUniverse }, 'Squad rethemed');
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** Get squad by ID */
|
|
251
|
+
export async function getSquadById(squadId: string): Promise<Squad | null> {
|
|
252
|
+
const db = getDatabase();
|
|
253
|
+
const result = await db.execute({
|
|
254
|
+
sql: 'SELECT * FROM squads WHERE id = ?',
|
|
255
|
+
args: [squadId],
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
if (result.rows.length === 0) return null;
|
|
259
|
+
const row = result.rows[0];
|
|
260
|
+
|
|
261
|
+
return {
|
|
262
|
+
id: row.id as string,
|
|
263
|
+
name: row.name as string,
|
|
264
|
+
projectPath: row.project_path as string,
|
|
265
|
+
repoUrl: (row.repo_url as string) || undefined,
|
|
266
|
+
universe: (row.universe as string) || undefined,
|
|
267
|
+
autonomyTier: row.autonomy_tier as AutonomyTier,
|
|
268
|
+
autonomyConfig: JSON.parse((row.autonomy_config as string) || '{}') as AutonomyConfig,
|
|
269
|
+
status: row.status as Squad['status'],
|
|
270
|
+
createdAt: new Date(row.created_at as string),
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** Boot a squad's agents (creates sessions for each member) */
|
|
275
|
+
export async function bootSquad(squad: Squad): Promise<SquadRuntime> {
|
|
276
|
+
const members = await getSquadMembers(squad.id);
|
|
277
|
+
const runtime: SquadRuntime = {
|
|
278
|
+
squad,
|
|
279
|
+
members: new Map(),
|
|
280
|
+
skills: new Map(),
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
const squadContext = `Project: ${squad.name}\nPath: ${squad.projectPath}${squad.repoUrl ? `\nRepo: ${squad.repoUrl}` : ''}`;
|
|
284
|
+
|
|
285
|
+
for (const member of members) {
|
|
286
|
+
let skill: SkillDefinition;
|
|
287
|
+
if (member.skillFilePath) {
|
|
288
|
+
skill = parseSkillFile(member.skillFilePath);
|
|
289
|
+
} else {
|
|
290
|
+
// Fallback: generate a minimal skill
|
|
291
|
+
skill = parseSkillContent(
|
|
292
|
+
`---\nrole: ${member.roleName}\ntools: []\nveto: false\n---\nYou are the ${member.roleName}.`,
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
runtime.skills.set(member.roleName, skill);
|
|
297
|
+
|
|
298
|
+
const agent = new Agent({
|
|
299
|
+
skill,
|
|
300
|
+
squadId: squad.id,
|
|
301
|
+
squadName: squad.name,
|
|
302
|
+
model: 'claude-opus-4.6',
|
|
303
|
+
identity: {
|
|
304
|
+
displayName: member.displayName,
|
|
305
|
+
persona: member.persona,
|
|
306
|
+
universe: squad.universe,
|
|
307
|
+
},
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
await agent.init(squadContext);
|
|
311
|
+
runtime.members.set(member.roleName, agent);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
activeSquads.set(squad.id, runtime);
|
|
315
|
+
logger().info({ squadId: squad.id, memberCount: members.length }, 'Squad booted');
|
|
316
|
+
return runtime;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/** Get a running squad's runtime */
|
|
320
|
+
export function getSquadRuntime(squadId: string): SquadRuntime | undefined {
|
|
321
|
+
return activeSquads.get(squadId);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** Delegate a message to a squad's team lead */
|
|
325
|
+
export async function delegateToSquad(squadId: string, message: string): Promise<string> {
|
|
326
|
+
const runtime = activeSquads.get(squadId);
|
|
327
|
+
if (!runtime) {
|
|
328
|
+
throw new Error(`Squad ${squadId} is not running`);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const teamLead = runtime.members.get('team-lead');
|
|
332
|
+
if (!teamLead) {
|
|
333
|
+
throw new Error(`Squad ${squadId} has no team lead`);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return teamLead.send(message);
|
|
337
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { getClient } from '../copilot/client.js';
|
|
2
|
+
import { createChildLogger } from '../logging/logger.js';
|
|
3
|
+
|
|
4
|
+
const logger = () => createChildLogger('name-generator');
|
|
5
|
+
|
|
6
|
+
export interface NameAssignment {
|
|
7
|
+
role: string;
|
|
8
|
+
displayName: string;
|
|
9
|
+
persona: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface GeneratedNames {
|
|
13
|
+
universe: string;
|
|
14
|
+
assignments: NameAssignment[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const NAME_GENERATION_PROMPT = `You are a creative naming assistant. Your job is to assign pop-culture character names to a team of AI agents based on their technical roles.
|
|
18
|
+
|
|
19
|
+
Rules:
|
|
20
|
+
- Each character name must be UNIQUE within the team
|
|
21
|
+
- Match character personalities to the technical role's nature (e.g. a methodical character for QA, a creative character for frontend)
|
|
22
|
+
- The persona should be 1-2 sentences describing how this character communicates — their tone, quirks, and style
|
|
23
|
+
- Keep personas fun but professional — they should enhance communication, not distract from technical work
|
|
24
|
+
- Return ONLY valid JSON, no markdown fencing
|
|
25
|
+
|
|
26
|
+
Respond with this exact JSON structure:
|
|
27
|
+
{
|
|
28
|
+
"universe": "<the universe name>",
|
|
29
|
+
"assignments": [
|
|
30
|
+
{ "role": "<technical role>", "displayName": "<character name>", "persona": "<1-2 sentence persona description>" }
|
|
31
|
+
]
|
|
32
|
+
}`;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Generate character names and persona blurbs for squad members using the LLM.
|
|
36
|
+
* If a universe is provided, names come from that universe.
|
|
37
|
+
* If not, the LLM picks a fun universe on its own.
|
|
38
|
+
*/
|
|
39
|
+
export async function generateSquadNames(
|
|
40
|
+
roles: string[],
|
|
41
|
+
universe?: string,
|
|
42
|
+
): Promise<GeneratedNames> {
|
|
43
|
+
const log = logger();
|
|
44
|
+
|
|
45
|
+
const userPrompt = universe
|
|
46
|
+
? `Assign character names from the "${universe}" universe to these team roles: ${roles.join(', ')}`
|
|
47
|
+
: `Pick a fun pop-culture universe of your choice and assign character names to these team roles: ${roles.join(', ')}`;
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const client = await getClient();
|
|
51
|
+
const session = await client.createSession({
|
|
52
|
+
systemMessage: { mode: 'replace' as const, content: NAME_GENERATION_PROMPT },
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
let accumulated = '';
|
|
56
|
+
const unsubDelta = session.on('assistant.message_delta', (event) => {
|
|
57
|
+
accumulated += event.data.deltaContent;
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
await session.sendAndWait({ prompt: userPrompt }, 60_000);
|
|
62
|
+
} finally {
|
|
63
|
+
unsubDelta();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Parse the JSON response
|
|
67
|
+
const parsed = extractJson(accumulated);
|
|
68
|
+
if (!parsed || !parsed.universe || !Array.isArray(parsed.assignments)) {
|
|
69
|
+
throw new Error('Invalid response structure from LLM');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Validate all roles are covered
|
|
73
|
+
const result: GeneratedNames = {
|
|
74
|
+
universe: parsed.universe as string,
|
|
75
|
+
assignments: [],
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
for (const role of roles) {
|
|
79
|
+
const match = (parsed.assignments as Record<string, string>[]).find(
|
|
80
|
+
(a) => a.role?.toLowerCase() === role.toLowerCase(),
|
|
81
|
+
);
|
|
82
|
+
if (match) {
|
|
83
|
+
result.assignments.push({
|
|
84
|
+
role,
|
|
85
|
+
displayName: match.displayName || match.display_name || role,
|
|
86
|
+
persona: match.persona || '',
|
|
87
|
+
});
|
|
88
|
+
} else {
|
|
89
|
+
// Role wasn't in LLM response — use role name as fallback
|
|
90
|
+
result.assignments.push({ role, displayName: role, persona: '' });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
log.info({ universe: result.universe, count: result.assignments.length }, 'Names generated');
|
|
95
|
+
return result;
|
|
96
|
+
} catch (err) {
|
|
97
|
+
log.error({ err, roles, universe }, 'Failed to generate names, falling back to role names');
|
|
98
|
+
return fallback(roles);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Fallback: use role names as display names with no persona */
|
|
103
|
+
function fallback(roles: string[]): GeneratedNames {
|
|
104
|
+
return {
|
|
105
|
+
universe: 'none',
|
|
106
|
+
assignments: roles.map((role) => ({ role, displayName: role, persona: '' })),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Extract JSON from LLM response (handles possible markdown fencing) */
|
|
111
|
+
function extractJson(text: string): Record<string, unknown> | null {
|
|
112
|
+
try {
|
|
113
|
+
return JSON.parse(text.trim());
|
|
114
|
+
} catch {
|
|
115
|
+
// Try extracting from markdown code fence
|
|
116
|
+
const match = text.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
|
|
117
|
+
if (match) {
|
|
118
|
+
try {
|
|
119
|
+
return JSON.parse(match[1].trim());
|
|
120
|
+
} catch {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// Try finding JSON object in text
|
|
125
|
+
const braceMatch = text.match(/\{[\s\S]*\}/);
|
|
126
|
+
if (braceMatch) {
|
|
127
|
+
try {
|
|
128
|
+
return JSON.parse(braceMatch[0]);
|
|
129
|
+
} catch {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/** Built-in SKILL.md templates for core squad roles */
|
|
2
|
+
|
|
3
|
+
export const TEAM_LEAD_SKILL = `---
|
|
4
|
+
role: team-lead
|
|
5
|
+
tools:
|
|
6
|
+
- read_file
|
|
7
|
+
- search_code
|
|
8
|
+
veto: true
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Team Lead
|
|
12
|
+
|
|
13
|
+
## Identity
|
|
14
|
+
You are the Team Lead — a senior engineering manager and project coordinator.
|
|
15
|
+
|
|
16
|
+
## Responsibilities
|
|
17
|
+
- Analyze incoming tasks and break them into actionable work items
|
|
18
|
+
- Assign tasks to the appropriate specialist agents
|
|
19
|
+
- Coordinate round-table meetings for complex decisions
|
|
20
|
+
- Review completed work before it becomes a PR
|
|
21
|
+
- Maintain project direction and consistency
|
|
22
|
+
- Report progress and blockers back to the orchestrator
|
|
23
|
+
|
|
24
|
+
## Boundaries
|
|
25
|
+
- You do NOT write or edit code directly
|
|
26
|
+
- You do NOT run commands
|
|
27
|
+
- You delegate all implementation work to specialists
|
|
28
|
+
- You focus on planning, coordination, and quality assurance
|
|
29
|
+
|
|
30
|
+
## Communication Style
|
|
31
|
+
- Be concise and structured in task descriptions
|
|
32
|
+
- Include acceptance criteria for every task
|
|
33
|
+
- Reference relevant files and code patterns when assigning work
|
|
34
|
+
`;
|
|
35
|
+
|
|
36
|
+
export const SCRIBE_SKILL = `---
|
|
37
|
+
role: scribe
|
|
38
|
+
tools:
|
|
39
|
+
- read_file
|
|
40
|
+
- edit_file
|
|
41
|
+
veto: false
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
# Scribe
|
|
45
|
+
|
|
46
|
+
## Identity
|
|
47
|
+
You are the Scribe — responsible for recording decisions, meeting notes, and project documentation.
|
|
48
|
+
|
|
49
|
+
## Responsibilities
|
|
50
|
+
- Record all key decisions made during meetings with rationale
|
|
51
|
+
- Maintain a decisions log that all team members can reference
|
|
52
|
+
- Document architectural choices and trade-offs
|
|
53
|
+
- Write clear commit messages and PR descriptions
|
|
54
|
+
- Update project README and docs when changes warrant it
|
|
55
|
+
|
|
56
|
+
## Boundaries
|
|
57
|
+
- You do NOT make technical decisions — you record them
|
|
58
|
+
- You do NOT modify source code — only documentation files
|
|
59
|
+
- You ask clarifying questions when decisions are ambiguous
|
|
60
|
+
- You ensure decisions are searchable and well-categorized
|
|
61
|
+
|
|
62
|
+
## Output Format
|
|
63
|
+
Decisions should follow this format:
|
|
64
|
+
- **Decision**: What was decided
|
|
65
|
+
- **Context**: Why this was needed
|
|
66
|
+
- **Rationale**: Why this option was chosen over alternatives
|
|
67
|
+
- **Consequences**: What this means going forward
|
|
68
|
+
`;
|
|
69
|
+
|
|
70
|
+
export const QA_TESTER_SKILL = `---
|
|
71
|
+
role: qa-tester
|
|
72
|
+
tools:
|
|
73
|
+
- read_file
|
|
74
|
+
- edit_file
|
|
75
|
+
- run_command
|
|
76
|
+
- search_code
|
|
77
|
+
veto: true
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
# QA / Test Engineer
|
|
81
|
+
|
|
82
|
+
## Identity
|
|
83
|
+
You are the QA/Test Engineer — the quality gate for all code changes.
|
|
84
|
+
|
|
85
|
+
## Responsibilities
|
|
86
|
+
- Write comprehensive tests for new features (unit, integration)
|
|
87
|
+
- Run existing test suites and report failures
|
|
88
|
+
- Review code for edge cases, error handling, and security issues
|
|
89
|
+
- Verify changes don't break existing functionality
|
|
90
|
+
- Block merges that don't meet quality standards
|
|
91
|
+
|
|
92
|
+
## Boundaries
|
|
93
|
+
- You focus on test code, not feature implementation
|
|
94
|
+
- You veto changes that reduce test coverage or break tests
|
|
95
|
+
- You report issues clearly with reproduction steps
|
|
96
|
+
- You suggest fixes but don't implement production features
|
|
97
|
+
|
|
98
|
+
## Quality Standards
|
|
99
|
+
- All new code must have corresponding tests
|
|
100
|
+
- No PR should reduce overall test coverage
|
|
101
|
+
- All tests must pass before approval
|
|
102
|
+
- Edge cases (empty inputs, null values, errors) must be covered
|
|
103
|
+
- Async code must have timeout and error handling tests
|
|
104
|
+
`;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import matter from 'gray-matter';
|
|
3
|
+
import { getActiveSkillsContent } from '../skills/index.js';
|
|
4
|
+
import { getPageListing, getSquadScopes } from '../wiki/index.js';
|
|
5
|
+
|
|
6
|
+
export interface SkillDefinition {
|
|
7
|
+
role: string;
|
|
8
|
+
tools: string[];
|
|
9
|
+
veto: boolean;
|
|
10
|
+
systemPrompt: string;
|
|
11
|
+
rawMarkdown: string;
|
|
12
|
+
filePath: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface SkillFrontmatter {
|
|
16
|
+
role: string;
|
|
17
|
+
tools?: string[];
|
|
18
|
+
veto?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Parse a SKILL.md file into a structured SkillDefinition.
|
|
23
|
+
* Format:
|
|
24
|
+
* ---
|
|
25
|
+
* role: react-developer
|
|
26
|
+
* tools:
|
|
27
|
+
* - edit_file
|
|
28
|
+
* - read_file
|
|
29
|
+
* veto: false
|
|
30
|
+
* ---
|
|
31
|
+
* # Role Name
|
|
32
|
+
* System prompt markdown content...
|
|
33
|
+
*/
|
|
34
|
+
export function parseSkillFile(filePath: string): SkillDefinition {
|
|
35
|
+
if (!existsSync(filePath)) {
|
|
36
|
+
throw new Error(`SKILL.md not found: ${filePath}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const raw = readFileSync(filePath, 'utf-8');
|
|
40
|
+
return parseSkillContent(raw, filePath);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Parse SKILL.md content string into a SkillDefinition.
|
|
45
|
+
*/
|
|
46
|
+
export function parseSkillContent(content: string, filePath = '<inline>'): SkillDefinition {
|
|
47
|
+
const { data, content: body } = matter(content);
|
|
48
|
+
const frontmatter = data as SkillFrontmatter;
|
|
49
|
+
|
|
50
|
+
if (!frontmatter.role) {
|
|
51
|
+
throw new Error(`SKILL.md missing required 'role' in frontmatter: ${filePath}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
role: frontmatter.role,
|
|
56
|
+
tools: frontmatter.tools ?? [],
|
|
57
|
+
veto: frontmatter.veto ?? false,
|
|
58
|
+
systemPrompt: body.trim(),
|
|
59
|
+
rawMarkdown: content,
|
|
60
|
+
filePath,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Compile a SkillDefinition into a full system message string for the LLM.
|
|
66
|
+
* Injects role identity, boundaries, and tool context.
|
|
67
|
+
*/
|
|
68
|
+
export async function compileSystemPrompt(
|
|
69
|
+
skill: SkillDefinition,
|
|
70
|
+
squadContext?: string,
|
|
71
|
+
squadName?: string,
|
|
72
|
+
squadId?: string,
|
|
73
|
+
identity?: { displayName: string; persona?: string; universe?: string },
|
|
74
|
+
): Promise<string> {
|
|
75
|
+
const parts: string[] = [];
|
|
76
|
+
|
|
77
|
+
if (identity?.displayName && identity.displayName !== skill.role) {
|
|
78
|
+
const intro = `You are ${identity.displayName}${identity.universe ? ` from ${identity.universe}` : ''}, the ${skill.role} agent in an IO squad.`;
|
|
79
|
+
parts.push(intro);
|
|
80
|
+
if (identity.persona) {
|
|
81
|
+
parts.push(identity.persona);
|
|
82
|
+
}
|
|
83
|
+
} else {
|
|
84
|
+
parts.push(`You are the ${skill.role} agent in an IO squad.`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (squadContext) {
|
|
88
|
+
parts.push(`\n## Squad Context\n${squadContext}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
parts.push(`\n## Your Role\n${skill.systemPrompt}`);
|
|
92
|
+
|
|
93
|
+
if (skill.tools.length > 0) {
|
|
94
|
+
parts.push(`\n## Allowed Tools\nYou may ONLY use: ${skill.tools.join(', ')}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (skill.veto) {
|
|
98
|
+
parts.push(
|
|
99
|
+
'\n## Veto Power\nYou have veto power in meetings. Use it when you identify critical issues.',
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Inject wiki page listing so agents know what knowledge is available
|
|
104
|
+
if (squadName) {
|
|
105
|
+
const wikiListing = getPageListing(getSquadScopes(squadName));
|
|
106
|
+
parts.push(
|
|
107
|
+
`\n## Wiki Knowledge\n${wikiListing}\n\nUse read_wiki to access page content. Use write_wiki to record important project knowledge.`,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Inject active skills for this squad
|
|
112
|
+
if (squadId) {
|
|
113
|
+
const skillsContent = await getActiveSkillsContent('squad', squadId);
|
|
114
|
+
if (skillsContent) {
|
|
115
|
+
parts.push(skillsContent);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return parts.join('\n');
|
|
120
|
+
}
|