toga-ai 1.0.78 → 1.0.80

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,5 +4,6 @@
4
4
  |-----|---------|-------|
5
5
  | [_underscore Framework Architecture](architecture.md) | `_underscore` is the shared PHP backend framework for **all 2.0 applications**. | _underscore/_underscore.php, _underscore/Loader.php, _underscore/Framework.php, _underscore/Model.php, _underscore/Database.php, _underscore/Query.php, _underscore/Route.php, _underscore/Component.php |
6
6
  | [Carrier Shipping Labels (UPS/FedEx) & NetSuite Item Fulfillment](features/carrier-shipping-labels.md) | Backend mechanics behind TOGa Supply's Fulfill & Ship: buying a carrier label (UPS/FedEx), persisting it, and creating the NetSuite Item Fulfillment with tracki | _underscore/Model/Client/ItemFulfillment.php, _underscore/Component/Library/Carriers/Ups/Ups.php, _underscore/Trait/Netsuite/ItemFulfillment.php, _underscore/Trait/Netsuite/SalesOrder.php, _underscore/Component/Library/NetSuite/NetSuite.php, _underscore/Model/Client/TrackingNumber.php, _underscore/Model/Client/ShippingMethod.php, _underscore/Model.php, _underscore/Cloud.php |
7
+ | [Client Email Template Sending](features/email-template-sending.md) | `_Model_Client_EmailTemplate` sends a stored, client-defined email template by UUID. | _underscore/Model/Client/EmailTemplate.php, _underscore/Model/Client/EmailTemplateOutgoingEmailAddress.php, _underscore/Email.php |
7
8
  | [Recursive Item Fulfillments (upstream mirroring)](features/recursive-item-fulfillments.md) | In a multi-tier supply chain a sales order (SO) spawns a purchase order (PO) that becomes another SO downstream, and so on. | _underscore/Model/Client/ItemFulfillment.php, _underscore/Model/Client/ItemFulfillmentItem.php, _underscore/Model/Client/ItemFulfillmentItemUnit.php, _underscore/Model/Client/ItemFulfillmentPackage.php, _underscore/Model/Compass/AdvanceShippingNotice.php, dbchanges2/Core/2026-02-13 - 75601 - RecursiveItemFulfillmentCreation.sql, dbchanges2/Core/2026-06-04 - RecursiveItemFulfillmentPut.sql |
8
9
  | [Tracking-Number Bridge Migration (ASN / Item Fulfillment / Item Receipt)](features/tracking-number-bridges.md) | Shipment tracking numbers used to live as **scalar FK columns** (`trackingNumberId`, `returnTrackingNumberId`) directly on the lowest-level "unit"/"item" tables | _underscore/Model/Client/AdvanceShippingNoticeItemUnit.php, _underscore/Model/Client/AdvanceShippingNoticeItemUnits/TrackingNumber.php, _underscore/Model/Client/ItemFulfillmentItemUnits/TrackingNumber.php, _underscore/Model/Client/ItemFulfillment.php, _underscore/Model/Prudential/AdvanceShippingNotice.php, _underscore/Model/Compass/AdvanceShippingNotice.php, _underscore/Trait/Netsuite/ItemFulfillment.php, api2/Component/Api/Cxml/Cxml.php, dbchanges2/Client/2026-06-10 - TrackingNumberBridges.sql, dbchanges2/Core/2026-06-10 - TrackingNumberBridges.sql |
@@ -0,0 +1,88 @@
1
+ ---
2
+ title: Client Email Template Sending
3
+ framework: "2.0"
4
+ repo: _underscore
5
+ project: _Underscore
6
+ client: shared
7
+ type: feature
8
+ status: active
9
+ updated: 2026-06-15
10
+ owners: ["jcardinal"]
11
+ files:
12
+ - _underscore/Model/Client/EmailTemplate.php
13
+ - _underscore/Model/Client/EmailTemplateOutgoingEmailAddress.php
14
+ - _underscore/Email.php
15
+ related: []
16
+ ---
17
+
18
+ ## Summary
19
+
20
+ `_Model_Client_EmailTemplate` sends a stored, client-defined email template by UUID. It
21
+ loads the template from the client DB (`EmailTemplates` table), merges any template-stored
22
+ recipient addresses, substitutes `{variable}` placeholders in the subject and body, and
23
+ sends via `_Email`. The only client-specific input the send actually needs is the
24
+ **client identifier** (used to scope the outgoing mail via `_Email::setClientIdentifier()`).
25
+
26
+ ## Key files / entry points
27
+
28
+ - `_underscore/Model/Client/EmailTemplate.php` — the model. Three relevant methods:
29
+ - **`sendEmail(&$api, $uuid, $to, $cc, $bcc, ...$args): bool`** — the scripted-API entry
30
+ point. First param is `&$api` per the Record Script contract (see backend-php standard,
31
+ *Record Scripts*). The only thing it uses from `$api` is `$api->client->clientIdentifier`.
32
+ - **`send(string $clientIdentifier, $uuid, $to, $cc, $bcc, ...$args): bool`** — the
33
+ non-API entry point. Caller passes the client identifier directly; no `$api` object.
34
+ - **`dispatch(string $clientIdentifier, ...): bool`** (private) — the shared body both
35
+ entry points call. Holds all the real logic.
36
+ - `_Model_Client_EmailTemplateOutgoingEmailAddress` — per-template stored TO/CC/BCC
37
+ addresses (`toCcBcc` enum), merged into the caller-supplied recipients.
38
+ - `_underscore/Email.php` — `_Email` requires a non-empty `clientIdentifier` (throws
39
+ `clientIdentifier is required` otherwise) and uses it as the CloudWatch log stream name.
40
+
41
+ ## How it works
42
+
43
+ 1. Load the template by `uuid`; return `false` immediately if `!isActive`.
44
+ 2. Search `EmailTemplateOutgoingEmailAddress` for the template and append each stored
45
+ address to `$to` / `$cc` / `$bcc` by its `toCcBcc` value.
46
+ 3. Build `_Email`, set the client identifier, add recipients, set From from the template's
47
+ `sendFromEmailAddress` / `sendFromName`.
48
+ 4. Replace `{key}` placeholders in subject and body from the `$args` variadic map
49
+ (`replaceTemplateVariables`), then `send()`.
50
+
51
+ Both `sendEmail()` and `send()` are thin wrappers that forward to `dispatch()`, so the API
52
+ and non-API paths run identical code — no behavioral drift between them.
53
+
54
+ ## Data model
55
+
56
+ - `EmailTemplates` (client DB): `uuid`, `isActive`, `sendFromEmailAddress`, `sendFromName`,
57
+ `priority`, `subject`, `body`.
58
+ - `EmailTemplateOutgoingEmailAddress` (client DB): `emailTemplateId`, `emailAddress`,
59
+ `toCcBcc` (`TO`/`CC`/`BCC`).
60
+
61
+ ## Client variations
62
+
63
+ The model is shared; client-specific senders pass their own identifier. Workers use a class
64
+ constant (e.g. `self::CLIENT_IDENTIFIER`); model/interceptor code that already has an `$api`
65
+ can keep using `sendEmail($api, ...)`.
66
+
67
+ ## Gotchas / known issues
68
+
69
+ - **Use `send()` from any non-API context (workers, cron, internal code).** Before
70
+ 2026-06-15 the only entry point was `sendEmail(&$api, ...)`, so callers with no API
71
+ context faked one: `$api = (object)['client' => (object)['clientIdentifier' => …]]`.
72
+ That hack is obsolete — pass the identifier to `send()` instead.
73
+ - **`sendEmail()`'s signature is load-bearing for scripted APIs** — the Record Script engine
74
+ (`api2/Component/Api/V2/V2.php`, ~line 3594) calls the method with `api` as a named
75
+ argument, so the first param must stay `&$api`. Do not "clean it up" by removing it.
76
+ - `_Email::send()` throws if the client identifier is empty — `send('')` will fail at send
77
+ time, not at call time.
78
+
79
+ ## Change history
80
+
81
+ - 2026-06-15 — Added non-API `send(clientIdentifier, …)` entry point + private `dispatch()`;
82
+ `sendEmail(&$api, …)` kept unchanged as a wrapper for backward compatibility. Migrated the
83
+ `worker2` Compass Report and AI-BDR NetSuite callers off the fake-`$api` hack. (jcardinal)
84
+
85
+ ## Related docs
86
+
87
+ - `2.0/standards/backend-php.md` — *Record Scripts* (the `&$api` contract).
88
+ - `2.0/apps/api2/architecture.md` — scripted-API dispatch in `V2.php`.
@@ -5,7 +5,7 @@ project: _Underscore
5
5
  client: shared
6
6
  type: standard
7
7
  status: active
8
- updated: 2026-06-08
8
+ updated: 2026-06-15
9
9
  owners: [jcardinal]
10
10
  files: []
11
11
  related:
@@ -425,6 +425,13 @@ if ($user->load()) { /* found */ }
425
425
 
426
426
  * Use tabs for indentation, not spaces.
427
427
 
428
+ #### **Line Endings**
429
+
430
+ * The 2.0 repos have **no `.gitattributes`** and contain a **mix of LF and CRLF files** — there is no single repo-wide line ending. Core files (e.g. `Model.php`, `Loader.php`, `Email.php`) are CRLF; many others (e.g. `Model/Client/EmailTemplate.php`) are LF.
431
+ * **Preserve a file's existing line ending when you edit it.** Do not let an editor or tool silently convert the whole file (a common LF→CRLF flip on Windows).
432
+ * A whole-file ending flip turns a one-line change into a full-file rewrite in `git diff`, burying the real change, breaking `git blame`, and inviting merge conflicts.
433
+ * **Check before committing:** if `git diff --stat` shows a tiny edit as a near-total rewrite, the endings flipped. Restore them — e.g. for an LF file: `tr -d '\r' < file > file.tmp && mv file.tmp file` — then re-stage.
434
+
428
435
  #### **Braces**
429
436
 
430
437
  * Opening braces for classes, methods and control structures (`if`, `else`, `for`, `while`, etc.) should go on the same line.
@@ -11,7 +11,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
11
11
 
12
12
  ## 2.0 framework
13
13
 
14
- - **_underscore** (_Underscore) _(framework core)_ — 5 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
14
+ - **_underscore** (_Underscore) _(framework core)_ — 6 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
15
15
  - **worker2** (Worker) — 6 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
16
16
  - **api2** (API) — 1 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
17
17
  - **dbchanges2** (Database Changes) _(framework core)_ — 1 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.78",
3
+ "version": "1.0.80",
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",
@@ -5,16 +5,36 @@ description: Start-of-session context loader for TOGA Technology projects. Run t
5
5
 
6
6
  # Kickoff — prime a coding session from the team knowledge base
7
7
 
8
+ > ## 🛑 STOP — THIS IS A BLOCKING GATE. READ BEFORE DOING ANYTHING ELSE.
9
+ >
10
+ > When `/kickoff` is invoked you **MUST** complete Steps 0–6 of this skill **before**
11
+ > any other tool call — no `Read`, `Grep`, `Glob`, `Edit`, `Write`, `Bash`, no agent
12
+ > spawn, no investigation, no answering the developer's question. Priming comes first,
13
+ > **always**.
14
+ >
15
+ > **Trailing text after `/kickoff` is NEVER a reason to skip priming.** A long, detailed
16
+ > paragraph describing a specific task (file paths, line numbers, a design question) is
17
+ > *still just the Step 2 task description* — it is input to the interview, **not**
18
+ > permission to start working. The more detailed and actionable the request looks, the
19
+ > more tempting it is to dive in — **resist that.** If you find yourself about to open a
20
+ > file the developer named before you have loaded the knowledge base, you are violating
21
+ > this gate. Stop and run Steps 0–6 first.
22
+ >
23
+ > Only after Step 5's "primed and ready" summary (and Step 6's plan, for non-trivial work)
24
+ > may you touch the task itself.
25
+
8
26
  ## Arguments — text passed after `/kickoff` never skips any step
9
27
 
10
- `/kickoff` may be invoked with trailing text (e.g. `/kickoff worker2 backend fix for Compass`).
11
- That text is the developer's description of today's work it is **not** permission to
12
- shortcut the flow.
28
+ `/kickoff` may be invoked with trailing text (e.g. `/kickoff worker2 backend fix for Compass`),
29
+ including a long, specific paragraph naming exact files, line numbers, and a concrete
30
+ change. That text is the developer's description of today's work — it is **not** permission
31
+ to shortcut the flow, no matter how actionable it looks.
13
32
 
14
33
  - **Step 0 (auto-update check) ALWAYS runs first**, with or without arguments.
15
34
  - Use the argument text to **pre-fill answers** to the Step 2 interview (framework, layer,
16
35
  repo, client, task). Only ask about whatever is still missing or ambiguous.
17
- - Never treat the argument as an instruction to start coding before Steps 0–6 complete.
36
+ - Never treat the argument as an instruction to start coding or even to start *reading
37
+ the named files* — before Steps 0–6 complete. Investigation IS work; it waits for priming.
18
38
 
19
39
  ## Step 0 — Auto-update check (runs before anything else, even with arguments)
20
40