toga-ai 1.0.558 → 1.0.560

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
  "hooks": [
6
6
  {
7
7
  "type": "command",
8
- "command": "node \".claude/hooks/toga/kickoff-gate.js\"",
8
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/toga/kickoff-gate.js\"",
9
9
  "timeout": 3000
10
10
  }
11
11
  ]
@@ -17,7 +17,7 @@
17
17
  "hooks": [
18
18
  {
19
19
  "type": "command",
20
- "command": "node \".claude/hooks/toga/kickoff-gate.js\"",
20
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/toga/kickoff-gate.js\"",
21
21
  "timeout": 3000
22
22
  }
23
23
  ]
@@ -27,7 +27,7 @@
27
27
  "hooks": [
28
28
  {
29
29
  "type": "command",
30
- "command": "node \".claude/hooks/toga/block-destructive.js\"",
30
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/toga/block-destructive.js\"",
31
31
  "timeout": 3000
32
32
  }
33
33
  ]
@@ -37,7 +37,7 @@
37
37
  "hooks": [
38
38
  {
39
39
  "type": "command",
40
- "command": "node \".claude/hooks/toga/guard-index.js\"",
40
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/toga/guard-index.js\"",
41
41
  "timeout": 3000
42
42
  }
43
43
  ]
@@ -47,7 +47,7 @@
47
47
  "hooks": [
48
48
  {
49
49
  "type": "command",
50
- "command": "node \".claude/hooks/toga/dbchanges2-cluster-isolation.js\"",
50
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/toga/dbchanges2-cluster-isolation.js\"",
51
51
  "timeout": 3000
52
52
  }
53
53
  ]
@@ -57,7 +57,7 @@
57
57
  "hooks": [
58
58
  {
59
59
  "type": "command",
60
- "command": "node \".claude/hooks/toga/dbchanges2-record-ids.js\"",
60
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/toga/dbchanges2-record-ids.js\"",
61
61
  "timeout": 3000
62
62
  }
63
63
  ]
@@ -69,7 +69,7 @@
69
69
  "hooks": [
70
70
  {
71
71
  "type": "command",
72
- "command": "node \".claude/hooks/toga/post-edit-validate.js\"",
72
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/toga/post-edit-validate.js\"",
73
73
  "timeout": 15000
74
74
  }
75
75
  ]
@@ -79,7 +79,7 @@
79
79
  "hooks": [
80
80
  {
81
81
  "type": "command",
82
- "command": "node \".claude/hooks/toga/remind-php-review.js\"",
82
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/toga/remind-php-review.js\"",
83
83
  "timeout": 3000
84
84
  }
85
85
  ]
@@ -89,7 +89,7 @@
89
89
  "hooks": [
90
90
  {
91
91
  "type": "command",
92
- "command": "node \".claude/hooks/toga/remind-sql-review.js\"",
92
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/toga/remind-sql-review.js\"",
93
93
  "timeout": 3000
94
94
  }
95
95
  ]
@@ -99,7 +99,7 @@
99
99
  "hooks": [
100
100
  {
101
101
  "type": "command",
102
- "command": "node \".claude/hooks/toga/scope-counter.js\"",
102
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/toga/scope-counter.js\"",
103
103
  "timeout": 3000
104
104
  }
105
105
  ]
@@ -110,7 +110,7 @@
110
110
  "hooks": [
111
111
  {
112
112
  "type": "command",
113
- "command": "node \".claude/hooks/toga/session-start.js\"",
113
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/toga/session-start.js\"",
114
114
  "timeout": 5000
115
115
  }
116
116
  ]
@@ -121,7 +121,7 @@
121
121
  "hooks": [
122
122
  {
123
123
  "type": "command",
124
- "command": "node \".claude/hooks/toga/session-end.js\"",
124
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/toga/session-end.js\"",
125
125
  "timeout": 5000
126
126
  }
127
127
  ]
@@ -130,7 +130,7 @@
130
130
  "hooks": [
131
131
  {
132
132
  "type": "command",
133
- "command": "node \".claude/hooks/toga/evaluate-session.js\"",
133
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/toga/evaluate-session.js\"",
134
134
  "timeout": 5000
135
135
  }
136
136
  ]
@@ -139,7 +139,7 @@
139
139
  "hooks": [
140
140
  {
141
141
  "type": "command",
142
- "command": "node \".claude/hooks/toga/remind-php-lint.js\"",
142
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/toga/remind-php-lint.js\"",
143
143
  "timeout": 3000
144
144
  }
145
145
  ]
@@ -32,7 +32,7 @@
32
32
  | [Background Email-Template Worker (_Worker_Notification_EmailTemplate)](features/notification-email-template.md) | `_Worker_Notification_EmailTemplate::Send(...)` dispatches a **stored, client-defined `EmailTemplates` row off-thread** as a background WorkerJob. | worker2/Worker/Notification/EmailTemplate.php, worker2/Worker/Client/True.php, _underscore/Model/Client/EmailTemplate.php |
33
33
  | [DB-Driven Notification (Internal) Email](features/notification-email.md) | Internal/notification emails (merge-conflict alerts, ops notices — anything system-generated, not client-facing transactional mail) are sent through one worker | worker2/Worker/Notification/Email.php, _underscore/Model/Client/EmailTemplate.php, dbchanges2/Client/2026-06-23a - EmailTemplateWrapper.sql, dbchanges2/Client_True/2026-06-23a - EmailTemplateWrapper.sql |
34
34
  | [NYCHH Asset-Tag Backfill (worker2)](features/nychh-asset-tag-backfill.md) | Keeps NYC Health & Hospitals (`Client_Nychh`) unit asset tags and MAC addresses synced from NetSuite. | worker2/Worker/Client/Nychh.php, worker2/Worker/Client/Nychh/AssetTagBackfill.php, worker/crons/toga2/netsuite/verify_fulfillment_asset_tag_sync_nychh.php, worker/crons/toga2/netsuite/backfill_all_asset_tags_from_netsuite_nychh.php |
35
- | [OneUptime Incident → ClickUp Task Sync (Monitor/Oneuptime/SyncIncidents)](features/oneuptime-incident-clickup-sync.md) | An internal/shared TOGA ops feature: worker2 polls the OneUptime API every 15 minutes for **currently-open** incidents and ensures a ClickUp task exists for eac | worker2/Component/Api/Oneuptime/Oneuptime.php, worker2/Worker/Monitor/Oneuptime.php, worker2/Config/production.ini, dbchanges2/Team/2026-08-10a - OneUptime Incident ClickUp Tasks.sql, dbchanges2/Core/2026-08-10b - OneUptime Incident Sync CronJob.sql |
35
+ | [OneUptime Incident → ClickUp Task Sync (Monitor/Oneuptime/SyncIncidents)](features/oneuptime-incident-clickup-sync.md) | An internal/shared TOGA ops feature: worker2 polls the OneUptime API every 15 minutes for **currently-open** incidents and ensures a ClickUp task exists for eac | worker2/Component/Api/Oneuptime/Oneuptime.php, worker2/Worker/Monitor/Oneuptime.php, worker2/Config/production.ini, dbchanges2/Team/2026-08-10a - OneUptime Incident ClickUp Tasks.sql, dbchanges2/Core/2026-08-10b - OneUptime Incident Sync CronJob.sql, dbchanges2/Team/2026-08-12a - OneUptime Incident Tasks Closed Marker.sql |
36
36
  | [OneUptime push-metric monitors for 2.0 workers](features/oneuptime-worker2-monitoring.md) | A second, **OneUptime-reporting** monitoring pattern for the 2.0 worker2 tier, ported from the 1.0 `App_SystemMonitor_Compass` monitors. | worker2/Worker/Monitor/Compass.php, worker2/Worker/Client/Compass.php, worker2/composer.json, _underscore/Cloud.php, worker2/Worker/Monitor/Operations.php |
37
37
  | [Platform Cache Cleanup (_Worker_Platform_Cache — Clean + Truncate)](features/platform-cache-cleanup.md) | `_Worker_Platform_Cache` owns maintenance of the shared **Cache** cluster that backs api2's [multi-client data retrieval](../../api2/features/cross-client-data- | worker2/Worker/Platform/Cache.php, worker2/Controller/Index.php, worker2/_.php, dbchanges2/Core/2026-07-27a - PlatformCacheCleanCron.sql |
38
38
  | [Startech Webhook Handler (worker2)](features/startech-webhook-handler.md) | Receives inbound webhook events from Startech (Easeedesk) and creates or updates the corresponding ticket in TOGA 2.0. | worker2/Worker/Startech.php |
@@ -6,7 +6,7 @@ project: Worker
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-08-10
9
+ updated: 2026-08-12
10
10
  owners: ["jcardinal"]
11
11
  files:
12
12
  - worker2/Component/Api/Oneuptime/Oneuptime.php
@@ -14,6 +14,7 @@ files:
14
14
  - worker2/Config/production.ini
15
15
  - dbchanges2/Team/2026-08-10a - OneUptime Incident ClickUp Tasks.sql
16
16
  - dbchanges2/Core/2026-08-10b - OneUptime Incident Sync CronJob.sql
17
+ - dbchanges2/Team/2026-08-12a - OneUptime Incident Tasks Closed Marker.sql
17
18
  related:
18
19
  - ./oneuptime-worker2-monitoring.md
19
20
  - ./error-escalation-cron.md
@@ -41,7 +42,7 @@ reporter, smart monitor"; this feature adds the reader half.
41
42
  open incidents (filters out resolved **in PHP**).
42
43
  - `worker2/Worker/Monitor/Oneuptime.php` — `_Worker_Monitor_Oneuptime::SyncIncidents(): string`,
43
44
  the cron action. Returns a structured summary string on every path
44
- (`incidentsPolled` / `created` / `skipped` / `failed`).
45
+ (`incidentsPolled` / `created` / `skipped` / `failed`, plus `closed` / `closeFailed`).
45
46
 
46
47
  ## How it works
47
48
 
@@ -68,6 +69,36 @@ worker2 way to prevent duplicate ClickUp tasks under overlapping cron runs — p
68
69
  from `_Worker_Infrastructure_Errors::createClickupTask`. `INSERT … WHERE NOT EXISTS` is
69
70
  **insufficient** under REPEATABLE READ; the UNIQUE key is the concurrency arbiter.
70
71
 
72
+ ### Close pass (auto-close on incident resolution)
73
+
74
+ The sync was originally **create-only**; a ClickUp task opened for an incident was never
75
+ closed once the incident resolved, so a recurrence (a *new* OneUptime incident with the same
76
+ monitor title) opened a second task that **looked like a duplicate** but was not (see the
77
+ gotcha below). `SyncIncidents()` now runs a **close pass first** each cycle, independent of how
78
+ many incidents are currently open, via `closeResolvedIncidents()`:
79
+
80
+ - SELECT ledger rows `WHERE clickupIdentifier IS NOT NULL AND dtClosed IS NULL`.
81
+ - For each, call `_Component_Api_Oneuptime::getIncidentResolvedState(string $incidentId): ?bool`
82
+ — `true` = resolved, `false` = confirmed still open, `null` = unknown. **Only a definite
83
+ `true` closes a task**; `null` means "leave alone" so an ambiguous read never closes a live
84
+ incident's ticket. This lookup uses a new per-incident route (`ROUTE__INCIDENT_ITEM`,
85
+ `/api/incident/{id}/get-item`, still a PLACEHOLDER) because resolved incidents no longer
86
+ appear in `getActiveIncidents()` and must be re-checked individually. A 2xx response missing
87
+ `currentIncidentState.isResolvedState` logs a warning (incident id only, no body/PII) so a
88
+ wrong placeholder shape is detectable rather than silently returning `null` forever.
89
+ - To close, PUT `/task/{id}` with the list's **closed-type status NAME** (ClickUp has no
90
+ generic close verb — see gotcha), resolved per-list via `GET /list/{id}` `statuses[]` where
91
+ `type == 'closed'`, cached per run in `$closedStatusByList`. Then stamp `dtClosed = NOW()`.
92
+ - `dtClosed` is stamped **only after a successful close**, so a failure retries next run.
93
+ Per-incident failures are caught (`Throwable`) + `error_log` + counted, not fatal; whole-run
94
+ failures (e.g. no sprint list) still re-throw for queue retry.
95
+ - All new ClickUp calls in the close path are throttled with
96
+ `usleep(_Worker_Clickup_Project::RATE_LIMIT_DELAY_US)` (120ms = 500/min), matching
97
+ `_Worker_Infrastructure_Errors`.
98
+
99
+ Helpers added: `closeResolvedIncidents()`, `closeClickupTask()`, `resolveClosedStatusForList()`,
100
+ `markClosed()`, and the `$closedStatusByList` per-run cache.
101
+
71
102
  ### Target list = current sprint list
72
103
 
73
104
  The ClickUp task lands in the **current sprint** list, resolved by `GET /folder/{id}` and
@@ -80,8 +111,11 @@ id is read from `[clickup] sprint_folder_id` with a constant fallback (`90020432
80
111
 
81
112
  `OneUptimeIncidentTasks` (CREATE on the **Team** cluster — chosen over Logs;
82
113
  `dbchanges2/Team/2026-08-10a`): `id`, `uuid char(36)`, `dtCreated`,
83
- `oneuptimeIncidentId varchar(64)`, `clickupIdentifier varchar(64) NULL`. The **UNIQUE KEY on
114
+ `oneuptimeIncidentId varchar(64)`, `clickupIdentifier varchar(64) NULL`, `dtClosed DATETIME NULL` (added
115
+ `dbchanges2/Team/2026-08-12a`, `AFTER clickupIdentifier`). The **UNIQUE KEY on
84
116
  `oneuptimeIncidentId`** is the dedup contract, same rationale as `IssueClickupTasks`.
117
+ `dtClosed` marks a ledger row whose ClickUp task has been closed following incident
118
+ resolution (NULL = still open / not yet closed).
85
119
 
86
120
  ## Provisioning / deploy
87
121
 
@@ -104,13 +138,30 @@ id is read from `[clickup] sprint_folder_id` with a constant fallback (`90020432
104
138
 
105
139
  ## Gotchas / known issues
106
140
 
107
- - **Incident-list route/fields are PLACEHOLDER** (`/api/incident/get-list`) pending
108
- confirmation against the live OneUptime instance. Do not enable the cron (`isActive = 1`)
109
- until the route and returned fields are verified.
141
+ - **Same-title ClickUp tickets from OneUptime are expected on incident recurrence.** Dedup is
142
+ **per-incident-id, not per-title**: the `oneuptimeIncidentId` UNIQUE key + claim-first insert
143
+ only prevents duplicates for the *same* incident id. Two tasks with the same monitor title
144
+ minutes apart are two *different* OneUptime incidents, not a dedup failure. The symptom that
145
+ looked like duplication was actually the missing auto-close: incident #1 resolved but its
146
+ task never closed, then a fresh incident opened a second task. Auto-close (above) closes
147
+ #1 on resolution so a recurrence (#2) is the only open ticket.
148
+ - **ClickUp has no generic "close" verb.** A task is closed by PUTting the exact NAME of the
149
+ status whose `type == 'closed'`, which is **list-specific** — resolved per-list from
150
+ `GET /list/{id}` `statuses[]` and cached per run.
151
+ - Throttle every ClickUp call in the close path with `usleep(RATE_LIMIT_DELAY_US)` (120ms) or
152
+ rate-limit failures degrade into `closeFailed` counts.
153
+ - **Both OneUptime routes remain PLACEHOLDERS** — the incident-list route
154
+ (`/api/incident/get-list`) *and* the new per-incident route (`/api/incident/{id}/get-item`,
155
+ `ROUTE__INCIDENT_ITEM`) are unverified against the live OneUptime API. Auto-close **fails
156
+ safe** (only a definite resolved `true` closes anything; `null` leaves the task alone) but
157
+ won't fire until the route/field shape is confirmed. Do not enable the cron (`isActive = 1`)
158
+ until the routes and returned fields are verified.
110
159
  - **OneUptime cannot filter open-only server-side here** — resolution filtering happens in
111
160
  PHP, so the returned field set must actually carry the resolved/open state.
112
161
  - Ship the worker2 code deploy and both dbchanges2 migrations **together**; the cron row is
113
162
  inert until the action is deployed and the ledger table exists.
163
+ - **Deploy order for the auto-close change:** run `dbchanges2/Team/2026-08-12a` (the `dtClosed`
164
+ column) **before** the worker2 code deploys — the new close-pass queries reference `dtClosed`.
114
165
 
115
166
  ## Change history
116
167
  - 2026-08-10 — Built: first OneUptime API *reader* (`_Component_Api_Oneuptime`) + the
@@ -118,5 +169,13 @@ id is read from `[clickup] sprint_folder_id` with a constant fallback (`90020432
118
169
  ClickUp task in the current sprint list; new `OneUptimeIncidentTasks` ledger (Team cluster,
119
170
  UNIQUE on incident id) and a seeded-inactive `*/15 * * * *` cron row. Incident-list route
120
171
  left PLACEHOLDER pending confirmation. (jcardinal)
172
+ - 2026-08-12 — Built: auto-close of ClickUp tasks when the OneUptime incident resolves (the
173
+ sync was create-only). Added `dtClosed` marker to the ledger
174
+ (`dbchanges2/Team/2026-08-12a`), `_Component_Api_Oneuptime::getIncidentResolvedState()` +
175
+ PLACEHOLDER per-incident route, and a close-first pass in `SyncIncidents()` (resolves each
176
+ list's `type=='closed'` status name, PUTs it, stamps `dtClosed` only on success, throttled
177
+ 120ms; return string now carries `closed`/`closeFailed`). Discovered: apparent duplicate
178
+ same-title tickets were two distinct incidents — dedup is per-incident-id, not per-title; the
179
+ real gap was the missing close path. (jcardinal)
121
180
  </content>
122
181
  </invoke>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.558",
3
+ "version": "1.0.560",
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",