Skip to content

feat(cards): move and copy cards to other projects - #973

Open
robzolkos wants to merge 19 commits into
mainfrom
card-moves-cross-project
Open

robzolkos wants to merge 19 commits into
mainfrom
card-moves-cross-project

Conversation

@robzolkos

@robzolkos robzolkos commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator

Why

On the web, a card's Move… and Copy… menus send it to a card table in any project. The CLI could only cross projects through a wormhole someone had already set up. It couldn't copy a card, and after a wormhole move it never said where the card landed. CLI users miss all three.

What changed

  • cards move <card> --to-project <project> (or --to-card-table) moves the card to that project's card table. cards copy <card> copies it onto its own table or, with --to-project, another project's, and --include-comments brings the comments. Both wait for Basecamp to finish and print the new card. As on the web, the card lands in Triage, or in Done if it's completed.
  • --to-wormhole now prints the card that landed, and --to with another project's column points to --to-project.
  • cards show, cards steps and todos show follow a moved card or to-do from its old id to its new one.

Scope

  • Uses basecamp-sdk v0.30.0, the release of feat(recordings): move and copy recordings to another project basecamp-sdk#1053. v0.30.0 also adds the SDK's project-logo operations. The built-in MCP server serves remove_project_logo and skips the upload, as it skips the other uploads. CLI logo commands come in a separate PR stacked on this one.
  • The bump also adds move_recording, copy_recording and get_filing to the built-in MCP server's catalog.

Tradeoffs

A copy is never retried. A copy that fails to start with a 5xx, or that's still running when the wait ends, says "don't copy again" and is not marked retryable, because Basecamp may already be making it.

Verification

  • bin/ci passes.
  • A local bc3 harness ran each web card path through the CLI: columns, On hold, Done, another table, wormhole, move to a project, and copy to a project and to its own table. Every path works, and a column from another project still fails. By hand, --include-comments carried a comment and a completed card landed in Done.
  • An independent GPT-6.1-sol review converged after 4 rounds. Each finding it raised has a test that fails without the fix.

Summary by cubic

Adds cross-project card moves and copies to the CLI, matching the web's Move… and Copy… menus.

  • cards move <card> --to-project <project> (or --to-card-table) files the card into that project's card table; it lands in Triage, or Done if completed, under a new id, with a bounded backoff wait on Basecamp's background move.
  • New cards copy <card> copies onto its own table or, with --to-project, another project's; --include-comments carries the comments. A copy that fails is never retried unless Basecamp answered 4xx, because a 5xx, dropped connection, or wait time-out may already have started it; rate limits stay retryable.
  • --to-wormhole now prints where the card landed, and cards show, cards steps, and todos show follow a moved record from its old id to its new one; --to a foreign column suggests --to-project.
  • Source cards, card URLs, and destination card tables must belong to the session account on a trusted host, an explicit --in/--project must match the card's own project, and a destination table URL whose project disagrees with --to-project is refused. A record that reads back as any type other than a card is refused.

Scope
Bumps basecamp-sdk to v0.30.0, the release of the move, copy, and filing operations. It also brings project logos: the built-in MCP server serves remove_project_logo and skips the multipart upload.

Written for commit 539bc14. Summary will update on new commits.

View guided diff Turn on auto-fix

…rd table name two tables share

A 5xx or dropped connection on the copy request can follow a copy Basecamp
already started, so the error is no longer retryable and says how to check.
A rate limit stays retryable: Basecamp refuses it before copying. A
--to-card-table name that matches two tables now lists both instead of
taking the first.
…mit gate

output.AsError renders a gate error found anywhere in the cause chain in place
of the error around it, so a gate trip while polling turned "do not copy it
again" into a retryable rate limit. The in-progress and read-back errors now
keep their cause as text.
Reading a moved record's old id redirects to the record at its new id, but
cards show, cards steps and todos show kept using the old id for the full step
list, comments and follow-up commands, which then 404.
@robzolkos
robzolkos requested a review from a team as a code owner October 27, 2026 16:57
Copilot AI balanced review requested due to automatic review settings October 27, 2026 16:57
@github-actions github-actions Bot added commands CLI command implementations sdk SDK wrapper and provenance tests Tests (unit and e2e) output Output formatting and presentation docs deps labels Oct 27, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

URL validation and explicit source-project checks remain incomplete, and uncertain copy failures can omit duplicate-copy warnings.

4 open issues
What changed in this PR

Adds cross-project card moves and copies to the CLI, including background-operation tracking and support for reading moved records through their old IDs.

Changes:

  • Adds cards copy and destination flags for card moves, with optional comment copying.
  • Reports landed cards after moves and follows updated IDs when reading cards, to-dos and steps.
  • Updates the SDK dependency, MCP catalog, documentation and tests. The SDK release and repinning remain prerequisites for merging.

[!TIP]
If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.

File Description
nix/​package.nix Updates the dependency vendor hash.
internal/​version/​sdk-provenance.json Records the pinned SDK revision.
internal/​output/​codes.go Adds the background-work status code.
internal/​mcpserver/​testdata/​catalog_snapshot.txt Lists three new recording actions.
internal/​mcpserver/​model/​PROVENANCE.json Updates model source provenance.
internal/​mcpserver/​model/​openapi.json Adds move, copy and filing schemas.
internal/​mcpserver/​model/​behavior-model.json Adds operation behavior metadata.
internal/​mcpserver/​catalog_test.go Updates the served operation count.
internal/​commands/​todos.go Uses moved to-dos’ new IDs.
internal/​commands/​subtasks_test.go Covers step reads after record moves.
internal/​commands/​commands.go Registers the copy action in the catalog.
internal/​commands/​cards.go Implements card filing, polling and destination resolution.
internal/​commands/​cards_test.go Tests landed and pending wormhole moves.
internal/​commands/​cards_filing_test.go Covers filing destinations, failures and polling.
go.sum Updates SDK checksums.
go.mod Pins the SDK commit providing filing APIs.
e2e/​smoke/​smoke_cards_write.bats Adds a card-copy smoke test.
API-COVERAGE.md Documents filing endpoint coverage.
.surface Records the new command and flags.

Review effort: Balanced

Comment thread internal/commands/cards.go Outdated
Comment thread internal/commands/cards.go Outdated
Comment thread internal/commands/cards.go Outdated
Comment thread internal/commands/cards.go Outdated
…ther project

cards move --to-project/--to-card-table and cards copy took the card id and
ignored an explicit --in, --project or card URL bucket, so a card in project
123 moved even when the caller said 888. They now check those against the
card's own project before filing it, as the wormhole move already did. The
check is renamed rejectCardProjectConflict and its hint no longer names
wormholes. Config and interactive defaults are not constraints.
…ss-table moves and copies

cards copy and cards move --to-project/--to-card-table reduced a pasted URL
to its trailing number, so a look-alike host, another account's card, a to-do
or a cards listing filed whichever card in this account had that number. The
URL must now be a single card on a trusted Basecamp host in the session's
account, checked before any request, as files replace and boost update do.
…o-project

A destination card table URL was reduced to its number without checking the
host or account, and its project was ignored when --to-project named another.
It is now held to the same checks as the card URL, a project that disagrees
with --to-project is refused, and the destination is resolved before the card
is read so a bad URL is refused before any request.
A dropped connection or an unreadable answer to the copy request came back as
a plain error, which skipped the "may have started the copy" warning because
only retryable errors got it. The rule is inverted: every failed copy request
gets the warning and is not retryable, except a 4xx answer, which shows
Basecamp refused it, and a rate limit, which stays retryable. Moves are
unchanged.
Copilot AI balanced review requested due to automatic review settings October 27, 2026 17:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

URL-only destination resolution can move or copy a card into a project different from the one named in the supplied URL.

1 open issue
4 resolved since last review

Review effort: Balanced

Comment thread internal/commands/cards.go Outdated
A moved message's old id redirects through the card endpoint to the message,
so the read alone does not prove the id names a card.
Copilot AI balanced review requested due to automatic review settings October 27, 2026 18:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The PR explicitly requires an SDK release and repin before merging, which still needs final human verification.

1 open issue

Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 27, 2026 18:27

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The documented requirement to replace the SDK PR commit with a released version remains outstanding and needs confirmation before merge.

1 open issue

Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 27, 2026 18:37

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Asynchronous cross-project mutations need final human verification, and the stated SDK release-and-repin requirement remains outstanding.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

@robzolkos

Copy link
Copy Markdown
Collaborator Author

Review summary for 3a1d3d1f. Every review thread is resolved, and Copilot reports no open issues on this head. GPT-6.1-sol reports no findings (round 8). bin/ci passes. Copilot's two review-level notes:

  • Human verification of cross-project moves and copies. A local bc3 harness ran each web card path through this branch: columns, On hold, Done, another table, wormhole (reports the new id), move to another project, and copy to another project and to the card's own table. All worked, and a column from another project still failed. By hand, --include-comments carried a comment, a completed card landed in Done, and a moved message's old id was refused as not a card. The final sign-off is the maintainer's.
  • SDK release and repin. This PR pins the unreleased feat(recordings): move and copy recordings to another project basecamp-sdk#1053 commit. After #1053 merges and is released, this PR gets repinned with make bump-sdk and bin/ci reruns before merge.

v0.30.0 is the release of the move, copy and filing operations this branch
used from a pre-release pin. It also brings project logos: the built-in MCP
server serves remove_project_logo and skips the multipart UpdateProjectLogo,
as it skips the other uploads.
@robzolkos

Copy link
Copy Markdown
Collaborator Author

Repinned to the released basecamp-sdk v0.30.0 (539bc144). bin/ci passes, and the live harness gives the same results on every card path. GPT-6.1-sol pointed out that v0.30.0's Projects().UpdateLogo and RemoveLogo have no CLI commands yet. That's deliberate: they arrive in the project-logo PR stacked on this one, which can go out in the same CLI release.

Copilot AI balanced review requested due to automatic review settings October 27, 2026 20:05

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approved

No active review issues are reported for the card move and copy changes.

No open issues 🎉

Review effort: Balanced

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

commands CLI command implementations deps docs output Output formatting and presentation sdk SDK wrapper and provenance tests Tests (unit and e2e)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants