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)
|
log.info("Created custom field '%s' (%s) on list %s", name, field_type, list_id)
|
||||||
return result
|
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:
|
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.
|
"""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:
|
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:
|
if not task_id or not ctx:
|
||||||
return
|
return
|
||||||
client = _get_clickup_client(ctx)
|
client = _get_clickup_client(ctx)
|
||||||
if not client:
|
if not client:
|
||||||
return
|
return
|
||||||
try:
|
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(
|
client.add_comment(
|
||||||
task_id,
|
task_id,
|
||||||
f"📝 CheddahBot generated a content outline.\n\n"
|
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(
|
def _run_phase2(
|
||||||
*,
|
*,
|
||||||
agent,
|
agent,
|
||||||
|
|
@ -556,8 +593,12 @@ def _run_phase2(
|
||||||
is_service_page: bool = False,
|
is_service_page: bool = False,
|
||||||
capabilities_default: str = "",
|
capabilities_default: str = "",
|
||||||
) -> str:
|
) -> str:
|
||||||
# Read the (possibly edited) outline
|
# Resolve outline path: ClickUp field → convention → state fallback
|
||||||
outline_path = existing_state.get("outline_path", "")
|
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 = ""
|
outline_text = ""
|
||||||
if outline_path:
|
if outline_path:
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue