toga-ai 1.0.184 → 1.0.186

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.
@@ -4,6 +4,7 @@
4
4
  |-----|---------|-------|
5
5
  | [TOGa Commerce (toga2-commerce / commerce2-react) Architecture](architecture.md) | `toga2-commerce` (npm package name **`commerce2-react`**, product name **TOGa Commerce**) is the customer-facing **B2B commerce storefront** of the 2.0 platform | src/main.tsx, src/App.tsx, src/routes.tsx, src/contexts/AuthContext.tsx, src/contexts/helpers/getLoginSettings.ts, src/api/axiosInstance.ts, src/stores/, src/themeConfig/ThemeContext.tsx, src/fieldsConfig/index.ts, src/hooks/useAssignClientFields.ts, vite.config.ts, package.json |
6
6
  | [Cart Notification Emails — duplicate prevention](features/cart-notification-emails.md) | On the cart "Notifications" section a user can add CC email addresses to an order. | src/pages/Cart/CartPage.tsx, src/pages/Cart/view/cartForm/CartForm.tsx, src/stores/useEmailOptionsStore.ts, src/stores/useCartSalesQuoteZu.ts, src/pages/Cart/viewModel/FIELDS/*/*/*/CARTPAGE.ts |
7
+ | [Cart Page — config-driven form architecture (current state + planned refactor)](features/cart-page-config-architecture.md) | The Cart page (`src/pages/Cart/`) is the most config-heavy page in `toga2-commerce`. | src/pages/Cart/CartPage.tsx, src/pages/Cart/view/cartForm/CartForm.tsx, src/pages/Cart/view/cartForm/CartFormSection.tsx, src/pages/Cart/view/cartForm/CartFormRenderer.tsx, src/pages/Cart/view/EditCart.tsx, src/pages/Cart/view/EditOrder.tsx, src/pages/Cart/viewModel/useEditOrderOrEditCartViewModel.ts, src/pages/Cart/viewModel/FIELDS/*/*/*/CARTPAGE.ts, src/hooks/useAssignClientFields.ts |
7
8
  | [Client Fields — per-tenant / language / role content & config](features/client-fields.md) | Almost no user-facing text, field layout, or page config is hard-coded in `toga2-commerce`. | src/fieldsConfig/index.ts, src/fieldsConfig/getClientLoginFields.ts, src/fieldsConfig/clientFields/COMPASS.json, src/fieldsConfig/clientFields/COMPASSCANADA.json, src/fieldsConfig/clientFields/QUAD.json, src/hooks/useAssignClientFields.ts, src/hooks/useDynamicConditionalFieldOptions.ts, src/stores/useFieldsStore.ts, src/components/BaseDetailField/BaseDetailField.tsx |
8
9
  | [Multi-Tenant Resolution & Theming](features/multi-tenant-theming.md) | `toga2-commerce` serves multiple clients from one codebase. | src/themeConfig/themes.json, src/themeConfig/ThemeContext.tsx, src/themeConfig/types.ts, src/components/ThemeSwitcher/ThemeSwitcher.tsx, src/components/AuthLayout/AuthLayout.tsx, src/api/axiosInstance.ts, src/contexts/AuthContext.tsx, tailwind.config.js |
9
10
  | [AWS Amplify Build & Deploy (non-prod environments)](workflows/amplify-build-and-deploy.md) | How `toga2-commerce` (React + Vite, "commerce2-react") builds and deploys on **AWS Amplify**. | toga2-commerce/amplify.yml, toga2-commerce/.gitattributes, toga2-commerce/package.json, toga2-commerce/.github/workflows/sync-stage-environments.yml |
@@ -14,7 +14,9 @@ files:
14
14
  - src/stores/useEmailOptionsStore.ts
15
15
  - src/stores/useCartSalesQuoteZu.ts
16
16
  - src/pages/Cart/viewModel/FIELDS/*/*/*/CARTPAGE.ts
17
- related: []
17
+ related:
18
+ - 2.0/apps/toga2-commerce/features/cart-page-config-architecture.md
19
+ - 2.0/apps/toga2-commerce/features/client-fields.md
18
20
  ---
19
21
 
20
22
  ## Summary
@@ -0,0 +1,155 @@
1
+ ---
2
+ title: Cart Page — config-driven form architecture (current state + planned refactor)
3
+ framework: "2.0"
4
+ repo: toga2-commerce
5
+ project: TOGa Commerce
6
+ client: shared
7
+ type: feature
8
+ status: active
9
+ updated: 2026-06-24
10
+ owners: ["apeterson"]
11
+ files:
12
+ - src/pages/Cart/CartPage.tsx
13
+ - src/pages/Cart/view/cartForm/CartForm.tsx
14
+ - src/pages/Cart/view/cartForm/CartFormSection.tsx
15
+ - src/pages/Cart/view/cartForm/CartFormRenderer.tsx
16
+ - src/pages/Cart/view/EditCart.tsx
17
+ - src/pages/Cart/view/EditOrder.tsx
18
+ - src/pages/Cart/viewModel/useEditOrderOrEditCartViewModel.ts
19
+ - src/pages/Cart/viewModel/FIELDS/*/*/*/CARTPAGE.ts
20
+ - src/hooks/useAssignClientFields.ts
21
+ related:
22
+ - 2.0/apps/toga2-commerce/architecture.md
23
+ - 2.0/apps/toga2-commerce/features/client-fields.md
24
+ - 2.0/apps/toga2-commerce/features/cart-notification-emails.md
25
+ - 2.0/apps/toga25-supply/features/client-configurable-fields.md
26
+ ---
27
+
28
+ ## Summary
29
+
30
+ The Cart page (`src/pages/Cart/`) is the most config-heavy page in `toga2-commerce`. As of
31
+ `_production` it is **config-driven at the field level but hardcoded at the section level**, and it
32
+ renders the cart through **two near-duplicate dual-mode forms** (cart-checkout vs edit-order). A
33
+ config-driven refactor is **planned but not yet implemented** (see *Planned refactor* below). This
34
+ doc records both the current architecture and the agreed target so the work can be picked up without
35
+ re-deriving it. The cart consumes the per-tenant `CARTPAGE.ts` config resolved through
36
+ `useAssignClientFields` — see [client-fields](client-fields.md) for that resolution model.
37
+
38
+ ## Current architecture (`_production`)
39
+
40
+ ```
41
+ CartPage.tsx (~1052 lines) owns useForm(), watched values, all handlers; picks EditCart vs EditOrder
42
+ ├─ EditCart.tsx (~596) cart-checkout mode: sync useEffects + renders <CartForm> (~30 props)
43
+ └─ EditOrder.tsx (~665) edit-order mode: near-duplicate sync useEffects + renders <CartForm>
44
+ └─ CartForm.tsx (~1074) HARDCODED section layout (summary / orderFor / shippingAddress /
45
+ costCenter / notifications / confirm) as JSX; calls CartFormSection per section
46
+ └─ CartFormSection.tsx (~296) ALREADY config-driven: renders inputs from a `fields` prop,
47
+ honoring visibleWhen / inputButtons / labelButton / isEditable / inputType
48
+ ```
49
+
50
+ `useEditOrderOrEditCartViewModel.ts` is a dispatcher hook that returns one view-model or the other
51
+ based on `inEditMode`. `CartFormRenderer.tsx` exists only on the `implement-config` spike branch
52
+ (reads `section.fields`); it is **not on `_production`**.
53
+
54
+ ### What is already config-driven (keep it)
55
+ - **Field-level rendering** — `CartFormSection` maps over a section's input array and interprets
56
+ `visibleWhen`, `inputButtons`, `labelButton`, `isEditable`/`isVisible`, `inputType`
57
+ (advancedSelect / BaseInput / display variants). This mirrors toga2.5's `renderField`/`BaseInput`.
58
+ - **Per-tenant `CARTPAGE.ts`** per client/language/role, resolved via `useAssignClientFields`
59
+ (React-Query cached) — the same resolution model as toga2.5's `useClientFields`.
60
+
61
+ ### What is NOT config-driven / is duplicated (the refactor target)
62
+ - **Section layout is code, not config** — `CartForm.tsx` (~1074 lines) hardcodes which sections
63
+ exist and their order in JSX.
64
+ - **The form is built twice** — `EditCart` and `EditOrder` share ~80% of their logic but are
65
+ separate files, each holding its own copy of the mount / orderFor-cascade / form-sync effects.
66
+ - **Prop drilling** — `CartPage → EditCart/EditOrder → CartForm → CartFormSection` passes ~30 props
67
+ by hand.
68
+
69
+ ## Gotchas (current state — all pre-existing on `_production`)
70
+
71
+ - **Config-key naming drift.** `CARTPAGE.ts` uses `inputs:` for the `orderFor` section but
72
+ `formFields:` for `shippingAddress` / `costCenter` / `notifications`, while `CartFormSection`
73
+ reads a `fields` prop. Three names for one concept; `CartForm` hand-maps each section's array into
74
+ the `fields` prop. This pre-existing drift is what tripped the prior spike. (Target: rename all to
75
+ a single `fields` key.)
76
+ - **`useForm` is seeded with a hardcoded union of every client's fields.** `CartPage.tsx`
77
+ (~lines 230–258) calls `useForm` with one static `defaultValues` object that is the **union** of
78
+ all clients' fields, with the **COMPASS-only magic shipping-method UUID `087a9d0b-…` baked in**.
79
+ Two mode-change `reset()` effects (~lines 267, 282) re-seed from that same union shape. This is
80
+ wrong for per-client field sets: it registers fields a client never renders and bakes one client's
81
+ default into all of them, and risks RHF controlled/uncontrolled warnings.
82
+ - **Client differences are hardcoded, not config.** Examples observed: COMPASS renders
83
+ `DuplicateKitGuardrail`, QUAD does not; COMPASS seeds the cart form (cost center / manager / emails)
84
+ from the order-for user, QUAD does not. These are per-client conditionals in cart code today.
85
+ - `useAssignClientFields.ts` returns `FIELDS?.[clientName]?.[language]?.[role]` with no transform —
86
+ there is no fallback (a missing combo renders empty). See [client-fields](client-fields.md).
87
+
88
+ ## Planned refactor (DECIDED 2026-06-24 — plan only, not implemented)
89
+
90
+ **Approach (Route B): rebuild the config-driven cart from the working `_production` branch**, using
91
+ the broken `implement-config` spike branch only as a *reference blueprint* (its target-shaped files
92
+ — `CartFormContext`, `CartFormRenderer`, `useCartFormViewModel`, `useCartFormSync`, standalone
93
+ sections — are architecturally sound but mid-rename and non-functional; lift code where correct,
94
+ do not finish that branch). Rationale: the spike's −3,940-line deletion silently dropped real
95
+ behavior (email seeding, duplicate-email validation, edit-order cost-center/address). The risk is
96
+ **behavior parity, not architecture**, so migrate in verifiable slices against the running
97
+ `_production` app as the oracle. Target pattern = toga2.5 (`toga25-supply`): one form, section
98
+ layout from config, state via context, consolidated sync effects.
99
+
100
+ Plan document (in the toga2-commerce repo, not in the KB):
101
+ `docs/reviews/implement-config-cart-refactor-PLAN.md`.
102
+
103
+ Phases: **P0** pin a behavior oracle + inventory hardcoded client branches · **P1** behavior-neutral
104
+ config-key rename (`inputs`/`formFields` → single `fields`) · **P2** `CartFormContext` to kill
105
+ prop-drilling + config-derived form defaults · **P3** consolidate the dual view-models and the three
106
+ sync effects into `useCartFormViewModel` + `useCartFormSync`, branching on `inEditMode` once · **P4**
107
+ drive section layout from config (`type` + dispatch map with explicit fallback) + capability flags,
108
+ then delete `CartForm`/`EditCart`/`EditOrder` · **P5** hydration memo + standards sweep (magic UUID
109
+ removed from code, lives in config).
110
+
111
+ ### Key principle established — client behavior comes from config, never `if (client === ...)`
112
+
113
+ A new client must be onboarded by **editing `CARTPAGE.ts`**, not by adding `clientSlug` branches to
114
+ cart code. Two flavors, modeled exactly on toga2.5's SalesOrder approval pattern (config carries
115
+ `approvalActionStatuses` + `approveButtonRule:{type:"requireStepTwoAssigned"}`; a pure
116
+ `evaluateApproveRule(rule, ctx)` switch over a discriminated union in `useApprovalModalViewModel.tsx`
117
+ interprets it — the component never knows it is COMPASS):
118
+
119
+ 1. **Capability flags** — pure on/off presence, no runtime data:
120
+ `capabilities.duplicateKitGuardrail.isEnabled` (COMPASS true, QUAD omits). The renderer reads the
121
+ flag to decide whether to mount `DuplicateKitGuardrail`.
122
+ 2. **Named rules** — conditional logic depending on runtime data, declared as `{ type }` and
123
+ evaluated generically: e.g. `orderForSeedRules: [{type:"seedCostCenterFromUser"}, …]` iterated by
124
+ an `applySeedRule` evaluator in `useCartFormSync` (mirrors `evaluateApproveRule`). QUAD omits the
125
+ rules → seeds nothing, no branch.
126
+
127
+ ### Config-derived form defaults (cart-specific extension of the toga2.5 pattern)
128
+
129
+ The config already enumerates exactly the fields a client renders, so derive `useForm`
130
+ `defaultValues` from it via a `buildCartDefaultValues(fields, ctx)` builder with three sources:
131
+ - per-field `defaultValue` in config (where the COMPASS shipping UUID **moves to**; QUAD omits it);
132
+ - type-derived empties (`text`/`textArea`→`""`, `checkbox`→`false`,
133
+ `select`/`advancedSelect`→`{uuid:"",name:""}`);
134
+ - dynamic `defaultValueRules` reusing the same `{type}` rule shape
135
+ (e.g. `currentUserWhenNotEditable`, `fromSalesOrderErpEntity`).
136
+
137
+ The two mode-change `reset()` effects call the **same builder** so defaults and rendered inputs can
138
+ never drift. Noted honestly: toga2.5 uses a *static* `emptyItemDefaults` because its item-create form
139
+ is a single shape; the cart is genuinely multi-shape per client, so config-derived defaults is a
140
+ **cart-specific extension** of toga2.5's philosophy, not a literal copy.
141
+
142
+ > The general "no `if (client === ...)` — drive client behavior from config via capability flags and
143
+ > named rules evaluated by a generic evaluator" rule, applied across 2.0 React frontends beyond just
144
+ > the cart, is being proposed as a team **standard** separately (senior-owned / pending approval).
145
+
146
+ ## Change history
147
+ - 2026-06-24 — Initial: documented the current `_production` Cart architecture (field-level config
148
+ via `CartFormSection`, hardcoded section layout in `CartForm`, dual `EditCart`/`EditOrder` forms +
149
+ dispatcher VM, prop-drilling), its gotchas (config-key drift `inputs`/`formFields`/`fields`,
150
+ hardcoded union `defaultValues` with the COMPASS magic shipping UUID, hardcoded per-client
151
+ branches), and the DECIDED config-driven refactor approach (rebuild from `_production`, branch as
152
+ blueprint; capability flags + named rules à la toga2.5 approval; config-derived
153
+ `buildCartDefaultValues`). Plan only — not implemented (apeterson)
154
+ </content>
155
+ </invoke>
@@ -22,6 +22,7 @@ related:
22
22
  - 2.0/apps/toga2-commerce/architecture.md
23
23
  - 2.0/apps/toga2-commerce/features/multi-tenant-theming.md
24
24
  - 2.0/apps/toga2-commerce/features/cart-notification-emails.md
25
+ - 2.0/apps/toga2-commerce/features/cart-page-config-architecture.md
25
26
  ---
26
27
 
27
28
  ## Summary
@@ -12,5 +12,6 @@
12
12
  | [NetSuite → TOGA Opportunity Sync (API Message Queue + worker2 webhook)](features/netsuite-opportunity-sync.md) | Outbound sync from NetSuite to TOGA for the record types the Forecast2 importer pulls (opportunities first; sales/items/etc. | worker2/Worker/Netsuite.php, worker2/Worker/Netsuite/Opportunity.php, worker2/Controller/Index.php, _underscore/Worker.php, test/@dave/NetSuite/api-message-queue/lib_amq_queue.js, test/@dave/NetSuite/api-message-queue/ue_api_msg_queue_enqueue.js, test/@dave/NetSuite/api-message-queue/ue_amq_drain.js, test/@dave/NetSuite/api-message-queue/ss_amq_drain.js, test/@dave/NetSuite/api-message-queue/DEPLOY_RUNBOOK.md, test/@dave/clickup/backfill_opportunity_numbers.php, test/@dave/clickup/probe_opportunity_fields.php, test/@dave/probe_clickup_desc_match.php, worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php |
13
13
  | [NetSuite → Forecast Open-Orders Sync (salesOrder webhook → OpenOrderItems)](features/netsuite-salesorder-open-orders-sync.md) | Webhook-driven, single-record port of the legacy open-orders importer (TRUE-79142). | worker2/Worker/Netsuite/SalesOrder.php, worker2/Worker/Netsuite.php, test/@dave/probe_salesorder_rest_shape.php, test/@dave/probe_open_order_lines.php, test/@dave/check_so_status.php, test/@dave/check_so_history.php, test/@dave/probe_so_rest_lines.php, test/@dave/probe_missing_oo_timing.php, test/@dave/probe_missing_oo_createdby.php, test/@dave/probe_drift_so_dates.php, worker/crons/toga2/forecast2/import_open_orders.php, worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php |
14
14
  | [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 |
15
+ | [Team Sprint Management & Reporting](features/team-sprint-management.md) | `_Worker_Team_Sprint` (file `Worker/Team/Sprint.php`) is the engine behind TOGA's internal **development-sprint process and reporting**. | worker2/Worker/Team/Sprint.php |
15
16
  | [Teams Meeting Transcript Export](features/teams-transcript-export.md) | `_Worker_Team_Transcripts` (action `Team/Transcripts/Export`) polls Microsoft Graph for Teams meeting transcripts produced by a set of organizers, classifies ea | worker2/Worker/Team/Transcripts.php, worker2/Config/production.ini, worker2/Database/TeamsTranscriptExports.sql, dbchanges2/Core/2026-06-18a - Teams Transcript Export schedule.sql |
16
17
  | [WJE Freshservice Sync (worker2)](features/wje-freshservice-sync.md) | WJE ("WJE IT", helpdesk `wje.freshservice.com`) is a **Freshservice**-based help-desk client whose tickets, contacts, assets, groups, categories, and canned res | worker2/Worker/Wje.php, _underscore/Component/Api/Wje/Wje.php, _underscore/Model/Wje/Ticket.php, _underscore/Model/Wje/TicketNote.php, _underscore/Model/Wje/Contact.php, _underscore/Model/Wje/Unit.php, _underscore/Model/Wje/TicketTeam.php, _underscore/Model/Wje/TicketCategory.php, _underscore/Model/Wje/AssetType.php, _underscore/Model/Wje/PredefinedReply.php, library/app/api/wje.php, worker/crons/toga2/wje/import_supporting_records.php, worker/crons/toga2/wje/sync_togasupply_wje.php, worker/crons/notifications/reports/wje/wje_common.php, library/app/systemmonitor/wje.php, dbchanges2/Client_Wje/2024-10-04 - WjeOnboarding.sql |
@@ -0,0 +1,203 @@
1
+ ---
2
+ title: Team Sprint Management & Reporting
3
+ framework: "2.0"
4
+ repo: worker2
5
+ project: Worker
6
+ client: shared
7
+ type: feature
8
+ status: active
9
+ updated: 2026-06-24
10
+ owners: ["jcardinal"]
11
+ files:
12
+ - worker2/Worker/Team/Sprint.php
13
+ related:
14
+ - ../architecture.md
15
+ - ./creating-worker-actions.md
16
+ - ./clickup-work-type-automation.md
17
+ - ./clickup-project-routing.md
18
+ ---
19
+
20
+ ## Summary
21
+
22
+ `_Worker_Team_Sprint` (file `Worker/Team/Sprint.php`) is the engine behind TOGA's internal
23
+ **development-sprint process and reporting**. It is an `abstract` worker class of `static`
24
+ methods — each public method is a dispatchable worker action (`Team/Sprint/<Method>`, e.g.
25
+ `Team/Sprint/SprintEnd`) invoked on a schedule via `_Worker::runTask()`.
26
+
27
+ The feature mirrors the team's two-week (`SPRINT_LENGTH_DAYS = 14`) ClickUp-based sprint
28
+ ceremony into the `DB_TEAM` database, then produces:
29
+
30
+ - **Warning emails** before sprint-end capture and before sprint lock.
31
+ - A **Sprint Lock** snapshot that freezes each task's committed/planned state ("at lock").
32
+ - **Daily** leadership progress reports during the sprint.
33
+ - A comprehensive **Sprint End** run that scores every developer across four metrics,
34
+ emails per-developer report workbooks and a leadership summary workbook.
35
+ - **AI-generated Release Notes** (via TOGa IQ) emailed to leadership.
36
+
37
+ ClickUp is the source of truth for tasks/assignees; `DB_TEAM` is the durable store of
38
+ sprint history and computed metrics; Excel (`PhpOffice\PhpSpreadsheet`) is the report
39
+ format; `_Email` delivers everything.
40
+
41
+ ## Key files / entry points
42
+
43
+ - `Worker/Team/Sprint.php` — the entire feature (one abstract class, static methods).
44
+ - `initialize()` — registers the `Logs_True` DB connection used for ClickUp API logging.
45
+ - ClickUp config lives in class constants: `CLICKUP_TEAM_ID`, `CLICKUP_DEV_SPRINTS_FOLDER_ID`
46
+ (the folder whose child lists are the sprints), and the API access token.
47
+ - AI config constants: `AI_API_ENDPOINT` (TOGa IQ `…/api/ai/generate`), `AI_MODEL_ID`
48
+ (`bedrock/us.anthropic.claude-haiku-4-5-…`), temperature, max-tokens, timeout.
49
+ - Report distribution: `SEND_LEADERSHIP_REPORTS_TO` (leadership email list);
50
+ `SEND_EMAILS_FROM` / `SEND_EMAILS_FROM_NAME`.
51
+
52
+ > **Dispatch reminder (2.0 worker contract).** Action `Team/Sprint/SprintEnd` routes to
53
+ > `_Worker_Team_Sprint::SprintEnd`. New actions are only reachable once registered as a
54
+ > `CronJobs` row (or webhook route). See [creating-worker-actions](./creating-worker-actions.md).
55
+
56
+ ## The sprint lifecycle (and which action runs when)
57
+
58
+ A sprint runs ~14 days. Across the cycle these actions fire (scheduled as crons):
59
+
60
+ 1. **Sprint launch day, 8:30 AM** — `SendSprintEndWarnings` and `SendSprintLockWarnings`
61
+ email every developer who had a task in the latest sprint, reminding them to (a) finalize
62
+ the *previous* sprint's task data before the 9:00 AM End capture, and (b) get their *new*
63
+ sprint commitments ready to lock before the 9:45 AM launch ceremony.
64
+ 2. **~9:00 AM** — `SprintEnd` captures and scores the just-finished sprint, emails reports,
65
+ and (optionally) sends release notes.
66
+ 3. **~9:45 AM (after launch ceremony)** — `SprintLock` freezes the new sprint's plan.
67
+ 4. **Daily during the sprint** — `SprintDaily` emails leadership a progress dashboard.
68
+
69
+ Most methods accept an optional `$sprint` number; when omitted they resolve the relevant
70
+ sprint by date via `getSprint()` (which returns the sprint whose `dateStart…dateEnd` range
71
+ contains today, or, for end-of-sprint, the previous sprint).
72
+
73
+ ## Public actions (entry points)
74
+
75
+ ### `SendSprintEndWarnings()` / `SendSprintLockWarnings()`
76
+ Look up the latest `Sprints` row, find every distinct developer with a task in that sprint
77
+ (`Developers ⋈ Tasks_Developers ⋈ Tasks`), and email each a templated reminder. End-warning =
78
+ "clean up your data before the 9:00 AM End capture"; Lock-warning = "have your commitments
79
+ ready before the 9:45 AM lock; work added after lock is treated as unplanned/interrupt work."
80
+ Both send via the `Notification/Email/Send` worker action.
81
+
82
+ ### `SprintLock($sprint = null, $captureSprintLock = true, $generateSprintLockReport = true, $allowUnplannedTasks = false)`
83
+ Freezes the sprint's plan at launch time. Optionally re-captures ClickUp data first, then for
84
+ each task records the **"at lock" baseline** — `workTypeAtLock`, `sprintPointsAtLock`,
85
+ `statusAtLock` — and initializes the per-sprint tracking counters (rework events, unjustified
86
+ status/work-effort changes, in-progress time). These at-lock values are what later metrics
87
+ measure *against*. If the target sprint list cannot be found in ClickUp it emails a leadership
88
+ alert. With `$generateSprintLockReport`, emits an Excel lock report of committed/conditional/
89
+ unplanned/stretch points and tasks. When `$allowUnplannedTasks` is false, tasks appearing
90
+ after lock with an UNPLANNED work type are flagged.
91
+
92
+ ### `SprintDaily($sprint = null, …)`
93
+ In-sprint **leadership dashboard**, read-only with respect to metrics. Optionally re-captures
94
+ ClickUp data, then builds a multi-sheet workbook:
95
+ - **Dashboard** — sprint progress by *time elapsed* vs. *points completed* (and the
96
+ difference, i.e. ahead/behind pace); counts of tasks in attention statuses (Roadblocked,
97
+ On Hold, Awaiting Client, the various Review states); points/tasks by work type.
98
+ - **Team Summary** — per-developer committed/conditional/unplanned/stretch completion plus
99
+ N-sprint historical averages.
100
+ - **Stalled Task Detail** — tasks in Roadblocked / Awaiting Client / On Hold with the reason.
101
+ Emailed to the leadership list as `TeamSprintDailyReport_Sprint{N}_{date}.xlsx`.
102
+
103
+ ### `SprintEnd($sprint = null, …)`
104
+ The largest method and the heart of the feature. Phases:
105
+ 1. **Resolve** the target (previous) sprint and its developers; optionally call
106
+ `CaptureSprintEnd()` to sync the final ClickUp state into `DB_TEAM`.
107
+ 2. **Score every developer** across the four metrics below.
108
+ 3. **Per-developer report workbook** (Dashboard, Team Summary, Sprint History, Task Detail,
109
+ and a Score-Trends chart over the last `SPRINTS_BACK_FOR_AVERAGES` sprints), emailed to
110
+ each developer.
111
+ 4. **Leadership summary workbook** (team-wide dashboard, all-developer summary with a
112
+ *recommended commitment* for next sprint, and stalled-task detail), emailed to leadership.
113
+ 5. Optionally call the release-notes generator.
114
+
115
+ ### `SendReleaseNotes($sprint = null)`
116
+ Pulls every **completed** task in the sprint (`statusNow IN (…DONE)`) into a CSV of
117
+ `workType, taskType, stakeholders, applications, sprintPoints, title`, then POSTs it to the
118
+ TOGa IQ AI endpoint with a detailed system prompt that rewrites technical task titles into
119
+ business-friendly, stakeholder-grouped HTML release notes. The returned HTML (code fences
120
+ stripped) is emailed to the leadership list as "Sprint N Release Notes".
121
+
122
+ ## The scoring model (Sprint End)
123
+
124
+ Each developer receives four 0–100 sub-scores and a weighted **Final Score**:
125
+
126
+ | Metric | What it measures | Penalized by |
127
+ |--------|------------------|--------------|
128
+ | **Reliability** | Did they deliver what they committed to | Committed points/tasks not completed |
129
+ | **Quality** | Did the work hold up | Rework events |
130
+ | **Hygiene** | Was ClickUp data kept clean | Missing "Task Knowledge" / "Work Knowledge" custom fields (`hygieneMisses`) |
131
+ | **Integrity** | Were estimates honest | Unjustified work-effort (point/estimate) changes |
132
+
133
+ Key rules driven by the class constants:
134
+ - **Team-relative zero-out:** for Quality, Hygiene, and Integrity, if a developer's negative
135
+ rate is more than `FACTOR_OF_TEAM_AVERAGE_*_FOR_ZERO_SCORE` (= 4×) the team average for that
136
+ metric, that sub-score floors at 0. Scores are graded against the team, not an absolute bar.
137
+ - **Weighting:** `Final = 0.7·Reliability + 0.1·Quality + 0.1·Hygiene + 0.1·Integrity`
138
+ (`FINAL_SCORE_WEIGHT_*`). Reliability dominates.
139
+ - **Caps:** a low sub-score caps the final regardless of the others —
140
+ Reliability < 80 caps final at 80; Quality/Hygiene/Integrity < 90 cap final at 90
141
+ (`FINAL_SCORE_CAP_*`).
142
+ - **Recommended next commitment:** `PERCENT_OF_PREVIOUS_COMMITTED_TASK_AVERAGE_FOR_RECOMMENDED_COMMITMENT`
143
+ (= 0.9) × the developer's recent committed-points average, over the last 4 sprints.
144
+ - `scoreHeatMap($score)` interpolates a red→green cell color for the spreadsheets (stops
145
+ roughly at 60/70/80/90/100).
146
+
147
+ ## Data model (`DB_TEAM`)
148
+
149
+ All sprint state lives in the Team database (`_underscore::DB_TEAM`). Core tables:
150
+
151
+ - **`Sprints`** — one row per sprint: `id`, `sprint` (number), `dateStart`, `dateEnd`.
152
+ - **`Developers`** — `id`, `name`, `emailAddress`, ClickUp user identifier.
153
+ - **`Tasks`** — one row per sprint task, keyed to ClickUp. Carries the lifecycle snapshots
154
+ (`workTypeAtLock` vs `workTypeNow`, `sprintPointsAtLock` vs `sprintPointsNow`,
155
+ `statusAtLock` / `statusNow` / `statusAtEnd`), classification fields (`taskType`,
156
+ `stakeholders`, `applications`, `link`), and the computed metric counters
157
+ (`reworkEvents`, `hygieneMisses`, `unjustifiedStatusChanges`,
158
+ `unjustifiedWorkEffortChanges`, `timeInProgressThisSprint`). Task statuses use the
159
+ `_Model_Team_Task::STATUS_*` constants (e.g. `STATUS_IN__DONE`).
160
+ - **`Tasks_Developers`** — task ↔ developer assignment join table.
161
+
162
+ `CaptureSprintEnd()` and `CaptureSprintDaily()` are the private sync routines that walk the
163
+ ClickUp sprint list (paginated, `include_timl=true`), upsert `Sprints`/`Developers`/`Tasks`/
164
+ `Tasks_Developers`, count hygiene misses, and archive tasks no longer present. They differ
165
+ mainly in which snapshot columns they write (End → `statusAtEnd`; Daily → `statusNow`, and it
166
+ backfills `workTypeAtLock` the first time a task is seen).
167
+
168
+ ## External integrations
169
+
170
+ - **ClickUp API v2** — the dev-sprints **folder** (`CLICKUP_DEV_SPRINTS_FOLDER_ID`) holds one
171
+ list per sprint, named like `Sprint NN (M/D/YY - M/D/YY)`. Tasks are fetched page-by-page;
172
+ custom fields read include Work Type (committed/conditional/unplanned/stretch), Task Type,
173
+ Stakeholders, Application, and the two Knowledge fields used for hygiene scoring.
174
+ - **TOGa IQ / AI** (`api.togaiq.com/api/ai/generate`) — release-notes generation only.
175
+ - **Email** (`_Email`) — all reports and warnings; per-developer reports go to the developer,
176
+ everything else to `SEND_LEADERSHIP_REPORTS_TO`.
177
+ - **Excel** (`PhpOffice\PhpSpreadsheet`) — every report is an `.xlsx` attachment.
178
+
179
+ ## Gotchas
180
+
181
+ - **Single source file, huge methods.** `SprintEnd` alone is ~2,700 lines; `SprintLock` and
182
+ `SprintDaily` are each ~1,000. Almost all report layout/formatting is inline. Treat the
183
+ method boundaries (and the constants block at the top) as the map.
184
+ - **At-lock baseline is load-bearing.** Reliability/Integrity compare *now* vs *at lock*. If
185
+ `SprintLock` did not run (or ran without capture), the `*AtLock` columns are empty and the
186
+ end-of-sprint scores are meaningless. Lock must happen at launch.
187
+ - **Sprint list naming is a contract.** Capture matches the ClickUp sprint list by its
188
+ `Sprint NN (dates)` name / date range. A mis-named list is silently not found → leadership
189
+ gets a "sprint not found" alert instead of a report.
190
+ - **Hardcoded credentials.** ⚠ The ClickUp access token and the TOGa IQ AI API key are
191
+ currently **hardcoded as class constants** in `Sprint.php` rather than read from config.
192
+ This is a known security debt (secrets in source) — they should move to `Config/*.ini`
193
+ and the committed values should be rotated. See team coding/security standards.
194
+ - **Worker contract.** Like all 2.0 workers this must return cleanly and commit writes via
195
+ the `_Db`/transaction layer to be durable — uncommitted captures can be silently dropped.
196
+
197
+ ## Change history
198
+
199
+ - 2026-06-24 — Initial documentation of `_Worker_Team_Sprint` (`Worker/Team/Sprint.php`):
200
+ the sprint lifecycle (warnings → lock → daily → end → release notes), the four-metric
201
+ developer scoring model (reliability/quality/hygiene/integrity with weights, team-relative
202
+ zero-out, and final-score caps), the `DB_TEAM` data model, and the ClickUp / TOGa IQ / email
203
+ integrations. Flagged hardcoded ClickUp + AI credentials as security debt.
@@ -16,7 +16,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
16
16
  ## 2.0 framework
17
17
 
18
18
  - **_underscore** (_Underscore) _(framework core)_ — 11 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
19
- - **worker2** (Worker) — 12 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
19
+ - **worker2** (Worker) — 13 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
20
20
  - **api2** (API) — 6 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
21
21
  - **dbchanges2** (Database Changes) _(framework core)_ — 2 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
22
22
  - **toga2-supply** (TOGa Supply) — 3 doc(s) → [2.0/apps/toga2-supply/INDEX.md](2.0/apps/toga2-supply/INDEX.md)
@@ -26,7 +26,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
26
26
  - **talos** (TOGa IQ) — 6 doc(s) → [2.0/apps/talos/INDEX.md](2.0/apps/talos/INDEX.md)
27
27
  - **voice-to-voice** (TOGa Voice) — 4 doc(s) → [2.0/apps/voice-to-voice/INDEX.md](2.0/apps/voice-to-voice/INDEX.md)
28
28
  - **ai-bdr** (AI-BDR) — 4 doc(s) → [2.0/apps/ai-bdr/INDEX.md](2.0/apps/ai-bdr/INDEX.md)
29
- - **toga2-commerce** (TOGa Commerce) — 5 doc(s) → [2.0/apps/toga2-commerce/INDEX.md](2.0/apps/toga2-commerce/INDEX.md)
29
+ - **toga2-commerce** (TOGa Commerce) — 6 doc(s) → [2.0/apps/toga2-commerce/INDEX.md](2.0/apps/toga2-commerce/INDEX.md)
30
30
  - **toga25-supply** (TOGa 2.5 Supply) — 5 doc(s) → [2.0/apps/toga25-supply/INDEX.md](2.0/apps/toga25-supply/INDEX.md)
31
31
  - **toga-blox** (TOGa Blox) — 7 doc(s) → [2.0/apps/toga-blox/INDEX.md](2.0/apps/toga-blox/INDEX.md)
32
32
 
@@ -5,6 +5,7 @@ apps:
5
5
  - _underscore
6
6
  - api2
7
7
  - toga2-supply
8
+ - toga2-commerce
8
9
  - dbchanges2
9
10
  project: _Underscore
10
11
  client: quad
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.184",
3
+ "version": "1.0.186",
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",