@specforge/mcp 3.1.2 → 3.2.1

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 (33) hide show
  1. package/dist/autopilot/api/autopilot-api-client.d.ts.map +1 -1
  2. package/dist/autopilot/api/autopilot-api-client.js +0 -8
  3. package/dist/autopilot/api/autopilot-api-client.js.map +1 -1
  4. package/dist/autopilot/readiness/post-scoring.js +3 -1
  5. package/dist/autopilot/readiness/post-scoring.js.map +1 -1
  6. package/dist/cli/commands/docs/tool-examples.js +4 -3
  7. package/dist/cli/commands/docs/tool-examples.js.map +1 -1
  8. package/dist/cli/commands/docs/types.js +1 -1
  9. package/dist/cli/commands/docs/types.js.map +1 -1
  10. package/dist/lib/prompt-generator.js +1 -1
  11. package/dist/lib/prompt-generator.js.map +1 -1
  12. package/dist/tools/core/help.js +1 -1
  13. package/dist/tools/core/help.js.map +1 -1
  14. package/dist/tools/core/index.d.ts +0 -1
  15. package/dist/tools/core/index.d.ts.map +1 -1
  16. package/dist/tools/core/index.js +0 -1
  17. package/dist/tools/core/index.js.map +1 -1
  18. package/dist/tools/core/session.d.ts.map +1 -1
  19. package/dist/tools/core/session.js +6 -21
  20. package/dist/tools/core/session.js.map +1 -1
  21. package/dist/tools/core/ticket.js +1 -1
  22. package/dist/tools/core/ticket.js.map +1 -1
  23. package/dist/tools/index.d.ts.map +1 -1
  24. package/dist/tools/index.js +214 -279
  25. package/dist/tools/index.js.map +1 -1
  26. package/dist/types/index.d.ts +82 -11
  27. package/dist/types/index.d.ts.map +1 -1
  28. package/dist/validation/index.d.ts.map +1 -1
  29. package/dist/validation/index.js +20 -56
  30. package/dist/validation/index.js.map +1 -1
  31. package/package.json +1 -1
  32. package/src/cli/templates/skills/specforge-orchestrator.md +17 -15
  33. package/src/cli/templates/skills/specforge-worker.md +1 -9
@@ -82,8 +82,6 @@ const READ_TOOL_NAMES = new Set([
82
82
  'search_tickets',
83
83
  // Working context
84
84
  'get_working_context',
85
- // Job status
86
- 'get_job_status',
87
85
  // Agent Teams (read operations)
88
86
  'get_epic_dependency_graph',
89
87
  'get_implementation_plan',
@@ -726,7 +724,7 @@ Examples:
726
724
  // ========================================================================
727
725
  {
728
726
  name: 'start_work_session',
729
- description: 'Start working on a ticket. Transitions ready -> active. Returns error with statusReason if ticket is pending (dependencies not met).',
727
+ description: 'Start working on a ticket. Creates a WorkSession record and transitions ready -> active. Returns the workSessionId along with error with statusReason if ticket is pending (dependencies not met).',
730
728
  inputSchema: {
731
729
  type: 'object',
732
730
  properties: {
@@ -740,10 +738,9 @@ Examples:
740
738
  },
741
739
  {
742
740
  name: 'complete_work_session',
743
- description: 'Mark a ticket as complete. Transitions active -> done.\n\n' +
741
+ description: 'Mark a ticket as complete and finalize the active WorkSession. Transitions active -> done. Session data (files, test results, time) is stored on the WorkSession record.\n\n' +
742
+ 'All acceptance criteria must be validated and all implementation steps must be completed via action_work_session before calling this. The completion gate enforces this — shortcuts are not available.\n\n' +
744
743
  'Automatically recalculates status for dependent tickets and returns cascade info.\n\n' +
745
- 'By default, checks if summary addresses acceptance criteria and returns warnings (not errors) for gaps.\n\n' +
746
- 'Use validated: true as a simple confirmation that the agent has verified all work meets criteria.\n\n' +
747
744
  'Optional validation flags can be provided to report test results inline:\n' +
748
745
  '- tests: Unit/integration test results\n' +
749
746
  '- lint: Linting results\n' +
@@ -770,21 +767,18 @@ Examples:
770
767
  items: { type: 'string' },
771
768
  description: 'List of files created',
772
769
  },
770
+ filesDeleted: {
771
+ type: 'array',
772
+ items: { type: 'string' },
773
+ description: 'List of files deleted',
774
+ },
773
775
  actualHours: {
774
776
  type: 'number',
775
777
  description: 'Actual hours spent on the ticket',
776
778
  },
777
- validated: {
778
- type: 'boolean',
779
- description: 'Simple validation confirmation. When true, indicates:\n' +
780
- '- Agent has verified work meets acceptance criteria\n' +
781
- '- All necessary tests/validations were run and passed\n' +
782
- '- No detailed breakdown needed\n' +
783
- 'Trust-based approach for streamlined completion.',
784
- },
785
779
  validation: {
786
780
  type: 'object',
787
- description: 'Detailed validation flags (optional with validated: true)',
781
+ description: 'Detailed validation flags for reporting test/lint/build results',
788
782
  properties: {
789
783
  tests: {
790
784
  type: 'string',
@@ -812,61 +806,175 @@ Examples:
812
806
  },
813
807
  },
814
808
  },
815
- skipCriteriaCheck: {
816
- type: 'boolean',
817
- description: 'Skip acceptance criteria validation. Use for trivial changes or when criteria check is not needed. Default: false',
818
- },
819
809
  },
820
810
  required: ['ticketId', 'summary'],
821
811
  },
822
812
  },
823
813
  {
824
- name: 'report_progress',
825
- description: 'Report incremental progress on a ticket',
814
+ name: 'pause_work_session',
815
+ description: 'Pause work on a ticket, setting it back to ready status. Use this to temporarily pause work on a ticket without completing it.',
826
816
  inputSchema: {
827
817
  type: 'object',
828
818
  properties: {
829
819
  ticketId: {
830
820
  type: 'string',
831
- description: 'The ID of the ticket',
832
- },
833
- progress: {
834
- type: 'number',
835
- minimum: 0,
836
- maximum: 100,
837
- description: 'Progress percentage (0-100)',
838
- },
839
- message: {
840
- type: 'string',
841
- description: 'Progress message',
821
+ description: 'The ID of the ticket to pause',
842
822
  },
843
823
  },
844
- required: ['ticketId', 'progress'],
824
+ required: ['ticketId'],
845
825
  },
846
826
  },
847
827
  {
848
- name: 'pause_work_session',
849
- description: 'Pause work on a ticket, setting it back to ready status. Use this to temporarily pause work on a ticket without completing it.',
828
+ name: 'resume_work_session',
829
+ description: 'Resume work on a paused ticket, setting it to active status. Use this to continue work on a ticket that was previously paused.',
850
830
  inputSchema: {
851
831
  type: 'object',
852
832
  properties: {
853
833
  ticketId: {
854
834
  type: 'string',
855
- description: 'The ID of the ticket to pause',
835
+ description: 'The ID of the ticket to resume',
856
836
  },
857
837
  },
858
838
  required: ['ticketId'],
859
839
  },
860
840
  },
861
841
  {
862
- name: 'resume_work_session',
863
- description: 'Resume work on a paused ticket, setting it to active status. Use this to continue work on a ticket that was previously paused.',
842
+ name: 'action_work_session',
843
+ description: `Update checklist state during an active WorkSession. Combines step completion, AC validation, test result reporting, and file tracking into a single atomic operation. All state changes are recorded on the WorkSession and its related validation/completion records.
844
+
845
+ Use this instead of calling update_ticket for checklist changes. Provides:
846
+ - Step completion (individual or bulk via WorkSessionStepCompletion)
847
+ - Acceptance criteria validation (individual or bulk via WorkSessionACValidation)
848
+ - Test result reporting (merged by test type via WorkSessionTestResult)
849
+ - File tracking (created, modified, deleted on WorkSession)
850
+ - Blocker management (set/clear block reasons)
851
+ - Auto-calculated progress percentage
852
+ - Completion readiness hints
853
+
854
+ IMPORTANT: All acceptance criteria must be validated and all implementation steps must be completed via this tool before calling complete_work_session. The completion gate enforces this.`,
864
855
  inputSchema: {
865
856
  type: 'object',
866
857
  properties: {
867
858
  ticketId: {
868
859
  type: 'string',
869
- description: 'The ID of the ticket to resume',
860
+ description: 'The ID of the ticket being worked on (must be in active status)',
861
+ },
862
+ steps: {
863
+ type: 'array',
864
+ description: 'Individual step completion updates',
865
+ items: {
866
+ type: 'object',
867
+ properties: {
868
+ index: {
869
+ type: 'number',
870
+ description: 'Zero-based index of the step in the implementation.steps array',
871
+ },
872
+ completed: {
873
+ type: 'boolean',
874
+ description: 'Whether the step is completed',
875
+ },
876
+ notes: {
877
+ type: 'string',
878
+ description: 'Optional note about this step',
879
+ },
880
+ },
881
+ required: ['index', 'completed'],
882
+ },
883
+ },
884
+ allStepsDone: {
885
+ type: 'boolean',
886
+ description: 'Shortcut: mark all steps as completed',
887
+ },
888
+ acceptanceCriteria: {
889
+ type: 'array',
890
+ description: 'Individual AC validation updates',
891
+ items: {
892
+ type: 'object',
893
+ properties: {
894
+ index: {
895
+ type: 'number',
896
+ description: 'Zero-based index of the AC in the acceptanceCriteria array',
897
+ },
898
+ validated: {
899
+ type: 'boolean',
900
+ description: 'Whether the AC is validated',
901
+ },
902
+ notes: {
903
+ type: 'string',
904
+ description: 'Optional note about this AC',
905
+ },
906
+ },
907
+ required: ['index', 'validated'],
908
+ },
909
+ },
910
+ allACValidated: {
911
+ type: 'boolean',
912
+ description: 'Shortcut: mark all acceptance criteria as validated',
913
+ },
914
+ testResults: {
915
+ type: 'array',
916
+ description: 'Test result reports to append (merged by testType)',
917
+ items: {
918
+ type: 'object',
919
+ properties: {
920
+ testType: {
921
+ type: 'string',
922
+ description: 'Type of test (e.g., "unit", "integration", "e2e", "lint", "typeCheck")',
923
+ },
924
+ passed: {
925
+ type: 'number',
926
+ description: 'Number of tests that passed',
927
+ },
928
+ failed: {
929
+ type: 'number',
930
+ description: 'Number of tests that failed',
931
+ },
932
+ skipped: {
933
+ type: 'number',
934
+ description: 'Number of tests skipped',
935
+ },
936
+ command: {
937
+ type: 'string',
938
+ description: 'Command used to run the tests',
939
+ },
940
+ output: {
941
+ type: 'string',
942
+ description: 'Test output (truncated if needed)',
943
+ },
944
+ duration: {
945
+ type: 'number',
946
+ description: 'Duration in milliseconds',
947
+ },
948
+ },
949
+ required: ['testType', 'passed', 'failed'],
950
+ },
951
+ },
952
+ notes: {
953
+ type: 'string',
954
+ description: 'Additional notes to append to the ticket',
955
+ },
956
+ filesCreated: {
957
+ type: 'array',
958
+ items: { type: 'string' },
959
+ description: 'Files created during this session. Accumulated incrementally.',
960
+ },
961
+ filesModified: {
962
+ type: 'array',
963
+ items: { type: 'string' },
964
+ description: 'Files modified during this session. Accumulated incrementally.',
965
+ },
966
+ filesDeleted: {
967
+ type: 'array',
968
+ items: { type: 'string' },
969
+ description: 'Files deleted during this session. Accumulated incrementally.',
970
+ },
971
+ blockReason: {
972
+ type: 'string',
973
+ description: 'Set a blocker on this ticket. Requires a prior discovery via report_discovery.',
974
+ },
975
+ clearBlockReason: {
976
+ type: 'boolean',
977
+ description: 'Clear existing blocker and resume work.',
870
978
  },
871
979
  },
872
980
  required: ['ticketId'],
@@ -883,7 +991,7 @@ Report types:
883
991
  - 'implementation': Progress, velocity, completions (replaces get_implementation_summary)
884
992
  - 'time': Estimated vs actual hours (replaces get_time_report)
885
993
  - 'blockers': Blocked tickets with reasons (replaces get_blockers_report)
886
- - 'work': Completed work summary (replaces get_work_summary)
994
+ - 'work': Completed work summary from WorkSession records (replaces get_work_summary)
887
995
 
888
996
  Format options:
889
997
  - 'json': Full structured data (default)
@@ -1475,10 +1583,6 @@ At least one scope filter (projectId, specificationId, or epicId) is required.`,
1475
1583
  items: { type: 'string' },
1476
1584
  description: 'Array of ticket IDs this ticket depends on',
1477
1585
  },
1478
- skipValidation: {
1479
- type: 'boolean',
1480
- description: 'Skip validation and completeness scoring. Default: false',
1481
- },
1482
1586
  validationStrictness: {
1483
1587
  type: 'string',
1484
1588
  enum: ['lenient', 'normal', 'strict'],
@@ -1587,11 +1691,6 @@ At least one scope filter (projectId, specificationId, or epicId) is required.`,
1587
1691
  type: 'object',
1588
1692
  description: 'Technical details like stack, endpoints (JSON) (optional)',
1589
1693
  },
1590
- skipValidation: {
1591
- type: 'boolean',
1592
- description: 'Skip validation and completeness scoring (default: false)',
1593
- default: false,
1594
- },
1595
1694
  validationStrictness: {
1596
1695
  type: 'string',
1597
1696
  enum: ['lenient', 'normal', 'strict'],
@@ -2043,119 +2142,6 @@ At least one scope filter (projectId, specificationId, or epicId) is required.`,
2043
2142
  // ========================================================================
2044
2143
  // Bulk Operations
2045
2144
  // ========================================================================
2046
- {
2047
- name: 'bulk_create_tickets',
2048
- description: 'Create multiple tickets in an epic with optional inline dependencies in a single atomic transaction. Reduces 15+ API calls to one.',
2049
- inputSchema: {
2050
- type: 'object',
2051
- properties: {
2052
- epicId: {
2053
- type: 'string',
2054
- description: 'The ID of the epic to create tickets in',
2055
- },
2056
- tickets: {
2057
- type: 'array',
2058
- description: 'Array of ticket definitions to create',
2059
- items: {
2060
- type: 'object',
2061
- properties: {
2062
- title: { type: 'string', description: 'Ticket title (required)' },
2063
- description: { type: 'string', description: 'Ticket description' },
2064
- estimatedHours: { type: 'number', description: 'Estimated hours' },
2065
- complexity: {
2066
- type: 'string',
2067
- enum: ['small', 'medium', 'large', 'xlarge'],
2068
- description: 'Complexity level',
2069
- },
2070
- priority: {
2071
- type: 'string',
2072
- enum: ['high', 'medium', 'low'],
2073
- description: 'Priority level',
2074
- },
2075
- acceptanceCriteria: {
2076
- type: 'array',
2077
- items: { type: 'string' },
2078
- description: 'Acceptance criteria',
2079
- },
2080
- implementation: {
2081
- type: 'object',
2082
- description: 'Implementation details (filesToCreate, filesToModify, steps, etc.)',
2083
- },
2084
- technicalDetails: {
2085
- type: 'object',
2086
- description: 'Technical details (files, stack, endpoints, etc.)',
2087
- },
2088
- tags: {
2089
- type: 'array',
2090
- items: { type: 'string' },
2091
- description: 'Tags',
2092
- },
2093
- },
2094
- required: ['title'],
2095
- },
2096
- },
2097
- dependencies: {
2098
- type: 'array',
2099
- description: 'Optional inline dependencies using array indexes',
2100
- items: {
2101
- type: 'object',
2102
- properties: {
2103
- ticket: {
2104
- type: 'number',
2105
- description: 'Index of the ticket in the tickets array',
2106
- },
2107
- dependsOn: {
2108
- oneOf: [
2109
- { type: 'number', description: 'Index of the ticket it depends on' },
2110
- {
2111
- type: 'array',
2112
- items: { type: 'number' },
2113
- description: 'Indexes of the tickets it depends on',
2114
- },
2115
- ],
2116
- description: 'Index(es) of the ticket(s) it depends on',
2117
- },
2118
- },
2119
- required: ['ticket', 'dependsOn'],
2120
- },
2121
- },
2122
- onError: {
2123
- type: 'string',
2124
- enum: ['rollback', 'continue'],
2125
- description: "Error handling mode: 'rollback' stops and undoes all, 'continue' skips failures (default: 'continue')",
2126
- },
2127
- },
2128
- required: ['epicId', 'tickets'],
2129
- },
2130
- },
2131
- {
2132
- name: 'get_job_status',
2133
- description: 'Get the status of an asynchronous bulk operation job. For batches >= 20 items, bulk operations return a job ID for async processing.',
2134
- inputSchema: {
2135
- type: 'object',
2136
- properties: {
2137
- jobId: {
2138
- type: 'string',
2139
- description: 'The job ID returned from a bulk operation',
2140
- },
2141
- },
2142
- required: ['jobId'],
2143
- },
2144
- },
2145
- {
2146
- name: 'cancel_job',
2147
- description: 'Cancel a running async job. Only queued or processing jobs can be cancelled.',
2148
- inputSchema: {
2149
- type: 'object',
2150
- properties: {
2151
- jobId: {
2152
- type: 'string',
2153
- description: 'The job ID returned from a bulk operation',
2154
- },
2155
- },
2156
- required: ['jobId'],
2157
- },
2158
- },
2159
2145
  {
2160
2146
  name: 'bulk_add_dependencies',
2161
2147
  description: 'Add multiple dependencies between tickets in a single atomic transaction.',
@@ -3223,19 +3209,9 @@ export function createToolHandlers(apiClient) {
3223
3209
  summary: args.summary,
3224
3210
  filesModified: args.filesModified,
3225
3211
  filesCreated: args.filesCreated,
3212
+ filesDeleted: args.filesDeleted,
3226
3213
  actualHours: args.actualHours,
3227
- validated: args.validated, // MCI-066: simple validation flag
3228
- validation: args.validation, // MCI-064: validation flags
3229
- skipCriteriaCheck: args.skipCriteriaCheck, // MCI-065: skip criteria check
3230
- });
3231
- },
3232
- report_progress: async (_client, args) => {
3233
- validateRequired(args, 'ticketId', 'progress');
3234
- validateRange(args.progress, 'progress', 0, 100);
3235
- return await apiClient.call('report_progress', {
3236
- ticketId: args.ticketId,
3237
- progress: args.progress,
3238
- message: args.message,
3214
+ validation: args.validation,
3239
3215
  });
3240
3216
  },
3241
3217
  pause_work_session: async (_client, args) => {
@@ -3250,6 +3226,23 @@ export function createToolHandlers(apiClient) {
3250
3226
  ticketId: args.ticketId,
3251
3227
  });
3252
3228
  },
3229
+ action_work_session: async (_client, args) => {
3230
+ validateRequired(args, 'ticketId');
3231
+ return await apiClient.call('action_work_session', {
3232
+ ticketId: args.ticketId,
3233
+ steps: args.steps,
3234
+ allStepsDone: args.allStepsDone,
3235
+ acceptanceCriteria: args.acceptanceCriteria,
3236
+ allACValidated: args.allACValidated,
3237
+ testResults: args.testResults,
3238
+ notes: args.notes,
3239
+ filesCreated: args.filesCreated,
3240
+ filesModified: args.filesModified,
3241
+ filesDeleted: args.filesDeleted,
3242
+ blockReason: args.blockReason,
3243
+ clearBlockReason: args.clearBlockReason,
3244
+ });
3245
+ },
3253
3246
  // ========================================================================
3254
3247
  // Status & Analytics Tools
3255
3248
  // ========================================================================
@@ -3400,7 +3393,6 @@ export function createToolHandlers(apiClient) {
3400
3393
  validateRequired(args, 'epicId', 'title');
3401
3394
  const responseMode = getResponseModeFromArgs(args);
3402
3395
  // Extract validation options
3403
- const skipValidation = args.skipValidation === true;
3404
3396
  const validationStrictness = args.validationStrictness === 'lenient' || args.validationStrictness === 'strict'
3405
3397
  ? args.validationStrictness
3406
3398
  : 'normal';
@@ -3421,21 +3413,19 @@ export function createToolHandlers(apiClient) {
3421
3413
  dependsOn: args.dependsOn,
3422
3414
  notes: args.notes, // F8-001: Add missing notes field
3423
3415
  });
3424
- // Run validation unless skipped
3425
- if (!skipValidation) {
3426
- const validation = validateTicket({
3427
- complexity: args.complexity,
3428
- implementation: args.implementation,
3429
- technicalDetails: args.technicalDetails,
3430
- notes: args.notes,
3431
- acceptanceCriteria: args.acceptanceCriteria,
3432
- });
3433
- // Filter warnings by strictness level
3434
- const warnings = filterWarningsByStrictness(validation.warnings, validationStrictness);
3435
- // Add validation results to response
3436
- result.warnings = warnings;
3437
- result.completenessScore = validation.completenessScore;
3438
- }
3416
+ // Run validation
3417
+ const validation = validateTicket({
3418
+ complexity: args.complexity,
3419
+ implementation: args.implementation,
3420
+ technicalDetails: args.technicalDetails,
3421
+ notes: args.notes,
3422
+ acceptanceCriteria: args.acceptanceCriteria,
3423
+ });
3424
+ // Filter warnings by strictness level
3425
+ const warnings = filterWarningsByStrictness(validation.warnings, validationStrictness);
3426
+ // Add validation results to response
3427
+ result.warnings = warnings;
3428
+ result.completenessScore = validation.completenessScore;
3439
3429
  return formatWriteResponse(result, responseMode, 'Ticket created');
3440
3430
  },
3441
3431
  import_specification: async (_client, args) => {
@@ -3454,31 +3444,28 @@ export function createToolHandlers(apiClient) {
3454
3444
  validateRequired(args, 'ticketId');
3455
3445
  const responseMode = getResponseModeFromArgs(args);
3456
3446
  // Extract validation options
3457
- const skipValidation = args.skipValidation === true;
3458
3447
  const validationStrictness = args.validationStrictness === 'lenient' || args.validationStrictness === 'strict'
3459
3448
  ? args.validationStrictness
3460
3449
  : 'normal';
3461
3450
  // Get current ticket to calculate previous completeness score
3462
3451
  let previousCompletenessScore;
3463
- if (!skipValidation) {
3464
- try {
3465
- const currentTicket = await apiClient.call('get_ticket', {
3466
- ticketId: args.ticketId,
3467
- });
3468
- // Calculate previous completeness score
3469
- const prevValidation = validateTicket({
3470
- complexity: currentTicket.complexity,
3471
- implementation: currentTicket.implementation,
3472
- technicalDetails: currentTicket.technicalDetails,
3473
- notes: currentTicket.notes,
3474
- acceptanceCriteria: currentTicket.acceptanceCriteria,
3475
- });
3476
- previousCompletenessScore = prevValidation.completenessScore;
3477
- }
3478
- catch {
3479
- // If we can't get the current ticket, skip previous score tracking
3480
- previousCompletenessScore = undefined;
3481
- }
3452
+ try {
3453
+ const currentTicket = await apiClient.call('get_ticket', {
3454
+ ticketId: args.ticketId,
3455
+ });
3456
+ // Calculate previous completeness score
3457
+ const prevValidation = validateTicket({
3458
+ complexity: currentTicket.complexity,
3459
+ implementation: currentTicket.implementation,
3460
+ technicalDetails: currentTicket.technicalDetails,
3461
+ notes: currentTicket.notes,
3462
+ acceptanceCriteria: currentTicket.acceptanceCriteria,
3463
+ });
3464
+ previousCompletenessScore = prevValidation.completenessScore;
3465
+ }
3466
+ catch {
3467
+ // If we can't get the current ticket, skip previous score tracking
3468
+ previousCompletenessScore = undefined;
3482
3469
  }
3483
3470
  // Update the ticket
3484
3471
  const result = await apiClient.call('update_ticket', {
@@ -3497,25 +3484,23 @@ export function createToolHandlers(apiClient) {
3497
3484
  technicalDetails: args.technicalDetails,
3498
3485
  blockReason: args.blockReason,
3499
3486
  });
3500
- // Run validation on updated ticket unless skipped
3501
- if (!skipValidation) {
3502
- // Use the updated values (from args) merged with existing values (from result)
3503
- // The result should contain the final state after update
3504
- const validation = validateTicket({
3505
- complexity: (args.complexity || result.complexity),
3506
- implementation: (args.implementation || result.implementation),
3507
- technicalDetails: (args.technicalDetails || result.technicalDetails),
3508
- notes: (args.notes || result.notes),
3509
- acceptanceCriteria: (args.acceptanceCriteria || result.acceptanceCriteria),
3510
- });
3511
- // Filter warnings by strictness level
3512
- const warnings = filterWarningsByStrictness(validation.warnings, validationStrictness);
3513
- // Add validation results to response
3514
- result.warnings = warnings;
3515
- result.completenessScore = validation.completenessScore;
3516
- if (previousCompletenessScore !== undefined) {
3517
- result.previousCompletenessScore = previousCompletenessScore;
3518
- }
3487
+ // Run validation on updated ticket
3488
+ // Use the updated values (from args) merged with existing values (from result)
3489
+ // The result should contain the final state after update
3490
+ const validation = validateTicket({
3491
+ complexity: (args.complexity || result.complexity),
3492
+ implementation: (args.implementation || result.implementation),
3493
+ technicalDetails: (args.technicalDetails || result.technicalDetails),
3494
+ notes: (args.notes || result.notes),
3495
+ acceptanceCriteria: (args.acceptanceCriteria || result.acceptanceCriteria),
3496
+ });
3497
+ // Filter warnings by strictness level
3498
+ const warnings = filterWarningsByStrictness(validation.warnings, validationStrictness);
3499
+ // Add validation results to response
3500
+ result.warnings = warnings;
3501
+ result.completenessScore = validation.completenessScore;
3502
+ if (previousCompletenessScore !== undefined) {
3503
+ result.previousCompletenessScore = previousCompletenessScore;
3519
3504
  }
3520
3505
  return formatWriteResponse(result, responseMode, 'Ticket updated');
3521
3506
  },
@@ -3630,56 +3615,6 @@ export function createToolHandlers(apiClient) {
3630
3615
  // ========================================================================
3631
3616
  // Bulk Operations
3632
3617
  // ========================================================================
3633
- bulk_create_tickets: async (_client, args) => {
3634
- validateRequired(args, 'epicId', 'tickets');
3635
- if (!Array.isArray(args.tickets) || args.tickets.length === 0) {
3636
- throw new Error('tickets array is required and must not be empty');
3637
- }
3638
- // Validate each ticket has a title
3639
- const tickets = args.tickets;
3640
- for (let i = 0; i < tickets.length; i++) {
3641
- if (!tickets[i].title || typeof tickets[i].title !== 'string') {
3642
- throw new Error(`tickets[${i}].title is required and must be a string`);
3643
- }
3644
- }
3645
- // Validate dependencies if provided
3646
- if (args.dependencies && Array.isArray(args.dependencies)) {
3647
- const deps = args.dependencies;
3648
- for (let i = 0; i < deps.length; i++) {
3649
- const dep = deps[i];
3650
- if (typeof dep.ticket !== 'number' || dep.ticket < 0 || dep.ticket >= tickets.length) {
3651
- throw new Error(`dependencies[${i}].ticket must be a valid index in tickets array (0-${tickets.length - 1})`);
3652
- }
3653
- const dependsOn = Array.isArray(dep.dependsOn) ? dep.dependsOn : [dep.dependsOn];
3654
- for (const idx of dependsOn) {
3655
- if (typeof idx !== 'number' || idx < 0 || idx >= tickets.length) {
3656
- throw new Error(`dependencies[${i}].dependsOn contains invalid index ${idx} (must be 0-${tickets.length - 1})`);
3657
- }
3658
- if (idx === dep.ticket) {
3659
- throw new Error(`dependencies[${i}]: ticket cannot depend on itself`);
3660
- }
3661
- }
3662
- }
3663
- }
3664
- return await apiClient.call('bulk_create_tickets', {
3665
- epicId: args.epicId,
3666
- tickets: args.tickets,
3667
- dependencies: args.dependencies,
3668
- onError: args.onError,
3669
- });
3670
- },
3671
- get_job_status: async (_client, args) => {
3672
- validateRequired(args, 'jobId');
3673
- return await apiClient.call('get_job_status', {
3674
- jobId: args.jobId,
3675
- });
3676
- },
3677
- cancel_job: async (_client, args) => {
3678
- validateRequired(args, 'jobId');
3679
- return await apiClient.call('cancel_job', {
3680
- jobId: args.jobId,
3681
- });
3682
- },
3683
3618
  bulk_add_dependencies: async (_client, args) => {
3684
3619
  validateRequired(args, 'dependencies');
3685
3620
  if (!Array.isArray(args.dependencies) || args.dependencies.length === 0) {