Files
pad/cmd
xarmian f5579300fb feat(cli,mcp): expose 'collection update' via CLI and MCP catalog (TASK-1510) (#572)
* feat(cli,mcp): expose 'collection update' via CLI and MCP catalog (TASK-1510)

The HTTP handler at handlers_collections.go::handleUpdateCollection
already supported PATCHing a collection's name, icon, description,
prefix, schema, settings, and sort_order (plus field-value migrations).
The CLI and MCP surfaces never exposed it, so agents couldn't rename
collections, swap icons, or reshape schemas — a hard blocker for the
adaptive /pad onboard playbook (TASK-1499) which needs to rewrite
seeded collections to match each project's actual vocabulary.

This wires both agent-facing surfaces to the existing handler:

- cmd/pad: new 'pad collection update <slug>' Cobra subcommand with
  --name / --icon / --description / --prefix / --schema / --fields /
  --sort-order flags. Only flags explicitly set are sent (uses
  cmd.Flags().Changed); --schema and --fields reuse the existing
  collectionSchemaJSONFromFlags helper so DSL parity stays.

- internal/mcp/catalog_collection: add 'update' action plus the
  slug, prefix, and sort_order params on padCollectionTool.

- internal/mcp/dispatch_http_routes: new mapCollectionUpdate handles
  the schema-object-vs-string coercion. The catalog declares schema
  as a JSON object for MCP ergonomics, but
  models.CollectionUpdate.Schema is *string — and its UnmarshalJSON
  only flexes settings, not schema. The mapper re-marshals object
  input to its JSON-string form before sending, symmetric to what
  the CLI does via collectionSchemaJSONFromFlags.

Tests cover canonical body, schema-object-to-string coercion
(round-trip through CollectionUpdate.UnmarshalJSON), schema-string
pass-through, empty-field omission, and required-arg validation.
catalog_readonly_test bijection + liveCmdhelpDoc fake updated.

Parent: PLAN-1496.

* fix(mcp): collection update — clear-on-empty + fields DSL parity per Codex review (round 1)

Addresses two P2 findings on PR #572:

1. The catalog advertises `icon=""` / `description=""` / `prefix=""`
   as clear-the-field, and the CLI flag help says the same, but the
   HTTP mapper filtered empty strings via `v != ""` — leaving MCP HTTP
   callers unable to clear fields the CLI can. Switched to key-presence
   semantics for the four string fields so explicit empty strings
   round-trip to the store (which honors *string("") as "clear").

2. The catalog advertises `fields OR schema` as mutually exclusive
   (mirroring `pad collection create`), but the mapper only consumed
   `schema`. An MCP HTTP request with `fields=...` produced a `{}` PATCH
   body silently. Extracted the DSL parser to a shared package
   (internal/collections/dsl.go::ParseFieldsDSL + FieldsDSLToSchemaJSON)
   so the CLI and the mapper share one parser; mapper now resolves
   fields-or-schema with the same mutual-exclusion guard the CLI has.

Tests added in dispatch_http_routes_extras_test.go:
- TestMapCollectionUpdate_EmptyStringClearsField
- TestMapCollectionUpdate_AcceptsFieldsDSL (round-trips through
  models.CollectionSchema to confirm the parsed shape)
- TestMapCollectionUpdate_RejectsFieldsAndSchemaTogether

cmd/pad/main.go's parseFieldsDSL becomes a one-line alias for
collections.ParseFieldsDSL so the CLI's behavior stays identical.

Parent: PLAN-1496, fixing PR #572 / TASK-1510.

* fix(mcp): collection update — use encodeSchemaForBody + normalize empty schema (round 2)

Addresses two more findings from Codex round 2 on PR #572:

1. P2: mapCollectionUpdate bypassed encodeSchemaForBody, so structured
   schemas didn't get label backfill and string schemas weren't
   validated before PATCH — diverged from collection create + CLI.
   Now reuses encodeSchemaForBody (the same encoder collection create
   uses at dispatch_http_routes.go:418), getting label-backfill via
   the Title-Case-of-key heuristic and shape validation for free.

2. P3: schema=null or schema="" plus a real fields=... update tripped
   the mutual-exclusion check. Now normalizes empty inputs as absent
   BEFORE checking exclusivity, matching the relaxed handling
   collection create has for optional empty params.

Tests:
- Renamed TestMapCollectionUpdate_PassesSchemaStringVerbatim to
  TestMapCollectionUpdate_AcceptsSchemaString — the new property is
  round-trip parity + label backfill, not verbatim pass-through.
- New TestMapCollectionUpdate_EmptySchemaDoesNotBlockFields covers
  both nil and empty-string schema combined with a real fields value.

Parent: PLAN-1496, addressing Codex round 2 on PR #572 / TASK-1510.
2026-05-17 01:45:40 -04:00
..