@kendoo.agentdesk/agentdesk 0.9.6 → 0.9.7
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/cli/agents.mjs +53 -81
- package/cli/init.mjs +51 -0
- package/cli/prompt.mjs +1 -2
- package/cli/stream-parser.mjs +1 -1
- package/cli/team.mjs +34 -0
- package/cli/tracker-check.mjs +211 -0
- package/package.json +1 -1
- package/prompts/team.md +98 -352
package/cli/agents.mjs
CHANGED
|
@@ -4,36 +4,32 @@ export const BUILT_IN_AGENTS = {
|
|
|
4
4
|
Jane: {
|
|
5
5
|
badge: "●● JANE ●●",
|
|
6
6
|
role: "Product Analyst / Team Lead",
|
|
7
|
-
description: "
|
|
8
|
-
groundRules: "Jane
|
|
9
|
-
|
|
10
|
-
planning: "Requirements Summary (what we're building, acceptance criteria, scope boundaries). If this task involves UI changes, Jane declares it a UI task and tells Luna to define screenshot requirements.",
|
|
7
|
+
description: "leads the session, clarifies requirements, coordinates the team, manages tracker status",
|
|
8
|
+
groundRules: "Jane focuses on requirements, scope, and coordination — she does not read code. She creates tracker tasks when needed, manages status transitions, and posts the session start/end comments.",
|
|
9
|
+
planning: "Requirements: what we're building, acceptance criteria, scope. Flags UI tasks for Luna.",
|
|
11
10
|
execution: {
|
|
12
11
|
step: "Jane wraps up",
|
|
13
12
|
tasks: [
|
|
14
|
-
"Transition
|
|
15
|
-
"
|
|
16
|
-
"Post
|
|
17
|
-
"Summarize the session.",
|
|
13
|
+
"Transition task to 'In Review'.",
|
|
14
|
+
"Verify PR description is clear.",
|
|
15
|
+
"Post final summary comment on tracker.",
|
|
18
16
|
],
|
|
19
|
-
order: 99,
|
|
17
|
+
order: 99,
|
|
20
18
|
},
|
|
21
19
|
},
|
|
22
20
|
Dennis: {
|
|
23
21
|
badge: "■■ DENNIS ■■",
|
|
24
22
|
role: "Senior Developer",
|
|
25
|
-
description: "
|
|
26
|
-
groundRules: "Dennis
|
|
27
|
-
codePrinciple: "
|
|
28
|
-
|
|
29
|
-
planning: "Implementation Plan (files to modify, approach, complexity S/M/L)",
|
|
23
|
+
description: "implements the solution, verifies technical feasibility",
|
|
24
|
+
groundRules: "Dennis verifies assumptions about the codebase with tools before committing to an approach.",
|
|
25
|
+
codePrinciple: "Extract logic out of components/templates into functions, hooks, or services.",
|
|
26
|
+
planning: "Implementation plan: files to modify, approach, complexity (S/M/L)",
|
|
30
27
|
execution: {
|
|
31
28
|
step: "Dennis implements",
|
|
32
29
|
tasks: [
|
|
33
|
-
"Create
|
|
34
|
-
"Implement the changes according to the agreed plan.",
|
|
30
|
+
"Create branch, implement changes per the plan.",
|
|
35
31
|
"Run linter and build to verify.",
|
|
36
|
-
"Commit
|
|
32
|
+
"Commit.",
|
|
37
33
|
],
|
|
38
34
|
order: 1,
|
|
39
35
|
},
|
|
@@ -41,17 +37,15 @@ export const BUILT_IN_AGENTS = {
|
|
|
41
37
|
Sam: {
|
|
42
38
|
badge: "◆◆ SAM ◆◆",
|
|
43
39
|
role: "Architecture Auditor",
|
|
44
|
-
description: "
|
|
45
|
-
groundRules: "Sam
|
|
46
|
-
codePrinciple: "
|
|
47
|
-
|
|
48
|
-
planning: "Architecture Review (existing violations, whether approach is clean)",
|
|
40
|
+
description: "guards code architecture and separation of concerns",
|
|
41
|
+
groundRules: "Sam backs claims with file:line references.",
|
|
42
|
+
codePrinciple: "Guards separation of concerns. Cites file and line number.",
|
|
43
|
+
planning: "Architecture review: existing patterns, whether approach is clean",
|
|
49
44
|
execution: {
|
|
50
|
-
step: "Sam
|
|
45
|
+
step: "Sam audits",
|
|
51
46
|
tasks: [
|
|
52
|
-
"Read
|
|
53
|
-
"
|
|
54
|
-
"If violations found, the developer fixes them before proceeding.",
|
|
47
|
+
"Read changed files, check for architecture violations.",
|
|
48
|
+
"If violations found, developer fixes before proceeding.",
|
|
55
49
|
],
|
|
56
50
|
order: 2,
|
|
57
51
|
},
|
|
@@ -59,22 +53,18 @@ export const BUILT_IN_AGENTS = {
|
|
|
59
53
|
Bart: {
|
|
60
54
|
badge: "▲▲ BART ▲▲",
|
|
61
55
|
role: "QA Engineer",
|
|
62
|
-
description: "
|
|
63
|
-
groundRules: "Bart
|
|
64
|
-
codePrinciple: "Flag
|
|
65
|
-
|
|
66
|
-
planning: "Test Plan (acceptance criteria, key test cases, edge cases)",
|
|
56
|
+
description: "reviews quality, edge cases, creates PR, captures screenshots",
|
|
57
|
+
groundRules: "Bart identifies risks and edge cases before approving.",
|
|
58
|
+
codePrinciple: "Flag inline logic in components/templates.",
|
|
59
|
+
planning: "Test plan: acceptance criteria, key edge cases",
|
|
67
60
|
execution: {
|
|
68
|
-
step: "Bart reviews",
|
|
61
|
+
step: "Bart reviews & creates PR",
|
|
69
62
|
tasks: [
|
|
70
|
-
"Read
|
|
71
|
-
"Check calculations, edge cases, error handling.",
|
|
63
|
+
"Read all changed files. Check edge cases, error handling.",
|
|
72
64
|
"Run linter and build.",
|
|
73
|
-
"
|
|
74
|
-
"
|
|
75
|
-
"Post
|
|
76
|
-
"Post screenshots to the task tracker as a separate comment (not inside badge blocks).",
|
|
77
|
-
"Approve the PR if clean.",
|
|
65
|
+
"Capture screenshots if UI task (per Luna's plan).",
|
|
66
|
+
"Push and create PR. Post PR link on tracker.",
|
|
67
|
+
"Post screenshots as separate tracker comment.",
|
|
78
68
|
],
|
|
79
69
|
order: 4,
|
|
80
70
|
},
|
|
@@ -82,17 +72,15 @@ export const BUILT_IN_AGENTS = {
|
|
|
82
72
|
Vera: {
|
|
83
73
|
badge: "◈◈ VERA ◈◈",
|
|
84
74
|
role: "Test Engineer",
|
|
85
|
-
description: "writes unit and regression tests for changed code
|
|
86
|
-
groundRules: "Vera
|
|
87
|
-
codePrinciple: "
|
|
88
|
-
|
|
89
|
-
planning: "Test Plan (which functions need tests, regression tests)",
|
|
75
|
+
description: "writes unit and regression tests for changed code",
|
|
76
|
+
groundRules: "Vera identifies which functions need test coverage.",
|
|
77
|
+
codePrinciple: "Test logic directly — don't mount UI to test outcomes. Mirror service/utility structure.",
|
|
78
|
+
planning: "Test coverage: which functions need tests, regression cases",
|
|
90
79
|
execution: {
|
|
91
80
|
step: "Vera writes tests",
|
|
92
81
|
tasks: [
|
|
93
|
-
"
|
|
94
|
-
"Write unit tests following existing
|
|
95
|
-
"Run tests to verify they pass.",
|
|
82
|
+
"Identify testable functions in changed files.",
|
|
83
|
+
"Write unit tests following existing patterns. Run and verify.",
|
|
96
84
|
"Commit test files.",
|
|
97
85
|
],
|
|
98
86
|
order: 3,
|
|
@@ -101,21 +89,16 @@ export const BUILT_IN_AGENTS = {
|
|
|
101
89
|
Luna: {
|
|
102
90
|
badge: "☾☾ LUNA ☾☾",
|
|
103
91
|
role: "UX/UI Designer",
|
|
104
|
-
description: "
|
|
105
|
-
groundRules: "Luna
|
|
106
|
-
codePrinciple: "Reviews
|
|
107
|
-
|
|
108
|
-
planning: "UX Review (visual impact, layout concerns, accessibility checklist, interaction improvements). For UI tasks: define which pages/views need screenshots and whether desktop, mobile, or both are relevant.",
|
|
92
|
+
description: "reviews UI changes for visual consistency, accessibility, and UX",
|
|
93
|
+
groundRules: "Luna references specific components and proposes exact CSS fixes.",
|
|
94
|
+
codePrinciple: "Reviews visual hierarchy, spacing, color, typography, responsive behavior, accessibility (WCAG).",
|
|
95
|
+
planning: "UX review: visual impact, accessibility. For UI tasks: define screenshot plan (pages, viewports).",
|
|
109
96
|
execution: {
|
|
110
|
-
step: "Luna reviews UI
|
|
97
|
+
step: "Luna reviews UI (if applicable)",
|
|
111
98
|
tasks: [
|
|
112
|
-
"Read
|
|
113
|
-
"
|
|
114
|
-
"
|
|
115
|
-
"Check responsive behavior and interaction flow.",
|
|
116
|
-
"If issues found, propose specific fixes (exact CSS values, spacing, colors). The developer implements them before proceeding.",
|
|
117
|
-
"Define the screenshot plan: which pages/routes to capture and which viewports (desktop, mobile, or both). Hand this plan to Bart.",
|
|
118
|
-
"Skip this step if the task has no UI impact.",
|
|
99
|
+
"Read changed UI files. Check visual consistency and accessibility.",
|
|
100
|
+
"Propose specific fixes if needed. Developer implements.",
|
|
101
|
+
"Hand screenshot plan to Bart. Skip if no UI impact.",
|
|
119
102
|
],
|
|
120
103
|
order: 2.1,
|
|
121
104
|
},
|
|
@@ -123,19 +106,16 @@ export const BUILT_IN_AGENTS = {
|
|
|
123
106
|
Mark: {
|
|
124
107
|
badge: "✦✦ MARK ✦✦",
|
|
125
108
|
role: "Content Writer",
|
|
126
|
-
description: "
|
|
127
|
-
groundRules: "Mark
|
|
128
|
-
codePrinciple: "
|
|
129
|
-
|
|
130
|
-
planning: "Content Review (user-facing text audit, tone, clarity, empty states, error messages)",
|
|
109
|
+
description: "reviews user-facing text for clarity, tone, and consistency",
|
|
110
|
+
groundRules: "Mark reviews all labels, buttons, error messages, tooltips, and empty states.",
|
|
111
|
+
codePrinciple: "Rewrites vague or jargon-heavy copy into clear, concise language. Ensures consistent tone.",
|
|
112
|
+
planning: "Content review: user-facing text audit, tone consistency",
|
|
131
113
|
execution: {
|
|
132
114
|
step: "Mark reviews content (if applicable)",
|
|
133
115
|
tasks: [
|
|
134
|
-
"Read
|
|
135
|
-
"
|
|
136
|
-
"
|
|
137
|
-
"Propose exact replacement strings. The developer implements them.",
|
|
138
|
-
"Skip this step if the task has no user-facing text changes.",
|
|
116
|
+
"Read changed files with user-facing text.",
|
|
117
|
+
"Propose exact replacement strings if needed. Developer implements.",
|
|
118
|
+
"Skip if no user-facing text changes.",
|
|
139
119
|
],
|
|
140
120
|
order: 2.2,
|
|
141
121
|
},
|
|
@@ -175,7 +155,6 @@ export function resolveTeam(config) {
|
|
|
175
155
|
role: custom.role || "Team Member",
|
|
176
156
|
description: custom.role || "Custom team member",
|
|
177
157
|
groundRules: custom.when ? `${custom.name} is invoked: ${custom.when}. How to use: ${custom.how || "as needed"}.` : "",
|
|
178
|
-
brainstorm: { tag: "SAY", focus: custom.role || "general input" },
|
|
179
158
|
planning: `${custom.role || custom.name} Review`,
|
|
180
159
|
execution: custom.when ? {
|
|
181
160
|
step: `${custom.name} reviews`,
|
|
@@ -204,7 +183,6 @@ export function resolveTeam(config) {
|
|
|
204
183
|
role: entry.role || "Team Member",
|
|
205
184
|
description: entry.description || entry.role || "Custom team member",
|
|
206
185
|
groundRules: entry.groundRules || "",
|
|
207
|
-
brainstorm: entry.brainstorm || { tag: "SAY", focus: entry.role || "general input" },
|
|
208
186
|
planning: entry.planning || `${entry.role || entry.name} Review`,
|
|
209
187
|
execution: entry.execution || null,
|
|
210
188
|
});
|
|
@@ -245,9 +223,9 @@ export function generateTeamPrompt(team) {
|
|
|
245
223
|
|
|
246
224
|
// Ground rules
|
|
247
225
|
const rules = [];
|
|
248
|
-
rules.push(`1. Each agent speaks
|
|
249
|
-
rules.push(`2. ALL text output MUST be prefixed with the acting agent's badge
|
|
250
|
-
rules.push(`3.
|
|
226
|
+
rules.push(`1. Each agent speaks prefixed with their badge (e.g., "${team[0].badge} ...").`);
|
|
227
|
+
rules.push(`2. ALL text output MUST be prefixed with the acting agent's badge.`);
|
|
228
|
+
rules.push(`3. Raise concerns when you see problems — don't rubber-stamp.`);
|
|
251
229
|
let ruleNum = 4;
|
|
252
230
|
for (const a of team) {
|
|
253
231
|
if (a.groundRules) {
|
|
@@ -255,7 +233,6 @@ export function generateTeamPrompt(team) {
|
|
|
255
233
|
ruleNum++;
|
|
256
234
|
}
|
|
257
235
|
}
|
|
258
|
-
rules.push(`${ruleNum}. Every statement should add value — no filler, no repeating what someone else already said.`);
|
|
259
236
|
sections.groundRules = rules.join("\n");
|
|
260
237
|
|
|
261
238
|
// Code principles
|
|
@@ -264,11 +241,6 @@ export function generateTeamPrompt(team) {
|
|
|
264
241
|
.map(a => `- ${a.name}: ${a.codePrinciple}`);
|
|
265
242
|
sections.codePrinciples = principles.length > 0 ? principles.join("\n") : "";
|
|
266
243
|
|
|
267
|
-
// Brainstorm order
|
|
268
|
-
sections.brainstormOrder = team.map((a, i) =>
|
|
269
|
-
`${i + 1}. ${a.badge} [${a.brainstorm.tag}] ${a.brainstorm.focus}`
|
|
270
|
-
).join("\n");
|
|
271
|
-
|
|
272
244
|
// Planning presentations
|
|
273
245
|
sections.planningOrder = team.map(a =>
|
|
274
246
|
`${a.badge} ${a.planning}`
|
package/cli/init.mjs
CHANGED
|
@@ -7,6 +7,7 @@ import { detectProject } from "./detect.mjs";
|
|
|
7
7
|
import { loadConfig } from "./config.mjs";
|
|
8
8
|
import { getStoredApiKey } from "./login.mjs";
|
|
9
9
|
import { registerLocalProject } from "./projects.mjs";
|
|
10
|
+
import { checkTrackerPermissions, resolveCredentialsFromEnv } from "./tracker-check.mjs";
|
|
10
11
|
|
|
11
12
|
const SERVER = process.env.AGENTDESK_SERVER || "https://agentdesk.live";
|
|
12
13
|
|
|
@@ -114,6 +115,56 @@ export async function runInit(cwd) {
|
|
|
114
115
|
console.log("");
|
|
115
116
|
}
|
|
116
117
|
|
|
118
|
+
// --- Verify tracker permissions ---
|
|
119
|
+
if (tracker) {
|
|
120
|
+
console.log(" Checking tracker permissions...");
|
|
121
|
+
const envPath = join(cwd, ".env");
|
|
122
|
+
let dotEnv = {};
|
|
123
|
+
if (existsSync(envPath)) {
|
|
124
|
+
for (const line of readFileSync(envPath, "utf-8").split("\n")) {
|
|
125
|
+
const trimmed = line.trim();
|
|
126
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
127
|
+
const eq = trimmed.indexOf("=");
|
|
128
|
+
if (eq !== -1) dotEnv[trimmed.slice(0, eq)] = trimmed.slice(eq + 1);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Also try fetching credentials from server
|
|
133
|
+
const apiKey = loadApiKey(cwd);
|
|
134
|
+
let serverCreds = {};
|
|
135
|
+
if (apiKey) {
|
|
136
|
+
try {
|
|
137
|
+
const res = await fetch(`${SERVER}/api/projects/${finalProjectKey}/settings/credentials`, {
|
|
138
|
+
headers: { "x-api-key": apiKey },
|
|
139
|
+
signal: AbortSignal.timeout(5000),
|
|
140
|
+
});
|
|
141
|
+
if (res.ok) serverCreds = await res.json();
|
|
142
|
+
} catch {}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const credentials = resolveCredentialsFromEnv({ ...dotEnv, ...serverCreds });
|
|
146
|
+
const check = await checkTrackerPermissions({ tracker, config, credentials });
|
|
147
|
+
|
|
148
|
+
if (!check.ok) {
|
|
149
|
+
console.log("");
|
|
150
|
+
console.log(" ⚠ Tracker permission issues:");
|
|
151
|
+
for (const err of check.errors) {
|
|
152
|
+
console.log(` • ${err}`);
|
|
153
|
+
}
|
|
154
|
+
console.log("");
|
|
155
|
+
const proceed = await ask(rl, " Continue anyway? (y/N): ");
|
|
156
|
+
if (proceed.trim().toLowerCase() !== "y") {
|
|
157
|
+
console.log(" Setup cancelled. Fix the issues above and run 'agentdesk init' again.");
|
|
158
|
+
rl.close();
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
console.log("");
|
|
162
|
+
} else {
|
|
163
|
+
console.log(" ✓ Tracker permissions verified (read, create, update)");
|
|
164
|
+
console.log("");
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
117
168
|
// --- Save .agentdesk.json ---
|
|
118
169
|
let merged = {};
|
|
119
170
|
if (hasConfig) {
|
package/cli/prompt.mjs
CHANGED
|
@@ -17,7 +17,6 @@ export function buildPrompt({ taskId, taskLink, description, createTask, tracker
|
|
|
17
17
|
prompt = prompt.replace(/\{\{SPEAKING_ORDER\}\}/g, teamSections.speakingOrder);
|
|
18
18
|
prompt = prompt.replace(/\{\{GROUND_RULES\}\}/g, teamSections.groundRules);
|
|
19
19
|
prompt = prompt.replace(/\{\{CODE_PRINCIPLES\}\}/g, teamSections.codePrinciples);
|
|
20
|
-
prompt = prompt.replace(/\{\{BRAINSTORM_ORDER\}\}/g, teamSections.brainstormOrder);
|
|
21
20
|
prompt = prompt.replace(/\{\{PLANNING_ORDER\}\}/g, teamSections.planningOrder);
|
|
22
21
|
prompt = prompt.replace(/\{\{EXECUTION_STEPS\}\}/g, teamSections.executionSteps);
|
|
23
22
|
|
|
@@ -49,7 +48,7 @@ export function buildPrompt({ taskId, taskLink, description, createTask, tracker
|
|
|
49
48
|
createInstr += `2. Output the task ID on its own line in this exact format (required for dashboard linking):\n TASK_ID: <identifier>\n Example: TASK_ID: KEN-530\n`;
|
|
50
49
|
createInstr += `3. Immediately set the task status to "In Progress" and post the session start comment.\n`;
|
|
51
50
|
createInstr += `4. Use this new task ID for ALL subsequent tracker operations (comments, status updates, PR linking).\n`;
|
|
52
|
-
createInstr += `\nDo NOT proceed to
|
|
51
|
+
createInstr += `\nDo NOT proceed to PLAN until the task is created, announced, and set to "In Progress".\n`;
|
|
53
52
|
prompt += createInstr;
|
|
54
53
|
}
|
|
55
54
|
|
package/cli/stream-parser.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Shared Claude stream-json parser — used by both `agentdesk team` and `agentdesk daemon`
|
|
2
2
|
|
|
3
|
-
const PHASE_NAMES = ["INTAKE", "
|
|
3
|
+
const PHASE_NAMES = ["INTAKE", "PLAN", "EXECUTION", "REVIEW", "SUMMARY"];
|
|
4
4
|
|
|
5
5
|
function escapeRegex(s) {
|
|
6
6
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
package/cli/team.mjs
CHANGED
|
@@ -10,6 +10,7 @@ import { loadConfig } from "./config.mjs";
|
|
|
10
10
|
import { getStoredApiKey } from "./login.mjs";
|
|
11
11
|
import { resolveTeam, generateTeamPrompt } from "./agents.mjs";
|
|
12
12
|
import { runOrchestrator } from "./orchestrator.mjs";
|
|
13
|
+
import { checkTrackerPermissions, resolveCredentialsFromEnv } from "./tracker-check.mjs";
|
|
13
14
|
|
|
14
15
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
16
|
const CLI_VERSION = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf-8")).version;
|
|
@@ -83,6 +84,39 @@ export async function runTeam(taskId, opts = {}) {
|
|
|
83
84
|
const team = resolveTeam(config);
|
|
84
85
|
const teamSections = generateTeamPrompt(team);
|
|
85
86
|
|
|
87
|
+
// --- Verify tracker permissions before starting session ---
|
|
88
|
+
if (tracker) {
|
|
89
|
+
const projectEnvVars = loadDotEnv(cwd);
|
|
90
|
+
let serverCreds = {};
|
|
91
|
+
if (apiKey) {
|
|
92
|
+
try {
|
|
93
|
+
const res = await fetch(`${agentdeskServer}/api/projects/${config.projectKey || project.name}/settings/credentials`, {
|
|
94
|
+
headers: { "x-api-key": apiKey },
|
|
95
|
+
signal: AbortSignal.timeout(5000),
|
|
96
|
+
});
|
|
97
|
+
if (res.ok) serverCreds = await res.json();
|
|
98
|
+
} catch {}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const credentials = resolveCredentialsFromEnv({ ...projectEnvVars, ...serverCreds });
|
|
102
|
+
const check = await checkTrackerPermissions({ tracker, config, credentials });
|
|
103
|
+
|
|
104
|
+
if (!check.ok) {
|
|
105
|
+
console.log("");
|
|
106
|
+
console.log("Tracker permission issues:");
|
|
107
|
+
for (const err of check.errors) {
|
|
108
|
+
console.log(` • ${err}`);
|
|
109
|
+
}
|
|
110
|
+
console.log("");
|
|
111
|
+
console.log("Fix the issues above or run 'agentdesk init' to reconfigure.");
|
|
112
|
+
console.log("To skip this check, set AGENTDESK_SKIP_TRACKER_CHECK=1");
|
|
113
|
+
if (!process.env.AGENTDESK_SKIP_TRACKER_CHECK) {
|
|
114
|
+
return 1;
|
|
115
|
+
}
|
|
116
|
+
console.log("Skipping check (AGENTDESK_SKIP_TRACKER_CHECK=1)...\n");
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
86
120
|
// --- AgentDesk WebSocket config ---
|
|
87
121
|
const AGENTDESK_URL = process.env.AGENTDESK_URL || "wss://agentdesk.live/ws/agent";
|
|
88
122
|
const sessionId = `${taskId}-${randomUUID().slice(0, 8)}`;
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
// Verify tracker API permissions before starting a session
|
|
2
|
+
// Checks: read tasks, create tasks, update tasks
|
|
3
|
+
|
|
4
|
+
import { execSync } from "child_process";
|
|
5
|
+
|
|
6
|
+
const LINEAR_API = "https://api.linear.app/graphql";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Check tracker permissions. Returns { ok, errors[] }.
|
|
10
|
+
* Each error is a string describing what permission is missing.
|
|
11
|
+
*/
|
|
12
|
+
export async function checkTrackerPermissions({ tracker, config, credentials }) {
|
|
13
|
+
if (!tracker) return { ok: true, errors: [] };
|
|
14
|
+
|
|
15
|
+
switch (tracker) {
|
|
16
|
+
case "linear":
|
|
17
|
+
return checkLinear(config.linear || {}, credentials);
|
|
18
|
+
case "jira":
|
|
19
|
+
return checkJira(config.jira || {}, credentials);
|
|
20
|
+
case "github":
|
|
21
|
+
return checkGitHub(config.github || {}, credentials);
|
|
22
|
+
default:
|
|
23
|
+
return { ok: true, errors: [] };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function checkLinear({ teamKey, workspace }, creds) {
|
|
28
|
+
const apiKey = creds.LINEAR_API_KEY;
|
|
29
|
+
if (!apiKey) return { ok: false, errors: ["Missing LINEAR_API_KEY — configure it in the AgentDesk dashboard or .env"] };
|
|
30
|
+
|
|
31
|
+
const errors = [];
|
|
32
|
+
|
|
33
|
+
// Check read: fetch viewer and team info
|
|
34
|
+
try {
|
|
35
|
+
const viewerRes = await gql(apiKey, `{ viewer { id name } }`);
|
|
36
|
+
if (viewerRes.errors) {
|
|
37
|
+
return { ok: false, errors: ["LINEAR_API_KEY is invalid or expired"] };
|
|
38
|
+
}
|
|
39
|
+
} catch (e) {
|
|
40
|
+
return { ok: false, errors: [`Cannot reach Linear API: ${e.message}`] };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Check read issues: list 1 issue from the team
|
|
44
|
+
if (teamKey) {
|
|
45
|
+
try {
|
|
46
|
+
const issuesRes = await gql(apiKey, `{ issues(filter: { team: { key: { eq: "${teamKey}" } } }, first: 1) { nodes { id identifier } } }`);
|
|
47
|
+
if (issuesRes.errors) {
|
|
48
|
+
errors.push(`Cannot read issues for team ${teamKey}: ${issuesRes.errors[0]?.message}`);
|
|
49
|
+
}
|
|
50
|
+
} catch {
|
|
51
|
+
errors.push(`Cannot read issues for team ${teamKey}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Check create: attempt issueCreate dry-run — Linear doesn't have a dry-run mode,
|
|
56
|
+
// so we verify the team exists (which is required for creating issues)
|
|
57
|
+
if (teamKey) {
|
|
58
|
+
try {
|
|
59
|
+
const teamRes = await gql(apiKey, `{ teams(filter: { key: { eq: "${teamKey}" } }) { nodes { id name } } }`);
|
|
60
|
+
if (teamRes.errors || !teamRes.data?.teams?.nodes?.length) {
|
|
61
|
+
errors.push(`Team "${teamKey}" not found — cannot create issues`);
|
|
62
|
+
}
|
|
63
|
+
} catch {
|
|
64
|
+
errors.push(`Cannot verify team "${teamKey}" for issue creation`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Check update: verify we can read workflow states (needed for status transitions)
|
|
69
|
+
if (teamKey) {
|
|
70
|
+
try {
|
|
71
|
+
const statesRes = await gql(apiKey, `{ workflowStates(filter: { team: { key: { eq: "${teamKey}" } } }, first: 1) { nodes { id name } } }`);
|
|
72
|
+
if (statesRes.errors) {
|
|
73
|
+
errors.push(`Cannot read workflow states for team ${teamKey} — status updates will fail`);
|
|
74
|
+
}
|
|
75
|
+
} catch {
|
|
76
|
+
errors.push(`Cannot verify workflow states for team ${teamKey}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return { ok: errors.length === 0, errors };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function checkJira({ baseUrl, project }, creds) {
|
|
84
|
+
const email = creds.JIRA_EMAIL;
|
|
85
|
+
const token = creds.JIRA_API_TOKEN;
|
|
86
|
+
|
|
87
|
+
if (!email || !token) return { ok: false, errors: ["Missing JIRA_EMAIL or JIRA_API_TOKEN — configure them in the AgentDesk dashboard or .env"] };
|
|
88
|
+
if (!baseUrl) return { ok: false, errors: ["Missing Jira base URL — run 'agentdesk init' to configure"] };
|
|
89
|
+
|
|
90
|
+
const errors = [];
|
|
91
|
+
const auth = "Basic " + Buffer.from(`${email}:${token}`).toString("base64");
|
|
92
|
+
|
|
93
|
+
// Use Jira's mypermissions endpoint to check all permissions at once
|
|
94
|
+
const permissionsToCheck = "BROWSE_PROJECTS,CREATE_ISSUES,EDIT_ISSUES,ADD_COMMENTS,TRANSITION_ISSUES";
|
|
95
|
+
try {
|
|
96
|
+
const url = `${baseUrl}/rest/api/3/mypermissions?permissions=${permissionsToCheck}` +
|
|
97
|
+
(project ? `&projectKey=${project}` : "");
|
|
98
|
+
const res = await fetch(url, {
|
|
99
|
+
headers: { Authorization: auth, Accept: "application/json" },
|
|
100
|
+
signal: AbortSignal.timeout(10000),
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
if (res.status === 401) {
|
|
104
|
+
return { ok: false, errors: ["Jira authentication failed — check JIRA_EMAIL and JIRA_API_TOKEN"] };
|
|
105
|
+
}
|
|
106
|
+
if (res.status === 403) {
|
|
107
|
+
return { ok: false, errors: ["Jira access forbidden — your account may lack access to this project"] };
|
|
108
|
+
}
|
|
109
|
+
if (!res.ok) {
|
|
110
|
+
return { ok: false, errors: [`Jira API error (${res.status}) — check your base URL: ${baseUrl}`] };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const data = await res.json();
|
|
114
|
+
const perms = data.permissions || {};
|
|
115
|
+
|
|
116
|
+
const permMap = {
|
|
117
|
+
BROWSE_PROJECTS: "read tasks",
|
|
118
|
+
CREATE_ISSUES: "create tasks",
|
|
119
|
+
EDIT_ISSUES: "update tasks",
|
|
120
|
+
ADD_COMMENTS: "add comments",
|
|
121
|
+
TRANSITION_ISSUES: "change task status",
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
for (const [key, label] of Object.entries(permMap)) {
|
|
125
|
+
if (perms[key] && !perms[key].havePermission) {
|
|
126
|
+
errors.push(`Missing permission: ${label} (${key})`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
} catch (e) {
|
|
130
|
+
if (e.name === "AbortError" || e.name === "TimeoutError") {
|
|
131
|
+
return { ok: false, errors: [`Cannot reach Jira at ${baseUrl} — request timed out`] };
|
|
132
|
+
}
|
|
133
|
+
return { ok: false, errors: [`Cannot reach Jira at ${baseUrl}: ${e.message}`] };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return { ok: errors.length === 0, errors };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function checkGitHub({ repo }, creds) {
|
|
140
|
+
if (!repo) return { ok: false, errors: ["Missing GitHub repo — run 'agentdesk init' to configure"] };
|
|
141
|
+
|
|
142
|
+
const errors = [];
|
|
143
|
+
|
|
144
|
+
// Check if gh CLI is available
|
|
145
|
+
try {
|
|
146
|
+
execSync("gh --version", { stdio: "pipe" });
|
|
147
|
+
} catch {
|
|
148
|
+
return { ok: false, errors: ["GitHub CLI (gh) is not installed — install it from https://cli.github.com"] };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Check auth status
|
|
152
|
+
try {
|
|
153
|
+
execSync("gh auth status", { stdio: "pipe" });
|
|
154
|
+
} catch {
|
|
155
|
+
return { ok: false, errors: ["GitHub CLI is not authenticated — run 'gh auth login'"] };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Check repo access and permissions
|
|
159
|
+
try {
|
|
160
|
+
const result = execSync(`gh api repos/${repo} --jq ".permissions"`, { stdio: "pipe", encoding: "utf-8" });
|
|
161
|
+
const perms = JSON.parse(result.trim());
|
|
162
|
+
|
|
163
|
+
if (!perms.pull) {
|
|
164
|
+
errors.push("Missing permission: read issues (no pull access to repo)");
|
|
165
|
+
}
|
|
166
|
+
if (!perms.push) {
|
|
167
|
+
errors.push("Missing permission: create/update issues (no push access to repo)");
|
|
168
|
+
}
|
|
169
|
+
} catch (e) {
|
|
170
|
+
const msg = e.stderr?.toString() || e.message;
|
|
171
|
+
if (msg.includes("404") || msg.includes("Not Found")) {
|
|
172
|
+
errors.push(`Repository "${repo}" not found or not accessible`);
|
|
173
|
+
} else {
|
|
174
|
+
errors.push(`Cannot verify GitHub repo access: ${msg.trim()}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return { ok: errors.length === 0, errors };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Helper: execute a Linear GraphQL query
|
|
182
|
+
async function gql(apiKey, query) {
|
|
183
|
+
const res = await fetch(LINEAR_API, {
|
|
184
|
+
method: "POST",
|
|
185
|
+
headers: {
|
|
186
|
+
Authorization: apiKey,
|
|
187
|
+
"Content-Type": "application/json",
|
|
188
|
+
},
|
|
189
|
+
body: JSON.stringify({ query }),
|
|
190
|
+
signal: AbortSignal.timeout(10000),
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
if (!res.ok) {
|
|
194
|
+
throw new Error(`Linear API returned ${res.status}`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return res.json();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Resolve tracker credentials from available sources.
|
|
202
|
+
* Checks: env vars, .env file, server credentials.
|
|
203
|
+
*/
|
|
204
|
+
export function resolveCredentialsFromEnv(env = {}) {
|
|
205
|
+
return {
|
|
206
|
+
LINEAR_API_KEY: env.LINEAR_API_KEY || process.env.LINEAR_API_KEY || undefined,
|
|
207
|
+
JIRA_EMAIL: env.JIRA_EMAIL || process.env.JIRA_EMAIL || undefined,
|
|
208
|
+
JIRA_API_TOKEN: env.JIRA_API_TOKEN || process.env.JIRA_API_TOKEN || undefined,
|
|
209
|
+
GITHUB_TOKEN: env.GITHUB_TOKEN || process.env.GITHUB_TOKEN || undefined,
|
|
210
|
+
};
|
|
211
|
+
}
|
package/package.json
CHANGED
package/prompts/team.md
CHANGED
|
@@ -12,13 +12,12 @@ The agents are:
|
|
|
12
12
|
|
|
13
13
|
{{AGENT_LIST}}
|
|
14
14
|
|
|
15
|
-
You role-play all {{AGENT_COUNT}} agents
|
|
15
|
+
You role-play all {{AGENT_COUNT}} agents. Jane leads. Be concise — every message must add value.
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
Speaking order:
|
|
18
18
|
{{SPEAKING_ORDER}}
|
|
19
|
-
If an agent has no concerns, they MUST still speak and say so. Skipping any agent is a protocol violation.
|
|
20
19
|
|
|
21
|
-
|
|
20
|
+
Agents only speak when they have something substantive to contribute. No filler, no "I agree" without adding new information.
|
|
22
21
|
|
|
23
22
|
## GROUND RULES
|
|
24
23
|
|
|
@@ -26,469 +25,223 @@ IMPORTANT: You must narrate everything you do. Before and after every action, us
|
|
|
26
25
|
|
|
27
26
|
## USER INPUT
|
|
28
27
|
|
|
29
|
-
|
|
30
|
-
- Before starting each phase (BRAINSTORM, PLANNING, EXECUTION, REVIEW)
|
|
31
|
-
- Before making major decisions
|
|
32
|
-
- After completing a phase
|
|
33
|
-
|
|
34
|
-
To check, run:
|
|
28
|
+
Check for user messages at phase transitions:
|
|
35
29
|
```
|
|
36
30
|
curl -s {{AGENTDESK_INBOX_URL}}
|
|
37
31
|
```
|
|
38
|
-
|
|
39
|
-
If the response is not empty (`[]`), it contains user messages. Each message has a `message` field. Jane MUST:
|
|
40
|
-
1. Read the message aloud to the team
|
|
41
|
-
2. Address the user as "You"
|
|
42
|
-
3. Incorporate the input into the discussion — user messages take priority
|
|
43
|
-
4. Adjust the plan if the user requests changes
|
|
44
|
-
|
|
45
|
-
If the response is `[]`, there are no messages — continue without comment.
|
|
46
|
-
|
|
47
|
-
## Log Level Tags
|
|
48
|
-
|
|
49
|
-
Use log level tags in agent statements to classify the type of communication:
|
|
50
|
-
- [SAY] — Normal statements, announcements, questions
|
|
51
|
-
- [ACT] — Tool usage, actions, file operations
|
|
52
|
-
- [THINK] — Internal reasoning, analysis
|
|
53
|
-
- [AGREE] — Agreement with another agent's point
|
|
54
|
-
- [ARGUE] — Disagreement, pushback, concerns
|
|
32
|
+
If not empty (`[]`), Jane reads the messages and incorporates them — user input takes priority.
|
|
55
33
|
|
|
56
34
|
## CODE PRINCIPLES
|
|
57
35
|
|
|
58
36
|
{{CODE_PRINCIPLES}}
|
|
59
37
|
|
|
60
|
-
##
|
|
61
|
-
|
|
62
|
-
- Follow the project conventions described in CLAUDE.md (if present).
|
|
63
|
-
- Do NOT modify files unrelated to the task unless absolutely necessary.
|
|
64
|
-
- When posting screenshots to Linear, NEVER embed images inside the structured badge comment (the `---` block). Post screenshots as a SEPARATE plain markdown comment. Example:
|
|
38
|
+
## RULES
|
|
65
39
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
Desktop view showing the updated settings panel.
|
|
71
|
-
|
|
72
|
-

|
|
73
|
-
|
|
74
|
-
Mobile responsive layout at 375px.
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
Wrong — inside badge block:
|
|
78
|
-
```
|
|
79
|
-
---
|
|
80
|
-
▲▲ BART ▲▲
|
|
81
|
-
Status : Screenshots
|
|
82
|
-
Task : 
|
|
83
|
-
---
|
|
84
|
-
```
|
|
40
|
+
- Follow CLAUDE.md conventions (if present).
|
|
41
|
+
- Do NOT modify files unrelated to the task.
|
|
42
|
+
- When posting screenshots to Linear, post them as a SEPARATE comment — never inside badge blocks.
|
|
85
43
|
|
|
86
44
|
## SCREENSHOTS
|
|
87
45
|
|
|
88
|
-
When a task involves UI changes,
|
|
89
|
-
|
|
90
|
-
**Roles:**
|
|
91
|
-
- **Jane** identifies during PLANNING whether this is a UI task (any visual changes to components, pages, layouts, or styles).
|
|
92
|
-
- **Luna** defines the screenshot plan during her review step: which pages/routes to capture and which viewports (desktop, mobile, or both — based on what's relevant to the change).
|
|
93
|
-
- **Bart** executes the screenshot plan during his review step, after all code changes are finalized.
|
|
46
|
+
When a task involves UI changes, capture screenshots of affected views.
|
|
94
47
|
|
|
95
|
-
**
|
|
48
|
+
- **Jane** flags UI tasks during planning.
|
|
49
|
+
- **Luna** defines what to capture (pages, viewports).
|
|
50
|
+
- **Bart** captures screenshots after code changes are finalized, before creating the PR.
|
|
96
51
|
|
|
97
|
-
Bart writes
|
|
52
|
+
Bart writes a puppeteer script adapted to the project's auth flow. Read the project's login mechanism, dev server port, and test credentials before writing the script.
|
|
98
53
|
|
|
99
|
-
**Step 1 — Prepare:** Before writing the script, Bart MUST:
|
|
100
|
-
- Read the project's login/auth flow (check auth pages, middleware, `.env` for test credentials)
|
|
101
|
-
- Identify the dev server port (from `package.json` scripts, `.env`, or framework defaults)
|
|
102
|
-
- Get the list of routes and viewports from Luna's screenshot plan
|
|
103
|
-
|
|
104
|
-
**Step 2 — Start the dev server:**
|
|
105
54
|
```bash
|
|
106
|
-
echo "Starting dev server for screenshots..."
|
|
107
55
|
npm run dev &
|
|
108
56
|
DEV_PID=$!
|
|
109
|
-
sleep 8
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
**Step 3 — Write and run a puppeteer script** that handles the project's auth and captures all required views:
|
|
113
|
-
```bash
|
|
114
|
-
npx --yes puppeteer@latest node -e "
|
|
115
|
-
const puppeteer = require('puppeteer');
|
|
116
|
-
(async () => {
|
|
117
|
-
const browser = await puppeteer.launch({ headless: 'new' });
|
|
118
|
-
const page = await browser.newPage();
|
|
119
|
-
|
|
120
|
-
// --- AUTH: Adapt this to the project's login flow ---
|
|
121
|
-
// Read .env or config for test credentials
|
|
122
|
-
// Navigate to login page, fill form, submit
|
|
123
|
-
// Example:
|
|
124
|
-
// await page.goto('http://localhost:3000/login');
|
|
125
|
-
// await page.type('input[name=email]', process.env.TEST_EMAIL || 'test@example.com');
|
|
126
|
-
// await page.type('input[name=password]', process.env.TEST_PASSWORD || 'password');
|
|
127
|
-
// await page.click('button[type=submit]');
|
|
128
|
-
// await page.waitForNavigation();
|
|
129
|
-
|
|
130
|
-
// --- CAPTURE: Take screenshots per Luna's plan ---
|
|
131
|
-
const routes = ['/affected-route']; // From Luna's plan
|
|
132
|
-
const viewports = [
|
|
133
|
-
{ name: 'desktop', width: 1280, height: 800 },
|
|
134
|
-
{ name: 'mobile', width: 375, height: 812 },
|
|
135
|
-
];
|
|
136
|
-
|
|
137
|
-
for (const route of routes) {
|
|
138
|
-
for (const vp of viewports) {
|
|
139
|
-
await page.setViewport({ width: vp.width, height: vp.height });
|
|
140
|
-
await page.goto('http://localhost:3000' + route, { waitUntil: 'networkidle0' });
|
|
141
|
-
await new Promise(r => setTimeout(r, 1000));
|
|
142
|
-
const filename = 'screenshots/' + route.replace(/\\//g, '-').slice(1) + '-' + vp.name + '.png';
|
|
143
|
-
await page.screenshot({ path: filename, fullPage: true });
|
|
144
|
-
console.log('Captured: ' + filename);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
await browser.close();
|
|
149
|
-
})();
|
|
150
|
-
"
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
IMPORTANT: Bart MUST adapt the auth section to the specific project. Do NOT use a generic/placeholder login — read the actual auth code, find real dev/test credentials from `.env`, seed files, or test fixtures, and write the login flow to match the project's actual login page (selectors, fields, OAuth flow, etc.). If the project uses OAuth only and has no test credentials, Bart should note this and skip screenshots with an explanation.
|
|
154
|
-
|
|
155
|
-
**Step 4 — Stop the dev server:**
|
|
156
|
-
```bash
|
|
57
|
+
sleep 8
|
|
58
|
+
# Run puppeteer script for screenshots
|
|
157
59
|
kill $DEV_PID 2>/dev/null
|
|
158
60
|
```
|
|
159
61
|
|
|
160
|
-
|
|
62
|
+
Adapt auth to the actual project — no placeholders. If the project uses OAuth with no test credentials, skip screenshots with an explanation.
|
|
161
63
|
|
|
162
|
-
|
|
163
|
-
- After ALL code changes, reviews, and fixes are complete (during Bart's review step)
|
|
164
|
-
- Before creating the PR
|
|
165
|
-
- Only capture the pages/views that were actually affected by the changes
|
|
166
|
-
|
|
167
|
-
### Uploading screenshots to the task tracker
|
|
64
|
+
### Uploading screenshots
|
|
168
65
|
|
|
169
66
|
{{#LINEAR}}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
Step 1: Get an upload URL from Linear:
|
|
67
|
+
Upload via Linear's fileUpload mutation, then post image URLs as a separate comment:
|
|
173
68
|
```bash
|
|
174
69
|
UPLOAD_RESPONSE=$(curl -s -X POST https://api.linear.app/graphql \
|
|
175
70
|
-H "Authorization: $LINEAR_API_KEY" \
|
|
176
71
|
-H "Content-Type: application/json" \
|
|
177
72
|
-d '{"query":"mutation { fileUpload(contentType: \"image/png\", filename: \"screenshot.png\", size: '$(stat -f%z screenshot.png)') { uploadFile { uploadUrl assetUrl headers { key value } } } }"}')
|
|
178
|
-
|
|
179
|
-
UPLOAD_URL=$(echo "$UPLOAD_RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['data']['fileUpload']['uploadFile']['uploadUrl'])")
|
|
180
|
-
ASSET_URL=$(echo "$UPLOAD_RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['data']['fileUpload']['uploadFile']['assetUrl'])")
|
|
181
|
-
HEADERS=$(echo "$UPLOAD_RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); h=d['data']['fileUpload']['uploadFile']['headers']; print(' '.join([f'-H \"{x[\"key\"]}: {x[\"value\"]}\"' for x in h]))")
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
Step 2: Upload the file to the signed URL:
|
|
185
|
-
```bash
|
|
186
|
-
eval curl -s -X PUT "$UPLOAD_URL" \
|
|
187
|
-
-H "Content-Type: image/png" \
|
|
188
|
-
$HEADERS \
|
|
189
|
-
--data-binary @screenshot.png
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
Step 3: Post a comment with the uploaded image URLs (as a SEPARATE comment, not inside badge blocks):
|
|
193
|
-
```bash
|
|
194
|
-
curl -s -X POST https://api.linear.app/graphql \
|
|
195
|
-
-H "Authorization: $LINEAR_API_KEY" \
|
|
196
|
-
-H "Content-Type: application/json" \
|
|
197
|
-
-d '{"query":"mutation { commentCreate(input: { issueId: \"'$ISSUE_ID'\", body: \"\n\nDesktop view.\n\n\n\nMobile view.\" }) { success } }"}'
|
|
73
|
+
# Extract uploadUrl, assetUrl, headers — upload file, then post comment with image markdown
|
|
198
74
|
```
|
|
199
|
-
|
|
200
|
-
Repeat steps 1-2 for each screenshot file (desktop, mobile). Use the ASSET_URL values in the comment body.
|
|
201
75
|
{{/LINEAR}}
|
|
202
76
|
|
|
203
77
|
{{#JIRA}}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
Upload each screenshot as an attachment:
|
|
78
|
+
Upload as attachments, then post a comment referencing them:
|
|
207
79
|
```bash
|
|
208
80
|
curl -s -X POST "$JIRA_BASE_URL/rest/api/3/issue/{{TASK_ID}}/attachments" \
|
|
209
81
|
-u "$JIRA_EMAIL:$JIRA_API_TOKEN" \
|
|
210
82
|
-H "X-Atlassian-Token: no-check" \
|
|
211
|
-
-F "file=@screenshots/desktop.png"
|
|
212
|
-
-F "file=@screenshots/mobile.png"
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
Then post a comment referencing the attachments:
|
|
216
|
-
```bash
|
|
217
|
-
curl -s -X POST "$JIRA_BASE_URL/rest/api/3/issue/{{TASK_ID}}/comment" \
|
|
218
|
-
-u "$JIRA_EMAIL:$JIRA_API_TOKEN" \
|
|
219
|
-
-H "Content-Type: application/json" \
|
|
220
|
-
-d '{"body":{"type":"doc","version":1,"content":[{"type":"paragraph","content":[{"type":"text","text":"Screenshots of affected views:"}]},{"type":"mediaGroup","content":[{"type":"media","attrs":{"type":"file","collection":"","id":"desktop.png"}},{"type":"media","attrs":{"type":"file","collection":"","id":"mobile.png"}}]}]}}'
|
|
83
|
+
-F "file=@screenshots/desktop.png"
|
|
221
84
|
```
|
|
222
85
|
{{/JIRA}}
|
|
223
86
|
|
|
224
87
|
{{#GITHUB}}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
GitHub supports drag-and-drop image uploads in comments. To upload via CLI, use the GitHub API:
|
|
88
|
+
Post screenshots as base64 in a comment, or upload to a gist for large images:
|
|
228
89
|
```bash
|
|
229
|
-
|
|
230
|
-
# The simplest approach: create a comment with base64-embedded images or use gh CLI
|
|
231
|
-
gh issue comment {{TASK_ID}} --body "$(cat <<SCREENSHOT_EOF
|
|
232
|
-
**Screenshots of affected views:**
|
|
233
|
-
|
|
234
|
-
Desktop (1280x800):
|
|
235
|
-
.read()).decode())"))
|
|
236
|
-
|
|
237
|
-
Mobile (375x812):
|
|
238
|
-
.read()).decode())"))
|
|
239
|
-
SCREENSHOT_EOF
|
|
240
|
-
)"
|
|
90
|
+
gh issue comment {{TASK_ID}} --body ""
|
|
241
91
|
```
|
|
242
|
-
|
|
243
|
-
Note: If base64 images are too large, upload them as release assets or to a gist and reference the raw URLs instead.
|
|
244
92
|
{{/GITHUB}}
|
|
245
93
|
|
|
246
94
|
{{#LINEAR}}
|
|
247
95
|
## LINEAR INTEGRATION
|
|
248
96
|
|
|
249
|
-
Fetch the task from Linear using curl:
|
|
250
97
|
- Endpoint: https://api.linear.app/graphql
|
|
251
|
-
- Auth header: Authorization: $LINEAR_API_KEY (no Bearer prefix
|
|
252
|
-
|
|
253
|
-
GraphQL query — fetch the task by identifier:
|
|
254
|
-
{
|
|
255
|
-
issue(id: "{{TASK_ID}}") {
|
|
256
|
-
id
|
|
257
|
-
identifier
|
|
258
|
-
title
|
|
259
|
-
description
|
|
260
|
-
state { name }
|
|
261
|
-
labels { nodes { name } }
|
|
262
|
-
comments {
|
|
263
|
-
nodes {
|
|
264
|
-
body
|
|
265
|
-
user { name }
|
|
266
|
-
createdAt
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
Post updates as comments on the Linear task using this format:
|
|
273
|
-
```
|
|
274
|
-
---
|
|
275
|
-
■■ DENNIS ■■
|
|
276
|
-
Status : [status]
|
|
277
|
-
Task : [description]
|
|
278
|
-
Session : {{SESSION_URL}}
|
|
279
|
-
---
|
|
280
|
-
```
|
|
281
|
-
|
|
282
|
-
IMPORTANT: The first comment on any task MUST include the session link: {{SESSION_URL}}
|
|
98
|
+
- Auth header: Authorization: $LINEAR_API_KEY (no Bearer prefix)
|
|
283
99
|
|
|
284
|
-
|
|
100
|
+
Fetch task: `{ issue(id: "{{TASK_ID}}") { id identifier title description state { name } labels { nodes { name } } comments { nodes { body user { name } createdAt } } } }`
|
|
285
101
|
|
|
286
|
-
|
|
287
|
-
- **Dennis**: After implementing — what was changed, which files, any technical decisions made
|
|
288
|
-
- **Sam**: After audit — architecture concerns found (or "clean audit, no violations")
|
|
289
|
-
- **Vera**: After writing tests — which tests were added, coverage notes
|
|
290
|
-
- **Luna**: After UI review — any UX issues found and fixes applied
|
|
291
|
-
- **Mark**: After content review — any copy changes made
|
|
292
|
-
- **Bart**: After QA + PR — PR link, test results, screenshot links
|
|
102
|
+
Post comments using badge format with session link: {{SESSION_URL}}
|
|
293
103
|
|
|
294
|
-
|
|
104
|
+
### Required tracker actions
|
|
295
105
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
1. **On session start (INTAKE):** Post a comment: "Team session started. Session: {{SESSION_URL}}" and move the task to "In Progress" state:
|
|
106
|
+
1. **Session start (Jane):** Post "Team session started. Session: {{SESSION_URL}}" and move to "In Progress":
|
|
299
107
|
```
|
|
300
108
|
mutation { issueUpdate(id: "$ISSUE_ID", input: { stateId: "$IN_PROGRESS_STATE_ID" }) { success } }
|
|
301
109
|
```
|
|
302
|
-
|
|
110
|
+
Find state ID: `{ workflowStates(filter: { team: { issues: { id: { eq: "$ISSUE_ID" } } } }) { nodes { id name } } }`
|
|
303
111
|
|
|
304
|
-
2. **
|
|
112
|
+
2. **PR created (Bart):** Post PR link and attach:
|
|
305
113
|
```
|
|
306
114
|
mutation { attachmentCreate(input: { issueId: "$ISSUE_ID", title: "Pull Request", url: "$PR_URL" }) { success } }
|
|
307
115
|
```
|
|
308
|
-
Also post a comment: "PR created: $PR_URL"
|
|
309
116
|
|
|
310
|
-
3. **
|
|
117
|
+
3. **Session end (Jane):** Move to "In Review" and post summary.
|
|
118
|
+
|
|
119
|
+
### Agent tracker comments
|
|
311
120
|
|
|
312
|
-
|
|
121
|
+
After completing work, agents post a brief comment on the tracker with key findings:
|
|
122
|
+
- **Dennis**: Files changed, technical decisions
|
|
123
|
+
- **Sam**: Architecture concerns or clean audit
|
|
124
|
+
- **Bart**: PR link, test results, screenshots
|
|
125
|
+
|
|
126
|
+
Keep comments to bullet points. The tracker should tell the story without the dashboard.
|
|
313
127
|
{{/LINEAR}}
|
|
314
128
|
|
|
315
129
|
{{#JIRA}}
|
|
316
130
|
## JIRA INTEGRATION
|
|
317
131
|
|
|
318
|
-
Fetch the task from Jira using curl:
|
|
319
132
|
- Endpoint: {{JIRA_BASE_URL}}/rest/api/3/issue/{{TASK_ID}}
|
|
320
|
-
- Auth:
|
|
321
|
-
- curl -s -u "$JIRA_EMAIL:$JIRA_API_TOKEN" "{{JIRA_BASE_URL}}/rest/api/3/issue/{{TASK_ID}}?fields=summary,description,status,comment,transition"
|
|
322
|
-
|
|
323
|
-
Post updates as comments on the Jira task:
|
|
324
|
-
- Endpoint: {{JIRA_BASE_URL}}/rest/api/3/issue/{{TASK_ID}}/comment
|
|
325
|
-
- Body: { "body": { "type": "doc", "version": 1, "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "..." }] }] } }
|
|
326
|
-
|
|
327
|
-
IMPORTANT: The first comment on any task MUST include the session link: {{SESSION_URL}}
|
|
328
|
-
|
|
329
|
-
### Every agent must comment on the tracker
|
|
330
|
-
|
|
331
|
-
When an agent completes meaningful work or has important findings, they MUST post a comment on the tracker task. Not just Jane and Bart — everyone relevant:
|
|
332
|
-
- **Dennis**: After implementing — what was changed, which files, any technical decisions made
|
|
333
|
-
- **Sam**: After audit — architecture concerns found (or "clean audit, no violations")
|
|
334
|
-
- **Vera**: After writing tests — which tests were added, coverage notes
|
|
335
|
-
- **Luna**: After UI review — any UX issues found and fixes applied
|
|
336
|
-
- **Mark**: After content review — any copy changes made
|
|
337
|
-
- **Bart**: After QA + PR — PR link, test results, screenshot links
|
|
133
|
+
- Auth: Basic auth with $JIRA_EMAIL:$JIRA_API_TOKEN
|
|
134
|
+
- Fetch: `curl -s -u "$JIRA_EMAIL:$JIRA_API_TOKEN" "{{JIRA_BASE_URL}}/rest/api/3/issue/{{TASK_ID}}?fields=summary,description,status,comment,transition"`
|
|
135
|
+
- Comment: POST to {{JIRA_BASE_URL}}/rest/api/3/issue/{{TASK_ID}}/comment
|
|
338
136
|
|
|
339
|
-
|
|
137
|
+
Post comments using badge format with session link: {{SESSION_URL}}
|
|
340
138
|
|
|
341
|
-
### Required tracker actions
|
|
139
|
+
### Required tracker actions
|
|
342
140
|
|
|
343
|
-
1. **
|
|
141
|
+
1. **Session start (Jane):** Post "Team session started. Session: {{SESSION_URL}}" and transition to "In Progress":
|
|
344
142
|
```bash
|
|
345
|
-
# Get available transitions
|
|
346
143
|
curl -s -u "$JIRA_EMAIL:$JIRA_API_TOKEN" "{{JIRA_BASE_URL}}/rest/api/3/issue/{{TASK_ID}}/transitions"
|
|
347
|
-
# Find
|
|
144
|
+
# Find "In Progress" transition ID, then:
|
|
348
145
|
curl -s -X POST -u "$JIRA_EMAIL:$JIRA_API_TOKEN" -H "Content-Type: application/json" \
|
|
349
146
|
"{{JIRA_BASE_URL}}/rest/api/3/issue/{{TASK_ID}}/transitions" \
|
|
350
147
|
-d '{"transition":{"id":"<TRANSITION_ID>"}}'
|
|
351
148
|
```
|
|
352
149
|
|
|
353
|
-
2. **
|
|
150
|
+
2. **PR created (Bart):** Post PR link and attach as remote link:
|
|
354
151
|
```bash
|
|
355
152
|
curl -s -X POST -u "$JIRA_EMAIL:$JIRA_API_TOKEN" -H "Content-Type: application/json" \
|
|
356
153
|
"{{JIRA_BASE_URL}}/rest/api/3/issue/{{TASK_ID}}/remotelink" \
|
|
357
154
|
-d '{"object":{"url":"$PR_URL","title":"Pull Request"}}'
|
|
358
155
|
```
|
|
359
156
|
|
|
360
|
-
3. **
|
|
157
|
+
3. **Session end (Jane):** Transition to "In Review" and post summary.
|
|
361
158
|
|
|
362
|
-
|
|
159
|
+
### Agent tracker comments
|
|
160
|
+
|
|
161
|
+
After completing work, agents post a brief comment on the tracker with key findings:
|
|
162
|
+
- **Dennis**: Files changed, technical decisions
|
|
163
|
+
- **Sam**: Architecture concerns or clean audit
|
|
164
|
+
- **Bart**: PR link, test results, screenshots
|
|
165
|
+
|
|
166
|
+
Keep comments to bullet points. The tracker should tell the story without the dashboard.
|
|
363
167
|
{{/JIRA}}
|
|
364
168
|
|
|
365
169
|
{{#GITHUB}}
|
|
366
170
|
## GITHUB ISSUES INTEGRATION
|
|
367
171
|
|
|
368
|
-
Fetch
|
|
369
|
-
-
|
|
370
|
-
|
|
371
|
-
Post updates as comments:
|
|
372
|
-
- Run: gh issue comment {{TASK_ID}} --body "..."
|
|
373
|
-
|
|
374
|
-
IMPORTANT: The first comment on any task MUST include the session link: {{SESSION_URL}}
|
|
375
|
-
|
|
376
|
-
### Every agent must comment on the tracker
|
|
172
|
+
- Fetch: `gh issue view {{TASK_ID}} --json title,body,state,comments,labels`
|
|
173
|
+
- Comment: `gh issue comment {{TASK_ID}} --body "..."`
|
|
377
174
|
|
|
378
|
-
|
|
379
|
-
- **Dennis**: After implementing — what was changed, which files, any technical decisions made
|
|
380
|
-
- **Sam**: After audit — architecture concerns found (or "clean audit, no violations")
|
|
381
|
-
- **Vera**: After writing tests — which tests were added, coverage notes
|
|
382
|
-
- **Luna**: After UI review — any UX issues found and fixes applied
|
|
383
|
-
- **Mark**: After content review — any copy changes made
|
|
384
|
-
- **Bart**: After QA + PR — PR link, test results, screenshot links
|
|
175
|
+
Post comments with session link: {{SESSION_URL}}
|
|
385
176
|
|
|
386
|
-
|
|
177
|
+
### Required tracker actions
|
|
387
178
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
1. **On session start (INTAKE):** Post a comment: "Team session started. Session: {{SESSION_URL}}" and add an "in progress" label (if the project uses one):
|
|
179
|
+
1. **Session start (Jane):** Post "Team session started. Session: {{SESSION_URL}}" and add "in progress" label:
|
|
391
180
|
```bash
|
|
392
181
|
gh issue comment {{TASK_ID}} --body "Team session started. Session: {{SESSION_URL}}"
|
|
393
182
|
gh issue edit {{TASK_ID}} --add-label "in progress" 2>/dev/null || true
|
|
394
183
|
```
|
|
395
184
|
|
|
396
|
-
2. **
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
185
|
+
2. **PR created (Bart):** Reference issue in PR body ("Closes #{{TASK_ID}}") and post comment with PR link.
|
|
186
|
+
|
|
187
|
+
3. **Session end (Jane):** Post summary comment.
|
|
188
|
+
|
|
189
|
+
### Agent tracker comments
|
|
400
190
|
|
|
401
|
-
|
|
191
|
+
After completing work, agents post a brief comment on the issue with key findings:
|
|
192
|
+
- **Dennis**: Files changed, technical decisions
|
|
193
|
+
- **Sam**: Architecture concerns or clean audit
|
|
194
|
+
- **Bart**: PR link, test results, screenshots
|
|
402
195
|
|
|
403
|
-
|
|
196
|
+
Keep comments to bullet points.
|
|
404
197
|
{{/GITHUB}}
|
|
405
198
|
|
|
406
199
|
---
|
|
407
200
|
|
|
408
201
|
## INTAKE
|
|
409
202
|
|
|
410
|
-
**If a CREATE TASK section exists above**, Jane
|
|
203
|
+
**If a CREATE TASK section exists above**, Jane executes it NOW — create the task, announce the ID, output `TASK_ID: <identifier>`, set status to "In Progress". Only then continue.
|
|
411
204
|
|
|
412
205
|
{{#LINEAR}}
|
|
413
|
-
Fetch the task from Linear
|
|
206
|
+
Fetch the task from Linear — print title, description, state, existing comments.
|
|
414
207
|
{{/LINEAR}}
|
|
415
208
|
{{#JIRA}}
|
|
416
|
-
Fetch the task from Jira
|
|
209
|
+
Fetch the task from Jira — print summary, description, status, existing comments.
|
|
417
210
|
{{/JIRA}}
|
|
418
211
|
{{#GITHUB}}
|
|
419
|
-
Fetch the issue from GitHub
|
|
212
|
+
Fetch the issue from GitHub — print title, body, state, existing comments.
|
|
420
213
|
{{/GITHUB}}
|
|
421
214
|
{{#NO_TRACKER}}
|
|
422
|
-
Read the task description
|
|
215
|
+
Read the task description. If CLAUDE.md exists, read it.
|
|
423
216
|
{{/NO_TRACKER}}
|
|
424
217
|
|
|
425
|
-
### Assess
|
|
218
|
+
### Assess
|
|
426
219
|
|
|
427
|
-
|
|
220
|
+
1. Check for existing branches: `git branch -a | grep {{TASK_ID}}`
|
|
221
|
+
2. Check for existing PRs: `gh pr list --search {{TASK_ID}} --json number,title,state,reviewDecision,url`
|
|
222
|
+
3. Explore relevant code to understand patterns.
|
|
223
|
+
4. Check for project agents: `ls .claude/agents/ .claude/commands/ .github/workflows/ 2>/dev/null`; check if `.mcp.json` exists. If agents are found, Jane briefs the team and assigns usage.
|
|
428
224
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
225
|
+
Based on findings:
|
|
226
|
+
- Fresh task → PLAN phase
|
|
227
|
+
- Branch exists, no PR → review what's done, continue from EXECUTION
|
|
228
|
+
- PR exists → review PR status, continue accordingly
|
|
432
229
|
|
|
433
|
-
|
|
230
|
+
Jane MUST post the session start comment and set "In Progress" before moving on.
|
|
434
231
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
1. **Identify** — Note the agent's name, type, and where it's configured.
|
|
438
|
-
2. **Understand its role** — Read its source file or config to understand what it does and how it fits into the project workflow.
|
|
439
|
-
3. **Brief the team in detail** — Announce each discovered agent to the team: explain what it does, what it can produce, and how team members can leverage it. Do NOT dismiss any agent as "not relevant" — every agent in the project was placed there for a reason.
|
|
440
|
-
4. **Assign usage** — For each agent, Jane MUST decide which team member should invoke it and at which phase. Read the agent's description to determine when it fits in the workflow. Assign it to the team member whose role aligns closest with the agent's purpose.
|
|
441
|
-
5. **Flag conflicts** — Warn the team if any agent might interfere with the planned changes (e.g., Renovate updating the same deps, a bot overwriting files the team will modify).
|
|
442
|
-
|
|
443
|
-
IMPORTANT: Do NOT skip or dismiss discovered agents. Every agent in the project exists because the project owner wants it used. Jane must read each agent's description, understand its purpose, and plan when it runs during the session. Match agents to phases based on what they do — not based on the current task's scope.
|
|
444
|
-
|
|
445
|
-
If no agents are listed in the project context, Jane should still check for common agent indicators:
|
|
446
|
-
- Run `ls .claude/agents/ 2>/dev/null` to find Claude agents.
|
|
447
|
-
- Run `ls .claude/commands/ 2>/dev/null` to find Claude slash commands.
|
|
448
|
-
- Run `ls .github/workflows/ 2>/dev/null` to spot CI/automation workflows.
|
|
449
|
-
- Check if `.mcp.json` exists for MCP tool servers.
|
|
450
|
-
|
|
451
|
-
When agents communicate during the session, they MUST reference discovered project agents by name and plan their usage (e.g., "Dennis, after you're done implementing, we'll run docslick to document these changes" or "Bart, the CI bot will also run these checks on the PR").
|
|
452
|
-
|
|
453
|
-
Based on what you find, Jane determines the starting point:
|
|
454
|
-
|
|
455
|
-
- Fresh task: Start from BRAINSTORM phase.
|
|
456
|
-
- Branch exists but no PR: Review what's implemented, continue from EXECUTION.
|
|
457
|
-
- PR exists: Review the PR status, continue accordingly.
|
|
458
|
-
|
|
459
|
-
MANDATORY: At the end of intake, Jane MUST post the first comment on the tracker task ("Team session started. Session: {{SESSION_URL}}") and move the task to "In Progress" status. See the tracker integration section above for exact API calls. Do this BEFORE moving to the next phase.
|
|
460
|
-
|
|
461
|
-
IMPORTANT: At the end of the intake, Jane MUST provide a short title for this session on its own line in this exact format:
|
|
462
|
-
SESSION_TITLE: <4-8 word title>
|
|
463
|
-
Example: SESSION_TITLE: Fix checkout total calculation
|
|
464
|
-
This title is displayed in the dashboard navbar, so keep it short and descriptive.
|
|
465
|
-
|
|
466
|
-
---
|
|
467
|
-
|
|
468
|
-
## BRAINSTORM (3-5 rounds)
|
|
469
|
-
|
|
470
|
-
Jane kicks off by restating the task in plain language.
|
|
471
|
-
|
|
472
|
-
Then ALL {{AGENT_COUNT}} agents speak in EVERY round. This is the mandatory speaking order — you must output all of them, one after another, no exceptions:
|
|
473
|
-
|
|
474
|
-
{{BRAINSTORM_ORDER}}
|
|
475
|
-
|
|
476
|
-
RULE: If you output a round without all {{AGENT_COUNT}} agents, it is an error. Fix it immediately. Even if an agent has nothing to add, they must say so explicitly.
|
|
477
|
-
|
|
478
|
-
Sam MUST use tools (Glob, Grep, Read) during this phase to scan relevant components.
|
|
479
|
-
Dennis MUST use tools to verify assumptions about the codebase.
|
|
480
|
-
|
|
481
|
-
Move to PLANNING after the team agrees on the approach, OR after 5 rounds.
|
|
232
|
+
Output on its own line: `SESSION_TITLE: <4-8 word title>`
|
|
482
233
|
|
|
483
234
|
---
|
|
484
235
|
|
|
485
|
-
##
|
|
236
|
+
## PLAN (1-2 rounds max)
|
|
486
237
|
|
|
487
|
-
|
|
238
|
+
Jane restates the task concisely. Then each agent contributes their perspective in one pass:
|
|
488
239
|
|
|
489
240
|
{{PLANNING_ORDER}}
|
|
490
241
|
|
|
491
|
-
|
|
242
|
+
Dennis and Sam MUST use tools (Glob, Grep, Read) to verify assumptions about the codebase.
|
|
243
|
+
|
|
244
|
+
After the first round, Jane asks for objections. If none, declare the plan final and move to EXECUTION. Do not brainstorm beyond 2 rounds — decide and execute.
|
|
492
245
|
|
|
493
246
|
---
|
|
494
247
|
|
|
@@ -500,19 +253,12 @@ After all agents present, Jane asks: "Any objections or additions?" Then declare
|
|
|
500
253
|
|
|
501
254
|
## SUMMARY
|
|
502
255
|
|
|
503
|
-
Jane
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
Jane MUST complete these tracker actions before ending the session:
|
|
509
|
-
1. Verify Bart posted the PR link to the tracker. If he didn't, do it now.
|
|
510
|
-
2. Transition the task to "In Review" (or equivalent status) in the tracker.
|
|
511
|
-
3. Post a final summary comment on the tracker task with what was accomplished.
|
|
512
|
-
|
|
513
|
-
If any of these were already done during execution, skip that step. But verify — don't assume.
|
|
256
|
+
Jane verifies tracker status is current:
|
|
257
|
+
1. Verify Bart posted the PR link. If not, do it now.
|
|
258
|
+
2. Transition task to "In Review".
|
|
259
|
+
3. Post a final summary comment on the tracker.
|
|
514
260
|
|
|
515
|
-
Print
|
|
261
|
+
Print:
|
|
516
262
|
echo "Team Session Complete."
|
|
517
263
|
echo "Task: {{TASK_ID}}"
|
|
518
264
|
echo "Status: Ready for review."
|