@specforge/mcp 1.5.3 → 1.6.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 +1477 -70
  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,91 @@ 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
+ 'get_specification_status',
52
+ // Epic operations
53
+ 'list_epics',
54
+ 'get_epic',
55
+ 'lookup_epic',
56
+ 'get_epic_status',
57
+ // Ticket operations
58
+ 'list_tickets',
59
+ 'get_ticket',
60
+ 'lookup_ticket',
61
+ 'lookup_tickets_by_status',
62
+ 'get_ticket_dependencies',
63
+ 'get_ticket_test_status',
64
+ // Dependency operations
65
+ 'get_dependency_tree',
66
+ // Context operations
67
+ 'get_implementation_context',
68
+ 'get_next_actionable_tickets',
69
+ 'get_blocked_tickets',
70
+ 'get_critical_path',
71
+ 'get_patterns',
72
+ // Workflow read operations
73
+ 'get_work_summary',
74
+ // Discovery read operations
75
+ 'get_pending_discoveries',
76
+ // Status & Analytics
77
+ 'get_implementation_summary',
78
+ 'get_time_report',
79
+ 'get_blockers_report',
80
+ // Search operations
81
+ 'search_tickets',
82
+ 'find_tickets_by_file',
83
+ 'find_tickets_by_tag',
84
+ 'find_related_tickets',
85
+ // Git read operations
86
+ 'get_ticket_commits',
87
+ 'get_ticket_prs',
88
+ // Working context
89
+ 'get_working_context',
90
+ // Session read operations
91
+ 'get_session',
92
+ 'list_sessions',
93
+ // Job status
94
+ 'get_job_status',
95
+ ]);
96
+ /**
97
+ * Add format parameter to a tool's input schema
98
+ */
99
+ function addFormatParameter(tool) {
100
+ return {
101
+ ...tool,
102
+ inputSchema: {
103
+ ...tool.inputSchema,
104
+ properties: {
105
+ ...tool.inputSchema.properties,
106
+ format: FORMAT_PARAMETER,
107
+ },
108
+ },
109
+ };
110
+ }
26
111
  export function getTools() {
27
- return [
112
+ const tools = [
28
113
  // ========================================================================
29
114
  // Core Operations - Projects (Epic 3)
30
115
  // ========================================================================
@@ -51,12 +136,26 @@ export function getTools() {
51
136
  required: ['projectId'],
52
137
  },
53
138
  },
139
+ {
140
+ name: 'lookup_project',
141
+ 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.',
142
+ inputSchema: {
143
+ type: 'object',
144
+ properties: {
145
+ name: {
146
+ type: 'string',
147
+ description: 'Project name to search for (partial match, case-insensitive)',
148
+ },
149
+ },
150
+ required: ['name'],
151
+ },
152
+ },
54
153
  // ========================================================================
55
154
  // Core Operations - Specifications (Epic 3)
56
155
  // ========================================================================
57
156
  {
58
157
  name: 'list_specifications',
59
- description: 'List specifications for a project with optional status filter',
158
+ 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
159
  inputSchema: {
61
160
  type: 'object',
62
161
  properties: {
@@ -72,13 +171,60 @@ export function getTools() {
72
171
  },
73
172
  description: 'Filter by status (optional)',
74
173
  },
174
+ limit: {
175
+ type: 'integer',
176
+ minimum: 1,
177
+ maximum: 100,
178
+ description: 'Maximum number of items to return (default: 20, max: 100)',
179
+ },
180
+ offset: {
181
+ type: 'integer',
182
+ minimum: 0,
183
+ description: 'Number of items to skip for offset-based pagination (default: 0)',
184
+ },
185
+ fields: {
186
+ type: 'array',
187
+ items: {
188
+ type: 'string',
189
+ enum: [
190
+ 'id',
191
+ 'projectId',
192
+ 'title',
193
+ 'description',
194
+ 'background',
195
+ 'status',
196
+ 'priority',
197
+ 'estimatedHours',
198
+ 'goals',
199
+ 'requirements',
200
+ 'constraints',
201
+ 'guardrails',
202
+ 'techStack',
203
+ 'architecture',
204
+ 'fileStructure',
205
+ 'epicCount',
206
+ 'ticketCount',
207
+ 'completedTicketCount',
208
+ 'createdAt',
209
+ 'updatedAt',
210
+ ],
211
+ },
212
+ description: 'Select specific fields to return. Returns all fields if not specified. id is always included. Example: ["id", "title", "status"] for minimal response.',
213
+ },
75
214
  },
76
215
  required: ['projectId'],
77
216
  },
78
217
  },
79
218
  {
80
219
  name: 'get_specification',
81
- description: 'Get full specification with goals, requirements, constraints, and guardrails',
220
+ description: `Get specification with optional summary mode or full details.
221
+
222
+ Use summary: true for minimal response (~70 tokens) with key fields only (id, title, status, priority, epicCount, ticketCount, completedTicketCount).
223
+
224
+ Optional includes (ignored when summary=true):
225
+ - 'status': Progress, ticket counts, blockers (replaces get_specification_status)
226
+ - 'epics': List of epics with summary data
227
+ - 'patterns': Tech stack, code patterns, naming conventions`,
82
228
  inputSchema: {
83
229
  type: 'object',
84
230
  properties: {
@@ -86,16 +232,47 @@ export function getTools() {
86
232
  type: 'string',
87
233
  description: 'The ID of the specification to retrieve',
88
234
  },
235
+ summary: {
236
+ type: 'boolean',
237
+ description: 'Return minimal summary fields only (~70 tokens vs ~600 full). Ignores include parameter when true. Default: false',
238
+ default: false,
239
+ },
240
+ include: {
241
+ type: 'array',
242
+ items: {
243
+ type: 'string',
244
+ enum: ['status', 'epics', 'patterns'],
245
+ },
246
+ description: 'Optional data to include in response (ignored when summary=true)',
247
+ },
89
248
  },
90
249
  required: ['specificationId'],
91
250
  },
92
251
  },
252
+ {
253
+ name: 'lookup_specification',
254
+ description: 'Fast specification lookup by title. Returns minimal data (id, title, status, epicCount, ticketCount) for token efficiency.',
255
+ inputSchema: {
256
+ type: 'object',
257
+ properties: {
258
+ projectId: {
259
+ type: 'string',
260
+ description: 'Project ID to search within',
261
+ },
262
+ title: {
263
+ type: 'string',
264
+ description: 'Specification title to search for (partial match, case-insensitive)',
265
+ },
266
+ },
267
+ required: ['projectId', 'title'],
268
+ },
269
+ },
93
270
  // ========================================================================
94
271
  // Core Operations - Epics (Epic 3)
95
272
  // ========================================================================
96
273
  {
97
274
  name: 'list_epics',
98
- description: 'List epics for a specification with optional status filter',
275
+ 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
276
  inputSchema: {
100
277
  type: 'object',
101
278
  properties: {
@@ -108,13 +285,56 @@ export function getTools() {
108
285
  enum: ['todo', 'in_progress', 'completed'],
109
286
  description: 'Filter by status (optional)',
110
287
  },
288
+ limit: {
289
+ type: 'integer',
290
+ minimum: 1,
291
+ maximum: 100,
292
+ description: 'Maximum number of items to return (default: 20, max: 100)',
293
+ },
294
+ offset: {
295
+ type: 'integer',
296
+ minimum: 0,
297
+ description: 'Number of items to skip for offset-based pagination (default: 0)',
298
+ },
299
+ fields: {
300
+ type: 'array',
301
+ items: {
302
+ type: 'string',
303
+ enum: [
304
+ 'id',
305
+ 'specificationId',
306
+ 'epicNumber',
307
+ 'title',
308
+ 'description',
309
+ 'objective',
310
+ 'status',
311
+ 'priority',
312
+ 'estimatedHours',
313
+ 'acceptanceCriteria',
314
+ 'scope',
315
+ 'constraints',
316
+ 'assumptions',
317
+ 'ticketCount',
318
+ 'completedTicketCount',
319
+ 'createdAt',
320
+ 'updatedAt',
321
+ ],
322
+ },
323
+ description: 'Select specific fields to return. Returns all fields if not specified. id is always included. Example: ["id", "epicNumber", "title", "status"] for minimal response.',
324
+ },
111
325
  },
112
326
  required: ['specificationId'],
113
327
  },
114
328
  },
115
329
  {
116
330
  name: 'get_epic',
117
- description: 'Get epic with scope, goals, acceptance criteria, and ticket summary',
331
+ description: `Get epic with optional summary mode or full details.
332
+
333
+ Use summary: true for minimal response (~80 tokens) with key fields only (id, epicNumber, title, status, objective, ticketCount, completedTicketCount).
334
+
335
+ Optional includes (ignored when summary=true):
336
+ - 'status': Progress percentage, ticket counts, active tickets (replaces get_epic_status)
337
+ - 'tickets': List of tickets with summary data`,
118
338
  inputSchema: {
119
339
  type: 'object',
120
340
  properties: {
@@ -122,16 +342,51 @@ export function getTools() {
122
342
  type: 'string',
123
343
  description: 'The ID of the epic to retrieve',
124
344
  },
345
+ summary: {
346
+ type: 'boolean',
347
+ description: 'Return minimal summary fields only (~80 tokens vs ~400 full). Ignores include parameter when true. Default: false',
348
+ default: false,
349
+ },
350
+ include: {
351
+ type: 'array',
352
+ items: {
353
+ type: 'string',
354
+ enum: ['status', 'tickets'],
355
+ },
356
+ description: 'Optional data to include in response (ignored when summary=true)',
357
+ },
125
358
  },
126
359
  required: ['epicId'],
127
360
  },
128
361
  },
362
+ {
363
+ name: 'lookup_epic',
364
+ 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.",
365
+ inputSchema: {
366
+ type: 'object',
367
+ properties: {
368
+ specificationId: {
369
+ type: 'string',
370
+ description: 'Specification ID to search within',
371
+ },
372
+ number: {
373
+ type: 'integer',
374
+ description: 'Epic number for exact match lookup',
375
+ },
376
+ title: {
377
+ type: 'string',
378
+ description: 'Epic title for partial match lookup',
379
+ },
380
+ },
381
+ required: ['specificationId'],
382
+ },
383
+ },
129
384
  // ========================================================================
130
385
  // Core Operations - Tickets (Epic 3)
131
386
  // ========================================================================
132
387
  {
133
388
  name: 'list_tickets',
134
- description: 'List tickets for an epic with optional status filter',
389
+ 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
390
  inputSchema: {
136
391
  type: 'object',
137
392
  properties: {
@@ -143,9 +398,60 @@ export function getTools() {
143
398
  type: 'array',
144
399
  items: {
145
400
  type: 'string',
146
- enum: ['todo', 'queue', 'in_progress', 'blocked', 'done'],
401
+ enum: ['pending', 'ready', 'active', 'done'],
147
402
  },
148
- description: 'Filter by status (optional)',
403
+ description: 'Filter by status (optional). pending/ready are auto-calculated based on dependencies.',
404
+ },
405
+ limit: {
406
+ type: 'integer',
407
+ minimum: 1,
408
+ maximum: 100,
409
+ description: 'Maximum number of items to return (default: 20, max: 100)',
410
+ },
411
+ offset: {
412
+ type: 'integer',
413
+ minimum: 0,
414
+ description: 'Number of items to skip for offset-based pagination (default: 0)',
415
+ },
416
+ include: {
417
+ type: 'array',
418
+ items: {
419
+ type: 'string',
420
+ enum: ['statusReason'],
421
+ },
422
+ description: 'Include statusReason for pending tickets (shows unsatisfied dependencies or block reasons)',
423
+ },
424
+ fields: {
425
+ type: 'array',
426
+ items: {
427
+ type: 'string',
428
+ enum: [
429
+ 'id',
430
+ 'epicId',
431
+ 'ticketNumber',
432
+ 'title',
433
+ 'description',
434
+ 'status',
435
+ 'priority',
436
+ 'complexity',
437
+ 'estimatedHours',
438
+ 'actualHours',
439
+ 'acceptanceCriteria',
440
+ 'implementation',
441
+ 'technicalDetails',
442
+ 'notes',
443
+ 'tags',
444
+ 'blockReason',
445
+ 'progress',
446
+ 'testsPassed',
447
+ 'order',
448
+ 'startedAt',
449
+ 'completedAt',
450
+ 'createdAt',
451
+ 'updatedAt',
452
+ ],
453
+ },
454
+ 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
455
  },
150
456
  },
151
457
  required: ['epicId'],
@@ -153,7 +459,18 @@ export function getTools() {
153
459
  },
154
460
  {
155
461
  name: 'get_ticket',
156
- description: 'Get full ticket details including implementation steps, acceptance criteria, and dependencies',
462
+ description: `Get ticket with optional summary mode or full details.
463
+
464
+ Use summary: true for minimal response (~100 tokens) with key fields only (id, ticketNumber, title, status, complexity, estimatedHours, priority).
465
+
466
+ For full details, statusReason is auto-included for pending tickets explaining why the ticket is blocked.
467
+
468
+ Optional includes (ignored when summary=true):
469
+ - 'dependencies': Tickets this blocks and is blocked by (replaces get_ticket_dependencies)
470
+ - 'testStatus': Test results and history (replaces get_ticket_test_status)
471
+ - 'commits': Linked git commits (replaces get_ticket_commits)
472
+ - 'prs': Linked pull requests (replaces get_ticket_prs)
473
+ - 'statusReason': Why ticket is pending (auto-included for pending tickets)`,
157
474
  inputSchema: {
158
475
  type: 'object',
159
476
  properties: {
@@ -161,10 +478,61 @@ export function getTools() {
161
478
  type: 'string',
162
479
  description: 'The ID of the ticket to retrieve',
163
480
  },
481
+ summary: {
482
+ type: 'boolean',
483
+ description: 'Return minimal summary fields only (~100 tokens vs ~500 full). Ignores include parameter when true. Default: false',
484
+ default: false,
485
+ },
486
+ include: {
487
+ type: 'array',
488
+ items: {
489
+ type: 'string',
490
+ enum: ['dependencies', 'testStatus', 'commits', 'prs', 'statusReason'],
491
+ },
492
+ description: 'Additional data to include (ignored when summary=true). statusReason is auto-included for pending tickets.',
493
+ },
164
494
  },
165
495
  required: ['ticketId'],
166
496
  },
167
497
  },
498
+ {
499
+ name: 'lookup_ticket',
500
+ 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.",
501
+ inputSchema: {
502
+ type: 'object',
503
+ properties: {
504
+ epicId: {
505
+ type: 'string',
506
+ description: 'Epic ID to search within',
507
+ },
508
+ number: {
509
+ type: 'integer',
510
+ description: 'Ticket number for exact match lookup',
511
+ },
512
+ title: {
513
+ type: 'string',
514
+ description: 'Ticket title for partial match lookup',
515
+ },
516
+ },
517
+ required: ['epicId'],
518
+ },
519
+ },
520
+ {
521
+ name: 'lookup_tickets_by_status',
522
+ description: 'Batch lookup of ticket statuses. Optimized for checking if dependencies are satisfied. Returns minimal data (id, status, title) for up to 50 tickets.',
523
+ inputSchema: {
524
+ type: 'object',
525
+ properties: {
526
+ ticketIds: {
527
+ type: 'array',
528
+ items: { type: 'string' },
529
+ maxItems: 50,
530
+ description: 'Array of ticket IDs to lookup (max 50)',
531
+ },
532
+ },
533
+ required: ['ticketIds'],
534
+ },
535
+ },
168
536
  // ========================================================================
169
537
  // Core Operations - Dependencies (Epic 3)
170
538
  // ========================================================================
@@ -245,7 +613,7 @@ export function getTools() {
245
613
  // ========================================================================
246
614
  {
247
615
  name: 'get_implementation_context',
248
- description: 'Get full implementation context for a ticket including spec, epic, dependencies, and patterns',
616
+ 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
617
  inputSchema: {
250
618
  type: 'object',
251
619
  properties: {
@@ -253,13 +621,19 @@ export function getTools() {
253
621
  type: 'string',
254
622
  description: 'The ID of the ticket',
255
623
  },
624
+ depth: {
625
+ type: 'string',
626
+ enum: ['minimal', 'standard', 'full'],
627
+ 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',
628
+ default: 'standard',
629
+ },
256
630
  },
257
631
  required: ['ticketId'],
258
632
  },
259
633
  },
260
634
  {
261
635
  name: 'get_next_actionable_tickets',
262
- description: 'Get tickets that can be worked on now (all dependencies satisfied). Can query by specification or project.',
636
+ description: 'Get tickets with status "ready" (all dependencies satisfied). Use start_work_session() to begin work on these tickets.',
263
637
  inputSchema: {
264
638
  type: 'object',
265
639
  properties: {
@@ -280,7 +654,7 @@ export function getTools() {
280
654
  },
281
655
  {
282
656
  name: 'get_blocked_tickets',
283
- description: 'Get tickets that are blocked with reasons why',
657
+ description: 'Get tickets with status "pending" and their statusReason explaining why they are blocked (unsatisfied dependencies or external block)',
284
658
  inputSchema: {
285
659
  type: 'object',
286
660
  properties: {
@@ -308,16 +682,19 @@ export function getTools() {
308
682
  },
309
683
  {
310
684
  name: 'get_patterns',
311
- description: 'Extract tech stack, code patterns, and naming conventions from specification',
685
+ 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
686
  inputSchema: {
313
687
  type: 'object',
314
688
  properties: {
315
689
  specificationId: {
316
690
  type: 'string',
317
- description: 'The ID of the specification',
691
+ description: 'Get patterns for entire specification (raw spec patterns only)',
692
+ },
693
+ ticketId: {
694
+ type: 'string',
695
+ description: 'Get resolved patterns for a specific ticket with inheritance chain and override analysis',
318
696
  },
319
697
  },
320
- required: ['specificationId'],
321
698
  },
322
699
  },
323
700
  // ========================================================================
@@ -325,7 +702,7 @@ export function getTools() {
325
702
  // ========================================================================
326
703
  {
327
704
  name: 'start_work_session',
328
- description: 'Start working on a ticket - validates dependencies are satisfied',
705
+ description: 'Start working on a ticket. Transitions ready -> active. Returns error with statusReason if ticket is pending (dependencies not met).',
329
706
  inputSchema: {
330
707
  type: 'object',
331
708
  properties: {
@@ -339,7 +716,15 @@ export function getTools() {
339
716
  },
340
717
  {
341
718
  name: 'complete_work_session',
342
- description: 'Mark a ticket as complete with summary and results',
719
+ description: 'Mark a ticket as complete. Transitions active -> done.\n\n' +
720
+ 'Automatically recalculates status for dependent tickets and returns cascade info.\n\n' +
721
+ 'By default, checks if summary addresses acceptance criteria and returns warnings (not errors) for gaps.\n\n' +
722
+ 'Use validated: true as a simple confirmation that the agent has verified all work meets criteria.\n\n' +
723
+ 'Optional validation flags can be provided to report test results inline:\n' +
724
+ '- tests: Unit/integration test results\n' +
725
+ '- lint: Linting results\n' +
726
+ '- typeCheck: TypeScript type checking\n' +
727
+ '- build: Build/compilation results',
343
728
  inputSchema: {
344
729
  type: 'object',
345
730
  properties: {
@@ -365,6 +750,48 @@ export function getTools() {
365
750
  type: 'number',
366
751
  description: 'Actual hours spent on the ticket',
367
752
  },
753
+ validated: {
754
+ type: 'boolean',
755
+ description: 'Simple validation confirmation. When true, indicates:\n' +
756
+ '- Agent has verified work meets acceptance criteria\n' +
757
+ '- All necessary tests/validations were run and passed\n' +
758
+ '- No detailed breakdown needed\n' +
759
+ 'Trust-based approach for streamlined completion.',
760
+ },
761
+ validation: {
762
+ type: 'object',
763
+ description: 'Detailed validation flags (optional with validated: true)',
764
+ properties: {
765
+ tests: {
766
+ type: 'string',
767
+ enum: ['passed', 'failed', 'skipped', 'na'],
768
+ description: 'Unit/integration test results',
769
+ },
770
+ lint: {
771
+ type: 'string',
772
+ enum: ['passed', 'failed', 'skipped', 'na'],
773
+ description: 'Linting results',
774
+ },
775
+ typeCheck: {
776
+ type: 'string',
777
+ enum: ['passed', 'failed', 'skipped', 'na'],
778
+ description: 'TypeScript type checking results',
779
+ },
780
+ build: {
781
+ type: 'string',
782
+ enum: ['passed', 'failed', 'skipped', 'na'],
783
+ description: 'Build/compilation results',
784
+ },
785
+ notes: {
786
+ type: 'string',
787
+ description: 'Additional validation notes',
788
+ },
789
+ },
790
+ },
791
+ skipCriteriaCheck: {
792
+ type: 'boolean',
793
+ description: 'Skip acceptance criteria validation. Use for trivial changes or when criteria check is not needed. Default: false',
794
+ },
368
795
  },
369
796
  required: ['ticketId', 'summary'],
370
797
  },
@@ -395,7 +822,7 @@ export function getTools() {
395
822
  },
396
823
  {
397
824
  name: 'get_work_summary',
398
- description: 'Get summary of completed work with optional scope filtering',
825
+ description: '[DEPRECATED: Use get_report with type="work"] Get summary of completed work with optional scope filtering',
399
826
  inputSchema: {
400
827
  type: 'object',
401
828
  properties: {
@@ -425,7 +852,7 @@ export function getTools() {
425
852
  // ========================================================================
426
853
  {
427
854
  name: 'report_test_results',
428
- description: 'Report test results for a ticket',
855
+ description: '[DEPRECATED] Report test results for a ticket. Use complete_work_session({ validation: { tests: "passed", ... } }) instead. Will be removed in v3.0.',
429
856
  inputSchema: {
430
857
  type: 'object',
431
858
  properties: {
@@ -480,7 +907,7 @@ export function getTools() {
480
907
  },
481
908
  {
482
909
  name: 'get_ticket_test_status',
483
- description: 'Get test status and history for a ticket',
910
+ description: "[DEPRECATED] Get test status and history for a ticket. Use get_ticket({ include: ['testStatus'] }) instead. Will be removed in v3.0.",
484
911
  inputSchema: {
485
912
  type: 'object',
486
913
  properties: {
@@ -494,7 +921,7 @@ export function getTools() {
494
921
  },
495
922
  {
496
923
  name: 'validate_ticket_completion',
497
- description: 'Validate that a ticket is ready to be marked complete',
924
+ description: '[DEPRECATED] Validate that a ticket is ready to be marked complete. Validation is now built into complete_work_session(). Will be removed in v3.0.',
498
925
  inputSchema: {
499
926
  type: 'object',
500
927
  properties: {
@@ -507,11 +934,11 @@ export function getTools() {
507
934
  },
508
935
  },
509
936
  // ========================================================================
510
- // Discovery Tools (Epic 7)
937
+ // Discovery Tools (Epic 7) - DEPRECATED: Use tag-based ticket operations instead
511
938
  // ========================================================================
512
939
  {
513
940
  name: 'report_discovery',
514
- description: 'Report a discovery (new requirement, bug, or improvement) found during implementation',
941
+ description: '[DEPRECATED: Use create_ticket with tags: ["discovery", "discovery:<type>"] instead] Report a discovery (new requirement, bug, or improvement) found during implementation',
515
942
  inputSchema: {
516
943
  type: 'object',
517
944
  properties: {
@@ -543,7 +970,7 @@ export function getTools() {
543
970
  },
544
971
  {
545
972
  name: 'get_pending_discoveries',
546
- description: 'Get pending discoveries that need resolution',
973
+ description: '[DEPRECATED: Use search_tickets with tags: ["discovery"] and status filter instead] Get pending discoveries that need resolution',
547
974
  inputSchema: {
548
975
  type: 'object',
549
976
  properties: {
@@ -561,7 +988,7 @@ export function getTools() {
561
988
  },
562
989
  {
563
990
  name: 'resolve_discovery',
564
- description: 'Resolve a discovery as created (new ticket) or dismissed',
991
+ description: '[DEPRECATED: Use update_ticket to mark done or remove discovery tag instead] Resolve a discovery as created (new ticket) or dismissed',
565
992
  inputSchema: {
566
993
  type: 'object',
567
994
  properties: {
@@ -617,9 +1044,56 @@ export function getTools() {
617
1044
  required: ['epicId'],
618
1045
  },
619
1046
  },
1047
+ {
1048
+ name: 'get_report',
1049
+ description: `Generate unified reports for implementation, time tracking, blockers, or work summary.
1050
+
1051
+ Report types:
1052
+ - 'implementation': Progress, velocity, completions (replaces get_implementation_summary)
1053
+ - 'time': Estimated vs actual hours (replaces get_time_report)
1054
+ - 'blockers': Blocked tickets with reasons (replaces get_blockers_report)
1055
+ - 'work': Completed work summary (replaces get_work_summary)
1056
+
1057
+ Format options:
1058
+ - 'json': Full structured data (default)
1059
+ - 'summary': Condensed text summary`,
1060
+ inputSchema: {
1061
+ type: 'object',
1062
+ properties: {
1063
+ type: {
1064
+ type: 'string',
1065
+ enum: ['implementation', 'time', 'blockers', 'work'],
1066
+ description: 'Type of report to generate',
1067
+ },
1068
+ scope: {
1069
+ type: 'string',
1070
+ enum: ['project', 'specification', 'epic'],
1071
+ description: 'Scope of the report',
1072
+ },
1073
+ scopeId: {
1074
+ type: 'string',
1075
+ description: 'ID of the scoped entity (projectId, specificationId, or epicId)',
1076
+ },
1077
+ format: {
1078
+ type: 'string',
1079
+ enum: ['json', 'toon', 'summary'],
1080
+ description: 'Output format: json (full structured), toon (~44% smaller), summary (key metrics only ~70% smaller). Default: json',
1081
+ },
1082
+ startDate: {
1083
+ type: 'string',
1084
+ description: 'Start date for work report (ISO 8601)',
1085
+ },
1086
+ endDate: {
1087
+ type: 'string',
1088
+ description: 'End date for work report (ISO 8601)',
1089
+ },
1090
+ },
1091
+ required: ['type', 'scope', 'scopeId'],
1092
+ },
1093
+ },
620
1094
  {
621
1095
  name: 'get_implementation_summary',
622
- description: 'Get cross-specification implementation summary with velocity metrics',
1096
+ description: '[DEPRECATED: Use get_report with type="implementation"] Get cross-specification implementation summary with velocity metrics',
623
1097
  inputSchema: {
624
1098
  type: 'object',
625
1099
  properties: {
@@ -633,7 +1107,7 @@ export function getTools() {
633
1107
  },
634
1108
  {
635
1109
  name: 'get_time_report',
636
- description: 'Get time tracking report with estimated vs actual analysis',
1110
+ description: '[DEPRECATED: Use get_report with type="time"] Get time tracking report with estimated vs actual analysis',
637
1111
  inputSchema: {
638
1112
  type: 'object',
639
1113
  properties: {
@@ -652,7 +1126,7 @@ export function getTools() {
652
1126
  },
653
1127
  {
654
1128
  name: 'get_blockers_report',
655
- description: 'Get report of all blockers with duration and grouping',
1129
+ description: '[DEPRECATED: Use get_report with type="blockers"] Get report of all blockers with duration and grouping',
656
1130
  inputSchema: {
657
1131
  type: 'object',
658
1132
  properties: {
@@ -673,37 +1147,124 @@ export function getTools() {
673
1147
  // ========================================================================
674
1148
  {
675
1149
  name: 'search_tickets',
676
- description: 'Full-text search across tickets with relevance ranking',
1150
+ description: `Unified ticket search with multiple filter options.
1151
+
1152
+ Combines:
1153
+ - Full-text search (query)
1154
+ - File matching (files) - replaces find_tickets_by_file
1155
+ - Tag filtering (tags) - replaces find_tickets_by_tag
1156
+ - Related tickets (relatedTo) - replaces find_related_tickets
1157
+ - Status, complexity, priority filters
1158
+
1159
+ At least one of: query, files, tags, or relatedTo is required.
1160
+ At least one scope filter (projectId, specificationId, or epicId) is required.`,
677
1161
  inputSchema: {
678
1162
  type: 'object',
679
1163
  properties: {
680
1164
  query: {
681
1165
  type: 'string',
682
- description: 'Search query',
1166
+ description: 'Full-text search query',
683
1167
  },
684
- specificationId: {
1168
+ files: {
1169
+ type: 'array',
1170
+ items: { type: 'string' },
1171
+ description: 'Glob patterns to match ticket files (e.g., "**/channel/*.ts")',
1172
+ },
1173
+ tags: {
1174
+ type: 'array',
1175
+ items: { type: 'string' },
1176
+ description: 'Tags to filter by',
1177
+ },
1178
+ matchAllTags: {
1179
+ type: 'boolean',
1180
+ description: 'If true, match all tags (AND). If false, match any (OR). Default: false',
1181
+ },
1182
+ relatedTo: {
685
1183
  type: 'string',
686
- description: 'Limit search to specification (optional)',
1184
+ description: 'Find tickets related to this ticket ID (by tags, files, tech stack)',
1185
+ },
1186
+ status: {
1187
+ type: 'array',
1188
+ items: {
1189
+ type: 'string',
1190
+ enum: ['pending', 'ready', 'active', 'done'],
1191
+ },
1192
+ description: 'Filter by status',
1193
+ },
1194
+ complexity: {
1195
+ type: 'array',
1196
+ items: {
1197
+ type: 'string',
1198
+ enum: ['small', 'medium', 'large', 'xlarge'],
1199
+ },
1200
+ description: 'Filter by complexity',
1201
+ },
1202
+ priority: {
1203
+ type: 'array',
1204
+ items: {
1205
+ type: 'string',
1206
+ enum: ['low', 'medium', 'high', 'critical'],
1207
+ },
1208
+ description: 'Filter by priority',
687
1209
  },
688
1210
  projectId: {
689
1211
  type: 'string',
690
- description: 'Limit search to project (optional)',
1212
+ description: 'Limit search to project',
1213
+ },
1214
+ specificationId: {
1215
+ type: 'string',
1216
+ description: 'Limit search to specification',
1217
+ },
1218
+ epicId: {
1219
+ type: 'string',
1220
+ description: 'Limit search to epic',
691
1221
  },
692
1222
  limit: {
693
1223
  type: 'number',
694
- description: 'Maximum results (default: 20)',
1224
+ description: 'Maximum results (default: 20, max: 100)',
695
1225
  },
696
1226
  offset: {
697
1227
  type: 'number',
698
1228
  description: 'Pagination offset (default: 0)',
699
1229
  },
1230
+ fields: {
1231
+ type: 'array',
1232
+ items: {
1233
+ type: 'string',
1234
+ enum: [
1235
+ 'id',
1236
+ 'epicId',
1237
+ 'ticketNumber',
1238
+ 'title',
1239
+ 'description',
1240
+ 'status',
1241
+ 'priority',
1242
+ 'complexity',
1243
+ 'estimatedHours',
1244
+ 'actualHours',
1245
+ 'acceptanceCriteria',
1246
+ 'implementation',
1247
+ 'technicalDetails',
1248
+ 'notes',
1249
+ 'tags',
1250
+ 'blockReason',
1251
+ 'progress',
1252
+ 'testsPassed',
1253
+ 'order',
1254
+ 'startedAt',
1255
+ 'completedAt',
1256
+ 'createdAt',
1257
+ 'updatedAt',
1258
+ ],
1259
+ },
1260
+ description: 'Select specific fields to return. Returns all fields if not specified. id is always included. Example: ["id", "ticketNumber", "title", "status"] for minimal results.',
1261
+ },
700
1262
  },
701
- required: ['query'],
702
1263
  },
703
1264
  },
704
1265
  {
705
1266
  name: 'find_tickets_by_file',
706
- description: 'Find tickets that modify or create specific files (supports glob patterns)',
1267
+ description: '[DEPRECATED: Use search_tickets with files parameter] Find tickets that modify or create specific files (supports glob patterns)',
707
1268
  inputSchema: {
708
1269
  type: 'object',
709
1270
  properties: {
@@ -725,7 +1286,7 @@ export function getTools() {
725
1286
  },
726
1287
  {
727
1288
  name: 'find_tickets_by_tag',
728
- description: 'Find tickets by tags with AND/OR logic',
1289
+ description: '[DEPRECATED: Use search_tickets with tags parameter] Find tickets by tags with AND/OR logic',
729
1290
  inputSchema: {
730
1291
  type: 'object',
731
1292
  properties: {
@@ -752,7 +1313,7 @@ export function getTools() {
752
1313
  },
753
1314
  {
754
1315
  name: 'find_related_tickets',
755
- description: 'Find tickets related to a given ticket based on files, tags, and tech stack',
1316
+ description: '[DEPRECATED: Use search_tickets with relatedTo parameter] Find tickets related to a given ticket based on files, tags, and tech stack',
756
1317
  inputSchema: {
757
1318
  type: 'object',
758
1319
  properties: {
@@ -837,7 +1398,7 @@ export function getTools() {
837
1398
  },
838
1399
  {
839
1400
  name: 'get_ticket_commits',
840
- description: 'Get all commits linked to a ticket',
1401
+ description: "[DEPRECATED: Use get_ticket with include: ['commits'] instead] Get all commits linked to a ticket",
841
1402
  inputSchema: {
842
1403
  type: 'object',
843
1404
  properties: {
@@ -859,7 +1420,7 @@ export function getTools() {
859
1420
  },
860
1421
  {
861
1422
  name: 'get_ticket_prs',
862
- description: 'Get all pull requests linked to a ticket',
1423
+ description: "[DEPRECATED: Use get_ticket with include: ['prs'] instead] Get all pull requests linked to a ticket",
863
1424
  inputSchema: {
864
1425
  type: 'object',
865
1426
  properties: {
@@ -1027,6 +1588,48 @@ export function getTools() {
1027
1588
  required: ['title', 'description', 'objective'],
1028
1589
  },
1029
1590
  },
1591
+ // Pattern inheritance fields (specification level)
1592
+ codeStandards: {
1593
+ type: 'object',
1594
+ description: 'Code standards inherited by all epics/tickets',
1595
+ properties: {
1596
+ language: { type: 'string', description: 'Primary language (e.g., "TypeScript")' },
1597
+ asyncPattern: { type: 'string', description: 'Async pattern (e.g., "async/await")' },
1598
+ errorHandling: { type: 'string', description: 'Error handling approach' },
1599
+ documentation: { type: 'string', description: 'Documentation style' },
1600
+ testing: { type: 'string', description: 'Testing approach' },
1601
+ naming: {
1602
+ type: 'object',
1603
+ description: 'Naming conventions',
1604
+ properties: {
1605
+ files: { type: 'string', description: 'File naming convention' },
1606
+ functions: { type: 'string', description: 'Function naming convention' },
1607
+ interfaces: { type: 'string', description: 'Interface naming convention' },
1608
+ actions: { type: 'string', description: 'Action naming convention' },
1609
+ },
1610
+ },
1611
+ },
1612
+ },
1613
+ commonImports: {
1614
+ type: 'array',
1615
+ items: { type: 'string' },
1616
+ description: 'Common imports inherited by all epics/tickets',
1617
+ },
1618
+ returnTypes: {
1619
+ type: 'object',
1620
+ description: 'Standard return types for CRUD operations',
1621
+ properties: {
1622
+ create: { type: 'string', description: 'Return type for create operations' },
1623
+ update: { type: 'string', description: 'Return type for update operations' },
1624
+ delete: { type: 'string', description: 'Return type for delete operations' },
1625
+ list: { type: 'string', description: 'Return type for list operations' },
1626
+ },
1627
+ },
1628
+ responseMode: {
1629
+ type: 'string',
1630
+ enum: ['full', 'minimal', 'id-only'],
1631
+ description: 'Response verbosity: full (default, ~500 tokens), minimal (~50 tokens), id-only (~20 tokens)',
1632
+ },
1030
1633
  },
1031
1634
  required: ['projectId', 'title'],
1032
1635
  },
@@ -1133,7 +1736,7 @@ export function getTools() {
1133
1736
  },
1134
1737
  tickets: {
1135
1738
  type: 'array',
1136
- description: 'Nested tickets to create (bulk creation)',
1739
+ description: 'Nested tickets to create (bulk creation) with optional inline dependencies',
1137
1740
  items: {
1138
1741
  type: 'object',
1139
1742
  properties: {
@@ -1142,17 +1745,57 @@ export function getTools() {
1142
1745
  acceptanceCriteria: { type: 'array', items: { type: 'string' } },
1143
1746
  estimatedHours: { type: 'number' },
1144
1747
  complexity: { type: 'string', enum: ['small', 'medium', 'large', 'xlarge'] },
1748
+ priority: { type: 'string', enum: ['high', 'medium', 'low'] },
1749
+ implementation: { type: 'object', description: 'Implementation details' },
1750
+ technicalDetails: { type: 'object', description: 'Technical details' },
1751
+ tags: { type: 'array', items: { type: 'string' } },
1752
+ dependsOn: {
1753
+ type: 'array',
1754
+ items: { type: 'number' },
1755
+ description: 'Indexes of other tickets in this array that this ticket depends on',
1756
+ },
1145
1757
  },
1146
1758
  required: ['title'],
1147
1759
  },
1148
1760
  },
1761
+ // Pattern inheritance fields (epic level)
1762
+ sharedPatterns: {
1763
+ type: 'object',
1764
+ description: 'Patterns shared by all tickets in this epic',
1765
+ properties: {
1766
+ errorHandling: { type: 'string', description: 'Override error handling for this epic' },
1767
+ returnType: { type: 'string', description: 'Epic-specific return type' },
1768
+ actionPrefix: { type: 'string', description: 'Redux/action prefix for this epic' },
1769
+ stateSlice: { type: 'string', description: 'State slice path for this epic' },
1770
+ },
1771
+ },
1772
+ additionalImports: {
1773
+ type: 'array',
1774
+ items: { type: 'string' },
1775
+ description: 'Additional imports for tickets in this epic (added to spec-level imports)',
1776
+ },
1777
+ commonFiles: {
1778
+ type: 'object',
1779
+ description: 'Common file paths for this epic',
1780
+ properties: {
1781
+ reducer: { type: 'string', description: 'Reducer file path' },
1782
+ actions: { type: 'string', description: 'Actions file path' },
1783
+ types: { type: 'string', description: 'Types file path' },
1784
+ index: { type: 'string', description: 'Index/barrel file path' },
1785
+ },
1786
+ },
1787
+ responseMode: {
1788
+ type: 'string',
1789
+ enum: ['full', 'minimal', 'id-only'],
1790
+ description: 'Response verbosity: full (default, ~500 tokens), minimal (~50 tokens), id-only (~20 tokens)',
1791
+ },
1149
1792
  },
1150
1793
  required: ['specificationId', 'title', 'description', 'objective'],
1151
1794
  },
1152
1795
  },
1153
1796
  {
1154
1797
  name: 'create_ticket',
1155
- description: 'Create a new ticket in an epic',
1798
+ description: 'Create a new ticket in an epic. Returns validation warnings based on complexity level.',
1156
1799
  inputSchema: {
1157
1800
  type: 'object',
1158
1801
  properties: {
@@ -1213,6 +1856,20 @@ export function getTools() {
1213
1856
  items: { type: 'string' },
1214
1857
  description: 'Array of ticket IDs this ticket depends on',
1215
1858
  },
1859
+ skipValidation: {
1860
+ type: 'boolean',
1861
+ description: 'Skip validation and completeness scoring. Default: false',
1862
+ },
1863
+ validationStrictness: {
1864
+ type: 'string',
1865
+ enum: ['lenient', 'normal', 'strict'],
1866
+ description: 'Warning strictness level. lenient=warnings only, normal=warnings+recommendations, strict=all. Default: normal',
1867
+ },
1868
+ responseMode: {
1869
+ type: 'string',
1870
+ enum: ['full', 'minimal', 'id-only'],
1871
+ description: 'Response verbosity: full (default, ~500 tokens), minimal (~50 tokens), id-only (~20 tokens)',
1872
+ },
1216
1873
  },
1217
1874
  required: ['epicId', 'title'],
1218
1875
  },
@@ -1248,7 +1905,9 @@ export function getTools() {
1248
1905
  // ========================================================================
1249
1906
  {
1250
1907
  name: 'update_ticket',
1251
- description: "Update a ticket's properties (status, description, estimate, etc.)",
1908
+ description: "Update a ticket's properties (status, description, estimate, etc.).\n\n" +
1909
+ 'When status changes, counts are automatically propagated up the hierarchy ' +
1910
+ '(Epic → Specification → Project).',
1252
1911
  inputSchema: {
1253
1912
  type: 'object',
1254
1913
  properties: {
@@ -1266,8 +1925,8 @@ export function getTools() {
1266
1925
  },
1267
1926
  status: {
1268
1927
  type: 'string',
1269
- enum: ['todo', 'queue', 'in_progress', 'blocked', 'done'],
1270
- description: 'New status (optional)',
1928
+ enum: ['pending', 'ready', 'active', 'done'],
1929
+ description: 'Ticket status. pending/ready may be recalculated based on dependencies.',
1271
1930
  },
1272
1931
  priority: {
1273
1932
  type: 'string',
@@ -1281,7 +1940,7 @@ export function getTools() {
1281
1940
  },
1282
1941
  blockReason: {
1283
1942
  type: 'string',
1284
- description: 'Reason why ticket is blocked (optional, typically used when status is blocked)',
1943
+ description: 'External block reason. Forces status to pending until cleared.',
1285
1944
  },
1286
1945
  notes: {
1287
1946
  type: 'string',
@@ -1309,6 +1968,22 @@ export function getTools() {
1309
1968
  type: 'object',
1310
1969
  description: 'Technical details like stack, endpoints (JSON) (optional)',
1311
1970
  },
1971
+ skipValidation: {
1972
+ type: 'boolean',
1973
+ description: 'Skip validation and completeness scoring (default: false)',
1974
+ default: false,
1975
+ },
1976
+ validationStrictness: {
1977
+ type: 'string',
1978
+ enum: ['lenient', 'normal', 'strict'],
1979
+ description: 'Warning strictness level (default: normal)',
1980
+ default: 'normal',
1981
+ },
1982
+ responseMode: {
1983
+ type: 'string',
1984
+ enum: ['full', 'minimal', 'id-only'],
1985
+ description: 'Response verbosity: full (default, ~500 tokens), minimal (~50 tokens), id-only (~20 tokens)',
1986
+ },
1312
1987
  },
1313
1988
  required: ['ticketId'],
1314
1989
  },
@@ -1410,6 +2085,37 @@ export function getTools() {
1410
2085
  enum: ['low', 'medium', 'high', 'critical'],
1411
2086
  description: 'Priority level (optional)',
1412
2087
  },
2088
+ // Pattern inheritance fields (epic level)
2089
+ sharedPatterns: {
2090
+ type: 'object',
2091
+ description: 'Patterns shared by all tickets in this epic (optional)',
2092
+ properties: {
2093
+ errorHandling: { type: 'string', description: 'Override error handling for this epic' },
2094
+ returnType: { type: 'string', description: 'Epic-specific return type' },
2095
+ actionPrefix: { type: 'string', description: 'Redux/action prefix for this epic' },
2096
+ stateSlice: { type: 'string', description: 'State slice path for this epic' },
2097
+ },
2098
+ },
2099
+ additionalImports: {
2100
+ type: 'array',
2101
+ items: { type: 'string' },
2102
+ description: 'Additional imports for tickets in this epic (optional)',
2103
+ },
2104
+ commonFiles: {
2105
+ type: 'object',
2106
+ description: 'Common file paths for this epic (optional)',
2107
+ properties: {
2108
+ reducer: { type: 'string', description: 'Reducer file path' },
2109
+ actions: { type: 'string', description: 'Actions file path' },
2110
+ types: { type: 'string', description: 'Types file path' },
2111
+ index: { type: 'string', description: 'Index/barrel file path' },
2112
+ },
2113
+ },
2114
+ responseMode: {
2115
+ type: 'string',
2116
+ enum: ['full', 'minimal', 'id-only'],
2117
+ description: 'Response verbosity: full (default, ~500 tokens), minimal (~50 tokens), id-only (~20 tokens)',
2118
+ },
1413
2119
  },
1414
2120
  required: ['epicId'],
1415
2121
  },
@@ -1555,6 +2261,48 @@ export function getTools() {
1555
2261
  type: 'string',
1556
2262
  description: 'Output directory for build artifacts (optional)',
1557
2263
  },
2264
+ // Pattern inheritance fields (specification level)
2265
+ codeStandards: {
2266
+ type: 'object',
2267
+ description: 'Code standards inherited by all epics/tickets (optional)',
2268
+ properties: {
2269
+ language: { type: 'string', description: 'Primary language (e.g., "TypeScript")' },
2270
+ asyncPattern: { type: 'string', description: 'Async pattern (e.g., "async/await")' },
2271
+ errorHandling: { type: 'string', description: 'Error handling approach' },
2272
+ documentation: { type: 'string', description: 'Documentation style' },
2273
+ testing: { type: 'string', description: 'Testing approach' },
2274
+ naming: {
2275
+ type: 'object',
2276
+ description: 'Naming conventions',
2277
+ properties: {
2278
+ files: { type: 'string', description: 'File naming convention' },
2279
+ functions: { type: 'string', description: 'Function naming convention' },
2280
+ interfaces: { type: 'string', description: 'Interface naming convention' },
2281
+ actions: { type: 'string', description: 'Action naming convention' },
2282
+ },
2283
+ },
2284
+ },
2285
+ },
2286
+ commonImports: {
2287
+ type: 'array',
2288
+ items: { type: 'string' },
2289
+ description: 'Common imports inherited by all epics/tickets (optional)',
2290
+ },
2291
+ returnTypes: {
2292
+ type: 'object',
2293
+ description: 'Standard return types for CRUD operations (optional)',
2294
+ properties: {
2295
+ create: { type: 'string', description: 'Return type for create operations' },
2296
+ update: { type: 'string', description: 'Return type for update operations' },
2297
+ delete: { type: 'string', description: 'Return type for delete operations' },
2298
+ list: { type: 'string', description: 'Return type for list operations' },
2299
+ },
2300
+ },
2301
+ responseMode: {
2302
+ type: 'string',
2303
+ enum: ['full', 'minimal', 'id-only'],
2304
+ description: 'Response verbosity: full (default, ~500 tokens), minimal (~50 tokens), id-only (~20 tokens)',
2305
+ },
1558
2306
  },
1559
2307
  required: ['specificationId'],
1560
2308
  },
@@ -1648,7 +2396,7 @@ export function getTools() {
1648
2396
  },
1649
2397
  {
1650
2398
  name: 'get_session',
1651
- description: 'Get session by ID or get active session for specification',
2399
+ description: "[DEPRECATED: Use get_specification with include: ['session'] instead] Get session by ID or get active session for specification",
1652
2400
  inputSchema: {
1653
2401
  type: 'object',
1654
2402
  properties: {
@@ -1670,7 +2418,7 @@ export function getTools() {
1670
2418
  },
1671
2419
  {
1672
2420
  name: 'update_session',
1673
- description: 'Update session state (pause/resume, update config, add notes)',
2421
+ description: '[DEPRECATED: Use end_session() then create_session() with new config instead] Update session state (pause/resume, update config, add notes)',
1674
2422
  inputSchema: {
1675
2423
  type: 'object',
1676
2424
  properties: {
@@ -1701,7 +2449,7 @@ export function getTools() {
1701
2449
  },
1702
2450
  {
1703
2451
  name: 'record_session_ticket',
1704
- description: 'Record a ticket completion/failure within a session. Returns next actionable ticket if available.',
2452
+ description: '[DEPRECATED: Use complete_work_session() which auto-records in active session] Record a ticket completion/failure within a session. Returns next actionable ticket if available.',
1705
2453
  inputSchema: {
1706
2454
  type: 'object',
1707
2455
  properties: {
@@ -1759,7 +2507,7 @@ export function getTools() {
1759
2507
  },
1760
2508
  {
1761
2509
  name: 'list_sessions',
1762
- description: 'List sessions with filtering by specification, project, or status',
2510
+ description: "[DEPRECATED: Use get_report({ type: 'work' }) for work summaries] List sessions with filtering by specification, project, or status",
1763
2511
  inputSchema: {
1764
2512
  type: 'object',
1765
2513
  properties: {
@@ -1794,6 +2542,147 @@ export function getTools() {
1794
2542
  // ========================================================================
1795
2543
  // Bulk Operations
1796
2544
  // ========================================================================
2545
+ {
2546
+ name: 'bulk_create_tickets',
2547
+ description: 'Create multiple tickets in an epic with optional inline dependencies in a single atomic transaction. Reduces 15+ API calls to one.',
2548
+ inputSchema: {
2549
+ type: 'object',
2550
+ properties: {
2551
+ epicId: {
2552
+ type: 'string',
2553
+ description: 'The ID of the epic to create tickets in',
2554
+ },
2555
+ tickets: {
2556
+ type: 'array',
2557
+ description: 'Array of ticket definitions to create',
2558
+ items: {
2559
+ type: 'object',
2560
+ properties: {
2561
+ title: { type: 'string', description: 'Ticket title (required)' },
2562
+ description: { type: 'string', description: 'Ticket description' },
2563
+ estimatedHours: { type: 'number', description: 'Estimated hours' },
2564
+ complexity: {
2565
+ type: 'string',
2566
+ enum: ['small', 'medium', 'large', 'xlarge'],
2567
+ description: 'Complexity level',
2568
+ },
2569
+ priority: {
2570
+ type: 'string',
2571
+ enum: ['high', 'medium', 'low'],
2572
+ description: 'Priority level',
2573
+ },
2574
+ acceptanceCriteria: {
2575
+ type: 'array',
2576
+ items: { type: 'string' },
2577
+ description: 'Acceptance criteria',
2578
+ },
2579
+ implementation: {
2580
+ type: 'object',
2581
+ description: 'Implementation details (filesToCreate, filesToModify, steps, etc.)',
2582
+ },
2583
+ technicalDetails: {
2584
+ type: 'object',
2585
+ description: 'Technical details (files, stack, endpoints, etc.)',
2586
+ },
2587
+ tags: {
2588
+ type: 'array',
2589
+ items: { type: 'string' },
2590
+ description: 'Tags',
2591
+ },
2592
+ },
2593
+ required: ['title'],
2594
+ },
2595
+ },
2596
+ dependencies: {
2597
+ type: 'array',
2598
+ description: 'Optional inline dependencies using array indexes',
2599
+ items: {
2600
+ type: 'object',
2601
+ properties: {
2602
+ ticket: {
2603
+ type: 'number',
2604
+ description: 'Index of the ticket in the tickets array',
2605
+ },
2606
+ dependsOn: {
2607
+ oneOf: [
2608
+ { type: 'number', description: 'Index of the ticket it depends on' },
2609
+ {
2610
+ type: 'array',
2611
+ items: { type: 'number' },
2612
+ description: 'Indexes of the tickets it depends on',
2613
+ },
2614
+ ],
2615
+ description: 'Index(es) of the ticket(s) it depends on',
2616
+ },
2617
+ },
2618
+ required: ['ticket', 'dependsOn'],
2619
+ },
2620
+ },
2621
+ onError: {
2622
+ type: 'string',
2623
+ enum: ['rollback', 'continue'],
2624
+ description: "Error handling mode: 'rollback' stops and undoes all, 'continue' skips failures (default: 'continue')",
2625
+ },
2626
+ },
2627
+ required: ['epicId', 'tickets'],
2628
+ },
2629
+ },
2630
+ {
2631
+ name: 'get_job_status',
2632
+ description: 'Get the status of an asynchronous bulk operation job. For batches >= 20 items, bulk operations return a job ID for async processing.',
2633
+ inputSchema: {
2634
+ type: 'object',
2635
+ properties: {
2636
+ jobId: {
2637
+ type: 'string',
2638
+ description: 'The job ID returned from a bulk operation',
2639
+ },
2640
+ },
2641
+ required: ['jobId'],
2642
+ },
2643
+ },
2644
+ {
2645
+ name: 'bulk_add_dependencies',
2646
+ description: 'Add multiple dependencies between tickets in a single atomic transaction.',
2647
+ inputSchema: {
2648
+ type: 'object',
2649
+ properties: {
2650
+ dependencies: {
2651
+ type: 'array',
2652
+ description: 'Array of dependency definitions',
2653
+ items: {
2654
+ type: 'object',
2655
+ properties: {
2656
+ ticketId: {
2657
+ type: 'string',
2658
+ description: 'ID of the ticket that will depend on another',
2659
+ },
2660
+ dependsOnId: {
2661
+ type: 'string',
2662
+ description: 'ID of the ticket it will depend on',
2663
+ },
2664
+ type: {
2665
+ type: 'string',
2666
+ enum: ['blocks', 'requires'],
2667
+ description: "Type of dependency (default: 'requires')",
2668
+ },
2669
+ },
2670
+ required: ['ticketId', 'dependsOnId'],
2671
+ },
2672
+ },
2673
+ skipCircularCheck: {
2674
+ type: 'boolean',
2675
+ description: 'Skip validation of circular dependencies (default: false)',
2676
+ },
2677
+ onError: {
2678
+ type: 'string',
2679
+ enum: ['rollback', 'continue'],
2680
+ description: "Error handling mode: 'rollback' stops and undoes all, 'continue' skips failures (default: 'continue')",
2681
+ },
2682
+ },
2683
+ required: ['dependencies'],
2684
+ },
2685
+ },
1797
2686
  {
1798
2687
  name: 'bulk_update_tickets',
1799
2688
  description: 'Update multiple tickets in a single atomic operation. All updates succeed or all fail (unless atomic: false).',
@@ -1811,8 +2700,8 @@ export function getTools() {
1811
2700
  },
1812
2701
  status: {
1813
2702
  type: 'string',
1814
- enum: ['todo', 'queue', 'in_progress', 'blocked', 'done'],
1815
- description: 'New status',
2703
+ enum: ['pending', 'ready', 'active', 'done'],
2704
+ description: 'Ticket status. Completing (done) triggers cascade to unblock dependents.',
1816
2705
  },
1817
2706
  priority: {
1818
2707
  type: 'string',
@@ -1827,7 +2716,7 @@ export function getTools() {
1827
2716
  },
1828
2717
  blockReason: {
1829
2718
  type: 'string',
1830
- description: 'Reason for blocking (when status is blocked)',
2719
+ description: 'External block reason. Forces status to pending until cleared.',
1831
2720
  },
1832
2721
  },
1833
2722
  required: ['ticketId'],
@@ -1842,13 +2731,21 @@ export function getTools() {
1842
2731
  type: 'boolean',
1843
2732
  description: 'All-or-nothing updates. If false, partial success allowed (default: true)',
1844
2733
  },
2734
+ responseMode: {
2735
+ type: 'string',
2736
+ enum: ['full', 'minimal', 'id-only'],
2737
+ description: `Response verbosity:
2738
+ - full: Complete updated tickets (default, ~500 tokens per ticket)
2739
+ - minimal: id, title, status per ticket (~50 tokens total)
2740
+ - id-only: Array of updated ticket IDs only (~20 tokens total)`,
2741
+ },
1845
2742
  },
1846
2743
  required: ['updates'],
1847
2744
  },
1848
2745
  },
1849
2746
  {
1850
2747
  name: 'reset_tickets',
1851
- description: 'Reset tickets to todo/queue status. By default excludes completed tickets - use includeCompleted: true to also reset done tickets.',
2748
+ description: 'Reset tickets to pending/ready status (calculated from dependencies). Returns statusCalculation showing how many became pending vs ready.',
1852
2749
  inputSchema: {
1853
2750
  type: 'object',
1854
2751
  properties: {
@@ -1873,11 +2770,6 @@ export function getTools() {
1873
2770
  type: 'boolean',
1874
2771
  description: 'Reset all tickets in the specification',
1875
2772
  },
1876
- targetStatus: {
1877
- type: 'string',
1878
- enum: ['todo', 'queue'],
1879
- description: 'Status to reset tickets to (default: todo)',
1880
- },
1881
2773
  resetDependents: {
1882
2774
  type: 'boolean',
1883
2775
  description: 'Also reset tickets that depend on the specified tickets (default: false)',
@@ -1898,7 +2790,47 @@ export function getTools() {
1898
2790
  required: ['specificationId'],
1899
2791
  },
1900
2792
  },
2793
+ // ========================================================================
2794
+ // Validation Operations (MCI-075)
2795
+ // ========================================================================
2796
+ {
2797
+ name: 'validate_counts',
2798
+ description: 'Validate denormalized counts across the hierarchy. ' +
2799
+ 'Compares stored counts with actual data and optionally repairs discrepancies.\n\n' +
2800
+ 'Use this tool to:\n' +
2801
+ '- Diagnose count inconsistencies\n' +
2802
+ '- Repair counts after data migrations\n' +
2803
+ '- Verify counts before bulk operations',
2804
+ inputSchema: {
2805
+ type: 'object',
2806
+ properties: {
2807
+ projectId: {
2808
+ type: 'string',
2809
+ description: 'Validate all counts within a project',
2810
+ },
2811
+ specificationId: {
2812
+ type: 'string',
2813
+ description: 'Validate all counts within a specification',
2814
+ },
2815
+ epicId: {
2816
+ type: 'string',
2817
+ description: 'Validate counts for a single epic',
2818
+ },
2819
+ repair: {
2820
+ type: 'boolean',
2821
+ description: 'If true, fix any issues found. Default: false (dry run)',
2822
+ },
2823
+ verbose: {
2824
+ type: 'boolean',
2825
+ description: 'Include detailed breakdown in response',
2826
+ },
2827
+ },
2828
+ required: [],
2829
+ },
2830
+ },
1901
2831
  ];
2832
+ // Apply format parameter to all read operations
2833
+ return tools.map(tool => READ_TOOL_NAMES.has(tool.name) ? addFormatParameter(tool) : tool);
1902
2834
  }
1903
2835
  /**
1904
2836
  * Retry configuration for transient failures
@@ -1986,6 +2918,12 @@ export function createToolHandlers(apiClient) {
1986
2918
  projectId: args.projectId,
1987
2919
  });
1988
2920
  },
2921
+ lookup_project: async (_client, args) => {
2922
+ validateRequired(args, 'name');
2923
+ return await apiClient.call('lookup_project', {
2924
+ name: args.name,
2925
+ });
2926
+ },
1989
2927
  // ========================================================================
1990
2928
  // Core Operations - Specifications
1991
2929
  // ========================================================================
@@ -1994,12 +2932,24 @@ export function createToolHandlers(apiClient) {
1994
2932
  return await apiClient.call('list_specifications', {
1995
2933
  projectId: args.projectId,
1996
2934
  status: args.status,
2935
+ limit: args.limit,
2936
+ offset: args.offset,
2937
+ fields: args.fields,
1997
2938
  });
1998
2939
  },
1999
2940
  get_specification: async (_client, args) => {
2000
2941
  validateRequired(args, 'specificationId');
2001
2942
  return await apiClient.call('get_specification', {
2002
2943
  specificationId: args.specificationId,
2944
+ summary: args.summary,
2945
+ include: args.include,
2946
+ });
2947
+ },
2948
+ lookup_specification: async (_client, args) => {
2949
+ validateRequired(args, 'projectId', 'title');
2950
+ return await apiClient.call('lookup_specification', {
2951
+ projectId: args.projectId,
2952
+ title: args.title,
2003
2953
  });
2004
2954
  },
2005
2955
  // ========================================================================
@@ -2010,12 +2960,29 @@ export function createToolHandlers(apiClient) {
2010
2960
  return await apiClient.call('list_epics', {
2011
2961
  specificationId: args.specificationId,
2012
2962
  status: args.status,
2963
+ limit: args.limit,
2964
+ offset: args.offset,
2965
+ fields: args.fields,
2013
2966
  });
2014
2967
  },
2015
2968
  get_epic: async (_client, args) => {
2016
2969
  validateRequired(args, 'epicId');
2017
2970
  return await apiClient.call('get_epic', {
2018
2971
  epicId: args.epicId,
2972
+ summary: args.summary,
2973
+ include: args.include,
2974
+ });
2975
+ },
2976
+ lookup_epic: async (_client, args) => {
2977
+ validateRequired(args, 'specificationId');
2978
+ // Must provide either title or number
2979
+ if (!args.title && args.number === undefined) {
2980
+ throw new Error("Must provide either 'title' or 'number'");
2981
+ }
2982
+ return await apiClient.call('lookup_epic', {
2983
+ specificationId: args.specificationId,
2984
+ title: args.title,
2985
+ number: args.number,
2019
2986
  });
2020
2987
  },
2021
2988
  // ========================================================================
@@ -2026,12 +2993,53 @@ export function createToolHandlers(apiClient) {
2026
2993
  return await apiClient.call('list_tickets', {
2027
2994
  epicId: args.epicId,
2028
2995
  status: args.status,
2996
+ limit: args.limit,
2997
+ offset: args.offset,
2998
+ include: args.include,
2999
+ fields: args.fields,
2029
3000
  });
2030
3001
  },
2031
3002
  get_ticket: async (_client, args) => {
2032
3003
  validateRequired(args, 'ticketId');
2033
3004
  return await apiClient.call('get_ticket', {
2034
3005
  ticketId: args.ticketId,
3006
+ summary: args.summary,
3007
+ include: args.include,
3008
+ });
3009
+ },
3010
+ lookup_ticket: async (_client, args) => {
3011
+ validateRequired(args, 'epicId');
3012
+ // Must provide either title or number
3013
+ if (!args.title && args.number === undefined) {
3014
+ throw new Error("Must provide either 'title' or 'number'");
3015
+ }
3016
+ return await apiClient.call('lookup_ticket', {
3017
+ epicId: args.epicId,
3018
+ title: args.title,
3019
+ number: args.number,
3020
+ });
3021
+ },
3022
+ lookup_tickets_by_status: async (_client, args) => {
3023
+ validateRequired(args, 'ticketIds');
3024
+ if (!Array.isArray(args.ticketIds)) {
3025
+ throw new Error('ticketIds must be an array');
3026
+ }
3027
+ const MAX_BATCH_SIZE = 50;
3028
+ if (args.ticketIds.length > MAX_BATCH_SIZE) {
3029
+ throw new Error(`Maximum ${MAX_BATCH_SIZE} tickets per call`);
3030
+ }
3031
+ // Empty array returns empty result
3032
+ if (args.ticketIds.length === 0) {
3033
+ return { tickets: [], notFound: [] };
3034
+ }
3035
+ // Validate all IDs are strings
3036
+ for (let i = 0; i < args.ticketIds.length; i++) {
3037
+ if (typeof args.ticketIds[i] !== 'string') {
3038
+ throw new Error(`ticketIds[${i}] must be a string`);
3039
+ }
3040
+ }
3041
+ return await apiClient.call('lookup_tickets_by_status', {
3042
+ ticketIds: args.ticketIds,
2035
3043
  });
2036
3044
  },
2037
3045
  // ========================================================================
@@ -2074,9 +3082,52 @@ export function createToolHandlers(apiClient) {
2074
3082
  // ========================================================================
2075
3083
  get_implementation_context: async (_client, args) => {
2076
3084
  validateRequired(args, 'ticketId');
2077
- return await apiClient.call('get_implementation_context', {
3085
+ const depth = args.depth || 'standard';
3086
+ // Fetch the implementation context from API with depth parameter
3087
+ const context = await apiClient.call('get_implementation_context', {
2078
3088
  ticketId: args.ticketId,
3089
+ depth,
2079
3090
  });
3091
+ // Only resolve inherited patterns for 'full' depth (minimal/standard don't include them)
3092
+ if (depth === 'full' && context.specification && context.epic) {
3093
+ const spec = context.specification;
3094
+ const epic = context.epic;
3095
+ const ticket = context.ticket;
3096
+ try {
3097
+ const resolvedPatterns = resolvePatternsWithCache({
3098
+ ticketId: args.ticketId,
3099
+ specification: {
3100
+ id: spec.id,
3101
+ codeStandards: spec.codeStandards,
3102
+ commonImports: spec.commonImports,
3103
+ returnTypes: spec.returnTypes,
3104
+ },
3105
+ epic: {
3106
+ id: epic.id,
3107
+ sharedPatterns: epic.sharedPatterns,
3108
+ additionalImports: epic.additionalImports,
3109
+ commonFiles: epic.commonFiles,
3110
+ },
3111
+ ticket: ticket ? {
3112
+ id: ticket.id,
3113
+ technicalDetails: ticket.technicalDetails,
3114
+ } : undefined,
3115
+ });
3116
+ // Add inherited patterns to the response
3117
+ context.inheritedPatterns = {
3118
+ imports: resolvedPatterns.imports,
3119
+ patterns: resolvedPatterns.patterns,
3120
+ returnTypes: resolvedPatterns.returnTypes,
3121
+ commonFiles: resolvedPatterns.commonFiles,
3122
+ naming: resolvedPatterns.naming,
3123
+ };
3124
+ }
3125
+ catch {
3126
+ // If pattern resolution fails, return context without inherited patterns
3127
+ // This maintains backward compatibility
3128
+ }
3129
+ }
3130
+ return context;
2080
3131
  },
2081
3132
  get_next_actionable_tickets: async (_client, args) => {
2082
3133
  // Either specificationId or projectId is required
@@ -2102,10 +3153,130 @@ export function createToolHandlers(apiClient) {
2102
3153
  });
2103
3154
  },
2104
3155
  get_patterns: async (_client, args) => {
2105
- validateRequired(args, 'specificationId');
2106
- return await apiClient.call('get_patterns', {
3156
+ // Either specificationId or ticketId is required
3157
+ if (!args.specificationId && !args.ticketId) {
3158
+ throw new Error('Either specificationId or ticketId is required');
3159
+ }
3160
+ // If ticketId is provided, return resolved patterns with inheritance
3161
+ if (args.ticketId) {
3162
+ // Fetch ticket data
3163
+ const ticket = await apiClient.call('get_ticket', {
3164
+ ticketId: args.ticketId,
3165
+ });
3166
+ // Get epic data
3167
+ const epic = await apiClient.call('get_epic', {
3168
+ epicId: ticket.epicId,
3169
+ });
3170
+ // Get specification data
3171
+ const spec = await apiClient.call('get_specification', {
3172
+ specificationId: epic.specificationId,
3173
+ });
3174
+ // Build pattern resolution input
3175
+ const input = {
3176
+ ticketId: args.ticketId,
3177
+ specification: {
3178
+ id: spec.id,
3179
+ codeStandards: spec.codeStandards,
3180
+ commonImports: spec.commonImports,
3181
+ returnTypes: spec.returnTypes,
3182
+ },
3183
+ epic: {
3184
+ id: epic.id,
3185
+ sharedPatterns: epic.sharedPatterns,
3186
+ additionalImports: epic.additionalImports,
3187
+ commonFiles: epic.commonFiles,
3188
+ },
3189
+ ticket: {
3190
+ id: ticket.id,
3191
+ technicalDetails: ticket.technicalDetails,
3192
+ },
3193
+ };
3194
+ // Resolve patterns and get override info
3195
+ const resolved = resolvePatternsWithCache(input);
3196
+ const overrideReport = getPatternOverrides(input);
3197
+ return {
3198
+ resolved: {
3199
+ imports: resolved.imports,
3200
+ patterns: resolved.patterns,
3201
+ returnTypes: resolved.returnTypes,
3202
+ commonFiles: resolved.commonFiles,
3203
+ naming: resolved.naming,
3204
+ },
3205
+ raw: {
3206
+ specification: {
3207
+ codeStandards: spec.codeStandards ?? null,
3208
+ commonImports: spec.commonImports ?? [],
3209
+ returnTypes: spec.returnTypes ?? null,
3210
+ },
3211
+ epic: {
3212
+ sharedPatterns: epic.sharedPatterns ?? null,
3213
+ additionalImports: epic.additionalImports ?? [],
3214
+ commonFiles: epic.commonFiles ?? null,
3215
+ },
3216
+ ticket: {
3217
+ patterns: ticket.technicalDetails?.patterns ?? null,
3218
+ },
3219
+ },
3220
+ overrides: overrideReport.overrides,
3221
+ chain: {
3222
+ specificationId: spec.id,
3223
+ specificationTitle: spec.title,
3224
+ epicId: epic.id,
3225
+ epicTitle: epic.title,
3226
+ ticketId: ticket.id,
3227
+ ticketTitle: ticket.title,
3228
+ },
3229
+ };
3230
+ }
3231
+ // Specification-level query (backward compatible)
3232
+ const specPatterns = await apiClient.call('get_patterns', {
2107
3233
  specificationId: args.specificationId,
2108
3234
  });
3235
+ // Get spec details for raw patterns if available
3236
+ let specData = null;
3237
+ try {
3238
+ specData = await apiClient.call('get_specification', {
3239
+ specificationId: args.specificationId,
3240
+ });
3241
+ }
3242
+ catch {
3243
+ // If spec fetch fails, just return the raw response
3244
+ return specPatterns;
3245
+ }
3246
+ // Return enhanced response with raw patterns
3247
+ return {
3248
+ resolved: {
3249
+ imports: specData.commonImports ?? [],
3250
+ patterns: flattenCodeStandards(specData.codeStandards),
3251
+ returnTypes: specData.returnTypes ?? {},
3252
+ commonFiles: {},
3253
+ naming: specData.codeStandards?.naming ?? {},
3254
+ },
3255
+ raw: {
3256
+ specification: {
3257
+ codeStandards: specData.codeStandards ?? null,
3258
+ commonImports: specData.commonImports ?? [],
3259
+ returnTypes: specData.returnTypes ?? null,
3260
+ },
3261
+ epic: {
3262
+ sharedPatterns: null,
3263
+ additionalImports: [],
3264
+ commonFiles: null,
3265
+ },
3266
+ ticket: {
3267
+ patterns: null,
3268
+ },
3269
+ },
3270
+ overrides: [],
3271
+ chain: {
3272
+ specificationId: specData.id,
3273
+ specificationTitle: specData.title,
3274
+ epicId: '',
3275
+ epicTitle: '',
3276
+ ticketId: '',
3277
+ ticketTitle: '',
3278
+ },
3279
+ };
2109
3280
  },
2110
3281
  // ========================================================================
2111
3282
  // Workflow & Tracking
@@ -2124,6 +3295,9 @@ export function createToolHandlers(apiClient) {
2124
3295
  filesModified: args.filesModified,
2125
3296
  filesCreated: args.filesCreated,
2126
3297
  actualHours: args.actualHours,
3298
+ validated: args.validated, // MCI-066: simple validation flag
3299
+ validation: args.validation, // MCI-064: validation flags
3300
+ skipCriteriaCheck: args.skipCriteriaCheck, // MCI-065: skip criteria check
2127
3301
  });
2128
3302
  },
2129
3303
  report_progress: async (_client, args) => {
@@ -2235,15 +3409,41 @@ export function createToolHandlers(apiClient) {
2235
3409
  projectId: args.projectId,
2236
3410
  });
2237
3411
  },
3412
+ get_report: async (_client, args) => {
3413
+ validateRequired(args, 'type', 'scope', 'scopeId');
3414
+ return await apiClient.call('get_report', {
3415
+ type: args.type,
3416
+ scope: args.scope,
3417
+ scopeId: args.scopeId,
3418
+ format: args.format ?? 'json',
3419
+ startDate: args.startDate,
3420
+ endDate: args.endDate,
3421
+ });
3422
+ },
2238
3423
  // ========================================================================
2239
3424
  // Search Tools
2240
3425
  // ========================================================================
2241
3426
  search_tickets: async (_client, args) => {
2242
- validateRequired(args, 'query');
3427
+ // Validate at least one search criterion
3428
+ if (!args.query && !args.files && !args.tags && !args.relatedTo) {
3429
+ throw new Error('At least one filter is required: query, files, tags, or relatedTo');
3430
+ }
3431
+ // Validate at least one scope
3432
+ if (!args.projectId && !args.specificationId && !args.epicId) {
3433
+ throw new Error('One of projectId, specificationId, or epicId is required');
3434
+ }
2243
3435
  return await apiClient.call('search_tickets', {
2244
3436
  query: args.query,
3437
+ files: args.files,
3438
+ tags: args.tags,
3439
+ matchAllTags: args.matchAllTags ?? false,
3440
+ relatedTo: args.relatedTo,
3441
+ status: args.status,
3442
+ complexity: args.complexity,
3443
+ priority: args.priority,
2245
3444
  specificationId: args.specificationId,
2246
3445
  projectId: args.projectId,
3446
+ epicId: args.epicId,
2247
3447
  limit: args.limit ?? 20,
2248
3448
  offset: args.offset ?? 0,
2249
3449
  });
@@ -2321,7 +3521,8 @@ export function createToolHandlers(apiClient) {
2321
3521
  // ========================================================================
2322
3522
  create_specification: async (_client, args) => {
2323
3523
  validateRequired(args, 'projectId', 'title');
2324
- return await apiClient.call('create_specification', {
3524
+ const responseMode = getResponseModeFromArgs(args);
3525
+ const result = await apiClient.call('create_specification', {
2325
3526
  projectId: args.projectId,
2326
3527
  title: args.title,
2327
3528
  description: args.description,
@@ -2350,10 +3551,54 @@ export function createToolHandlers(apiClient) {
2350
3551
  apiContracts: args.apiContracts,
2351
3552
  targetAudience: args.targetAudience,
2352
3553
  });
3554
+ return formatWriteResponse(result, responseMode, 'Specification created');
2353
3555
  },
2354
3556
  create_epic: async (_client, args) => {
2355
3557
  validateRequired(args, 'specificationId', 'title', 'description', 'objective');
2356
- return await apiClient.call('create_epic', {
3558
+ const responseMode = getResponseModeFromArgs(args);
3559
+ // Validate inline dependencies if tickets are provided with dependsOn
3560
+ if (args.tickets && Array.isArray(args.tickets)) {
3561
+ const tickets = args.tickets;
3562
+ const ticketCount = tickets.length;
3563
+ for (let i = 0; i < ticketCount; i++) {
3564
+ const ticket = tickets[i];
3565
+ if (ticket.dependsOn && Array.isArray(ticket.dependsOn)) {
3566
+ for (const depIndex of ticket.dependsOn) {
3567
+ // Check bounds
3568
+ if (typeof depIndex !== 'number' || depIndex < 0 || depIndex >= ticketCount) {
3569
+ throw new Error(`tickets[${i}].dependsOn: index ${depIndex} is out of bounds (must be 0-${ticketCount - 1})`);
3570
+ }
3571
+ // Check self-reference
3572
+ if (depIndex === i) {
3573
+ throw new Error(`tickets[${i}].dependsOn: ticket cannot depend on itself`);
3574
+ }
3575
+ }
3576
+ }
3577
+ }
3578
+ // Check for circular dependencies using DFS
3579
+ const hasCycle = (startIdx, visited, path) => {
3580
+ if (path.has(startIdx))
3581
+ return true;
3582
+ if (visited.has(startIdx))
3583
+ return false;
3584
+ visited.add(startIdx);
3585
+ path.add(startIdx);
3586
+ const deps = tickets[startIdx].dependsOn || [];
3587
+ for (const depIdx of deps) {
3588
+ if (hasCycle(depIdx, visited, path))
3589
+ return true;
3590
+ }
3591
+ path.delete(startIdx);
3592
+ return false;
3593
+ };
3594
+ const visited = new Set();
3595
+ for (let i = 0; i < ticketCount; i++) {
3596
+ if (hasCycle(i, visited, new Set())) {
3597
+ throw new Error(`Circular dependency detected in tickets array`);
3598
+ }
3599
+ }
3600
+ }
3601
+ const result = await apiClient.call('create_epic', {
2357
3602
  specificationId: args.specificationId,
2358
3603
  title: args.title,
2359
3604
  description: args.description,
@@ -2379,10 +3624,18 @@ export function createToolHandlers(apiClient) {
2379
3624
  dependencies: args.dependencies,
2380
3625
  apiContracts: args.apiContracts,
2381
3626
  });
3627
+ return formatWriteResponse(result, responseMode, 'Epic created');
2382
3628
  },
2383
3629
  create_ticket: async (_client, args) => {
2384
3630
  validateRequired(args, 'epicId', 'title');
2385
- return await apiClient.call('create_ticket', {
3631
+ const responseMode = getResponseModeFromArgs(args);
3632
+ // Extract validation options
3633
+ const skipValidation = args.skipValidation === true;
3634
+ const validationStrictness = args.validationStrictness === 'lenient' || args.validationStrictness === 'strict'
3635
+ ? args.validationStrictness
3636
+ : 'normal';
3637
+ // Create the ticket
3638
+ const result = await apiClient.call('create_ticket', {
2386
3639
  epicId: args.epicId,
2387
3640
  title: args.title,
2388
3641
  ticketNumber: args.ticketNumber,
@@ -2397,6 +3650,22 @@ export function createToolHandlers(apiClient) {
2397
3650
  order: args.order,
2398
3651
  dependsOn: args.dependsOn,
2399
3652
  });
3653
+ // Run validation unless skipped
3654
+ if (!skipValidation) {
3655
+ const validation = validateTicket({
3656
+ complexity: args.complexity,
3657
+ implementation: args.implementation,
3658
+ technicalDetails: args.technicalDetails,
3659
+ notes: args.notes,
3660
+ acceptanceCriteria: args.acceptanceCriteria,
3661
+ });
3662
+ // Filter warnings by strictness level
3663
+ const warnings = filterWarningsByStrictness(validation.warnings, validationStrictness);
3664
+ // Add validation results to response
3665
+ result.warnings = warnings;
3666
+ result.completenessScore = validation.completenessScore;
3667
+ }
3668
+ return formatWriteResponse(result, responseMode, 'Ticket created');
2400
3669
  },
2401
3670
  import_specification: async (_client, args) => {
2402
3671
  validateRequired(args, 'projectId', 'content');
@@ -2412,7 +3681,36 @@ export function createToolHandlers(apiClient) {
2412
3681
  // ========================================================================
2413
3682
  update_ticket: async (_client, args) => {
2414
3683
  validateRequired(args, 'ticketId');
2415
- return await apiClient.call('update_ticket', {
3684
+ const responseMode = getResponseModeFromArgs(args);
3685
+ // Extract validation options
3686
+ const skipValidation = args.skipValidation === true;
3687
+ const validationStrictness = args.validationStrictness === 'lenient' || args.validationStrictness === 'strict'
3688
+ ? args.validationStrictness
3689
+ : 'normal';
3690
+ // Get current ticket to calculate previous completeness score
3691
+ let previousCompletenessScore;
3692
+ if (!skipValidation) {
3693
+ try {
3694
+ const currentTicket = await apiClient.call('get_ticket', {
3695
+ ticketId: args.ticketId,
3696
+ });
3697
+ // Calculate previous completeness score
3698
+ const prevValidation = validateTicket({
3699
+ complexity: currentTicket.complexity,
3700
+ implementation: currentTicket.implementation,
3701
+ technicalDetails: currentTicket.technicalDetails,
3702
+ notes: currentTicket.notes,
3703
+ acceptanceCriteria: currentTicket.acceptanceCriteria,
3704
+ });
3705
+ previousCompletenessScore = prevValidation.completenessScore;
3706
+ }
3707
+ catch {
3708
+ // If we can't get the current ticket, skip previous score tracking
3709
+ previousCompletenessScore = undefined;
3710
+ }
3711
+ }
3712
+ // Update the ticket
3713
+ const result = await apiClient.call('update_ticket', {
2416
3714
  ticketId: args.ticketId,
2417
3715
  title: args.title,
2418
3716
  description: args.description,
@@ -2428,10 +3726,32 @@ export function createToolHandlers(apiClient) {
2428
3726
  technicalDetails: args.technicalDetails,
2429
3727
  blockReason: args.blockReason,
2430
3728
  });
3729
+ // Run validation on updated ticket unless skipped
3730
+ if (!skipValidation) {
3731
+ // Use the updated values (from args) merged with existing values (from result)
3732
+ // The result should contain the final state after update
3733
+ const validation = validateTicket({
3734
+ complexity: (args.complexity || result.complexity),
3735
+ implementation: (args.implementation || result.implementation),
3736
+ technicalDetails: (args.technicalDetails || result.technicalDetails),
3737
+ notes: (args.notes || result.notes),
3738
+ acceptanceCriteria: (args.acceptanceCriteria || result.acceptanceCriteria),
3739
+ });
3740
+ // Filter warnings by strictness level
3741
+ const warnings = filterWarningsByStrictness(validation.warnings, validationStrictness);
3742
+ // Add validation results to response
3743
+ result.warnings = warnings;
3744
+ result.completenessScore = validation.completenessScore;
3745
+ if (previousCompletenessScore !== undefined) {
3746
+ result.previousCompletenessScore = previousCompletenessScore;
3747
+ }
3748
+ }
3749
+ return formatWriteResponse(result, responseMode, 'Ticket updated');
2431
3750
  },
2432
3751
  update_epic: async (_client, args) => {
2433
3752
  validateRequired(args, 'epicId');
2434
- return await apiClient.call('update_epic', {
3753
+ const responseMode = getResponseModeFromArgs(args);
3754
+ const result = await apiClient.call('update_epic', {
2435
3755
  epicId: args.epicId,
2436
3756
  title: args.title,
2437
3757
  description: args.description,
@@ -2454,10 +3774,12 @@ export function createToolHandlers(apiClient) {
2454
3774
  tags: args.tags,
2455
3775
  estimatedHours: args.estimatedHours,
2456
3776
  });
3777
+ return formatWriteResponse(result, responseMode, 'Epic updated');
2457
3778
  },
2458
3779
  update_specification: async (_client, args) => {
2459
3780
  validateRequired(args, 'specificationId');
2460
- return await apiClient.call('update_specification', {
3781
+ const responseMode = getResponseModeFromArgs(args);
3782
+ const result = await apiClient.call('update_specification', {
2461
3783
  specificationId: args.specificationId,
2462
3784
  title: args.title,
2463
3785
  description: args.description,
@@ -2488,6 +3810,7 @@ export function createToolHandlers(apiClient) {
2488
3810
  workingDirectory: args.workingDirectory,
2489
3811
  outputDirectory: args.outputDirectory,
2490
3812
  });
3813
+ return formatWriteResponse(result, responseMode, 'Specification updated');
2491
3814
  },
2492
3815
  // ========================================================================
2493
3816
  // Working Context Operations
@@ -2564,6 +3887,74 @@ export function createToolHandlers(apiClient) {
2564
3887
  // ========================================================================
2565
3888
  // Bulk Operations
2566
3889
  // ========================================================================
3890
+ bulk_create_tickets: async (_client, args) => {
3891
+ validateRequired(args, 'epicId', 'tickets');
3892
+ if (!Array.isArray(args.tickets) || args.tickets.length === 0) {
3893
+ throw new Error('tickets array is required and must not be empty');
3894
+ }
3895
+ // Validate each ticket has a title
3896
+ const tickets = args.tickets;
3897
+ for (let i = 0; i < tickets.length; i++) {
3898
+ if (!tickets[i].title || typeof tickets[i].title !== 'string') {
3899
+ throw new Error(`tickets[${i}].title is required and must be a string`);
3900
+ }
3901
+ }
3902
+ // Validate dependencies if provided
3903
+ if (args.dependencies && Array.isArray(args.dependencies)) {
3904
+ const deps = args.dependencies;
3905
+ for (let i = 0; i < deps.length; i++) {
3906
+ const dep = deps[i];
3907
+ if (typeof dep.ticket !== 'number' || dep.ticket < 0 || dep.ticket >= tickets.length) {
3908
+ throw new Error(`dependencies[${i}].ticket must be a valid index in tickets array (0-${tickets.length - 1})`);
3909
+ }
3910
+ const dependsOn = Array.isArray(dep.dependsOn) ? dep.dependsOn : [dep.dependsOn];
3911
+ for (const idx of dependsOn) {
3912
+ if (typeof idx !== 'number' || idx < 0 || idx >= tickets.length) {
3913
+ throw new Error(`dependencies[${i}].dependsOn contains invalid index ${idx} (must be 0-${tickets.length - 1})`);
3914
+ }
3915
+ if (idx === dep.ticket) {
3916
+ throw new Error(`dependencies[${i}]: ticket cannot depend on itself`);
3917
+ }
3918
+ }
3919
+ }
3920
+ }
3921
+ return await apiClient.call('bulk_create_tickets', {
3922
+ epicId: args.epicId,
3923
+ tickets: args.tickets,
3924
+ dependencies: args.dependencies,
3925
+ onError: args.onError,
3926
+ });
3927
+ },
3928
+ get_job_status: async (_client, args) => {
3929
+ validateRequired(args, 'jobId');
3930
+ return await apiClient.call('get_job_status', {
3931
+ jobId: args.jobId,
3932
+ });
3933
+ },
3934
+ bulk_add_dependencies: async (_client, args) => {
3935
+ validateRequired(args, 'dependencies');
3936
+ if (!Array.isArray(args.dependencies) || args.dependencies.length === 0) {
3937
+ throw new Error('dependencies array is required and must not be empty');
3938
+ }
3939
+ // Validate each dependency has required fields
3940
+ const deps = args.dependencies;
3941
+ for (let i = 0; i < deps.length; i++) {
3942
+ if (!deps[i].ticketId || typeof deps[i].ticketId !== 'string') {
3943
+ throw new Error(`dependencies[${i}].ticketId is required`);
3944
+ }
3945
+ if (!deps[i].dependsOnId || typeof deps[i].dependsOnId !== 'string') {
3946
+ throw new Error(`dependencies[${i}].dependsOnId is required`);
3947
+ }
3948
+ if (deps[i].ticketId === deps[i].dependsOnId) {
3949
+ throw new Error(`dependencies[${i}]: ticket cannot depend on itself`);
3950
+ }
3951
+ }
3952
+ return await apiClient.call('bulk_add_dependencies', {
3953
+ dependencies: args.dependencies,
3954
+ skipCircularCheck: args.skipCircularCheck,
3955
+ onError: args.onError,
3956
+ });
3957
+ },
2567
3958
  bulk_update_tickets: async (_client, args) => {
2568
3959
  if (!args.updates || !Array.isArray(args.updates) || args.updates.length === 0) {
2569
3960
  throw new Error('updates array is required and must not be empty');
@@ -2589,6 +3980,22 @@ export function createToolHandlers(apiClient) {
2589
3980
  clearTestResults: args.clearTestResults,
2590
3981
  });
2591
3982
  },
3983
+ // ========================================================================
3984
+ // Validation Operations (MCI-075)
3985
+ // ========================================================================
3986
+ validate_counts: async (_client, args) => {
3987
+ // Must specify at least one scope
3988
+ if (!args.projectId && !args.specificationId && !args.epicId) {
3989
+ throw new Error('Must specify projectId, specificationId, or epicId');
3990
+ }
3991
+ return await apiClient.call('validate_counts', {
3992
+ projectId: args.projectId,
3993
+ specificationId: args.specificationId,
3994
+ epicId: args.epicId,
3995
+ repair: args.repair,
3996
+ verbose: args.verbose,
3997
+ });
3998
+ },
2592
3999
  };
2593
4000
  }
2594
4001
  /**