@kendoo.agentdesk/agentdesk 0.9.11 → 0.9.12

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 CHANGED
@@ -4,8 +4,8 @@ export const BUILT_IN_AGENTS = {
4
4
  Jane: {
5
5
  badge: "●● JANE ●●",
6
6
  role: "Product Analyst / Team Lead",
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.",
7
+ description: "leads the session, clarifies requirements, coordinates the team, manages tracker status, decomposes large tasks into subtasks",
8
+ groundRules: "Jane focuses on requirements, scope, and coordination — she does not read code. She creates tracker tasks when needed, manages status transitions, posts the session start/end comments, and decomposes large features into subtasks (basic vs deferred).",
9
9
  planning: "Requirements: what we're building, acceptance criteria, scope. Flags UI tasks for Luna.",
10
10
  execution: {
11
11
  step: "Jane wraps up",
@@ -202,10 +202,119 @@ export function resolveTeam(config) {
202
202
  return team;
203
203
  }
204
204
 
205
+ /**
206
+ * Return tracker-specific command snippets for execution steps.
207
+ */
208
+ function trackerCommands(tracker, config) {
209
+ if (!tracker) return null;
210
+
211
+ if (tracker === "linear") {
212
+ return {
213
+ postPrLink: [
214
+ 'Post PR link on Linear using attachmentCreate:',
215
+ '```',
216
+ 'curl -s -X POST https://api.linear.app/graphql \\',
217
+ ' -H "Authorization: $LINEAR_API_KEY" -H "Content-Type: application/json" \\',
218
+ ' -d \'{"query":"mutation { attachmentCreate(input: { issueId: \\"$ISSUE_ID\\", title: \\"Pull Request\\", url: \\"$PR_URL\\" }) { success } }"}\'',
219
+ '```',
220
+ ].join('\n'),
221
+ agentComment: [
222
+ 'Post a comment on Linear with key findings:',
223
+ '```',
224
+ 'curl -s -X POST https://api.linear.app/graphql \\',
225
+ ' -H "Authorization: $LINEAR_API_KEY" -H "Content-Type: application/json" \\',
226
+ ' -d \'{"query":"mutation { commentCreate(input: { issueId: \\"$ISSUE_ID\\", body: \\"<COMMENT>\\" }) { success } }"}\'',
227
+ '```',
228
+ ].join('\n'),
229
+ transitionToReview: [
230
+ 'Transition to "In Review" on Linear:',
231
+ '```',
232
+ 'curl -s -X POST https://api.linear.app/graphql \\',
233
+ ' -H "Authorization: $LINEAR_API_KEY" -H "Content-Type: application/json" \\',
234
+ ' -d \'{"query":"mutation { issueUpdate(id: \\"$ISSUE_ID\\", input: { stateId: \\"$IN_REVIEW_STATE_ID\\" }) { success } }"}\'',
235
+ '```',
236
+ 'Find the "In Review" state ID from workflowStates first.',
237
+ ].join('\n'),
238
+ postSummary: [
239
+ 'Post final summary comment on Linear (use commentCreate mutation above).',
240
+ ].join('\n'),
241
+ };
242
+ }
243
+
244
+ const baseUrl = config?.jira?.baseUrl || '$JIRA_BASE_URL';
245
+ if (tracker === "jira") {
246
+ return {
247
+ postPrLink: [
248
+ 'Post PR link on Jira as a remote link:',
249
+ '```bash',
250
+ `curl -s -X POST -u "$JIRA_EMAIL:$JIRA_API_TOKEN" -H "Content-Type: application/json" \\`,
251
+ ` "${baseUrl}/rest/api/3/issue/$TASK_ID/remotelink" \\`,
252
+ ` -d '{"object":{"url":"$PR_URL","title":"Pull Request"}}'`,
253
+ '```',
254
+ ].join('\n'),
255
+ agentComment: [
256
+ 'Post a comment on Jira with key findings:',
257
+ '```bash',
258
+ `curl -s -X POST -u "$JIRA_EMAIL:$JIRA_API_TOKEN" -H "Content-Type: application/json" \\`,
259
+ ` "${baseUrl}/rest/api/3/issue/$TASK_ID/comment" \\`,
260
+ ` -d '{"body":{"type":"doc","version":1,"content":[{"type":"paragraph","content":[{"type":"text","text":"<COMMENT>"}]}]}}'`,
261
+ '```',
262
+ ].join('\n'),
263
+ transitionToReview: [
264
+ 'Transition task to "In Review" on Jira:',
265
+ '```bash',
266
+ `# First get available transitions:`,
267
+ `curl -s -u "$JIRA_EMAIL:$JIRA_API_TOKEN" "${baseUrl}/rest/api/3/issue/$TASK_ID/transitions"`,
268
+ `# Then transition (find the "In Review" transition ID):`,
269
+ `curl -s -X POST -u "$JIRA_EMAIL:$JIRA_API_TOKEN" -H "Content-Type: application/json" \\`,
270
+ ` "${baseUrl}/rest/api/3/issue/$TASK_ID/transitions" \\`,
271
+ ` -d '{"transition":{"id":"<TRANSITION_ID>"}}'`,
272
+ '```',
273
+ ].join('\n'),
274
+ postSummary: [
275
+ `Post final summary comment on Jira (use comment endpoint above).`,
276
+ ].join('\n'),
277
+ };
278
+ }
279
+
280
+ if (tracker === "github") {
281
+ return {
282
+ postPrLink: [
283
+ 'Reference the issue in the PR body ("Closes #$TASK_ID") and post a comment:',
284
+ '```bash',
285
+ 'gh issue comment $TASK_ID --body "PR created: $PR_URL"',
286
+ '```',
287
+ ].join('\n'),
288
+ agentComment: [
289
+ 'Post a comment on GitHub with key findings:',
290
+ '```bash',
291
+ 'gh issue comment $TASK_ID --body "<COMMENT>"',
292
+ '```',
293
+ ].join('\n'),
294
+ transitionToReview: [
295
+ 'Add "in review" label on GitHub:',
296
+ '```bash',
297
+ 'gh issue edit $TASK_ID --remove-label "in progress" --add-label "in review" 2>/dev/null || true',
298
+ '```',
299
+ ].join('\n'),
300
+ postSummary: [
301
+ 'Post final summary comment on GitHub:',
302
+ '```bash',
303
+ 'gh issue comment $TASK_ID --body "<SUMMARY>"',
304
+ '```',
305
+ ].join('\n'),
306
+ };
307
+ }
308
+
309
+ return null;
310
+ }
311
+
205
312
  /**
206
313
  * Generate the dynamic parts of the prompt from the team.
314
+ * @param {Array} team - resolved team array
315
+ * @param {Object} opts - optional { tracker, config } for tracker-specific execution steps
207
316
  */
208
- export function generateTeamPrompt(team) {
317
+ export function generateTeamPrompt(team, opts = {}) {
209
318
  const count = team.length;
210
319
  const agentWord = count === 1 ? "one AI agent" : `${count} AI agents`;
211
320
 
@@ -246,15 +355,55 @@ export function generateTeamPrompt(team) {
246
355
  `${a.badge} ${a.planning}`
247
356
  ).join("\n");
248
357
 
249
- // Execution steps
358
+ // Execution steps — inject tracker-specific commands
359
+ const cmds = trackerCommands(opts.tracker, opts.config);
250
360
  const execAgents = team
251
361
  .filter(a => a.execution)
252
362
  .sort((a, b) => a.execution.order - b.execution.order);
253
363
  let stepNum = 1;
254
364
  const execSteps = [];
255
365
  for (const a of execAgents) {
256
- const tasks = a.execution.tasks.map((t, i) => `${i + 1}. ${t}`).join("\n");
257
- execSteps.push(`### Step ${stepNum} — ${a.name} ${a.execution.step.replace(/^[A-Za-z]+ /, "")}:\n${tasks}`);
366
+ let tasks = [...a.execution.tasks];
367
+
368
+ if (cmds) {
369
+ // Bart: expand PR link + tracker comment tasks with actual commands
370
+ if (a.name === "Bart") {
371
+ tasks = tasks.map(t => {
372
+ if (/PR link/i.test(t) || /Post PR.*tracker/i.test(t)) {
373
+ return `Push and create PR.\n ${cmds.postPrLink}`;
374
+ }
375
+ if (/screenshot.*tracker/i.test(t)) {
376
+ return `Post screenshots as separate tracker comment.\n ${cmds.agentComment.replace('<COMMENT>', 'Screenshots attached above.')}`;
377
+ }
378
+ return t;
379
+ });
380
+ }
381
+
382
+ // Jane: expand transition + summary tasks
383
+ if (a.name === "Jane") {
384
+ tasks = tasks.map(t => {
385
+ if (/transition.*in review/i.test(t) || /move.*in review/i.test(t)) {
386
+ return `Transition task to "In Review".\n ${cmds.transitionToReview}`;
387
+ }
388
+ if (/summary.*tracker/i.test(t) || /final summary/i.test(t)) {
389
+ return `Post final summary comment on tracker.\n ${cmds.postSummary}`;
390
+ }
391
+ return t;
392
+ });
393
+ }
394
+
395
+ // Dennis & Sam: expand tracker comment
396
+ if (a.name === "Dennis" || a.name === "Sam") {
397
+ const hasTrackerTask = tasks.some(t => /tracker/i.test(t) || /comment/i.test(t));
398
+ if (!hasTrackerTask) {
399
+ const findingsLabel = a.name === "Dennis" ? "Files changed, technical decisions" : "Architecture concerns or clean audit";
400
+ tasks.push(`Post comment on tracker with key findings (${findingsLabel}).\n ${cmds.agentComment}`);
401
+ }
402
+ }
403
+ }
404
+
405
+ const taskList = tasks.map((t, i) => `${i + 1}. ${t}`).join("\n");
406
+ execSteps.push(`### Step ${stepNum} — ${a.name} ${a.execution.step.replace(/^[A-Za-z]+ /, "")}:\n${taskList}`);
258
407
  stepNum++;
259
408
  }
260
409
  sections.executionSteps = execSteps.join("\n\n");
package/cli/daemon.mjs CHANGED
@@ -365,7 +365,7 @@ export async function runDaemon() {
365
365
 
366
366
  // Resolve team
367
367
  const team = resolveTeam(config);
368
- const teamSections = generateTeamPrompt(team);
368
+ const teamSections = generateTeamPrompt(team, { tracker, config });
369
369
 
370
370
  const inboxUrl = `${agentdeskServer}/api/sessions/${sessionId}/inbox`;
371
371
  const sessionUrl = `${agentdeskServer}/sessions/${sessionId}`;
package/cli/team.mjs CHANGED
@@ -82,7 +82,7 @@ export async function runTeam(taskId, opts = {}) {
82
82
 
83
83
  // Resolve team and generate dynamic prompt sections
84
84
  const team = resolveTeam(config);
85
- const teamSections = generateTeamPrompt(team);
85
+ const teamSections = generateTeamPrompt(team, { tracker, config });
86
86
 
87
87
  // --- Verify tracker permissions before starting session ---
88
88
  if (tracker) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kendoo.agentdesk/agentdesk",
3
- "version": "0.9.11",
3
+ "version": "0.9.12",
4
4
  "description": "AI team orchestrator for Claude Code — run collaborative agent sessions from your terminal",
5
5
  "type": "module",
6
6
  "bin": {
@@ -21,7 +21,7 @@
21
21
  "server": "node server/index.mjs",
22
22
  "build": "vite build",
23
23
  "preview": "vite preview",
24
- "test": "node --test tests/server.test.mjs"
24
+ "test": "node --test tests/server.test.mjs tests/agents.test.mjs"
25
25
  },
26
26
  "dependencies": {
27
27
  "bcryptjs": "^3.0.3",
package/prompts/team.md CHANGED
@@ -231,6 +231,88 @@ Jane MUST post the session start comment and set "In Progress" before moving on.
231
231
 
232
232
  Output on its own line: `SESSION_TITLE: <4-8 word title>`
233
233
 
234
+ ### Decompose (if needed)
235
+
236
+ After assessing the task, Jane evaluates whether it is **too large for a single session**. Signs of a large task:
237
+ - Multiple independent workstreams or features
238
+ - Cross-cutting concerns spanning 3+ areas of the codebase
239
+ - Estimated effort exceeding what a team session can deliver in one run
240
+ - The description explicitly describes an epic, initiative, or multi-part feature
241
+
242
+ **If the task is small/focused** — skip decomposition, proceed to PLAN.
243
+
244
+ **If the task is large**, Jane decomposes it:
245
+
246
+ 1. **Check current issue type.** If the task is already a subtask of another issue, do NOT decompose further — just work on it.
247
+
248
+ 2. **Break the feature into subtasks.** Draft 3-8 subtasks that together would deliver the full feature. Each subtask should be independently deliverable.
249
+
250
+ 3. **Classify each subtask:**
251
+ - `[basic]` — straightforward, low-risk, can be completed by the team in this session
252
+ - `[deferred]` — complex, needs user input, or depends on external factors
253
+
254
+ 4. **Create subtasks in the tracker:**
255
+
256
+ {{#JIRA}}
257
+ For each subtask, create a Jira issue with the original task as parent:
258
+ ```bash
259
+ curl -s -X POST -u "$JIRA_EMAIL:$JIRA_API_TOKEN" -H "Content-Type: application/json" \
260
+ "{{JIRA_BASE_URL}}/rest/api/3/issue" \
261
+ -d '{
262
+ "fields": {
263
+ "project": {"key": "<PROJECT_KEY>"},
264
+ "parent": {"key": "{{TASK_ID}}"},
265
+ "summary": "<subtask summary>",
266
+ "description": {"type":"doc","version":1,"content":[{"type":"paragraph","content":[{"type":"text","text":"<subtask description>"}]}]},
267
+ "issuetype": {"name": "Subtask"},
268
+ "labels": ["basic"] or ["deferred"]
269
+ }
270
+ }'
271
+ ```
272
+ Note: Extract the project key from {{TASK_ID}} (the part before the hyphen).
273
+ {{/JIRA}}
274
+
275
+ {{#LINEAR}}
276
+ For each subtask, create a Linear sub-issue:
277
+ ```
278
+ mutation {
279
+ issueCreate(input: {
280
+ teamId: "$TEAM_ID"
281
+ parentId: "$PARENT_ISSUE_ID"
282
+ title: "<subtask title>"
283
+ description: "<subtask description>"
284
+ labelIds: ["$BASIC_OR_DEFERRED_LABEL_ID"]
285
+ }) { success issue { id identifier title } }
286
+ }
287
+ ```
288
+ First fetch the parent issue's teamId and id. Create "basic" and "deferred" labels if they don't exist.
289
+ {{/LINEAR}}
290
+
291
+ {{#GITHUB}}
292
+ GitHub doesn't support native subtasks. Instead:
293
+ - Edit the original issue body to add a task list:
294
+ ```
295
+ ## Subtasks
296
+ - [ ] [basic] Subtask 1 description
297
+ - [ ] [basic] Subtask 2 description
298
+ - [ ] [deferred] Subtask 3 description
299
+ ```
300
+ - Create separate linked issues for each subtask with a `subtask` label:
301
+ ```bash
302
+ gh issue create --title "[basic] Subtask title" --body "Parent: #{{TASK_ID}}\n\nDescription..." --label subtask
303
+ ```
304
+ {{/GITHUB}}
305
+
306
+ {{#NO_TRACKER}}
307
+ Without a tracker, list the subtasks in the session output with their classification. The user can create tracker tasks manually.
308
+ {{/NO_TRACKER}}
309
+
310
+ 5. **Post decomposition summary** on the tracker as a comment. List all subtasks with their classification.
311
+
312
+ 6. **Announce to the team** which `[basic]` subtasks will be tackled in this session. The `[deferred]` subtasks are left for the user to schedule.
313
+
314
+ 7. **Continue to PLAN** with only the `[basic]` subtasks in scope.
315
+
234
316
  ---
235
317
 
236
318
  ## PLAN (1-2 rounds max)