Release v1.11.0 — Lenient Bulk Input Coercion #44

Merged
Mike Bros merged 10 commits from release/1.11.0 into main 2026-03-18 05:39:57 +00:00
Contributor

Release v1.11.0 — Lenient Bulk Input Coercion

Changes

  • OP#2494: Add z.preprocess reshaping for bulk_update_work_packages flat input
  • OP#2495: Add coaching error wrapper to bulk tool Zod schemas
  • OP#2496: Add z.preprocess reshaping for bulk_create_work_packages flat input
  • OP#2497: Add z.preprocess reshaping for bulk_create_relations flat input
  • OP#2498: Test lenient bulk coercion and coaching errors (16 new tests, 303 total)
  • OP#2499: Update CHANGELOG for v1.11.0

Summary

When AIs call bulk tools with the wrong input shape (e.g., flat work_package_ids + shared fields instead of items array), the server now auto-reshapes the input and succeeds. When reshaping isn't possible, it returns a coaching error with a concrete example of the correct format.

All 5 bulk tools gained lenient input handling:

  • bulk_update: accepts { work_package_ids: [1,2], status: "Closed" } shorthand
  • bulk_create: accepts single object without items wrapper
  • bulk_create_relations: accepts single relation without items wrapper
  • bulk_transition/delete: accept { items: [{ id: N }] } format

Checklist

  • All version tasks closed in Gravity PM
  • Tests passing (303/303)
  • Lint passing (biome clean)
  • Version file matches Gravity PM version

References

Version: 1.11.0 (Gravity PM)

## Release v1.11.0 — Lenient Bulk Input Coercion ### Changes - OP#2494: Add z.preprocess reshaping for bulk_update_work_packages flat input - OP#2495: Add coaching error wrapper to bulk tool Zod schemas - OP#2496: Add z.preprocess reshaping for bulk_create_work_packages flat input - OP#2497: Add z.preprocess reshaping for bulk_create_relations flat input - OP#2498: Test lenient bulk coercion and coaching errors (16 new tests, 303 total) - OP#2499: Update CHANGELOG for v1.11.0 ### Summary When AIs call bulk tools with the wrong input shape (e.g., flat `work_package_ids` + shared fields instead of `items` array), the server now auto-reshapes the input and succeeds. When reshaping isn't possible, it returns a coaching error with a concrete example of the correct format. All 5 bulk tools gained lenient input handling: - **bulk_update**: accepts `{ work_package_ids: [1,2], status: "Closed" }` shorthand - **bulk_create**: accepts single object without items wrapper - **bulk_create_relations**: accepts single relation without items wrapper - **bulk_transition/delete**: accept `{ items: [{ id: N }] }` format ### Checklist - [x] All version tasks closed in Gravity PM - [x] Tests passing (303/303) - [x] Lint passing (biome clean) - [x] Version file matches Gravity PM version ### References Version: 1.11.0 (Gravity PM)
Accept both { items: [{id, ...fields}] } (canonical) and
{ work_package_ids: [id, ...], field: value } (flat shorthand) as input.
The flat shorthand fans shared fields across all IDs into a canonical
items array. Returns a coaching error with examples when neither pattern
matches.

Closes OP#2494

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Accept both { items: [...] } (canonical) and a single object with
project_id + subject (auto-wrapped into items array). Returns coaching
error with correct format example on unrecognized input.

Closes OP#2496

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Accept both { items: [...] } (canonical) and a single object with
from_id + to_id + type (auto-wrapped). Returns coaching error with
correct format example on unrecognized input.

Closes OP#2497

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
bulk_transition_work_packages and bulk_delete_work_packages now accept
{ items: [{ id: N }] } in addition to { work_package_ids: [N] }. IDs
are extracted automatically. Shared idArrayPreprocess helper replaces
duplicated coercion logic. resolveWpIds provides coaching errors when
neither pattern matches.

Closes OP#2495

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
16 new tests covering:
- bulk_update flat shorthand (work_package_ids + shared fields)
- bulk_create single-object wrapping
- bulk_create_relations single-relation wrapping
- bulk_transition/delete items-format ID extraction
- Coaching error messages on invalid input for all tools

Also fixes err() call sites (pass Error object, not string) and adds
type defaults for bulk_create single-object shorthand.

Closes OP#2498

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Closes OP#2499

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
chore(release): bump version to 1.11.0
Some checks failed
CI / lint (pull_request) Failing after 9s
CI / security (pull_request) Successful in 9s
CI / test (pull_request) Successful in 1m40s
c47596118a
Refs OP#2501

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Author
Contributor

PR Review — Release v1.11.0 — Lenient Bulk Input Coercion

Verdict: Acceptable

303/303 tests passing. Lint clean. No security concerns.


Findings

🟡 Important (1)

1. Inline preprocess in bulk_update diverges from shared idArrayPreprocess
File: index.js, ~line 1763

The work_package_ids preprocess inside bulk_update_work_packages is written inline with slightly different behavior than the shared idArrayPreprocess helper used by bulk_transition and bulk_delete:

  • Missing: Position-based error messages for invalid CSV entries (Invalid ID at position N)
  • Missing: Empty-string validation in CSV parsing
  • Behavior difference: Invalid CSV entries silently coerce to NaN strings instead of throwing

The fix is straightforward — replace the inline preprocess with idArrayPreprocess:

work_package_ids: z
    .preprocess(idArrayPreprocess, z.array(z.number().int()).max(MAX_BULK_BATCH_SIZE))
    .optional()
    ...

This deduplicates the code and ensures consistent error handling across all bulk tools.

:blue_circle: Minor (2)

2. resolveWpIds silently filters invalid IDs from items array
File: index.js, ~line 1930

When extracting IDs from items: [{ id: N }], invalid IDs are silently filtered:

.filter((id) => typeof id === "number" && !Number.isNaN(id));

If an AI sends [{ id: "abc" }, { id: 2 }], only ID 2 would be processed with no warning about the dropped item. Consider logging or returning a warning for filtered IDs so the caller knows items were skipped.

3. CREATE_FIELDS constant defined inside function body
File: index.js, resolveBulkCreateItems

The CREATE_FIELDS array is defined inside the function, meaning it's recreated on every call. This matches the pattern used for BULK_UPDATE_SHARED_FIELDS (hoisted outside), so hoisting CREATE_FIELDS to module scope would be consistent. Not a performance concern — just style consistency.

Nitpick (1)

4. CHANGELOG gap: 1.8.0 → 1.11.0

The CHANGELOG jumps from 1.8.0 to 1.11.0. Versions 1.9.0, 1.10.0-alpha, and 1.10.1 have no entries. Pre-existing, not introduced by this PR, but worth addressing in a future housekeeping pass.


Summary

Well-executed feature. The approach of accepting both canonical and shorthand formats at the schema level (rather than fighting the MCP SDK with z.preprocess at the object level) is a pragmatic solution that works within SDK constraints. The resolver-per-tool pattern is clean and easy to follow.

Test coverage is thorough — 16 new tests covering all coercion paths (canonical passthrough, shorthand reshaping, coaching errors) for all 5 bulk tools. The err(e.message)err(e) bugfix is a good catch.

The only item worth fixing before merge is finding #1 (inline preprocess inconsistency) — it's a one-line change to use the shared helper.

Severity Count
Blocking 0
Important 1
Minor 2
Nitpick 1

Reviewed by Gravity Bot — advisory only, does not approve or reject

## PR Review — Release v1.11.0 — Lenient Bulk Input Coercion **Verdict: Acceptable** 303/303 tests passing. Lint clean. No security concerns. --- ### Findings #### :yellow_circle: Important (1) **1. Inline preprocess in `bulk_update` diverges from shared `idArrayPreprocess`** *File: index.js, ~line 1763* The `work_package_ids` preprocess inside `bulk_update_work_packages` is written inline with slightly different behavior than the shared `idArrayPreprocess` helper used by `bulk_transition` and `bulk_delete`: - **Missing:** Position-based error messages for invalid CSV entries (`Invalid ID at position N`) - **Missing:** Empty-string validation in CSV parsing - **Behavior difference:** Invalid CSV entries silently coerce to NaN strings instead of throwing The fix is straightforward — replace the inline preprocess with `idArrayPreprocess`: ```js work_package_ids: z .preprocess(idArrayPreprocess, z.array(z.number().int()).max(MAX_BULK_BATCH_SIZE)) .optional() ... ``` This deduplicates the code and ensures consistent error handling across all bulk tools. #### :blue_circle: Minor (2) **2. `resolveWpIds` silently filters invalid IDs from items array** *File: index.js, ~line 1930* When extracting IDs from `items: [{ id: N }]`, invalid IDs are silently filtered: ```js .filter((id) => typeof id === "number" && !Number.isNaN(id)); ``` If an AI sends `[{ id: "abc" }, { id: 2 }]`, only ID 2 would be processed with no warning about the dropped item. Consider logging or returning a warning for filtered IDs so the caller knows items were skipped. **3. `CREATE_FIELDS` constant defined inside function body** *File: index.js, `resolveBulkCreateItems`* The `CREATE_FIELDS` array is defined inside the function, meaning it's recreated on every call. This matches the pattern used for `BULK_UPDATE_SHARED_FIELDS` (hoisted outside), so hoisting `CREATE_FIELDS` to module scope would be consistent. Not a performance concern — just style consistency. #### :white_circle: Nitpick (1) **4. CHANGELOG gap: 1.8.0 → 1.11.0** The CHANGELOG jumps from 1.8.0 to 1.11.0. Versions 1.9.0, 1.10.0-alpha, and 1.10.1 have no entries. Pre-existing, not introduced by this PR, but worth addressing in a future housekeeping pass. --- ### Summary Well-executed feature. The approach of accepting both canonical and shorthand formats at the schema level (rather than fighting the MCP SDK with `z.preprocess` at the object level) is a pragmatic solution that works within SDK constraints. The resolver-per-tool pattern is clean and easy to follow. **Test coverage is thorough** — 16 new tests covering all coercion paths (canonical passthrough, shorthand reshaping, coaching errors) for all 5 bulk tools. The `err(e.message)` → `err(e)` bugfix is a good catch. The only item worth fixing before merge is finding #1 (inline preprocess inconsistency) — it's a one-line change to use the shared helper. | Severity | Count | |----------|-------| | Blocking | 0 | | Important | 1 | | Minor | 2 | | Nitpick | 1 | --- *Reviewed by Gravity Bot — advisory only, does not approve or reject*
fix(bulk): use shared idArrayPreprocess in bulk_update_work_packages
Some checks failed
CI / security (pull_request) Successful in 7s
CI / lint (pull_request) Failing after 1m34s
CI / test (pull_request) Successful in 1m52s
74bf9066ff
Replace the inline preprocess with the shared idArrayPreprocess helper
for consistent CSV validation and position-based error messages across
all bulk tools. Also hoist idArrayPreprocess before its first usage to
avoid const temporal dead zone.

Refs OP#2501

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Author
Contributor

PR Followup — Review Findings Addressed

Fixed (v1.11.0 — this branch)

# Severity Finding Resolution
1 Important Inline preprocess diverges from shared idArrayPreprocess Fixed in 74bf906 — replaced inline preprocess with shared helper, hoisted declaration before first usage

Deferred (backlog)

# Severity Finding Rationale
2 Minor resolveWpIds silently filters invalid IDs Low-risk edge case, deferred to backlog
3 Minor CREATE_FIELDS inside function body Style consistency, no functional impact
4 Nitpick CHANGELOG gap 1.8.0 → 1.11.0 Pre-existing, housekeeping task

Verification

  • 303/303 tests passing after fix
  • Branch updated: release/1.11.0 @ 74bf906

All important+ findings resolved. PR is ready for merge.


Followup by Gravity Bot

## PR Followup — Review Findings Addressed ### Fixed (v1.11.0 — this branch) | # | Severity | Finding | Resolution | |---|----------|---------|------------| | 1 | Important | Inline preprocess diverges from shared `idArrayPreprocess` | **Fixed** in `74bf906` — replaced inline preprocess with shared helper, hoisted declaration before first usage | ### Deferred (backlog) | # | Severity | Finding | Rationale | |---|----------|---------|-----------| | 2 | Minor | `resolveWpIds` silently filters invalid IDs | Low-risk edge case, deferred to backlog | | 3 | Minor | `CREATE_FIELDS` inside function body | Style consistency, no functional impact | | 4 | Nitpick | CHANGELOG gap 1.8.0 → 1.11.0 | Pre-existing, housekeeping task | ### Verification - 303/303 tests passing after fix - Branch updated: `release/1.11.0` @ `74bf906` All important+ findings resolved. PR is ready for merge. --- *Followup by Gravity Bot*
style: fix biome lint warnings in bulk coercion helpers
All checks were successful
CI / security (pull_request) Successful in 10s
CI / lint (pull_request) Successful in 12s
CI / test (pull_request) Successful in 1m16s
a08aa87df3
Replace unnecessary template literals with string literals and collapse
multi-line template concatenation into single template literal.

Refs OP#2501

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
feat(tools): cross-reference single and bulk tool descriptions
All checks were successful
CI / lint (pull_request) Successful in 9s
CI / security (pull_request) Successful in 12s
CI / test (pull_request) Successful in 35s
600ca26fca
Add "use bulk_X instead" hints to single-item tool descriptions
(create_work_package, update_work_package, delete_work_package,
create_relation) and add "Preferred over X" prefix to all bulk tool
descriptions. This helps AI clients discover and prefer the bulk
variants when operating on 2+ items.

Refs OP#2501

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Mike Bros approved these changes 2026-03-18 05:37:17 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
mike/open-project-sidecar-mcp!44
No description provided.