@specforge/mcp 1.5.3 → 2.0.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 (40) hide show
  1. package/dist/lib/format.d.ts +145 -0
  2. package/dist/lib/format.d.ts.map +1 -0
  3. package/dist/lib/format.js +227 -0
  4. package/dist/lib/format.js.map +1 -0
  5. package/dist/lib/index.d.ts +7 -0
  6. package/dist/lib/index.d.ts.map +1 -0
  7. package/dist/lib/index.js +7 -0
  8. package/dist/lib/index.js.map +1 -0
  9. package/dist/lib/response.d.ts +119 -0
  10. package/dist/lib/response.d.ts.map +1 -0
  11. package/dist/lib/response.js +123 -0
  12. package/dist/lib/response.js.map +1 -0
  13. package/dist/patterns/index.d.ts +9 -0
  14. package/dist/patterns/index.d.ts.map +1 -0
  15. package/dist/patterns/index.js +15 -0
  16. package/dist/patterns/index.js.map +1 -0
  17. package/dist/patterns/inheritance.d.ts +193 -0
  18. package/dist/patterns/inheritance.d.ts.map +1 -0
  19. package/dist/patterns/inheritance.js +265 -0
  20. package/dist/patterns/inheritance.js.map +1 -0
  21. package/dist/server.d.ts.map +1 -1
  22. package/dist/server.js +21 -4
  23. package/dist/server.js.map +1 -1
  24. package/dist/tools/index.d.ts +0 -8
  25. package/dist/tools/index.d.ts.map +1 -1
  26. package/dist/tools/index.js +1384 -695
  27. package/dist/tools/index.js.map +1 -1
  28. package/dist/types/index.d.ts +567 -19
  29. package/dist/types/index.d.ts.map +1 -1
  30. package/dist/types/index.js +244 -1
  31. package/dist/types/index.js.map +1 -1
  32. package/dist/validation/index.d.ts +1 -1
  33. package/dist/validation/index.d.ts.map +1 -1
  34. package/dist/validation/index.js +163 -0
  35. package/dist/validation/index.js.map +1 -1
  36. package/dist/validation/ticket-validation.d.ts +162 -0
  37. package/dist/validation/ticket-validation.d.ts.map +1 -0
  38. package/dist/validation/ticket-validation.js +311 -0
  39. package/dist/validation/ticket-validation.js.map +1 -0
  40. package/package.json +6 -5
@@ -14,7 +14,9 @@
14
14
  * - Search: Search, find by file/tag, find related
15
15
  * - Git: Link commits and PRs, get linked items
16
16
  */
17
- import { ValidationError, ApiError, validateToolArgs, formatMCPError, transformError, } from '../validation/index.js';
17
+ import { ValidationError, ApiError, validateToolArgs, formatMCPError, transformError, validateTicket, filterWarningsByStrictness, } from '../validation/index.js';
18
+ import { resolvePatternsWithCache, getPatternOverrides, flattenCodeStandards, } from '../patterns/index.js';
19
+ import { getResponseModeFromArgs, formatWriteResponse, } from '../lib/response.js';
18
20
  /**
19
21
  * Get list of all available tools
20
22
  *
@@ -23,8 +25,72 @@ import { ValidationError, ApiError, validateToolArgs, formatMCPError, transformE
23
25
  *
24
26
  * @returns Array of tool definitions
25
27
  */
28
+ /**
29
+ * Format parameter definition for read operations
30
+ * Allows AI agents to request TOON-formatted responses for token efficiency
31
+ */
32
+ const FORMAT_PARAMETER = {
33
+ type: 'string',
34
+ enum: ['json', 'toon'],
35
+ description: 'Output format. Use "toon" for ~44% token reduction. Default: "json"',
36
+ default: 'json',
37
+ };
38
+ /**
39
+ * List of read operation tool names that support the format parameter
40
+ * Write operations (create_*, update_*, report_*, etc.) are excluded
41
+ */
42
+ const READ_TOOL_NAMES = new Set([
43
+ // Project operations
44
+ 'list_projects',
45
+ 'get_project',
46
+ 'lookup_project',
47
+ // Specification operations
48
+ 'list_specifications',
49
+ 'get_specification',
50
+ 'lookup_specification',
51
+ // Epic operations
52
+ 'list_epics',
53
+ 'get_epic',
54
+ 'lookup_epic',
55
+ // Ticket operations
56
+ 'list_tickets',
57
+ 'get_ticket',
58
+ 'lookup_ticket',
59
+ 'lookup_tickets_by_status',
60
+ // Dependency operations
61
+ 'get_dependency_tree',
62
+ // Context operations
63
+ 'get_implementation_context',
64
+ 'get_next_actionable_tickets',
65
+ 'get_blocked_tickets',
66
+ 'get_critical_path',
67
+ 'get_patterns',
68
+ // Status & Analytics
69
+ 'get_report',
70
+ // Search operations
71
+ 'search_tickets',
72
+ // Working context
73
+ 'get_working_context',
74
+ // Job status
75
+ 'get_job_status',
76
+ ]);
77
+ /**
78
+ * Add format parameter to a tool's input schema
79
+ */
80
+ function addFormatParameter(tool) {
81
+ return {
82
+ ...tool,
83
+ inputSchema: {
84
+ ...tool.inputSchema,
85
+ properties: {
86
+ ...tool.inputSchema.properties,
87
+ format: FORMAT_PARAMETER,
88
+ },
89
+ },
90
+ };
91
+ }
26
92
  export function getTools() {
27
- return [
93
+ const tools = [
28
94
  // ========================================================================
29
95
  // Core Operations - Projects (Epic 3)
30
96
  // ========================================================================
@@ -51,12 +117,26 @@ export function getTools() {
51
117
  required: ['projectId'],
52
118
  },
53
119
  },
120
+ {
121
+ name: 'lookup_project',
122
+ description: 'Fast project lookup by name. Returns minimal data (id, name, specCount) for token efficiency. Use this instead of list_projects when you just need to find a project ID.',
123
+ inputSchema: {
124
+ type: 'object',
125
+ properties: {
126
+ name: {
127
+ type: 'string',
128
+ description: 'Project name to search for (partial match, case-insensitive)',
129
+ },
130
+ },
131
+ required: ['name'],
132
+ },
133
+ },
54
134
  // ========================================================================
55
135
  // Core Operations - Specifications (Epic 3)
56
136
  // ========================================================================
57
137
  {
58
138
  name: 'list_specifications',
59
- description: 'List specifications for a project with optional status filter',
139
+ description: 'List specifications for a project with optional status and field filters. Supports pagination with limit/offset. Use fields to select specific fields and reduce token usage.',
60
140
  inputSchema: {
61
141
  type: 'object',
62
142
  properties: {
@@ -72,13 +152,60 @@ export function getTools() {
72
152
  },
73
153
  description: 'Filter by status (optional)',
74
154
  },
155
+ limit: {
156
+ type: 'integer',
157
+ minimum: 1,
158
+ maximum: 100,
159
+ description: 'Maximum number of items to return (default: 20, max: 100)',
160
+ },
161
+ offset: {
162
+ type: 'integer',
163
+ minimum: 0,
164
+ description: 'Number of items to skip for offset-based pagination (default: 0)',
165
+ },
166
+ fields: {
167
+ type: 'array',
168
+ items: {
169
+ type: 'string',
170
+ enum: [
171
+ 'id',
172
+ 'projectId',
173
+ 'title',
174
+ 'description',
175
+ 'background',
176
+ 'status',
177
+ 'priority',
178
+ 'estimatedHours',
179
+ 'goals',
180
+ 'requirements',
181
+ 'constraints',
182
+ 'guardrails',
183
+ 'techStack',
184
+ 'architecture',
185
+ 'fileStructure',
186
+ 'epicCount',
187
+ 'ticketCount',
188
+ 'completedTicketCount',
189
+ 'createdAt',
190
+ 'updatedAt',
191
+ ],
192
+ },
193
+ description: 'Select specific fields to return. Returns all fields if not specified. id is always included. Example: ["id", "title", "status"] for minimal response.',
194
+ },
75
195
  },
76
196
  required: ['projectId'],
77
197
  },
78
198
  },
79
199
  {
80
200
  name: 'get_specification',
81
- description: 'Get full specification with goals, requirements, constraints, and guardrails',
201
+ description: `Get specification with optional summary mode or full details.
202
+
203
+ Use summary: true for minimal response (~70 tokens) with key fields only (id, title, status, priority, epicCount, ticketCount, completedTicketCount).
204
+
205
+ Optional includes (ignored when summary=true):
206
+ - 'status': Progress, ticket counts, blockers (replaces get_specification_status)
207
+ - 'epics': List of epics with summary data
208
+ - 'patterns': Tech stack, code patterns, naming conventions`,
82
209
  inputSchema: {
83
210
  type: 'object',
84
211
  properties: {
@@ -86,16 +213,47 @@ export function getTools() {
86
213
  type: 'string',
87
214
  description: 'The ID of the specification to retrieve',
88
215
  },
216
+ summary: {
217
+ type: 'boolean',
218
+ description: 'Return minimal summary fields only (~70 tokens vs ~600 full). Ignores include parameter when true. Default: false',
219
+ default: false,
220
+ },
221
+ include: {
222
+ type: 'array',
223
+ items: {
224
+ type: 'string',
225
+ enum: ['status', 'epics', 'patterns'],
226
+ },
227
+ description: 'Optional data to include in response (ignored when summary=true)',
228
+ },
89
229
  },
90
230
  required: ['specificationId'],
91
231
  },
92
232
  },
233
+ {
234
+ name: 'lookup_specification',
235
+ description: 'Fast specification lookup by title. Returns minimal data (id, title, status, epicCount, ticketCount) for token efficiency.',
236
+ inputSchema: {
237
+ type: 'object',
238
+ properties: {
239
+ projectId: {
240
+ type: 'string',
241
+ description: 'Project ID to search within',
242
+ },
243
+ title: {
244
+ type: 'string',
245
+ description: 'Specification title to search for (partial match, case-insensitive)',
246
+ },
247
+ },
248
+ required: ['projectId', 'title'],
249
+ },
250
+ },
93
251
  // ========================================================================
94
252
  // Core Operations - Epics (Epic 3)
95
253
  // ========================================================================
96
254
  {
97
255
  name: 'list_epics',
98
- description: 'List epics for a specification with optional status filter',
256
+ description: 'List epics for a specification with optional status and field filters. Supports pagination with limit/offset. Use fields to select specific fields and reduce token usage.',
99
257
  inputSchema: {
100
258
  type: 'object',
101
259
  properties: {
@@ -108,13 +266,56 @@ export function getTools() {
108
266
  enum: ['todo', 'in_progress', 'completed'],
109
267
  description: 'Filter by status (optional)',
110
268
  },
269
+ limit: {
270
+ type: 'integer',
271
+ minimum: 1,
272
+ maximum: 100,
273
+ description: 'Maximum number of items to return (default: 20, max: 100)',
274
+ },
275
+ offset: {
276
+ type: 'integer',
277
+ minimum: 0,
278
+ description: 'Number of items to skip for offset-based pagination (default: 0)',
279
+ },
280
+ fields: {
281
+ type: 'array',
282
+ items: {
283
+ type: 'string',
284
+ enum: [
285
+ 'id',
286
+ 'specificationId',
287
+ 'epicNumber',
288
+ 'title',
289
+ 'description',
290
+ 'objective',
291
+ 'status',
292
+ 'priority',
293
+ 'estimatedHours',
294
+ 'acceptanceCriteria',
295
+ 'scope',
296
+ 'constraints',
297
+ 'assumptions',
298
+ 'ticketCount',
299
+ 'completedTicketCount',
300
+ 'createdAt',
301
+ 'updatedAt',
302
+ ],
303
+ },
304
+ description: 'Select specific fields to return. Returns all fields if not specified. id is always included. Example: ["id", "epicNumber", "title", "status"] for minimal response.',
305
+ },
111
306
  },
112
307
  required: ['specificationId'],
113
308
  },
114
309
  },
115
310
  {
116
311
  name: 'get_epic',
117
- description: 'Get epic with scope, goals, acceptance criteria, and ticket summary',
312
+ description: `Get epic with optional summary mode or full details.
313
+
314
+ Use summary: true for minimal response (~80 tokens) with key fields only (id, epicNumber, title, status, objective, ticketCount, completedTicketCount).
315
+
316
+ Optional includes (ignored when summary=true):
317
+ - 'status': Progress percentage, ticket counts, active tickets (replaces get_epic_status)
318
+ - 'tickets': List of tickets with summary data`,
118
319
  inputSchema: {
119
320
  type: 'object',
120
321
  properties: {
@@ -122,16 +323,51 @@ export function getTools() {
122
323
  type: 'string',
123
324
  description: 'The ID of the epic to retrieve',
124
325
  },
326
+ summary: {
327
+ type: 'boolean',
328
+ description: 'Return minimal summary fields only (~80 tokens vs ~400 full). Ignores include parameter when true. Default: false',
329
+ default: false,
330
+ },
331
+ include: {
332
+ type: 'array',
333
+ items: {
334
+ type: 'string',
335
+ enum: ['status', 'tickets'],
336
+ },
337
+ description: 'Optional data to include in response (ignored when summary=true)',
338
+ },
125
339
  },
126
340
  required: ['epicId'],
127
341
  },
128
342
  },
343
+ {
344
+ name: 'lookup_epic',
345
+ description: "Fast epic lookup by number or title. Returns minimal data (id, number, title, status, ticketCount). Use 'number' for exact match, 'title' for partial match.",
346
+ inputSchema: {
347
+ type: 'object',
348
+ properties: {
349
+ specificationId: {
350
+ type: 'string',
351
+ description: 'Specification ID to search within',
352
+ },
353
+ number: {
354
+ type: 'integer',
355
+ description: 'Epic number for exact match lookup',
356
+ },
357
+ title: {
358
+ type: 'string',
359
+ description: 'Epic title for partial match lookup',
360
+ },
361
+ },
362
+ required: ['specificationId'],
363
+ },
364
+ },
129
365
  // ========================================================================
130
366
  // Core Operations - Tickets (Epic 3)
131
367
  // ========================================================================
132
368
  {
133
369
  name: 'list_tickets',
134
- description: 'List tickets for an epic with optional status filter',
370
+ description: 'List tickets for an epic with optional status and field filters. Supports pagination with limit/offset. Use fields to select specific fields and reduce token usage. Use include: ["statusReason"] to get reasons why pending tickets are blocked.',
135
371
  inputSchema: {
136
372
  type: 'object',
137
373
  properties: {
@@ -143,9 +379,60 @@ export function getTools() {
143
379
  type: 'array',
144
380
  items: {
145
381
  type: 'string',
146
- enum: ['todo', 'queue', 'in_progress', 'blocked', 'done'],
382
+ enum: ['pending', 'ready', 'active', 'done'],
147
383
  },
148
- description: 'Filter by status (optional)',
384
+ description: 'Filter by status (optional). pending/ready are auto-calculated based on dependencies.',
385
+ },
386
+ limit: {
387
+ type: 'integer',
388
+ minimum: 1,
389
+ maximum: 100,
390
+ description: 'Maximum number of items to return (default: 20, max: 100)',
391
+ },
392
+ offset: {
393
+ type: 'integer',
394
+ minimum: 0,
395
+ description: 'Number of items to skip for offset-based pagination (default: 0)',
396
+ },
397
+ include: {
398
+ type: 'array',
399
+ items: {
400
+ type: 'string',
401
+ enum: ['statusReason'],
402
+ },
403
+ description: 'Include statusReason for pending tickets (shows unsatisfied dependencies or block reasons)',
404
+ },
405
+ fields: {
406
+ type: 'array',
407
+ items: {
408
+ type: 'string',
409
+ enum: [
410
+ 'id',
411
+ 'epicId',
412
+ 'ticketNumber',
413
+ 'title',
414
+ 'description',
415
+ 'status',
416
+ 'priority',
417
+ 'complexity',
418
+ 'estimatedHours',
419
+ 'actualHours',
420
+ 'acceptanceCriteria',
421
+ 'implementation',
422
+ 'technicalDetails',
423
+ 'notes',
424
+ 'tags',
425
+ 'blockReason',
426
+ 'progress',
427
+ 'testsPassed',
428
+ 'order',
429
+ 'startedAt',
430
+ 'completedAt',
431
+ 'createdAt',
432
+ 'updatedAt',
433
+ ],
434
+ },
435
+ description: 'Select specific fields to return. Returns all fields if not specified. id is always included. Example: ["id", "ticketNumber", "title", "status"] for minimal response (~60 tokens per ticket vs ~500 full).',
149
436
  },
150
437
  },
151
438
  required: ['epicId'],
@@ -153,7 +440,18 @@ export function getTools() {
153
440
  },
154
441
  {
155
442
  name: 'get_ticket',
156
- description: 'Get full ticket details including implementation steps, acceptance criteria, and dependencies',
443
+ description: `Get ticket with optional summary mode or full details.
444
+
445
+ Use summary: true for minimal response (~100 tokens) with key fields only (id, ticketNumber, title, status, complexity, estimatedHours, priority).
446
+
447
+ For full details, statusReason is auto-included for pending tickets explaining why the ticket is blocked.
448
+
449
+ Optional includes (ignored when summary=true):
450
+ - 'dependencies': Tickets this blocks and is blocked by (replaces get_ticket_dependencies)
451
+ - 'testStatus': Test results and history (replaces get_ticket_test_status)
452
+ - 'commits': Linked git commits (replaces get_ticket_commits)
453
+ - 'prs': Linked pull requests (replaces get_ticket_prs)
454
+ - 'statusReason': Why ticket is pending (auto-included for pending tickets)`,
157
455
  inputSchema: {
158
456
  type: 'object',
159
457
  properties: {
@@ -161,27 +459,64 @@ export function getTools() {
161
459
  type: 'string',
162
460
  description: 'The ID of the ticket to retrieve',
163
461
  },
462
+ summary: {
463
+ type: 'boolean',
464
+ description: 'Return minimal summary fields only (~100 tokens vs ~500 full). Ignores include parameter when true. Default: false',
465
+ default: false,
466
+ },
467
+ include: {
468
+ type: 'array',
469
+ items: {
470
+ type: 'string',
471
+ enum: ['dependencies', 'testStatus', 'commits', 'prs', 'statusReason'],
472
+ },
473
+ description: 'Additional data to include (ignored when summary=true). statusReason is auto-included for pending tickets.',
474
+ },
164
475
  },
165
476
  required: ['ticketId'],
166
477
  },
167
478
  },
168
- // ========================================================================
169
- // Core Operations - Dependencies (Epic 3)
170
- // ========================================================================
171
479
  {
172
- name: 'get_ticket_dependencies',
173
- description: 'Get dependency information for a ticket - what it blocks and what blocks it',
480
+ name: 'lookup_ticket',
481
+ description: "Fast ticket lookup by number or title. Returns minimal data (id, number, title, status, complexity). Use 'number' for exact match, 'title' for partial match.",
174
482
  inputSchema: {
175
483
  type: 'object',
176
484
  properties: {
177
- ticketId: {
485
+ epicId: {
178
486
  type: 'string',
179
- description: 'The ID of the ticket',
487
+ description: 'Epic ID to search within',
488
+ },
489
+ number: {
490
+ type: 'integer',
491
+ description: 'Ticket number for exact match lookup',
492
+ },
493
+ title: {
494
+ type: 'string',
495
+ description: 'Ticket title for partial match lookup',
180
496
  },
181
497
  },
182
- required: ['ticketId'],
498
+ required: ['epicId'],
499
+ },
500
+ },
501
+ {
502
+ name: 'lookup_tickets_by_status',
503
+ description: 'Batch lookup of ticket statuses. Optimized for checking if dependencies are satisfied. Returns minimal data (id, status, title) for up to 50 tickets.',
504
+ inputSchema: {
505
+ type: 'object',
506
+ properties: {
507
+ ticketIds: {
508
+ type: 'array',
509
+ items: { type: 'string' },
510
+ maxItems: 50,
511
+ description: 'Array of ticket IDs to lookup (max 50)',
512
+ },
513
+ },
514
+ required: ['ticketIds'],
183
515
  },
184
516
  },
517
+ // ========================================================================
518
+ // Core Operations - Dependencies (Epic 3)
519
+ // ========================================================================
185
520
  {
186
521
  name: 'add_dependency',
187
522
  description: 'Add a dependency between two tickets. The first ticket will depend on the second.',
@@ -245,7 +580,7 @@ export function getTools() {
245
580
  // ========================================================================
246
581
  {
247
582
  name: 'get_implementation_context',
248
- description: 'Get full implementation context for a ticket including spec, epic, dependencies, and patterns',
583
+ description: 'Get implementation context for a ticket. Use depth parameter to control response size: minimal (~300 tokens) for quick status checks, standard (~800 tokens) for typical implementation, full (~2000 tokens) for complex debugging.',
249
584
  inputSchema: {
250
585
  type: 'object',
251
586
  properties: {
@@ -253,13 +588,19 @@ export function getTools() {
253
588
  type: 'string',
254
589
  description: 'The ID of the ticket',
255
590
  },
591
+ depth: {
592
+ type: 'string',
593
+ enum: ['minimal', 'standard', 'full'],
594
+ description: 'Context depth level. minimal: ticket essentials + dependency status (~300 tokens). standard: ticket + epic summary + blockers (~800 tokens). full: complete context with patterns and history (~2000 tokens). Default: standard',
595
+ default: 'standard',
596
+ },
256
597
  },
257
598
  required: ['ticketId'],
258
599
  },
259
600
  },
260
601
  {
261
602
  name: 'get_next_actionable_tickets',
262
- description: 'Get tickets that can be worked on now (all dependencies satisfied). Can query by specification or project.',
603
+ description: 'Get tickets with status "ready" (all dependencies satisfied). Use start_work_session() to begin work on these tickets.',
263
604
  inputSchema: {
264
605
  type: 'object',
265
606
  properties: {
@@ -280,7 +621,7 @@ export function getTools() {
280
621
  },
281
622
  {
282
623
  name: 'get_blocked_tickets',
283
- description: 'Get tickets that are blocked with reasons why',
624
+ description: 'Get tickets with status "pending" and their statusReason explaining why they are blocked (unsatisfied dependencies or external block)',
284
625
  inputSchema: {
285
626
  type: 'object',
286
627
  properties: {
@@ -308,16 +649,19 @@ export function getTools() {
308
649
  },
309
650
  {
310
651
  name: 'get_patterns',
311
- description: 'Extract tech stack, code patterns, and naming conventions from specification',
652
+ description: 'Get patterns for a specification or ticket with full inheritance chain. When ticketId is provided, returns resolved patterns with spec epic → ticket inheritance.',
312
653
  inputSchema: {
313
654
  type: 'object',
314
655
  properties: {
315
656
  specificationId: {
316
657
  type: 'string',
317
- description: 'The ID of the specification',
658
+ description: 'Get patterns for entire specification (raw spec patterns only)',
659
+ },
660
+ ticketId: {
661
+ type: 'string',
662
+ description: 'Get resolved patterns for a specific ticket with inheritance chain and override analysis',
318
663
  },
319
664
  },
320
- required: ['specificationId'],
321
665
  },
322
666
  },
323
667
  // ========================================================================
@@ -325,7 +669,7 @@ export function getTools() {
325
669
  // ========================================================================
326
670
  {
327
671
  name: 'start_work_session',
328
- description: 'Start working on a ticket - validates dependencies are satisfied',
672
+ description: 'Start working on a ticket. Transitions ready -> active. Returns error with statusReason if ticket is pending (dependencies not met).',
329
673
  inputSchema: {
330
674
  type: 'object',
331
675
  properties: {
@@ -339,7 +683,15 @@ export function getTools() {
339
683
  },
340
684
  {
341
685
  name: 'complete_work_session',
342
- description: 'Mark a ticket as complete with summary and results',
686
+ description: 'Mark a ticket as complete. Transitions active -> done.\n\n' +
687
+ 'Automatically recalculates status for dependent tickets and returns cascade info.\n\n' +
688
+ 'By default, checks if summary addresses acceptance criteria and returns warnings (not errors) for gaps.\n\n' +
689
+ 'Use validated: true as a simple confirmation that the agent has verified all work meets criteria.\n\n' +
690
+ 'Optional validation flags can be provided to report test results inline:\n' +
691
+ '- tests: Unit/integration test results\n' +
692
+ '- lint: Linting results\n' +
693
+ '- typeCheck: TypeScript type checking\n' +
694
+ '- build: Build/compilation results',
343
695
  inputSchema: {
344
696
  type: 'object',
345
697
  properties: {
@@ -365,6 +717,48 @@ export function getTools() {
365
717
  type: 'number',
366
718
  description: 'Actual hours spent on the ticket',
367
719
  },
720
+ validated: {
721
+ type: 'boolean',
722
+ description: 'Simple validation confirmation. When true, indicates:\n' +
723
+ '- Agent has verified work meets acceptance criteria\n' +
724
+ '- All necessary tests/validations were run and passed\n' +
725
+ '- No detailed breakdown needed\n' +
726
+ 'Trust-based approach for streamlined completion.',
727
+ },
728
+ validation: {
729
+ type: 'object',
730
+ description: 'Detailed validation flags (optional with validated: true)',
731
+ properties: {
732
+ tests: {
733
+ type: 'string',
734
+ enum: ['passed', 'failed', 'skipped', 'na'],
735
+ description: 'Unit/integration test results',
736
+ },
737
+ lint: {
738
+ type: 'string',
739
+ enum: ['passed', 'failed', 'skipped', 'na'],
740
+ description: 'Linting results',
741
+ },
742
+ typeCheck: {
743
+ type: 'string',
744
+ enum: ['passed', 'failed', 'skipped', 'na'],
745
+ description: 'TypeScript type checking results',
746
+ },
747
+ build: {
748
+ type: 'string',
749
+ enum: ['passed', 'failed', 'skipped', 'na'],
750
+ description: 'Build/compilation results',
751
+ },
752
+ notes: {
753
+ type: 'string',
754
+ description: 'Additional validation notes',
755
+ },
756
+ },
757
+ },
758
+ skipCriteriaCheck: {
759
+ type: 'boolean',
760
+ description: 'Skip acceptance criteria validation. Use for trivial changes or when criteria check is not needed. Default: false',
761
+ },
368
762
  },
369
763
  required: ['ticketId', 'summary'],
370
764
  },
@@ -393,379 +787,174 @@ export function getTools() {
393
787
  required: ['ticketId', 'progress'],
394
788
  },
395
789
  },
396
- {
397
- name: 'get_work_summary',
398
- description: 'Get summary of completed work with optional scope filtering',
399
- inputSchema: {
400
- type: 'object',
401
- properties: {
402
- scope: {
403
- type: 'string',
404
- enum: ['epic', 'specification', 'project', 'user'],
405
- description: 'Scope of the summary',
406
- },
407
- scopeId: {
408
- type: 'string',
409
- description: 'ID of the scoped entity (epicId, specificationId, or projectId)',
410
- },
411
- startDate: {
412
- type: 'string',
413
- description: 'Start date filter (ISO 8601)',
414
- },
415
- endDate: {
416
- type: 'string',
417
- description: 'End date filter (ISO 8601)',
418
- },
419
- },
420
- required: ['scope'],
421
- },
422
- },
423
- // ========================================================================
424
- // Testing Tools (Epic 6)
425
- // ========================================================================
426
- {
427
- name: 'report_test_results',
428
- description: 'Report test results for a ticket',
429
- inputSchema: {
430
- type: 'object',
431
- properties: {
432
- ticketId: {
433
- type: 'string',
434
- description: 'The ID of the ticket',
435
- },
436
- passed: {
437
- type: 'boolean',
438
- description: 'Whether the tests passed',
439
- },
440
- testType: {
441
- type: 'string',
442
- enum: [
443
- 'unit', 'integration', 'e2e', 'manual',
444
- 'typescript', 'lint', 'build', 'typecheck',
445
- 'format', 'security', 'custom',
446
- ],
447
- description: 'Type of test',
448
- },
449
- summary: {
450
- type: 'string',
451
- description: 'Summary of test results',
452
- },
453
- output: {
454
- type: 'string',
455
- description: 'Test output (will be truncated if too long)',
456
- },
457
- command: {
458
- type: 'string',
459
- description: 'The command that was run',
460
- },
461
- exitCode: {
462
- type: 'number',
463
- description: 'Process exit code',
464
- },
465
- durationMs: {
466
- type: 'number',
467
- description: 'How long it took in milliseconds',
468
- },
469
- errorCount: {
470
- type: 'number',
471
- description: 'Number of errors (for lint, typecheck)',
472
- },
473
- warningCount: {
474
- type: 'number',
475
- description: 'Number of warnings',
476
- },
477
- },
478
- required: ['ticketId', 'passed', 'testType'],
479
- },
480
- },
481
- {
482
- name: 'get_ticket_test_status',
483
- description: 'Get test status and history for a ticket',
484
- inputSchema: {
485
- type: 'object',
486
- properties: {
487
- ticketId: {
488
- type: 'string',
489
- description: 'The ID of the ticket',
490
- },
491
- },
492
- required: ['ticketId'],
493
- },
494
- },
495
- {
496
- name: 'validate_ticket_completion',
497
- description: 'Validate that a ticket is ready to be marked complete',
498
- inputSchema: {
499
- type: 'object',
500
- properties: {
501
- ticketId: {
502
- type: 'string',
503
- description: 'The ID of the ticket',
504
- },
505
- },
506
- required: ['ticketId'],
507
- },
508
- },
509
- // ========================================================================
510
- // Discovery Tools (Epic 7)
511
- // ========================================================================
512
- {
513
- name: 'report_discovery',
514
- description: 'Report a discovery (new requirement, bug, or improvement) found during implementation',
515
- inputSchema: {
516
- type: 'object',
517
- properties: {
518
- ticketId: {
519
- type: 'string',
520
- description: 'The ID of the ticket where discovery was made',
521
- },
522
- type: {
523
- type: 'string',
524
- enum: ['bug', 'requirement', 'improvement', 'question'],
525
- description: 'Type of discovery',
526
- },
527
- title: {
528
- type: 'string',
529
- description: 'Short title for the discovery',
530
- },
531
- description: {
532
- type: 'string',
533
- description: 'Detailed description',
534
- },
535
- priority: {
536
- type: 'string',
537
- enum: ['low', 'medium', 'high', 'critical'],
538
- description: 'Priority level',
539
- },
540
- },
541
- required: ['ticketId', 'type', 'title'],
542
- },
543
- },
544
- {
545
- name: 'get_pending_discoveries',
546
- description: 'Get pending discoveries that need resolution',
547
- inputSchema: {
548
- type: 'object',
549
- properties: {
550
- specificationId: {
551
- type: 'string',
552
- description: 'Filter by specification (optional)',
553
- },
554
- projectId: {
555
- type: 'string',
556
- description: 'Filter by project (optional)',
557
- },
558
- },
559
- required: [],
560
- },
561
- },
562
- {
563
- name: 'resolve_discovery',
564
- description: 'Resolve a discovery as created (new ticket) or dismissed',
565
- inputSchema: {
566
- type: 'object',
567
- properties: {
568
- discoveryId: {
569
- type: 'string',
570
- description: 'The ID of the discovery',
571
- },
572
- resolution: {
573
- type: 'string',
574
- enum: ['created', 'dismissed'],
575
- description: 'Resolution type',
576
- },
577
- createdTicketId: {
578
- type: 'string',
579
- description: 'ID of created ticket (if resolution is "created")',
580
- },
581
- notes: {
582
- type: 'string',
583
- description: 'Resolution notes',
584
- },
585
- },
586
- required: ['discoveryId', 'resolution'],
587
- },
588
- },
589
790
  // ========================================================================
590
791
  // Status & Analytics Tools (Epic 8)
591
792
  // ========================================================================
592
793
  {
593
- name: 'get_specification_status',
594
- description: 'Get detailed status for a specification including progress, time tracking, and blockers',
595
- inputSchema: {
596
- type: 'object',
597
- properties: {
598
- specificationId: {
599
- type: 'string',
600
- description: 'The ID of the specification',
601
- },
602
- },
603
- required: ['specificationId'],
604
- },
605
- },
606
- {
607
- name: 'get_epic_status',
608
- description: 'Get detailed status for an epic including progress and actionable tickets',
609
- inputSchema: {
610
- type: 'object',
611
- properties: {
612
- epicId: {
613
- type: 'string',
614
- description: 'The ID of the epic',
615
- },
616
- },
617
- required: ['epicId'],
618
- },
619
- },
620
- {
621
- name: 'get_implementation_summary',
622
- description: 'Get cross-specification implementation summary with velocity metrics',
794
+ name: 'get_report',
795
+ description: `Generate unified reports for implementation, time tracking, blockers, or work summary.
796
+
797
+ Report types:
798
+ - 'implementation': Progress, velocity, completions (replaces get_implementation_summary)
799
+ - 'time': Estimated vs actual hours (replaces get_time_report)
800
+ - 'blockers': Blocked tickets with reasons (replaces get_blockers_report)
801
+ - 'work': Completed work summary (replaces get_work_summary)
802
+
803
+ Format options:
804
+ - 'json': Full structured data (default)
805
+ - 'summary': Condensed text summary`,
623
806
  inputSchema: {
624
807
  type: 'object',
625
808
  properties: {
626
- projectId: {
809
+ type: {
627
810
  type: 'string',
628
- description: 'Filter by project (optional)',
811
+ enum: ['implementation', 'time', 'blockers', 'work'],
812
+ description: 'Type of report to generate',
629
813
  },
630
- },
631
- required: [],
632
- },
633
- },
634
- {
635
- name: 'get_time_report',
636
- description: 'Get time tracking report with estimated vs actual analysis',
637
- inputSchema: {
638
- type: 'object',
639
- properties: {
640
814
  scope: {
641
815
  type: 'string',
642
- enum: ['epic', 'specification', 'project'],
816
+ enum: ['project', 'specification', 'epic'],
643
817
  description: 'Scope of the report',
644
818
  },
645
819
  scopeId: {
646
820
  type: 'string',
647
- description: 'ID of the scoped entity',
648
- },
649
- },
650
- required: ['scope', 'scopeId'],
651
- },
652
- },
653
- {
654
- name: 'get_blockers_report',
655
- description: 'Get report of all blockers with duration and grouping',
656
- inputSchema: {
657
- type: 'object',
658
- properties: {
659
- specificationId: {
660
- type: 'string',
661
- description: 'Filter by specification (optional)',
662
- },
663
- projectId: {
664
- type: 'string',
665
- description: 'Filter by project (optional)',
666
- },
667
- },
668
- required: [],
669
- },
670
- },
671
- // ========================================================================
672
- // Search Tools (Epic 9)
673
- // ========================================================================
674
- {
675
- name: 'search_tickets',
676
- description: 'Full-text search across tickets with relevance ranking',
677
- inputSchema: {
678
- type: 'object',
679
- properties: {
680
- query: {
681
- type: 'string',
682
- description: 'Search query',
683
- },
684
- specificationId: {
685
- type: 'string',
686
- description: 'Limit search to specification (optional)',
687
- },
688
- projectId: {
689
- type: 'string',
690
- description: 'Limit search to project (optional)',
691
- },
692
- limit: {
693
- type: 'number',
694
- description: 'Maximum results (default: 20)',
695
- },
696
- offset: {
697
- type: 'number',
698
- description: 'Pagination offset (default: 0)',
821
+ description: 'ID of the scoped entity (projectId, specificationId, or epicId)',
699
822
  },
700
- },
701
- required: ['query'],
702
- },
703
- },
704
- {
705
- name: 'find_tickets_by_file',
706
- description: 'Find tickets that modify or create specific files (supports glob patterns)',
707
- inputSchema: {
708
- type: 'object',
709
- properties: {
710
- filePath: {
823
+ format: {
711
824
  type: 'string',
712
- description: 'File path or glob pattern (e.g., "src/components/*.tsx")',
825
+ enum: ['json', 'toon', 'summary'],
826
+ description: 'Output format: json (full structured), toon (~44% smaller), summary (key metrics only ~70% smaller). Default: json',
713
827
  },
714
- specificationId: {
828
+ startDate: {
715
829
  type: 'string',
716
- description: 'Limit search to specification (optional)',
830
+ description: 'Start date for work report (ISO 8601)',
717
831
  },
718
- projectId: {
832
+ endDate: {
719
833
  type: 'string',
720
- description: 'Limit search to project (optional)',
834
+ description: 'End date for work report (ISO 8601)',
721
835
  },
722
836
  },
723
- required: ['filePath'],
837
+ required: ['type', 'scope', 'scopeId'],
724
838
  },
725
839
  },
840
+ // ========================================================================
841
+ // Search Tools (Epic 9)
842
+ // ========================================================================
726
843
  {
727
- name: 'find_tickets_by_tag',
728
- description: 'Find tickets by tags with AND/OR logic',
844
+ name: 'search_tickets',
845
+ description: `Unified ticket search with multiple filter options.
846
+
847
+ Combines:
848
+ - Full-text search (query)
849
+ - File matching (files) - replaces find_tickets_by_file
850
+ - Tag filtering (tags) - replaces find_tickets_by_tag
851
+ - Related tickets (relatedTo) - replaces find_related_tickets
852
+ - Status, complexity, priority filters
853
+
854
+ At least one of: query, files, tags, or relatedTo is required.
855
+ At least one scope filter (projectId, specificationId, or epicId) is required.`,
729
856
  inputSchema: {
730
857
  type: 'object',
731
858
  properties: {
859
+ query: {
860
+ type: 'string',
861
+ description: 'Full-text search query',
862
+ },
863
+ files: {
864
+ type: 'array',
865
+ items: { type: 'string' },
866
+ description: 'Glob patterns to match ticket files (e.g., "**/channel/*.ts")',
867
+ },
732
868
  tags: {
733
869
  type: 'array',
734
870
  items: { type: 'string' },
735
- description: 'Tags to search for',
871
+ description: 'Tags to filter by',
736
872
  },
737
- matchAll: {
873
+ matchAllTags: {
738
874
  type: 'boolean',
739
875
  description: 'If true, match all tags (AND). If false, match any (OR). Default: false',
740
876
  },
741
- specificationId: {
877
+ relatedTo: {
742
878
  type: 'string',
743
- description: 'Limit search to specification (optional)',
879
+ description: 'Find tickets related to this ticket ID (by tags, files, tech stack)',
880
+ },
881
+ status: {
882
+ type: 'array',
883
+ items: {
884
+ type: 'string',
885
+ enum: ['pending', 'ready', 'active', 'done'],
886
+ },
887
+ description: 'Filter by status',
888
+ },
889
+ complexity: {
890
+ type: 'array',
891
+ items: {
892
+ type: 'string',
893
+ enum: ['small', 'medium', 'large', 'xlarge'],
894
+ },
895
+ description: 'Filter by complexity',
896
+ },
897
+ priority: {
898
+ type: 'array',
899
+ items: {
900
+ type: 'string',
901
+ enum: ['low', 'medium', 'high', 'critical'],
902
+ },
903
+ description: 'Filter by priority',
744
904
  },
745
905
  projectId: {
746
906
  type: 'string',
747
- description: 'Limit search to project (optional)',
907
+ description: 'Limit search to project',
748
908
  },
749
- },
750
- required: ['tags'],
751
- },
752
- },
753
- {
754
- name: 'find_related_tickets',
755
- description: 'Find tickets related to a given ticket based on files, tags, and tech stack',
756
- inputSchema: {
757
- type: 'object',
758
- properties: {
759
- ticketId: {
909
+ specificationId: {
760
910
  type: 'string',
761
- description: 'The ID of the ticket',
911
+ description: 'Limit search to specification',
912
+ },
913
+ epicId: {
914
+ type: 'string',
915
+ description: 'Limit search to epic',
762
916
  },
763
917
  limit: {
764
918
  type: 'number',
765
- description: 'Maximum results (default: 10)',
919
+ description: 'Maximum results (default: 20, max: 100)',
920
+ },
921
+ offset: {
922
+ type: 'number',
923
+ description: 'Pagination offset (default: 0)',
924
+ },
925
+ fields: {
926
+ type: 'array',
927
+ items: {
928
+ type: 'string',
929
+ enum: [
930
+ 'id',
931
+ 'epicId',
932
+ 'ticketNumber',
933
+ 'title',
934
+ 'description',
935
+ 'status',
936
+ 'priority',
937
+ 'complexity',
938
+ 'estimatedHours',
939
+ 'actualHours',
940
+ 'acceptanceCriteria',
941
+ 'implementation',
942
+ 'technicalDetails',
943
+ 'notes',
944
+ 'tags',
945
+ 'blockReason',
946
+ 'progress',
947
+ 'testsPassed',
948
+ 'order',
949
+ 'startedAt',
950
+ 'completedAt',
951
+ 'createdAt',
952
+ 'updatedAt',
953
+ ],
954
+ },
955
+ description: 'Select specific fields to return. Returns all fields if not specified. id is always included. Example: ["id", "ticketNumber", "title", "status"] for minimal results.',
766
956
  },
767
957
  },
768
- required: ['ticketId'],
769
958
  },
770
959
  },
771
960
  // ========================================================================
@@ -835,50 +1024,6 @@ export function getTools() {
835
1024
  required: ['ticketId'],
836
1025
  },
837
1026
  },
838
- {
839
- name: 'get_ticket_commits',
840
- description: 'Get all commits linked to a ticket',
841
- inputSchema: {
842
- type: 'object',
843
- properties: {
844
- ticketId: {
845
- type: 'string',
846
- description: 'The ID of the ticket',
847
- },
848
- limit: {
849
- type: 'number',
850
- description: 'Maximum results (default: 20)',
851
- },
852
- offset: {
853
- type: 'number',
854
- description: 'Pagination offset (default: 0)',
855
- },
856
- },
857
- required: ['ticketId'],
858
- },
859
- },
860
- {
861
- name: 'get_ticket_prs',
862
- description: 'Get all pull requests linked to a ticket',
863
- inputSchema: {
864
- type: 'object',
865
- properties: {
866
- ticketId: {
867
- type: 'string',
868
- description: 'The ID of the ticket',
869
- },
870
- limit: {
871
- type: 'number',
872
- description: 'Maximum results (default: 20)',
873
- },
874
- offset: {
875
- type: 'number',
876
- description: 'Pagination offset (default: 0)',
877
- },
878
- },
879
- required: ['ticketId'],
880
- },
881
- },
882
1027
  // ========================================================================
883
1028
  // Creation Operations
884
1029
  // ========================================================================
@@ -1027,6 +1172,48 @@ export function getTools() {
1027
1172
  required: ['title', 'description', 'objective'],
1028
1173
  },
1029
1174
  },
1175
+ // Pattern inheritance fields (specification level)
1176
+ codeStandards: {
1177
+ type: 'object',
1178
+ description: 'Code standards inherited by all epics/tickets',
1179
+ properties: {
1180
+ language: { type: 'string', description: 'Primary language (e.g., "TypeScript")' },
1181
+ asyncPattern: { type: 'string', description: 'Async pattern (e.g., "async/await")' },
1182
+ errorHandling: { type: 'string', description: 'Error handling approach' },
1183
+ documentation: { type: 'string', description: 'Documentation style' },
1184
+ testing: { type: 'string', description: 'Testing approach' },
1185
+ naming: {
1186
+ type: 'object',
1187
+ description: 'Naming conventions',
1188
+ properties: {
1189
+ files: { type: 'string', description: 'File naming convention' },
1190
+ functions: { type: 'string', description: 'Function naming convention' },
1191
+ interfaces: { type: 'string', description: 'Interface naming convention' },
1192
+ actions: { type: 'string', description: 'Action naming convention' },
1193
+ },
1194
+ },
1195
+ },
1196
+ },
1197
+ commonImports: {
1198
+ type: 'array',
1199
+ items: { type: 'string' },
1200
+ description: 'Common imports inherited by all epics/tickets',
1201
+ },
1202
+ returnTypes: {
1203
+ type: 'object',
1204
+ description: 'Standard return types for CRUD operations',
1205
+ properties: {
1206
+ create: { type: 'string', description: 'Return type for create operations' },
1207
+ update: { type: 'string', description: 'Return type for update operations' },
1208
+ delete: { type: 'string', description: 'Return type for delete operations' },
1209
+ list: { type: 'string', description: 'Return type for list operations' },
1210
+ },
1211
+ },
1212
+ responseMode: {
1213
+ type: 'string',
1214
+ enum: ['full', 'minimal', 'id-only'],
1215
+ description: 'Response verbosity: full (default, ~500 tokens), minimal (~50 tokens), id-only (~20 tokens)',
1216
+ },
1030
1217
  },
1031
1218
  required: ['projectId', 'title'],
1032
1219
  },
@@ -1133,7 +1320,7 @@ export function getTools() {
1133
1320
  },
1134
1321
  tickets: {
1135
1322
  type: 'array',
1136
- description: 'Nested tickets to create (bulk creation)',
1323
+ description: 'Nested tickets to create (bulk creation) with optional inline dependencies',
1137
1324
  items: {
1138
1325
  type: 'object',
1139
1326
  properties: {
@@ -1142,17 +1329,57 @@ export function getTools() {
1142
1329
  acceptanceCriteria: { type: 'array', items: { type: 'string' } },
1143
1330
  estimatedHours: { type: 'number' },
1144
1331
  complexity: { type: 'string', enum: ['small', 'medium', 'large', 'xlarge'] },
1332
+ priority: { type: 'string', enum: ['high', 'medium', 'low'] },
1333
+ implementation: { type: 'object', description: 'Implementation details' },
1334
+ technicalDetails: { type: 'object', description: 'Technical details' },
1335
+ tags: { type: 'array', items: { type: 'string' } },
1336
+ dependsOn: {
1337
+ type: 'array',
1338
+ items: { type: 'number' },
1339
+ description: 'Indexes of other tickets in this array that this ticket depends on',
1340
+ },
1145
1341
  },
1146
1342
  required: ['title'],
1147
1343
  },
1148
1344
  },
1345
+ // Pattern inheritance fields (epic level)
1346
+ sharedPatterns: {
1347
+ type: 'object',
1348
+ description: 'Patterns shared by all tickets in this epic',
1349
+ properties: {
1350
+ errorHandling: { type: 'string', description: 'Override error handling for this epic' },
1351
+ returnType: { type: 'string', description: 'Epic-specific return type' },
1352
+ actionPrefix: { type: 'string', description: 'Redux/action prefix for this epic' },
1353
+ stateSlice: { type: 'string', description: 'State slice path for this epic' },
1354
+ },
1355
+ },
1356
+ additionalImports: {
1357
+ type: 'array',
1358
+ items: { type: 'string' },
1359
+ description: 'Additional imports for tickets in this epic (added to spec-level imports)',
1360
+ },
1361
+ commonFiles: {
1362
+ type: 'object',
1363
+ description: 'Common file paths for this epic',
1364
+ properties: {
1365
+ reducer: { type: 'string', description: 'Reducer file path' },
1366
+ actions: { type: 'string', description: 'Actions file path' },
1367
+ types: { type: 'string', description: 'Types file path' },
1368
+ index: { type: 'string', description: 'Index/barrel file path' },
1369
+ },
1370
+ },
1371
+ responseMode: {
1372
+ type: 'string',
1373
+ enum: ['full', 'minimal', 'id-only'],
1374
+ description: 'Response verbosity: full (default, ~500 tokens), minimal (~50 tokens), id-only (~20 tokens)',
1375
+ },
1149
1376
  },
1150
1377
  required: ['specificationId', 'title', 'description', 'objective'],
1151
1378
  },
1152
1379
  },
1153
1380
  {
1154
1381
  name: 'create_ticket',
1155
- description: 'Create a new ticket in an epic',
1382
+ description: 'Create a new ticket in an epic. Returns validation warnings based on complexity level.',
1156
1383
  inputSchema: {
1157
1384
  type: 'object',
1158
1385
  properties: {
@@ -1213,6 +1440,20 @@ export function getTools() {
1213
1440
  items: { type: 'string' },
1214
1441
  description: 'Array of ticket IDs this ticket depends on',
1215
1442
  },
1443
+ skipValidation: {
1444
+ type: 'boolean',
1445
+ description: 'Skip validation and completeness scoring. Default: false',
1446
+ },
1447
+ validationStrictness: {
1448
+ type: 'string',
1449
+ enum: ['lenient', 'normal', 'strict'],
1450
+ description: 'Warning strictness level. lenient=warnings only, normal=warnings+recommendations, strict=all. Default: normal',
1451
+ },
1452
+ responseMode: {
1453
+ type: 'string',
1454
+ enum: ['full', 'minimal', 'id-only'],
1455
+ description: 'Response verbosity: full (default, ~500 tokens), minimal (~50 tokens), id-only (~20 tokens)',
1456
+ },
1216
1457
  },
1217
1458
  required: ['epicId', 'title'],
1218
1459
  },
@@ -1248,7 +1489,9 @@ export function getTools() {
1248
1489
  // ========================================================================
1249
1490
  {
1250
1491
  name: 'update_ticket',
1251
- description: "Update a ticket's properties (status, description, estimate, etc.)",
1492
+ description: "Update a ticket's properties (status, description, estimate, etc.).\n\n" +
1493
+ 'When status changes, counts are automatically propagated up the hierarchy ' +
1494
+ '(Epic → Specification → Project).',
1252
1495
  inputSchema: {
1253
1496
  type: 'object',
1254
1497
  properties: {
@@ -1266,8 +1509,8 @@ export function getTools() {
1266
1509
  },
1267
1510
  status: {
1268
1511
  type: 'string',
1269
- enum: ['todo', 'queue', 'in_progress', 'blocked', 'done'],
1270
- description: 'New status (optional)',
1512
+ enum: ['pending', 'ready', 'active', 'done'],
1513
+ description: 'Ticket status. pending/ready may be recalculated based on dependencies.',
1271
1514
  },
1272
1515
  priority: {
1273
1516
  type: 'string',
@@ -1281,7 +1524,7 @@ export function getTools() {
1281
1524
  },
1282
1525
  blockReason: {
1283
1526
  type: 'string',
1284
- description: 'Reason why ticket is blocked (optional, typically used when status is blocked)',
1527
+ description: 'External block reason. Forces status to pending until cleared.',
1285
1528
  },
1286
1529
  notes: {
1287
1530
  type: 'string',
@@ -1309,6 +1552,22 @@ export function getTools() {
1309
1552
  type: 'object',
1310
1553
  description: 'Technical details like stack, endpoints (JSON) (optional)',
1311
1554
  },
1555
+ skipValidation: {
1556
+ type: 'boolean',
1557
+ description: 'Skip validation and completeness scoring (default: false)',
1558
+ default: false,
1559
+ },
1560
+ validationStrictness: {
1561
+ type: 'string',
1562
+ enum: ['lenient', 'normal', 'strict'],
1563
+ description: 'Warning strictness level (default: normal)',
1564
+ default: 'normal',
1565
+ },
1566
+ responseMode: {
1567
+ type: 'string',
1568
+ enum: ['full', 'minimal', 'id-only'],
1569
+ description: 'Response verbosity: full (default, ~500 tokens), minimal (~50 tokens), id-only (~20 tokens)',
1570
+ },
1312
1571
  },
1313
1572
  required: ['ticketId'],
1314
1573
  },
@@ -1410,6 +1669,37 @@ export function getTools() {
1410
1669
  enum: ['low', 'medium', 'high', 'critical'],
1411
1670
  description: 'Priority level (optional)',
1412
1671
  },
1672
+ // Pattern inheritance fields (epic level)
1673
+ sharedPatterns: {
1674
+ type: 'object',
1675
+ description: 'Patterns shared by all tickets in this epic (optional)',
1676
+ properties: {
1677
+ errorHandling: { type: 'string', description: 'Override error handling for this epic' },
1678
+ returnType: { type: 'string', description: 'Epic-specific return type' },
1679
+ actionPrefix: { type: 'string', description: 'Redux/action prefix for this epic' },
1680
+ stateSlice: { type: 'string', description: 'State slice path for this epic' },
1681
+ },
1682
+ },
1683
+ additionalImports: {
1684
+ type: 'array',
1685
+ items: { type: 'string' },
1686
+ description: 'Additional imports for tickets in this epic (optional)',
1687
+ },
1688
+ commonFiles: {
1689
+ type: 'object',
1690
+ description: 'Common file paths for this epic (optional)',
1691
+ properties: {
1692
+ reducer: { type: 'string', description: 'Reducer file path' },
1693
+ actions: { type: 'string', description: 'Actions file path' },
1694
+ types: { type: 'string', description: 'Types file path' },
1695
+ index: { type: 'string', description: 'Index/barrel file path' },
1696
+ },
1697
+ },
1698
+ responseMode: {
1699
+ type: 'string',
1700
+ enum: ['full', 'minimal', 'id-only'],
1701
+ description: 'Response verbosity: full (default, ~500 tokens), minimal (~50 tokens), id-only (~20 tokens)',
1702
+ },
1413
1703
  },
1414
1704
  required: ['epicId'],
1415
1705
  },
@@ -1555,6 +1845,48 @@ export function getTools() {
1555
1845
  type: 'string',
1556
1846
  description: 'Output directory for build artifacts (optional)',
1557
1847
  },
1848
+ // Pattern inheritance fields (specification level)
1849
+ codeStandards: {
1850
+ type: 'object',
1851
+ description: 'Code standards inherited by all epics/tickets (optional)',
1852
+ properties: {
1853
+ language: { type: 'string', description: 'Primary language (e.g., "TypeScript")' },
1854
+ asyncPattern: { type: 'string', description: 'Async pattern (e.g., "async/await")' },
1855
+ errorHandling: { type: 'string', description: 'Error handling approach' },
1856
+ documentation: { type: 'string', description: 'Documentation style' },
1857
+ testing: { type: 'string', description: 'Testing approach' },
1858
+ naming: {
1859
+ type: 'object',
1860
+ description: 'Naming conventions',
1861
+ properties: {
1862
+ files: { type: 'string', description: 'File naming convention' },
1863
+ functions: { type: 'string', description: 'Function naming convention' },
1864
+ interfaces: { type: 'string', description: 'Interface naming convention' },
1865
+ actions: { type: 'string', description: 'Action naming convention' },
1866
+ },
1867
+ },
1868
+ },
1869
+ },
1870
+ commonImports: {
1871
+ type: 'array',
1872
+ items: { type: 'string' },
1873
+ description: 'Common imports inherited by all epics/tickets (optional)',
1874
+ },
1875
+ returnTypes: {
1876
+ type: 'object',
1877
+ description: 'Standard return types for CRUD operations (optional)',
1878
+ properties: {
1879
+ create: { type: 'string', description: 'Return type for create operations' },
1880
+ update: { type: 'string', description: 'Return type for update operations' },
1881
+ delete: { type: 'string', description: 'Return type for delete operations' },
1882
+ list: { type: 'string', description: 'Return type for list operations' },
1883
+ },
1884
+ },
1885
+ responseMode: {
1886
+ type: 'string',
1887
+ enum: ['full', 'minimal', 'id-only'],
1888
+ description: 'Response verbosity: full (default, ~500 tokens), minimal (~50 tokens), id-only (~20 tokens)',
1889
+ },
1558
1890
  },
1559
1891
  required: ['specificationId'],
1560
1892
  },
@@ -1647,153 +1979,172 @@ export function getTools() {
1647
1979
  },
1648
1980
  },
1649
1981
  {
1650
- name: 'get_session',
1651
- description: 'Get session by ID or get active session for specification',
1652
- inputSchema: {
1653
- type: 'object',
1654
- properties: {
1655
- sessionId: {
1656
- type: 'string',
1657
- description: 'Get specific session by ID',
1658
- },
1659
- specificationId: {
1660
- type: 'string',
1661
- description: 'Get active session for specification',
1662
- },
1663
- includeHistory: {
1664
- type: 'boolean',
1665
- description: 'Include completed sessions when querying by specification',
1666
- },
1667
- },
1668
- required: [],
1669
- },
1670
- },
1671
- {
1672
- name: 'update_session',
1673
- description: 'Update session state (pause/resume, update config, add notes)',
1982
+ name: 'end_session',
1983
+ description: 'End an implementation session. Returns final stats.',
1674
1984
  inputSchema: {
1675
1985
  type: 'object',
1676
1986
  properties: {
1677
1987
  sessionId: {
1678
1988
  type: 'string',
1679
- description: 'The ID of the session to update',
1989
+ description: 'The ID of the session to end',
1680
1990
  },
1681
1991
  status: {
1682
1992
  type: 'string',
1683
- enum: ['paused', 'active'],
1684
- description: 'New status (only paused/active transitions allowed)',
1685
- },
1686
- config: {
1687
- type: 'object',
1688
- description: 'Config updates to merge with existing',
1689
- },
1690
- notes: {
1691
- type: 'string',
1692
- description: 'Session notes',
1993
+ enum: ['completed', 'aborted'],
1994
+ description: 'How the session ended',
1693
1995
  },
1694
- currentTicketId: {
1996
+ summary: {
1695
1997
  type: 'string',
1696
- description: 'Update current ticket being worked on',
1998
+ description: 'End-of-session summary',
1697
1999
  },
1698
2000
  },
1699
- required: ['sessionId'],
2001
+ required: ['sessionId', 'status'],
1700
2002
  },
1701
2003
  },
2004
+ // ========================================================================
2005
+ // Bulk Operations
2006
+ // ========================================================================
1702
2007
  {
1703
- name: 'record_session_ticket',
1704
- description: 'Record a ticket completion/failure within a session. Returns next actionable ticket if available.',
2008
+ name: 'bulk_create_tickets',
2009
+ description: 'Create multiple tickets in an epic with optional inline dependencies in a single atomic transaction. Reduces 15+ API calls to one.',
1705
2010
  inputSchema: {
1706
2011
  type: 'object',
1707
2012
  properties: {
1708
- sessionId: {
1709
- type: 'string',
1710
- description: 'The ID of the session',
1711
- },
1712
- ticketId: {
1713
- type: 'string',
1714
- description: 'The ID of the ticket',
1715
- },
1716
- result: {
2013
+ epicId: {
1717
2014
  type: 'string',
1718
- enum: ['completed', 'failed', 'skipped'],
1719
- description: 'Result of the ticket work',
2015
+ description: 'The ID of the epic to create tickets in',
1720
2016
  },
1721
- durationMinutes: {
1722
- type: 'number',
1723
- description: 'Active work time in minutes',
2017
+ tickets: {
2018
+ type: 'array',
2019
+ description: 'Array of ticket definitions to create',
2020
+ items: {
2021
+ type: 'object',
2022
+ properties: {
2023
+ title: { type: 'string', description: 'Ticket title (required)' },
2024
+ description: { type: 'string', description: 'Ticket description' },
2025
+ estimatedHours: { type: 'number', description: 'Estimated hours' },
2026
+ complexity: {
2027
+ type: 'string',
2028
+ enum: ['small', 'medium', 'large', 'xlarge'],
2029
+ description: 'Complexity level',
2030
+ },
2031
+ priority: {
2032
+ type: 'string',
2033
+ enum: ['high', 'medium', 'low'],
2034
+ description: 'Priority level',
2035
+ },
2036
+ acceptanceCriteria: {
2037
+ type: 'array',
2038
+ items: { type: 'string' },
2039
+ description: 'Acceptance criteria',
2040
+ },
2041
+ implementation: {
2042
+ type: 'object',
2043
+ description: 'Implementation details (filesToCreate, filesToModify, steps, etc.)',
2044
+ },
2045
+ technicalDetails: {
2046
+ type: 'object',
2047
+ description: 'Technical details (files, stack, endpoints, etc.)',
2048
+ },
2049
+ tags: {
2050
+ type: 'array',
2051
+ items: { type: 'string' },
2052
+ description: 'Tags',
2053
+ },
2054
+ },
2055
+ required: ['title'],
2056
+ },
1724
2057
  },
1725
- errorMessage: {
1726
- type: 'string',
1727
- description: 'Error message if failed',
2058
+ dependencies: {
2059
+ type: 'array',
2060
+ description: 'Optional inline dependencies using array indexes',
2061
+ items: {
2062
+ type: 'object',
2063
+ properties: {
2064
+ ticket: {
2065
+ type: 'number',
2066
+ description: 'Index of the ticket in the tickets array',
2067
+ },
2068
+ dependsOn: {
2069
+ oneOf: [
2070
+ { type: 'number', description: 'Index of the ticket it depends on' },
2071
+ {
2072
+ type: 'array',
2073
+ items: { type: 'number' },
2074
+ description: 'Indexes of the tickets it depends on',
2075
+ },
2076
+ ],
2077
+ description: 'Index(es) of the ticket(s) it depends on',
2078
+ },
2079
+ },
2080
+ required: ['ticket', 'dependsOn'],
2081
+ },
1728
2082
  },
1729
- skipReason: {
2083
+ onError: {
1730
2084
  type: 'string',
1731
- description: 'Reason for skipping',
2085
+ enum: ['rollback', 'continue'],
2086
+ description: "Error handling mode: 'rollback' stops and undoes all, 'continue' skips failures (default: 'continue')",
1732
2087
  },
1733
2088
  },
1734
- required: ['sessionId', 'ticketId', 'result'],
2089
+ required: ['epicId', 'tickets'],
1735
2090
  },
1736
2091
  },
1737
2092
  {
1738
- name: 'end_session',
1739
- description: 'End an implementation session. Returns final stats.',
2093
+ name: 'get_job_status',
2094
+ description: 'Get the status of an asynchronous bulk operation job. For batches >= 20 items, bulk operations return a job ID for async processing.',
1740
2095
  inputSchema: {
1741
2096
  type: 'object',
1742
2097
  properties: {
1743
- sessionId: {
1744
- type: 'string',
1745
- description: 'The ID of the session to end',
1746
- },
1747
- status: {
1748
- type: 'string',
1749
- enum: ['completed', 'aborted'],
1750
- description: 'How the session ended',
1751
- },
1752
- summary: {
2098
+ jobId: {
1753
2099
  type: 'string',
1754
- description: 'End-of-session summary',
2100
+ description: 'The job ID returned from a bulk operation',
1755
2101
  },
1756
2102
  },
1757
- required: ['sessionId', 'status'],
2103
+ required: ['jobId'],
1758
2104
  },
1759
2105
  },
1760
2106
  {
1761
- name: 'list_sessions',
1762
- description: 'List sessions with filtering by specification, project, or status',
2107
+ name: 'bulk_add_dependencies',
2108
+ description: 'Add multiple dependencies between tickets in a single atomic transaction.',
1763
2109
  inputSchema: {
1764
2110
  type: 'object',
1765
2111
  properties: {
1766
- specificationId: {
1767
- type: 'string',
1768
- description: 'Filter by specification',
1769
- },
1770
- projectId: {
1771
- type: 'string',
1772
- description: 'Filter by project',
1773
- },
1774
- status: {
2112
+ dependencies: {
1775
2113
  type: 'array',
2114
+ description: 'Array of dependency definitions',
1776
2115
  items: {
1777
- type: 'string',
1778
- enum: ['active', 'paused', 'completed', 'aborted'],
2116
+ type: 'object',
2117
+ properties: {
2118
+ ticketId: {
2119
+ type: 'string',
2120
+ description: 'ID of the ticket that will depend on another',
2121
+ },
2122
+ dependsOnId: {
2123
+ type: 'string',
2124
+ description: 'ID of the ticket it will depend on',
2125
+ },
2126
+ type: {
2127
+ type: 'string',
2128
+ enum: ['blocks', 'requires'],
2129
+ description: "Type of dependency (default: 'requires')",
2130
+ },
2131
+ },
2132
+ required: ['ticketId', 'dependsOnId'],
1779
2133
  },
1780
- description: 'Filter by status',
1781
2134
  },
1782
- limit: {
1783
- type: 'number',
1784
- description: 'Maximum results (default: 20)',
2135
+ skipCircularCheck: {
2136
+ type: 'boolean',
2137
+ description: 'Skip validation of circular dependencies (default: false)',
1785
2138
  },
1786
- offset: {
1787
- type: 'number',
1788
- description: 'Pagination offset (default: 0)',
2139
+ onError: {
2140
+ type: 'string',
2141
+ enum: ['rollback', 'continue'],
2142
+ description: "Error handling mode: 'rollback' stops and undoes all, 'continue' skips failures (default: 'continue')",
1789
2143
  },
1790
2144
  },
1791
- required: [],
2145
+ required: ['dependencies'],
1792
2146
  },
1793
2147
  },
1794
- // ========================================================================
1795
- // Bulk Operations
1796
- // ========================================================================
1797
2148
  {
1798
2149
  name: 'bulk_update_tickets',
1799
2150
  description: 'Update multiple tickets in a single atomic operation. All updates succeed or all fail (unless atomic: false).',
@@ -1811,8 +2162,8 @@ export function getTools() {
1811
2162
  },
1812
2163
  status: {
1813
2164
  type: 'string',
1814
- enum: ['todo', 'queue', 'in_progress', 'blocked', 'done'],
1815
- description: 'New status',
2165
+ enum: ['pending', 'ready', 'active', 'done'],
2166
+ description: 'Ticket status. Completing (done) triggers cascade to unblock dependents.',
1816
2167
  },
1817
2168
  priority: {
1818
2169
  type: 'string',
@@ -1827,7 +2178,7 @@ export function getTools() {
1827
2178
  },
1828
2179
  blockReason: {
1829
2180
  type: 'string',
1830
- description: 'Reason for blocking (when status is blocked)',
2181
+ description: 'External block reason. Forces status to pending until cleared.',
1831
2182
  },
1832
2183
  },
1833
2184
  required: ['ticketId'],
@@ -1842,13 +2193,21 @@ export function getTools() {
1842
2193
  type: 'boolean',
1843
2194
  description: 'All-or-nothing updates. If false, partial success allowed (default: true)',
1844
2195
  },
2196
+ responseMode: {
2197
+ type: 'string',
2198
+ enum: ['full', 'minimal', 'id-only'],
2199
+ description: `Response verbosity:
2200
+ - full: Complete updated tickets (default, ~500 tokens per ticket)
2201
+ - minimal: id, title, status per ticket (~50 tokens total)
2202
+ - id-only: Array of updated ticket IDs only (~20 tokens total)`,
2203
+ },
1845
2204
  },
1846
2205
  required: ['updates'],
1847
2206
  },
1848
2207
  },
1849
2208
  {
1850
2209
  name: 'reset_tickets',
1851
- description: 'Reset tickets to todo/queue status. By default excludes completed tickets - use includeCompleted: true to also reset done tickets.',
2210
+ description: 'Reset tickets to pending/ready status (calculated from dependencies). Returns statusCalculation showing how many became pending vs ready.',
1852
2211
  inputSchema: {
1853
2212
  type: 'object',
1854
2213
  properties: {
@@ -1873,11 +2232,6 @@ export function getTools() {
1873
2232
  type: 'boolean',
1874
2233
  description: 'Reset all tickets in the specification',
1875
2234
  },
1876
- targetStatus: {
1877
- type: 'string',
1878
- enum: ['todo', 'queue'],
1879
- description: 'Status to reset tickets to (default: todo)',
1880
- },
1881
2235
  resetDependents: {
1882
2236
  type: 'boolean',
1883
2237
  description: 'Also reset tickets that depend on the specified tickets (default: false)',
@@ -1898,7 +2252,47 @@ export function getTools() {
1898
2252
  required: ['specificationId'],
1899
2253
  },
1900
2254
  },
2255
+ // ========================================================================
2256
+ // Validation Operations (MCI-075)
2257
+ // ========================================================================
2258
+ {
2259
+ name: 'validate_counts',
2260
+ description: 'Validate denormalized counts across the hierarchy. ' +
2261
+ 'Compares stored counts with actual data and optionally repairs discrepancies.\n\n' +
2262
+ 'Use this tool to:\n' +
2263
+ '- Diagnose count inconsistencies\n' +
2264
+ '- Repair counts after data migrations\n' +
2265
+ '- Verify counts before bulk operations',
2266
+ inputSchema: {
2267
+ type: 'object',
2268
+ properties: {
2269
+ projectId: {
2270
+ type: 'string',
2271
+ description: 'Validate all counts within a project',
2272
+ },
2273
+ specificationId: {
2274
+ type: 'string',
2275
+ description: 'Validate all counts within a specification',
2276
+ },
2277
+ epicId: {
2278
+ type: 'string',
2279
+ description: 'Validate counts for a single epic',
2280
+ },
2281
+ repair: {
2282
+ type: 'boolean',
2283
+ description: 'If true, fix any issues found. Default: false (dry run)',
2284
+ },
2285
+ verbose: {
2286
+ type: 'boolean',
2287
+ description: 'Include detailed breakdown in response',
2288
+ },
2289
+ },
2290
+ required: [],
2291
+ },
2292
+ },
1901
2293
  ];
2294
+ // Apply format parameter to all read operations
2295
+ return tools.map(tool => READ_TOOL_NAMES.has(tool.name) ? addFormatParameter(tool) : tool);
1902
2296
  }
1903
2297
  /**
1904
2298
  * Retry configuration for transient failures
@@ -1986,6 +2380,12 @@ export function createToolHandlers(apiClient) {
1986
2380
  projectId: args.projectId,
1987
2381
  });
1988
2382
  },
2383
+ lookup_project: async (_client, args) => {
2384
+ validateRequired(args, 'name');
2385
+ return await apiClient.call('lookup_project', {
2386
+ name: args.name,
2387
+ });
2388
+ },
1989
2389
  // ========================================================================
1990
2390
  // Core Operations - Specifications
1991
2391
  // ========================================================================
@@ -1994,12 +2394,24 @@ export function createToolHandlers(apiClient) {
1994
2394
  return await apiClient.call('list_specifications', {
1995
2395
  projectId: args.projectId,
1996
2396
  status: args.status,
2397
+ limit: args.limit,
2398
+ offset: args.offset,
2399
+ fields: args.fields,
1997
2400
  });
1998
2401
  },
1999
2402
  get_specification: async (_client, args) => {
2000
2403
  validateRequired(args, 'specificationId');
2001
2404
  return await apiClient.call('get_specification', {
2002
2405
  specificationId: args.specificationId,
2406
+ summary: args.summary,
2407
+ include: args.include,
2408
+ });
2409
+ },
2410
+ lookup_specification: async (_client, args) => {
2411
+ validateRequired(args, 'projectId', 'title');
2412
+ return await apiClient.call('lookup_specification', {
2413
+ projectId: args.projectId,
2414
+ title: args.title,
2003
2415
  });
2004
2416
  },
2005
2417
  // ========================================================================
@@ -2010,12 +2422,29 @@ export function createToolHandlers(apiClient) {
2010
2422
  return await apiClient.call('list_epics', {
2011
2423
  specificationId: args.specificationId,
2012
2424
  status: args.status,
2425
+ limit: args.limit,
2426
+ offset: args.offset,
2427
+ fields: args.fields,
2013
2428
  });
2014
2429
  },
2015
2430
  get_epic: async (_client, args) => {
2016
2431
  validateRequired(args, 'epicId');
2017
2432
  return await apiClient.call('get_epic', {
2018
2433
  epicId: args.epicId,
2434
+ summary: args.summary,
2435
+ include: args.include,
2436
+ });
2437
+ },
2438
+ lookup_epic: async (_client, args) => {
2439
+ validateRequired(args, 'specificationId');
2440
+ // Must provide either title or number
2441
+ if (!args.title && args.number === undefined) {
2442
+ throw new Error("Must provide either 'title' or 'number'");
2443
+ }
2444
+ return await apiClient.call('lookup_epic', {
2445
+ specificationId: args.specificationId,
2446
+ title: args.title,
2447
+ number: args.number,
2019
2448
  });
2020
2449
  },
2021
2450
  // ========================================================================
@@ -2026,23 +2455,58 @@ export function createToolHandlers(apiClient) {
2026
2455
  return await apiClient.call('list_tickets', {
2027
2456
  epicId: args.epicId,
2028
2457
  status: args.status,
2458
+ limit: args.limit,
2459
+ offset: args.offset,
2460
+ include: args.include,
2461
+ fields: args.fields,
2029
2462
  });
2030
2463
  },
2031
2464
  get_ticket: async (_client, args) => {
2032
2465
  validateRequired(args, 'ticketId');
2033
2466
  return await apiClient.call('get_ticket', {
2034
2467
  ticketId: args.ticketId,
2468
+ summary: args.summary,
2469
+ include: args.include,
2470
+ });
2471
+ },
2472
+ lookup_ticket: async (_client, args) => {
2473
+ validateRequired(args, 'epicId');
2474
+ // Must provide either title or number
2475
+ if (!args.title && args.number === undefined) {
2476
+ throw new Error("Must provide either 'title' or 'number'");
2477
+ }
2478
+ return await apiClient.call('lookup_ticket', {
2479
+ epicId: args.epicId,
2480
+ title: args.title,
2481
+ number: args.number,
2482
+ });
2483
+ },
2484
+ lookup_tickets_by_status: async (_client, args) => {
2485
+ validateRequired(args, 'ticketIds');
2486
+ if (!Array.isArray(args.ticketIds)) {
2487
+ throw new Error('ticketIds must be an array');
2488
+ }
2489
+ const MAX_BATCH_SIZE = 50;
2490
+ if (args.ticketIds.length > MAX_BATCH_SIZE) {
2491
+ throw new Error(`Maximum ${MAX_BATCH_SIZE} tickets per call`);
2492
+ }
2493
+ // Empty array returns empty result
2494
+ if (args.ticketIds.length === 0) {
2495
+ return { tickets: [], notFound: [] };
2496
+ }
2497
+ // Validate all IDs are strings
2498
+ for (let i = 0; i < args.ticketIds.length; i++) {
2499
+ if (typeof args.ticketIds[i] !== 'string') {
2500
+ throw new Error(`ticketIds[${i}] must be a string`);
2501
+ }
2502
+ }
2503
+ return await apiClient.call('lookup_tickets_by_status', {
2504
+ ticketIds: args.ticketIds,
2035
2505
  });
2036
2506
  },
2037
2507
  // ========================================================================
2038
2508
  // Core Operations - Dependencies
2039
2509
  // ========================================================================
2040
- get_ticket_dependencies: async (_client, args) => {
2041
- validateRequired(args, 'ticketId');
2042
- return await apiClient.call('get_ticket_dependencies', {
2043
- ticketId: args.ticketId,
2044
- });
2045
- },
2046
2510
  add_dependency: async (_client, args) => {
2047
2511
  validateRequired(args, 'ticketId');
2048
2512
  validateRequired(args, 'dependsOnId');
@@ -2074,9 +2538,52 @@ export function createToolHandlers(apiClient) {
2074
2538
  // ========================================================================
2075
2539
  get_implementation_context: async (_client, args) => {
2076
2540
  validateRequired(args, 'ticketId');
2077
- return await apiClient.call('get_implementation_context', {
2541
+ const depth = args.depth || 'standard';
2542
+ // Fetch the implementation context from API with depth parameter
2543
+ const context = await apiClient.call('get_implementation_context', {
2078
2544
  ticketId: args.ticketId,
2545
+ depth,
2079
2546
  });
2547
+ // Only resolve inherited patterns for 'full' depth (minimal/standard don't include them)
2548
+ if (depth === 'full' && context.specification && context.epic) {
2549
+ const spec = context.specification;
2550
+ const epic = context.epic;
2551
+ const ticket = context.ticket;
2552
+ try {
2553
+ const resolvedPatterns = resolvePatternsWithCache({
2554
+ ticketId: args.ticketId,
2555
+ specification: {
2556
+ id: spec.id,
2557
+ codeStandards: spec.codeStandards,
2558
+ commonImports: spec.commonImports,
2559
+ returnTypes: spec.returnTypes,
2560
+ },
2561
+ epic: {
2562
+ id: epic.id,
2563
+ sharedPatterns: epic.sharedPatterns,
2564
+ additionalImports: epic.additionalImports,
2565
+ commonFiles: epic.commonFiles,
2566
+ },
2567
+ ticket: ticket ? {
2568
+ id: ticket.id,
2569
+ technicalDetails: ticket.technicalDetails,
2570
+ } : undefined,
2571
+ });
2572
+ // Add inherited patterns to the response
2573
+ context.inheritedPatterns = {
2574
+ imports: resolvedPatterns.imports,
2575
+ patterns: resolvedPatterns.patterns,
2576
+ returnTypes: resolvedPatterns.returnTypes,
2577
+ commonFiles: resolvedPatterns.commonFiles,
2578
+ naming: resolvedPatterns.naming,
2579
+ };
2580
+ }
2581
+ catch {
2582
+ // If pattern resolution fails, return context without inherited patterns
2583
+ // This maintains backward compatibility
2584
+ }
2585
+ }
2586
+ return context;
2080
2587
  },
2081
2588
  get_next_actionable_tickets: async (_client, args) => {
2082
2589
  // Either specificationId or projectId is required
@@ -2102,10 +2609,130 @@ export function createToolHandlers(apiClient) {
2102
2609
  });
2103
2610
  },
2104
2611
  get_patterns: async (_client, args) => {
2105
- validateRequired(args, 'specificationId');
2106
- return await apiClient.call('get_patterns', {
2612
+ // Either specificationId or ticketId is required
2613
+ if (!args.specificationId && !args.ticketId) {
2614
+ throw new Error('Either specificationId or ticketId is required');
2615
+ }
2616
+ // If ticketId is provided, return resolved patterns with inheritance
2617
+ if (args.ticketId) {
2618
+ // Fetch ticket data
2619
+ const ticket = await apiClient.call('get_ticket', {
2620
+ ticketId: args.ticketId,
2621
+ });
2622
+ // Get epic data
2623
+ const epic = await apiClient.call('get_epic', {
2624
+ epicId: ticket.epicId,
2625
+ });
2626
+ // Get specification data
2627
+ const spec = await apiClient.call('get_specification', {
2628
+ specificationId: epic.specificationId,
2629
+ });
2630
+ // Build pattern resolution input
2631
+ const input = {
2632
+ ticketId: args.ticketId,
2633
+ specification: {
2634
+ id: spec.id,
2635
+ codeStandards: spec.codeStandards,
2636
+ commonImports: spec.commonImports,
2637
+ returnTypes: spec.returnTypes,
2638
+ },
2639
+ epic: {
2640
+ id: epic.id,
2641
+ sharedPatterns: epic.sharedPatterns,
2642
+ additionalImports: epic.additionalImports,
2643
+ commonFiles: epic.commonFiles,
2644
+ },
2645
+ ticket: {
2646
+ id: ticket.id,
2647
+ technicalDetails: ticket.technicalDetails,
2648
+ },
2649
+ };
2650
+ // Resolve patterns and get override info
2651
+ const resolved = resolvePatternsWithCache(input);
2652
+ const overrideReport = getPatternOverrides(input);
2653
+ return {
2654
+ resolved: {
2655
+ imports: resolved.imports,
2656
+ patterns: resolved.patterns,
2657
+ returnTypes: resolved.returnTypes,
2658
+ commonFiles: resolved.commonFiles,
2659
+ naming: resolved.naming,
2660
+ },
2661
+ raw: {
2662
+ specification: {
2663
+ codeStandards: spec.codeStandards ?? null,
2664
+ commonImports: spec.commonImports ?? [],
2665
+ returnTypes: spec.returnTypes ?? null,
2666
+ },
2667
+ epic: {
2668
+ sharedPatterns: epic.sharedPatterns ?? null,
2669
+ additionalImports: epic.additionalImports ?? [],
2670
+ commonFiles: epic.commonFiles ?? null,
2671
+ },
2672
+ ticket: {
2673
+ patterns: ticket.technicalDetails?.patterns ?? null,
2674
+ },
2675
+ },
2676
+ overrides: overrideReport.overrides,
2677
+ chain: {
2678
+ specificationId: spec.id,
2679
+ specificationTitle: spec.title,
2680
+ epicId: epic.id,
2681
+ epicTitle: epic.title,
2682
+ ticketId: ticket.id,
2683
+ ticketTitle: ticket.title,
2684
+ },
2685
+ };
2686
+ }
2687
+ // Specification-level query (backward compatible)
2688
+ const specPatterns = await apiClient.call('get_patterns', {
2107
2689
  specificationId: args.specificationId,
2108
2690
  });
2691
+ // Get spec details for raw patterns if available
2692
+ let specData = null;
2693
+ try {
2694
+ specData = await apiClient.call('get_specification', {
2695
+ specificationId: args.specificationId,
2696
+ });
2697
+ }
2698
+ catch {
2699
+ // If spec fetch fails, just return the raw response
2700
+ return specPatterns;
2701
+ }
2702
+ // Return enhanced response with raw patterns
2703
+ return {
2704
+ resolved: {
2705
+ imports: specData.commonImports ?? [],
2706
+ patterns: flattenCodeStandards(specData.codeStandards),
2707
+ returnTypes: specData.returnTypes ?? {},
2708
+ commonFiles: {},
2709
+ naming: specData.codeStandards?.naming ?? {},
2710
+ },
2711
+ raw: {
2712
+ specification: {
2713
+ codeStandards: specData.codeStandards ?? null,
2714
+ commonImports: specData.commonImports ?? [],
2715
+ returnTypes: specData.returnTypes ?? null,
2716
+ },
2717
+ epic: {
2718
+ sharedPatterns: null,
2719
+ additionalImports: [],
2720
+ commonFiles: null,
2721
+ },
2722
+ ticket: {
2723
+ patterns: null,
2724
+ },
2725
+ },
2726
+ overrides: [],
2727
+ chain: {
2728
+ specificationId: specData.id,
2729
+ specificationTitle: specData.title,
2730
+ epicId: '',
2731
+ epicTitle: '',
2732
+ ticketId: '',
2733
+ ticketTitle: '',
2734
+ },
2735
+ };
2109
2736
  },
2110
2737
  // ========================================================================
2111
2738
  // Workflow & Tracking
@@ -2124,6 +2751,9 @@ export function createToolHandlers(apiClient) {
2124
2751
  filesModified: args.filesModified,
2125
2752
  filesCreated: args.filesCreated,
2126
2753
  actualHours: args.actualHours,
2754
+ validated: args.validated, // MCI-066: simple validation flag
2755
+ validation: args.validation, // MCI-064: validation flags
2756
+ skipCriteriaCheck: args.skipCriteriaCheck, // MCI-065: skip criteria check
2127
2757
  });
2128
2758
  },
2129
2759
  report_progress: async (_client, args) => {
@@ -2135,143 +2765,48 @@ export function createToolHandlers(apiClient) {
2135
2765
  message: args.message,
2136
2766
  });
2137
2767
  },
2138
- get_work_summary: async (_client, args) => {
2139
- validateRequired(args, 'scope');
2140
- return await apiClient.call('get_work_summary', {
2141
- scope: args.scope,
2142
- scopeId: args.scopeId,
2143
- startDate: args.startDate,
2144
- endDate: args.endDate,
2145
- });
2146
- },
2147
- // ========================================================================
2148
- // Testing Tools
2149
- // ========================================================================
2150
- report_test_results: async (_client, args) => {
2151
- validateRequired(args, 'ticketId', 'passed', 'testType');
2152
- return await apiClient.call('report_test_results', {
2153
- ticketId: args.ticketId,
2154
- passed: args.passed,
2155
- testType: args.testType,
2156
- summary: args.summary,
2157
- output: args.output,
2158
- command: args.command,
2159
- exitCode: args.exitCode,
2160
- durationMs: args.durationMs,
2161
- errorCount: args.errorCount,
2162
- warningCount: args.warningCount,
2163
- });
2164
- },
2165
- get_ticket_test_status: async (_client, args) => {
2166
- validateRequired(args, 'ticketId');
2167
- return await apiClient.call('get_ticket_test_status', {
2168
- ticketId: args.ticketId,
2169
- });
2170
- },
2171
- validate_ticket_completion: async (_client, args) => {
2172
- validateRequired(args, 'ticketId');
2173
- return await apiClient.call('validate_ticket_completion', {
2174
- ticketId: args.ticketId,
2175
- });
2176
- },
2177
- // ========================================================================
2178
- // Discovery Tools
2179
- // ========================================================================
2180
- report_discovery: async (_client, args) => {
2181
- validateRequired(args, 'ticketId', 'type', 'title');
2182
- return await apiClient.call('report_discovery', {
2183
- ticketId: args.ticketId,
2184
- type: args.type,
2185
- title: args.title,
2186
- description: args.description,
2187
- priority: args.priority,
2188
- });
2189
- },
2190
- get_pending_discoveries: async (_client, args) => {
2191
- return await apiClient.call('get_pending_discoveries', {
2192
- specificationId: args.specificationId,
2193
- projectId: args.projectId,
2194
- });
2195
- },
2196
- resolve_discovery: async (_client, args) => {
2197
- validateRequired(args, 'discoveryId', 'resolution');
2198
- return await apiClient.call('resolve_discovery', {
2199
- discoveryId: args.discoveryId,
2200
- resolution: args.resolution,
2201
- createdTicketId: args.createdTicketId,
2202
- notes: args.notes,
2203
- });
2204
- },
2205
2768
  // ========================================================================
2206
2769
  // Status & Analytics Tools
2207
2770
  // ========================================================================
2208
- get_specification_status: async (_client, args) => {
2209
- validateRequired(args, 'specificationId');
2210
- return await apiClient.call('get_specification_status', {
2211
- specificationId: args.specificationId,
2212
- });
2213
- },
2214
- get_epic_status: async (_client, args) => {
2215
- validateRequired(args, 'epicId');
2216
- return await apiClient.call('get_epic_status', {
2217
- epicId: args.epicId,
2218
- });
2219
- },
2220
- get_implementation_summary: async (_client, args) => {
2221
- return await apiClient.call('get_implementation_summary', {
2222
- projectId: args.projectId,
2223
- });
2224
- },
2225
- get_time_report: async (_client, args) => {
2226
- validateRequired(args, 'scope', 'scopeId');
2227
- return await apiClient.call('get_time_report', {
2771
+ get_report: async (_client, args) => {
2772
+ validateRequired(args, 'type', 'scope', 'scopeId');
2773
+ return await apiClient.call('get_report', {
2774
+ type: args.type,
2228
2775
  scope: args.scope,
2229
2776
  scopeId: args.scopeId,
2230
- });
2231
- },
2232
- get_blockers_report: async (_client, args) => {
2233
- return await apiClient.call('get_blockers_report', {
2234
- specificationId: args.specificationId,
2235
- projectId: args.projectId,
2777
+ format: args.format ?? 'json',
2778
+ startDate: args.startDate,
2779
+ endDate: args.endDate,
2236
2780
  });
2237
2781
  },
2238
2782
  // ========================================================================
2239
2783
  // Search Tools
2240
2784
  // ========================================================================
2241
2785
  search_tickets: async (_client, args) => {
2242
- validateRequired(args, 'query');
2786
+ // Validate at least one search criterion
2787
+ if (!args.query && !args.files && !args.tags && !args.relatedTo) {
2788
+ throw new Error('At least one filter is required: query, files, tags, or relatedTo');
2789
+ }
2790
+ // Validate at least one scope
2791
+ if (!args.projectId && !args.specificationId && !args.epicId) {
2792
+ throw new Error('One of projectId, specificationId, or epicId is required');
2793
+ }
2243
2794
  return await apiClient.call('search_tickets', {
2244
2795
  query: args.query,
2796
+ files: args.files,
2797
+ tags: args.tags,
2798
+ matchAllTags: args.matchAllTags ?? false,
2799
+ relatedTo: args.relatedTo,
2800
+ status: args.status,
2801
+ complexity: args.complexity,
2802
+ priority: args.priority,
2245
2803
  specificationId: args.specificationId,
2246
2804
  projectId: args.projectId,
2805
+ epicId: args.epicId,
2247
2806
  limit: args.limit ?? 20,
2248
2807
  offset: args.offset ?? 0,
2249
2808
  });
2250
2809
  },
2251
- find_tickets_by_file: async (_client, args) => {
2252
- validateRequired(args, 'filePath');
2253
- return await apiClient.call('find_tickets_by_file', {
2254
- filePath: args.filePath,
2255
- specificationId: args.specificationId,
2256
- projectId: args.projectId,
2257
- });
2258
- },
2259
- find_tickets_by_tag: async (_client, args) => {
2260
- validateRequired(args, 'tags');
2261
- return await apiClient.call('find_tickets_by_tag', {
2262
- tags: args.tags,
2263
- matchAll: args.matchAll ?? false,
2264
- specificationId: args.specificationId,
2265
- projectId: args.projectId,
2266
- });
2267
- },
2268
- find_related_tickets: async (_client, args) => {
2269
- validateRequired(args, 'ticketId');
2270
- return await apiClient.call('find_related_tickets', {
2271
- ticketId: args.ticketId,
2272
- limit: args.limit ?? 10,
2273
- });
2274
- },
2275
2810
  // ========================================================================
2276
2811
  // Git Integration Tools
2277
2812
  // ========================================================================
@@ -2300,28 +2835,13 @@ export function createToolHandlers(apiClient) {
2300
2835
  repoUrl: args.repoUrl,
2301
2836
  });
2302
2837
  },
2303
- get_ticket_commits: async (_client, args) => {
2304
- validateRequired(args, 'ticketId');
2305
- return await apiClient.call('get_ticket_commits', {
2306
- ticketId: args.ticketId,
2307
- limit: args.limit ?? 20,
2308
- offset: args.offset ?? 0,
2309
- });
2310
- },
2311
- get_ticket_prs: async (_client, args) => {
2312
- validateRequired(args, 'ticketId');
2313
- return await apiClient.call('get_ticket_prs', {
2314
- ticketId: args.ticketId,
2315
- limit: args.limit ?? 20,
2316
- offset: args.offset ?? 0,
2317
- });
2318
- },
2319
2838
  // ========================================================================
2320
2839
  // Creation Operations
2321
2840
  // ========================================================================
2322
2841
  create_specification: async (_client, args) => {
2323
2842
  validateRequired(args, 'projectId', 'title');
2324
- return await apiClient.call('create_specification', {
2843
+ const responseMode = getResponseModeFromArgs(args);
2844
+ const result = await apiClient.call('create_specification', {
2325
2845
  projectId: args.projectId,
2326
2846
  title: args.title,
2327
2847
  description: args.description,
@@ -2350,10 +2870,54 @@ export function createToolHandlers(apiClient) {
2350
2870
  apiContracts: args.apiContracts,
2351
2871
  targetAudience: args.targetAudience,
2352
2872
  });
2873
+ return formatWriteResponse(result, responseMode, 'Specification created');
2353
2874
  },
2354
2875
  create_epic: async (_client, args) => {
2355
2876
  validateRequired(args, 'specificationId', 'title', 'description', 'objective');
2356
- return await apiClient.call('create_epic', {
2877
+ const responseMode = getResponseModeFromArgs(args);
2878
+ // Validate inline dependencies if tickets are provided with dependsOn
2879
+ if (args.tickets && Array.isArray(args.tickets)) {
2880
+ const tickets = args.tickets;
2881
+ const ticketCount = tickets.length;
2882
+ for (let i = 0; i < ticketCount; i++) {
2883
+ const ticket = tickets[i];
2884
+ if (ticket.dependsOn && Array.isArray(ticket.dependsOn)) {
2885
+ for (const depIndex of ticket.dependsOn) {
2886
+ // Check bounds
2887
+ if (typeof depIndex !== 'number' || depIndex < 0 || depIndex >= ticketCount) {
2888
+ throw new Error(`tickets[${i}].dependsOn: index ${depIndex} is out of bounds (must be 0-${ticketCount - 1})`);
2889
+ }
2890
+ // Check self-reference
2891
+ if (depIndex === i) {
2892
+ throw new Error(`tickets[${i}].dependsOn: ticket cannot depend on itself`);
2893
+ }
2894
+ }
2895
+ }
2896
+ }
2897
+ // Check for circular dependencies using DFS
2898
+ const hasCycle = (startIdx, visited, path) => {
2899
+ if (path.has(startIdx))
2900
+ return true;
2901
+ if (visited.has(startIdx))
2902
+ return false;
2903
+ visited.add(startIdx);
2904
+ path.add(startIdx);
2905
+ const deps = tickets[startIdx].dependsOn || [];
2906
+ for (const depIdx of deps) {
2907
+ if (hasCycle(depIdx, visited, path))
2908
+ return true;
2909
+ }
2910
+ path.delete(startIdx);
2911
+ return false;
2912
+ };
2913
+ const visited = new Set();
2914
+ for (let i = 0; i < ticketCount; i++) {
2915
+ if (hasCycle(i, visited, new Set())) {
2916
+ throw new Error(`Circular dependency detected in tickets array`);
2917
+ }
2918
+ }
2919
+ }
2920
+ const result = await apiClient.call('create_epic', {
2357
2921
  specificationId: args.specificationId,
2358
2922
  title: args.title,
2359
2923
  description: args.description,
@@ -2379,10 +2943,18 @@ export function createToolHandlers(apiClient) {
2379
2943
  dependencies: args.dependencies,
2380
2944
  apiContracts: args.apiContracts,
2381
2945
  });
2946
+ return formatWriteResponse(result, responseMode, 'Epic created');
2382
2947
  },
2383
2948
  create_ticket: async (_client, args) => {
2384
2949
  validateRequired(args, 'epicId', 'title');
2385
- return await apiClient.call('create_ticket', {
2950
+ const responseMode = getResponseModeFromArgs(args);
2951
+ // Extract validation options
2952
+ const skipValidation = args.skipValidation === true;
2953
+ const validationStrictness = args.validationStrictness === 'lenient' || args.validationStrictness === 'strict'
2954
+ ? args.validationStrictness
2955
+ : 'normal';
2956
+ // Create the ticket
2957
+ const result = await apiClient.call('create_ticket', {
2386
2958
  epicId: args.epicId,
2387
2959
  title: args.title,
2388
2960
  ticketNumber: args.ticketNumber,
@@ -2397,6 +2969,22 @@ export function createToolHandlers(apiClient) {
2397
2969
  order: args.order,
2398
2970
  dependsOn: args.dependsOn,
2399
2971
  });
2972
+ // Run validation unless skipped
2973
+ if (!skipValidation) {
2974
+ const validation = validateTicket({
2975
+ complexity: args.complexity,
2976
+ implementation: args.implementation,
2977
+ technicalDetails: args.technicalDetails,
2978
+ notes: args.notes,
2979
+ acceptanceCriteria: args.acceptanceCriteria,
2980
+ });
2981
+ // Filter warnings by strictness level
2982
+ const warnings = filterWarningsByStrictness(validation.warnings, validationStrictness);
2983
+ // Add validation results to response
2984
+ result.warnings = warnings;
2985
+ result.completenessScore = validation.completenessScore;
2986
+ }
2987
+ return formatWriteResponse(result, responseMode, 'Ticket created');
2400
2988
  },
2401
2989
  import_specification: async (_client, args) => {
2402
2990
  validateRequired(args, 'projectId', 'content');
@@ -2412,7 +3000,36 @@ export function createToolHandlers(apiClient) {
2412
3000
  // ========================================================================
2413
3001
  update_ticket: async (_client, args) => {
2414
3002
  validateRequired(args, 'ticketId');
2415
- return await apiClient.call('update_ticket', {
3003
+ const responseMode = getResponseModeFromArgs(args);
3004
+ // Extract validation options
3005
+ const skipValidation = args.skipValidation === true;
3006
+ const validationStrictness = args.validationStrictness === 'lenient' || args.validationStrictness === 'strict'
3007
+ ? args.validationStrictness
3008
+ : 'normal';
3009
+ // Get current ticket to calculate previous completeness score
3010
+ let previousCompletenessScore;
3011
+ if (!skipValidation) {
3012
+ try {
3013
+ const currentTicket = await apiClient.call('get_ticket', {
3014
+ ticketId: args.ticketId,
3015
+ });
3016
+ // Calculate previous completeness score
3017
+ const prevValidation = validateTicket({
3018
+ complexity: currentTicket.complexity,
3019
+ implementation: currentTicket.implementation,
3020
+ technicalDetails: currentTicket.technicalDetails,
3021
+ notes: currentTicket.notes,
3022
+ acceptanceCriteria: currentTicket.acceptanceCriteria,
3023
+ });
3024
+ previousCompletenessScore = prevValidation.completenessScore;
3025
+ }
3026
+ catch {
3027
+ // If we can't get the current ticket, skip previous score tracking
3028
+ previousCompletenessScore = undefined;
3029
+ }
3030
+ }
3031
+ // Update the ticket
3032
+ const result = await apiClient.call('update_ticket', {
2416
3033
  ticketId: args.ticketId,
2417
3034
  title: args.title,
2418
3035
  description: args.description,
@@ -2428,10 +3045,32 @@ export function createToolHandlers(apiClient) {
2428
3045
  technicalDetails: args.technicalDetails,
2429
3046
  blockReason: args.blockReason,
2430
3047
  });
3048
+ // Run validation on updated ticket unless skipped
3049
+ if (!skipValidation) {
3050
+ // Use the updated values (from args) merged with existing values (from result)
3051
+ // The result should contain the final state after update
3052
+ const validation = validateTicket({
3053
+ complexity: (args.complexity || result.complexity),
3054
+ implementation: (args.implementation || result.implementation),
3055
+ technicalDetails: (args.technicalDetails || result.technicalDetails),
3056
+ notes: (args.notes || result.notes),
3057
+ acceptanceCriteria: (args.acceptanceCriteria || result.acceptanceCriteria),
3058
+ });
3059
+ // Filter warnings by strictness level
3060
+ const warnings = filterWarningsByStrictness(validation.warnings, validationStrictness);
3061
+ // Add validation results to response
3062
+ result.warnings = warnings;
3063
+ result.completenessScore = validation.completenessScore;
3064
+ if (previousCompletenessScore !== undefined) {
3065
+ result.previousCompletenessScore = previousCompletenessScore;
3066
+ }
3067
+ }
3068
+ return formatWriteResponse(result, responseMode, 'Ticket updated');
2431
3069
  },
2432
3070
  update_epic: async (_client, args) => {
2433
3071
  validateRequired(args, 'epicId');
2434
- return await apiClient.call('update_epic', {
3072
+ const responseMode = getResponseModeFromArgs(args);
3073
+ const result = await apiClient.call('update_epic', {
2435
3074
  epicId: args.epicId,
2436
3075
  title: args.title,
2437
3076
  description: args.description,
@@ -2454,10 +3093,12 @@ export function createToolHandlers(apiClient) {
2454
3093
  tags: args.tags,
2455
3094
  estimatedHours: args.estimatedHours,
2456
3095
  });
3096
+ return formatWriteResponse(result, responseMode, 'Epic updated');
2457
3097
  },
2458
3098
  update_specification: async (_client, args) => {
2459
3099
  validateRequired(args, 'specificationId');
2460
- return await apiClient.call('update_specification', {
3100
+ const responseMode = getResponseModeFromArgs(args);
3101
+ const result = await apiClient.call('update_specification', {
2461
3102
  specificationId: args.specificationId,
2462
3103
  title: args.title,
2463
3104
  description: args.description,
@@ -2488,6 +3129,7 @@ export function createToolHandlers(apiClient) {
2488
3129
  workingDirectory: args.workingDirectory,
2489
3130
  outputDirectory: args.outputDirectory,
2490
3131
  });
3132
+ return formatWriteResponse(result, responseMode, 'Specification updated');
2491
3133
  },
2492
3134
  // ========================================================================
2493
3135
  // Working Context Operations
@@ -2516,34 +3158,6 @@ export function createToolHandlers(apiClient) {
2516
3158
  config: args.config,
2517
3159
  });
2518
3160
  },
2519
- get_session: async (_client, args) => {
2520
- return await apiClient.call('get_session', {
2521
- sessionId: args.sessionId,
2522
- specificationId: args.specificationId,
2523
- includeHistory: args.includeHistory,
2524
- });
2525
- },
2526
- update_session: async (_client, args) => {
2527
- validateRequired(args, 'sessionId');
2528
- return await apiClient.call('update_session', {
2529
- sessionId: args.sessionId,
2530
- status: args.status,
2531
- config: args.config,
2532
- notes: args.notes,
2533
- currentTicketId: args.currentTicketId,
2534
- });
2535
- },
2536
- record_session_ticket: async (_client, args) => {
2537
- validateRequired(args, 'sessionId', 'ticketId', 'result');
2538
- return await apiClient.call('record_session_ticket', {
2539
- sessionId: args.sessionId,
2540
- ticketId: args.ticketId,
2541
- result: args.result,
2542
- durationMinutes: args.durationMinutes,
2543
- errorMessage: args.errorMessage,
2544
- skipReason: args.skipReason,
2545
- });
2546
- },
2547
3161
  end_session: async (_client, args) => {
2548
3162
  validateRequired(args, 'sessionId', 'status');
2549
3163
  return await apiClient.call('end_session', {
@@ -2552,18 +3166,77 @@ export function createToolHandlers(apiClient) {
2552
3166
  summary: args.summary,
2553
3167
  });
2554
3168
  },
2555
- list_sessions: async (_client, args) => {
2556
- return await apiClient.call('list_sessions', {
2557
- specificationId: args.specificationId,
2558
- projectId: args.projectId,
2559
- status: args.status,
2560
- limit: args.limit,
2561
- offset: args.offset,
2562
- });
2563
- },
2564
3169
  // ========================================================================
2565
3170
  // Bulk Operations
2566
3171
  // ========================================================================
3172
+ bulk_create_tickets: async (_client, args) => {
3173
+ validateRequired(args, 'epicId', 'tickets');
3174
+ if (!Array.isArray(args.tickets) || args.tickets.length === 0) {
3175
+ throw new Error('tickets array is required and must not be empty');
3176
+ }
3177
+ // Validate each ticket has a title
3178
+ const tickets = args.tickets;
3179
+ for (let i = 0; i < tickets.length; i++) {
3180
+ if (!tickets[i].title || typeof tickets[i].title !== 'string') {
3181
+ throw new Error(`tickets[${i}].title is required and must be a string`);
3182
+ }
3183
+ }
3184
+ // Validate dependencies if provided
3185
+ if (args.dependencies && Array.isArray(args.dependencies)) {
3186
+ const deps = args.dependencies;
3187
+ for (let i = 0; i < deps.length; i++) {
3188
+ const dep = deps[i];
3189
+ if (typeof dep.ticket !== 'number' || dep.ticket < 0 || dep.ticket >= tickets.length) {
3190
+ throw new Error(`dependencies[${i}].ticket must be a valid index in tickets array (0-${tickets.length - 1})`);
3191
+ }
3192
+ const dependsOn = Array.isArray(dep.dependsOn) ? dep.dependsOn : [dep.dependsOn];
3193
+ for (const idx of dependsOn) {
3194
+ if (typeof idx !== 'number' || idx < 0 || idx >= tickets.length) {
3195
+ throw new Error(`dependencies[${i}].dependsOn contains invalid index ${idx} (must be 0-${tickets.length - 1})`);
3196
+ }
3197
+ if (idx === dep.ticket) {
3198
+ throw new Error(`dependencies[${i}]: ticket cannot depend on itself`);
3199
+ }
3200
+ }
3201
+ }
3202
+ }
3203
+ return await apiClient.call('bulk_create_tickets', {
3204
+ epicId: args.epicId,
3205
+ tickets: args.tickets,
3206
+ dependencies: args.dependencies,
3207
+ onError: args.onError,
3208
+ });
3209
+ },
3210
+ get_job_status: async (_client, args) => {
3211
+ validateRequired(args, 'jobId');
3212
+ return await apiClient.call('get_job_status', {
3213
+ jobId: args.jobId,
3214
+ });
3215
+ },
3216
+ bulk_add_dependencies: async (_client, args) => {
3217
+ validateRequired(args, 'dependencies');
3218
+ if (!Array.isArray(args.dependencies) || args.dependencies.length === 0) {
3219
+ throw new Error('dependencies array is required and must not be empty');
3220
+ }
3221
+ // Validate each dependency has required fields
3222
+ const deps = args.dependencies;
3223
+ for (let i = 0; i < deps.length; i++) {
3224
+ if (!deps[i].ticketId || typeof deps[i].ticketId !== 'string') {
3225
+ throw new Error(`dependencies[${i}].ticketId is required`);
3226
+ }
3227
+ if (!deps[i].dependsOnId || typeof deps[i].dependsOnId !== 'string') {
3228
+ throw new Error(`dependencies[${i}].dependsOnId is required`);
3229
+ }
3230
+ if (deps[i].ticketId === deps[i].dependsOnId) {
3231
+ throw new Error(`dependencies[${i}]: ticket cannot depend on itself`);
3232
+ }
3233
+ }
3234
+ return await apiClient.call('bulk_add_dependencies', {
3235
+ dependencies: args.dependencies,
3236
+ skipCircularCheck: args.skipCircularCheck,
3237
+ onError: args.onError,
3238
+ });
3239
+ },
2567
3240
  bulk_update_tickets: async (_client, args) => {
2568
3241
  if (!args.updates || !Array.isArray(args.updates) || args.updates.length === 0) {
2569
3242
  throw new Error('updates array is required and must not be empty');
@@ -2589,6 +3262,22 @@ export function createToolHandlers(apiClient) {
2589
3262
  clearTestResults: args.clearTestResults,
2590
3263
  });
2591
3264
  },
3265
+ // ========================================================================
3266
+ // Validation Operations (MCI-075)
3267
+ // ========================================================================
3268
+ validate_counts: async (_client, args) => {
3269
+ // Must specify at least one scope
3270
+ if (!args.projectId && !args.specificationId && !args.epicId) {
3271
+ throw new Error('Must specify projectId, specificationId, or epicId');
3272
+ }
3273
+ return await apiClient.call('validate_counts', {
3274
+ projectId: args.projectId,
3275
+ specificationId: args.specificationId,
3276
+ epicId: args.epicId,
3277
+ repair: args.repair,
3278
+ verbose: args.verbose,
3279
+ });
3280
+ },
2592
3281
  };
2593
3282
  }
2594
3283
  /**