heyio 4.2.3 → 4.2.4
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/daemon/cli.js +178 -308
- package/dist/daemon/index.js +179 -300
- package/package.json +1 -1
package/dist/daemon/cli.js
CHANGED
|
@@ -75,19 +75,18 @@ var init_api = __esm({
|
|
|
75
75
|
});
|
|
76
76
|
|
|
77
77
|
// packages/shared/dist/constants.js
|
|
78
|
-
var APP_NAME, APP_VERSION, API_PORT, API_HOST, DEFAULT_MODEL, SESSION_RESET_THRESHOLD, SCHEDULER_INTERVAL_MS, QA_MAX_REVISIONS,
|
|
78
|
+
var APP_NAME, APP_VERSION, API_PORT, API_HOST, DEFAULT_MODEL, SESSION_RESET_THRESHOLD, SCHEDULER_INTERVAL_MS, QA_MAX_REVISIONS, EVENT_NAMES;
|
|
79
79
|
var init_constants = __esm({
|
|
80
80
|
"packages/shared/dist/constants.js"() {
|
|
81
81
|
"use strict";
|
|
82
82
|
APP_NAME = "io";
|
|
83
|
-
APP_VERSION = "4.2.
|
|
83
|
+
APP_VERSION = "4.2.4";
|
|
84
84
|
API_PORT = 7777;
|
|
85
85
|
API_HOST = "0.0.0.0";
|
|
86
86
|
DEFAULT_MODEL = "gpt-4o";
|
|
87
87
|
SESSION_RESET_THRESHOLD = 50;
|
|
88
88
|
SCHEDULER_INTERVAL_MS = 6e4;
|
|
89
89
|
QA_MAX_REVISIONS = 3;
|
|
90
|
-
MANDATORY_ROLES = ["team-lead", "qa"];
|
|
91
90
|
EVENT_NAMES = {
|
|
92
91
|
SQUAD_CREATED: "squad.created",
|
|
93
92
|
SQUAD_DELETED: "squad.deleted",
|
|
@@ -68441,9 +68440,10 @@ function buildSystemPrompt(options2) {
|
|
|
68441
68440
|
const sections = [
|
|
68442
68441
|
"You are Io, a helpful AI orchestrator companion for IO v4.",
|
|
68443
68442
|
"You coordinate work across squads, wiki knowledge, installed skills, and direct execution tools.",
|
|
68444
|
-
"When a user asks about a project that has a squad, delegate to that squad. When no squad exists, you can do the work directly.",
|
|
68443
|
+
"When a user asks about a project that has a squad, delegate to that squad. When no squad exists, you can do the work directly or create a squad for ongoing project work.",
|
|
68445
68444
|
"Be practical, concise, and execution-oriented. Use tools when they help you produce a more accurate or durable result.",
|
|
68446
|
-
"## Delegation Rules\n- Prefer delegation for project-specific work when a matching squad exists.\n- If no squad exists for the target repository or project, use direct coding and knowledge tools yourself.\n-
|
|
68445
|
+
"## Delegation Rules\n- Prefer delegation for project-specific work when a matching squad exists.\n- If no squad exists for the target repository or project, use direct coding and knowledge tools yourself for one-off tasks.\n- For ongoing project work where no squad exists, create one with `create_squad` and then add members with `add_squad_member`.\n- Use the wiki to remember important information and recover prior context.\n- Use installed skills when they are relevant to the current request.",
|
|
68446
|
+
"## Squad Creation Guidelines\n- Use `analyze_repo` first to understand the repository's tech stack and structure.\n- Always add a `team-lead` member (coordinates planning, task assignment, and technical review) and a `qa` member (validates diffs, tests, and objective completion).\n- Add additional role-specific members based on the repository analysis (e.g. senior-frontend-engineer, senior-backend-engineer, senior-platform-engineer).\n- Write detailed system prompts for each member that define their expertise, responsibilities, and behavioral guidelines.\n- Use `remove_squad_member` and `add_squad_member` to restructure existing squads when the user requests changes.",
|
|
68447
68447
|
`## Squad Roster
|
|
68448
68448
|
${formatSquadRoster(options2.squads)}`,
|
|
68449
68449
|
formatOptionalSection("## Conversation Summary", options2.conversationSummary ?? ""),
|
|
@@ -69842,22 +69842,7 @@ var init_instance_context = __esm({
|
|
|
69842
69842
|
});
|
|
69843
69843
|
|
|
69844
69844
|
// packages/daemon/src/squad/roles.ts
|
|
69845
|
-
|
|
69846
|
-
const normalizedRole = role.trim() || "specialist";
|
|
69847
|
-
return `You are the ${normalizedRole} for the IO v4 daemon squad system.
|
|
69848
|
-
|
|
69849
|
-
${ROLE_GUIDELINES}
|
|
69850
|
-
|
|
69851
|
-
Role expectations:
|
|
69852
|
-
- Own work that naturally fits the ${normalizedRole} discipline.
|
|
69853
|
-
- Make changes that align with the repository's existing architecture, conventions, and tooling.
|
|
69854
|
-
- Collaborate with the Team Lead and QA by leaving behind clear implementation notes and validation evidence.
|
|
69855
|
-
- Escalate blockers or missing context early.
|
|
69856
|
-
|
|
69857
|
-
Repository context:
|
|
69858
|
-
${repoContext}`;
|
|
69859
|
-
}
|
|
69860
|
-
var ROLE_GUIDELINES, TEAM_LEAD_PROMPT, QA_PROMPT, ROLE_GENERATION_PROMPT;
|
|
69845
|
+
var ROLE_GUIDELINES, TEAM_LEAD_PROMPT, QA_PROMPT;
|
|
69861
69846
|
var init_roles = __esm({
|
|
69862
69847
|
"packages/daemon/src/squad/roles.ts"() {
|
|
69863
69848
|
"use strict";
|
|
@@ -69903,29 +69888,6 @@ Review rules:
|
|
|
69903
69888
|
- If approving, summarize why the work is acceptable.
|
|
69904
69889
|
- If rejecting, include concrete fixes or follow-up actions.
|
|
69905
69890
|
- Do not approve work that lacks evidence for important behavior changes.`;
|
|
69906
|
-
ROLE_GENERATION_PROMPT = `You are staffing an autonomous software squad for a GitHub repository.
|
|
69907
|
-
|
|
69908
|
-
Given repository analysis describing languages, frameworks, file structure, architectural patterns, and operational concerns, propose the smallest effective team that can deliver objectives safely.
|
|
69909
|
-
|
|
69910
|
-
Requirements:
|
|
69911
|
-
- Mandatory roles Team Lead and QA must always exist \u2014 do NOT include them in your response.
|
|
69912
|
-
- Suggest only additional roles that are clearly justified by the repository analysis.
|
|
69913
|
-
- Role names must reflect Senior or Principal seniority and be specific to the actual technology stack and work scope. Examples: "Principal React Engineer", "Senior DevOps Engineer", "Senior CI/CD Solutions Engineer", "Principal .NET API Engineer", "Senior Data Pipeline Engineer".
|
|
69914
|
-
- Do NOT use generic names like "Frontend Engineer" or "Backend Engineer". Be specific about the technology and domain.
|
|
69915
|
-
- Each role must have a short human-readable name and a concise description of responsibilities.
|
|
69916
|
-
- Avoid duplicate or overlapping roles.
|
|
69917
|
-
- Optimize for implementation, verification, and maintainability.
|
|
69918
|
-
|
|
69919
|
-
Return strict JSON in this shape:
|
|
69920
|
-
{
|
|
69921
|
-
"roles": [
|
|
69922
|
-
{
|
|
69923
|
-
"role": "principal-react-engineer",
|
|
69924
|
-
"name": "Principal React Engineer",
|
|
69925
|
-
"description": "Owns UI implementation, client state management, and browser-facing integration tests."
|
|
69926
|
-
}
|
|
69927
|
-
]
|
|
69928
|
-
}`;
|
|
69929
69891
|
}
|
|
69930
69892
|
});
|
|
69931
69893
|
|
|
@@ -70732,8 +70694,19 @@ var init_runner = __esm({
|
|
|
70732
70694
|
}
|
|
70733
70695
|
});
|
|
70734
70696
|
|
|
70735
|
-
// packages/daemon/src/squad
|
|
70736
|
-
import {
|
|
70697
|
+
// packages/daemon/src/orchestrator/tools/squad.ts
|
|
70698
|
+
import { exec as exec8 } from "node:child_process";
|
|
70699
|
+
import { mkdir as mkdir10, readFile as readFile9, readdir as readdir6, stat as stat4 } from "node:fs/promises";
|
|
70700
|
+
import { join as join15 } from "node:path";
|
|
70701
|
+
import { promisify as promisify8 } from "node:util";
|
|
70702
|
+
async function pathExists2(path) {
|
|
70703
|
+
try {
|
|
70704
|
+
await stat4(path);
|
|
70705
|
+
return true;
|
|
70706
|
+
} catch {
|
|
70707
|
+
return false;
|
|
70708
|
+
}
|
|
70709
|
+
}
|
|
70737
70710
|
function parseRepoUrl(repoUrl) {
|
|
70738
70711
|
const trimmed = repoUrl.trim();
|
|
70739
70712
|
const sshMatch = trimmed.match(/^git@[^:]+:([^/]+)\/([^/]+?)(?:\.git)?$/i);
|
|
@@ -70748,248 +70721,9 @@ function parseRepoUrl(repoUrl) {
|
|
|
70748
70721
|
}
|
|
70749
70722
|
return { owner: segments[0], name: segments[1] };
|
|
70750
70723
|
}
|
|
70751
|
-
function slugifyRole(role) {
|
|
70752
|
-
return role.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
70753
|
-
}
|
|
70754
70724
|
function titleCase(value) {
|
|
70755
70725
|
return value.split(/[-_\s]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
|
|
70756
70726
|
}
|
|
70757
|
-
function extractJsonObject4(content) {
|
|
70758
|
-
const fenced = content.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
70759
|
-
if (fenced?.[1]) {
|
|
70760
|
-
return fenced[1].trim();
|
|
70761
|
-
}
|
|
70762
|
-
const start = content.indexOf("{");
|
|
70763
|
-
const end = content.lastIndexOf("}");
|
|
70764
|
-
return start >= 0 && end > start ? content.slice(start, end + 1) : null;
|
|
70765
|
-
}
|
|
70766
|
-
function detectRolesFromAnalysis(repoAnalysis) {
|
|
70767
|
-
const analysis = repoAnalysis.toLowerCase();
|
|
70768
|
-
const roles = [];
|
|
70769
|
-
if (/(react|next|vue|angular|frontend|ui|xaml)/.test(analysis)) {
|
|
70770
|
-
roles.push({
|
|
70771
|
-
role: "senior-frontend-engineer",
|
|
70772
|
-
name: "Senior Frontend Engineer",
|
|
70773
|
-
description: "Implements user-facing experiences, UI state, and presentation-layer changes."
|
|
70774
|
-
});
|
|
70775
|
-
}
|
|
70776
|
-
if (/(node|express|api|backend|server|daemon|database|sql|postgres|sqlite)/.test(analysis)) {
|
|
70777
|
-
roles.push({
|
|
70778
|
-
role: "senior-backend-engineer",
|
|
70779
|
-
name: "Senior Backend Engineer",
|
|
70780
|
-
description: "Implements server-side logic, data access, integrations, and execution flow changes."
|
|
70781
|
-
});
|
|
70782
|
-
}
|
|
70783
|
-
if (/(infra|docker|kubernetes|deploy|ci|cd|workflow|github actions)/.test(analysis)) {
|
|
70784
|
-
roles.push({
|
|
70785
|
-
role: "senior-platform-engineer",
|
|
70786
|
-
name: "Senior Platform Engineer",
|
|
70787
|
-
description: "Owns automation, pipelines, environments, and operational tooling."
|
|
70788
|
-
});
|
|
70789
|
-
}
|
|
70790
|
-
if (/(security|auth|oauth|secret|policy)/.test(analysis)) {
|
|
70791
|
-
roles.push({
|
|
70792
|
-
role: "senior-security-engineer",
|
|
70793
|
-
name: "Senior Security Engineer",
|
|
70794
|
-
description: "Reviews authentication, authorization, secrets handling, and security-sensitive changes."
|
|
70795
|
-
});
|
|
70796
|
-
}
|
|
70797
|
-
return roles;
|
|
70798
|
-
}
|
|
70799
|
-
function dedupeRoles(roles) {
|
|
70800
|
-
const seen = /* @__PURE__ */ new Set();
|
|
70801
|
-
const output2 = [];
|
|
70802
|
-
for (const role of roles) {
|
|
70803
|
-
const normalizedRole = slugifyRole(role.role);
|
|
70804
|
-
if (!normalizedRole || seen.has(normalizedRole) || MANDATORY_ROLES.includes(normalizedRole)) {
|
|
70805
|
-
continue;
|
|
70806
|
-
}
|
|
70807
|
-
seen.add(normalizedRole);
|
|
70808
|
-
output2.push({
|
|
70809
|
-
role: normalizedRole,
|
|
70810
|
-
name: role.name?.trim() || titleCase(normalizedRole),
|
|
70811
|
-
description: role.description?.trim() || `${titleCase(normalizedRole)} responsibilities`
|
|
70812
|
-
});
|
|
70813
|
-
}
|
|
70814
|
-
return output2;
|
|
70815
|
-
}
|
|
70816
|
-
function buildMandatoryRoles() {
|
|
70817
|
-
return [
|
|
70818
|
-
{
|
|
70819
|
-
role: "team-lead",
|
|
70820
|
-
name: "Team Lead",
|
|
70821
|
-
description: "Coordinates planning, task assignment, sequencing, and final technical review."
|
|
70822
|
-
},
|
|
70823
|
-
{
|
|
70824
|
-
role: "qa",
|
|
70825
|
-
name: "QA Reviewer",
|
|
70826
|
-
description: "Validates diffs, test evidence, and objective completion before delivery."
|
|
70827
|
-
}
|
|
70828
|
-
];
|
|
70829
|
-
}
|
|
70830
|
-
function modelForRole(role) {
|
|
70831
|
-
const normalized = slugifyRole(role);
|
|
70832
|
-
if (normalized === "team-lead") {
|
|
70833
|
-
return DEFAULT_MODEL;
|
|
70834
|
-
}
|
|
70835
|
-
return DEFAULT_MODEL;
|
|
70836
|
-
}
|
|
70837
|
-
function systemPromptForRole(role, repoContext) {
|
|
70838
|
-
const normalized = slugifyRole(role);
|
|
70839
|
-
if (normalized === "team-lead") {
|
|
70840
|
-
return `${TEAM_LEAD_PROMPT}
|
|
70841
|
-
|
|
70842
|
-
Repository context:
|
|
70843
|
-
${repoContext}`;
|
|
70844
|
-
}
|
|
70845
|
-
if (normalized === "qa") {
|
|
70846
|
-
return `${QA_PROMPT}
|
|
70847
|
-
|
|
70848
|
-
Repository context:
|
|
70849
|
-
${repoContext}`;
|
|
70850
|
-
}
|
|
70851
|
-
return generateRolePrompt(normalized, repoContext);
|
|
70852
|
-
}
|
|
70853
|
-
async function proposeSquadComposition(repoUrl, repoAnalysis, context) {
|
|
70854
|
-
const mandatory = buildMandatoryRoles();
|
|
70855
|
-
const fallbackAdditional = dedupeRoles(detectRolesFromAnalysis(repoAnalysis));
|
|
70856
|
-
const contextSection = context ? `
|
|
70857
|
-
|
|
70858
|
-
Additional context from user:
|
|
70859
|
-
${context}` : "";
|
|
70860
|
-
const prompt = `Repository URL: ${repoUrl}
|
|
70861
|
-
|
|
70862
|
-
Repository analysis:
|
|
70863
|
-
${repoAnalysis}${contextSection}
|
|
70864
|
-
|
|
70865
|
-
${ROLE_GENERATION_PROMPT}`;
|
|
70866
|
-
let client2 = null;
|
|
70867
|
-
try {
|
|
70868
|
-
client2 = new CopilotClient6();
|
|
70869
|
-
await client2.start();
|
|
70870
|
-
const session = await client2.createSession({
|
|
70871
|
-
model: DEFAULT_MODEL,
|
|
70872
|
-
onPermissionRequest: approveAll6,
|
|
70873
|
-
systemMessage: {
|
|
70874
|
-
content: ROLE_GENERATION_PROMPT
|
|
70875
|
-
}
|
|
70876
|
-
});
|
|
70877
|
-
try {
|
|
70878
|
-
const response = await session.sendAndWait({ prompt }, 6e4);
|
|
70879
|
-
const content = response?.data.content?.trim() ?? "";
|
|
70880
|
-
const json2 = extractJsonObject4(content);
|
|
70881
|
-
if (!json2) {
|
|
70882
|
-
return { roles: [...mandatory, ...fallbackAdditional] };
|
|
70883
|
-
}
|
|
70884
|
-
const parsed = JSON.parse(json2);
|
|
70885
|
-
return {
|
|
70886
|
-
roles: [...mandatory, ...dedupeRoles(parsed.roles ?? fallbackAdditional)]
|
|
70887
|
-
};
|
|
70888
|
-
} finally {
|
|
70889
|
-
await session.disconnect().catch(() => void 0);
|
|
70890
|
-
}
|
|
70891
|
-
} catch {
|
|
70892
|
-
return { roles: [...mandatory, ...fallbackAdditional] };
|
|
70893
|
-
} finally {
|
|
70894
|
-
if (client2) {
|
|
70895
|
-
await client2.stop().catch(() => []);
|
|
70896
|
-
}
|
|
70897
|
-
}
|
|
70898
|
-
}
|
|
70899
|
-
async function hireSquad(repoUrl, composition, config2) {
|
|
70900
|
-
const parsedRepo = parseRepoUrl(repoUrl);
|
|
70901
|
-
const repoContext = `Repository: ${parsedRepo.owner}/${parsedRepo.name}
|
|
70902
|
-
Configured MCP servers: ${config2.mcpServers.join(", ") || "none"}
|
|
70903
|
-
QA max revisions: ${config2.maxRevisions || QA_MAX_REVISIONS}`;
|
|
70904
|
-
const existingSquad = await getSquadByRepo(parsedRepo.owner, parsedRepo.name);
|
|
70905
|
-
const normalizedConfig = {
|
|
70906
|
-
prMode: config2.prMode,
|
|
70907
|
-
mcpServers: [...config2.mcpServers],
|
|
70908
|
-
maxRevisions: config2.maxRevisions || QA_MAX_REVISIONS
|
|
70909
|
-
};
|
|
70910
|
-
const desiredRoles = composition.roles.length > 0 ? composition.roles : buildMandatoryRoles();
|
|
70911
|
-
const membersToCreate = desiredRoles.map((role) => ({
|
|
70912
|
-
role: slugifyRole(role.role),
|
|
70913
|
-
name: role.name?.trim() || titleCase(role.role),
|
|
70914
|
-
description: role.description?.trim() || `${titleCase(role.role)} responsibilities`
|
|
70915
|
-
}));
|
|
70916
|
-
let squadId;
|
|
70917
|
-
if (existingSquad) {
|
|
70918
|
-
const updatedSquad = await updateSquad(existingSquad.id, {
|
|
70919
|
-
name: `${titleCase(parsedRepo.name)} Squad`,
|
|
70920
|
-
config: normalizedConfig,
|
|
70921
|
-
status: existingSquad.status
|
|
70922
|
-
});
|
|
70923
|
-
if (!updatedSquad) {
|
|
70924
|
-
throw new Error(`Unable to update existing squad for ${repoUrl}`);
|
|
70925
|
-
}
|
|
70926
|
-
squadId = updatedSquad.id;
|
|
70927
|
-
const existingMembers = await getMembers(updatedSquad.id);
|
|
70928
|
-
await Promise.all(existingMembers.map((member) => removeMember(member.id)));
|
|
70929
|
-
} else {
|
|
70930
|
-
const squad = await createSquad({
|
|
70931
|
-
name: `${titleCase(parsedRepo.name)} Squad`,
|
|
70932
|
-
repoUrl,
|
|
70933
|
-
repoOwner: parsedRepo.owner,
|
|
70934
|
-
repoName: parsedRepo.name,
|
|
70935
|
-
status: "active",
|
|
70936
|
-
config: normalizedConfig
|
|
70937
|
-
});
|
|
70938
|
-
squadId = squad.id;
|
|
70939
|
-
}
|
|
70940
|
-
const members = [];
|
|
70941
|
-
for (const memberDefinition of membersToCreate) {
|
|
70942
|
-
const member = await addMember(squadId, {
|
|
70943
|
-
role: memberDefinition.role,
|
|
70944
|
-
name: memberDefinition.name,
|
|
70945
|
-
systemPrompt: systemPromptForRole(
|
|
70946
|
-
memberDefinition.role,
|
|
70947
|
-
`${repoContext}
|
|
70948
|
-
Role: ${memberDefinition.description}`
|
|
70949
|
-
),
|
|
70950
|
-
model: modelForRole(memberDefinition.role)
|
|
70951
|
-
});
|
|
70952
|
-
members.push(member);
|
|
70953
|
-
}
|
|
70954
|
-
const fullSquad = await getSquad(squadId);
|
|
70955
|
-
if (!fullSquad) {
|
|
70956
|
-
throw new Error(`Unable to load squad ${squadId} after hiring`);
|
|
70957
|
-
}
|
|
70958
|
-
eventBus.emit(existingSquad ? EVENT_NAMES.SQUAD_UPDATED : EVENT_NAMES.SQUAD_CREATED, {
|
|
70959
|
-
squad: fullSquad
|
|
70960
|
-
});
|
|
70961
|
-
return { squad: fullSquad, members: fullSquad.members };
|
|
70962
|
-
}
|
|
70963
|
-
var init_hiring = __esm({
|
|
70964
|
-
"packages/daemon/src/squad/hiring.ts"() {
|
|
70965
|
-
"use strict";
|
|
70966
|
-
init_dist();
|
|
70967
|
-
init_event_bus();
|
|
70968
|
-
init_store2();
|
|
70969
|
-
init_roles();
|
|
70970
|
-
}
|
|
70971
|
-
});
|
|
70972
|
-
|
|
70973
|
-
// packages/daemon/src/orchestrator/tools/squad.ts
|
|
70974
|
-
import { exec as exec8 } from "node:child_process";
|
|
70975
|
-
import { mkdir as mkdir10, readFile as readFile9, readdir as readdir6, stat as stat4 } from "node:fs/promises";
|
|
70976
|
-
import { join as join15 } from "node:path";
|
|
70977
|
-
import { promisify as promisify8 } from "node:util";
|
|
70978
|
-
async function pathExists2(path) {
|
|
70979
|
-
try {
|
|
70980
|
-
await stat4(path);
|
|
70981
|
-
return true;
|
|
70982
|
-
} catch {
|
|
70983
|
-
return false;
|
|
70984
|
-
}
|
|
70985
|
-
}
|
|
70986
|
-
function getDefaultSquadConfig(_config) {
|
|
70987
|
-
return {
|
|
70988
|
-
prMode: "draft-pr",
|
|
70989
|
-
mcpServers: [],
|
|
70990
|
-
maxRevisions: QA_MAX_REVISIONS
|
|
70991
|
-
};
|
|
70992
|
-
}
|
|
70993
70727
|
async function readManifests(rootPath, rootLabel) {
|
|
70994
70728
|
const lines = [];
|
|
70995
70729
|
for (const manifest of MANIFEST_FILES) {
|
|
@@ -71154,18 +70888,98 @@ async function startAndExecuteInstance(instanceId, squad, objectiveId) {
|
|
|
71154
70888
|
await processQueue2(squad.id);
|
|
71155
70889
|
}
|
|
71156
70890
|
}
|
|
71157
|
-
async function
|
|
71158
|
-
const { repoUrl,
|
|
71159
|
-
const
|
|
70891
|
+
async function handleCreateSquad(rawArgs) {
|
|
70892
|
+
const { repoUrl, name, config: config2 } = createSquadSchema.parse(rawArgs);
|
|
70893
|
+
const parsedRepo = parseRepoUrl(repoUrl);
|
|
70894
|
+
const existingSquad = await getSquadByRepo(parsedRepo.owner, parsedRepo.name);
|
|
70895
|
+
if (existingSquad) {
|
|
70896
|
+
return {
|
|
70897
|
+
message: `A squad already exists for ${parsedRepo.owner}/${parsedRepo.name}.`,
|
|
70898
|
+
squad: existingSquad
|
|
70899
|
+
};
|
|
70900
|
+
}
|
|
70901
|
+
const squadName = name || `${titleCase(parsedRepo.name)} Squad`;
|
|
70902
|
+
const squadConfig = {
|
|
70903
|
+
prMode: config2?.prMode ?? "draft-pr",
|
|
70904
|
+
mcpServers: config2?.mcpServers ?? [],
|
|
70905
|
+
maxRevisions: config2?.maxRevisions ?? QA_MAX_REVISIONS
|
|
70906
|
+
};
|
|
70907
|
+
const squad = await createSquad({
|
|
70908
|
+
name: squadName,
|
|
71160
70909
|
repoUrl,
|
|
71161
|
-
|
|
71162
|
-
|
|
71163
|
-
|
|
71164
|
-
|
|
70910
|
+
repoOwner: parsedRepo.owner,
|
|
70911
|
+
repoName: parsedRepo.name,
|
|
70912
|
+
status: "active",
|
|
70913
|
+
config: squadConfig
|
|
70914
|
+
});
|
|
70915
|
+
eventBus.emit(EVENT_NAMES.SQUAD_CREATED, { squad });
|
|
70916
|
+
return {
|
|
70917
|
+
message: `Squad "${squad.name}" created for ${parsedRepo.owner}/${parsedRepo.name}. Add members with add_squad_member.`,
|
|
70918
|
+
squad
|
|
70919
|
+
};
|
|
70920
|
+
}
|
|
70921
|
+
async function handleAddSquadMember(rawArgs) {
|
|
70922
|
+
const { squadId, role, name, systemPrompt, model } = addSquadMemberSchema.parse(rawArgs);
|
|
70923
|
+
const squad = await getSquad(squadId);
|
|
70924
|
+
if (!squad) {
|
|
70925
|
+
throw new Error(`Squad ${squadId} was not found.`);
|
|
70926
|
+
}
|
|
70927
|
+
const member = await addMember(squadId, {
|
|
70928
|
+
role,
|
|
70929
|
+
name,
|
|
70930
|
+
systemPrompt,
|
|
70931
|
+
model: model ?? null
|
|
70932
|
+
});
|
|
70933
|
+
eventBus.emit(EVENT_NAMES.SQUAD_MEMBER_UPDATED, { squadId, member });
|
|
71165
70934
|
return {
|
|
71166
|
-
message: `
|
|
71167
|
-
|
|
71168
|
-
|
|
70935
|
+
message: `Added member "${member.name}" (${member.role}) to squad "${squad.name}".`,
|
|
70936
|
+
member
|
|
70937
|
+
};
|
|
70938
|
+
}
|
|
70939
|
+
async function handleRemoveSquadMember(rawArgs) {
|
|
70940
|
+
const { squadId, memberId } = removeSquadMemberSchema.parse(rawArgs);
|
|
70941
|
+
const squad = await getSquad(squadId);
|
|
70942
|
+
if (!squad) {
|
|
70943
|
+
throw new Error(`Squad ${squadId} was not found.`);
|
|
70944
|
+
}
|
|
70945
|
+
const member = await getMember(memberId);
|
|
70946
|
+
if (!member || member.squadId !== squadId) {
|
|
70947
|
+
throw new Error(`Member ${memberId} was not found in squad ${squadId}.`);
|
|
70948
|
+
}
|
|
70949
|
+
await removeMember(memberId);
|
|
70950
|
+
eventBus.emit(EVENT_NAMES.SQUAD_UPDATED, { squad });
|
|
70951
|
+
return {
|
|
70952
|
+
message: `Removed member "${member.name}" (${member.role}) from squad "${squad.name}".`,
|
|
70953
|
+
memberId
|
|
70954
|
+
};
|
|
70955
|
+
}
|
|
70956
|
+
async function handleUpdateSquadConfig(rawArgs) {
|
|
70957
|
+
const { squadId, config: config2 } = updateSquadConfigSchema.parse(rawArgs);
|
|
70958
|
+
const squad = await getSquad(squadId);
|
|
70959
|
+
if (!squad) {
|
|
70960
|
+
throw new Error(`Squad ${squadId} was not found.`);
|
|
70961
|
+
}
|
|
70962
|
+
const mergedConfig = {
|
|
70963
|
+
prMode: config2.prMode ?? squad.config.prMode,
|
|
70964
|
+
mcpServers: config2.mcpServers ?? squad.config.mcpServers,
|
|
70965
|
+
maxRevisions: config2.maxRevisions ?? squad.config.maxRevisions
|
|
70966
|
+
};
|
|
70967
|
+
const updated = await updateSquad(squadId, { config: mergedConfig });
|
|
70968
|
+
if (!updated) {
|
|
70969
|
+
throw new Error(`Failed to update squad ${squadId}.`);
|
|
70970
|
+
}
|
|
70971
|
+
eventBus.emit(EVENT_NAMES.SQUAD_UPDATED, { squad: updated });
|
|
70972
|
+
return {
|
|
70973
|
+
message: `Updated config for squad "${updated.name}".`,
|
|
70974
|
+
squad: updated
|
|
70975
|
+
};
|
|
70976
|
+
}
|
|
70977
|
+
async function handleAnalyzeRepo(rawArgs) {
|
|
70978
|
+
const { repoUrl, scanPaths } = analyzeRepoSchema.parse(rawArgs);
|
|
70979
|
+
const analysis = await buildRepoAnalysis(repoUrl, scanPaths);
|
|
70980
|
+
return {
|
|
70981
|
+
message: "Repository analysis complete.",
|
|
70982
|
+
analysis
|
|
71169
70983
|
};
|
|
71170
70984
|
}
|
|
71171
70985
|
async function handleFireSquad(rawArgs) {
|
|
@@ -71237,8 +71051,16 @@ async function handleUpdateSquadMember(rawArgs) {
|
|
|
71237
71051
|
function createSquadToolExecutor(config2) {
|
|
71238
71052
|
return async (toolName, rawArgs) => {
|
|
71239
71053
|
switch (toolName) {
|
|
71240
|
-
case "
|
|
71241
|
-
return
|
|
71054
|
+
case "create_squad":
|
|
71055
|
+
return handleCreateSquad(rawArgs);
|
|
71056
|
+
case "add_squad_member":
|
|
71057
|
+
return handleAddSquadMember(rawArgs);
|
|
71058
|
+
case "remove_squad_member":
|
|
71059
|
+
return handleRemoveSquadMember(rawArgs);
|
|
71060
|
+
case "update_squad_config":
|
|
71061
|
+
return handleUpdateSquadConfig(rawArgs);
|
|
71062
|
+
case "analyze_repo":
|
|
71063
|
+
return handleAnalyzeRepo(rawArgs);
|
|
71242
71064
|
case "fire_squad":
|
|
71243
71065
|
return handleFireSquad(rawArgs);
|
|
71244
71066
|
case "list_squads": {
|
|
@@ -71277,7 +71099,7 @@ function createSquadToolExecutor(config2) {
|
|
|
71277
71099
|
}
|
|
71278
71100
|
};
|
|
71279
71101
|
}
|
|
71280
|
-
var execAsync8,
|
|
71102
|
+
var execAsync8, createSquadSchema, addSquadMemberSchema, removeSquadMemberSchema, updateSquadConfigSchema, analyzeRepoSchema, squadIdSchema, delegateToSquadSchema, renameSquadSchema, updateSquadMemberSchema, squadToolDefinitions, MANIFEST_FILES, SRC_DIR_NAMES, README_CANDIDATES2;
|
|
71281
71103
|
var init_squad2 = __esm({
|
|
71282
71104
|
"packages/daemon/src/orchestrator/tools/squad.ts"() {
|
|
71283
71105
|
"use strict";
|
|
@@ -71287,17 +71109,41 @@ var init_squad2 = __esm({
|
|
|
71287
71109
|
init_event_bus();
|
|
71288
71110
|
init_instances2();
|
|
71289
71111
|
init_runner();
|
|
71290
|
-
init_hiring();
|
|
71291
71112
|
init_manager2();
|
|
71292
71113
|
init_store2();
|
|
71293
71114
|
execAsync8 = promisify8(exec8);
|
|
71294
|
-
|
|
71295
|
-
repoUrl: external_exports.string().trim().min(1).describe("The repository URL to
|
|
71296
|
-
|
|
71297
|
-
|
|
71298
|
-
|
|
71115
|
+
createSquadSchema = external_exports.object({
|
|
71116
|
+
repoUrl: external_exports.string().trim().min(1).describe("The repository URL to create a squad for"),
|
|
71117
|
+
name: external_exports.string().trim().optional().describe("Squad name. Defaults to '<RepoName> Squad' if not provided."),
|
|
71118
|
+
config: external_exports.object({
|
|
71119
|
+
prMode: external_exports.enum(["draft-pr", "branch-only", "ready-pr", "auto-merge"]).optional().describe("PR creation mode. Defaults to 'draft-pr'."),
|
|
71120
|
+
mcpServers: external_exports.array(external_exports.string()).optional().describe("MCP server URLs to configure for the squad."),
|
|
71121
|
+
maxRevisions: external_exports.number().optional().describe("Max QA revision cycles. Defaults to system default.")
|
|
71122
|
+
}).optional().describe("Squad configuration. All fields are optional with sensible defaults.")
|
|
71123
|
+
});
|
|
71124
|
+
addSquadMemberSchema = external_exports.object({
|
|
71125
|
+
squadId: external_exports.string().trim().min(1),
|
|
71126
|
+
role: external_exports.string().trim().min(1).describe("Slug-style role identifier (e.g. 'senior-frontend-engineer', 'team-lead')"),
|
|
71127
|
+
name: external_exports.string().trim().min(1).describe("Display name for the member (e.g. 'Senior Frontend Engineer')"),
|
|
71128
|
+
systemPrompt: external_exports.string().min(1).describe("The system prompt defining this member's expertise, responsibilities, and behavior."),
|
|
71129
|
+
model: external_exports.string().optional().describe("Model override for this member. Uses squad default if not provided.")
|
|
71130
|
+
});
|
|
71131
|
+
removeSquadMemberSchema = external_exports.object({
|
|
71132
|
+
squadId: external_exports.string().trim().min(1),
|
|
71133
|
+
memberId: external_exports.string().trim().min(1)
|
|
71134
|
+
});
|
|
71135
|
+
updateSquadConfigSchema = external_exports.object({
|
|
71136
|
+
squadId: external_exports.string().trim().min(1),
|
|
71137
|
+
config: external_exports.object({
|
|
71138
|
+
prMode: external_exports.enum(["draft-pr", "branch-only", "ready-pr", "auto-merge"]).optional(),
|
|
71139
|
+
mcpServers: external_exports.array(external_exports.string()).optional(),
|
|
71140
|
+
maxRevisions: external_exports.number().optional()
|
|
71141
|
+
}).describe("Partial config fields to update. Only provided fields are changed.")
|
|
71142
|
+
});
|
|
71143
|
+
analyzeRepoSchema = external_exports.object({
|
|
71144
|
+
repoUrl: external_exports.string().trim().min(1).describe("The repository URL to analyze"),
|
|
71299
71145
|
scanPaths: external_exports.array(external_exports.string()).optional().describe(
|
|
71300
|
-
"Relative paths within the repository to focus the analysis on. When provided, only these directories are scanned
|
|
71146
|
+
"Relative paths within the repository to focus the analysis on. When provided, only these directories are scanned instead of the entire repo."
|
|
71301
71147
|
)
|
|
71302
71148
|
});
|
|
71303
71149
|
squadIdSchema = external_exports.object({
|
|
@@ -71320,9 +71166,33 @@ var init_squad2 = __esm({
|
|
|
71320
71166
|
});
|
|
71321
71167
|
squadToolDefinitions = [
|
|
71322
71168
|
{
|
|
71323
|
-
name: "
|
|
71324
|
-
description: "
|
|
71325
|
-
parameters:
|
|
71169
|
+
name: "create_squad",
|
|
71170
|
+
description: "Create a new squad for a repository. After creating, use add_squad_member to populate it with agents. Always include team-lead and qa roles.",
|
|
71171
|
+
parameters: createSquadSchema,
|
|
71172
|
+
skipPermission: true
|
|
71173
|
+
},
|
|
71174
|
+
{
|
|
71175
|
+
name: "add_squad_member",
|
|
71176
|
+
description: "Add a member (agent) to an existing squad. Provide a role slug, display name, and a detailed system prompt defining the agent's expertise and responsibilities.",
|
|
71177
|
+
parameters: addSquadMemberSchema,
|
|
71178
|
+
skipPermission: true
|
|
71179
|
+
},
|
|
71180
|
+
{
|
|
71181
|
+
name: "remove_squad_member",
|
|
71182
|
+
description: "Remove a member from a squad by their ID.",
|
|
71183
|
+
parameters: removeSquadMemberSchema,
|
|
71184
|
+
skipPermission: true
|
|
71185
|
+
},
|
|
71186
|
+
{
|
|
71187
|
+
name: "update_squad_config",
|
|
71188
|
+
description: "Update a squad's configuration (prMode, mcpServers, maxRevisions).",
|
|
71189
|
+
parameters: updateSquadConfigSchema,
|
|
71190
|
+
skipPermission: true
|
|
71191
|
+
},
|
|
71192
|
+
{
|
|
71193
|
+
name: "analyze_repo",
|
|
71194
|
+
description: "Analyze a repository's structure and tech stack. Returns information about frameworks, languages, directory layout, and config files. Use this before creating a squad to inform role decisions.",
|
|
71195
|
+
parameters: analyzeRepoSchema,
|
|
71326
71196
|
skipPermission: true
|
|
71327
71197
|
},
|
|
71328
71198
|
{
|
package/dist/daemon/index.js
CHANGED
|
@@ -74,19 +74,18 @@ var init_api = __esm({
|
|
|
74
74
|
});
|
|
75
75
|
|
|
76
76
|
// packages/shared/dist/constants.js
|
|
77
|
-
var APP_NAME, APP_VERSION, API_PORT, API_HOST, DEFAULT_MODEL, SESSION_RESET_THRESHOLD, SCHEDULER_INTERVAL_MS, QA_MAX_REVISIONS,
|
|
77
|
+
var APP_NAME, APP_VERSION, API_PORT, API_HOST, DEFAULT_MODEL, SESSION_RESET_THRESHOLD, SCHEDULER_INTERVAL_MS, QA_MAX_REVISIONS, EVENT_NAMES;
|
|
78
78
|
var init_constants = __esm({
|
|
79
79
|
"packages/shared/dist/constants.js"() {
|
|
80
80
|
"use strict";
|
|
81
81
|
APP_NAME = "io";
|
|
82
|
-
APP_VERSION = "4.2.
|
|
82
|
+
APP_VERSION = "4.2.4";
|
|
83
83
|
API_PORT = 7777;
|
|
84
84
|
API_HOST = "0.0.0.0";
|
|
85
85
|
DEFAULT_MODEL = "gpt-4o";
|
|
86
86
|
SESSION_RESET_THRESHOLD = 50;
|
|
87
87
|
SCHEDULER_INTERVAL_MS = 6e4;
|
|
88
88
|
QA_MAX_REVISIONS = 3;
|
|
89
|
-
MANDATORY_ROLES = ["team-lead", "qa"];
|
|
90
89
|
EVENT_NAMES = {
|
|
91
90
|
SQUAD_CREATED: "squad.created",
|
|
92
91
|
SQUAD_DELETED: "squad.deleted",
|
|
@@ -81247,9 +81246,10 @@ function buildSystemPrompt(options2) {
|
|
|
81247
81246
|
const sections = [
|
|
81248
81247
|
"You are Io, a helpful AI orchestrator companion for IO v4.",
|
|
81249
81248
|
"You coordinate work across squads, wiki knowledge, installed skills, and direct execution tools.",
|
|
81250
|
-
"When a user asks about a project that has a squad, delegate to that squad. When no squad exists, you can do the work directly.",
|
|
81249
|
+
"When a user asks about a project that has a squad, delegate to that squad. When no squad exists, you can do the work directly or create a squad for ongoing project work.",
|
|
81251
81250
|
"Be practical, concise, and execution-oriented. Use tools when they help you produce a more accurate or durable result.",
|
|
81252
|
-
"## Delegation Rules\n- Prefer delegation for project-specific work when a matching squad exists.\n- If no squad exists for the target repository or project, use direct coding and knowledge tools yourself.\n-
|
|
81251
|
+
"## Delegation Rules\n- Prefer delegation for project-specific work when a matching squad exists.\n- If no squad exists for the target repository or project, use direct coding and knowledge tools yourself for one-off tasks.\n- For ongoing project work where no squad exists, create one with `create_squad` and then add members with `add_squad_member`.\n- Use the wiki to remember important information and recover prior context.\n- Use installed skills when they are relevant to the current request.",
|
|
81252
|
+
"## Squad Creation Guidelines\n- Use `analyze_repo` first to understand the repository's tech stack and structure.\n- Always add a `team-lead` member (coordinates planning, task assignment, and technical review) and a `qa` member (validates diffs, tests, and objective completion).\n- Add additional role-specific members based on the repository analysis (e.g. senior-frontend-engineer, senior-backend-engineer, senior-platform-engineer).\n- Write detailed system prompts for each member that define their expertise, responsibilities, and behavioral guidelines.\n- Use `remove_squad_member` and `add_squad_member` to restructure existing squads when the user requests changes.",
|
|
81253
81253
|
`## Squad Roster
|
|
81254
81254
|
${formatSquadRoster(options2.squads)}`,
|
|
81255
81255
|
formatOptionalSection("## Conversation Summary", options2.conversationSummary ?? ""),
|
|
@@ -82612,44 +82612,6 @@ Review rules:
|
|
|
82612
82612
|
- If approving, summarize why the work is acceptable.
|
|
82613
82613
|
- If rejecting, include concrete fixes or follow-up actions.
|
|
82614
82614
|
- Do not approve work that lacks evidence for important behavior changes.`;
|
|
82615
|
-
var ROLE_GENERATION_PROMPT = `You are staffing an autonomous software squad for a GitHub repository.
|
|
82616
|
-
|
|
82617
|
-
Given repository analysis describing languages, frameworks, file structure, architectural patterns, and operational concerns, propose the smallest effective team that can deliver objectives safely.
|
|
82618
|
-
|
|
82619
|
-
Requirements:
|
|
82620
|
-
- Mandatory roles Team Lead and QA must always exist \u2014 do NOT include them in your response.
|
|
82621
|
-
- Suggest only additional roles that are clearly justified by the repository analysis.
|
|
82622
|
-
- Role names must reflect Senior or Principal seniority and be specific to the actual technology stack and work scope. Examples: "Principal React Engineer", "Senior DevOps Engineer", "Senior CI/CD Solutions Engineer", "Principal .NET API Engineer", "Senior Data Pipeline Engineer".
|
|
82623
|
-
- Do NOT use generic names like "Frontend Engineer" or "Backend Engineer". Be specific about the technology and domain.
|
|
82624
|
-
- Each role must have a short human-readable name and a concise description of responsibilities.
|
|
82625
|
-
- Avoid duplicate or overlapping roles.
|
|
82626
|
-
- Optimize for implementation, verification, and maintainability.
|
|
82627
|
-
|
|
82628
|
-
Return strict JSON in this shape:
|
|
82629
|
-
{
|
|
82630
|
-
"roles": [
|
|
82631
|
-
{
|
|
82632
|
-
"role": "principal-react-engineer",
|
|
82633
|
-
"name": "Principal React Engineer",
|
|
82634
|
-
"description": "Owns UI implementation, client state management, and browser-facing integration tests."
|
|
82635
|
-
}
|
|
82636
|
-
]
|
|
82637
|
-
}`;
|
|
82638
|
-
function generateRolePrompt(role, repoContext) {
|
|
82639
|
-
const normalizedRole = role.trim() || "specialist";
|
|
82640
|
-
return `You are the ${normalizedRole} for the IO v4 daemon squad system.
|
|
82641
|
-
|
|
82642
|
-
${ROLE_GUIDELINES}
|
|
82643
|
-
|
|
82644
|
-
Role expectations:
|
|
82645
|
-
- Own work that naturally fits the ${normalizedRole} discipline.
|
|
82646
|
-
- Make changes that align with the repository's existing architecture, conventions, and tooling.
|
|
82647
|
-
- Collaborate with the Team Lead and QA by leaving behind clear implementation notes and validation evidence.
|
|
82648
|
-
- Escalate blockers or missing context early.
|
|
82649
|
-
|
|
82650
|
-
Repository context:
|
|
82651
|
-
${repoContext}`;
|
|
82652
|
-
}
|
|
82653
82615
|
|
|
82654
82616
|
// packages/daemon/src/execution/planning.ts
|
|
82655
82617
|
var execAsync4 = promisify4(exec4);
|
|
@@ -83389,236 +83351,6 @@ ${reviewOutcome.issues?.join("\n") ?? "Resolve the review feedback and re-valida
|
|
|
83389
83351
|
}
|
|
83390
83352
|
}
|
|
83391
83353
|
|
|
83392
|
-
// packages/daemon/src/squad/hiring.ts
|
|
83393
|
-
init_dist();
|
|
83394
|
-
import { CopilotClient as CopilotClient6, approveAll as approveAll6 } from "@github/copilot-sdk";
|
|
83395
|
-
function parseRepoUrl(repoUrl) {
|
|
83396
|
-
const trimmed = repoUrl.trim();
|
|
83397
|
-
const sshMatch = trimmed.match(/^git@[^:]+:([^/]+)\/([^/]+?)(?:\.git)?$/i);
|
|
83398
|
-
if (sshMatch) {
|
|
83399
|
-
return { owner: sshMatch[1], name: sshMatch[2] };
|
|
83400
|
-
}
|
|
83401
|
-
const normalized = trimmed.replace(/\.git$/i, "");
|
|
83402
|
-
const url2 = new URL(normalized);
|
|
83403
|
-
const segments = url2.pathname.split("/").filter(Boolean);
|
|
83404
|
-
if (segments.length < 2) {
|
|
83405
|
-
throw new Error(`Unable to parse owner/name from repo URL: ${repoUrl}`);
|
|
83406
|
-
}
|
|
83407
|
-
return { owner: segments[0], name: segments[1] };
|
|
83408
|
-
}
|
|
83409
|
-
function slugifyRole(role) {
|
|
83410
|
-
return role.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
83411
|
-
}
|
|
83412
|
-
function titleCase(value) {
|
|
83413
|
-
return value.split(/[-_\s]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
|
|
83414
|
-
}
|
|
83415
|
-
function extractJsonObject4(content) {
|
|
83416
|
-
const fenced = content.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
83417
|
-
if (fenced?.[1]) {
|
|
83418
|
-
return fenced[1].trim();
|
|
83419
|
-
}
|
|
83420
|
-
const start = content.indexOf("{");
|
|
83421
|
-
const end = content.lastIndexOf("}");
|
|
83422
|
-
return start >= 0 && end > start ? content.slice(start, end + 1) : null;
|
|
83423
|
-
}
|
|
83424
|
-
function detectRolesFromAnalysis(repoAnalysis) {
|
|
83425
|
-
const analysis = repoAnalysis.toLowerCase();
|
|
83426
|
-
const roles = [];
|
|
83427
|
-
if (/(react|next|vue|angular|frontend|ui|xaml)/.test(analysis)) {
|
|
83428
|
-
roles.push({
|
|
83429
|
-
role: "senior-frontend-engineer",
|
|
83430
|
-
name: "Senior Frontend Engineer",
|
|
83431
|
-
description: "Implements user-facing experiences, UI state, and presentation-layer changes."
|
|
83432
|
-
});
|
|
83433
|
-
}
|
|
83434
|
-
if (/(node|express|api|backend|server|daemon|database|sql|postgres|sqlite)/.test(analysis)) {
|
|
83435
|
-
roles.push({
|
|
83436
|
-
role: "senior-backend-engineer",
|
|
83437
|
-
name: "Senior Backend Engineer",
|
|
83438
|
-
description: "Implements server-side logic, data access, integrations, and execution flow changes."
|
|
83439
|
-
});
|
|
83440
|
-
}
|
|
83441
|
-
if (/(infra|docker|kubernetes|deploy|ci|cd|workflow|github actions)/.test(analysis)) {
|
|
83442
|
-
roles.push({
|
|
83443
|
-
role: "senior-platform-engineer",
|
|
83444
|
-
name: "Senior Platform Engineer",
|
|
83445
|
-
description: "Owns automation, pipelines, environments, and operational tooling."
|
|
83446
|
-
});
|
|
83447
|
-
}
|
|
83448
|
-
if (/(security|auth|oauth|secret|policy)/.test(analysis)) {
|
|
83449
|
-
roles.push({
|
|
83450
|
-
role: "senior-security-engineer",
|
|
83451
|
-
name: "Senior Security Engineer",
|
|
83452
|
-
description: "Reviews authentication, authorization, secrets handling, and security-sensitive changes."
|
|
83453
|
-
});
|
|
83454
|
-
}
|
|
83455
|
-
return roles;
|
|
83456
|
-
}
|
|
83457
|
-
function dedupeRoles(roles) {
|
|
83458
|
-
const seen = /* @__PURE__ */ new Set();
|
|
83459
|
-
const output = [];
|
|
83460
|
-
for (const role of roles) {
|
|
83461
|
-
const normalizedRole = slugifyRole(role.role);
|
|
83462
|
-
if (!normalizedRole || seen.has(normalizedRole) || MANDATORY_ROLES.includes(normalizedRole)) {
|
|
83463
|
-
continue;
|
|
83464
|
-
}
|
|
83465
|
-
seen.add(normalizedRole);
|
|
83466
|
-
output.push({
|
|
83467
|
-
role: normalizedRole,
|
|
83468
|
-
name: role.name?.trim() || titleCase(normalizedRole),
|
|
83469
|
-
description: role.description?.trim() || `${titleCase(normalizedRole)} responsibilities`
|
|
83470
|
-
});
|
|
83471
|
-
}
|
|
83472
|
-
return output;
|
|
83473
|
-
}
|
|
83474
|
-
function buildMandatoryRoles() {
|
|
83475
|
-
return [
|
|
83476
|
-
{
|
|
83477
|
-
role: "team-lead",
|
|
83478
|
-
name: "Team Lead",
|
|
83479
|
-
description: "Coordinates planning, task assignment, sequencing, and final technical review."
|
|
83480
|
-
},
|
|
83481
|
-
{
|
|
83482
|
-
role: "qa",
|
|
83483
|
-
name: "QA Reviewer",
|
|
83484
|
-
description: "Validates diffs, test evidence, and objective completion before delivery."
|
|
83485
|
-
}
|
|
83486
|
-
];
|
|
83487
|
-
}
|
|
83488
|
-
function modelForRole(role) {
|
|
83489
|
-
const normalized = slugifyRole(role);
|
|
83490
|
-
if (normalized === "team-lead") {
|
|
83491
|
-
return DEFAULT_MODEL;
|
|
83492
|
-
}
|
|
83493
|
-
return DEFAULT_MODEL;
|
|
83494
|
-
}
|
|
83495
|
-
function systemPromptForRole(role, repoContext) {
|
|
83496
|
-
const normalized = slugifyRole(role);
|
|
83497
|
-
if (normalized === "team-lead") {
|
|
83498
|
-
return `${TEAM_LEAD_PROMPT}
|
|
83499
|
-
|
|
83500
|
-
Repository context:
|
|
83501
|
-
${repoContext}`;
|
|
83502
|
-
}
|
|
83503
|
-
if (normalized === "qa") {
|
|
83504
|
-
return `${QA_PROMPT}
|
|
83505
|
-
|
|
83506
|
-
Repository context:
|
|
83507
|
-
${repoContext}`;
|
|
83508
|
-
}
|
|
83509
|
-
return generateRolePrompt(normalized, repoContext);
|
|
83510
|
-
}
|
|
83511
|
-
async function proposeSquadComposition(repoUrl, repoAnalysis, context) {
|
|
83512
|
-
const mandatory = buildMandatoryRoles();
|
|
83513
|
-
const fallbackAdditional = dedupeRoles(detectRolesFromAnalysis(repoAnalysis));
|
|
83514
|
-
const contextSection = context ? `
|
|
83515
|
-
|
|
83516
|
-
Additional context from user:
|
|
83517
|
-
${context}` : "";
|
|
83518
|
-
const prompt = `Repository URL: ${repoUrl}
|
|
83519
|
-
|
|
83520
|
-
Repository analysis:
|
|
83521
|
-
${repoAnalysis}${contextSection}
|
|
83522
|
-
|
|
83523
|
-
${ROLE_GENERATION_PROMPT}`;
|
|
83524
|
-
let client2 = null;
|
|
83525
|
-
try {
|
|
83526
|
-
client2 = new CopilotClient6();
|
|
83527
|
-
await client2.start();
|
|
83528
|
-
const session = await client2.createSession({
|
|
83529
|
-
model: DEFAULT_MODEL,
|
|
83530
|
-
onPermissionRequest: approveAll6,
|
|
83531
|
-
systemMessage: {
|
|
83532
|
-
content: ROLE_GENERATION_PROMPT
|
|
83533
|
-
}
|
|
83534
|
-
});
|
|
83535
|
-
try {
|
|
83536
|
-
const response = await session.sendAndWait({ prompt }, 6e4);
|
|
83537
|
-
const content = response?.data.content?.trim() ?? "";
|
|
83538
|
-
const json2 = extractJsonObject4(content);
|
|
83539
|
-
if (!json2) {
|
|
83540
|
-
return { roles: [...mandatory, ...fallbackAdditional] };
|
|
83541
|
-
}
|
|
83542
|
-
const parsed = JSON.parse(json2);
|
|
83543
|
-
return {
|
|
83544
|
-
roles: [...mandatory, ...dedupeRoles(parsed.roles ?? fallbackAdditional)]
|
|
83545
|
-
};
|
|
83546
|
-
} finally {
|
|
83547
|
-
await session.disconnect().catch(() => void 0);
|
|
83548
|
-
}
|
|
83549
|
-
} catch {
|
|
83550
|
-
return { roles: [...mandatory, ...fallbackAdditional] };
|
|
83551
|
-
} finally {
|
|
83552
|
-
if (client2) {
|
|
83553
|
-
await client2.stop().catch(() => []);
|
|
83554
|
-
}
|
|
83555
|
-
}
|
|
83556
|
-
}
|
|
83557
|
-
async function hireSquad(repoUrl, composition, config2) {
|
|
83558
|
-
const parsedRepo = parseRepoUrl(repoUrl);
|
|
83559
|
-
const repoContext = `Repository: ${parsedRepo.owner}/${parsedRepo.name}
|
|
83560
|
-
Configured MCP servers: ${config2.mcpServers.join(", ") || "none"}
|
|
83561
|
-
QA max revisions: ${config2.maxRevisions || QA_MAX_REVISIONS}`;
|
|
83562
|
-
const existingSquad = await getSquadByRepo(parsedRepo.owner, parsedRepo.name);
|
|
83563
|
-
const normalizedConfig = {
|
|
83564
|
-
prMode: config2.prMode,
|
|
83565
|
-
mcpServers: [...config2.mcpServers],
|
|
83566
|
-
maxRevisions: config2.maxRevisions || QA_MAX_REVISIONS
|
|
83567
|
-
};
|
|
83568
|
-
const desiredRoles = composition.roles.length > 0 ? composition.roles : buildMandatoryRoles();
|
|
83569
|
-
const membersToCreate = desiredRoles.map((role) => ({
|
|
83570
|
-
role: slugifyRole(role.role),
|
|
83571
|
-
name: role.name?.trim() || titleCase(role.role),
|
|
83572
|
-
description: role.description?.trim() || `${titleCase(role.role)} responsibilities`
|
|
83573
|
-
}));
|
|
83574
|
-
let squadId;
|
|
83575
|
-
if (existingSquad) {
|
|
83576
|
-
const updatedSquad = await updateSquad(existingSquad.id, {
|
|
83577
|
-
name: `${titleCase(parsedRepo.name)} Squad`,
|
|
83578
|
-
config: normalizedConfig,
|
|
83579
|
-
status: existingSquad.status
|
|
83580
|
-
});
|
|
83581
|
-
if (!updatedSquad) {
|
|
83582
|
-
throw new Error(`Unable to update existing squad for ${repoUrl}`);
|
|
83583
|
-
}
|
|
83584
|
-
squadId = updatedSquad.id;
|
|
83585
|
-
const existingMembers = await getMembers(updatedSquad.id);
|
|
83586
|
-
await Promise.all(existingMembers.map((member) => removeMember(member.id)));
|
|
83587
|
-
} else {
|
|
83588
|
-
const squad = await createSquad({
|
|
83589
|
-
name: `${titleCase(parsedRepo.name)} Squad`,
|
|
83590
|
-
repoUrl,
|
|
83591
|
-
repoOwner: parsedRepo.owner,
|
|
83592
|
-
repoName: parsedRepo.name,
|
|
83593
|
-
status: "active",
|
|
83594
|
-
config: normalizedConfig
|
|
83595
|
-
});
|
|
83596
|
-
squadId = squad.id;
|
|
83597
|
-
}
|
|
83598
|
-
const members = [];
|
|
83599
|
-
for (const memberDefinition of membersToCreate) {
|
|
83600
|
-
const member = await addMember(squadId, {
|
|
83601
|
-
role: memberDefinition.role,
|
|
83602
|
-
name: memberDefinition.name,
|
|
83603
|
-
systemPrompt: systemPromptForRole(
|
|
83604
|
-
memberDefinition.role,
|
|
83605
|
-
`${repoContext}
|
|
83606
|
-
Role: ${memberDefinition.description}`
|
|
83607
|
-
),
|
|
83608
|
-
model: modelForRole(memberDefinition.role)
|
|
83609
|
-
});
|
|
83610
|
-
members.push(member);
|
|
83611
|
-
}
|
|
83612
|
-
const fullSquad = await getSquad(squadId);
|
|
83613
|
-
if (!fullSquad) {
|
|
83614
|
-
throw new Error(`Unable to load squad ${squadId} after hiring`);
|
|
83615
|
-
}
|
|
83616
|
-
eventBus.emit(existingSquad ? EVENT_NAMES.SQUAD_UPDATED : EVENT_NAMES.SQUAD_CREATED, {
|
|
83617
|
-
squad: fullSquad
|
|
83618
|
-
});
|
|
83619
|
-
return { squad: fullSquad, members: fullSquad.members };
|
|
83620
|
-
}
|
|
83621
|
-
|
|
83622
83354
|
// packages/daemon/src/orchestrator/tools/squad.ts
|
|
83623
83355
|
var execAsync8 = promisify8(exec8);
|
|
83624
83356
|
async function pathExists2(path) {
|
|
@@ -83629,13 +83361,38 @@ async function pathExists2(path) {
|
|
|
83629
83361
|
return false;
|
|
83630
83362
|
}
|
|
83631
83363
|
}
|
|
83632
|
-
var
|
|
83633
|
-
repoUrl: external_exports.string().trim().min(1).describe("The repository URL to
|
|
83634
|
-
|
|
83635
|
-
|
|
83636
|
-
|
|
83364
|
+
var createSquadSchema = external_exports.object({
|
|
83365
|
+
repoUrl: external_exports.string().trim().min(1).describe("The repository URL to create a squad for"),
|
|
83366
|
+
name: external_exports.string().trim().optional().describe("Squad name. Defaults to '<RepoName> Squad' if not provided."),
|
|
83367
|
+
config: external_exports.object({
|
|
83368
|
+
prMode: external_exports.enum(["draft-pr", "branch-only", "ready-pr", "auto-merge"]).optional().describe("PR creation mode. Defaults to 'draft-pr'."),
|
|
83369
|
+
mcpServers: external_exports.array(external_exports.string()).optional().describe("MCP server URLs to configure for the squad."),
|
|
83370
|
+
maxRevisions: external_exports.number().optional().describe("Max QA revision cycles. Defaults to system default.")
|
|
83371
|
+
}).optional().describe("Squad configuration. All fields are optional with sensible defaults.")
|
|
83372
|
+
});
|
|
83373
|
+
var addSquadMemberSchema = external_exports.object({
|
|
83374
|
+
squadId: external_exports.string().trim().min(1),
|
|
83375
|
+
role: external_exports.string().trim().min(1).describe("Slug-style role identifier (e.g. 'senior-frontend-engineer', 'team-lead')"),
|
|
83376
|
+
name: external_exports.string().trim().min(1).describe("Display name for the member (e.g. 'Senior Frontend Engineer')"),
|
|
83377
|
+
systemPrompt: external_exports.string().min(1).describe("The system prompt defining this member's expertise, responsibilities, and behavior."),
|
|
83378
|
+
model: external_exports.string().optional().describe("Model override for this member. Uses squad default if not provided.")
|
|
83379
|
+
});
|
|
83380
|
+
var removeSquadMemberSchema = external_exports.object({
|
|
83381
|
+
squadId: external_exports.string().trim().min(1),
|
|
83382
|
+
memberId: external_exports.string().trim().min(1)
|
|
83383
|
+
});
|
|
83384
|
+
var updateSquadConfigSchema = external_exports.object({
|
|
83385
|
+
squadId: external_exports.string().trim().min(1),
|
|
83386
|
+
config: external_exports.object({
|
|
83387
|
+
prMode: external_exports.enum(["draft-pr", "branch-only", "ready-pr", "auto-merge"]).optional(),
|
|
83388
|
+
mcpServers: external_exports.array(external_exports.string()).optional(),
|
|
83389
|
+
maxRevisions: external_exports.number().optional()
|
|
83390
|
+
}).describe("Partial config fields to update. Only provided fields are changed.")
|
|
83391
|
+
});
|
|
83392
|
+
var analyzeRepoSchema = external_exports.object({
|
|
83393
|
+
repoUrl: external_exports.string().trim().min(1).describe("The repository URL to analyze"),
|
|
83637
83394
|
scanPaths: external_exports.array(external_exports.string()).optional().describe(
|
|
83638
|
-
"Relative paths within the repository to focus the analysis on. When provided, only these directories are scanned
|
|
83395
|
+
"Relative paths within the repository to focus the analysis on. When provided, only these directories are scanned instead of the entire repo."
|
|
83639
83396
|
)
|
|
83640
83397
|
});
|
|
83641
83398
|
var squadIdSchema = external_exports.object({
|
|
@@ -83658,9 +83415,33 @@ var updateSquadMemberSchema = external_exports.object({
|
|
|
83658
83415
|
});
|
|
83659
83416
|
var squadToolDefinitions = [
|
|
83660
83417
|
{
|
|
83661
|
-
name: "
|
|
83662
|
-
description: "
|
|
83663
|
-
parameters:
|
|
83418
|
+
name: "create_squad",
|
|
83419
|
+
description: "Create a new squad for a repository. After creating, use add_squad_member to populate it with agents. Always include team-lead and qa roles.",
|
|
83420
|
+
parameters: createSquadSchema,
|
|
83421
|
+
skipPermission: true
|
|
83422
|
+
},
|
|
83423
|
+
{
|
|
83424
|
+
name: "add_squad_member",
|
|
83425
|
+
description: "Add a member (agent) to an existing squad. Provide a role slug, display name, and a detailed system prompt defining the agent's expertise and responsibilities.",
|
|
83426
|
+
parameters: addSquadMemberSchema,
|
|
83427
|
+
skipPermission: true
|
|
83428
|
+
},
|
|
83429
|
+
{
|
|
83430
|
+
name: "remove_squad_member",
|
|
83431
|
+
description: "Remove a member from a squad by their ID.",
|
|
83432
|
+
parameters: removeSquadMemberSchema,
|
|
83433
|
+
skipPermission: true
|
|
83434
|
+
},
|
|
83435
|
+
{
|
|
83436
|
+
name: "update_squad_config",
|
|
83437
|
+
description: "Update a squad's configuration (prMode, mcpServers, maxRevisions).",
|
|
83438
|
+
parameters: updateSquadConfigSchema,
|
|
83439
|
+
skipPermission: true
|
|
83440
|
+
},
|
|
83441
|
+
{
|
|
83442
|
+
name: "analyze_repo",
|
|
83443
|
+
description: "Analyze a repository's structure and tech stack. Returns information about frameworks, languages, directory layout, and config files. Use this before creating a squad to inform role decisions.",
|
|
83444
|
+
parameters: analyzeRepoSchema,
|
|
83664
83445
|
skipPermission: true
|
|
83665
83446
|
},
|
|
83666
83447
|
{
|
|
@@ -83700,12 +83481,22 @@ var squadToolDefinitions = [
|
|
|
83700
83481
|
skipPermission: true
|
|
83701
83482
|
}
|
|
83702
83483
|
];
|
|
83703
|
-
function
|
|
83704
|
-
|
|
83705
|
-
|
|
83706
|
-
|
|
83707
|
-
|
|
83708
|
-
}
|
|
83484
|
+
function parseRepoUrl(repoUrl) {
|
|
83485
|
+
const trimmed = repoUrl.trim();
|
|
83486
|
+
const sshMatch = trimmed.match(/^git@[^:]+:([^/]+)\/([^/]+?)(?:\.git)?$/i);
|
|
83487
|
+
if (sshMatch) {
|
|
83488
|
+
return { owner: sshMatch[1], name: sshMatch[2] };
|
|
83489
|
+
}
|
|
83490
|
+
const normalized = trimmed.replace(/\.git$/i, "");
|
|
83491
|
+
const url2 = new URL(normalized);
|
|
83492
|
+
const segments = url2.pathname.split("/").filter(Boolean);
|
|
83493
|
+
if (segments.length < 2) {
|
|
83494
|
+
throw new Error(`Unable to parse owner/name from repo URL: ${repoUrl}`);
|
|
83495
|
+
}
|
|
83496
|
+
return { owner: segments[0], name: segments[1] };
|
|
83497
|
+
}
|
|
83498
|
+
function titleCase(value) {
|
|
83499
|
+
return value.split(/[-_\s]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
|
|
83709
83500
|
}
|
|
83710
83501
|
var MANIFEST_FILES = [
|
|
83711
83502
|
"package.json",
|
|
@@ -83889,18 +83680,98 @@ async function startAndExecuteInstance(instanceId, squad, objectiveId) {
|
|
|
83889
83680
|
await processQueue2(squad.id);
|
|
83890
83681
|
}
|
|
83891
83682
|
}
|
|
83892
|
-
async function
|
|
83893
|
-
const { repoUrl,
|
|
83894
|
-
const
|
|
83683
|
+
async function handleCreateSquad(rawArgs) {
|
|
83684
|
+
const { repoUrl, name, config: config2 } = createSquadSchema.parse(rawArgs);
|
|
83685
|
+
const parsedRepo = parseRepoUrl(repoUrl);
|
|
83686
|
+
const existingSquad = await getSquadByRepo(parsedRepo.owner, parsedRepo.name);
|
|
83687
|
+
if (existingSquad) {
|
|
83688
|
+
return {
|
|
83689
|
+
message: `A squad already exists for ${parsedRepo.owner}/${parsedRepo.name}.`,
|
|
83690
|
+
squad: existingSquad
|
|
83691
|
+
};
|
|
83692
|
+
}
|
|
83693
|
+
const squadName = name || `${titleCase(parsedRepo.name)} Squad`;
|
|
83694
|
+
const squadConfig = {
|
|
83695
|
+
prMode: config2?.prMode ?? "draft-pr",
|
|
83696
|
+
mcpServers: config2?.mcpServers ?? [],
|
|
83697
|
+
maxRevisions: config2?.maxRevisions ?? QA_MAX_REVISIONS
|
|
83698
|
+
};
|
|
83699
|
+
const squad = await createSquad({
|
|
83700
|
+
name: squadName,
|
|
83895
83701
|
repoUrl,
|
|
83896
|
-
|
|
83897
|
-
|
|
83898
|
-
|
|
83899
|
-
|
|
83702
|
+
repoOwner: parsedRepo.owner,
|
|
83703
|
+
repoName: parsedRepo.name,
|
|
83704
|
+
status: "active",
|
|
83705
|
+
config: squadConfig
|
|
83706
|
+
});
|
|
83707
|
+
eventBus.emit(EVENT_NAMES.SQUAD_CREATED, { squad });
|
|
83708
|
+
return {
|
|
83709
|
+
message: `Squad "${squad.name}" created for ${parsedRepo.owner}/${parsedRepo.name}. Add members with add_squad_member.`,
|
|
83710
|
+
squad
|
|
83711
|
+
};
|
|
83712
|
+
}
|
|
83713
|
+
async function handleAddSquadMember(rawArgs) {
|
|
83714
|
+
const { squadId, role, name, systemPrompt, model } = addSquadMemberSchema.parse(rawArgs);
|
|
83715
|
+
const squad = await getSquad(squadId);
|
|
83716
|
+
if (!squad) {
|
|
83717
|
+
throw new Error(`Squad ${squadId} was not found.`);
|
|
83718
|
+
}
|
|
83719
|
+
const member = await addMember(squadId, {
|
|
83720
|
+
role,
|
|
83721
|
+
name,
|
|
83722
|
+
systemPrompt,
|
|
83723
|
+
model: model ?? null
|
|
83724
|
+
});
|
|
83725
|
+
eventBus.emit(EVENT_NAMES.SQUAD_MEMBER_UPDATED, { squadId, member });
|
|
83726
|
+
return {
|
|
83727
|
+
message: `Added member "${member.name}" (${member.role}) to squad "${squad.name}".`,
|
|
83728
|
+
member
|
|
83729
|
+
};
|
|
83730
|
+
}
|
|
83731
|
+
async function handleRemoveSquadMember(rawArgs) {
|
|
83732
|
+
const { squadId, memberId } = removeSquadMemberSchema.parse(rawArgs);
|
|
83733
|
+
const squad = await getSquad(squadId);
|
|
83734
|
+
if (!squad) {
|
|
83735
|
+
throw new Error(`Squad ${squadId} was not found.`);
|
|
83736
|
+
}
|
|
83737
|
+
const member = await getMember(memberId);
|
|
83738
|
+
if (!member || member.squadId !== squadId) {
|
|
83739
|
+
throw new Error(`Member ${memberId} was not found in squad ${squadId}.`);
|
|
83740
|
+
}
|
|
83741
|
+
await removeMember(memberId);
|
|
83742
|
+
eventBus.emit(EVENT_NAMES.SQUAD_UPDATED, { squad });
|
|
83743
|
+
return {
|
|
83744
|
+
message: `Removed member "${member.name}" (${member.role}) from squad "${squad.name}".`,
|
|
83745
|
+
memberId
|
|
83746
|
+
};
|
|
83747
|
+
}
|
|
83748
|
+
async function handleUpdateSquadConfig(rawArgs) {
|
|
83749
|
+
const { squadId, config: config2 } = updateSquadConfigSchema.parse(rawArgs);
|
|
83750
|
+
const squad = await getSquad(squadId);
|
|
83751
|
+
if (!squad) {
|
|
83752
|
+
throw new Error(`Squad ${squadId} was not found.`);
|
|
83753
|
+
}
|
|
83754
|
+
const mergedConfig = {
|
|
83755
|
+
prMode: config2.prMode ?? squad.config.prMode,
|
|
83756
|
+
mcpServers: config2.mcpServers ?? squad.config.mcpServers,
|
|
83757
|
+
maxRevisions: config2.maxRevisions ?? squad.config.maxRevisions
|
|
83758
|
+
};
|
|
83759
|
+
const updated = await updateSquad(squadId, { config: mergedConfig });
|
|
83760
|
+
if (!updated) {
|
|
83761
|
+
throw new Error(`Failed to update squad ${squadId}.`);
|
|
83762
|
+
}
|
|
83763
|
+
eventBus.emit(EVENT_NAMES.SQUAD_UPDATED, { squad: updated });
|
|
83764
|
+
return {
|
|
83765
|
+
message: `Updated config for squad "${updated.name}".`,
|
|
83766
|
+
squad: updated
|
|
83767
|
+
};
|
|
83768
|
+
}
|
|
83769
|
+
async function handleAnalyzeRepo(rawArgs) {
|
|
83770
|
+
const { repoUrl, scanPaths } = analyzeRepoSchema.parse(rawArgs);
|
|
83771
|
+
const analysis = await buildRepoAnalysis(repoUrl, scanPaths);
|
|
83900
83772
|
return {
|
|
83901
|
-
message:
|
|
83902
|
-
|
|
83903
|
-
members: result.members
|
|
83773
|
+
message: "Repository analysis complete.",
|
|
83774
|
+
analysis
|
|
83904
83775
|
};
|
|
83905
83776
|
}
|
|
83906
83777
|
async function handleFireSquad(rawArgs) {
|
|
@@ -83972,8 +83843,16 @@ async function handleUpdateSquadMember(rawArgs) {
|
|
|
83972
83843
|
function createSquadToolExecutor(config2) {
|
|
83973
83844
|
return async (toolName, rawArgs) => {
|
|
83974
83845
|
switch (toolName) {
|
|
83975
|
-
case "
|
|
83976
|
-
return
|
|
83846
|
+
case "create_squad":
|
|
83847
|
+
return handleCreateSquad(rawArgs);
|
|
83848
|
+
case "add_squad_member":
|
|
83849
|
+
return handleAddSquadMember(rawArgs);
|
|
83850
|
+
case "remove_squad_member":
|
|
83851
|
+
return handleRemoveSquadMember(rawArgs);
|
|
83852
|
+
case "update_squad_config":
|
|
83853
|
+
return handleUpdateSquadConfig(rawArgs);
|
|
83854
|
+
case "analyze_repo":
|
|
83855
|
+
return handleAnalyzeRepo(rawArgs);
|
|
83977
83856
|
case "fire_squad":
|
|
83978
83857
|
return handleFireSquad(rawArgs);
|
|
83979
83858
|
case "list_squads": {
|