@vibescope/mcp-server 0.2.9 → 0.3.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.
Files changed (95) hide show
  1. package/CHANGELOG.md +84 -84
  2. package/README.md +194 -194
  3. package/dist/api-client.d.ts +36 -0
  4. package/dist/api-client.js +34 -0
  5. package/dist/cli.d.ts +1 -1
  6. package/dist/cli.js +30 -38
  7. package/dist/handlers/discovery.js +2 -0
  8. package/dist/handlers/session.d.ts +11 -0
  9. package/dist/handlers/session.js +101 -0
  10. package/dist/handlers/tasks.d.ts +8 -0
  11. package/dist/handlers/tasks.js +163 -3
  12. package/dist/handlers/tool-docs.js +840 -828
  13. package/dist/handlers/validation.js +45 -2
  14. package/dist/index.js +73 -73
  15. package/dist/setup.js +6 -6
  16. package/dist/templates/agent-guidelines.js +185 -185
  17. package/dist/templates/help-content.js +1622 -1544
  18. package/dist/tools.js +126 -74
  19. package/dist/utils.d.ts +15 -11
  20. package/dist/utils.js +53 -28
  21. package/docs/TOOLS.md +2406 -2053
  22. package/package.json +51 -51
  23. package/scripts/generate-docs.ts +212 -212
  24. package/scripts/version-bump.ts +203 -203
  25. package/src/api-client.test.ts +723 -723
  26. package/src/api-client.ts +2561 -2499
  27. package/src/cli.test.ts +24 -8
  28. package/src/cli.ts +204 -212
  29. package/src/handlers/__test-setup__.ts +236 -236
  30. package/src/handlers/__test-utils__.ts +87 -87
  31. package/src/handlers/blockers.test.ts +468 -468
  32. package/src/handlers/blockers.ts +163 -163
  33. package/src/handlers/bodies-of-work.test.ts +704 -704
  34. package/src/handlers/bodies-of-work.ts +526 -526
  35. package/src/handlers/connectors.test.ts +834 -834
  36. package/src/handlers/connectors.ts +229 -229
  37. package/src/handlers/cost.test.ts +462 -462
  38. package/src/handlers/cost.ts +285 -285
  39. package/src/handlers/decisions.test.ts +382 -382
  40. package/src/handlers/decisions.ts +153 -153
  41. package/src/handlers/deployment.test.ts +551 -551
  42. package/src/handlers/deployment.ts +541 -541
  43. package/src/handlers/discovery.test.ts +206 -206
  44. package/src/handlers/discovery.ts +392 -390
  45. package/src/handlers/fallback.test.ts +537 -537
  46. package/src/handlers/fallback.ts +194 -194
  47. package/src/handlers/file-checkouts.test.ts +750 -750
  48. package/src/handlers/file-checkouts.ts +185 -185
  49. package/src/handlers/findings.test.ts +633 -633
  50. package/src/handlers/findings.ts +239 -239
  51. package/src/handlers/git-issues.test.ts +631 -631
  52. package/src/handlers/git-issues.ts +136 -136
  53. package/src/handlers/ideas.test.ts +644 -644
  54. package/src/handlers/ideas.ts +207 -207
  55. package/src/handlers/index.ts +84 -84
  56. package/src/handlers/milestones.test.ts +475 -475
  57. package/src/handlers/milestones.ts +180 -180
  58. package/src/handlers/organizations.test.ts +826 -826
  59. package/src/handlers/organizations.ts +315 -315
  60. package/src/handlers/progress.test.ts +269 -269
  61. package/src/handlers/progress.ts +77 -77
  62. package/src/handlers/project.test.ts +546 -546
  63. package/src/handlers/project.ts +239 -239
  64. package/src/handlers/requests.test.ts +303 -303
  65. package/src/handlers/requests.ts +99 -99
  66. package/src/handlers/roles.test.ts +305 -305
  67. package/src/handlers/roles.ts +219 -219
  68. package/src/handlers/session.test.ts +998 -875
  69. package/src/handlers/session.ts +839 -730
  70. package/src/handlers/sprints.test.ts +732 -732
  71. package/src/handlers/sprints.ts +537 -537
  72. package/src/handlers/tasks.test.ts +931 -907
  73. package/src/handlers/tasks.ts +1121 -945
  74. package/src/handlers/tool-categories.test.ts +66 -66
  75. package/src/handlers/tool-docs.ts +1109 -1096
  76. package/src/handlers/types.test.ts +259 -259
  77. package/src/handlers/types.ts +175 -175
  78. package/src/handlers/validation.test.ts +582 -582
  79. package/src/handlers/validation.ts +159 -113
  80. package/src/index.test.ts +674 -0
  81. package/src/index.ts +792 -792
  82. package/src/setup.test.ts +233 -233
  83. package/src/setup.ts +404 -403
  84. package/src/templates/agent-guidelines.ts +210 -210
  85. package/src/templates/help-content.ts +1751 -1673
  86. package/src/token-tracking.test.ts +463 -463
  87. package/src/token-tracking.ts +166 -166
  88. package/src/tools.test.ts +416 -0
  89. package/src/tools.ts +3607 -3555
  90. package/src/utils.test.ts +785 -683
  91. package/src/utils.ts +469 -436
  92. package/src/validators.test.ts +223 -223
  93. package/src/validators.ts +249 -249
  94. package/tsconfig.json +16 -16
  95. package/vitest.config.ts +14 -14
package/dist/cli.js CHANGED
@@ -13,6 +13,7 @@
13
13
  */
14
14
  import { execSync } from 'child_process';
15
15
  import { runSetup } from './setup.js';
16
+ import { normalizeGitUrl as normalizeGitUrlFn } from './utils.js';
16
17
  // ============================================================================
17
18
  // Configuration (read at runtime for testability)
18
19
  // ============================================================================
@@ -25,17 +26,8 @@ function getApiUrl() {
25
26
  // ============================================================================
26
27
  // Git URL Detection
27
28
  // ============================================================================
28
- export function normalizeGitUrl(url) {
29
- // Remove .git suffix
30
- let normalized = url.replace(/\.git$/, '');
31
- // Convert SSH to HTTPS format
32
- if (normalized.startsWith('git@')) {
33
- normalized = normalized
34
- .replace(/^git@/, 'https://')
35
- .replace(/:([^/])/, '/$1');
36
- }
37
- return normalized;
38
- }
29
+ // Re-export normalizeGitUrl from utils for backwards compatibility with tests
30
+ export { normalizeGitUrl } from './utils.js';
39
31
  export function detectGitUrl() {
40
32
  try {
41
33
  const url = execSync('git config --get remote.origin.url', {
@@ -43,7 +35,7 @@ export function detectGitUrl() {
43
35
  timeout: 5000,
44
36
  stdio: ['pipe', 'pipe', 'pipe'],
45
37
  }).trim();
46
- return normalizeGitUrl(url);
38
+ return normalizeGitUrlFn(url);
47
39
  }
48
40
  catch {
49
41
  return null;
@@ -124,32 +116,32 @@ async function main() {
124
116
  }
125
117
  }
126
118
  else if (command === 'help' || command === '--help' || command === '-h') {
127
- console.log(`
128
- Vibescope CLI - Setup wizard and enforcement verification tool
129
-
130
- Usage:
131
- vibescope-cli setup Interactive setup wizard for your IDE
132
- vibescope-cli verify [options] Check Vibescope compliance before exit
133
-
134
- Setup:
135
- Configures Vibescope MCP integration for:
136
- - Claude Code (CLI)
137
- - Claude Desktop
138
- - Cursor
139
- - Gemini CLI
140
-
141
- Verify Options:
142
- --git-url <url> Git repository URL (auto-detected if not provided)
143
- --project-id <id> Vibescope project UUID
144
-
145
- Exit Codes (verify):
146
- 0 Compliant - agent can exit
147
- 1 Non-compliant - agent should continue work
148
- 2 Error - allow exit with warning
149
-
150
- Environment Variables:
151
- VIBESCOPE_API_KEY Required for verify - Your Vibescope API key
152
- VIBESCOPE_API_URL Optional - API URL (default: https://vibescope.dev)
119
+ console.log(`
120
+ Vibescope CLI - Setup wizard and enforcement verification tool
121
+
122
+ Usage:
123
+ vibescope-cli setup Interactive setup wizard for your IDE
124
+ vibescope-cli verify [options] Check Vibescope compliance before exit
125
+
126
+ Setup:
127
+ Configures Vibescope MCP integration for:
128
+ - Claude Code (CLI)
129
+ - Claude Desktop
130
+ - Cursor
131
+ - Gemini CLI
132
+
133
+ Verify Options:
134
+ --git-url <url> Git repository URL (auto-detected if not provided)
135
+ --project-id <id> Vibescope project UUID
136
+
137
+ Exit Codes (verify):
138
+ 0 Compliant - agent can exit
139
+ 1 Non-compliant - agent should continue work
140
+ 2 Error - allow exit with warning
141
+
142
+ Environment Variables:
143
+ VIBESCOPE_API_KEY Required for verify - Your Vibescope API key
144
+ VIBESCOPE_API_URL Optional - API URL (default: https://vibescope.dev)
153
145
  `);
154
146
  process.exit(0);
155
147
  }
@@ -40,6 +40,7 @@ export const TOOL_CATEGORIES = {
40
40
  { name: 'get_token_usage', brief: 'View token stats' },
41
41
  { name: 'report_token_usage', brief: 'Report actual Claude API usage' },
42
42
  { name: 'heartbeat', brief: 'Maintain active status' },
43
+ { name: 'signal_idle', brief: 'Signal agent is idle' },
43
44
  { name: 'end_work_session', brief: 'End session, release tasks' },
44
45
  { name: 'confirm_agent_setup', brief: 'Mark agent setup as complete' },
45
46
  ],
@@ -69,6 +70,7 @@ export const TOOL_CATEGORIES = {
69
70
  { name: 'complete_task', brief: 'Mark task done' },
70
71
  { name: 'delete_task', brief: 'Remove a task' },
71
72
  { name: 'cancel_task', brief: 'Cancel task with reason' },
73
+ { name: 'release_task', brief: 'Unclaim task to pending' },
72
74
  { name: 'batch_update_tasks', brief: 'Update multiple tasks' },
73
75
  { name: 'batch_complete_tasks', brief: 'Complete multiple tasks' },
74
76
  { name: 'add_task_reference', brief: 'Add URL to task' },
@@ -26,6 +26,17 @@ export declare const reportTokenUsage: Handler;
26
26
  * This marks the agent type as onboarded, so future sessions won't receive setup instructions.
27
27
  */
28
28
  export declare const confirmAgentSetup: Handler;
29
+ /**
30
+ * Signal that the agent is idle (no more tasks to work on).
31
+ * This immediately updates the session status to 'idle', providing real-time
32
+ * visibility on the dashboard instead of waiting for heartbeat timeout.
33
+ *
34
+ * Call this when:
35
+ * - complete_task returns no next_task
36
+ * - get_next_task returns no tasks
37
+ * - There's genuinely no work to do
38
+ */
39
+ export declare const signalIdle: Handler;
29
40
  /**
30
41
  * Session handlers registry
31
42
  */
@@ -13,6 +13,7 @@ import { parseArgs, createEnumValidator } from '../validators.js';
13
13
  import { getApiClient } from '../api-client.js';
14
14
  import { getAgentGuidelinesTemplate, getAgentGuidelinesSummary } from '../templates/agent-guidelines.js';
15
15
  import { getFallbackHelpContent, getAvailableHelpTopics } from '../templates/help-content.js';
16
+ import { normalizeGitUrl } from '../utils.js';
16
17
  // Auto-detect machine hostname for worktree tracking
17
18
  const MACHINE_HOSTNAME = os.hostname();
18
19
  const VALID_MODES = ['lite', 'full'];
@@ -41,6 +42,9 @@ export const startWorkSession = async (args, ctx) => {
41
42
  const { project_id, git_url, mode, model, role, hostname: providedHostname, agent_type } = parseArgs(args, startWorkSessionSchema);
42
43
  // Use auto-detected hostname if not provided - enables machine-aware worktree filtering
43
44
  const hostname = providedHostname || MACHINE_HOSTNAME;
45
+ // Normalize git_url and track if it was changed - helps agents understand URL matching
46
+ const normalizedGitUrl = git_url ? normalizeGitUrl(git_url) : null;
47
+ const gitUrlWasNormalized = git_url && normalizedGitUrl && git_url !== normalizedGitUrl;
44
48
  const { session, updateSession } = ctx;
45
49
  // Reset token tracking for new session with model info
46
50
  // Model is now open-ended - use as-is (normalize Claude model names for consistency)
@@ -145,6 +149,19 @@ export const startWorkSession = async (args, ctx) => {
145
149
  result.persona = data.persona;
146
150
  result.role = data.role;
147
151
  result.project = data.project;
152
+ // Inform agent if git_url was normalized (helps explain URL matching behavior)
153
+ if (gitUrlWasNormalized) {
154
+ result.git_url_normalized = {
155
+ message: 'Your git URL was normalized for project lookup. All URL formats for the same repository resolve to the same project.',
156
+ original: git_url,
157
+ normalized: normalizedGitUrl,
158
+ examples: [
159
+ 'git@github.com:owner/repo.git → https://github.com/owner/repo',
160
+ 'https://GITHUB.COM/Owner/Repo/ → https://github.com/owner/repo',
161
+ 'http://github.com/owner/repo.git → https://github.com/owner/repo',
162
+ ],
163
+ };
164
+ }
148
165
  // Add task data
149
166
  if (data.next_task) {
150
167
  result.next_task = data.next_task;
@@ -205,6 +222,31 @@ export const startWorkSession = async (args, ctx) => {
205
222
  command: `git worktree add ../<project>-<persona>-<task> -b feature/<task-id> ${baseBranch}`,
206
223
  help: 'Run get_help("git") for full instructions',
207
224
  };
225
+ // Add FIRST_TIME_CONNECTION guidance for git-flow
226
+ if (data.project.git_workflow === 'git-flow') {
227
+ result.FIRST_TIME_CONNECTION = {
228
+ workflow: 'git-flow',
229
+ steps: [
230
+ `1. git checkout ${data.project.git_develop_branch || 'develop'}`,
231
+ `2. git pull origin ${data.project.git_develop_branch || 'develop'}`,
232
+ '3. All feature branches must be created from develop',
233
+ ],
234
+ warning: 'Working from main or stale branches causes merge conflicts.',
235
+ base_branch: data.project.git_develop_branch || 'develop',
236
+ };
237
+ }
238
+ else if (data.project.git_workflow === 'github-flow') {
239
+ result.FIRST_TIME_CONNECTION = {
240
+ workflow: 'github-flow',
241
+ steps: [
242
+ `1. git checkout ${data.project.git_main_branch || 'main'}`,
243
+ `2. git pull origin ${data.project.git_main_branch || 'main'}`,
244
+ '3. All feature branches must be created from main',
245
+ ],
246
+ warning: 'Working from stale branches causes merge conflicts.',
247
+ base_branch: data.project.git_main_branch || 'main',
248
+ };
249
+ }
208
250
  }
209
251
  }
210
252
  // Add agent setup instructions if this is a new agent type for the project
@@ -601,6 +643,64 @@ export const confirmAgentSetup = async (args, _ctx) => {
601
643
  },
602
644
  };
603
645
  };
646
+ const signalIdleSchema = {
647
+ session_id: { type: 'string' },
648
+ };
649
+ /**
650
+ * Signal that the agent is idle (no more tasks to work on).
651
+ * This immediately updates the session status to 'idle', providing real-time
652
+ * visibility on the dashboard instead of waiting for heartbeat timeout.
653
+ *
654
+ * Call this when:
655
+ * - complete_task returns no next_task
656
+ * - get_next_task returns no tasks
657
+ * - There's genuinely no work to do
658
+ */
659
+ export const signalIdle = async (args, ctx) => {
660
+ const { session_id } = parseArgs(args, signalIdleSchema);
661
+ const { session } = ctx;
662
+ const targetSession = session_id || session.currentSessionId;
663
+ if (!targetSession) {
664
+ return {
665
+ result: {
666
+ error: 'No active session. Call start_work_session first.',
667
+ },
668
+ };
669
+ }
670
+ const apiClient = getApiClient();
671
+ const response = await apiClient.signalIdle(targetSession);
672
+ if (!response.ok) {
673
+ return {
674
+ result: {
675
+ error: response.error || 'Failed to signal idle',
676
+ },
677
+ };
678
+ }
679
+ return {
680
+ result: {
681
+ success: true,
682
+ session_id: targetSession,
683
+ status: 'idle',
684
+ message: 'Session marked as idle. Dashboard will show you as inactive until you start a new task.',
685
+ next_steps: [
686
+ 'Wait for new tasks to be added by the user',
687
+ 'Or call start_fallback_activity() to do background work',
688
+ 'Or call end_work_session() if you are done for the session',
689
+ ],
690
+ AUTONOMY_RULES: {
691
+ message: 'Agents must be autonomous. Follow these rules:',
692
+ rules: [
693
+ 'Never ask "should I continue?" → Just continue',
694
+ 'Never ask "should I clear context?" → Just clear it',
695
+ 'Never say "let me know what to do" → Use get_next_task or fallback activities',
696
+ 'When context grows large: /clear → start_work_session (don\'t ask, just do it)',
697
+ ],
698
+ preferred_activities: ['code_review', 'security_review', 'test_coverage', 'documentation_review', 'dependency_audit'],
699
+ directive: 'BE PROACTIVE: Start a fallback activity now rather than waiting for user input.',
700
+ },
701
+ },
702
+ };
703
+ };
604
704
  /**
605
705
  * Session handlers registry
606
706
  */
@@ -608,6 +708,7 @@ export const sessionHandlers = {
608
708
  start_work_session: startWorkSession,
609
709
  heartbeat: heartbeat,
610
710
  end_work_session: endWorkSession,
711
+ signal_idle: signalIdle,
611
712
  get_help: getHelp,
612
713
  get_token_usage: getTokenUsage,
613
714
  report_token_usage: reportTokenUsage,
@@ -12,6 +12,8 @@
12
12
  * - update_task
13
13
  * - complete_task
14
14
  * - delete_task
15
+ * - release_task
16
+ * - cancel_task
15
17
  * - add_task_reference
16
18
  * - remove_task_reference
17
19
  * - batch_update_tasks
@@ -39,6 +41,12 @@ export declare const addTask: Handler;
39
41
  export declare const updateTask: Handler;
40
42
  export declare const completeTask: Handler;
41
43
  export declare const deleteTask: Handler;
44
+ /**
45
+ * Release a task back to pending status.
46
+ * Use when an agent needs to give up a claimed task (context limits, conflicts, user request).
47
+ */
48
+ export declare const releaseTask: Handler;
49
+ export declare const cancelTask: Handler;
42
50
  export declare const addTaskReference: Handler;
43
51
  export declare const removeTaskReference: Handler;
44
52
  export declare const batchUpdateTasks: Handler;
@@ -12,6 +12,8 @@
12
12
  * - update_task
13
13
  * - complete_task
14
14
  * - delete_task
15
+ * - release_task
16
+ * - cancel_task
15
17
  * - add_task_reference
16
18
  * - remove_task_reference
17
19
  * - batch_update_tasks
@@ -65,6 +67,19 @@ const completeTaskSchema = {
65
67
  const deleteTaskSchema = {
66
68
  task_id: { type: 'string', required: true, validate: uuidValidator },
67
69
  };
70
+ const releaseTaskSchema = {
71
+ task_id: { type: 'string', required: true, validate: uuidValidator },
72
+ reason: { type: 'string' },
73
+ };
74
+ // Valid reasons for task cancellation
75
+ const VALID_CANCELLED_REASONS = [
76
+ 'pr_closed', 'superseded', 'user_cancelled', 'validation_failed', 'obsolete', 'blocked'
77
+ ];
78
+ const cancelTaskSchema = {
79
+ task_id: { type: 'string', required: true, validate: uuidValidator },
80
+ cancelled_reason: { type: 'string', validate: createEnumValidator(VALID_CANCELLED_REASONS) },
81
+ cancellation_note: { type: 'string' },
82
+ };
68
83
  const addTaskReferenceSchema = {
69
84
  task_id: { type: 'string', required: true, validate: uuidValidator },
70
85
  url: { type: 'string', required: true },
@@ -194,6 +209,21 @@ export const getNextTask = async (args, ctx) => {
194
209
  }
195
210
  else {
196
211
  result.task = null;
212
+ // Add IDLE_GUIDANCE when no tasks are available
213
+ result.IDLE_GUIDANCE = {
214
+ message: 'No tasks available. Follow these steps:',
215
+ steps: [
216
+ '1. Call signal_idle() to update dashboard immediately - shows you are available',
217
+ '2. Start a fallback_activity (code_review, security_review, test_coverage, etc.)',
218
+ '3. Never ask "what should I do?" - be autonomous',
219
+ ],
220
+ autonomy_rules: [
221
+ 'Never ask "should I continue?" → Just continue',
222
+ 'Never say "let me know what to do" → Use fallback activities',
223
+ 'When context grows large: /clear → start_work_session (don\'t ask, just do it)',
224
+ ],
225
+ next_action: `signal_idle() then start_fallback_activity(project_id: "${project_id}", activity: "code_review")`,
226
+ };
197
227
  }
198
228
  if (data.blocking_task)
199
229
  result.blocking_task = true;
@@ -281,11 +311,15 @@ export const updateTask = async (args, ctx) => {
281
311
  },
282
312
  };
283
313
  }
284
- if (response.error?.includes('task_claimed') || response.error?.includes('being worked on')) {
314
+ if (response.error?.includes('task_claimed') || response.error?.includes('task_already_claimed') || response.error?.includes('being worked on') || response.error?.includes('already being worked on')) {
315
+ const data = response.data;
285
316
  return {
286
317
  result: {
287
- error: 'task_claimed',
288
- message: response.error,
318
+ error: 'task_already_claimed',
319
+ message: data?.message || response.error || 'Task is already claimed by another agent',
320
+ claimed_by: data?.claimed_by,
321
+ claimed_session_id: data?.claimed_session_id,
322
+ suggestion: 'Use get_next_task() to get a different available task, or wait for the claiming agent to finish.',
289
323
  },
290
324
  };
291
325
  }
@@ -330,6 +364,63 @@ export const updateTask = async (args, ctx) => {
330
364
  test_patterns: ['*.test.ts', '*.spec.ts', '*.test.js', '*.spec.js', '__tests__/*'],
331
365
  note: 'Validators will check for test file changes during review. Documentation-only or config changes may not require tests.',
332
366
  };
367
+ // Add comprehensive WORKTREE RULES for branching workflows
368
+ // This reminds agents of the critical workflow order
369
+ result.WORKTREE_RULES = {
370
+ mandatory: true,
371
+ rules: [
372
+ '1. Create worktree BEFORE any file edits - reading is fine, editing requires worktree first',
373
+ '2. Naming: ../PROJECT-PERSONA-short-desc (max 24 chars for description)',
374
+ '3. Command: git worktree add ../PROJECT-PERSONA-desc -b feature/TASKID-desc BASE_BRANCH',
375
+ '4. Report location: heartbeat(current_worktree_path: "...")',
376
+ '5. Store path: update_task(task_id, worktree_path: "...")',
377
+ '6. REBASE before PR: git fetch origin && git rebase origin/BASE_BRANCH && git push --force-with-lease',
378
+ ],
379
+ rebase_before_pr: {
380
+ mandatory: true,
381
+ why: 'Without rebasing, your branch may contain old versions of files that other agents modified. When merged, your old version overwrites their changes.',
382
+ commands: [
383
+ 'git fetch origin',
384
+ 'git rebase origin/develop # or origin/main for github-flow',
385
+ 'git push --force-with-lease',
386
+ ],
387
+ },
388
+ wrong_order: {
389
+ violation: 'Edit file → stash → create worktree → pop → commit',
390
+ why: 'Even if you eventually use a worktree, editing before creating one is a violation',
391
+ },
392
+ right_order: {
393
+ correct: 'Read to understand → create worktree → cd into it → THEN edit',
394
+ why: 'Worktrees must exist BEFORE any file modifications',
395
+ },
396
+ };
397
+ // Add HOTFIX_WORKFLOW guidance when branch name indicates hotfix
398
+ if (git_branch && git_branch.includes('hotfix/')) {
399
+ result.HOTFIX_WORKFLOW = {
400
+ message: 'HOTFIX detected - special workflow applies:',
401
+ steps: [
402
+ '1. Create worktree from MAIN (not develop): git worktree add ../PROJECT-PERSONA-hotfix-desc -b hotfix/TASKID-desc main',
403
+ '2. Work in worktree and make your fix',
404
+ '3. Commit: git add -A && git commit -m "fix: description"',
405
+ '4. Push: git push -u origin hotfix/TASKID-desc',
406
+ '5. Create PR targeting MAIN: gh pr create --base main --title "fix: ..." --body "Hotfix for production"',
407
+ '6. Remove worktree immediately after PR',
408
+ ],
409
+ important: 'Hotfixes go to MAIN, not develop. They are later merged to develop separately.',
410
+ worktree_required: true,
411
+ };
412
+ }
413
+ // Guidance for when investigation reveals fix already exists
414
+ result.FIX_ALREADY_EXISTS_GUIDANCE = {
415
+ message: 'If investigation reveals the fix already exists but needs deployment:',
416
+ steps: [
417
+ '1. Add finding: add_finding(project_id, title: "Fix exists, awaits deployment", category: "other", severity: "info", description: "...", related_task_id: task_id)',
418
+ '2. Complete task: complete_task(task_id, summary: "Fix already exists in codebase (PR #XXX). Needs deployment.")',
419
+ '3. Check deployment: check_deployment_status(project_id)',
420
+ '4. Request deployment if not pending: request_deployment(project_id, notes: "Includes fix for [issue]")',
421
+ ],
422
+ rationale: 'This prevents tasks from being blocked waiting for deployment when the actual work is done.',
423
+ };
333
424
  }
334
425
  return { result };
335
426
  };
@@ -365,6 +456,28 @@ export const completeTask = async (args, ctx) => {
365
456
  // Git workflow instructions are already in API response but we need to fetch
366
457
  // task details if we want to include them (API should return these)
367
458
  result.next_action = data.next_action;
459
+ // Add mandatory action reminders for complete_task
460
+ result.MANDATORY_ACTIONS = {
461
+ message: 'Before marking task complete, ensure you have done the following:',
462
+ checklist: [
463
+ 'If you made code changes: Commit and push all changes to your branch',
464
+ 'REBASE before PR: git fetch origin && git rebase origin/BASE_BRANCH && git push --force-with-lease',
465
+ 'If project uses PR workflow: Create PR targeting correct branch (develop for git-flow, main for github-flow)',
466
+ 'If using worktree: Remove worktree IMMEDIATELY after PR is created',
467
+ ],
468
+ sequence: 'Commit → Rebase → Push → PR created → complete_task() → remove worktree → next task',
469
+ important: 'DO NOT wait for PR review/merge - validation handles that. Complete task immediately after PR.',
470
+ rebase_warning: 'Always rebase before creating PR to avoid overwriting other agents\' work.',
471
+ };
472
+ // Add worktree cleanup reminder if worktree was used
473
+ if (data.context?.worktree_path) {
474
+ result.worktree_cleanup = {
475
+ required: true,
476
+ path: data.context.worktree_path,
477
+ command: `git worktree remove ${data.context.worktree_path}`,
478
+ timing: 'Remove immediately after PR is created and complete_task is called',
479
+ };
480
+ }
368
481
  return { result };
369
482
  };
370
483
  export const deleteTask = async (args, ctx) => {
@@ -376,6 +489,51 @@ export const deleteTask = async (args, ctx) => {
376
489
  }
377
490
  return { result: { success: true, deleted_id: task_id } };
378
491
  };
492
+ /**
493
+ * Release a task back to pending status.
494
+ * Use when an agent needs to give up a claimed task (context limits, conflicts, user request).
495
+ */
496
+ export const releaseTask = async (args, ctx) => {
497
+ const { task_id, reason } = parseArgs(args, releaseTaskSchema);
498
+ const api = getApiClient();
499
+ const response = await api.releaseTask(task_id, {
500
+ reason,
501
+ session_id: ctx.session.currentSessionId || undefined,
502
+ });
503
+ if (!response.ok) {
504
+ return { result: { error: response.error || 'Failed to release task' }, isError: true };
505
+ }
506
+ return {
507
+ result: {
508
+ success: true,
509
+ task_id,
510
+ message: response.data?.message || 'Task released and returned to pending status',
511
+ reason: reason || null,
512
+ hint: 'The task is now available for other agents to claim. Call get_next_task() to get a new task.',
513
+ },
514
+ };
515
+ };
516
+ export const cancelTask = async (args, ctx) => {
517
+ const { task_id, cancelled_reason, cancellation_note } = parseArgs(args, cancelTaskSchema);
518
+ const api = getApiClient();
519
+ // Cast cancelled_reason to the expected union type - validation already ensures it's valid
520
+ const response = await api.cancelTask(task_id, {
521
+ cancelled_reason: cancelled_reason,
522
+ cancellation_note,
523
+ session_id: ctx.session.currentSessionId || undefined,
524
+ });
525
+ if (!response.ok) {
526
+ return { result: { error: response.error || 'Failed to cancel task' }, isError: true };
527
+ }
528
+ return {
529
+ result: {
530
+ success: true,
531
+ task_id,
532
+ cancelled_reason: cancelled_reason || null,
533
+ message: response.data?.message || `Task cancelled${cancelled_reason ? ` (${cancelled_reason})` : ''}`,
534
+ },
535
+ };
536
+ };
379
537
  export const addTaskReference = async (args, ctx) => {
380
538
  const { task_id, url, label } = parseArgs(args, addTaskReferenceSchema);
381
539
  const api = getApiClient();
@@ -740,6 +898,8 @@ export const taskHandlers = {
740
898
  update_task: updateTask,
741
899
  complete_task: completeTask,
742
900
  delete_task: deleteTask,
901
+ release_task: releaseTask,
902
+ cancel_task: cancelTask,
743
903
  add_task_reference: addTaskReference,
744
904
  remove_task_reference: removeTaskReference,
745
905
  batch_update_tasks: batchUpdateTasks,