Conversation
There was a problem hiding this comment.
🟡 Changes recommended
The pinned SDK leaves the CLI uncompilable, and coverage, test, completion-breadcrumb, and documentation fixes remain unresolved.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
This PR groups template commands by kind and adds card-table templates plus templatification workflows while preserving legacy aliases.
Changes:
- Adds grouped project, to-do list, and card-table template commands.
- Adds duplication and templatification operations, including card triage support.
- Updates registrations, documentation, generated surfaces, tests, and smoke coverage.
File summaries
| File | Reviewed changes and findings |
|---|---|
skills/basecamp/SKILL.md |
Documents grouped commands and aliases. Nit (1 vote, line 1022): document card-table compatibility aliases. |
internal/commands/todolists.go |
Registers to-do-list templatification commands. |
internal/commands/templates.go |
Implements template operations. Critical (3 votes, line 148): bump the SDK and refresh provenance/checksums; the current pin leaves the CLI uncompilable. Moderate (2 votes, line 1128): add card-table templatification tests. Moderate (1 vote, line 267): add card-table creation tests. Nit (1 vote, line 148): update API coverage. Moderate (1 vote, line 330): preserve the destination project in completion breadcrumbs. |
internal/commands/templates_test.go |
Adds request and output tests. |
internal/commands/commands.go |
Updates the command catalog. Nit (1 vote, line 46): update the stale API coverage matrix and summary. |
internal/commands/commands_test.go |
Updates command registration fixtures. |
internal/commands/cardtables.go |
Adds the card-tables command group. |
internal/cli/root.go |
Registers card-tables at the root. |
e2e/templates.bats |
Adds CLI surface and validation coverage. Nit (1 vote, line 359): rename the duplicated test case. |
e2e/smoke/smoke_lifecycle.bats |
Excludes new mutations from lifecycle smoke coverage. |
e2e/smoke/smoke_account.bats |
Adds account-level template smoke coverage. |
.surface |
Updates generated command and flag metadata. |
Review details
Suppressed comments (6)
e2e/templates.bats:359
- This test name is duplicated at lines 302 and 359, so the Bats report cannot distinguish the template-group help check from the root card-tables help check. Rename this newly added case to describe the root command explicitly.
@test "card-tables without subcommand shows help" {
internal/commands/commands.go:46
- The new SDK-backed methods also leave the coverage matrix stale:
API-COVERAGE.mdstill reports 10 template operations and 192/192 tracked endpoints. The repository's SDK completeness bar inAGENTS.md:160-164requires a coverage row for every new SDK service method; update the matrix and summary alongside this catalog entry.
{Name: "card-tables", Category: "core", Description: "Manage card tables", Actions: []string{"templatify", "templatification"}},
internal/commands/templates.go:269
CreateLibraryCardTableis a new SDK operation, but the tests cover only card-table listing and duplication; unlike the to-do-list create path, there is no recording test for this endpoint's request body and rendered result. Add one alongsideTestTemplatesTodolistsCreateSendsNameAndDescriptionso a wrong route or payload cannot pass.
cardTable, err := app.Account().Templates().CreateLibraryCardTable(cmd.Context(), name)
if err != nil {
return convertSDKError(err)
internal/commands/templates.go:149
- The new template operations are not reflected in
API-COVERAGE.md: its Templates row still lists 10 operations and omits card-table template reads/creation, to-do-list template creation, and templatification. The repository's SDK sync rule inAGENTS.md:146-164requires an API-COVERAGE entry for every new SDK method, so update the matrix and totals with this change.
library, err := app.Account().Templates().GetLibraryCardTables(cmd.Context())
if err != nil {
internal/commands/templates.go:330
- The completion breadcrumb drops the destination project.
cards listalways resolves a project before using--card-table(seeinternal/commands/cards.go:511-525), so without a configured project this follow-up prompts or can target the wrong project and fail to find the duplicated board. Include the destination bucket in the breadcrumb so the generated command is self-contained.
Cmd: fmt.Sprintf("basecamp cards list --card-table %d%s", table.ID, contextArgs),
skills/basecamp/SKILL.md:1023
- The implementation exposes
copyandcopy-statusas aliases under both grouped kinds, but this documentation only mentionstemplates todolists. Please mentiontemplates card-tableshere too so users can discover the compatibility spelling for card-table templates.
`copy` and `copy-status` also work inside `templates todolists`. Prefer the grouped,
canonical spellings above when writing new commands.
- Files reviewed: 12/12 changed files
- Comments generated: 2
- Review effort level: Lite (auto)
Note
Copilot is running an experiment and ran this review at Lite.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| group: "card-tables", | ||
| offersTriage: true, | ||
| describeCompletedResult: func(save *basecamp.Templatification, contextArgs string) (string, []output.Breadcrumb, bool) { | ||
| table := save.DestinationCardTable | ||
| if table == nil { |
There was a problem hiding this comment.
🤖 Fair catch, and this one is not covered by a prior decision. You are right that nothing asserts move_cards_to_triage reaches the wire, and nothing asserts the card-table completion renders a real title and id rather than falling through to the generic line. TestTemplatifyOffersTriageOnlyForCardTables only checks flag registration, which proves the flag exists and not that it does anything.
Both behaviours were verified end to end against a live server, including running the same board twice with and without the flag and diffing the resulting template structure. But that is not in CI, so it does not protect against a regression, which is exactly your point about a silent fallback.
Adding a recording test for both.
7d91270 to
ec83f76
Compare
The templates command flattened two kinds of template into one list of verbs, so a command's name never said whether it acted on a project template or a to-do list template. The only way to find out was to run one and read what came back. Basecamp's own template library keeps the kinds apart, and the CLI now does too. Duplicating follows the product, which calls the operation duplicating wherever it appears. The API resource stays a copy. Every spelling that shipped before still works. Keeping them visible rather than hidden is deliberate: the surface snapshot skips hidden commands, so hiding them would record them as removed while they still work.
The address this listing read predates the library holding more than one kind of template. It survives only as a redirect, and the SDK now marks it deprecated, so a newly grouped command would have been built on a call already scheduled to disappear.
The template library has held card table templates since the API gained them, and the CLI could not see them. Someone who had built a board worth reusing could reach it from Basecamp but not from a script. A card table duplicate lands on the destination project rather than in one of its tools, so this command takes no container flag. A project has exactly one dock. This does not build against the released SDK. The operations it needs are not published yet, so the dependency is deliberately left where it was. Bump it before merging.
… board The card table half of the template library could be seeded from the CLI and the to-do list half could not, so someone scripting the library had to open Basecamp for one kind and not the other. The asymmetry was never intended. It existed because the SDK wrapped one endpoint before the other.
Building a template meant starting from an empty one and retyping work that already existed somewhere. Basecamp itself offers this from the list's own menu, and the route is nested under the recording, so the verb belongs on the to-do list rather than in the template library. The save runs asynchronously and hands back an ID to poll, matching how constructing a project and duplicating a template already behave. Polling inside the command would leave someone who interrupted it holding a finished template with no way to ask about it. Templatify and templatification are made-up words, and the only honest ones available. Construct pairs with construction and duplicate with duplication, each naming the async record it polls, and saving work as a template has no plain English verb and noun to take those slots. The help text carries the definition.
Saving existing work as a template reached to-do lists but not card tables, even though the same endpoint serves both. Card tables had no home in the CLI for actions on the table itself: the cards group manages what is inside a board, and its card-table flag only picks which board to look in. Gathering the cards into Triage is offered only here. bc3 accepts that attribute on any recording but never reads it for a to-do list, so a per-group command is what keeps the invalid combination unreachable.
The e2e suite needs bats, and nothing in the repo installed it. CI cloned a pinned tag, but a local checkout was left to find bats on its own, so bin/ci failed at the e2e step on a machine that had no bats or had a mise shim shadowing one. Pinning it alongside the other tools makes bin/setup enough to get a green local run.
Basecamp offers to archive or delete a to-do list or card table template, and the CLI could do neither. The verbs that looked like they would serve project templates only, and return 404 for anything else in the library. The flat spellings go at the same time. A verb sitting directly under templates cannot say which kind of template it acts on, which is how a library template ended up at the project-template route to begin with. Every verb now lives under the kind it acts on, so there is one way to spell each one.
ec83f76 to
6383b7e
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Legacy template command compatibility is not implemented, and card-table creation lacks required request/output coverage.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (6)
.surface-breaking:24
- This allowlist records the legacy flat template commands as intentional removals, but the PR description explicitly promises that those spellings still resolve. Keeping these entries would let the surface check accept a compatibility break; either remove the allowlist entries and preserve the commands, or correct the compatibility promise everywhere.
ARG basecamp templates construct 00 <template_id>
ARG basecamp templates construction 00 <template_id>
ARG basecamp templates construction 01 <construction_id>
ARG basecamp templates copy 00 <template_id>
ARG basecamp templates copy-status 00 <copy_id>
STYLE.md:47
- This new style rule says every flat template spelling has been removed, which conflicts with the PR description's explicit backward-compatibility requirement. Keep the canonical grouped form as the style guidance, but document the legacy spellings as supported aliases if the stated contract is retained.
Where a group is split by the kind of thing it holds, every verb lives under the
kind it acts on. `templates` has `projects`, `todolists`, and `card-tables`; the
flat pre-grouping spellings (`templates list`, `templates show`, `templates
create`, `templates update`, `templates delete`, `templates construct`,
`templates construction`, `templates library`, `templates copy`,
internal/commands/templates.go:307
- The new card-table completion formatter is not exercised:
TestTemplatesCopyStatusStatesinvokes only thetodolistsduplication command, while the card-table test stops at the pending response. A regression inDestinationCardTablehandling or its cards breadcrumb would therefore silently fall back to the generic completion message; add a completed card-table duplication case.
func describeCardTableDuplication(templateCopy *basecamp.TemplateLibraryCopy, contextArgs string) (string, []output.Breadcrumb, bool) {
table := templateCopy.DestinationCardTable
if table == nil {
return "", nil, false
internal/commands/templates.go:24
- The exported constructor's documentation still says it manages only project and to-do list templates, while this command now also owns card-table templates. Update the Go doc comment so generated API documentation matches the expanded command scope.
Short: "Manage project, to-do list, and card table templates",
internal/commands/templates.go:1096
- The construct command's long help still tells users to poll
templates construction, but this change makestemplates projects constructionthe canonical status command and updates the emitted breadcrumb accordingly. Update that help text as well; otherwise users followingconstruct --helpare directed to the old flat path.
Cmd: fmt.Sprintf("basecamp templates projects construction %d %d", templateID, construction.ID),
skills/basecamp/SKILL.md:1035
- The skill now tells agents that all flat template spellings have been removed, while the PR description says they continue to resolve. This will cause generated agent instructions to reject commands that the stated compatibility contract requires; update it consistently with the final command behavior.
**Every verb lives under its kind.** The flat pre-grouping spellings
(`templates list`, `templates show`, `templates create`, `templates update`,
`templates delete`, `templates construct`, `templates construction`,
`templates library`, `templates copy`, `templates copy-status`) have been
removed — use `templates projects delete`, not `templates delete`. `copy` and
- Files reviewed: 16/16 changed files
- Comments generated: 3
- Review effort level: Lite (auto)
Note
Copilot is running an experiment and ran this review at Lite.
| cmd.AddCommand( | ||
| newTemplatesProjectsCmd(), | ||
| newTemplatesTodolistsCmd(), | ||
| newTemplatesCardTablesCmd(), | ||
| ) |
There was a problem hiding this comment.
🤖 Correct that the two disagreed, so I took your second option and revised the claim: the removal is deliberate and the PR description now states it.
▸ More details
The flat spellings were dropped on purpose after the description was written, so the description was the stale half, not the code.
Retaining forwarding commands would have preserved the problem this PR exists to fix. A verb sitting directly under templates cannot say which kind of template it acts on, and that ambiguity is exactly how a library template ended up routed to the project-template endpoint and answering 404.
The surface and docs changes you mention are in place rather than missing: all 252 removed lines are acknowledged in .surface-breaking, the rule is written into STYLE.md, and skills/basecamp/SKILL.md lists the removed spellings with their grouped replacements. A removed spelling now returns a usage error naming the unknown subcommand.
copy and copy-status do survive, as aliases inside the grouped paths.
| app.Config.AccountID, | ||
| ) | ||
|
|
||
| cardTable, err := app.Account().Templates().CreateLibraryCardTable(cmd.Context(), name) |
There was a problem hiding this comment.
🤖 Both paths are now covered by 37a726d, which adds a request-shape test for card table creation and a completion test that asserts the board is named rather than falling through to the generic line.
▸ More details
TestTemplatesCardTablesCreateSendsNameAndReportsTheBoard pins the POST to /:account/template_library/card_tables.json, asserts the body is {"name":"Launch board"}, and asserts the rendered summary carries the returned id and title.
TestTemplatesCardTablesDuplicationReportsTheBoardItMade covers line 304. It asserts the card-table-specific summary and the cards list --card-table breadcrumb, so a destination_card_table that stopped parsing would fail the test instead of silently degrading to the generic completion message. That was the exact risk you named.
Both were real gaps. The to-do list twin of each already had a test, which is what made the asymmetry easy to miss.
| @test "templates projects list returns templates" { | ||
| run_smoke basecamp templates projects list --json | ||
| assert_success | ||
| assert_json_value '.ok' 'true' | ||
| } |
There was a problem hiding this comment.
🤖 Confirmed, and it was worse than reported: the rename created three duplicate pairs in this file, not one, all now removed in feb930e.
▸ More details
The third pair was templates projects show returns template detail, which only became visible once the first two were gone.
Cause: renaming the flat spellings to their grouped form turned templates list, templates library and templates show into exact copies of grouped tests that already sat further down the file. Each pair spent a second live API call asserting what the first had just asserted.
Why nothing caught it: bats does reject duplicate test names, and it did catch the equivalent collisions in e2e/templates.bats during this work. The smoke suite runs outside that harness, against a live account, so it never saw them.
I also swept every other .bats file in the repo for duplicate test names. This file was the only one affected.
jeremy
left a comment
There was a problem hiding this comment.
Can we devise a more fluent CLI grammar for these rather than reflecting the underlying resources verbatim?
|
Sure! I was following the pattern of the existing Here's how that could look. Your thoughts? Breaking Changes:
# Project templates
- basecamp templates list
- basecamp templates show <id>
- basecamp templates create <name>
- basecamp templates update <id>
- basecamp templates delete <id>
+ basecamp templates projects list
+ basecamp templates projects show <id>
+ basecamp templates projects create <name>
+ basecamp templates projects update <id>
+ basecamp templates projects delete <id>
# Project creation from a template
- basecamp templates construct <template_id> --name "Q4 Launch" --start-date "next monday"
- basecamp templates construction <template_id> <construction_id>
+ basecamp projects create "Q4 Launch" --from <template_id> --start-date "next monday"
+ basecamp projects creation <template_id> <creation_id>
# To-do list templates
- basecamp templates library
+ basecamp templates todolists list
# To-do list creation from a template
- basecamp templates copy <template_id> --in <project>
- basecamp templates copy-status <copy_id>
+ basecamp todolists create --from <template_id> --in <project>
+ basecamp todolists creation <creation_id>
All new:
# Card table templates
+ basecamp templates card-tables list
+ basecamp templates card-tables create <name>
# Card table creation from a template
+ basecamp tools create --type kanban_board --from <template_id> --in
+ basecamp tools creation <creation_id>
# Project templates from an existing project
+ basecamp templates projects create "Onboarding" --from <project_id>
+ basecamp templates projects creation <project_id> <creation_id>
# To-do list templates from an existing list
+ basecamp templates todolists create <name>
+ basecamp templates todolists create --from <todolist_id> --in <proj
+ basecamp templates todolists creation <todolist_id> <creation_id> -
# Card table templates from an existing board
+ basecamp templates card-tables create --from <card_table_id> --in <
+ basecamp templates card-tables creation <card_table_id> <creation_i
# Template lifecycle
+ basecamp templates todolists archive|trash|restore <template_id>
+ basecamp templates card-tables archive|trash|restore <template_id>I'll get the bots working on this new approach in the meantime |
The template library holds three kinds of template. Basecamp's own navigation separates them, and the CLI did not:
templates listmeant project templates,templates librarymeant to-do list templates, and nothing in either name said so. You found out by running one and reading what came back.This groups the commands by the kind they act on, adds card table templates, and adds a way to turn work you already have into a template.
The command tree
The flat spellings are gone.
templates list,templates show,templates create,templates update,templates delete,templates construct,templates construction,templates library,templates copyandtemplates copy-statusno longer resolve, and each returns a usage error naming the unknown subcommand. A verb sitting directly undertemplatescannot say which kind it acts on, which is the whole problem this PR set out to fix, so leaving the flat spellings in place would have preserved it.templates librarybecomestemplates todolists list.copyandcopy-statussurvive as aliases inside the grouped paths.All 252 removed surface lines are acknowledged in
.surface-breaking, with the rule written down inSTYLE.md.Taking a template out of the library
Basecamp offers to archive or delete a to-do list or card table template, and the CLI could do neither.
templates showandtemplates deleteroute to project templates and answer 404 for anything in the library.A library template is a recording, so
archive,trashandrestorego through the recordings status endpoint. They are spelled the way every other recording in this CLI is:cards,files,messages,commentsandtodolistsall usetrash/archive/restoreoff one shared helper, sodeletehere would have made the templates tree the only place the same operation on the same kind of object gets a different verb.templates projects deletekeeps its name because it is a different endpoint.restorehas no UI equivalent under that name; it is the CLI spelling of the "unarchive it" affordance on an archived template.Naming
Three asynchronous operations now read the same way, each status command named after the record it polls:
duplicatefollows the product, which calls the operation Duplicate everywhere it appears. The API resource stayscopiesand the SDK operation staysCreateLibraryCopy; that separation is deliberate and matches what the front-end already does, where the UI says Duplicate and the route says copies.templatifyandtemplatificationare coined words, and they are the only honest ones available. The product has no name for this operation at all, so rather than borrowing an unrelated English word that would read ambiguously next totodolists' other verbs, the CLI takes the resource noun. The help text carries the definition at the point you meet it.A card table group
There was no home for actions on a card table itself.
cardsmanages what is inside a board, and its--card-tableflag only picks which board to look in.basecamp card-tablesis that home, and it starts with one verb because a command group is a resource identity rather than a subcommand quota.messageboardshas exactly one action for the same reason.Duplicating names the project, not the container
duplicatesends the destination project and Basecamp resolves the container from the template's kind: a to-do list into the project's To-dos tool, a card table onto its dock.--todosetstill pins the container when a project has more than one to-do set, and still checks that it belongs to the project and is enabled, because a clear message beats a 404.card-tables duplicatehas no container flag, because a project has exactly one dock.Verification
Unit tests cover request shape and output rendering. e2e covers error paths, flag surface and help.
Run against a live server with a seeded database:
templatify <id>with no flags sends{}. The template came back carrying the source's title, "Strategy ideas".card-tables templatifyon the same board twice, with and without--move-cards-to-triage. With it, all 7 cards including one under an on-hold container are inKanban::Triage. Without it,Triage 1, Column 4, NotNowColumn 1, OnHold 1. The two results differ, so the flag reaches the wire rather than being dropped.templatificationon both kinds reports a real template name and id, and the matchingdestination_todolist/destination_card_tableis populated, so neither falls through to the generic completion message.templatificationwith its two ids transposed exits 2 withnot_found, rather than reading a different record.templatifyon a recording that cannot be templatified exits 4 withforbidden, which is a different code and shape from the transposed case, so the two stay distinguishable.templates todolists create,templates todolists listandtemplates card-tables listall succeed, and everything created during the run appears in the listings.archive,trashandrestorewere not exercised against a live account from this branch. Unit tests pin each verb to its exact route (/:account/recordings/:id/status/{archived,trashed,active}.json) and assert a non-numeric id is rejected before any request goes out. The endpoints themselves were verified end to end against a local bc3 for both template kinds: each call returned 204 and the status transition was confirmed in the database.Depends on
basecamp/basecamp-sdk#877
Three commits use SDK operations added in basecamp-sdk#877 and do not compile against the current release. Bump
go.modbefore merging.