Skip to content

ClickUp-style branch the source of truth for ticket-backed Agent work - #774

Draft
adriandemian wants to merge 55 commits into
mainfrom
ralphx/ralphx/agent-e1517796
Draft

ClickUp-style branch the source of truth for ticket-backed Agent work#774
adriandemian wants to merge 55 commits into
mainfrom
ralphx/ralphx/agent-e1517796

Conversation

@adriandemian

@adriandemian adriandemian commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

Summary

Implements strict ClickUp Git naming enforcement for ticket-backed Agent workspaces. When enabled, every Agent started from a ClickUp task creates or reuses a stable, ticket-shaped branch with frozen commit subjects and PR titles derived from the task name and authenticated user. The feature is opt-in and defaults off; all existing workflows remain unchanged.

User Impact

Once the frontend Settings UI is wired (not yet in this PR), users can enable "Strict Git naming" in Settings > Integrations > ClickUp to:

  • Enforce a stable branch per ticket (e.g., cu-123_fix-login-redirect_ada-lovelace)
  • Freeze commit subject rule and PR title to the task name, blocking drift
  • Support multiple PR cycles on the same branch after merge
  • Block concurrent workspace ownership of the same ticket
  • Reject unsafe branch reuse (e.g., after an unmerged PR closure)

Non-ClickUp workspaces, branchless Chat mode, and existing ClickUp workspaces without the setting enabled are unaffected.

Technical Context

Domain Model

  • Extended TicketCanonicalBranch with policy_kind (LegacyCanonicalBase | StrictGitConvention), immutable strict_policy snapshot (task title, commit rule, PR title, username), and generational cycle state machine
  • New cycle states: Preparing, Active, Merged, ClosedUnmerged, Blocked; legacy rows retain LegacyCanonicalBase semantics
  • Repository trait gains: create_if_absent (atomic first-time binding), compare_and_swap_cycle (CAS on generation + state), get_by_branch_name (ownership resolution by exact branch name)
  • Two migrations: ClickUp settings schema and cycle tracking table

Application Layer

  • ticket_git_convention: Template interpolation (:taskId:, :taskName:, :username:, :summary:) with placeholder validation
  • ticket_git_strict_start: Resolves target base, fetches ClickUp task and authenticated user, atomically loads or creates first-cycle binding, checks ownership and safety
  • ticket_git_cycle_lifecycle: State machine advancing cycles (Preparing → Active → Merged/ClosedUnmerged), integrated with PR merge polling
  • ticket_git_publish_policy: Pre-publish validation of commit subjects and PR title against frozen convention
  • ticket_git_publish_hook: Git hook installed in worktree for early commit-message check

Integration Points

  • Conversation start service detects strict policy applicability (setting + ClickUp task) and calls ensure_strict_clickup_ticket_branch_from_services during workspace provisioning
  • Unified chat commands load binding and apply frozen titles/commit rules only when present
  • PR poller updates cycle state on merge/closure and prepares branch for safe rollover
  • Mode switches (Chat → Edit/Plan) recover ticket identity and apply binding if switching into a workspace-owning mode
  • Agent conversation start service wires early commit hook installation and cycle activation

Feature Gate

All strict code paths guarded by:

  1. strict_clickup_ticket_policy_applies() → checks setting enabled + ClickUp task linked
  2. load_ticket_git_publish_policy() → returns None for legacy or unbound workspaces
  3. Non-strict paths are byte-identical to before (commit subject templates, PR title logic, branch naming)

Risks / Follow-Ups

Verified scope isolation (orchestrator review completed):

  • Strict resolution only runs when ClickUp task linked AND (existing binding OR setting enabled)
  • Legacy ticket workspaces unaffected; retain old canonical-base behavior
  • Non-ClickUp conversations and branchless Chat unaffected
  • Conversation start without ClickUp task or with strict setting off uses unchanged logic

Coverage adjustments:

  • Moved orchestration-heavy modules (agent_conversation_start_service, ticket_git_* lifecycle, publish hooks) to codecov ignore — behavior covered via integration and helper determinism tests
  • CI timeout bumped 60→90 minutes due to expanded test suite

Not yet implemented:

  • Frontend template editor, start preview, and managed workspace summary
  • Settings > Integrations > ClickUp currently exposes only API token and workspace selector
  • Feature is backend-complete and MCP-testable; awaits frontend rollout phases

Existing bindings migration:

  • Ticket workspaces created before this PR remain on LegacyCanonicalBase behavior
  • Turning strict mode off preserves existing frozen bindings but disables enforcement for new tickets
  • Disconnecting ClickUp persists template strings for later reconnect
View full plan

Goal

“If we work on a ClickUp ticket, have an option in ClickUp settings to force branch-match behavior … all conversations tied to the ticket … no ralphx/ … commits and PR creation must follow the convention.”

Add an opt-in strict ClickUp Git convention that makes a persisted, ClickUp-style branch the source of truth for ticket-backed Agent work. When enabled, RalphX must create or reuse the exact resolved ticket branch, allow only one active owning conversation for that branch, enforce matching commit subjects and PR titles, and safely reuse the same branch for later PR cycles.

Assumptions:

  • The feature is ClickUp-first. Internally, convention rendering should be a provider-neutral value object so the existing Jira title normalization is not duplicated, but Jira and Linear behavior must not change in this slice.
  • The setting is global within the existing singleton ClickUp integration settings model.
  • Strict behavior applies to ClickUp-linked Agent modes that own a worktree (Edit, Plan, and Ideation, including a later Plan → Implement transition). Branchless Chat remains branchless until it switches to a worktree-owning mode. If a branchless conversation is tied to a ClickUp task, the task identity must be persisted or recoverable from the conversation’s external issue link so the later mode switch applies the same strict binding instead of losing the convention trigger.
  • Existing strict ticket bindings remain authoritative if the global toggle is later disabled; disabling affects new ticket bindings and must not silently orphan or rename established branches.
  • ClickUp documents that a valid task ID in a branch, commit, PR title, or PR body links GitHub activity, and its default branch template is :taskId:_:taskName:_:username:: https://help.clickup.com/hc/en-us/articles/6305771568791-GitHub-integration

Repository Evidence

  • src-tauri/src/application/agent_conversation_start_service.rs already fetches the authoritative ClickUp task, derives the preferred custom/raw ID, searches matching open PRs/local branches, and selects linked mode only when a unique candidate exists.
  • src-tauri/src/application/agent_conversation_workspace.rs currently generates isolated branches as ralphx/{project}/agent-{provider}-{ticket}-{conversation}; linked mode checks out the selected branch and already refuses a second active workspace for the same branch.
  • src-tauri/src/application/agent_conversation_start_service/helpers.rs::ensure_linked_branch_workspace_available already implements the selected “redirect and block” ownership rule, but returns an unstructured string.
  • src-tauri/src/application/ticket_canonical_branch.rs, TicketCanonicalBranch, and ticket_canonical_branches already persist a per-ticket branch and partial-push recovery, but the service is not called by production conversation startup and currently creates ralphx/ticket/... branches that are never worktree-checked-out.
  • src-tauri/src/application/clickup_integration_service.rs::ClickUpTaskContent supplies task ID, custom ID, name, creator, and assignees; current_user() supplies the authenticated ClickUp user needed for :username:.
  • src-tauri/src/commands/unified_chat_commands.rs creates automatic workspace commits and drives publication; src-tauri/src/domain/services/pr_publish_service.rs::AgentWorkspacePrPublisher currently derives PR titles from the describer/conversation and only has special Jira-key normalization.
  • src-tauri/src/application/publish_resilience.rs is the canonical Agent-workspace source-update seam: ensure_publish_branch_fresh fetches origin and delegates to update_source_from_target, classifying clean updates, conflicts requiring the workspace repair agent, missing branches, and operational failures.
  • src-tauri/src/application/agent_workspace_publish_recovery.rs, agent_workspace_pr_supervision_recovery.rs, and agents/ralphx-agent-workspace-repair/shared/prompt.md already provide durable needs_agent repair, completion verification, auto-publish retry, stale-run repair, and startup recovery. Commit-hook policy failures are already classified as agent-fixable while hook/environment failures are operational.
  • src-tauri/src/application/services/pr_merge_poller.rs and agent_workspace_external_pr_reconciliation.rs make GitHub the Agent-workspace merge authority, persist PR terminal state, stop active runs, emit workspace/publication events, reconcile externally-created PRs, and trigger guarded local cleanup.
  • src-tauri/src/application/agent_conversation_workspace.rs::rollover_agent_conversation_workspace_with_setup_mode currently handles continued conversation after a merged/closed PR by deleting a clean old worktree and creating a new continuation branch. agent_workspace_continuation.rs and chat_resumption.rs separately classify terminal/missing workspaces for resume. Strict ClickUp reuse must align all of these entry points.
  • src-tauri/src/application/git_artifact_cleanup.rs intentionally separates worktree cleanup from branch deletion and deletes only branch names it can prove RalphX owns. A ClickUp-named strict branch is persistent binding state: RalphX may remove its validated clean worktree but must not make that branch eligible for generic terminal deletion.
  • frontend/src/components/settings/ClickUpIntegrationSettingsPanel.tsx is the existing lazy-loaded ClickUp settings surface; frontend/src/components/agents/AgentsStartComposer.tsx and agentStartErrors.ts own linked-workspace start failures.

Decisions

  1. Busy ticket branch: use the user-selected “redirect and block” policy. Exactly one active Agent conversation owns a strict ticket branch. A second start returns a typed busy result containing the owner conversation ID and a UI action to open it. Do not create a hash branch for ordinary concurrency.
  2. After merge: use the user-selected “reuse same branch” policy. Once the prior workspace is terminal/released, safely advance or recreate the clean branch at the latest target branch and allow a new PR cycle with the same name.
  3. Templates: use editable, validated templates with defaults:
    • Branch: :taskId:_:taskName:_:username:
    • Commit subject: :taskId: - :taskName:
    • PR title: :taskId: - :taskName:
  4. Require :taskId: in all three templates. Allow :taskId:, :taskName:, and :username: everywhere; optionally allow :summary: only for commit/PR templates so teams can preserve per-commit detail without weakening the ticket prefix.
  5. Resolve :username: from the authenticated ClickUp user, not the task assignee or task creator. If a template requires it and ClickUp cannot resolve the current user, fail closed before workspace creation.
  6. Freeze the rendered branch, commit subject rule, PR title, task title, username, and policy version on first binding. Later task renames, account changes, or template edits affect new ticket bindings only.
  7. A short deterministic hash is permitted only when required to keep a truncated branch Git-safe or to disambiguate a true normalized-name collision. It is never a concurrency suffix.
  8. Strict policy is backend-owned. Prompts and hooks provide early guidance, but branch/start checks and the pre-publish commit/PR validation gate are authoritative.
  9. A mismatched open PR or active branch that references the same ClickUp task must block strict startup with remediation; RalphX must not create a duplicate PR or silently adopt a nonconforming name.
  10. Preserve linked branches during conversation archive/cleanup. Cleanup removes only validated RalphX-owned worktrees and never deletes the reusable ticket branch.
  11. Keep strict ticket workspaces on the existing Agent conversation PR lifecycle, not the task PendingMerge → Merging state machine. Source freshness, repair, push, PR supervision, terminalization, reconciliation, and startup recovery remain owned by their current Agent-workspace services; GitHub remains final merge authority.
  12. Terminal handling is outcome-aware. A merged PR—including squash/rebase merge proven content-equivalent—may start a new cycle on the exact frozen branch after safe cleanup and refresh. A closed-unmerged PR, unknown terminal state, or branch whose old content is not contained/equivalent must block reuse; do not discard the old head to satisfy stable naming.
  13. Convention failures join the existing publish failure model. Unpushed local commit-policy violations are agent-fixable and may use the existing workspace-repair → verified completion → auto-publish retry flow. A remote/published history violation, unsafe rewrite requirement, missing hook/runtime dependency, or repository/environment failure is operational and must not trigger force-push or an automatic repair loop.

Data / State

ClickUp settings

Extend ClickUpIntegrationSettings and clickup_integration_settings additively with:

  • strict_git_naming_enabled (default false)
  • branch_name_template
  • commit_subject_template
  • pr_title_template

Use non-null database defaults matching the ClickUp-style defaults so old rows deserialize safely. Update the memory/SQLite repositories, Tauri response/input types, frontend zod schema, mocks, and tests. Disconnecting ClickUp clears credentials/validation state and disables enforcement, but preserves the user’s template strings for reconnect.

Frontend read models

Do not make the frontend infer policy from a branch prefix. Add an optional, additive TicketGitConventionSummary to Agent workspace/start payloads with the minimum display-safe frozen state: provider, task ID/key, task title snapshot, rendered branch, commit subject rule/example, rendered PR title, policy version, and managed/frozen status. This same summary drives the conversation header, workspace line, publish confirmation, replay, and recovery UI.

Add a read-only ClickUp convention preview command/query for the start composer. Given project ID plus the selected ClickUp task reference and proposed PR base, it refetches authoritative task/current-user/settings data and returns either a rendered preview or a typed preflight blocker. It must not reserve a branch, create a binding, scan/mutate worktrees, or promise startup success. Cache it by project + task + settings version, cancel/ignore stale results when the reference/project changes, and re-resolve authoritatively during submit.

Represent start/publish policy failures with stable error codes and structured details rather than extending LINKED_SETUP_FAILURE_MARKER prose parsing. Required details include expected branch, owner conversation ID when busy, related PR URL/number when known, offending commit SHA/subject for commit-policy failure, and a remediation category. Keep a compatibility parser for legacy linked-setup errors during rollout.

Ticket binding

Evolve the existing ticket_canonical_branches model rather than creating a competing mapping. Add immutable strict-policy snapshot fields such as:

  • policy kind/version
  • task title snapshot
  • ClickUp username snapshot
  • rendered commit subject/prefix rule
  • rendered PR title
  • per-cycle base commit/effective merge-base used for publish-range validation
  • timestamps needed for safe recovery

Add a unique (project_id, branch_name) lookup and repository method so send/publish/recovery paths can resolve the ticket policy from a workspace branch. Introduce create-if-absent/CAS semantics: concurrent first starts must converge on one stored binding and never overwrite its rendered branch.

Legacy ralphx/ticket/... rows are not silently rewritten. If strict mode encounters one, adopt/rebind only when repository evidence proves there is no active workspace, open PR, divergence, or unpublished work; otherwise fail closed with explicit migration guidance. Existing terminal semantics for legacy canonical-base rows must not be reused to block the selected strict “reuse same branch after merge” policy; strict bindings need explicit per-cycle state instead of treating the ticket branch as permanently terminal.

Runtime ownership and lifecycle

  • A strict binding owns the stable branch identity; a conversation workspace temporarily owns its checkout. Branch names never determine filesystem placement: continue using resolve_agent_conversation_workspace_path() and its hashed project/conversation components under the configured worktree root.
  • Reuse find_active_by_project_and_branch_name for the one-active-owner guard.
  • Before every worktree-owning start, mode upgrade, send/run, repair, and publish, verify that the workspace branch equals the strict binding, the checked-out branch still resolves to the persisted ticket binding, and the current commit is reachable from the recorded cycle base. Normal work is allowed to advance HEAD; publish validation must inspect every introduced commit from the recorded cycle base/effective merge-base to HEAD.
  • PR merge/close detection continues through pr_merge_poller and external/startup reconciliation. Terminalization must persist the terminal PR and publication event, stop/settle the current run, and finish guarded worktree cleanup before a strict binding becomes eligible for another cycle.
  • Same-conversation continuation and a later conversation start must call one policy-aware cycle-preparation service. That service fetches origin, resolves the effective PR base, verifies the prior PR outcome, proves the old branch head is an ancestor or content-equivalent to the target (covering squash/rebase merges), and checks that no local/remote-only unpublished work exists.
  • For an eligible merged cycle, reuse existing Git helpers instead of resetting history: restore the local branch from its exact remote ref when needed; recreate it at the effective target only when both safe local/remote evidence and terminal proof permit; otherwise update it through update_source_from_target/the shared freshness path so an ancestor branch advances without a force move. If GitHub deleted the remote head after merge, treat that as normal only after the same terminal/containment proof.
  • Remove only the validated clean worktree, recreate the worktree on the exact frozen branch, run normal workspace setup, capture the new cycle base commit, and clear prior PR/push/supervision fields only after the replacement worktree is ready. Persist cycle rollover with compare-and-set/current-cycle authority so crashes cannot expose a cleared publication state with no usable workspace.
  • A closed-unmerged PR, dirty worktree, unpublished local or remote commits, conflicting remote tips, orphaned unmanaged worktree, missing terminal proof, or non-contained/non-equivalent history blocks with recovery guidance. Never reset, force-move, force-push, or delete the strict branch to make reuse succeed.
  • Generic terminal cleanup must continue using its existing name/ownership proof. Do not broaden is_expected_agent_workspace_branch so ClickUp template branches become disposable; pass/derive an explicit persistent-branch retention policy while still allowing validated clean worktree removal.
  • Keep agent_workspace_continuation, chat_resumption, direct send, external ideation messaging, and Chat-service rollover decisions aligned so one entry point cannot resume a terminal strict workspace while another blocks or creates a continuation suffix.

Architecture And Runtime Flow

  1. Add a focused application module (for example ticket_git_convention.rs) containing:
    • template parsing/validation;
    • branch-safe placeholder normalization;
    • deterministic length/collision handling;
    • immutable convention snapshots;
    • commit-subject validation.
  2. Rework ticket_canonical_branch.rs into the strict binding resolver:
    • fetch ClickUp task and current user;
    • render/validate the expected convention;
    • read/create the immutable binding;
    • search exact local/remote branch and PR evidence;
    • reject mismatched open evidence;
    • establish/push or safely reuse the branch;
    • return a linked-workspace base selection plus convention snapshot.
  3. Call that resolver in AgentConversationStartService before conversation/worktree creation whenever strict mode is enabled and a ClickUp reference is present. Reuse the existing ClickUp reference/task lookup and AgentConversationWorkspaceBranchNameHint as the trigger/input seam, but do not send the strict branch through agent_conversation_ticket_branch_segment() because that deliberately produces the legacy ralphx/...-{conversation} name. In strict mode, reject or override user-supplied branch-mode/head-branch selections that do not equal the rendered ticket branch; only the PR target/base branch remains user-selectable. Keep the current hint/candidate search and isolated naming byte-compatible behind the disabled path.
  4. Provision strict workspaces through the existing linked-checkout machinery with the exact persisted branch as workspace.branch_name; workspace.base_ref remains the selected/persisted PR target branch. Do not change hashed worktree-path derivation. Exclude Review PR mode from this override so its source-PR isolation rules remain unchanged.
  5. Keep the oversized unified_chat_commands.rs changes to thin calls into the focused convention service. Acquire the existing try_acquire_agent_workspace_publish_guard() first, then perform the authoritative convention check before any commit/push/PR side effect: verify the checked-out/frozen branch, compute the current cycle range from the refreshed effective base, and validate every introduced commit subject. Continue through the existing freshness wrapper selected by workspace ownership—direct branches use ensure_publish_branch_freshupdate_source_from_target; linked plan branches use ensure_plan_publish_branch_freshupdate_plan_from_main_isolated—then the normal push/PR publisher pipeline.
  6. Install a RalphX-managed commit-msg hook/config for strict managed worktrees to reject bad subjects early. Treat the hook as UX only: --no-verify cannot bypass the authoritative pre-publish range validation. Feed typed convention failures directly into PublishFailureClass rather than relying on new error-string heuristics.
  7. Extend the existing needs_agent repair path for unpushed policy violations. Include the frozen convention and offending commits in the workspace-repair payload; after complete_agent_workspace_repair, extend the existing completion proof (current HEAD, clean tree, no merge/rebase/conflict markers, current base) with branch identity and full cycle-range convention validation before the automatic publish retry. Never let stale repair completion or an old policy snapshot authorize the retry.
  8. Pass an optional resolved convention into AgentWorkspacePrPublisher. In strict mode, ignore arbitrary describer/conversation title drift and create/update the PR with the frozen title; keep the generated reviewer-focused body and repository template behavior unchanged.
  9. Route manual publish, auto-publish, PR-fix republish, base repair/update-from-base, review-to-publish handoff, duplicate-PR recovery, and startup recovery through the same convention gate, per-conversation publish serialization, existing push-status vocabulary (pending/checking/committing/refreshing/describing/pushing/pushed/needs_agent/failed/refreshed), and durable publication events. Add structured convention details/classification without inventing a parallel status machine. No alternate publish path may bypass validation.
  10. Add policy-aware terminal-cycle preparation around the existing rollover/continuation seams rather than a second merge implementation. Standard workspaces keep agent_conversation_continuation_branch_name; strict workspaces reuse the frozen branch only after terminal/content/cleanliness proof and atomic new-cycle persistence.
  11. Update external PR reconciliation before any update_publication write: strict workspaces may adopt only a PR whose head is the exact frozen branch and whose base matches the effective workspace base. Validate/fix the title only through the normal publisher when safe; otherwise append a typed blocked publication event and leave prior binding/publication authority intact. Apply the same rule during startup reconciliation.
  12. Keep the task merge state machine unchanged. Its merge-recovery rule—one shared failure classification across live, manual, reconciliation, startup, and UI paths—is the pattern to follow, not a new status transition surface for Agent conversations.

Agent And MCP Surface

  • Do not add model-owned branch-selection or persistence tools. The backend resolves identity, ownership, worktree state, and publication policy.
  • Add the resolved branch and required commit/PR convention to Agent workspace context so coding/fix agents receive actionable instructions.
  • For strict workspaces, the PR describer prompt should treat the title as locked and focus on the body. The existing optional title field may remain for non-strict workspaces; the backend remains authoritative.
  • Keep tool descriptions surface-local. No prompt should narrate migration or legacy branch behavior.
  • Preserve current ClickUp composer references as the trigger, but refetch the authoritative task before first binding rather than trusting display metadata from the frontend.

UI / UX

1. Settings → Integrations → ClickUp

Keep this inside the existing lazy-loaded ClickUp section; do not add another top-level settings destination. Split the current panel into two visually distinct cards:

  • Connection and workspace: retain the existing token, validation, disconnect, and workspace controls.
  • Git naming convention: show an “Enforce ClickUp Git naming” switch with a short explanation that ticket-backed Agent work will use one stable ClickUp branch and locked commit/PR naming. Disable the switch until ClickUp is connected and a workspace is selected, with inline guidance instead of a dead control.

When enforcement is off, keep the convention card collapsed to the switch plus default examples. When on, reveal three labeled template inputs (Branch name, Commit subject, Pull request title), supported-token chips, and a compact preview block showing the rendered branch, commit, and PR title for one representative task/user. Use explicit Save/Discard actions and an unsaved-changes state; do not save each keystroke. Validate locally while typing, then validate again on save through the backend renderer. Associate field-level errors with the relevant input for missing :taskId:, unknown tokens, empty render, invalid Git ref, byte-length overflow, and required-but-unavailable username. Keep the last persisted policy active if save fails.

Below the editor, show persistent copy: “Changes apply to new ticket bindings. Existing managed ticket branches keep their frozen naming.” Disconnect preserves templates but visibly disables enforcement. Reconnect does not silently re-enable it. Preserve the current first-paint loading shell and lazy settings import; do not fetch preview dependencies before the Git naming card is expanded/enabled.

2. Agent start composer preflight

The selected ClickUp reference is the trigger. After a project and one ClickUp ticket are selected, render a lightweight inline ClickUp-managed Git callout near the ticket chips/base control:

  • loading: “Resolving ClickUp branch…” without blocking typing or the first composer paint;
  • ready: ticket ID/title, exact rendered head branch, required commit format/example, and locked PR title;
  • setting off: no managed callout and all existing behavior remains unchanged;
  • branchless Chat: “Naming applies when this conversation enters Edit or Plan” instead of implying a worktree already exists.

For Edit, Plan, or Ideation, retain the base picker but label its purpose as the PR base in the managed callout. Hide/disable the isolated-branch toggle and any ralphx/... head-branch choice because the strict head branch is fixed. A base selection must never be presented as changing the managed head branch. The preview is advisory: submit performs the authoritative refetch/reservation and may return a newer resolved state.

Replace the current generic linked-setup retry card for strict failures. In particular, never offer “Retry with isolated branch” for a managed ticket. Render typed inline recovery cards:

  • Branch in use: exact branch, owner conversation summary when available, primary “Open conversation” action, secondary copy-branch action.
  • Nonconforming branch or PR: expected versus found branch, related PR link when known, and concise manual-remediation guidance; no silent adoption.
  • Unsafe reuse/divergence: identify whether the blocker is dirty worktree, unpublished commits, remote divergence, or unmanaged checkout; do not offer destructive reset/delete actions.
  • Settings/task identity failure: identify invalid template, missing ClickUp user/task, or disconnected integration and deep-link to Settings → ClickUp when user action there can resolve it.

The optimistic conversation must be removed on every blocked start, the original draft/ticket reference must remain intact, and navigation occurs only after a successful authoritative start or the explicit “Open conversation” action.

3. Active conversation and workspace identity

Expose the optional convention summary on AgentConversationWorkspace; never derive “managed” from the branch text. Extend AgentConversationWorkspaceLine and AgentsWorkspaceStatusPill with a compact ClickUp-managed indicator while preserving the existing branch/status/PR information. The header tooltip/details show the full branch, PR base, ClickUp task ID/title, frozen commit rule, frozen PR title, and “Naming frozen for this ticket.” The short header remains compact and truncates the branch as it does today.

For branchless Chat with a linked ClickUp task, show a non-workspace integration note that the convention will be resolved on Edit/Plan transition. After mode upgrade, refresh from the returned workspace summary rather than keeping the advisory preview. Replayed/recovered conversations must render the same managed identity from persisted backend state, including when the global toggle is now off.

4. Commit & Publish

In PublishWorkspaceDialog, add a small ClickUp convention summary for managed workspaces: exact branch, commit rule, locked PR title, and selected PR base. Do not add editable title controls. The confirmation text must make clear that RalphX will validate all commits in the cycle before any push/PR side effect.

In AgentsPublishPanel and pipeline notices, map typed policy failures to the existing repair/operational split. For an unpushed local commit mismatch, list each offending short SHA and subject plus the expected format, show that RalphX is repairing or waiting for repair, and retain safe terminal/copy-remediation affordances. The repair agent may reword only proven-unpushed local commits and must pass the existing completion gate plus convention validation before auto-publish retries. If violating history is already remote, rewriting would require force, or the blocker is environmental, show operator action required and never auto-amend/reset/force-push. For branch/PR-title drift, show expected versus actual and whether RalphX can safely correct only the open PR title through the normal publisher; retain the frozen title on retry. Auto-publish and PR-fix failures surface the same durable policy card and do not degrade into a generic failed status.

5. Ticket dashboard and repeated entry points

Ticket-dashboard launches and any other Agent-entry composer reuse the same preview component/query/error model rather than duplicating ClickUp naming logic. Conversation/sidebar rows may show the existing branch text, but managed status must come from the workspace summary if a badge is added. Keep the full policy details in the active header/publish surfaces to avoid visual noise.

Interaction, accessibility, and native behavior

  • Treat previews as cancellable/debounced read-only work; a shell/callout paints before network work, and stale task/project responses cannot overwrite the latest selection.
  • Use text plus icon/status, not color alone. All fields have labels/descriptions; errors use aria-describedby; any icon-only copy/open control uses the app tooltip and an accessible name.
  • Use explicit WebKit-safe background/border longhands for new themed cards and verify Settings, composer error cards, header tooltip, and publish dialog in native Tauri/WKWebView as well as Chromium.
  • Long Unicode branches/titles must wrap or truncate without hiding the full value from accessible text/tooltip; copy actions copy the full backend value.

Progression Scenarios

  1. Setting off: all current ClickUp/Jira/Linear behavior and per-conversation branch naming remain unchanged.
  2. First strict start: RalphX fetches task + current ClickUp user, renders and stores the convention, creates/pushes the exact branch, and checks it out as a linked worktree.
  3. Exact branch already exists: validate it against the stored/expected binding and reuse it; hydrate an exact open PR if present.
  4. Another active owner exists: fail before creating a workspace and redirect to the owning conversation.
  5. Task renamed/template edited: reuse the frozen binding; do not rename the branch or change commit/PR rules mid-ticket.
  6. Prior PR merged: after the old owner releases the worktree, safely recreate/fast-forward the same branch and open a new PR cycle.
  7. Prior branch diverged or is dirty: block; never hard-reset or discard work.
  8. Nonconforming manual commit: reject via hook when possible and always block publish with the offending commit SHA/subject plus remediation.
  9. PR describer proposes another title: retain its body but force the frozen strict PR title.
  10. True length/collision problem: use deterministic truncation and the smallest documented hash suffix; persist the result forever.
  11. Branchless Chat → Edit/Plan: resolve and reserve the strict binding at mode switch, applying the same busy/race checks.
  12. Strict toggle later disabled: existing strict bindings still reuse their exact branch; new unbound tickets use legacy behavior.
  13. Continue the same conversation after merged PR: the Chat-service rollover path verifies terminal/content-equivalent history, removes the clean old worktree, refreshes the exact frozen branch to the effective base, atomically starts a new cycle, and does not append -next-*.
  14. Squash/rebase merge: accept reuse only when the existing merged-or-content-equivalent helper proves the old branch content landed; ancestor-only checks are insufficient.
  15. Closed without merge: keep the frozen binding and old branch intact, but block the next cycle until the user resolves/discards the unmerged history outside automated reuse.
  16. Unpushed bad commit: classify as needs_agent, run the existing workspace-repair flow with convention context, verify current-attempt completion plus all cycle commits, then retry publish.
  17. Remote bad history or operational failure: persist a failed/operator-action event; startup recovery must not recast it as a safe local repair or loop publication.
  18. External or startup PR discovery: adopt only exact strict head/base evidence, then start normal polling or terminalization; mismatched evidence remains visible but cannot mutate the binding/publication state.

Affected Files

  • Extend settings domain/persistence: src-tauri/src/domain/integrations/clickup_settings.rs, src-tauri/src/infrastructure/{memory,sqlite}/*clickup_integration_settings*, a generated timestamped migration plus migration registration/tests, src-tauri/src/commands/clickup_commands.rs, and focused service/command tests.
  • Centralize convention rendering/enforcement: add a focused src-tauri/src/application/ticket_git_convention.rs (and sibling test file) and register it in application/mod.rs.
  • Evolve the canonical binding: src-tauri/src/application/ticket_canonical_branch.rs, src-tauri/crates/ralphx-domain/src/entities/ticket_canonical_branch.rs, repository traits and memory/SQLite implementations/tests, and the existing canonical-branch migration via a new additive migration.
  • Wire startup/worktrees and continuation: src-tauri/src/application/agent_conversation_start_service.rs, clickup_git_association.rs, its helpers.rs and sibling tests, agent_conversation_workspace.rs, agent_workspace_continuation.rs, chat_resumption.rs, chat_service/mod.rs, linked-plan-branch worktree/publication paths, external ideation messaging, mode-switch paths, and focused sibling/integration tests.
  • Reuse publish freshness/repair: thin integrations in src-tauri/src/commands/unified_chat_commands.rs, agent_workspace_auto_publish.rs, src-tauri/src/application/publish_resilience.rs, agent_workspace_publish_recovery.rs, agent_workspace_pr_supervision_recovery.rs, review-to-publish handoff, complete_agent_workspace_repair, its canonical agent payload/prompt, and focused suites; keep policy logic outside giant command/handler modules.
  • Align PR terminal/reconciliation/cleanup: src-tauri/src/application/services/pr_merge_poller.rs, agent_workspace_external_pr_reconciliation.rs, pr_startup_recovery.rs, startup_pipeline.rs, git_artifact_cleanup.rs, archive/close-PR paths, repository candidate queries, and coupled tests.
  • Enforce PR titles: src-tauri/src/domain/services/pr_publish_service.rs, src-tauri/src/application/agent_workspace_pr_description.rs, and focused tests.
  • Expose settings and preview contracts: frontend/src/api/clickup.ts, useClickUpIntegration.ts, a focused convention-preview hook/query-key module, API transforms/schemas, mocks, and their tests; add the corresponding read-only Tauri command and backend response DTO.
  • Expose managed start UX: frontend/src/components/settings/ClickUpIntegrationSettingsPanel.tsx, a focused template editor/preview component if the panel would otherwise become oversized, AgentsStartComposer.tsx, the shared composer/ticket-reference surfaces, useStartAgentConversation.ts, agentStartErrors.ts, agentSessionStore.ts, ticket-dashboard launch coverage, and focused Testing Library tests.
  • Expose persistent workspace/publish UX: frontend/src/api/chat.ts workspace transforms, AgentConversationWorkspaceLine.tsx, AgentsChatHeader.tsx, AgentsPublishWorkspaceDialog.tsx, AgentsPublishPanel.tsx, publish pipeline notices, mocks/fixtures, and focused tests.
  • Document behavior: update the relevant ticketing/Git workflow documentation and release notes without changing unrelated provider workflows.

Constraints

  • TDD-first for backend, prompt-contract, and UI regressions.
  • Preserve selected PR base semantics; ticket metadata controls the head branch name, not an unrelated base branch.
  • Preserve existing behavior when strict mode is disabled, including AgentConversationWorkspaceBranchNameHint sanitization, isolated ralphx/...-{conversation} naming, branch-mode defaults, normal continuation suffixes, existing push-status values, and generic terminal cleanup.
  • Keep worktree paths hashed and rooted by the existing resolver; never interpolate a ClickUp template result, task title, username, branch name, or PR title into a filesystem path.
  • Follow one Agent-workspace Git flow: existing direct-versus-linked-plan freshness wrappers, repair, publisher, PR poller, reconciliation, startup recovery, and cleanup services remain authoritative. Do not route this feature through task merge transitions.
  • Use validated Git refs and process-owned RalphX runtime/worktree roots for every path or hook sink; follow CodeQL path-safety rules.
  • Use additive database changes and keep legacy persisted rows readable.
  • Keep branch identity immutable after first binding.
  • Reuse the existing active-workspace repository guard; do not invent a second in-memory lock as the source of truth.
  • Follow the API snake_case → camelCase transform conventions and strict TypeScript.
  • Keep UI opening/loading first-paint safe and verify native Tauri/WKWebView behavior.
  • Use focused Rust leaf-file formatting and the repository’s selective test stack; no broad formatter churn.

Avoid

  • Do not prompt an agent to choose, remember, or recreate the canonical branch.
  • Do not create ralphx/... or per-conversation branches for a strict bound ticket; keep the existing generated naming path unchanged for non-strict workspaces.
  • Do not use branch/task/template text as a worktree directory component; hashed path identity remains independent of Git identity.
  • Do not use hash suffixes to permit parallel conversations.
  • Do not silently adopt a merely token-matching branch when the exact convention does not match.
  • Do not auto-rename, hard-reset, force-push, delete, or move a branch/worktree with unproven ownership or divergence.
  • Do not append normal continuation suffixes to a strict frozen branch, and do not treat a closed-unmerged PR as equivalent to a merge.
  • Do not add a parallel ClickUp merge/poller/recovery state machine or reuse task PendingMerge statuses for Agent workspace PRs.
  • Do not broaden generic RalphX branch-name ownership checks so ClickUp-named persistent branches become cleanup candidates.
  • Do not rely only on a Git hook, prompt, conversation title, or frontend preview for enforcement.
  • Do not duplicate ClickUp policy logic in startup, commit, and PR paths; use one convention snapshot/service.
  • Do not let template edits retroactively mutate active/existing bindings.
  • Do not expand this slice into changing Jira/Linear branch behavior.

Proof Obligations

  • The first strict start and all races converge on exactly one immutable ticket binding and exact branch.
  • A second active conversation cannot obtain a worktree for that branch and receives a navigable owner ID.
  • No strict publish path can push or create/update a PR when HEAD is on another branch or any introduced commit violates the frozen rule; the check executes inside the existing publish guard and both direct and linked-plan freshness paths remain covered.
  • Both new and existing PRs receive the frozen title even when the describer supplies another title.
  • The same branch can be safely reused after merge without reset/force operations: merge and content-equivalent squash/rebase outcomes pass, while closed-unmerged, unknown terminal state, divergence, dirty state, and local/remote unpublished work fail closed.
  • Same-conversation continuation, later-conversation start, resumption, external messaging, and startup recovery reach the same strict cycle decision; no path creates -next-* or clears publication state early.
  • Archive/terminal cleanup removes only validated clean worktrees and preserves the reusable ticket branch; standard RalphX branches retain their existing deletion behavior.
  • Setting-off behavior is byte-for-byte compatible at API/branch-policy decision boundaries.
  • Missing/renamed ClickUp task data, current-user failure, template drift, stale worktrees, remote deletion, duplicate PRs, and partial push persistence cannot produce false success.
  • The UI never claims a new conversation started when the backend returned a busy/blocked result; optimistic state is removed while the user’s draft and ClickUp reference remain available.
  • Strict-mode UI never offers isolated-branch retry or implies that changing the PR base changes the managed head branch.
  • Advisory preview and persisted workspace displays cannot drift silently: submit is authoritative, stale preview responses are ignored, and replay/recovery renders the frozen backend summary even after settings change.
  • Every manual, automatic, retry, and PR-fix publish failure exposes the same typed convention blocker and performs no push/PR side effect before validation passes.
  • Existing Jira title normalization and non-ClickUp publication tests remain green.

Testing Strategy

Backend TDD

  • Pure renderer tests for placeholders, casing, whitespace/punctuation normalization, required task ID, unsafe refs, Unicode, length truncation/hash determinism, and unknown placeholders.
  • Migration and concrete SQLite repository round trips for new settings and immutable ticket binding snapshots; cover create-if-absent races/CAS rejection and legacy rows.
  • ClickUp settings service/command tests for save, validate, disconnect-with-template-preservation, and current-user-required behavior.
  • Production start-path and mode-switch tests for first create, exact local/remote reuse, exact open PR hydration, mismatched PR block, explicit wrong-branch override rejection, active-owner redirect, concurrent first starts, task rename/template drift, branchless Chat → Plan/Edit recovery from persisted ClickUp link, Review PR isolation, hashed worktree paths with hostile/Unicode template values, and toggle-off compatibility with existing hint-generated names.
  • Worktree tests for linked ownership, stale managed worktree recovery, unmanaged worktree block, branch mismatch, clean post-merge fast-forward/recreate, divergence block, and preservation on archive.
  • Commit tests through manual publish, auto-publish, repair/PR-fix, review handoff, and update-from-base entry paths; assert both accepted/rejected subjects, publish-guard serialization, unchanged status/event progression, direct-versus-linked-plan freshness routing, and absence of push/PR side effects before validation.
  • Publish failure-class tests proving unpushed policy violations become agent-fixable needs_agent, remote/force-rewrite and environment failures stay operational, and typed classification does not depend on prose matching.
  • Workspace-repair completion tests proving stale attempts, stale policy snapshots, wrong branch/HEAD/base, dirty/conflicted state, or any remaining nonconforming cycle commit cannot trigger auto-publish; a valid current repair can.
  • PR publisher tests for create/update/duplicate recovery with a frozen strict title and unchanged non-strict/Jira behavior.
  • Terminal-cycle tests through the production Chat-service/send path for normal suffix behavior when non-strict, exact-branch reuse when strict, atomic publication-field reset, deleted remote head, clean fast-forward, already-current branch, squash/content-equivalent merge, closed-unmerged block, remote-only work block, and crash/retry idempotence.
  • Continuation/resumption/external messaging tests proving all entry points agree on terminal strict availability and never bypass cycle preparation.
  • Cleanup tests proving strict worktrees can be removed while their branch is retained, standard owned branches are still deleted, and dirty/mismatched/unmanaged paths remain untouched.
  • External/live/startup reconciliation tests proving exact strict head+base evidence links and polls/terminalizes, while wrong base/head/title policy blockers do not silently mutate publication or binding state.

Frontend TDD

  • Zod/API/transform/hook/mock tests for settings round trips, preview success/blockers, and additive workspace convention summaries; old payloads without a summary remain valid.
  • Settings panel behavior for disconnected/connected states, collapsed-off versus expanded-on editor, explicit Save/Discard, unsaved edits, field-linked placeholder/ref/byte-length errors, backend rejection preserving the last saved policy, live preview, reconnect semantics, and “new bindings only” copy.
  • Composer tests for lazy/cancellable preview, stale response suppression after project/task changes, ready/loading/off/branchless states, PR-base wording, hidden isolated toggle, authoritative submit drift, and draft/reference preservation after failure.
  • Typed start-error tests for busy-owner navigation, expected/found mismatch, PR link, divergent/dirty/unmanaged blockers, Settings deep-link, and proof that strict errors never render the existing isolated retry action.
  • Active workspace/header tests for managed versus ordinary workspaces, compact and full-value display, frozen policy after global disable, replay/recovery, and Chat → Edit/Plan hydration.
  • Publish dialog/panel tests for locked branch/commit/PR summary, offending commit rows, auto-publish and PR-fix parity, accessible copy/open actions, and absence of push/PR-success UI on validation failure.
  • Ticket dashboard launch and every shared Agent-entry surface reuse the same preview/error behavior.
  • Assert user-visible behavior with Testing Library rather than implementation state; include reduced-motion/accessibility assertions where new loading/status UI is introduced.

Validation

  • Focused Vitest suites for touched files, then frontend typecheck/lint.
  • Focused Rust tests first; broad lib/nextest and both clippy gates only after focused suites are green.
  • python3 scripts/check-layering.py, migration validation, and rustfmt checks on every touched Rust leaf file.
  • Manual native Tauri smoke against a disposable ClickUp task/repository: first branch, hashed worktree path, busy redirect, valid/invalid commit and repair, PR title, direct and Plan-linked update-from-base where applicable, GitHub merge terminalization, same-branch reuse, closed-unmerged block, and a deliberately divergent branch.
  • Review the final diff against HEAD and perform the required false-success audit on persistence, race, recovery, publish, and cleanup paths.

Risks And Open Questions

  • GitHub may auto-delete a merged head branch; the reuse algorithm must treat deletion as normal only after confirming the prior PR is terminal and no unpublished work exists.
  • Git hooks can be bypassed, so publish-time validation remains mandatory.
  • Very long Unicode titles need byte-aware Git-ref limits even though the displayed preview is character-oriented.
  • A future project-specific override may be useful because ClickUp workspaces can span repositories, but v1 follows the current global ClickUp settings model.

Generated by RalphX

@adriandemian adriandemian changed the title Implement requested behavior correctly ClickUp-style branch the source of truth for ticket-backed Agent work Jul 17, 2026
@adriandemian adriandemian changed the title ClickUp-style branch the source of truth for ticket-backed Agent work Make ClickUp ticket branches the source of truth for Agent work Jul 17, 2026
@adriandemian adriandemian changed the title Make ClickUp ticket branches the source of truth for Agent work ClickUp-style branch becomes source of truth for ticket-backed Agent work Jul 17, 2026
@adriandemian adriandemian changed the title ClickUp-style branch becomes source of truth for ticket-backed Agent work ClickUp-style branch the source of truth for ticket-backed Agent work Jul 17, 2026
@adriandemian adriandemian changed the title ClickUp-style branch the source of truth for ticket-backed Agent work Make ClickUp branches the source of truth for ticket-backed Agent work Jul 17, 2026
@adriandemian adriandemian changed the title Make ClickUp branches the source of truth for ticket-backed Agent work ClickUp-style branch the source of truth for ticket-backed Agent work Jul 17, 2026
@adriandemian adriandemian changed the title ClickUp-style branch the source of truth for ticket-backed Agent work Strict ClickUp ticket branches become the source of truth for Agent work Jul 17, 2026
@adriandemian adriandemian changed the title Make ClickUp strict Git naming the source of truth for ticket-backed Agent work ClickUp-style branch the source of truth for ticket-backed Agent work Jul 22, 2026
Main's #787 workspace-cleanup rewrite made terminal cleanup bail with
branch_not_ralphx_owned for any branch not matching the ralphx/<slug>/
naming, skipping worktree removal entirely. Strict ClickUp ticket
workspaces use the provider's branch convention (e.g. eng-42_ticket_ada),
so after that change merged into this branch the strict startup cleanup
and next-cycle prepare paths could no longer release their worktrees.

Add a strict-managed cleanup variant that removes the terminal worktree
while preserving the canonical ticket branch, and route the strict
prepare and startup paths through it. Generic (non-strict) cleanup is
unchanged and still preserves non-RalphX branches by skipping removal.

Fixes the two Rust lib tests failing on PR #774 (Shard 2/4):
- later_conversation_prepares_next_generation_only_after_clean_terminal_release
- startup_terminal_workspace_cleanup_removes_worktree_but_preserves_strict_ticket_branch
@adriandemian adriandemian changed the title ClickUp-style branch the source of truth for ticket-backed Agent work MBE-3250: ClickUp-style branch the source of truth for ticket-backed Agent work Jul 22, 2026
@adriandemian adriandemian changed the title MBE-3250: ClickUp-style branch the source of truth for ticket-backed Agent work ClickUp-style branch the source of truth for ticket-backed Agent work Jul 22, 2026
The two guarded-review race tests polled only 500ms (100x5ms) for the
spawned review task to reach disable_pr_auto_merge. That task performs
real git subprocess work (target resolution) before the call, so on a
loaded CI shard it can exceed the budget, failing Rust Lib Tests
(Shard 2/4) with left: 0, right: 1. The mock increments the counter on
entry and then holds a 250ms sleep().await, so observation still lands
inside that window regardless of total wait; only the budget to reach
the call needed widening.
…1517796

# Conflicts:
#	src-tauri/src/http_server/handlers/agent_workspaces/mod.rs
#	src-tauri/tests/suite_agent_workspace/agent_workspace_repair_auto_publish.rs
@adriandemian adriandemian changed the title ClickUp-style branch the source of truth for ticket-backed Agent work Make ClickUp ticket branches the source of truth for Agent work Jul 22, 2026
@adriandemian adriandemian changed the title Make ClickUp ticket branches the source of truth for Agent work Enforce strict ClickUp Git naming for ticket-backed Agent work Jul 22, 2026
@adriandemian adriandemian changed the title Enforce strict ClickUp Git naming for ticket-backed Agent work Enforce strict ClickUp ticket branches and frozen PR naming Jul 22, 2026
Resolve conflicts from #810 (legacy Claude-only team mode removal):
- agent_conversation_start_service/mod.rs: keep strict-ticket PR-naming
  imports, drop removed TeamService import.
- agent_workspace_review_unfinished_git_tests.rs: adopt main's bounded
  timeout pause-synchronization (pause_started) matching the shared tail.
- unified_chat_commands/mod.rs: drop stale team_service param/arg from
  publish_agent_conversation_workspace_while_guarded to match main's
  team-mode removal and the existing 4-arg call sites.
@adriandemian adriandemian changed the title Enforce strict ClickUp ticket branches and frozen PR naming ClickUp-style branch the source of truth for ticket-backed Agent work Jul 22, 2026
Base merge updated update_agent_conversation_workspace_from_base_for_app_state
and publish_agent_conversation_workspace_for_app_state to 4-arg signatures, but
three test call sites still passed an extra None, breaking the lib test compile
(Rust Lib Tests Archive CI failure, E0061).
Resolve conflicts between PR naming enforcement work and origin/main:
- startup_pipeline: keep main's inline background lane, keep ticket branch repo arg
- unified_chat_commands: publish guard moves into the _with_caller impl;
  while_guarded wrapper now owns the workspace review lifecycle lock
- pr_publish_service: keep the frozen-title path inside main's
  publish_draft_pr_inner; port main's managed-marker PR body format into
  the extracted pr_publish_body module
- tests: keep both strict-ticket-policy and no-origin publish coverage
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant