toga-ai 1.0.17 → 1.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,3 +3,5 @@
3
3
  | Doc | Summary | Files |
4
4
  |-----|---------|-------|
5
5
  | [API (api2 / TOGa API v2) Architecture](architecture.md) | `api2` is the backend powering the public **TOGa 2.0 API**. | api2/Controller/Index.php, api2/Component/Api/V2/V2.php, api2/Component/Api/Cxml/Cxml.php, api2/Component/Api/V2/Response/Response.php, api2/Config/ |
6
+ | [Error Response Envelope](features/error-response.md) | Every api2 action method returns the same three-key envelope. | api2/Controller/Index.php |
7
+ | [Health Check Endpoint](features/health-endpoint.md) | `GET /health` returns HTTP 200 with an empty JSON object (`{}`). | api2/Controller/Index.php |
@@ -2,7 +2,7 @@
2
2
  title: Error Response Envelope
3
3
  framework: "2.0"
4
4
  repo: api2
5
- project: TOGa API
5
+ project: API
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
@@ -3,4 +3,5 @@
3
3
  | Doc | Summary | Files |
4
4
  |-----|---------|-------|
5
5
  | [Worker (worker2) Architecture](architecture.md) | Worker (repo `worker2`) is an AWS Elastic Beanstalk **Worker Tier** application that processes background jobs. | worker2/Controller/Index.php, worker2/Worker/, worker2/LambdaFunctions/, _underscore/Worker.php |
6
+ | [ClickUp Project & Opportunity Multi-List Routing](features/clickup-project-routing.md) | Routes ClickUp tasks into the correct **secondary multi-list memberships** based on their custom-field values, via the `clickup` webhook. | worker2/Worker/Clickup/Project.php, worker2/Worker/Clickup.php |
6
7
  | [Creating Worker Actions](features/creating-worker-actions.md) | How to add a new callable Worker action — a PHP class whose `public static` methods are invoked as background jobs (via webhook, cron, or `_Worker::runTask()`). | worker2/Worker/, worker2/Controller/Index.php, _underscore/Worker.php |
@@ -0,0 +1,123 @@
1
+ ---
2
+ title: ClickUp Project & Opportunity Multi-List Routing
3
+ framework: "2.0"
4
+ repo: worker2
5
+ project: Worker
6
+ client: shared
7
+ type: feature
8
+ status: active
9
+ updated: 2026-06-09
10
+ owners: [jcardinal]
11
+ files:
12
+ - worker2/Worker/Clickup/Project.php
13
+ - worker2/Worker/Clickup.php
14
+ related:
15
+ - ../architecture.md
16
+ - ./creating-worker-actions.md
17
+ ---
18
+
19
+ ## Summary
20
+
21
+ Routes ClickUp tasks into the correct **secondary multi-list memberships** based on their
22
+ custom-field values, via the `clickup` webhook. Implemented in `_Worker_Clickup_Project`
23
+ (an abstract worker class) with three independent entry points covering three ClickUp spaces.
24
+
25
+ **Hybrid design (the central constraint):** ClickUp's public **v2 API forbids changing or
26
+ removing a task's home list** (`400 TASK_035 — Task home list cannot be altered`). So:
27
+ - **Home-list movement** (e.g. moving a Project Hub Epic between phase lists) is owned by
28
+ **ClickUp native Automations** configured in the ClickUp UI — *not* this worker.
29
+ - **Secondary multi-list ADD/DELETE** (where the list is never the task's home) is owned by
30
+ **this worker** via `POST /list/{id}/task/{id}` and `DELETE /list/{id}/task/{id}`.
31
+
32
+ ## Key files / entry points
33
+
34
+ - `worker2/Worker/Clickup/Project.php` — `_Worker_Clickup_Project`, all routing logic.
35
+ - `worker2/Worker/Clickup.php` — delegates from `_Worker_Clickup::Webhook()`:
36
+ - `taskStatusUpdated` (~L643): calls `handleEpicUpdate($taskId)` unconditionally.
37
+ - `taskUpdated` (~L826–828): behind a **self-trigger guard**, calls all three handlers.
38
+
39
+ Three public entry points (all `public static function …(string $taskId): void`):
40
+
41
+ | Handler | Space (guard) | Driver fields | Effect |
42
+ |---|---|---|---|
43
+ | `handleEpicUpdate` | Project Hub `90113939341` | Project Phase, Business Unit, Project Category | Places top-level Epics into the matching Lifecycle Services folder+list when BU = "Lifecycle Services"; removes from all LS lists otherwise. |
44
+ | `handleOpportunityUpdate` | Opportunity/RFP Hub `90113928591` | Business Unit, Project Category, Opportunity Stage | Adds to the LS folder's **Opportunities** list when BU = "Lifecycle Services", category maps to an LS folder, and stage is not `Closed Lost` / not Won/Signed; removes otherwise. |
45
+ | `handleAmOpportunityUpdate` | Opportunity/RFP Hub `90113928591` | Business Unit, Project Category, Opportunity Stage, Project Phase | Two passes: (1) space-level A&M Opportunities list; (2) A&M folder list driven by Opportunity Stage, with Execution→Completion driven by Project Phase = "Complete". |
46
+
47
+ ## How it works
48
+
49
+ 1. The `clickup` webhook → `_Worker_Clickup::Webhook()` → delegation in `Worker/Clickup.php`.
50
+ 2. Each handler fetches task details via `_Worker_Clickup::getTaskDetails()` (a per-invocation
51
+ `static` cache shared across all handlers in one job), then guards on **space id** and
52
+ **top-level** (`parent` empty). Epics/tasks are identified structurally — ClickUp Task
53
+ Types is not enabled, so `task_type` is unreliable.
54
+ 3. Custom fields are read by name via `extractCustomFields()` (one pass, last-non-null-wins,
55
+ trimmed) → mapped to a target list id → reconciled by `applyMultiListPlacement()`:
56
+ POST the target if absent, DELETE every other in-scope list (DELETE failures are caught
57
+ and logged, never fatal).
58
+
59
+ ### Custom field extraction — three ClickUp dropdown shapes
60
+
61
+ `extractDropdownValue()` resolves a field value to its option text across:
62
+ - **Legacy dropdown** — `value` is an integer → match `option->orderindex`.
63
+ - **New dropdown** (`new_drop_down`) — `value` is a UUID string (webhooks) or sometimes still
64
+ an int (GET responses) → match `option->id`, fall back to `orderindex`.
65
+ - **Labels / multi-select** — `value` is an array of UUIDs → first match wins.
66
+ Option text is read from `option->name`, falling back to `option->label`.
67
+
68
+ ### Self-trigger guard (prevents flapping)
69
+
70
+ Our own ADD/DELETE calls cause ClickUp to fire `taskUpdated` webhooks with
71
+ `history_items[].field = list_added` / `list_removed`. The guard in `Worker/Clickup.php`
72
+ (~L808) only re-invokes the handlers when a changed field is `custom_field` AND its name is
73
+ one of: **Project Phase, Business Unit, Project Category, Opportunity Stage**. Without it the
74
+ system flaps (POST → webhook → DELETE → webhook → …).
75
+
76
+ ## Data model
77
+
78
+ None in MySQL — all state lives in ClickUp. Each invocation is recorded as a normal
79
+ `Clickup/Webhook` row in `Core.WorkerJobs` (see worker2 architecture).
80
+
81
+ ClickUp structure encoded as constants in `Project.php`: 4 LS folders
82
+ (Onsite, Factory, Audio-Visual, Managed Services), 4 A&M folders (Retainer, Security,
83
+ AI & Data Center, Cloud), plus space-level lists. Regenerate with the `DiscoverIds()` /
84
+ `DiscoverAmIds()` diagnostic actions if the ClickUp structure changes.
85
+
86
+ ## Client variations
87
+
88
+ None — uniform across all clients (shared internal automation for Agilant's ClickUp workspace).
89
+
90
+ ## Gotchas / known issues
91
+
92
+ - **List-name slash spacing differs by space, intentionally:** LS uses
93
+ `"Client Scoping / Discovery"` (space before slash); A&M uses `"Client Scoping/ Discovery"`
94
+ (no space). These match the real ClickUp list names — do not "fix" one to match the other.
95
+ - **String-typed list IDs:** `collectCurrentMemberships()` returns IDs as **strings** and
96
+ avoids array-key dedup, because PHP coerces numeric-string keys to int and would break the
97
+ strict `in_array` comparisons against the string constants.
98
+ - **Shared `getTaskDetails()` cache is stale after writes:** all three handlers share one
99
+ cached task fetch per job. After one handler mutates memberships, the cached `locations`
100
+ are stale for the others. Currently safe only because the space guards are mutually
101
+ exclusive (a task lives in one space → exactly one handler does work). Wiring a second
102
+ handler onto `taskStatusUpdated`, or overlapping list scopes, would break this.
103
+ - **`RATE_LIMIT_DELAY_US` (120ms) is defined but unused** — pacing relies entirely on
104
+ `_Component_Api_Clickup::send()`'s internal `sleep(1)`. `handleAmOpportunityUpdate` can
105
+ issue several placement passes (multiple POST/DELETE) per event.
106
+ - **POST failures are fatal, DELETE failures are tolerated** — a failed ADD leaves the task
107
+ out of its correct list (surfaced as job failure); a failed DELETE (most likely TASK_035,
108
+ which shouldn't occur on secondary lists) is logged with `[ClickupProject]` and skipped.
109
+ - **Webhook subscriptions** must cover each space. Three exist, all → `webhook.togahub.com/clickup`:
110
+ legacy sprint space `90020178491`, Project Hub `90113939341`, Lifecycle Services
111
+ `90114156087`. Opportunity Hub coverage is registered via `RegisterOpportunityWebhook()`.
112
+
113
+ ## Diagnostics
114
+
115
+ Read-only actions (no writes), invoked via `curl -X POST https://worker.togahub.com/` with
116
+ `{"action":"Clickup/Project/<Method>","parameters":{...}}`:
117
+ `DiagnoseOpportunity`, `DiagnoseAmTask` (routing verdicts), `DiscoverIds`, `DiscoverAmIds`
118
+ (print constant declarations), `RegisterOpportunityWebhook` (one-time setup).
119
+
120
+ ## Related docs
121
+
122
+ - [Worker (worker2) Architecture](../architecture.md) — always-HTTP-200, commit-before-SQS.
123
+ - [Creating Worker Actions](./creating-worker-actions.md) — the worker-action contract.
@@ -10,8 +10,8 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
10
10
  ## 2.0 framework
11
11
 
12
12
  - **_underscore** (_Underscore) _(framework core)_ — 2 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
13
- - **worker2** (Worker) — 2 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
14
- - **api2** (API) — 1 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
13
+ - **worker2** (Worker) — 3 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
14
+ - **api2** (API) — 3 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
15
15
 
16
16
  ## Clients
17
17
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.17",
3
+ "version": "1.0.18",
4
4
  "description": "TOGA Technology Team Claude Knowledge System — shared AI coding harness with skills, knowledge base CLI, and project installer for Claude Code.",
5
5
  "keywords": [
6
6
  "claude",