toga-ai 1.0.274 → 1.0.276

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.
@@ -5,7 +5,7 @@
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
6
  | [Automated PR Merger — Concurrent Force-Push Clobber Race](features/automated-pr-merger-force-push-race.md) | The automated PR merger `_Worker_Team_GitHub::Merge` (`worker2` `Worker/Team/Github.php`) merges approved PRs to `_production` by **force-pushing from a clone t | Worker/Team/Github.php |
7
7
  | [ClickUp Connectivity Watchdog](features/clickup-connectivity-watchdog.md) | A cron watchdog that emails when the ClickUp integration looks disconnected during business hours. | worker2/Worker/Clickup/Health.php, worker2/Database/ClickupHealthWatchdog.sql |
8
- | [ClickUp Design Sprint Automation (Final Design Outcome)](features/clickup-design-sprint-automation.md) | `_Worker_Clickup_Design` is meant to drive the design-sprint workflow in ClickUp via the API, replacing a set of native ClickUp automations. | worker2/Worker/Clickup/Design.php, worker2/Worker/Clickup.php |
8
+ | [ClickUp Design Sprint Automation (Final Design Outcome)](features/clickup-design-sprint-automation.md) | `_Worker_Clickup_Design` is meant to drive the design-sprint workflow in ClickUp via the API, replacing a set of native ClickUp automations. | worker2/Worker/Clickup/Design.php, worker2/Worker/Clickup.php, worker2/Controller/ClickupDesignTest.php, _underscore/Component/Api/Clickup/Clickup.php |
9
9
  | [ClickUp GitHub-tab Auto-linking & Ticket-id Branch Naming](features/clickup-github-autolink.md) | How ClickUp surfaces branches/PRs/commits in a ticket's **GitHub tab**, and the branch / PR-title naming convention that triggers it. | |
10
10
  | [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 |
11
11
  | [ClickUp Rich-Text Custom Fields via Quill Delta (API)](features/clickup-richtext-api.md) | ClickUp custom text fields (type `text` and long-text) support rich formatting only through a **Quill Delta** written to the undocumented `value_richtext` key o | test/@dave/clickup_md2delta.js, .claude/skills/plan-ticket/scripts/clickup.js |
@@ -6,11 +6,13 @@ project: Worker
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-07-06
9
+ updated: 2026-07-07
10
10
  owners: ["ajean"]
11
11
  files:
12
12
  - worker2/Worker/Clickup/Design.php
13
13
  - worker2/Worker/Clickup.php
14
+ - worker2/Controller/ClickupDesignTest.php
15
+ - _underscore/Component/Api/Clickup/Clickup.php
14
16
  related:
15
17
  - ./clickup-work-type-automation.md
16
18
  - ./team-sprint-management.md
@@ -22,87 +24,170 @@ replacing a set of native ClickUp automations. When a task's **Final Design Outc
22
24
  drop-down is set to an actionable value, the worker either spawns an iteration task or
23
25
  completes the task, and a daily cron reminds the Design Approver of pending reviews.
24
26
 
25
- **Critical current state (as of 2026-07-06):** the worker's
26
- `handleFinalDesignOutcome` has been **silently no-opping in production** — the native
27
- ClickUp automations it was supposed to replace were never disabled and had been doing the
28
- real work all along, masking the failure. Do not assume the API path is live. See Gotchas
29
- and the open action item below before touching this code.
27
+ **Current state (as of 2026-07-07):** the silent no-op fix is **shipped and live** —
28
+ worker2 PR #98 was merged to `_production` (commit `7a36564`); the type-tolerant resolver
29
+ and the ClickBot email guard are now in production. On top of that, the review workflow was
30
+ extended: entering the **"iterated"** status now auto-drives the review state and iteration
31
+ count, `handleFinalDesignOutcome` now handles **all five** FDO options explicitly, and the
32
+ spawned duplicate carries over more fields. This new work is on branch
33
+ `feature/design-sprint-iterated-automation` in **both** worker2 and _underscore (committed +
34
+ pushed; PRs to be opened manually — must deploy together, see Data model). The native ClickUp
35
+ automations that were masking the no-op must stay disabled — see Gotchas.
30
36
 
31
37
  ## Key files / entry points
32
38
  - `Worker/Clickup/Design.php` → `_Worker_Clickup_Design` — the design-sprint engine.
33
- - `handleFinalDesignOutcome(...)` — the entry that decides spawn vs. complete (currently a
34
- silent no-op in prod).
39
+ - `handleFinalDesignOutcome(...)` — the entry that decides spawn vs. complete; now branches
40
+ on **all five** FDO options explicitly (no silent fall-through — see How it works).
41
+ - `handleTaskEnteredIterated()` — **new**; fired when a task ENTERS "iterated" status. Sets
42
+ FDO = "In Review" then increments the Iteration custom field by +1 (idempotent — see
43
+ Gotchas). Folder-gated via `isInDesignSprintsFolder()`.
44
+ - `isInDesignSprintsFolder(...)` — **new** private folder-gate helper (reused by the
45
+ iterated-entry trigger).
35
46
  - `resolveFinalDesignOutcomeOptionId(...)` (Design.php:298-317) — maps the drop-down value
36
- to an option ID.
37
- - `spawnIterationTask(...)` / `carryDesignApprover(...)` — duplicate the task and copy the
38
- Design Approver.
39
- - `completeTask(...)` — closes an approved task.
47
+ to an option ID; now **string-compares** value/orderindex/id (see Gotchas).
48
+ - `spawnIterationTask(...)` / `buildIterationCreatePayload(...)` / `carryDesignApprover(...)` /
49
+ `carryComments(...)` (**new**) — duplicate the task and copy description, Design Approver,
50
+ and now all user comments; the duplicate no longer bumps Iteration (moved to the
51
+ iterated-entry trigger) and is set to FDO = "In Progress" post-create.
52
+ - `completeTask(...)` — closes an approved / cancelled task (also used on iterated → complete).
40
53
  - `SendReviewReminders(...)` — daily reminder cron action.
41
54
  - `TRUE_DESIGN_SPRINTS_FOLDER_ID` constant (Design.php:27,50).
42
- - `Worker/Clickup.php` (dispatcher ~lines 822-875) — routes ClickUp webhooks; the Design
43
- Approver / status guard lives at 822-833.
55
+ - `Worker/Clickup.php` `Webhook` — the `switch ($historyItem->after->status)` block (see
56
+ Gotchas re: after vs. before) now has a `case _Worker_Clickup_Design::STATUS_ITERATED`
57
+ dispatching to `handleTaskEnteredIterated()`.
58
+ - `Worker/Clickup.php` — routes ClickUp webhooks.
59
+ - `shouldEmailDesignApprover($payload, $taskDetails)` — the single predicate gating the
60
+ "Action Needed: Design Review" email (requires history_items[0] is a Design Approver
61
+ custom_field change, task status still `iterated`, and user-driven: `user.id != -1`).
62
+ - `taskUpdated` path (~822) and `taskStatusUpdated` path (~230) both gate the email on this
63
+ predicate.
64
+ - `Controller/ClickupDesignTest.php` — regression tests reached via the
65
+ `?controller=ClickupDesignTest` route (worker2 has **no PHPUnit harness**; mirrors the
66
+ `CompassPeopleTest` convention). Uses `ReflectionMethod` to reach the private methods.
44
67
 
45
68
  ## How it works
46
69
  1. A ClickUp webhook reaches `_Worker_Clickup`; the dispatcher (Clickup.php ~822-875) routes
47
70
  design-folder events into `_Worker_Clickup_Design`.
48
- 2. The worker acts on **only two** Final Design Outcome values:
49
- - **"Iterated – New Task"** (option `3f835ac8…`) → `spawnIterationTask` — duplicates the
50
- ticket into a new iteration task and carries over the Design Approver.
51
- - **"Approved"** (option `2d17868b…`) → `completeTask`.
52
- 3. The full Final Design Outcome option set is: In Progress, In Review, **Iterated – New
53
- Task**, **Approved**, Cancelled. Only the two bolded values are actionable.
54
- 4. A separate ClickUp automation (`a64c863a`) sets the outcome to **"In Review"** when a
55
- task's *status* changes to Iterated. "In Review" is **not** actionable — so
56
- `status → Iterated` alone never triggers the API. The worker acts only when the Design
57
- Approver **manually** selects one of the two actionable outcome values.
71
+ 2. `handleFinalDesignOutcome` now branches on **all five** FDO options explicitly:
72
+ - **"Iterated – New Task"** → `spawnIterationTask` (duplicate) **then** `completeTask` on
73
+ the source.
74
+ - **"Approved"** → `completeTask`.
75
+ - **"Cancelled"** → `completeTask` (terminal; newly handled — Cancelled now closes the task).
76
+ - **"In Progress" / "In Review"** → explicit no-op.
77
+ - unrecognized → logged (no more silent implicit-else fall-through).
78
+ 3. **Iterated-entry trigger (new):** when a True Design Sprints task ENTERS the "iterated"
79
+ status, `handleTaskEnteredIterated()` sets FDO = **"In Review"** and increments the
80
+ **Iteration** custom field by +1. This is what now drives the review state + round count
81
+ on review submission; previously nothing fired on entering "iterated". Folder-gated.
82
+ 4. **Duplicate field carry-over:** the spawned iteration task copies description, Design
83
+ Approver, and **all user comments** (`carryComments()` — GET `/task/{id}/comment`,
84
+ re-posted oldest-first with `[Original by <user> on <date>]` attribution, non-fatal). It
85
+ copies **Iteration unchanged** (omitted if the source has none — the +1 now lives only on
86
+ the iterated-entry trigger, so a review round is never double-counted) and is set to
87
+ FDO = **"In Progress"** post-create (POST `/task/{id}/field/{id}` drop_down pattern).
58
88
  5. Daily reminder cron `Core.CronJobs` id 21 ("Design Review Reminders", `0 9 * * 1-5`,
59
89
  action `Clickup/Design/SendReviewReminders`) nudges the approver about pending reviews.
60
90
 
61
91
  ## Data model
62
92
  Configuration lives as ClickUp identifiers (folder IDs, custom-field IDs, drop-down option
63
93
  UUIDs), not in the DB. The custom-field / option-ID constants are defined in `_underscore`
64
- (`_production`) — confirmed present (worker2 PR #80, _underscore PR #624); the
65
- undefined-constant theory was ruled out.
94
+ (`_Component_Api_Clickup`, `_underscore/Component/Api/Clickup/Clickup.php`) — confirmed
95
+ present (worker2 PR #80, _underscore PR #624).
96
+
97
+ **New FDO option constants (this session):** `FINAL_DESIGN_OUTCOME__IN_PROGRESS`,
98
+ `FINAL_DESIGN_OUTCOME__IN_REVIEW`, and `FINAL_DESIGN_OUTCOME__CANCELLED` were added to
99
+ `_Component_Api_Clickup` (previously only `..._ITERATED_NEW_TASK` and `..._APPROVED` existed).
100
+ **Cross-repo coupling:** worker2 references these at runtime, so `_underscore` must deploy
101
+ **together with** the worker2 change — deploying worker2 alone against an old _underscore
102
+ throws undefined-constant. Both changes are on branch
103
+ `feature/design-sprint-iterated-automation`.
66
104
 
67
105
  ## Client variations
68
106
  None — internal TOGA Technology (`true`) design-sprint tooling.
69
107
 
70
108
  ## Gotchas / known issues
71
- - **Silent no-op masked by native automations (root cause of TRUE-79685).** The worker's
72
- docstring claims it replaced the native automations, but they were never disabled. Native
73
- automation `c25fd345` ("When Final Design Outcome is Iterated, duplicate the ticket and
74
- close the original") was doing the duplicate+close and **hiding** that the worker never
75
- spawned. Only after disabling **all** native automations did setting Final Design Outcome →
76
- "Iterated – New Task" on TRUE-80046 (prod job 295376, isSuccess=1) produce **no** spawn,
77
- exposing the failure.
78
- - **OPEN — two prime suspects for the silent no-op** (need a combined code + live-API session
79
- to confirm/fix):
80
- 1. **Folder-gate ID mismatch.** `TRUE_DESIGN_SPRINTS_FOLDER_ID = '90115939842'`
81
- (Design.php:27,50) vs. the file-header comment naming `90020178491`. `90115939842` is a
82
- valid folder (the daily reminder cron GETs `/folder/90115939842/list` successfully) but
83
- may not be the folder the automation-test tasks live in — in which case the folder gate
84
- returns early on every task. **Verify:** compare `getTaskDetails` `folder.id` for
85
- TRUE-80046 against the constant.
86
- 2. **Strict `===` mismatch in `resolveFinalDesignOutcomeOptionId`** (Design.php:298-317). If
87
- `getTaskDetails` returns the `drop_down` value in a shape (string `orderindex`, or an
88
- option-id) that matches neither `option->id === value` nor `option->orderindex === value`,
89
- it returns `null` → silent no-op. Fix should be an explicit **cast/validate at the
90
- ClickUp-payload boundary**, not merely switching `===` to `==`.
91
- - **Latent bug — spurious "Design Review" email on freshly spawned tasks.** Spawned iteration
92
- tasks transiently pass through "iterated" status during creation (a ClickUp list
93
- automation), and `spawnIterationTask → carryDesignApprover` copies the Design Approver onto
94
- the new task. If that copy lands while the new task is momentarily "iterated", the dispatcher
95
- guard (Clickup.php:822-833: `custom_field.name == 'Design Approver' && status == 'iterated'`)
96
- fires a **spurious** "Action Needed: Design Review" email. Candidate fix: gate
97
- `sendDesignApproverEmail` on the change being user-driven (history `user.id != ClickBot -1`).
98
- - **Native automations must be disabled once the worker is fixed.** The worker was meant to
99
- replace: `c25fd345`, `272f743e` (Automation #39, native "email Aaron when iterated"),
100
- `a64c863a`, `7739cc41`, `08570f31`, `70b09962`. Keep sprint-readiness automations only:
101
- `10df1786`, `f6214678`. **INTERIM:** if the design team needs the workflow before the worker
102
- is fixed, re-enable **only** `c25fd345` (temporary — this reintroduces the Iteration-reset
103
- and spurious-email issues).
109
+ - **The ClickUp API auth is a single hard-coded personal token — a dead token silently kills
110
+ ALL ClickUp automation.** The integration authenticates via one committed class constant,
111
+ `_Component_Api_Clickup::ACCESS_TOKEN` (in `_underscore/Component/Api/Clickup/Clickup.php`),
112
+ identical on `_production` and feature branches. This is a known secret-in-committed-code
113
+ risk. When the token is revoked/expired, `GET /user` and `GET /task` return **HTTP 401
114
+ "Oauth token not found" / error `OAUTH_019`**; `getTaskDetails` then throws and **every**
115
+ Clickup/Webhook worker job fails — presenting as "the automation silently does nothing."
116
+ This actually happened mid-session on 2026-07-07 (~09:49). **Fix:** regenerate the token in
117
+ ClickUp, update the `ACCESS_TOKEN` constant, and deploy `_underscore`. (Do not record the
118
+ token value anywhere.)
119
+ - **Iterated-entry trigger is idempotent — order matters.** `handleTaskEnteredIterated()`
120
+ writes **FDO = In Review FIRST** as the marker, THEN increments Iteration. A re-delivered /
121
+ retried webhook sees FDO already "In Review" and skips, so **Iteration can never
122
+ compound-double-increment**. Worst case is a single missed +1, visible as a failed
123
+ WorkerJob. If you reorder these two writes you reintroduce the double-count risk.
124
+ - **`taskStatusUpdated` dispatch structure (route-safety facts).** The
125
+ `switch ($historyItem->after->status)` block sits **OUTSIDE** the `if (!is_null($sprintNumber))`
126
+ guard, so entering-status handlers fire even for design tasks with no "sprint N" tag —
127
+ **each case must self-guard on `sprintNumber`** (the individual cases do). No webhook loop:
128
+ custom-field writes emit `taskUpdated` (not `taskStatusUpdated`) and land on the FDO no-op
129
+ cases; the Iteration write's custom_field is named "Iteration" so it never re-enters the FDO
130
+ dispatch gate.
131
+ - **Never use strict `===` when reading a ClickUp `drop_down` value/orderindex/id (ROOT CAUSE
132
+ of the silent no-op).** ClickUp returns `value`/`orderindex` with **inconsistent int-vs-string
133
+ typing** (`"2" !== 2`), so `resolveFinalDesignOutcomeOptionId` matched nothing, returned
134
+ `null`, and `handleFinalDesignOutcome` hit its early return (job still `isSuccess=1`). Fixed
135
+ by comparing **as strings**: `(string)$option->orderindex === (string)$field->value` (and the
136
+ same for `$option->id`). Note that **every other** drop_down reader in `Worker/Clickup.php`
137
+ relies on PHP **array-key coercion** (`$optionsByIndex[$option->orderindex]` then indexed by
138
+ `$customField->value`) precisely because of this typing inconsistency — strict `===` anywhere
139
+ reading a ClickUp drop_down value is a bug. The native automation `c25fd345` had been masking
140
+ this no-op in prod.
141
+ - **`taskStatusUpdated` switches on `before->status`, NOT `after->status`** (Clickup.php ~71).
142
+ A `case '<status>'` in that switch therefore fires when a task **LEAVES** that status — the
143
+ in-code comment claiming "just changed TO iterated" is misleading. This is the trap behind the
144
+ second spurious-email vector: the `iterated` case fired on `completeTask()`'s
145
+ `iterated → complete` transition during `spawnIterationTask`. Anyone adding status-transition
146
+ logic must account for this direction. The "entering iterated" trigger added this session is
147
+ correctly placed under `switch ($historyItem->after->status)` (`case ...::STATUS_ITERATED` →
148
+ `handleTaskEnteredIterated()`) — distinct from the `before->status` switch.
149
+ - **Spurious "Design Review" email — fixed on a single predicate.** Both vectors (the
150
+ `taskUpdated` Design-Approver-field path ~822 and the `taskStatusUpdated` `iterated` path
151
+ ~230) now gate on `shouldEmailDesignApprover($payload, $taskDetails)`, which requires:
152
+ `history_items[0]` is a Design Approver custom_field change, task status still `iterated`, and
153
+ the change is **user-driven** (`user.id != -1`; ClickBot/automation writes carry
154
+ `user.id == -1`). The dead `$isDesignApproverSet` block was removed. Consequence: a pure status
155
+ change **into** iterated (approver already set, no field change) no longer emails from the
156
+ status path — the legitimate review email flows from the `taskUpdated` field handler.
157
+ - **Native automations must stay disabled once the worker is verified.** The worker replaces:
158
+ `c25fd345`, `272f743e`, `a64c863a`, `7739cc41`, `08570f31`, `70b09962`. Keep sprint-readiness
159
+ automations only: `10df1786`, `f6214678`. Re-enabling any of the replaced set reintroduces the
160
+ duplicate+close and spurious-email behavior.
161
+ - **Not the bug (ruled out):** `_underscore` PR #624 field/option constants were confirmed
162
+ present and correct — no `_underscore` change was needed. The earlier folder-gate ID-mismatch
163
+ suspicion was also not the cause.
104
164
 
105
165
  ## Change history
166
+ - 2026-07-07 (later) — **Extended the review workflow (branch
167
+ `feature/design-sprint-iterated-automation`, worker2 + _underscore; PRs pending manual open).**
168
+ Added the iterated-entry trigger `handleTaskEnteredIterated()` (FDO → In Review, then
169
+ Iteration +1; idempotent, folder-gated via new `isInDesignSprintsFolder()`), wired via a new
170
+ `case ...::STATUS_ITERATED` under `switch ($historyItem->after->status)` in `Clickup::Webhook`.
171
+ Moved the Iteration +1 OFF the spawn path (`buildIterationCreatePayload`) so the duplicate
172
+ copies Iteration unchanged and review rounds are not double-counted; the duplicate now also
173
+ sets FDO = In Progress post-create and carries over all user comments (`carryComments()`) plus
174
+ the existing description + Design Approver. `handleFinalDesignOutcome` now handles all five FDO
175
+ options explicitly (Cancelled now closes the task; In Progress/In Review are explicit no-ops;
176
+ unrecognized is logged). Added FDO option constants `__IN_PROGRESS`, `__IN_REVIEW`,
177
+ `__CANCELLED` to `_Component_Api_Clickup` in _underscore (cross-repo coupling — deploy
178
+ together). **Operational:** discovered the ClickUp `ACCESS_TOKEN` is a single committed
179
+ constant and was revoked mid-session (HTTP 401 / `OAUTH_019`), taking all ClickUp automation
180
+ down — documented the symptom and fix (regenerate + redeploy _underscore) as a gotcha. PR #98
181
+ is now MERGED to `_production` (commit `7a36564`) and live. (ajean)
182
+ - 2026-07-07 — Fixed and shipped the silent no-op (worker2 PR #98, branch
183
+ `fix/clickup-design-outcome-resolver`). Confirmed root cause: strict `===` on ClickUp
184
+ `drop_down` value/orderindex/id in `resolveFinalDesignOutcomeOptionId` (inconsistent int/string
185
+ typing) — now string-compared. Fixed the spurious "Design Review" email (both the `taskUpdated`
186
+ and `taskStatusUpdated` vectors) behind a single `shouldEmailDesignApprover` predicate that
187
+ requires a user-driven Design-Approver field change on an `iterated` task. Documented that
188
+ `taskStatusUpdated` switches on `before->status` (fires on LEAVING a status). Added regression
189
+ tests in `Controller/ClickupDesignTest.php` (run via `?controller=ClickupDesignTest`). Ruled
190
+ out `_underscore` PR #624 constants as the cause. (ajean)
106
191
  - 2026-07-06 — Diagnosed the "design-sprint API automation not running" report (TRUE-79685;
107
192
  tests TRUE-80026/80046): `handleFinalDesignOutcome` is silently no-opping in prod, masked by
108
193
  never-disabled native automation `c25fd345`. Narrowed to two suspects (folder-gate ID
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.274",
3
+ "version": "1.0.276",
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",