Repository navigation
feat(plan): agent-first T5 leaves + parity (CLI_HANDOFF) - #129
Conversation
## Summary Implement the 15 OpenAPI x-cli-command Plan leaves for agent-first parity (project/board reorder, move-preview, view create, workspace views, tasks board, member update, org label CRUD). task brief already existed. ## Change Log - Client methods for reorder, move-preview, workspace views, plan labels, tasks board - Click commands under board, project, tasks, plan label, plan views - 19 wire+CLI tests; skill intent notes for T5 leaves ## Risks - Workspace views group uses invoke_without_command for GET + save subcommand - Live API must match schema annotations on plan__next
|
Companion skill docs: DailybotHQ/agent-skill#66 — Code release WC-37 / https://app.dailybot.com/forms/59bdce83-ea16-4d29-a289-44db77fc9d5f/responses/17878edd-ee7e-4f20-88e1-cb5a839c6295 |
## Summary Code Check failed on ruff format --check for the five T5 leaf files.
## Summary mypy failed CI: member update, move-preview, and tasks board human renderers omitted the required fields list.
Loop review / CI fixCI was red on Code Check — not pytest failures:
Local: HEAD: |
## Summary Keep vendored skill pack aligned with DailybotHQ/agent-skill#66 after loop review.
AI review for
|
There was a problem hiding this comment.
Code review — 7 finding(s): 0 critical · 4 warning · 3 info
Verification: 2 verified · 0 downgraded · 0 refuted · 2 unverified · 3 skipped — grok-4.5
Check: ✅ passing — strictness block-on-critical: highest severity warning ≤ critical threshold
Findings
| Severity | Location | Title | Verification | Agreement |
|---|---|---|---|---|
dailybot_cli/api_client.py:3047 |
Member inspect PATCH is not empty when agent_name is set | unverified | — | |
dailybot_cli/api_client.py:4357 |
Project member inspect also stamps agent_name into empty PATCH | unverified | — | |
dailybot_cli/commands/plan_views.py:84 |
Workspace --etag uses click.echo instead of print_raw_value | verified | — | |
.agents/skills/dailybot/tasks/SKILL.md:1200 |
SKILL points at missing commands.md § Agent-first T5 leaves | verified | — | |
| ℹ️ info | dailybot_cli/commands/board.py:221 |
Member detail panel lists Role while help says membership has no role | skipped | — |
| ℹ️ info | dailybot_cli/commands/tasks.py:431 |
Redundant local imports already present at module top | skipped | — |
| ℹ️ info | tests/plan_agent_first_t5_leaves_test.py:151 |
No CLI coverage for reorder --before/--after mutual exclusion | skipped | — |
Summary
Verdict
Solid agent-first T5 surface: client paths, thin Click wiring, Idempotency-Key / If-Match patterns, destructive label delete, and mocked tests all track existing Plan conventions. A few agent-path and docs-contract gaps should be fixed before merge.
What matters
-
Member inspect + agent stamp (warning).
update_board_member/update_project_memberdocument an empty PATCH body, but_tasks_writeinjectsagent_nameinto every dict JSON body. WithDAILYBOT_AGENT_NAMEset (normal for these leaves), the wire body is no longer{}. Preferstamp_agent=Falselikeupdate_view, and extend the empty-PATCH tests to a client that hasagent_name. -
Workspace
--etagoutput (warning, Rule 9).plan_views.pyuses rawclick.echo(etag);board views/project viewsalready useprint_raw_value. Align so shell capture stays on the shared helper. -
Skill ↔ commands.md gap (warning). The new SKILL section points at
commands.md§ “Agent-first T5 leaves”, which is absent, and the inventory tables still omit the new leaves. Agents will not find flag contracts. Ship that section here or in the companion skill PR before merge.
Secondary
- Member detail panel still lists
Rolewhile help says membership has no role column (info). - Redundant display imports inside
tasks_board(info). - No CLI test for
--before+--afteron reorder (info / test-gap).
What looks good
- Workspace views save mirrors board/project If-Match +
--fetch-etagmutual exclusion. - View create doors take Idempotency-Key through
_tasks_write(idempotent=True). - Label CRUD reuses
update_tasks_label/delete_tasks_labelandconfirm_without_preview. - Tests mock
require_authper module and never touch the real API. - No new dependencies, secrets, or auth-resolution changes.
Note to verify with API (not filed)
create_board_view / create_project_view stamp agent_name into the POST body. Single-view update_view already uses stamp_agent=False because the door rejects agent_name. Confirm the nested create serializers accept the stamp; if they reject unknown_field, disable stamping there too.
Recommendation: request-changes
Check status: ✅ passing — strictness
block-on-critical, highest severity in effectwarning: highest severitywarning≤ critical threshold.This line is written by the reviewer runtime after the gate ran and matches the check conclusion and the tracking comment. Any recommendation above is the model's advisory opinion, not the gate.
| return self._tasks_write( | ||
| "PATCH", | ||
| f"boards/{_path_segment(board_uuid)}/members/{_path_segment(user_uuid)}/", | ||
| json={}, |
There was a problem hiding this comment.
AGENTS.md Rule (Tasks agent stamp): _tasks_write stamps agent_name into every JSON dict body on POST/PUT/PATCH. These inspect doors document an empty body (json={}), but with DAILYBOT_AGENT_NAME / --agent-name (the agent-first path) the wire body becomes {"agent_name": "…"}.
Failure mode: agents calling plan board member update / project member update no longer send the empty PATCH the API contract describes; if the membership serializer treats non-empty bodies strictly (or rejects agent_name as unknown_field), the leaf fails exactly for the audience these T5 leaves target. The unit test only covers a client with no agent_name.
Fix: pass stamp_agent=False (same posture as update_view) so the body stays {}:
| return self._tasks_write( | |
| "PATCH", | |
| f"boards/{_path_segment(board_uuid)}/members/{_path_segment(user_uuid)}/", | |
| json={}, | |
| return self._tasks_write( | |
| "PATCH", | |
| f"boards/{_path_segment(board_uuid)}/members/{_path_segment(user_uuid)}/", | |
| json={}, | |
| stamp_agent=False, | |
| ) |
| return self._tasks_write( | ||
| "PATCH", | ||
| f"projects/{_path_segment(project_uuid)}/members/{_path_segment(user_uuid)}/", | ||
| json={}, |
There was a problem hiding this comment.
Same failure mode as update_board_member: update_project_member sends json={} but _tasks_write injects agent_name whenever the client has one, so agent-first calls are not an empty inspect PATCH.
Apply the same fix:
| return self._tasks_write( | |
| "PATCH", | |
| f"projects/{_path_segment(project_uuid)}/members/{_path_segment(user_uuid)}/", | |
| json={}, | |
| return self._tasks_write( | |
| "PATCH", | |
| f"projects/{_path_segment(project_uuid)}/members/{_path_segment(user_uuid)}/", | |
| json={}, | |
| stamp_agent=False, | |
| ) |
Add a client wire test with agent_name="…" asserting the JSON body stays {} for both member inspect methods.
| if etag_only: | ||
| if etag is None: | ||
| raise click.ClickException( | ||
| "The server returned no ETag for workspace views, so a save cannot be " | ||
| "made safely from this response." | ||
| ) | ||
| click.echo(etag) |
There was a problem hiding this comment.
AGENTS.md Rule 9: user-facing output goes through display.py; raw click.echo is only allowed for _print_org_list and the hook group. Board and project views already print the pipeable ETag via print_raw_value(etag or "") (board.py / project.py).
This leaf uses click.echo(etag) instead, so workspace ETag capture drifts from the established helper (and from the SECURITY.md note that views --etag values are entity tags printed for shell capture).
| if etag_only: | |
| if etag is None: | |
| raise click.ClickException( | |
| "The server returned no ETag for workspace views, so a save cannot be " | |
| "made safely from this response." | |
| ) | |
| click.echo(etag) | |
| print_raw_value(etag) |
Import print_raw_value from dailybot_cli.display next to the other helpers.
| ### Agent-first T5 leaves (OpenAPI `x-cli-command`) | ||
|
|
||
| **CLI requirement.** These leaves need a CLI build that ships them (DailybotHQ/cli#129 on `plan__next`, or a release **after** `3.26.2` that includes that PR). The pack floor for the rest of Plan remains `dailybot-cli >= 3.25.0`. If a leaf is missing, run `dailybot upgrade` (or install from that branch) before inventing alternate command names. | ||
|
|
||
| Contract strings — prefer these exact invocations (full flags in [commands.md](commands.md) § Agent-first T5 leaves): |
There was a problem hiding this comment.
This new section tells agents to use “full flags in commands.md § Agent-first T5 leaves”, but that section does not exist in .agents/skills/dailybot/tasks/commands.md (grep count 0). The command inventory tables there still omit board reorder, board move-preview, board view create, project reorder, project view create, member update, tasks board, plan label *, and plan views *.
Failure mode: agents following the skill will open commands.md for flag contracts, find nothing, and invent alternate invocations — the opposite of the “prefer these exact invocations” goal. Either land the T5 commands.md section in this PR (or the linked companion skill PR before merge), or drop the cross-reference until that section exists.
| _MEMBER_DETAIL_FIELDS: list[tuple[str, str]] = [ | ||
| ("UUID", "uuid"), | ||
| ("User UUID", "user_uuid"), | ||
| ("Team UUID", "team_uuid"), | ||
| ("Role", "role"), |
There was a problem hiding this comment.
board_member_update help states board membership has no role column (org roles + visibility are the access model), yet _MEMBER_DETAIL_FIELDS always renders a Role row. Human non-JSON output will show a blank Role line on every inspect.
Drop ("Role", "role") from _MEMBER_DETAIL_FIELDS here (and the twin in project.py) unless the grant row actually returns a meaningful role.
| # Reuse board snapshot renderer when shape matches; otherwise detail panel. | ||
| if isinstance(data, dict) and (data.get("groups") is not None or data.get("board") is not None): | ||
| from dailybot_cli.display import print_board_snapshot |
There was a problem hiding this comment.
print_board_snapshot and print_tasks_detail_panel are already imported at the top of tasks.py (lines 58 and 65). The deferred imports inside tasks_board add noise without avoiding a cycle. Use the module-level names directly.
| class TestCommandWiring: | ||
| def test_board_reorder_json(self, runner: CliRunner, auth_client: MagicMock) -> None: |
There was a problem hiding this comment.
Both board reorder and project reorder raise UsageError when both anchors are passed, but TestCommandWiring only exercises the happy JSON path. A single runner.invoke(..., "--before", …, "--after", …) asserting non-zero exit would lock the local guard that mirrors reorder_anchor_invalid.
Summary
Fifteen agent-first Plan leaves + skill coverage aligned with API OpenAPI
x-cli-command(CLI_HANDOFF / plan__next).Code release: WC-37 · response 17878edd
Companion: DailybotHQ/agent-skill PR (T5 skill docs) — linked when opened.
Leaves
plan project reorder/v1/plan/projects/{id}/reorder/plan board reorder/v1/plan/boards/{id}/reorder/plan board move-preview.../move-preview/?project=plan board view create.../views/+ Idempotency-Keyplan views workspace/v1/plan/views/workspace/plan views workspace saveplan tasks board/v1/plan/tasks/board/plan task brief.../brief/(existing)plan board member update.../members/{user}/plan label list|create|update|delete/v1/plan/labels/plan project view createplan project member updateTest plan
pytest tests/plan_agent_first_t5_leaves_test.py(19 passed)Notes
author_kind=agentnever from org agent key.