@specforge/mcp 1.5.2 → 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 +1482 -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: {
@@ -361,6 +746,52 @@ export function getTools() {
361
746
  items: { type: 'string' },
362
747
  description: 'List of files created',
363
748
  },
749
+ actualHours: {
750
+ type: 'number',
751
+ description: 'Actual hours spent on the ticket',
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
+ },
364
795
  },
365
796
  required: ['ticketId', 'summary'],
366
797
  },
@@ -391,7 +822,7 @@ export function getTools() {
391
822
  },
392
823
  {
393
824
  name: 'get_work_summary',
394
- 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',
395
826
  inputSchema: {
396
827
  type: 'object',
397
828
  properties: {
@@ -421,7 +852,7 @@ export function getTools() {
421
852
  // ========================================================================
422
853
  {
423
854
  name: 'report_test_results',
424
- 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.',
425
856
  inputSchema: {
426
857
  type: 'object',
427
858
  properties: {
@@ -476,7 +907,7 @@ export function getTools() {
476
907
  },
477
908
  {
478
909
  name: 'get_ticket_test_status',
479
- 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.",
480
911
  inputSchema: {
481
912
  type: 'object',
482
913
  properties: {
@@ -490,7 +921,7 @@ export function getTools() {
490
921
  },
491
922
  {
492
923
  name: 'validate_ticket_completion',
493
- 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.',
494
925
  inputSchema: {
495
926
  type: 'object',
496
927
  properties: {
@@ -503,11 +934,11 @@ export function getTools() {
503
934
  },
504
935
  },
505
936
  // ========================================================================
506
- // Discovery Tools (Epic 7)
937
+ // Discovery Tools (Epic 7) - DEPRECATED: Use tag-based ticket operations instead
507
938
  // ========================================================================
508
939
  {
509
940
  name: 'report_discovery',
510
- 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',
511
942
  inputSchema: {
512
943
  type: 'object',
513
944
  properties: {
@@ -539,7 +970,7 @@ export function getTools() {
539
970
  },
540
971
  {
541
972
  name: 'get_pending_discoveries',
542
- 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',
543
974
  inputSchema: {
544
975
  type: 'object',
545
976
  properties: {
@@ -557,7 +988,7 @@ export function getTools() {
557
988
  },
558
989
  {
559
990
  name: 'resolve_discovery',
560
- 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',
561
992
  inputSchema: {
562
993
  type: 'object',
563
994
  properties: {
@@ -613,9 +1044,56 @@ export function getTools() {
613
1044
  required: ['epicId'],
614
1045
  },
615
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
+ },
616
1094
  {
617
1095
  name: 'get_implementation_summary',
618
- 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',
619
1097
  inputSchema: {
620
1098
  type: 'object',
621
1099
  properties: {
@@ -629,7 +1107,7 @@ export function getTools() {
629
1107
  },
630
1108
  {
631
1109
  name: 'get_time_report',
632
- 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',
633
1111
  inputSchema: {
634
1112
  type: 'object',
635
1113
  properties: {
@@ -648,7 +1126,7 @@ export function getTools() {
648
1126
  },
649
1127
  {
650
1128
  name: 'get_blockers_report',
651
- 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',
652
1130
  inputSchema: {
653
1131
  type: 'object',
654
1132
  properties: {
@@ -669,37 +1147,124 @@ export function getTools() {
669
1147
  // ========================================================================
670
1148
  {
671
1149
  name: 'search_tickets',
672
- 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.`,
673
1161
  inputSchema: {
674
1162
  type: 'object',
675
1163
  properties: {
676
1164
  query: {
677
1165
  type: 'string',
678
- description: 'Search query',
1166
+ description: 'Full-text search query',
679
1167
  },
680
- 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: {
681
1183
  type: 'string',
682
- 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',
683
1209
  },
684
1210
  projectId: {
685
1211
  type: 'string',
686
- 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',
687
1221
  },
688
1222
  limit: {
689
1223
  type: 'number',
690
- description: 'Maximum results (default: 20)',
1224
+ description: 'Maximum results (default: 20, max: 100)',
691
1225
  },
692
1226
  offset: {
693
1227
  type: 'number',
694
1228
  description: 'Pagination offset (default: 0)',
695
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
+ },
696
1262
  },
697
- required: ['query'],
698
1263
  },
699
1264
  },
700
1265
  {
701
1266
  name: 'find_tickets_by_file',
702
- 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)',
703
1268
  inputSchema: {
704
1269
  type: 'object',
705
1270
  properties: {
@@ -721,7 +1286,7 @@ export function getTools() {
721
1286
  },
722
1287
  {
723
1288
  name: 'find_tickets_by_tag',
724
- 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',
725
1290
  inputSchema: {
726
1291
  type: 'object',
727
1292
  properties: {
@@ -748,7 +1313,7 @@ export function getTools() {
748
1313
  },
749
1314
  {
750
1315
  name: 'find_related_tickets',
751
- 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',
752
1317
  inputSchema: {
753
1318
  type: 'object',
754
1319
  properties: {
@@ -833,7 +1398,7 @@ export function getTools() {
833
1398
  },
834
1399
  {
835
1400
  name: 'get_ticket_commits',
836
- 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",
837
1402
  inputSchema: {
838
1403
  type: 'object',
839
1404
  properties: {
@@ -855,7 +1420,7 @@ export function getTools() {
855
1420
  },
856
1421
  {
857
1422
  name: 'get_ticket_prs',
858
- 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",
859
1424
  inputSchema: {
860
1425
  type: 'object',
861
1426
  properties: {
@@ -1023,6 +1588,48 @@ export function getTools() {
1023
1588
  required: ['title', 'description', 'objective'],
1024
1589
  },
1025
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
+ },
1026
1633
  },
1027
1634
  required: ['projectId', 'title'],
1028
1635
  },
@@ -1129,7 +1736,7 @@ export function getTools() {
1129
1736
  },
1130
1737
  tickets: {
1131
1738
  type: 'array',
1132
- description: 'Nested tickets to create (bulk creation)',
1739
+ description: 'Nested tickets to create (bulk creation) with optional inline dependencies',
1133
1740
  items: {
1134
1741
  type: 'object',
1135
1742
  properties: {
@@ -1138,17 +1745,57 @@ export function getTools() {
1138
1745
  acceptanceCriteria: { type: 'array', items: { type: 'string' } },
1139
1746
  estimatedHours: { type: 'number' },
1140
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
+ },
1141
1757
  },
1142
1758
  required: ['title'],
1143
1759
  },
1144
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
+ },
1145
1792
  },
1146
1793
  required: ['specificationId', 'title', 'description', 'objective'],
1147
1794
  },
1148
1795
  },
1149
1796
  {
1150
1797
  name: 'create_ticket',
1151
- description: 'Create a new ticket in an epic',
1798
+ description: 'Create a new ticket in an epic. Returns validation warnings based on complexity level.',
1152
1799
  inputSchema: {
1153
1800
  type: 'object',
1154
1801
  properties: {
@@ -1209,6 +1856,20 @@ export function getTools() {
1209
1856
  items: { type: 'string' },
1210
1857
  description: 'Array of ticket IDs this ticket depends on',
1211
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
+ },
1212
1873
  },
1213
1874
  required: ['epicId', 'title'],
1214
1875
  },
@@ -1244,7 +1905,9 @@ export function getTools() {
1244
1905
  // ========================================================================
1245
1906
  {
1246
1907
  name: 'update_ticket',
1247
- 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).',
1248
1911
  inputSchema: {
1249
1912
  type: 'object',
1250
1913
  properties: {
@@ -1262,8 +1925,8 @@ export function getTools() {
1262
1925
  },
1263
1926
  status: {
1264
1927
  type: 'string',
1265
- enum: ['todo', 'queue', 'in_progress', 'blocked', 'done'],
1266
- description: 'New status (optional)',
1928
+ enum: ['pending', 'ready', 'active', 'done'],
1929
+ description: 'Ticket status. pending/ready may be recalculated based on dependencies.',
1267
1930
  },
1268
1931
  priority: {
1269
1932
  type: 'string',
@@ -1277,7 +1940,7 @@ export function getTools() {
1277
1940
  },
1278
1941
  blockReason: {
1279
1942
  type: 'string',
1280
- description: 'Reason why ticket is blocked (optional, typically used when status is blocked)',
1943
+ description: 'External block reason. Forces status to pending until cleared.',
1281
1944
  },
1282
1945
  notes: {
1283
1946
  type: 'string',
@@ -1305,6 +1968,22 @@ export function getTools() {
1305
1968
  type: 'object',
1306
1969
  description: 'Technical details like stack, endpoints (JSON) (optional)',
1307
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
+ },
1308
1987
  },
1309
1988
  required: ['ticketId'],
1310
1989
  },
@@ -1406,6 +2085,37 @@ export function getTools() {
1406
2085
  enum: ['low', 'medium', 'high', 'critical'],
1407
2086
  description: 'Priority level (optional)',
1408
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
+ },
1409
2119
  },
1410
2120
  required: ['epicId'],
1411
2121
  },
@@ -1551,6 +2261,48 @@ export function getTools() {
1551
2261
  type: 'string',
1552
2262
  description: 'Output directory for build artifacts (optional)',
1553
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
+ },
1554
2306
  },
1555
2307
  required: ['specificationId'],
1556
2308
  },
@@ -1644,7 +2396,7 @@ export function getTools() {
1644
2396
  },
1645
2397
  {
1646
2398
  name: 'get_session',
1647
- 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",
1648
2400
  inputSchema: {
1649
2401
  type: 'object',
1650
2402
  properties: {
@@ -1666,7 +2418,7 @@ export function getTools() {
1666
2418
  },
1667
2419
  {
1668
2420
  name: 'update_session',
1669
- 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)',
1670
2422
  inputSchema: {
1671
2423
  type: 'object',
1672
2424
  properties: {
@@ -1697,7 +2449,7 @@ export function getTools() {
1697
2449
  },
1698
2450
  {
1699
2451
  name: 'record_session_ticket',
1700
- 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.',
1701
2453
  inputSchema: {
1702
2454
  type: 'object',
1703
2455
  properties: {
@@ -1755,7 +2507,7 @@ export function getTools() {
1755
2507
  },
1756
2508
  {
1757
2509
  name: 'list_sessions',
1758
- 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",
1759
2511
  inputSchema: {
1760
2512
  type: 'object',
1761
2513
  properties: {
@@ -1790,6 +2542,147 @@ export function getTools() {
1790
2542
  // ========================================================================
1791
2543
  // Bulk Operations
1792
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
+ },
1793
2686
  {
1794
2687
  name: 'bulk_update_tickets',
1795
2688
  description: 'Update multiple tickets in a single atomic operation. All updates succeed or all fail (unless atomic: false).',
@@ -1807,8 +2700,8 @@ export function getTools() {
1807
2700
  },
1808
2701
  status: {
1809
2702
  type: 'string',
1810
- enum: ['todo', 'queue', 'in_progress', 'blocked', 'done'],
1811
- description: 'New status',
2703
+ enum: ['pending', 'ready', 'active', 'done'],
2704
+ description: 'Ticket status. Completing (done) triggers cascade to unblock dependents.',
1812
2705
  },
1813
2706
  priority: {
1814
2707
  type: 'string',
@@ -1823,7 +2716,7 @@ export function getTools() {
1823
2716
  },
1824
2717
  blockReason: {
1825
2718
  type: 'string',
1826
- description: 'Reason for blocking (when status is blocked)',
2719
+ description: 'External block reason. Forces status to pending until cleared.',
1827
2720
  },
1828
2721
  },
1829
2722
  required: ['ticketId'],
@@ -1838,13 +2731,21 @@ export function getTools() {
1838
2731
  type: 'boolean',
1839
2732
  description: 'All-or-nothing updates. If false, partial success allowed (default: true)',
1840
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
+ },
1841
2742
  },
1842
2743
  required: ['updates'],
1843
2744
  },
1844
2745
  },
1845
2746
  {
1846
2747
  name: 'reset_tickets',
1847
- 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.',
1848
2749
  inputSchema: {
1849
2750
  type: 'object',
1850
2751
  properties: {
@@ -1869,11 +2770,6 @@ export function getTools() {
1869
2770
  type: 'boolean',
1870
2771
  description: 'Reset all tickets in the specification',
1871
2772
  },
1872
- targetStatus: {
1873
- type: 'string',
1874
- enum: ['todo', 'queue'],
1875
- description: 'Status to reset tickets to (default: todo)',
1876
- },
1877
2773
  resetDependents: {
1878
2774
  type: 'boolean',
1879
2775
  description: 'Also reset tickets that depend on the specified tickets (default: false)',
@@ -1894,7 +2790,47 @@ export function getTools() {
1894
2790
  required: ['specificationId'],
1895
2791
  },
1896
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
+ },
1897
2831
  ];
2832
+ // Apply format parameter to all read operations
2833
+ return tools.map(tool => READ_TOOL_NAMES.has(tool.name) ? addFormatParameter(tool) : tool);
1898
2834
  }
1899
2835
  /**
1900
2836
  * Retry configuration for transient failures
@@ -1982,6 +2918,12 @@ export function createToolHandlers(apiClient) {
1982
2918
  projectId: args.projectId,
1983
2919
  });
1984
2920
  },
2921
+ lookup_project: async (_client, args) => {
2922
+ validateRequired(args, 'name');
2923
+ return await apiClient.call('lookup_project', {
2924
+ name: args.name,
2925
+ });
2926
+ },
1985
2927
  // ========================================================================
1986
2928
  // Core Operations - Specifications
1987
2929
  // ========================================================================
@@ -1990,12 +2932,24 @@ export function createToolHandlers(apiClient) {
1990
2932
  return await apiClient.call('list_specifications', {
1991
2933
  projectId: args.projectId,
1992
2934
  status: args.status,
2935
+ limit: args.limit,
2936
+ offset: args.offset,
2937
+ fields: args.fields,
1993
2938
  });
1994
2939
  },
1995
2940
  get_specification: async (_client, args) => {
1996
2941
  validateRequired(args, 'specificationId');
1997
2942
  return await apiClient.call('get_specification', {
1998
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,
1999
2953
  });
2000
2954
  },
2001
2955
  // ========================================================================
@@ -2006,12 +2960,29 @@ export function createToolHandlers(apiClient) {
2006
2960
  return await apiClient.call('list_epics', {
2007
2961
  specificationId: args.specificationId,
2008
2962
  status: args.status,
2963
+ limit: args.limit,
2964
+ offset: args.offset,
2965
+ fields: args.fields,
2009
2966
  });
2010
2967
  },
2011
2968
  get_epic: async (_client, args) => {
2012
2969
  validateRequired(args, 'epicId');
2013
2970
  return await apiClient.call('get_epic', {
2014
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,
2015
2986
  });
2016
2987
  },
2017
2988
  // ========================================================================
@@ -2022,12 +2993,53 @@ export function createToolHandlers(apiClient) {
2022
2993
  return await apiClient.call('list_tickets', {
2023
2994
  epicId: args.epicId,
2024
2995
  status: args.status,
2996
+ limit: args.limit,
2997
+ offset: args.offset,
2998
+ include: args.include,
2999
+ fields: args.fields,
2025
3000
  });
2026
3001
  },
2027
3002
  get_ticket: async (_client, args) => {
2028
3003
  validateRequired(args, 'ticketId');
2029
3004
  return await apiClient.call('get_ticket', {
2030
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,
2031
3043
  });
2032
3044
  },
2033
3045
  // ========================================================================
@@ -2070,9 +3082,52 @@ export function createToolHandlers(apiClient) {
2070
3082
  // ========================================================================
2071
3083
  get_implementation_context: async (_client, args) => {
2072
3084
  validateRequired(args, 'ticketId');
2073
- 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', {
2074
3088
  ticketId: args.ticketId,
3089
+ depth,
2075
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;
2076
3131
  },
2077
3132
  get_next_actionable_tickets: async (_client, args) => {
2078
3133
  // Either specificationId or projectId is required
@@ -2098,10 +3153,130 @@ export function createToolHandlers(apiClient) {
2098
3153
  });
2099
3154
  },
2100
3155
  get_patterns: async (_client, args) => {
2101
- validateRequired(args, 'specificationId');
2102
- 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', {
2103
3233
  specificationId: args.specificationId,
2104
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
+ };
2105
3280
  },
2106
3281
  // ========================================================================
2107
3282
  // Workflow & Tracking
@@ -2119,6 +3294,10 @@ export function createToolHandlers(apiClient) {
2119
3294
  summary: args.summary,
2120
3295
  filesModified: args.filesModified,
2121
3296
  filesCreated: args.filesCreated,
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
2122
3301
  });
2123
3302
  },
2124
3303
  report_progress: async (_client, args) => {
@@ -2230,15 +3409,41 @@ export function createToolHandlers(apiClient) {
2230
3409
  projectId: args.projectId,
2231
3410
  });
2232
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
+ },
2233
3423
  // ========================================================================
2234
3424
  // Search Tools
2235
3425
  // ========================================================================
2236
3426
  search_tickets: async (_client, args) => {
2237
- 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
+ }
2238
3435
  return await apiClient.call('search_tickets', {
2239
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,
2240
3444
  specificationId: args.specificationId,
2241
3445
  projectId: args.projectId,
3446
+ epicId: args.epicId,
2242
3447
  limit: args.limit ?? 20,
2243
3448
  offset: args.offset ?? 0,
2244
3449
  });
@@ -2316,7 +3521,8 @@ export function createToolHandlers(apiClient) {
2316
3521
  // ========================================================================
2317
3522
  create_specification: async (_client, args) => {
2318
3523
  validateRequired(args, 'projectId', 'title');
2319
- return await apiClient.call('create_specification', {
3524
+ const responseMode = getResponseModeFromArgs(args);
3525
+ const result = await apiClient.call('create_specification', {
2320
3526
  projectId: args.projectId,
2321
3527
  title: args.title,
2322
3528
  description: args.description,
@@ -2345,10 +3551,54 @@ export function createToolHandlers(apiClient) {
2345
3551
  apiContracts: args.apiContracts,
2346
3552
  targetAudience: args.targetAudience,
2347
3553
  });
3554
+ return formatWriteResponse(result, responseMode, 'Specification created');
2348
3555
  },
2349
3556
  create_epic: async (_client, args) => {
2350
3557
  validateRequired(args, 'specificationId', 'title', 'description', 'objective');
2351
- 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', {
2352
3602
  specificationId: args.specificationId,
2353
3603
  title: args.title,
2354
3604
  description: args.description,
@@ -2374,10 +3624,18 @@ export function createToolHandlers(apiClient) {
2374
3624
  dependencies: args.dependencies,
2375
3625
  apiContracts: args.apiContracts,
2376
3626
  });
3627
+ return formatWriteResponse(result, responseMode, 'Epic created');
2377
3628
  },
2378
3629
  create_ticket: async (_client, args) => {
2379
3630
  validateRequired(args, 'epicId', 'title');
2380
- 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', {
2381
3639
  epicId: args.epicId,
2382
3640
  title: args.title,
2383
3641
  ticketNumber: args.ticketNumber,
@@ -2392,6 +3650,22 @@ export function createToolHandlers(apiClient) {
2392
3650
  order: args.order,
2393
3651
  dependsOn: args.dependsOn,
2394
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');
2395
3669
  },
2396
3670
  import_specification: async (_client, args) => {
2397
3671
  validateRequired(args, 'projectId', 'content');
@@ -2407,7 +3681,36 @@ export function createToolHandlers(apiClient) {
2407
3681
  // ========================================================================
2408
3682
  update_ticket: async (_client, args) => {
2409
3683
  validateRequired(args, 'ticketId');
2410
- 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', {
2411
3714
  ticketId: args.ticketId,
2412
3715
  title: args.title,
2413
3716
  description: args.description,
@@ -2423,10 +3726,32 @@ export function createToolHandlers(apiClient) {
2423
3726
  technicalDetails: args.technicalDetails,
2424
3727
  blockReason: args.blockReason,
2425
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');
2426
3750
  },
2427
3751
  update_epic: async (_client, args) => {
2428
3752
  validateRequired(args, 'epicId');
2429
- return await apiClient.call('update_epic', {
3753
+ const responseMode = getResponseModeFromArgs(args);
3754
+ const result = await apiClient.call('update_epic', {
2430
3755
  epicId: args.epicId,
2431
3756
  title: args.title,
2432
3757
  description: args.description,
@@ -2449,10 +3774,12 @@ export function createToolHandlers(apiClient) {
2449
3774
  tags: args.tags,
2450
3775
  estimatedHours: args.estimatedHours,
2451
3776
  });
3777
+ return formatWriteResponse(result, responseMode, 'Epic updated');
2452
3778
  },
2453
3779
  update_specification: async (_client, args) => {
2454
3780
  validateRequired(args, 'specificationId');
2455
- return await apiClient.call('update_specification', {
3781
+ const responseMode = getResponseModeFromArgs(args);
3782
+ const result = await apiClient.call('update_specification', {
2456
3783
  specificationId: args.specificationId,
2457
3784
  title: args.title,
2458
3785
  description: args.description,
@@ -2483,6 +3810,7 @@ export function createToolHandlers(apiClient) {
2483
3810
  workingDirectory: args.workingDirectory,
2484
3811
  outputDirectory: args.outputDirectory,
2485
3812
  });
3813
+ return formatWriteResponse(result, responseMode, 'Specification updated');
2486
3814
  },
2487
3815
  // ========================================================================
2488
3816
  // Working Context Operations
@@ -2559,6 +3887,74 @@ export function createToolHandlers(apiClient) {
2559
3887
  // ========================================================================
2560
3888
  // Bulk Operations
2561
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
+ },
2562
3958
  bulk_update_tickets: async (_client, args) => {
2563
3959
  if (!args.updates || !Array.isArray(args.updates) || args.updates.length === 0) {
2564
3960
  throw new Error('updates array is required and must not be empty');
@@ -2584,6 +3980,22 @@ export function createToolHandlers(apiClient) {
2584
3980
  clearTestResults: args.clearTestResults,
2585
3981
  });
2586
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
+ },
2587
3999
  };
2588
4000
  }
2589
4001
  /**