heyio 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/server.js +16 -1
- package/dist/copilot/agents.js +127 -16
- package/dist/copilot/cron.js +136 -0
- package/dist/copilot/event-summary.js +286 -0
- package/dist/copilot/io-scheduler.js +132 -0
- package/dist/copilot/orchestrator.js +1 -0
- package/dist/copilot/review-backfill.js +57 -0
- package/dist/copilot/scheduler.js +171 -0
- package/dist/copilot/system-message.js +27 -2
- package/dist/copilot/tools.js +324 -6
- package/dist/daemon.js +31 -1
- package/dist/store/db.js +27 -0
- package/dist/store/io-schedules.js +63 -0
- package/dist/store/schedules.js +83 -0
- package/dist/store/squads.js +21 -0
- package/dist/tui/index.js +62 -2
- package/package.json +1 -1
- package/web-dist/assets/index-BWGQix5_.css +1 -0
- package/web-dist/assets/index-BksyB2za.js +74 -0
- package/web-dist/index.html +2 -2
- package/web-dist/assets/index-B6FXWKsy.js +0 -74
- package/web-dist/assets/index-CXTrW8OO.css +0 -1
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// IO-level scheduler — fires recurring tasks for IO itself, independent of
|
|
2
|
+
// any squad. Mirrors the squad scheduler in shape (TICK_MS loop, in-flight
|
|
3
|
+
// guard, reconcile on startup) but dispatches into the orchestrator via
|
|
4
|
+
// sendToOrchestrator with a `background` source so IO can handle the prompt
|
|
5
|
+
// the same way it handles any other user message.
|
|
6
|
+
import { listIoSchedules, listDueIoSchedules, recordIoScheduleRun, setIoScheduleTimestamps, updateIoScheduleNextRun, } from "../store/io-schedules.js";
|
|
7
|
+
import { sendToOrchestrator } from "./orchestrator.js";
|
|
8
|
+
import { nextRun } from "./cron.js";
|
|
9
|
+
const TICK_MS = 30_000;
|
|
10
|
+
let timer;
|
|
11
|
+
const inFlight = new Set();
|
|
12
|
+
function buildPrompt(schedule) {
|
|
13
|
+
const header = `# Scheduled task: ${schedule.name}\n\n_This prompt was fired automatically by the IO scheduler. Cron expression: \`${schedule.cron_expr}\`._`;
|
|
14
|
+
const notes = schedule.notes
|
|
15
|
+
? `\n\n**Operator notes:** ${schedule.notes}`
|
|
16
|
+
: "";
|
|
17
|
+
return `${header}\n\n${schedule.prompt}${notes}`;
|
|
18
|
+
}
|
|
19
|
+
async function fireSchedule(schedule) {
|
|
20
|
+
if (inFlight.has(schedule.id))
|
|
21
|
+
return;
|
|
22
|
+
inFlight.add(schedule.id);
|
|
23
|
+
const ranAt = new Date();
|
|
24
|
+
let nextIso = null;
|
|
25
|
+
try {
|
|
26
|
+
nextIso = nextRun(schedule.cron_expr, ranAt).toISOString();
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
console.error(`[io] io-scheduler: cron parse error for schedule ${schedule.id}:`, err instanceof Error ? err.message : err);
|
|
30
|
+
}
|
|
31
|
+
recordIoScheduleRun(schedule.id, ranAt, nextIso);
|
|
32
|
+
console.log(`[io] io-scheduler: firing schedule "${schedule.name}" (next run: ${nextIso ?? "never"})`);
|
|
33
|
+
try {
|
|
34
|
+
await sendToOrchestrator(buildPrompt(schedule), { type: "background" }, () => {
|
|
35
|
+
// No-op: scheduled work is fire-and-forget; output is captured in
|
|
36
|
+
// the orchestrator's conversation log.
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
console.error(`[io] io-scheduler: failed to dispatch schedule ${schedule.id}:`, err instanceof Error ? err.message : err);
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
inFlight.delete(schedule.id);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async function tick() {
|
|
47
|
+
let due;
|
|
48
|
+
try {
|
|
49
|
+
due = listDueIoSchedules(new Date());
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
console.error("[io] io-scheduler tick failed:", err instanceof Error ? err.message : err);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
for (const s of due) {
|
|
56
|
+
await fireSchedule(s);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Backfill next_run_at for any IO schedules that are NULL or stale. We
|
|
61
|
+
* advance to the next future occurrence rather than replaying missed runs
|
|
62
|
+
* — same semantics as the squad scheduler.
|
|
63
|
+
*/
|
|
64
|
+
export function reconcileIoSchedules(now = new Date()) {
|
|
65
|
+
for (const s of listIoSchedules()) {
|
|
66
|
+
if (!s.enabled)
|
|
67
|
+
continue;
|
|
68
|
+
let needsUpdate = false;
|
|
69
|
+
if (!s.next_run_at) {
|
|
70
|
+
needsUpdate = true;
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
const next = new Date(s.next_run_at);
|
|
74
|
+
if (Number.isNaN(next.getTime()) || next <= now)
|
|
75
|
+
needsUpdate = true;
|
|
76
|
+
}
|
|
77
|
+
if (!needsUpdate)
|
|
78
|
+
continue;
|
|
79
|
+
try {
|
|
80
|
+
const next = nextRun(s.cron_expr, now);
|
|
81
|
+
updateIoScheduleNextRun(s.id, next.toISOString());
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
console.error(`[io] io-scheduler: invalid cron "${s.cron_expr}" on schedule ${s.id}; clearing next_run_at:`, err instanceof Error ? err.message : err);
|
|
85
|
+
updateIoScheduleNextRun(s.id, null);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
export function startIoScheduler() {
|
|
90
|
+
if (timer)
|
|
91
|
+
return;
|
|
92
|
+
reconcileIoSchedules();
|
|
93
|
+
timer = setInterval(() => {
|
|
94
|
+
void tick();
|
|
95
|
+
}, TICK_MS);
|
|
96
|
+
// Don't keep the event loop alive on shutdown
|
|
97
|
+
timer.unref?.();
|
|
98
|
+
}
|
|
99
|
+
export function stopIoScheduler() {
|
|
100
|
+
if (timer) {
|
|
101
|
+
clearInterval(timer);
|
|
102
|
+
timer = undefined;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Force a schedule to run immediately. Used by the `schedule_run_now` tool.
|
|
107
|
+
*
|
|
108
|
+
* The regular tick path (`fireSchedule`) advances `last_run_at` and
|
|
109
|
+
* `next_run_at` as a side effect, which is correct for an automatic firing
|
|
110
|
+
* but is the wrong behaviour for a manual one — a user testing a schedule at
|
|
111
|
+
* 04:30 should not have the 05:00 occurrence skipped or the schedule shifted.
|
|
112
|
+
* We therefore snapshot both timestamps before firing and restore them after,
|
|
113
|
+
* leaving the persisted schedule untouched.
|
|
114
|
+
*/
|
|
115
|
+
export async function runIoScheduleNow(id) {
|
|
116
|
+
const all = listIoSchedules();
|
|
117
|
+
const s = all.find((x) => x.id === id);
|
|
118
|
+
if (!s)
|
|
119
|
+
return false;
|
|
120
|
+
const previousLast = s.last_run_at;
|
|
121
|
+
const previousNext = s.next_run_at;
|
|
122
|
+
try {
|
|
123
|
+
await fireSchedule(s);
|
|
124
|
+
}
|
|
125
|
+
finally {
|
|
126
|
+
// Restore the original timestamps even if fireSchedule threw, so a
|
|
127
|
+
// failed manual run cannot silently shift the schedule either.
|
|
128
|
+
setIoScheduleTimestamps(id, previousLast, previousNext);
|
|
129
|
+
}
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=io-scheduler.js.map
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { getDb } from "../store/db.js";
|
|
2
|
+
/**
|
|
3
|
+
* Surgically correct historical peer-review rows that were stored as REJECTED
|
|
4
|
+
* (approved=0) but whose comments unambiguously begin with `APPROVED` (issue
|
|
5
|
+
* #50). Earlier daemon builds (pre-#43) inspected only the literal first line,
|
|
6
|
+
* which silently flipped many APPROVED reviews into REJECTED whenever the
|
|
7
|
+
* agent began its response with a markdown rule, blank line, header, or a
|
|
8
|
+
* short prose preamble.
|
|
9
|
+
*
|
|
10
|
+
* We only flip 0 -> 1, and only when the *current* parser sees an explicit
|
|
11
|
+
* line-leading APPROVED token in the prose. We never flip 1 -> 0: doing so
|
|
12
|
+
* would destroy data on legitimate prose-only approvals (e.g. "Excellent
|
|
13
|
+
* work — ships it") that the conservative parser would otherwise downgrade.
|
|
14
|
+
*
|
|
15
|
+
* Returns the number of rows updated.
|
|
16
|
+
*/
|
|
17
|
+
export function backfillReviewVerdicts() {
|
|
18
|
+
const db = getDb();
|
|
19
|
+
const rows = db
|
|
20
|
+
.prepare("SELECT id, approved, comments FROM squad_task_reviews WHERE approved = 0 AND comments IS NOT NULL AND comments != ''")
|
|
21
|
+
.all();
|
|
22
|
+
const update = db.prepare("UPDATE squad_task_reviews SET approved = 1 WHERE id = ?");
|
|
23
|
+
let fixed = 0;
|
|
24
|
+
const tx = db.transaction((batch) => {
|
|
25
|
+
for (const r of batch) {
|
|
26
|
+
if (hasExplicitApproval(r.comments ?? "")) {
|
|
27
|
+
update.run(r.id);
|
|
28
|
+
fixed++;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
tx(rows);
|
|
33
|
+
return fixed;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* True when the comment body unambiguously starts with an APPROVED verdict —
|
|
37
|
+
* either as the first non-empty line (after stripping markdown noise) or as
|
|
38
|
+
* the verdict the current parser extracts before any REJECTED token. Anything
|
|
39
|
+
* shorter than that is left as the daemon originally recorded it.
|
|
40
|
+
*/
|
|
41
|
+
function hasExplicitApproval(content) {
|
|
42
|
+
const stripped = content.replace(/[*_`#>]/g, "");
|
|
43
|
+
const lines = stripped
|
|
44
|
+
.split(/\r?\n/)
|
|
45
|
+
.map((l) => l.trim())
|
|
46
|
+
.filter(Boolean)
|
|
47
|
+
.slice(0, 10);
|
|
48
|
+
for (const line of lines) {
|
|
49
|
+
const lead = line
|
|
50
|
+
.toUpperCase()
|
|
51
|
+
.match(/^[^A-Z]*\b(APPROVED|REJECTED)\b/);
|
|
52
|
+
if (lead)
|
|
53
|
+
return lead[1] === "APPROVED";
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=review-backfill.js.map
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// Squad scheduler — fires recurring "stand-up" meetings on a cron schedule.
|
|
2
|
+
//
|
|
3
|
+
// Design:
|
|
4
|
+
// - Runs as a single setInterval on the daemon (TICK_MS).
|
|
5
|
+
// - Each tick: fetch schedules whose next_run_at is in the past.
|
|
6
|
+
// - For each due schedule: compose a stand-up prompt and delegate it to the
|
|
7
|
+
// squad lead via the existing delegateToAgent() pipeline. The lead then
|
|
8
|
+
// orchestrates the agenda by delegating subtasks to teammates internally.
|
|
9
|
+
// - After firing, advance next_run_at to the next cron occurrence.
|
|
10
|
+
//
|
|
11
|
+
// Schedules survive daemon restarts because next_run_at is persisted. On
|
|
12
|
+
// startup we backfill any next_run_at fields that became stale (or are NULL).
|
|
13
|
+
import { listSchedules, listDueSchedules, recordScheduleRun, setScheduleTimestamps, updateNextRun } from "../store/schedules.js";
|
|
14
|
+
import { getSquad } from "../store/squads.js";
|
|
15
|
+
import { delegateToAgent } from "./agents.js";
|
|
16
|
+
import { nextRun } from "./cron.js";
|
|
17
|
+
const TICK_MS = 30_000;
|
|
18
|
+
const AGENDA_BLOCKS = {
|
|
19
|
+
triage: `**Triage**
|
|
20
|
+
- Use the GitHub CLI (\`gh issue list\`) to pull open issues with the \`needs-triage\` label.
|
|
21
|
+
- For each issue: read the body and decide on appropriate labels (priority, area, type, etc).
|
|
22
|
+
- Apply labels with \`gh issue edit <num> --add-label "..."\` and remove \`needs-triage\` once labelled.
|
|
23
|
+
- If the issue lacks information, post a clarifying comment with \`gh issue comment\` instead of labelling, and leave \`needs-triage\` on it.`,
|
|
24
|
+
prioritize: `**Prioritize**
|
|
25
|
+
- Identify open issues that are ready to be worked on: properly labelled, not blocked, no \`needs-triage\` or \`needs-review\` label, no open PR already addressing them.
|
|
26
|
+
- Rank by priority labels and surface the top candidate.
|
|
27
|
+
- After the stand-up, the team lead should immediately begin work on the highest-priority ready issue by delegating it to the right teammate.`,
|
|
28
|
+
ideation: `**Ideation**
|
|
29
|
+
- Brainstorm 1–3 concrete improvements or new features for the project.
|
|
30
|
+
- Discuss as a team (use \`delegate_to_teammate\` to gather input from members whose expertise fits the idea).
|
|
31
|
+
- For each idea the team agrees on, create a GitHub issue with \`gh issue create\` tagged with the \`needs-review\` label so the human can approve it before work begins.`,
|
|
32
|
+
};
|
|
33
|
+
function buildStandupPrompt(squad, schedule) {
|
|
34
|
+
const blocks = schedule.agenda
|
|
35
|
+
.map((item) => AGENDA_BLOCKS[item] ?? `**${item}** _(no built-in template — improvise)_`)
|
|
36
|
+
.join("\n\n");
|
|
37
|
+
const notes = schedule.notes ? `\n\n**Operator notes:** ${schedule.notes}` : "";
|
|
38
|
+
return `# Scheduled stand-up: ${schedule.name}
|
|
39
|
+
|
|
40
|
+
You are the team lead for the **${squad.name}** squad (\`${squad.slug}\`). Run a stand-up meeting now.
|
|
41
|
+
|
|
42
|
+
**Project path:** \`${squad.project_path}\` — \`cd\` here before invoking the GitHub CLI so it picks up the right repo.
|
|
43
|
+
|
|
44
|
+
**Agenda** (work through these in order; use \`delegate_to_teammate\` to pull in the right specialist for each item):
|
|
45
|
+
|
|
46
|
+
${blocks}
|
|
47
|
+
|
|
48
|
+
When you finish the agenda, summarise what was triaged, what was prioritised (and what work you've kicked off), and what new issues were filed during ideation.${notes}`;
|
|
49
|
+
}
|
|
50
|
+
let timer;
|
|
51
|
+
const inFlight = new Set();
|
|
52
|
+
async function fireSchedule(schedule) {
|
|
53
|
+
if (inFlight.has(schedule.id))
|
|
54
|
+
return;
|
|
55
|
+
const squad = getSquad(schedule.squad_slug);
|
|
56
|
+
if (!squad) {
|
|
57
|
+
console.error(`[io] scheduler: squad "${schedule.squad_slug}" missing for schedule ${schedule.id}; disabling next_run_at`);
|
|
58
|
+
updateNextRun(schedule.id, null);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
inFlight.add(schedule.id);
|
|
62
|
+
const ranAt = new Date();
|
|
63
|
+
let nextIso = null;
|
|
64
|
+
try {
|
|
65
|
+
nextIso = nextRun(schedule.cron_expr, ranAt).toISOString();
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
console.error(`[io] scheduler: cron parse error for schedule ${schedule.id}:`, err instanceof Error ? err.message : err);
|
|
69
|
+
}
|
|
70
|
+
recordScheduleRun(schedule.id, ranAt, nextIso);
|
|
71
|
+
const prompt = buildStandupPrompt({ name: squad.name, slug: squad.slug, project_path: squad.project_path }, schedule);
|
|
72
|
+
console.log(`[io] scheduler: firing schedule "${schedule.name}" for squad "${squad.slug}" (next run: ${nextIso ?? "never"})`);
|
|
73
|
+
try {
|
|
74
|
+
await delegateToAgent(squad.slug, prompt, () => {
|
|
75
|
+
// No-op: result is recorded on the agent task; the standup is fire-and-forget.
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
console.error(`[io] scheduler: failed to delegate stand-up for schedule ${schedule.id}:`, err instanceof Error ? err.message : err);
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
inFlight.delete(schedule.id);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async function tick() {
|
|
86
|
+
let due;
|
|
87
|
+
try {
|
|
88
|
+
due = listDueSchedules(new Date());
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
console.error("[io] scheduler tick failed:", err instanceof Error ? err.message : err);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
for (const s of due) {
|
|
95
|
+
// Sequential — avoid stampeding multiple stand-ups simultaneously.
|
|
96
|
+
await fireSchedule(s);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Backfill next_run_at for any schedules where it's NULL or already in the past
|
|
101
|
+
* but should not have fired (e.g. daemon was offline). We deliberately advance
|
|
102
|
+
* to the next future occurrence rather than replaying missed runs.
|
|
103
|
+
*/
|
|
104
|
+
export function reconcileSchedules(now = new Date()) {
|
|
105
|
+
for (const s of listSchedules()) {
|
|
106
|
+
if (!s.enabled)
|
|
107
|
+
continue;
|
|
108
|
+
let needsUpdate = false;
|
|
109
|
+
if (!s.next_run_at) {
|
|
110
|
+
needsUpdate = true;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
const next = new Date(s.next_run_at);
|
|
114
|
+
if (Number.isNaN(next.getTime()) || next <= now)
|
|
115
|
+
needsUpdate = true;
|
|
116
|
+
}
|
|
117
|
+
if (!needsUpdate)
|
|
118
|
+
continue;
|
|
119
|
+
try {
|
|
120
|
+
const next = nextRun(s.cron_expr, now);
|
|
121
|
+
updateNextRun(s.id, next.toISOString());
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
console.error(`[io] scheduler: invalid cron "${s.cron_expr}" on schedule ${s.id}; disabling next_run_at:`, err instanceof Error ? err.message : err);
|
|
125
|
+
updateNextRun(s.id, null);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
export function startScheduler() {
|
|
130
|
+
if (timer)
|
|
131
|
+
return;
|
|
132
|
+
reconcileSchedules();
|
|
133
|
+
timer = setInterval(() => {
|
|
134
|
+
void tick();
|
|
135
|
+
}, TICK_MS);
|
|
136
|
+
if (typeof timer.unref === "function")
|
|
137
|
+
timer.unref();
|
|
138
|
+
console.log(`[io] Scheduler started (tick every ${TICK_MS / 1000}s)`);
|
|
139
|
+
}
|
|
140
|
+
export function stopScheduler() {
|
|
141
|
+
if (!timer)
|
|
142
|
+
return;
|
|
143
|
+
clearInterval(timer);
|
|
144
|
+
timer = undefined;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Manually fire a schedule. Used by squad_schedule_run_now.
|
|
148
|
+
*
|
|
149
|
+
* Snapshots last_run_at and next_run_at before firing and restores them
|
|
150
|
+
* after, so a manual fire never disturbs the regular schedule (a user
|
|
151
|
+
* testing a 05:00 schedule at 04:30 should not have today's 05:00 run
|
|
152
|
+
* skipped or shifted). The fireSchedule path itself advances both fields
|
|
153
|
+
* because that's correct for an automatic firing — only manual runs need
|
|
154
|
+
* to leave the schedule untouched.
|
|
155
|
+
*/
|
|
156
|
+
export async function runScheduleNow(scheduleId) {
|
|
157
|
+
const all = listSchedules();
|
|
158
|
+
const s = all.find((x) => x.id === scheduleId);
|
|
159
|
+
if (!s)
|
|
160
|
+
return { ok: false, error: `Schedule ${scheduleId} not found` };
|
|
161
|
+
const previousLast = s.last_run_at;
|
|
162
|
+
const previousNext = s.next_run_at;
|
|
163
|
+
try {
|
|
164
|
+
await fireSchedule(s);
|
|
165
|
+
}
|
|
166
|
+
finally {
|
|
167
|
+
setScheduleTimestamps(scheduleId, previousLast, previousNext);
|
|
168
|
+
}
|
|
169
|
+
return { ok: true };
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=scheduler.js.map
|
|
@@ -93,12 +93,37 @@ Every squad should have a **team lead**. After building the team with \`squad_ad
|
|
|
93
93
|
### Peer Review & QA Approvals
|
|
94
94
|
When an agent finishes a task, the other squad members automatically review the work and vote APPROVED or REJECTED. Reviews are recorded and emitted as \`task.review\` events.
|
|
95
95
|
|
|
96
|
-
-
|
|
97
|
-
-
|
|
96
|
+
- **Required**: every squad must have at least one agent designated as QA via \`squad_set_qa\`, AND at least one agent whose role title implies a testing/quality focus (e.g. role contains "test", "qa", or "quality"). Both can be the same agent.
|
|
97
|
+
- \`squad_status\`, \`squad_agents\`, and \`squad_delegate\` will surface a ⚠️ warning when either is missing. Delegation is not blocked, but you should fix the gap before promoting work.
|
|
98
|
+
- **QA agents and the team lead have veto power**: if any QA reviewer or the team lead rejects, the PR stays as a draft. The lead's veto is automatic — no need to also designate them as QA.
|
|
98
99
|
- Non-QA rejections are advisory — they're recorded but don't block promotion.
|
|
99
100
|
- When all QA approvals pass (or no QA agents exist) and the task result contains a GitHub PR URL, the PR is automatically promoted from draft to ready via \`gh pr ready\`.
|
|
100
101
|
- Use \`squad_task_reviews\` to inspect the reviews on any completed task.
|
|
101
102
|
|
|
103
|
+
### Squad Build Checklist
|
|
104
|
+
After \`squad_create\`, before delegating real work:
|
|
105
|
+
1. Add agents with \`squad_add_agent\` (use roles tailored to the project's stack).
|
|
106
|
+
2. Include at least one **test/quality engineer** role (e.g. "Integration Test Engineer", "QA Specialist", "Quality Reviewer").
|
|
107
|
+
3. Designate a team lead with \`squad_set_lead\`.
|
|
108
|
+
4. Designate at least one QA reviewer with \`squad_set_qa\` (often the same agent as the test engineer).
|
|
109
|
+
|
|
110
|
+
### Scheduled Stand-ups
|
|
111
|
+
Squads can be put on a recurring cron-style schedule. At the scheduled time IO wakes the team lead, who runs the agenda by delegating to teammates. This runs in the background even when no human is in the TUI/Telegram.
|
|
112
|
+
|
|
113
|
+
- \`squad_schedule_create\` — create a recurring stand-up. Cron uses standard 5-field syntax: "minute hour day-of-month month day-of-week". Examples: \`0 5 * * *\` (daily 5AM), \`0 9 * * 1-5\` (9AM weekdays), \`30 14 * * 1\` (Mondays 14:30).
|
|
114
|
+
- Built-in agenda items: \`triage\` (process \`needs-triage\` issues), \`prioritize\` (pick highest-priority ready work and start on it), \`ideation\` (brainstorm and open \`needs-review\` issues for the human). Custom items are passed verbatim to the lead.
|
|
115
|
+
- \`squad_schedule_list\`, \`squad_schedule_pause\`, \`squad_schedule_resume\`, \`squad_schedule_delete\`, \`squad_schedule_run_now\` round out the lifecycle.
|
|
116
|
+
|
|
117
|
+
When a user asks something like "have the IO squad meet every weekday at 5AM to triage and prioritize", call \`squad_schedule_create\` with \`cron: "0 5 * * 1-5"\` and \`agenda: ["triage", "prioritize"]\`.
|
|
118
|
+
|
|
119
|
+
### IO-level Schedules (squad-independent)
|
|
120
|
+
For recurring work that doesn't belong to any project squad — daily digests, periodic health checks, monitoring loops, reminders — use the IO-level scheduler instead of inventing a placeholder squad.
|
|
121
|
+
|
|
122
|
+
- \`schedule_create\` — create a recurring task. Each tick the configured prompt is delivered to the orchestrator as if a user had typed it. Cron is the same 5-field syntax as squad schedules.
|
|
123
|
+
- \`schedule_list\`, \`schedule_pause\`, \`schedule_resume\`, \`schedule_delete\`, \`schedule_run_now\` mirror the squad-schedule lifecycle.
|
|
124
|
+
|
|
125
|
+
When a user asks "remind me at 9AM every day to check the dashboard" or "every hour, summarize my open PRs", reach for \`schedule_create\` — not a squad.
|
|
126
|
+
|
|
102
127
|
### Agent Roles Are Dynamic
|
|
103
128
|
**Do NOT use generic roles** like "developer" or "tester". Analyze the project first and create roles that match its actual technology stack. Examples:
|
|
104
129
|
- IO project → "Copilot SDK Specialist", "Vue.js Frontend Dev", "Express API Engineer"
|