Fix 2: Store OutlinePath in ClickUp custom field for Phase 2 retrieval
Phase 1 now writes OutlinePath to a ClickUp custom field via
set_custom_field_by_name(). Phase 2 reads it back with
get_custom_field_by_name(), falling back to convention path
({outline_dir}/{slug}/outline.md) if the field is empty.
Added get_task(), set_custom_field_by_name(), and
get_custom_field_by_name() helpers to ClickUpClient.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix/customer-field-migration
parent
5ddeb93033
commit
c80d237e36
|
|
@ -419,6 +419,56 @@ class ClickUpClient:
|
|||
log.info("Created custom field '%s' (%s) on list %s", name, field_type, list_id)
|
||||
return result
|
||||
|
||||
def get_task(self, task_id: str) -> ClickUpTask:
|
||||
"""Fetch a single task by ID."""
|
||||
resp = self._client.get(f"/task/{task_id}")
|
||||
resp.raise_for_status()
|
||||
return ClickUpTask.from_api(resp.json(), self._task_type_field_name)
|
||||
|
||||
def set_custom_field_by_name(
|
||||
self, task_id: str, field_name: str, value: Any
|
||||
) -> bool:
|
||||
"""Set a custom field by its human-readable name.
|
||||
|
||||
Looks up the field ID from the task's list, then sets the value.
|
||||
Falls back gracefully if the field doesn't exist.
|
||||
"""
|
||||
try:
|
||||
task_data = self._client.get(f"/task/{task_id}").json()
|
||||
list_id = task_data.get("list", {}).get("id", "")
|
||||
if not list_id:
|
||||
log.warning("Could not determine list_id for task %s", task_id)
|
||||
return False
|
||||
|
||||
fields = self.get_custom_fields(list_id)
|
||||
field_id = None
|
||||
for f in fields:
|
||||
if f.get("name") == field_name:
|
||||
field_id = f["id"]
|
||||
break
|
||||
|
||||
if not field_id:
|
||||
log.warning("Field '%s' not found in list %s", field_name, list_id)
|
||||
return False
|
||||
|
||||
return self.set_custom_field_value(task_id, field_id, value)
|
||||
except Exception as e:
|
||||
log.error("Failed to set field '%s' on task %s: %s", field_name, task_id, e)
|
||||
return False
|
||||
|
||||
def get_custom_field_by_name(self, task_id: str, field_name: str) -> Any:
|
||||
"""Read a custom field value from a task by field name.
|
||||
|
||||
Fetches the task and looks up the field value from custom_fields.
|
||||
Returns None if not found.
|
||||
"""
|
||||
try:
|
||||
task = self.get_task(task_id)
|
||||
return task.custom_fields.get(field_name)
|
||||
except Exception as e:
|
||||
log.warning("Failed to read field '%s' from task %s: %s", field_name, task_id, e)
|
||||
return None
|
||||
|
||||
def discover_field_filter(self, list_id: str, field_name: str) -> dict[str, Any] | None:
|
||||
"""Discover a custom field's UUID and dropdown option map.
|
||||
|
||||
|
|
|
|||
|
|
@ -66,13 +66,16 @@ def _sync_clickup_start(ctx: dict | None, task_id: str) -> None:
|
|||
|
||||
|
||||
def _sync_clickup_outline_ready(ctx: dict | None, task_id: str, outline_path: str) -> None:
|
||||
"""Post outline comment and move ClickUp task to 'outline review'."""
|
||||
"""Post outline comment, set OutlinePath field, and move to 'outline review'."""
|
||||
if not task_id or not ctx:
|
||||
return
|
||||
client = _get_clickup_client(ctx)
|
||||
if not client:
|
||||
return
|
||||
try:
|
||||
# Store OutlinePath in ClickUp custom field for Phase 2 retrieval
|
||||
client.set_custom_field_by_name(task_id, "OutlinePath", outline_path)
|
||||
|
||||
client.add_comment(
|
||||
task_id,
|
||||
f"📝 CheddahBot generated a content outline.\n\n"
|
||||
|
|
@ -541,6 +544,40 @@ def _run_phase1(
|
|||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _resolve_outline_path(ctx: dict | None, task_id: str, keyword: str, config) -> str:
|
||||
"""Resolve the outline path from ClickUp field or convention.
|
||||
|
||||
Priority: ClickUp OutlinePath field → convention path → empty string.
|
||||
"""
|
||||
# Try ClickUp custom field first
|
||||
if task_id and ctx:
|
||||
client = _get_clickup_client(ctx)
|
||||
if client:
|
||||
try:
|
||||
outline_path = client.get_custom_field_by_name(task_id, "OutlinePath")
|
||||
if outline_path and str(outline_path).strip():
|
||||
return str(outline_path).strip()
|
||||
except Exception as e:
|
||||
log.warning("Failed to read OutlinePath from ClickUp for %s: %s", task_id, e)
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
# Fallback to convention path
|
||||
slug = _slugify(keyword)
|
||||
if slug and config and config.content.outline_dir:
|
||||
convention_path = Path(config.content.outline_dir) / slug / "outline.md"
|
||||
if convention_path.exists():
|
||||
return str(convention_path)
|
||||
|
||||
# Try local fallback too
|
||||
if slug:
|
||||
local_path = _LOCAL_CONTENT_DIR / slug / "outline.md"
|
||||
if local_path.exists():
|
||||
return str(local_path)
|
||||
|
||||
return ""
|
||||
|
||||
|
||||
def _run_phase2(
|
||||
*,
|
||||
agent,
|
||||
|
|
@ -556,8 +593,12 @@ def _run_phase2(
|
|||
is_service_page: bool = False,
|
||||
capabilities_default: str = "",
|
||||
) -> str:
|
||||
# Read the (possibly edited) outline
|
||||
# Resolve outline path: ClickUp field → convention → state fallback
|
||||
outline_path = _resolve_outline_path(ctx, task_id, keyword, config)
|
||||
if not outline_path:
|
||||
# Last resort: check existing_state (for continue_content calls)
|
||||
outline_path = existing_state.get("outline_path", "")
|
||||
|
||||
outline_text = ""
|
||||
if outline_path:
|
||||
try:
|
||||
|
|
|
|||
Loading…
Reference in New Issue