Commit Graph

97 Commits (7344781e73d99273af2f1962bdada331a02fa5ed)

Author SHA1 Message Date
PeninsulaInd 7344781e73 Fix clickup_task_status not reaching tool ctx
The tool registry only forwarded clickup_task_id from args to ctx,
so clickup_task_status was silently filtered out. Phase 2 detection
in create_content never saw the original "outline approved" status.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 19:23:32 -05:00
PeninsulaInd b4f1ddfced Fix Phase 2 detection: pass original task status to create_content
The scheduler sets status to "automation underway" before the tool runs,
so create_content's API re-fetch never saw "outline approved". Now the
scheduler passes the pre-change status via clickup_task_status arg.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 19:18:12 -05:00
PeninsulaInd a3eacbd0e5 Add status-aware auto_execute gate for Content Creation Phase 2
The scheduler's blanket auto_execute check was blocking "outline approved"
Content Creation tasks from reaching Phase 2. Now checks
auto_execute_on_status for status-specific overrides. Also adds
trigger_hint to skip log messages for easier debugging.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 19:05:50 -05:00
PeninsulaInd 8f2ec48e10 Increase CLI timeout to 15min, fix result file reprocessing, and save outlines directly
- Bump Claude Code subprocess timeout from 5 to 15 minutes for longer content tasks
- Fix scheduler result file loop: unlink source if already exists in processed/ dir
- Pass outline save path to execution brain so it writes directly to network share

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 14:38:48 -05:00
PeninsulaInd af67ae166d Fix ntfy dedup: make duplicate suppression permanent for process lifetime
The time-based 1-hour dedup window wasn't preventing repeated skip
notifications for tasks with missing fields every poll cycle. Replaced
with a permanent sent-set so each unique message only fires once per
process run. Dedup log bumped to INFO for visibility.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 14:22:27 -05:00
PeninsulaInd 9102657c15 Add dedup, daily cap, and 429 backoff to ntfy notifier
Prevents notification spam from repeated ClickUp poll cycles finding the
same tasks with missing fields. Dedup suppresses identical messages within
a 60-min window, daily cap stops at 200 sends (under ntfy.sh 250 free
tier), and 429 responses suppress all sends for the rest of the day.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 17:32:52 -06:00
PeninsulaInd 1e26969ff8 Add explicit poll_task_types allowlist to filter ClickUp polling
Only tasks with Work Category in poll_task_types are fetched and
processed. Prevents unrecognized types (SEO Audit, AEO, etc.) from
being evaluated every poll cycle. Falls back to skill_map keys if
the list is empty.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 19:28:42 -06:00
PeninsulaInd ca73689099 Add /api/system/briefing/force endpoint to trigger morning briefing on demand
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 22:30:46 -06:00
PeninsulaInd 6b5d0ac71e Add daily morning briefing via ntfy push notifications
Sends a summary at 6:30 AM weekdays / 8:00 AM weekends (Central) with
Cora reports needing action, outlines to approve, PRs to review, and
errors — grouped by customer. Cora tasks cross-reference network share
folders to show file pipeline status. No LLM API credits burned.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 22:15:49 -06:00
PeninsulaInd 45ab4c1b33 Fix task looping: increase stale recovery to 6h, add file logging, use UNC paths
- Stale task recovery threshold 2h → 6h to prevent resetting tasks while Cora is still running
- Add rotating file logger (WARNING+) to logs/cheddahbot.log for debugging
- Silence httpx/httpcore INFO spam from terminal
- Switch watch folder paths from Z: drive letters to UNC paths to avoid intermittent mount drops
- Fix test_db tests to add messages so list_conversations includes them

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 21:41:46 -06:00
PeninsulaInd 83c7c378e5 Add ntfy.sh push notifications for alerts and errors
New NtfyNotifier subscribes to NotificationBus and routes notifications
to ntfy.sh topics based on category + message pattern matching. Two
channels configured: human_action (task completed, Cora ready, etc.)
and errors (failures, missing fields). HTTP posts fire in daemon threads
to avoid blocking the notification bus lock.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 11:07:51 -06:00
PeninsulaInd 813dd4cb01 Wire Phase 3 test block pipeline into automated optimization flow
When a ClickUp task with a URL arrives at create_content, route it to the
new optimization pipeline instead of the outline-gate Phase 1/Phase 2 flow.
The pipeline runs 8 steps via the execution brain (scrape, deficit analysis,
entity filtering, template writing, test block generation, readability
rewrite, validation, surgical instructions doc) and uploads deliverables
(test_block.html, optimization_instructions.md, validation_report.json)
directly to ClickUp as attachments.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 15:49:49 -06:00
PeninsulaInd 236b64c11c Add Cora report distribution watcher to route xlsx to pipeline inboxes
New watcher thread scans Z:/Cora-For-Human for post-macro Cora xlsx files,
matches them to ClickUp tasks by keyword, and copies to the appropriate
pipeline inbox (Z:/cora-inbox for Link Building, Z:/content-cora-inbox for
Content/OPO). Fixes issue where shared Cora reports left one pipeline's
tasks stuck in automation underway forever.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 13:07:03 -06:00
PeninsulaInd 84c81b6df4 Default PR headlines to awareness framing, only allow announcement language for actual news
- Added _is_actual_news() detection based on PR Topic field signals
  (e.g. "Actual News", "New Product", "Launch")
- Headline generator defaults to awareness verbs (Highlights, Reinforces,
  Delivers) and bans announcement verbs (Announces, Launches, Expands)
- Judge prompt disqualifies fabricated events for non-news topics
- Body writer prompt explicitly forbids inventing expansions, milestones,
  or demand claims — frames as existing capabilities
- Updated skill file to match awareness-by-default approach

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 08:54:06 -06:00
PeninsulaInd 594245fec3 Pull PR topic from custom field and validate required fields before executing
- Changed Press Release topic source from task_name to "PR Topic" custom field
- Added required_fields config to skill_map so scheduler validates before
  setting task to "automation underway"
- Tasks missing PR Topic, Customer, or IMSURL are silently skipped with
  a notification and re-checked on next poll
- Updated docs and CLAUDE.md with new field mapping

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 16:29:12 -06:00
PeninsulaInd 917445ade4 Steps 7-8: Eliminate KV store from task pipelines and update tests
Remove all KV store reads/writes from task pipeline code. ClickUp is now
the single source of truth for task state. File location (processed/
subfolder) tracks file processing state. Loop timestamps use in-memory
dict on Scheduler.

Source changes:
- scheduler.py: Remove KV dedup, fallback sync path, docx extraction;
  tools own their ClickUp sync; in-memory timestamps
- press_release.py: Remove KV state writes, log-only _set_status
- linkbuilding.py: Remove KV state writes, processed/ subfolder check
- content_creation.py: Phase detection via ClickUp API status, remove
  KV phase/state tracking, _update_kv_state removed
- clickup_tool.py: Rewrite to query ClickUp API directly
- ui.py: Pipeline status polling is now a no-op

Test changes:
- test_scheduler.py: Remove KV dedup tests, remove fallback path test,
  verify ClickUp API calls instead of KV state
- test_content_creation.py: Mock _get_clickup_client for phase detection,
  verify ClickUp sync calls instead of KV assertions
- test_linkbuilding.py: Remove KV status test, verify ClickUp API calls
- test_clickup_tools.py: Rewrite for API-backed tools
- test_scheduler_helpers.py: Test in-memory timestamps

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 16:26:44 -06:00
PeninsulaInd 9a3ba54974 Fix 8: Reset task to 'to do' when outline file is missing
When Phase 2 cannot find the outline file at any of the searched
locations, the task is now reset to its initial poll status in ClickUp
with a comment explaining the issue and suggesting a Phase 1 re-run.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 16:03:43 -06:00
PeninsulaInd 1eb7df6143 Fix 7: Report attachment upload failures in ClickUp comment
When upload_attachment() returns False, the ClickUp comment now includes
a warning listing which files failed to upload and their local paths,
so Bryan can retrieve them manually.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 16:02:54 -06:00
PeninsulaInd 8c82ccf2de Fixes 4,5: Content watcher sets automation status and moves files to processed/
Fix 4: Content watcher now sets ClickUp task to "automation underway" after
matching, matching the behavior of the link building watcher.

Fix 5: Content watcher now moves .xlsx files to a processed/ subfolder on
success, preventing re-processing on subsequent scans.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 16:01:57 -06:00
PeninsulaInd c80d237e36 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>
2026-02-27 15:58:26 -06:00
PeninsulaInd 5ddeb93033 Fix 1: AutoCora prioritized sweep and KV-free result polling
Replace single-day task filter with multi-pass sweep when no explicit
target_date: (1) due today, (2) overdue + current month tag, (3) last
month tag, (4) look-ahead 2 days. Deduplicate across passes.

Remove KV store from submit (dedup by job file existence) and result
poller (scan results/ folder directly, move to processed/ after handling).
Scheduler auto-submit no longer passes explicit target_date.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 15:57:09 -06:00
PeninsulaInd 7d44014d7a Fix 3: Add staleness recovery for stuck automation tasks
Add date_updated field to ClickUpTask dataclass. Add _recover_stale_tasks()
to scheduler that resets tasks stuck in "automation underway" for >2 hours
back to "to do" with an explanatory comment. This prevents tasks from being
permanently stuck if CheddahBot crashes mid-execution.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 15:52:04 -06:00
PeninsulaInd 41487c8d6b Add file-first content creation pipeline with Cora inbox watcher
Content tasks now trigger from Cora xlsx files dropped in Z:/content-cora-inbox/
instead of auto-firing from ClickUp polling. The watcher fuzzy-matches files to
ClickUp tasks and auto-detects content type from URL presence (optimization vs
new content). Adds cli_flags support for service page hints.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 17:29:04 -06:00
PeninsulaInd 2ef7ae2607 Merge branch 'content-creation' 2026-02-25 15:13:57 -06:00
PeninsulaInd a3b8457afe Add two-phase content creation tool with human-in-the-loop outline review
Phase 1 researches competitors and generates an outline via the execution brain,
saves it to a network/local path, and pauses for human review. Phase 2 picks up
the approved outline and writes full SEO-optimized content. ClickUp integration
maps "On Page Optimization" and "Content Creation" work categories, with
"outline approved" added to poll_statuses for automatic Phase 2 triggering.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 15:10:09 -06:00
PeninsulaInd ada28d1655 Add AutoCora auto-submit for today's tasks and fix pipeline issues
- Auto-submit Cora jobs for tasks due today on each autocora loop cycle
- Move ClickUp tasks to "automation underway" at submission time
- Default to blank URL for tasks missing IMSURL (new content)
- Use task Keyword field as project_name in folder watcher (not task name)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 15:09:12 -06:00
PeninsulaInd bc64fae6f1 Add AutoCora job submission and result polling automation
Automates Cora SEO report workflow: queries ClickUp for qualifying tasks,
submits jobs to a shared folder queue, polls for results, and updates task
statuses. Includes two tools (submit_autocora_jobs, poll_autocora_results),
a scheduler polling loop, and 30 tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 07:52:53 -06:00
PeninsulaInd 0e3e3bc945 Wire scheduler to API/UI and improve loop control and PR headlines
- Pass scheduler instance to API router and UI for loop timestamps
  and force-run endpoints
- Add interruptible waits and force_heartbeat/force_poll methods
- Record last_run timestamps for all scheduler loops in KV store
- Update press release headline examples with real client headlines

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 21:18:29 -06:00
PeninsulaInd ffa8ad49e5 Add combined Cora Reports endpoint with keyword deduplication
New /api/tasks/need-cora endpoint pulls tasks needing Cora reports
across Link Building, On Page Optimization, and Content Creation work
categories, deduplicates by Keyword field, and filters to a 30-day
window. Dashboard overview now shows Cora Reports as the first section
with color-coded type badges (LB, OPO, Content).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 21:17:50 -06:00
PeninsulaInd 0b3ab904de Add API cost tracking and switch planner to Grok 4.1 Fast
Track per-call token usage and estimated costs across all OpenRouter models.
Switch planner agent from Claude Sonnet 4.6 ($3/$15 per M) to Grok 4.1 Fast
($0.20/$0.50 per M) for ~25x cost reduction. Add budget alerts, a dashboard
card, and a check_api_usage tool for visibility into spending.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 18:00:11 -06:00
PeninsulaInd ab2c313baa Add report_issue tool and planner agent for self-improvement tracking
- New report_issue tool logs bugs/improvements to memory/improvement_requests.md
- Planner agent (Sonnet via OpenRouter) for architecture and debugging tasks
- Heartbeat checks for pending improvement requests to surface to Bryan

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 17:31:28 -06:00
PeninsulaInd 30757b5bcf Generate conversation titles via LLM instead of truncating first line
- _maybe_set_title sets a quick truncated fallback immediately
- Then fires a background thread to ask the LLM for a 5-8 word summary
- Background thread doesn't block the streaming response
- Title appears in sidebar on first chunk, then upgrades when LLM responds

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:11:34 -06:00
PeninsulaInd d771dd5c80 Stop creating empty conversations that clutter sidebar with 'New Chat'
- agent.new_conversation() now just resets conv_id to None; DB row is
  created lazily by ensure_conversation() when user sends first message
- on_app_load no longer eagerly creates a conversation on page load
- list_conversations filters out conversations with no messages so
  orphaned empty rows don't appear in the sidebar

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:08:34 -06:00
PeninsulaInd 603878e095 Fix conversation titles stuck on 'New Chat' and add loop timestamps to dashboard
- Move _maybe_set_title() to run at start of respond() generator (before streaming)
  so titles are set even if the generator is closed mid-stream by Gradio
- Refresh sidebar conv list on first streaming chunk for immediate title display
- Backfilled 34 existing conversation titles from their first user messages
- Add scheduler loop status cards (heartbeat, poll, clickup, folder_watch) to
  both System Health and Notifications tabs in the HTML dashboard
- Loop cards show relative time (e.g. "3m ago") with color-coded status

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 10:54:43 -06:00
PeninsulaInd f8320a9fea Set folder watcher completed tasks to 'complete' instead of 'internal review'
Link building runs that finish successfully are already deployed,
so they go straight to complete. PRs still go to internal review.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 22:20:56 -06:00
PeninsulaInd 3c41c6bf5f Skip tasks without due dates in status counts, remove Companies card
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 22:07:26 -06:00
PeninsulaInd 6fd565734f Filter LB status cards to relevant statuses and recent tasks
Only show To Do, In Progress, Error, Automation Underway counts.
Exclude tasks due more than 1 month ago. Add "Scheduled next month"
card showing upcoming work.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 21:39:39 -06:00
PeninsulaInd f9142e6669 Skip Office temp/lock files (~$) in folder watcher
Prevents the watcher from picking up ~$*.xlsx lock files created by
Excel, which would match the same ClickUp task and corrupt its status.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 21:02:06 -06:00
PeninsulaInd ee7b4cc256 Add Cora Reports to Run queue as first section on Link Building tab
Filters out automation-touched tasks (error, automation underway, complete,
closed, done, internal review) and sorts by due date. Renders with 10-at-a-time
pagination and responsive mobile layout.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 20:55:07 -06:00
PeninsulaInd 3f2798d338 Add "automation underway" and "error" ClickUp statuses for bot visibility
Tasks now show "automation underway" when the bot picks them up and "error"
on failure, replacing the old "in progress" / "to do" fallbacks that were
invisible on Bryan's ClickUp board. Folder watcher also syncs ClickUp status
on match, missing IMSURL, pipeline failure, success, and exceptions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 20:36:58 -06:00
PeninsulaInd 62186d8dec Fix folder watcher matching, notifications, and BLM venv isolation
- Remove status filter from folder watcher so tasks in any open status
  (including "internal review") are matched by keyword
- Add retry logic for stuck processing/blocked/unmatched KV states
- Fix notification ordering (newest first, limit 50) and date parsing
- Use BLM's own .venv Python instead of uv run for subprocess calls
- Document external tools venv convention in CLAUDE.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 13:35:54 -06:00
PeninsulaInd 3b4a8e47be Require IMSURL for Cora pipeline, fail early if missing
Remove placeholder URL fallback from ingest-cora args. Add early
validation in run_cora_backlinks and folder watcher — if IMSURL is
empty, block the task with a notification instead of running with a
fake URL. Update tests to pass money_site_url and add missing-URL test.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 12:11:11 -06:00
PeninsulaInd 01ba657b35 Fix Cora filter, speed up Overview, add inbox path link
Remove status gate from Cora filter — LB Method is the discriminator,
not status. Derive Overview stats and Cora section from /tasks data
directly (no slow /tasks/link-building fetch). Add click-to-copy
Z:\cora-inbox path next to Cora header. Show due dates in This Month.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 11:51:29 -06:00
PeninsulaInd f67f1b9124 Rename Client field to Customer and refine Overview sections
ClickUp "Client" custom field was deleted; switch all references to
"Customer" across config, tools, tests, and docs. Refine Overview tab:
rename Due Soon to Up Next (today/tomorrow, fallback next 5), expand
This Month to include both month tag and due-date-in-month matches.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 11:20:19 -06:00
PeninsulaInd 0d60b1b516 Overhaul dashboard to use Overall lists with focused views
Switch from get_tasks_from_space (all lists) to get_tasks_from_overall_lists
(only "Overall" list per folder) to reduce noise. Add tags and date_done
fields to ClickUpTask. Redesign Overview tab with Due Soon, This Month,
and Cora Reports Needed sections. Add Recently Completed and In Progress
Not Started sections to Link Building tab.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 10:59:55 -06:00
PeninsulaInd e1992fa049 Add data-driven dashboard with API backend
- New cheddahbot/api.py: FastAPI router with endpoints for tasks,
  link building, press releases, agents, system health, notifications,
  KV states, and cache management (all cached 5min)
- Rewrote dashboard/index.html: replaces all hardcoded data with JS
  that fetches from /api/ endpoints. Tabs: Overview, Link Building,
  Press Releases, By Company, System Health, Agents, Notifications
- Updated __main__.py: mounts API router, removes old inline
  /api/linkbuilding/status endpoint
- Fixed run_link_building to reject empty LB Method instead of
  defaulting to Cora Backlinks

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 21:53:51 -06:00
PeninsulaInd dd39fa2e94 Show tool call args in chat and raise max iterations to 15
- Display tool arguments in the calling indicator so user can see what
  each tool call is doing (e.g. Calling delegate_task(prompt='...')...)
- Bump MAX_TOOL_ITERATIONS from 5 to 15 for complex chat interactions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 21:44:41 -06:00
PeninsulaInd d9e0020b67 Fix UI errors, message formatting, scheduler auto_execute, and LLM retry
Core fixes:
- Rewrite router.py format_messages_for_llm() to properly handle tool
  call/result message pairs in OpenAI format instead of faking them as
  user messages — root cause of most LLM API errors
- Fix scheduler ignoring auto_execute:false flag, which caused all Link
  Building tasks to be incorrectly executed and moved to internal review
- Add safety check so Skipped/Error tool results don't get marked as
  completed in ClickUp

Additional improvements:
- Add LLM retry logic (2 retries on transient 5xx/timeout/rate-limit)
- Replace raw LLM tracebacks with friendly error messages
- Fix ghost assistant bubble in UI by deferring append to first chunk
- Auto-title conversations from first user message
- Consistent tool_call_id generation (resolve once, reuse everywhere)
- Reduce pipeline status polling from 3s to 10s
- Update CLAUDE.md: remove stale watchdog/autostart docs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 21:34:02 -06:00
PeninsulaInd 916bec8c0e Reformat code and update ClickUp tools to reset pattern
- Ruff format: consistent dict/call wrapping in agent.py, db.py,
  skills.py, delegate.py
- Replace clickup_approve_task/clickup_decline_task with
  clickup_reset_task/clickup_reset_all (simpler state machine)
- Add kv_delete() method to Database
- Add due_date and field filter tests to test_clickup.py
- Update test_clickup_tools.py for new reset tools

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 20:44:40 -06:00
PeninsulaInd a1fc5a7c0f Fix lint issues across link building files
- Remove f-prefix from strings with no placeholders
- Use list unpacking instead of concatenation
- Fix import sorting in test file
- Remove unused Path import
- Use contextlib.suppress instead of try/except/pass
- Wrap long lines to stay under 100 chars

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 20:13:37 -06:00